mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(agents): skip unreadable tool search catalog entries
This commit is contained in:
@@ -158,6 +158,32 @@ describe("Tool Search", () => {
|
||||
expect(telemetry.callCount).toBe(1);
|
||||
});
|
||||
|
||||
it("skips catalog tools with unreadable schemas without dropping healthy siblings", () => {
|
||||
const codeTool = fakeTool(TOOL_SEARCH_CODE_MODE_TOOL_NAME, "code mode");
|
||||
const broken = pluginTool("fake_broken", "Broken target");
|
||||
Object.defineProperty(broken, "parameters", {
|
||||
enumerable: true,
|
||||
get() {
|
||||
throw new Error("fuzzplugin parameters getter exploded");
|
||||
},
|
||||
});
|
||||
const healthy = pluginTool("fake_healthy", "Healthy target");
|
||||
|
||||
const compacted = applyToolSearchCatalog({
|
||||
tools: [codeTool, broken, healthy],
|
||||
config: {
|
||||
tools: {
|
||||
toolSearch: true,
|
||||
},
|
||||
} as never,
|
||||
sessionId: "session-unreadable-schema",
|
||||
sessionKey: "agent:main:unreadable-schema",
|
||||
});
|
||||
|
||||
expect(compacted.tools.map((tool) => tool.name)).toEqual([TOOL_SEARCH_CODE_MODE_TOOL_NAME]);
|
||||
expect(compacted.catalogToolCount).toBe(1);
|
||||
});
|
||||
|
||||
it("scopes catalogs by run id when attempts share a session", async () => {
|
||||
const runATool = pluginTool("fake_run_a", "Tool visible only to run A");
|
||||
const runBTool = pluginTool("fake_run_b", "Tool visible only to run B");
|
||||
|
||||
@@ -638,9 +638,35 @@ function classifyTool(tool: CatalogTool): {
|
||||
return { source: "openclaw", sourceName: "core" };
|
||||
}
|
||||
|
||||
function makeCatalogId(tool: CatalogTool, source: CatalogSource, sourceName?: string): string {
|
||||
function makeCatalogId(name: string, source: CatalogSource, sourceName?: string): string {
|
||||
const owner = sourceName?.trim() || "core";
|
||||
return `${source}:${owner}:${tool.name}`;
|
||||
return `${source}:${owner}:${name}`;
|
||||
}
|
||||
|
||||
type CatalogToolSnapshot = {
|
||||
name: string;
|
||||
label?: string;
|
||||
description: string;
|
||||
parameters?: unknown;
|
||||
};
|
||||
|
||||
function readCatalogToolSnapshot(tool: CatalogTool): CatalogToolSnapshot | undefined {
|
||||
try {
|
||||
const name = tool.name;
|
||||
if (typeof name !== "string" || !name.trim()) {
|
||||
return undefined;
|
||||
}
|
||||
const label = typeof tool.label === "string" ? tool.label : undefined;
|
||||
const description = typeof tool.description === "string" ? tool.description : "";
|
||||
return {
|
||||
name,
|
||||
...(label !== undefined ? { label } : {}),
|
||||
description,
|
||||
parameters: tool.parameters,
|
||||
};
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function wrapCatalogTool(tool: AnyAgentTool, hookContext?: HookContext): AnyAgentTool {
|
||||
@@ -654,21 +680,25 @@ function toCatalogEntry(
|
||||
tool: CatalogTool,
|
||||
sourceOverride?: CatalogSource,
|
||||
hookContext?: HookContext,
|
||||
): ToolSearchCatalogEntry {
|
||||
): ToolSearchCatalogEntry | undefined {
|
||||
const snapshot = readCatalogToolSnapshot(tool);
|
||||
if (!snapshot) {
|
||||
return undefined;
|
||||
}
|
||||
const classified = classifyTool(tool);
|
||||
const source = sourceOverride ?? classified.source;
|
||||
const sourceName = sourceOverride === "client" ? "client" : classified.sourceName;
|
||||
const catalogTool =
|
||||
source === "client" ? tool : wrapCatalogTool(tool as AnyAgentTool, hookContext);
|
||||
return {
|
||||
id: makeCatalogId(tool, source, sourceName),
|
||||
id: makeCatalogId(snapshot.name, source, sourceName),
|
||||
source,
|
||||
sourceName,
|
||||
...(source === "mcp" && classified.mcp ? { mcp: classified.mcp } : {}),
|
||||
name: tool.name,
|
||||
label: tool.label,
|
||||
description: tool.description ?? "",
|
||||
parameters: tool.parameters,
|
||||
name: snapshot.name,
|
||||
label: snapshot.label,
|
||||
description: snapshot.description,
|
||||
parameters: snapshot.parameters,
|
||||
tool: catalogTool,
|
||||
};
|
||||
}
|
||||
@@ -1292,7 +1322,10 @@ export function applyToolCatalogCompaction(params: {
|
||||
continue;
|
||||
}
|
||||
if (shouldCatalog(tool)) {
|
||||
catalog.push(toCatalogEntry(tool, undefined, params.toolHookContext));
|
||||
const entry = toCatalogEntry(tool, undefined, params.toolHookContext);
|
||||
if (entry) {
|
||||
catalog.push(entry);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
visible.push(tool);
|
||||
@@ -1382,16 +1415,19 @@ export function addClientToolsToToolCatalog(params: {
|
||||
if (!existing) {
|
||||
return { tools: params.tools, compacted: false, catalogToolCount: 0 };
|
||||
}
|
||||
const entries = params.tools
|
||||
.map((tool) => toCatalogEntry(tool, "client"))
|
||||
.filter((entry): entry is ToolSearchCatalogEntry => Boolean(entry));
|
||||
registerToolSearchCatalog({
|
||||
sessionId: params.sessionId,
|
||||
sessionKey: params.sessionKey,
|
||||
agentId: params.agentId,
|
||||
runId: params.runId,
|
||||
catalogRef: params.catalogRef,
|
||||
entries: params.tools.map((tool) => toCatalogEntry(tool, "client")),
|
||||
entries,
|
||||
append: true,
|
||||
});
|
||||
return { tools: [], compacted: params.tools.length > 0, catalogToolCount: params.tools.length };
|
||||
return { tools: [], compacted: entries.length > 0, catalogToolCount: entries.length };
|
||||
}
|
||||
|
||||
function toJsonSafe(value: unknown): unknown {
|
||||
|
||||
Reference in New Issue
Block a user