diff --git a/CHANGELOG.md b/CHANGELOG.md index 1435c2f2021..c4f5154bb0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Plugins/inspector: keep bundled plugin runtime capture quiet and config-tolerant for Codex, memory-lancedb, Feishu, QQBot, and Tlon so plugin-inspector JSON checks can validate the full bundled set. Thanks @vincentkoc. - Gateway/startup: start chat channels without waiting for primary model prewarm, keeping model warmup bounded in the background so Slack and other channels come online promptly when provider discovery is slow. Supersedes #73420. Thanks @dorukardahan. - Gateway/install: carry env-backed config SecretRefs such as `channels.discord.token` into generated service environments when they are present only in the installing shell, while keeping gateway auth SecretRefs non-persisted. Fixes #67817; supersedes #73426. Thanks @wdimaculangan and @ztexydt-cqh. - Auto-reply/commands: stop bare `/reset` and `/new` after reset hooks acknowledge the command, so non-ACP channels no longer fall through into empty provider calls while `/reset ` and `/new ` still seed the next model turn. Fixes #73367 and #73412. Thanks @hoyanhan, @wenxu007, and @amdhelper. diff --git a/extensions/codex/index.test.ts b/extensions/codex/index.test.ts index f6faca4ac5b..457ab20b95a 100644 --- a/extensions/codex/index.test.ts +++ b/extensions/codex/index.test.ts @@ -59,6 +59,27 @@ describe("codex plugin", () => { expect(onConversationBindingResolved).toHaveBeenCalledWith(expect.any(Function)); }); + it("registers with capture APIs that do not expose conversation binding hooks yet", () => { + const api = createTestPluginApi({ + id: "codex", + name: "Codex", + source: "test", + config: {}, + pluginConfig: {}, + runtime: {} as never, + registerAgentHarness: vi.fn(), + registerCommand: vi.fn(), + registerMediaUnderstandingProvider: vi.fn(), + registerProvider: vi.fn(), + on: vi.fn(), + }) as ReturnType & { + onConversationBindingResolved?: ReturnType; + }; + delete api.onConversationBindingResolved; + + expect(() => plugin.register(api)).not.toThrow(); + }); + it("only claims the codex provider by default", () => { const harness = createCodexAppServerAgentHarness(); diff --git a/extensions/codex/index.ts b/extensions/codex/index.ts index cb0ebd850cb..862a6222219 100644 --- a/extensions/codex/index.ts +++ b/extensions/codex/index.ts @@ -34,6 +34,6 @@ export default definePluginEntry({ pluginConfig: resolveCurrentPluginConfig(), }), ); - api.onConversationBindingResolved(handleCodexConversationBindingResolved); + api.onConversationBindingResolved?.(handleCodexConversationBindingResolved); }, }); diff --git a/extensions/feishu/src/bitable.ts b/extensions/feishu/src/bitable.ts index 933d2660cb8..2791a57adf7 100644 --- a/extensions/feishu/src/bitable.ts +++ b/extensions/feishu/src/bitable.ts @@ -543,13 +543,11 @@ const UpdateRecordSchema = Type.Object({ export function registerFeishuBitableTools(api: OpenClawPluginApi) { if (!api.config) { - api.logger.debug?.("feishu_bitable: No config available, skipping bitable tools"); return; } const accounts = listEnabledFeishuAccounts(api.config); if (accounts.length === 0) { - api.logger.debug?.("feishu_bitable: No Feishu accounts configured, skipping bitable tools"); return; } @@ -732,6 +730,4 @@ export function registerFeishuBitableTools(api: OpenClawPluginApi) { ); }, }); - - api.logger.debug?.("feishu_bitable: Registered bitable tools"); } diff --git a/extensions/feishu/src/chat.ts b/extensions/feishu/src/chat.ts index 1f402a81047..e1b8b29f91b 100644 --- a/extensions/feishu/src/chat.ts +++ b/extensions/feishu/src/chat.ts @@ -123,20 +123,17 @@ export async function getFeishuMemberInfo( export function registerFeishuChatTools(api: OpenClawPluginApi) { if (!api.config) { - api.logger.debug?.("feishu_chat: No config available, skipping chat tools"); return; } const accounts = listEnabledFeishuAccounts(api.config); if (accounts.length === 0) { - api.logger.debug?.("feishu_chat: No Feishu accounts configured, skipping chat tools"); return; } const firstAccount = accounts[0]; const toolsCfg = resolveToolsConfig(firstAccount.config.tools); if (!toolsCfg.chat) { - api.logger.debug?.("feishu_chat: chat tool disabled in config"); return; } @@ -188,6 +185,4 @@ export function registerFeishuChatTools(api: OpenClawPluginApi) { }, { name: "feishu_chat" }, ); - - api.logger.debug?.("feishu_chat: Registered feishu_chat tool"); } diff --git a/extensions/feishu/src/docx.ts b/extensions/feishu/src/docx.ts index 820aa5ce3ad..fb72a7a59f2 100644 --- a/extensions/feishu/src/docx.ts +++ b/extensions/feishu/src/docx.ts @@ -1386,14 +1386,12 @@ async function listAppScopes(client: Lark.Client) { export function registerFeishuDocTools(api: OpenClawPluginApi) { if (!api.config) { - api.logger.debug?.("feishu_doc: No config available, skipping doc tools"); return; } // Check if any account is configured const accounts = listEnabledFeishuAccounts(api.config); if (accounts.length === 0) { - api.logger.debug?.("feishu_doc: No Feishu accounts configured, skipping doc tools"); return; } @@ -1615,8 +1613,4 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) { ); registered.push("feishu_app_scopes"); } - - if (registered.length > 0) { - api.logger.debug?.(`feishu_doc: Registered ${registered.join(", ")}`); - } } diff --git a/extensions/feishu/src/drive.ts b/extensions/feishu/src/drive.ts index 9861d828fa2..0d0606b10cf 100644 --- a/extensions/feishu/src/drive.ts +++ b/extensions/feishu/src/drive.ts @@ -733,19 +733,16 @@ export async function deliverCommentThreadText( export function registerFeishuDriveTools(api: OpenClawPluginApi) { if (!api.config) { - api.logger.debug?.("feishu_drive: No config available, skipping drive tools"); return; } const accounts = listEnabledFeishuAccounts(api.config); if (accounts.length === 0) { - api.logger.debug?.("feishu_drive: No Feishu accounts configured, skipping drive tools"); return; } const toolsCfg = resolveAnyEnabledFeishuToolsConfig(accounts); if (!toolsCfg.drive) { - api.logger.debug?.("feishu_drive: drive tool disabled in config"); return; } @@ -829,6 +826,4 @@ export function registerFeishuDriveTools(api: OpenClawPluginApi) { }, { name: "feishu_drive" }, ); - - api.logger.debug?.(`feishu_drive: Registered feishu_drive tool`); } diff --git a/extensions/feishu/src/perm.ts b/extensions/feishu/src/perm.ts index 1ef078c94e3..4d7019e757e 100644 --- a/extensions/feishu/src/perm.ts +++ b/extensions/feishu/src/perm.ts @@ -114,19 +114,16 @@ async function removeMember( export function registerFeishuPermTools(api: OpenClawPluginApi) { if (!api.config) { - api.logger.debug?.("feishu_perm: No config available, skipping perm tools"); return; } const accounts = listEnabledFeishuAccounts(api.config); if (accounts.length === 0) { - api.logger.debug?.("feishu_perm: No Feishu accounts configured, skipping perm tools"); return; } const toolsCfg = resolveAnyEnabledFeishuToolsConfig(accounts); if (!toolsCfg.perm) { - api.logger.debug?.("feishu_perm: perm tool disabled in config (default: false)"); return; } @@ -170,6 +167,4 @@ export function registerFeishuPermTools(api: OpenClawPluginApi) { }, { name: "feishu_perm" }, ); - - api.logger.debug?.(`feishu_perm: Registered feishu_perm tool`); } diff --git a/extensions/feishu/src/wiki.ts b/extensions/feishu/src/wiki.ts index 80832901b11..2d85c2ed8f3 100644 --- a/extensions/feishu/src/wiki.ts +++ b/extensions/feishu/src/wiki.ts @@ -153,19 +153,16 @@ async function renameNode(client: Lark.Client, spaceId: string, nodeToken: strin export function registerFeishuWikiTools(api: OpenClawPluginApi) { if (!api.config) { - api.logger.debug?.("feishu_wiki: No config available, skipping wiki tools"); return; } const accounts = listEnabledFeishuAccounts(api.config); if (accounts.length === 0) { - api.logger.debug?.("feishu_wiki: No Feishu accounts configured, skipping wiki tools"); return; } const toolsCfg = resolveAnyEnabledFeishuToolsConfig(accounts); if (!toolsCfg.wiki) { - api.logger.debug?.("feishu_wiki: wiki tool disabled in config"); return; } @@ -227,6 +224,4 @@ export function registerFeishuWikiTools(api: OpenClawPluginApi) { }, { name: "feishu_wiki" }, ); - - api.logger.debug?.(`feishu_wiki: Registered feishu_wiki tool`); } diff --git a/extensions/memory-lancedb/index.test.ts b/extensions/memory-lancedb/index.test.ts index 97cc65f87e3..45747570dfd 100644 --- a/extensions/memory-lancedb/index.test.ts +++ b/extensions/memory-lancedb/index.test.ts @@ -202,6 +202,43 @@ describe("memory plugin e2e", () => { expect(config?.autoRecall).toBe(true); }); + test("registers as disabled instead of throwing when inspected without config", async () => { + const registerService = vi.fn(); + const logger = { + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + debug: vi.fn(), + }; + const mockApi = { + id: "memory-lancedb", + name: "Memory (LanceDB)", + source: "test", + config: {}, + pluginConfig: {}, + runtime: {}, + logger, + registerTool: vi.fn(), + registerCli: vi.fn(), + registerService, + on: vi.fn(), + resolvePath: (filePath: string) => filePath, + }; + + expect(() => memoryPlugin.register(mockApi as any)).not.toThrow(); + expect(registerService).toHaveBeenCalledWith({ + id: "memory-lancedb", + start: expect.any(Function), + }); + expect(mockApi.registerTool).not.toHaveBeenCalled(); + expect(mockApi.on).not.toHaveBeenCalled(); + + registerService.mock.calls[0]?.[0].start({}); + expect(logger.warn).toHaveBeenCalledWith( + "memory-lancedb: disabled until configured (embedding config required)", + ); + }); + test("registers auto-recall on before_prompt_build instead of the legacy hook", async () => { const on = vi.fn(); const mockApi = { diff --git a/extensions/memory-lancedb/index.ts b/extensions/memory-lancedb/index.ts index 1ddf10e3989..1f9bcfc9e93 100644 --- a/extensions/memory-lancedb/index.ts +++ b/extensions/memory-lancedb/index.ts @@ -503,7 +503,19 @@ export default definePluginEntry({ configSchema: memoryConfigSchema, register(api: OpenClawPluginApi) { - const cfg = memoryConfigSchema.parse(api.pluginConfig); + let cfg: MemoryConfig; + try { + cfg = memoryConfigSchema.parse(api.pluginConfig); + } catch (error) { + api.registerService({ + id: "memory-lancedb", + start: () => { + const message = error instanceof Error ? error.message : String(error); + api.logger.warn(`memory-lancedb: disabled until configured (${message})`); + }, + }); + return; + } const dbPath = cfg.dbPath!; const resolvedDbPath = dbPath.includes("://") ? dbPath : api.resolvePath(dbPath); const { model, dimensions } = cfg.embedding; diff --git a/extensions/qqbot/src/bridge/tools/channel.ts b/extensions/qqbot/src/bridge/tools/channel.ts index da3f25714e2..039bd123be1 100644 --- a/extensions/qqbot/src/bridge/tools/channel.ts +++ b/extensions/qqbot/src/bridge/tools/channel.ts @@ -3,7 +3,6 @@ import { getAccessToken } from "../../engine/messaging/sender.js"; import { ChannelApiSchema, executeChannelApi } from "../../engine/tools/channel-api.js"; import type { ChannelApiParams } from "../../engine/tools/channel-api.js"; import { listQQBotAccountIds, resolveQQBotAccount } from "../config.js"; -import { getBridgeLogger } from "../logger.js"; /** * Register the QQ channel API proxy tool. @@ -15,13 +14,11 @@ import { getBridgeLogger } from "../logger.js"; export function registerChannelTool(api: OpenClawPluginApi): void { const cfg = api.config; if (!cfg) { - getBridgeLogger().debug?.("[qqbot-channel-api] No config available, skipping"); return; } const accountIds = listQQBotAccountIds(cfg); if (accountIds.length === 0) { - getBridgeLogger().debug?.("[qqbot-channel-api] No QQBot accounts configured, skipping"); return; } @@ -29,7 +26,6 @@ export function registerChannelTool(api: OpenClawPluginApi): void { const account = resolveQQBotAccount(cfg, firstAccountId); if (!account.appId || !account.clientSecret) { - getBridgeLogger().debug?.("[qqbot-channel-api] Account not fully configured, skipping"); return; } diff --git a/extensions/tlon/index.ts b/extensions/tlon/index.ts index 8fb1ad5863a..cb05eedbe39 100644 --- a/extensions/tlon/index.ts +++ b/extensions/tlon/index.ts @@ -127,7 +127,6 @@ export default defineBundledChannelEntry({ exportName: "setTlonRuntime", }, registerFull(api) { - api.logger.debug?.("[tlon] Registering tlon tool"); api.registerTool({ name: "tlon", label: "Tlon CLI",