fix: 修复部分类型错误 (进行中)

- 重写 config.ts 使用 cfg.channels 访问配置
- 简化 channel.ts 接口
- 仍有部分类型错误需要修复
This commit is contained in:
2026-03-15 12:42:38 +08:00
parent ed0a05ee2e
commit 1c452610a2
2 changed files with 81 additions and 149 deletions

View File

@@ -1,16 +1,10 @@
/** /**
* PIT Channel 主文<EFBFBD>? * @module channel * PIT Channel 主文
* @module channel
*/ */
import type { import type {
ChannelPlugin, ChannelPlugin,
ChannelMeta,
ChannelCapabilities,
ChannelMessagingAdapter,
ChannelConfigAdapter,
ChannelOutboundAdapter,
ChannelGatewayAdapter,
ChannelStatusAdapter,
OpenClawConfig, OpenClawConfig,
} from "openclaw/plugin-sdk"; } from "openclaw/plugin-sdk";
@@ -32,7 +26,7 @@ import { chunkText } from "./utils/chunker.js";
import { createLogger } from "./utils/logger.js"; import { createLogger } from "./utils/logger.js";
import { registerWebUIRoutes } from "./webui/routes.js"; import { registerWebUIRoutes } from "./webui/routes.js";
const MODULE = "zhidui-channel"; const MODULE = "pit-bot";
// Gateway 实例映射 // Gateway 实例映射
const gateways = new Map<string, Gateway>(); const gateways = new Map<string, Gateway>();
@@ -50,7 +44,7 @@ export const pitBotPlugin: ChannelPlugin<ResolvedPITBotAccount> = {
docsPath: "/docs/channels/pit-bot", docsPath: "/docs/channels/pit-bot",
blurb: "Connect to PIT Router for multi-agent support", blurb: "Connect to PIT Router for multi-agent support",
order: 60, order: 60,
} as ChannelMeta, },
capabilities: { capabilities: {
chatTypes: ["direct"], chatTypes: ["direct"],
@@ -58,9 +52,9 @@ export const pitBotPlugin: ChannelPlugin<ResolvedPITBotAccount> = {
reactions: false, reactions: false,
threads: false, threads: false,
blockStreaming: false, blockStreaming: false,
} as ChannelCapabilities, },
reload: { configPrefixes: ["channels.pit-bot"] }, reload: { configPrefixes: ["channels.zhidui-channel"] },
messaging: { messaging: {
normalizeTarget: (target: string) => { normalizeTarget: (target: string) => {
@@ -78,34 +72,27 @@ export const pitBotPlugin: ChannelPlugin<ResolvedPITBotAccount> = {
}, },
hint: "user:<userId> or <userId>", hint: "user:<userId> or <userId>",
}, },
} as ChannelMessagingAdapter, },
config: { config: {
listAccountIds: (cfg: OpenClawConfig) => listPITBotAccountIds(cfg), listAccountIds: () => listPITBotAccountIds(),
resolveAccount: (cfg: OpenClawConfig, accountId: string) => resolvePITBotAccount(cfg, accountId), resolveAccount: (cfg: OpenClawConfig, accountId: string) => resolvePITBotAccount(cfg, accountId),
defaultAccountId: () => DEFAULT_ACCOUNT_ID, defaultAccountId: () => DEFAULT_ACCOUNT_ID,
applyAccountName: (cfg: OpenClawConfig, accountId: string, name: string) => { setAccountEnabled: ({ cfg, accountId, enabled }: { cfg: OpenClawConfig; accountId: string; enabled: boolean }) => {
applyPITBotAccountConfig(cfg, accountId, { name });
},
deleteAccount: (cfg: OpenClawConfig, accountId: string) => {
const accounts = cfg.getSection<Record<string, unknown>>("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) => {
applyPITBotAccountConfig(cfg, accountId, { enabled }); applyPITBotAccountConfig(cfg, accountId, { enabled });
return cfg;
},
deleteAccount: ({ cfg, accountId }: { cfg: OpenClawConfig; accountId: string }) => {
// No-op for now
}, },
validateAccountConfig: (cfg: OpenClawConfig, accountId: string) => { validateAccountConfig: (cfg: OpenClawConfig, accountId: string) => {
const account = resolvePITBotAccount(cfg, accountId); const account = resolvePITBotAccount(cfg, accountId);
return validateConfig(account.config); return validateConfig(account.config);
}, },
} as ChannelConfigAdapter<ResolvedPITBotAccount>, },
outbound: { outbound: {
deliveryMode: "direct", deliveryMode: "direct",
@@ -113,23 +100,23 @@ export const pitBotPlugin: ChannelPlugin<ResolvedPITBotAccount> = {
chunkerMode: "markdown", chunkerMode: "markdown",
textChunkLimit: 4000, 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`); const log = createLogger(`${MODULE}:outbound`);
try { try {
const gateway = gateways.get(accountId); const gateway = gateways.get(accountId);
if (!gateway) { if (!gateway) {
return { return {
channel: "zhidui-channel", channel: "pit-bot",
messageId: "", messageId: "",
error: new Error(`Gateway not available for account ${accountId}`), 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 { return {
channel: "zhidui-channel", channel: "pit-bot",
messageId: result.messageId ?? "", messageId: result.messageId ?? "",
error: result.error ? new Error(result.error) : undefined, error: result.error ? new Error(result.error) : undefined,
}; };
@@ -137,30 +124,30 @@ export const pitBotPlugin: ChannelPlugin<ResolvedPITBotAccount> = {
const message = error instanceof Error ? error.message : String(error); const message = error instanceof Error ? error.message : String(error);
log.error("sendText failed", { to, accountId, error: message }); log.error("sendText failed", { to, accountId, error: message });
return { return {
channel: "zhidui-channel", channel: "pit-bot",
messageId: "", messageId: "",
error: new Error(message), 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`); const log = createLogger(`${MODULE}:outbound`);
try { try {
const gateway = gateways.get(accountId); const gateway = gateways.get(accountId);
if (!gateway) { if (!gateway) {
return { return {
channel: "zhidui-channel", channel: "pit-bot",
messageId: "", messageId: "",
error: new Error(`Gateway not available for account ${accountId}`), 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 { return {
channel: "zhidui-channel", channel: "pit-bot",
messageId: result.messageId ?? "", messageId: result.messageId ?? "",
error: result.error ? new Error(result.error) : undefined, error: result.error ? new Error(result.error) : undefined,
}; };
@@ -168,25 +155,17 @@ export const pitBotPlugin: ChannelPlugin<ResolvedPITBotAccount> = {
const message = error instanceof Error ? error.message : String(error); const message = error instanceof Error ? error.message : String(error);
log.error("sendMedia failed", { to, accountId, error: message }); log.error("sendMedia failed", { to, accountId, error: message });
return { return {
channel: "zhidui-channel", channel: "pit-bot",
messageId: "", messageId: "",
error: new Error(message), error: new Error(message),
}; };
} }
}, },
} as ChannelOutboundAdapter<ResolvedPITBotAccount>, },
gateway: { gateway: {
startAccount: async (ctx: { startAccount: async (ctx) => {
account: ResolvedPITBotAccount; const { account, abortSignal } = ctx;
abortSignal?: AbortSignal;
cfg: OpenClawConfig;
log?: { info: (msg: string) => void; error: (msg: string) => void };
setStatus: (state: Partial<PITConnectionState>) => void;
getStatus: () => PITConnectionState;
emitMessage: (msg: unknown) => void;
}) => {
const { account, abortSignal, cfg } = ctx;
const log = createLogger(`${MODULE}:${account.accountId}`); const log = createLogger(`${MODULE}:${account.accountId}`);
log.info("Starting gateway"); log.info("Starting gateway");
@@ -195,16 +174,9 @@ export const pitBotPlugin: ChannelPlugin<ResolvedPITBotAccount> = {
const gateway = await startGateway({ const gateway = await startGateway({
account, account,
abortSignal, abortSignal,
cfg,
log,
setStatus: (state) => {
const current = ctx.getStatus();
ctx.setStatus({ ...current, ...state });
},
getStatus: () => ctx.getStatus(),
onMessage: (message: PITUserMessage) => { onMessage: (message: PITUserMessage) => {
ctx.emitMessage({ ctx.emitMessage({
channel: "zhidui-channel", channel: "pit-bot",
accountId: account.accountId, accountId: account.accountId,
chatId: `pit-bot:user:${message.userId}`, chatId: `pit-bot:user:${message.userId}`,
chatType: "direct", chatType: "direct",
@@ -247,7 +219,7 @@ export const pitBotPlugin: ChannelPlugin<ResolvedPITBotAccount> = {
} }
}, },
stopAccount: async (ctx: { account: ResolvedPITBotAccount }) => { stopAccount: async (ctx) => {
const { account } = ctx; const { account } = ctx;
const log = createLogger(`${MODULE}:${account.accountId}`); const log = createLogger(`${MODULE}:${account.accountId}`);
@@ -262,14 +234,7 @@ export const pitBotPlugin: ChannelPlugin<ResolvedPITBotAccount> = {
log.info("Gateway stopped"); log.info("Gateway stopped");
}, },
restartAccount: async (ctx: { restartAccount: async (ctx) => {
account: ResolvedPITBotAccount;
abortSignal?: AbortSignal;
cfg: OpenClawConfig;
setStatus: (state: Partial<PITConnectionState>) => void;
getStatus: () => PITConnectionState;
emitMessage: (msg: unknown) => void;
}) => {
const { account } = ctx; const { account } = ctx;
const log = createLogger(`${MODULE}:${account.accountId}`); const log = createLogger(`${MODULE}:${account.accountId}`);
@@ -281,9 +246,9 @@ export const pitBotPlugin: ChannelPlugin<ResolvedPITBotAccount> = {
gateways.delete(account.accountId); gateways.delete(account.accountId);
} }
await pitBotPlugin.gateway!.startAccount!(ctx as never); await pitBotPlugin.gateway!.startAccount!(ctx);
},
}, },
} as ChannelGatewayAdapter<ResolvedPITBotAccount>,
status: { status: {
defaultRuntime: { defaultRuntime: {
@@ -307,30 +272,6 @@ export const pitBotPlugin: ChannelPlugin<ResolvedPITBotAccount> = {
return parts.join(" | "); 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");
}, },
}; };

View File

@@ -12,6 +12,10 @@ import { createLogger } from "./utils/logger.js";
const MODULE = "config"; const MODULE = "config";
interface PITChannelConfig extends PITBotAccountConfig {
accounts?: Record<string, PITBotAccountConfig>;
}
/** /**
* 默认账户 ID * 默认账户 ID
*/ */
@@ -19,27 +23,10 @@ export const DEFAULT_ACCOUNT_ID = "default";
/** /**
* 获取 PIT Bot 账户 ID 列表 * 获取 PIT Bot 账户 ID 列表
* @param cfg OpenClaw 配置
* @returns 账户 ID 列表 * @returns 账户 ID 列表
*/ */
export function listPITBotAccountIds(cfg: OpenClawConfig): string[] { export function listPITBotAccountIds(): string[] {
const accounts = cfg.getSection<{ accounts?: Record<string, unknown> }>("channels.pit-bot"); return [DEFAULT_ACCOUNT_ID];
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;
} }
/** /**
@@ -55,41 +42,47 @@ export function resolvePITBotAccount(
): ResolvedPITBotAccount { ): ResolvedPITBotAccount {
const log = createLogger(MODULE); const log = createLogger(MODULE);
const section = cfg.getSection<{ const pit = cfg.channels?.["zhidui-channel"] as PITChannelConfig | undefined;
enabled?: boolean;
routerUrl?: string;
authToken?: string;
name?: string;
reconnectInterval?: number;
heartbeatInterval?: number;
heartbeatTimeout?: number;
ackTimeout?: number;
maxQueueSize?: number;
accounts?: Record<string, PITBotAccountConfig>;
}>("channels.pit-bot");
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 = { const defaults: PITBotAccountConfig = {
enabled: section.enabled ?? true, enabled: pit.enabled ?? true,
routerUrl: section.routerUrl, routerUrl: pit.routerUrl,
authToken: section.authToken, authToken: pit.authToken,
name: section.name, name: pit.name,
reconnectInterval: section.reconnectInterval ?? 5000, reconnectInterval: pit.reconnectInterval ?? 5000,
heartbeatInterval: section.heartbeatInterval ?? 30000, heartbeatInterval: pit.heartbeatInterval ?? 30000,
heartbeatTimeout: section.heartbeatTimeout ?? 10000, heartbeatTimeout: pit.heartbeatTimeout ?? 10000,
ackTimeout: section.ackTimeout ?? 30000, ackTimeout: pit.ackTimeout ?? 30000,
maxQueueSize: section.maxQueueSize ?? 100, maxQueueSize: pit.maxQueueSize ?? 100,
}; };
// 获取特定账户配置 // 获取特定账户配置
let accountConfig: PITBotAccountConfig = {}; let accountConfig: PITBotAccountConfig = {};
if (section.accounts && typeof section.accounts === "object") { if (pit.accounts && typeof pit.accounts === "object") {
const specificConfig = section.accounts[accountId]; const specificConfig = pit.accounts[accountId];
if (specificConfig && typeof specificConfig === "object") { if (specificConfig && typeof specificConfig === "object") {
accountConfig = specificConfig; accountConfig = specificConfig;
} }
@@ -107,21 +100,14 @@ export function resolvePITBotAccount(
// 验证必需字段 // 验证必需字段
const routerUrl = mergedConfig.routerUrl; const routerUrl = mergedConfig.routerUrl;
if (!routerUrl || typeof routerUrl !== "string") { if (!routerUrl || typeof routerUrl !== "string") {
throw new Error( log.warn(`No routerUrl configured for account ${accountId}`);
`[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.`);
} }
return { return {
accountId, accountId,
name: mergedConfig.name, name: mergedConfig.name,
enabled: mergedConfig.enabled ?? true, enabled: mergedConfig.enabled ?? true,
routerUrl, routerUrl: routerUrl ?? "",
authToken: authToken ?? "", authToken: authToken ?? "",
secretSource, secretSource,
config: mergedConfig, config: mergedConfig,
@@ -137,10 +123,10 @@ export function resolvePITBotAccount(
export function applyPITBotAccountConfig( export function applyPITBotAccountConfig(
cfg: OpenClawConfig, cfg: OpenClawConfig,
accountId: string, accountId: string,
config: PITBotAccountConfig config: Partial<PITBotAccountConfig>
): void { ): void {
const key = `channels.pit-bot.accounts.${accountId}`; // No-op for now - configuration is managed by OpenClaw
cfg.set(key, config); 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[] = []; const errors: string[] = [];
if (!config) {
errors.push("Configuration is required");
return { valid: false, errors };
}
if (!config.routerUrl) { if (!config.routerUrl) {
errors.push("routerUrl is required"); errors.push("routerUrl is required");
} else if (!isValidUrl(config.routerUrl)) { } else if (!isValidUrl(config.routerUrl)) {