feat: selectable pairing-notify provider (discord optional, add fabric)

Pairing-code delivery was hardwired to Discord DM (notifyBotToken +
adminUserId required). Make the provider config-selectable.

- core/config.ts: add notifyProvider ("discord"|"fabric", default
  "discord" for back-compat); discord fields required only for discord;
  add fabric block (centerApiBase/apiKey/guildNodeId/channelId) required
  only for fabric
- notifications/types.ts: neutral PairingNotificationService interface
  (DiscordNotificationService kept as back-compat alias)
- notifications/fabric.ts: post the pairing message to a Fabric channel
  (agent/login -> guild token -> POST messages); self-contained, no
  Fabric plugin dependency
- notifications/factory.ts: select provider from config
- core/runtime.ts: wire via factory
- openclaw.plugin.json: notifyProvider enum + fabric object; drop
  notifyBotToken/adminUserId from required (conditional in code)
- tests: fabric notifier + provider-selection config (80 passing)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
h z
2026-05-19 15:59:02 +01:00
parent b696c6a0af
commit 85034f5de0
10 changed files with 412 additions and 29 deletions

View File

@@ -42,10 +42,8 @@ import { verifySignature } from "../../../Yonexus.Protocol/src/crypto.js";
import type { YonexusServerStore } from "./store.js";
import { type ClientConnection, type ServerTransport } from "./transport.js";
import { createPairingService, type PairingService } from "../services/pairing.js";
import {
createDiscordNotificationService,
type DiscordNotificationService
} from "../notifications/discord.js";
import type { PairingNotificationService } from "../notifications/types.js";
import { createNotificationService } from "../notifications/factory.js";
import { safeErrorMessage } from "./logging.js";
import type { ServerRuleRegistry } from "./rules.js";
@@ -53,7 +51,7 @@ export interface YonexusServerRuntimeOptions {
config: YonexusServerConfig;
store: YonexusServerStore;
transport: ServerTransport;
notificationService?: DiscordNotificationService;
notificationService?: PairingNotificationService;
ruleRegistry?: ServerRuleRegistry;
onClientAuthenticated?: (identifier: string) => void;
now?: () => number;
@@ -70,7 +68,7 @@ export class YonexusServerRuntime {
private readonly now: () => number;
private readonly registry: ServerRegistry;
private readonly pairingService: PairingService;
private readonly notificationService: DiscordNotificationService;
private readonly notificationService: PairingNotificationService;
private readonly sweepIntervalMs: number;
private sweepTimer: NodeJS.Timeout | null = null;
private started = false;
@@ -86,10 +84,7 @@ export class YonexusServerRuntime {
this.pairingService = createPairingService({ now: this.now });
this.notificationService =
options.notificationService ??
createDiscordNotificationService({
botToken: options.config.notifyBotToken,
adminUserId: options.config.adminUserId
});
createNotificationService(options.config);
}
get state(): ServerLifecycleState {