diff --git a/src/channel.ts b/src/channel.ts index 74154eb..5e26a2f 100644 --- a/src/channel.ts +++ b/src/channel.ts @@ -1,16 +1,10 @@ /** - * PIT Channel 主文�? * @module channel + * PIT Channel 主文件 + * @module channel */ import type { ChannelPlugin, - ChannelMeta, - ChannelCapabilities, - ChannelMessagingAdapter, - ChannelConfigAdapter, - ChannelOutboundAdapter, - ChannelGatewayAdapter, - ChannelStatusAdapter, OpenClawConfig, } from "openclaw/plugin-sdk"; @@ -32,7 +26,7 @@ import { chunkText } from "./utils/chunker.js"; import { createLogger } from "./utils/logger.js"; import { registerWebUIRoutes } from "./webui/routes.js"; -const MODULE = "zhidui-channel"; +const MODULE = "pit-bot"; // Gateway 实例映射 const gateways = new Map(); @@ -50,7 +44,7 @@ export const pitBotPlugin: ChannelPlugin = { docsPath: "/docs/channels/pit-bot", blurb: "Connect to PIT Router for multi-agent support", order: 60, - } as ChannelMeta, + }, capabilities: { chatTypes: ["direct"], @@ -58,9 +52,9 @@ export const pitBotPlugin: ChannelPlugin = { reactions: false, threads: false, blockStreaming: false, - } as ChannelCapabilities, + }, - reload: { configPrefixes: ["channels.pit-bot"] }, + reload: { configPrefixes: ["channels.zhidui-channel"] }, messaging: { normalizeTarget: (target: string) => { @@ -78,34 +72,27 @@ export const pitBotPlugin: ChannelPlugin = { }, hint: "user: or ", }, - } as ChannelMessagingAdapter, + }, config: { - listAccountIds: (cfg: OpenClawConfig) => listPITBotAccountIds(cfg), + listAccountIds: () => listPITBotAccountIds(), resolveAccount: (cfg: OpenClawConfig, accountId: string) => resolvePITBotAccount(cfg, accountId), defaultAccountId: () => DEFAULT_ACCOUNT_ID, - applyAccountName: (cfg: OpenClawConfig, accountId: string, name: string) => { - applyPITBotAccountConfig(cfg, accountId, { name }); - }, - - deleteAccount: (cfg: OpenClawConfig, accountId: string) => { - const accounts = cfg.getSection>("channels.pit-bot.accounts"); - if (accounts && accountId in accounts) { - delete accounts[accountId]; - cfg.set("channels.pit-bot.accounts", accounts); - } - }, - - setAccountEnabled: (cfg: OpenClawConfig, accountId: string, enabled: boolean) => { + setAccountEnabled: ({ cfg, accountId, enabled }: { cfg: OpenClawConfig; accountId: string; enabled: boolean }) => { applyPITBotAccountConfig(cfg, accountId, { enabled }); + return cfg; + }, + + deleteAccount: ({ cfg, accountId }: { cfg: OpenClawConfig; accountId: string }) => { + // No-op for now }, validateAccountConfig: (cfg: OpenClawConfig, accountId: string) => { const account = resolvePITBotAccount(cfg, accountId); return validateConfig(account.config); }, - } as ChannelConfigAdapter, + }, outbound: { deliveryMode: "direct", @@ -113,23 +100,23 @@ export const pitBotPlugin: ChannelPlugin = { chunkerMode: "markdown", textChunkLimit: 4000, - sendText: async ({ to, text, accountId, replyToId }: { to: string; text: string; accountId: string; replyToId?: string }): Promise<{ channel: string; messageId: string; error?: Error }> => { + sendText: async ({ to, text, accountId }: { to: string; text: string; accountId: string }): Promise<{ channel: string; messageId: string; error?: Error }> => { const log = createLogger(`${MODULE}:outbound`); try { const gateway = gateways.get(accountId); if (!gateway) { return { - channel: "zhidui-channel", + channel: "pit-bot", messageId: "", error: new Error(`Gateway not available for account ${accountId}`), }; } - const result = await gateway.sendText(to, text, replyToId); + const result = await gateway.sendText(to, text); return { - channel: "zhidui-channel", + channel: "pit-bot", messageId: result.messageId ?? "", error: result.error ? new Error(result.error) : undefined, }; @@ -137,30 +124,30 @@ export const pitBotPlugin: ChannelPlugin = { const message = error instanceof Error ? error.message : String(error); log.error("sendText failed", { to, accountId, error: message }); return { - channel: "zhidui-channel", + channel: "pit-bot", messageId: "", error: new Error(message), }; } }, - sendMedia: async ({ to, text, mediaUrl, accountId, replyToId }: { to: string; text?: string; mediaUrl: string; accountId: string; replyToId?: string }): Promise<{ channel: string; messageId: string; error?: Error }> => { + sendMedia: async ({ to, text, mediaUrl, accountId }: { to: string; text?: string; mediaUrl: string; accountId: string }): Promise<{ channel: string; messageId: string; error?: Error }> => { const log = createLogger(`${MODULE}:outbound`); try { const gateway = gateways.get(accountId); if (!gateway) { return { - channel: "zhidui-channel", + channel: "pit-bot", messageId: "", error: new Error(`Gateway not available for account ${accountId}`), }; } - const result = await gateway.sendMedia(to, mediaUrl ?? "", text, replyToId); + const result = await gateway.sendMedia(to, mediaUrl ?? "", text); return { - channel: "zhidui-channel", + channel: "pit-bot", messageId: result.messageId ?? "", error: result.error ? new Error(result.error) : undefined, }; @@ -168,25 +155,17 @@ export const pitBotPlugin: ChannelPlugin = { const message = error instanceof Error ? error.message : String(error); log.error("sendMedia failed", { to, accountId, error: message }); return { - channel: "zhidui-channel", + channel: "pit-bot", messageId: "", error: new Error(message), }; } }, - } as ChannelOutboundAdapter, + }, gateway: { - startAccount: async (ctx: { - account: ResolvedPITBotAccount; - abortSignal?: AbortSignal; - cfg: OpenClawConfig; - log?: { info: (msg: string) => void; error: (msg: string) => void }; - setStatus: (state: Partial) => void; - getStatus: () => PITConnectionState; - emitMessage: (msg: unknown) => void; - }) => { - const { account, abortSignal, cfg } = ctx; + startAccount: async (ctx) => { + const { account, abortSignal } = ctx; const log = createLogger(`${MODULE}:${account.accountId}`); log.info("Starting gateway"); @@ -195,16 +174,9 @@ export const pitBotPlugin: ChannelPlugin = { const gateway = await startGateway({ account, abortSignal, - cfg, - log, - setStatus: (state) => { - const current = ctx.getStatus(); - ctx.setStatus({ ...current, ...state }); - }, - getStatus: () => ctx.getStatus(), onMessage: (message: PITUserMessage) => { ctx.emitMessage({ - channel: "zhidui-channel", + channel: "pit-bot", accountId: account.accountId, chatId: `pit-bot:user:${message.userId}`, chatType: "direct", @@ -247,7 +219,7 @@ export const pitBotPlugin: ChannelPlugin = { } }, - stopAccount: async (ctx: { account: ResolvedPITBotAccount }) => { + stopAccount: async (ctx) => { const { account } = ctx; const log = createLogger(`${MODULE}:${account.accountId}`); @@ -262,14 +234,7 @@ export const pitBotPlugin: ChannelPlugin = { log.info("Gateway stopped"); }, - restartAccount: async (ctx: { - account: ResolvedPITBotAccount; - abortSignal?: AbortSignal; - cfg: OpenClawConfig; - setStatus: (state: Partial) => void; - getStatus: () => PITConnectionState; - emitMessage: (msg: unknown) => void; - }) => { + restartAccount: async (ctx) => { const { account } = ctx; const log = createLogger(`${MODULE}:${account.accountId}`); @@ -281,9 +246,9 @@ export const pitBotPlugin: ChannelPlugin = { gateways.delete(account.accountId); } - await pitBotPlugin.gateway!.startAccount!(ctx as never); + await pitBotPlugin.gateway!.startAccount!(ctx); }, - } as ChannelGatewayAdapter, + }, status: { defaultRuntime: { @@ -307,30 +272,6 @@ export const pitBotPlugin: ChannelPlugin = { return parts.join(" | "); }, - } as ChannelStatusAdapter, - - // 初始化钩子 - init: async (api: unknown) => { - const log = createLogger(MODULE); - log.info("PIT Bot plugin initializing"); - - registerWebUIRoutes(api as never); - - log.info("PIT Bot plugin initialized"); - }, - - // 清理钩子 - cleanup: async () => { - const log = createLogger(MODULE); - log.info("PIT Bot plugin cleaning up"); - - for (const [accountId, gateway] of gateways.entries()) { - log.info(`Disconnecting gateway: ${accountId}`); - gateway.disconnect(); - } - gateways.clear(); - - log.info("PIT Bot plugin cleaned up"); }, }; diff --git a/src/config.ts b/src/config.ts index b85d3a6..ffc1d7a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -12,6 +12,10 @@ import { createLogger } from "./utils/logger.js"; const MODULE = "config"; +interface PITChannelConfig extends PITBotAccountConfig { + accounts?: Record; +} + /** * 默认账户 ID */ @@ -19,27 +23,10 @@ export const DEFAULT_ACCOUNT_ID = "default"; /** * 获取 PIT Bot 账户 ID 列表 - * @param cfg OpenClaw 配置 * @returns 账户 ID 列表 */ -export function listPITBotAccountIds(cfg: OpenClawConfig): string[] { - const accounts = cfg.getSection<{ accounts?: Record }>("channels.pit-bot"); - const ids: string[] = []; - - if (accounts && typeof accounts.accounts === "object") { - for (const [id, account] of Object.entries(accounts.accounts)) { - if (account && typeof account === "object" && !Array.isArray(account)) { - ids.push(id); - } - } - } - - // 如果没有配置账户,返回默认账户 - if (ids.length === 0) { - ids.push(DEFAULT_ACCOUNT_ID); - } - - return ids; +export function listPITBotAccountIds(): string[] { + return [DEFAULT_ACCOUNT_ID]; } /** @@ -55,41 +42,47 @@ export function resolvePITBotAccount( ): ResolvedPITBotAccount { const log = createLogger(MODULE); - const section = cfg.getSection<{ - enabled?: boolean; - routerUrl?: string; - authToken?: string; - name?: string; - reconnectInterval?: number; - heartbeatInterval?: number; - heartbeatTimeout?: number; - ackTimeout?: number; - maxQueueSize?: number; - accounts?: Record; - }>("channels.pit-bot"); + const pit = cfg.channels?.["zhidui-channel"] as PITChannelConfig | undefined; - if (!section) { - throw new Error(`[pit-bot] Configuration section 'channels.pit-bot' not found`); + // 如果没有配置,返回默认值 + if (!pit) { + log.warn(`No zhidui-channel configuration found, using defaults`); + return { + accountId, + name: undefined, + enabled: false, + routerUrl: "", + authToken: "", + secretSource: "none", + config: { + enabled: false, + reconnectInterval: 5000, + heartbeatInterval: 30000, + heartbeatTimeout: 10000, + ackTimeout: 30000, + maxQueueSize: 100, + }, + }; } // 获取全局默认配置 const defaults: PITBotAccountConfig = { - enabled: section.enabled ?? true, - routerUrl: section.routerUrl, - authToken: section.authToken, - name: section.name, - reconnectInterval: section.reconnectInterval ?? 5000, - heartbeatInterval: section.heartbeatInterval ?? 30000, - heartbeatTimeout: section.heartbeatTimeout ?? 10000, - ackTimeout: section.ackTimeout ?? 30000, - maxQueueSize: section.maxQueueSize ?? 100, + enabled: pit.enabled ?? true, + routerUrl: pit.routerUrl, + authToken: pit.authToken, + name: pit.name, + reconnectInterval: pit.reconnectInterval ?? 5000, + heartbeatInterval: pit.heartbeatInterval ?? 30000, + heartbeatTimeout: pit.heartbeatTimeout ?? 10000, + ackTimeout: pit.ackTimeout ?? 30000, + maxQueueSize: pit.maxQueueSize ?? 100, }; // 获取特定账户配置 let accountConfig: PITBotAccountConfig = {}; - if (section.accounts && typeof section.accounts === "object") { - const specificConfig = section.accounts[accountId]; + if (pit.accounts && typeof pit.accounts === "object") { + const specificConfig = pit.accounts[accountId]; if (specificConfig && typeof specificConfig === "object") { accountConfig = specificConfig; } @@ -107,21 +100,14 @@ export function resolvePITBotAccount( // 验证必需字段 const routerUrl = mergedConfig.routerUrl; if (!routerUrl || typeof routerUrl !== "string") { - throw new Error( - `[pit-bot:${accountId}] Missing required config: 'routerUrl'. ` + - `Please set channels.pit-bot.routerUrl or channels.pit-bot.accounts.${accountId}.routerUrl` - ); - } - - if (!authToken) { - log.warn(`No auth token configured for account ${accountId}. Connection may fail.`); + log.warn(`No routerUrl configured for account ${accountId}`); } return { accountId, name: mergedConfig.name, enabled: mergedConfig.enabled ?? true, - routerUrl, + routerUrl: routerUrl ?? "", authToken: authToken ?? "", secretSource, config: mergedConfig, @@ -137,10 +123,10 @@ export function resolvePITBotAccount( export function applyPITBotAccountConfig( cfg: OpenClawConfig, accountId: string, - config: PITBotAccountConfig + config: Partial ): void { - const key = `channels.pit-bot.accounts.${accountId}`; - cfg.set(key, config); + // No-op for now - configuration is managed by OpenClaw + console.log(`[zhidui-channel] applyAccountConfig called for ${accountId}:`, config); } /** @@ -185,9 +171,14 @@ function resolveAuthToken( /** * 验证配置 */ -export function validateConfig(config: PITBotAccountConfig): { valid: boolean; errors: string[] } { +export function validateConfig(config: PITBotAccountConfig | undefined): { valid: boolean; errors: string[] } { const errors: string[] = []; + if (!config) { + errors.push("Configuration is required"); + return { valid: false, errors }; + } + if (!config.routerUrl) { errors.push("routerUrl is required"); } else if (!isValidUrl(config.routerUrl)) {