fix(plugins): harden inspector runtime capture

This commit is contained in:
Vincent Koc
2026-04-28 02:04:04 -07:00
parent 0f24a8d8e1
commit 5f3b8b4100
13 changed files with 73 additions and 37 deletions

View File

@@ -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 <message>` and `/new <message>` still seed the next model turn. Fixes #73367 and #73412. Thanks @hoyanhan, @wenxu007, and @amdhelper.

View File

@@ -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<typeof createTestPluginApi> & {
onConversationBindingResolved?: ReturnType<typeof vi.fn>;
};
delete api.onConversationBindingResolved;
expect(() => plugin.register(api)).not.toThrow();
});
it("only claims the codex provider by default", () => {
const harness = createCodexAppServerAgentHarness();

View File

@@ -34,6 +34,6 @@ export default definePluginEntry({
pluginConfig: resolveCurrentPluginConfig(),
}),
);
api.onConversationBindingResolved(handleCodexConversationBindingResolved);
api.onConversationBindingResolved?.(handleCodexConversationBindingResolved);
},
});

View File

@@ -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");
}

View File

@@ -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");
}

View File

@@ -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(", ")}`);
}
}

View File

@@ -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`);
}

View File

@@ -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`);
}

View File

@@ -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`);
}

View File

@@ -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 = {

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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",