File size: 7,149 Bytes
76289e7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | // Doctor runtime error tests cover error handling in core runtime checks.
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { AnyAgentTool } from "../agents/tools/common.js";
import { setPluginToolMeta } from "../plugins/tool-metadata.js";
const mocks = vi.hoisted(() => ({
createBundleMcpToolRuntime: vi.fn(),
createOpenClawCodingTools: vi.fn(),
disposeBundleRuntime: vi.fn(),
loadModelCatalog: vi.fn(async (): Promise<Array<Record<string, unknown>>> => []),
normalizeProviderToolSchemasWithPlugin: vi.fn(),
resolveDefaultModelForAgent: vi.fn(() => ({ provider: "openai", model: "gpt-5.5" })),
}));
vi.mock("../agents/model-catalog.js", () => ({
findModelInCatalog: (
catalog: Array<{ provider?: string; id?: string }>,
provider: string,
modelId: string,
) => catalog.find((entry) => entry.provider === provider && entry.id === modelId),
}));
vi.mock("../agents/prepared-model-catalog.js", () => ({
loadProviderScopedThinkingCatalog: vi.fn(async () => []),
readPreparedModelCatalog: mocks.loadModelCatalog,
}));
vi.mock("../agents/model-selection.js", async (importOriginal) => ({
...(await importOriginal<typeof import("../agents/model-selection.js")>()),
resolveDefaultModelForAgent: mocks.resolveDefaultModelForAgent,
}));
vi.mock("../agents/agent-bundle-mcp-tools.js", () => ({
createBundleMcpToolRuntime: mocks.createBundleMcpToolRuntime,
}));
vi.mock("../agents/agent-tools.js", () => ({
createOpenClawCodingTools: mocks.createOpenClawCodingTools,
}));
vi.mock("../plugins/provider-runtime.js", () => ({
inspectProviderToolSchemasWithPlugin: () => [],
normalizeProviderToolSchemasWithPlugin: mocks.normalizeProviderToolSchemasWithPlugin,
}));
const { collectRuntimeToolSchemaFindings } = await import("./doctor-core-checks.runtime.js");
function tool(name: string, parameters: unknown): AnyAgentTool {
return {
name,
label: name,
description: name,
parameters,
execute: async () => ({ text: "ok" }),
} as unknown as AnyAgentTool;
}
function bundleMcpTool(name: string, parameters: unknown): AnyAgentTool {
const entry = tool(name, parameters);
setPluginToolMeta(entry, { pluginId: "bundle-mcp", optional: false });
return entry;
}
describe("doctor runtime tool schema error handling", () => {
beforeEach(() => {
mocks.createOpenClawCodingTools.mockReset().mockReturnValue([]);
mocks.createBundleMcpToolRuntime.mockReset().mockResolvedValue({
tools: [],
dispose: mocks.disposeBundleRuntime,
});
mocks.disposeBundleRuntime.mockReset().mockResolvedValue(undefined);
mocks.loadModelCatalog.mockClear();
mocks.normalizeProviderToolSchemasWithPlugin
.mockReset()
.mockImplementation(({ context }) => context.tools);
mocks.resolveDefaultModelForAgent.mockClear();
});
it("reports agent runtime tool construction failures without aborting schema checks", async () => {
mocks.createOpenClawCodingTools.mockImplementationOnce(() => {
throw new Error("fuzzplugin startup failed");
});
await expect(collectRuntimeToolSchemaFindings({})).resolves.toContainEqual({
checkId: "core/doctor/runtime-tool-schemas",
severity: "error",
message: "Agent main runtime tool schema validation could not load the runtime tool set.",
path: "agents.main.tools",
requirement: "fuzzplugin startup failed",
fixHint:
"Fix provider/plugin tool loading errors, then rerun doctor before relying on assistant tool startup.",
});
expect(mocks.createBundleMcpToolRuntime).toHaveBeenCalledTimes(1);
expect(mocks.disposeBundleRuntime).toHaveBeenCalledTimes(1);
});
it("reports agent runtime tool normalization failures without aborting doctor", async () => {
mocks.createOpenClawCodingTools.mockReturnValueOnce([
tool("fuzzplugin_move_angles", { type: "object", properties: {} }),
]);
mocks.normalizeProviderToolSchemasWithPlugin.mockImplementation(({ context }) => {
const tools = context.tools as AnyAgentTool[];
if (tools.some((entry) => entry.name === "fuzzplugin_move_angles")) {
throw new Error("fuzzplugin schema normalization failed");
}
return tools;
});
await expect(collectRuntimeToolSchemaFindings({})).resolves.toContainEqual({
checkId: "core/doctor/runtime-tool-schemas",
severity: "error",
message:
"Agent main runtime tool schema validation could not normalize the runtime tool set.",
path: "agents.main.tools",
requirement: "fuzzplugin schema normalization failed",
fixHint:
"Fix provider/plugin schema normalization errors, then rerun doctor before relying on assistant tool startup.",
});
expect(mocks.disposeBundleRuntime).toHaveBeenCalledTimes(1);
});
it("reports unreadable agent runtime tool schemas without aborting doctor", async () => {
const unreadable = tool("fuzzplugin_unreadable", { type: "object", properties: {} });
Object.defineProperty(unreadable, "parameters", {
enumerable: true,
get() {
throw new Error("fuzzplugin parameters getter exploded");
},
});
mocks.createOpenClawCodingTools.mockReturnValueOnce([
unreadable,
tool("healthy", { type: "object", properties: {} }),
]);
await expect(collectRuntimeToolSchemaFindings({})).resolves.toContainEqual({
checkId: "core/doctor/runtime-tool-schemas",
severity: "error",
message:
"Agent main tool fuzzplugin_unreadable has an unsupported input schema for runtime projection.",
path: "tools.fuzzplugin_unreadable",
target: "fuzzplugin_unreadable",
requirement: "fuzzplugin_unreadable.parameters is unreadable",
fixHint:
"Disable or update the offending plugin/tool so its parameters are a JSON object schema, then rerun doctor.",
});
expect(mocks.disposeBundleRuntime).toHaveBeenCalledTimes(1);
});
it("reports bundle MCP runtime tool normalization failures without aborting doctor", async () => {
mocks.createBundleMcpToolRuntime.mockResolvedValueOnce({
tools: [bundleMcpTool("fuzzplugin__move_angles", { type: "object", properties: {} })],
dispose: mocks.disposeBundleRuntime,
});
mocks.normalizeProviderToolSchemasWithPlugin.mockImplementation(({ context }) => {
const tools = context.tools as AnyAgentTool[];
if (tools.some((entry) => entry.name === "fuzzplugin__move_angles")) {
throw new Error("fuzzplugin MCP schema normalization failed");
}
return tools;
});
await expect(collectRuntimeToolSchemaFindings({})).resolves.toContainEqual({
checkId: "core/doctor/runtime-tool-schemas",
severity: "error",
message: "Configured MCP tool schema validation could not normalize the runtime tool set.",
path: "mcp.servers",
requirement: "fuzzplugin MCP schema normalization failed",
fixHint:
"Fix provider/plugin schema normalization errors, then rerun doctor before relying on assistant tool startup.",
});
expect(mocks.disposeBundleRuntime).toHaveBeenCalledTimes(1);
});
});
|