mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
fix(doctor): prevent false provider failures after fresh setup (#114533)
This commit is contained in:
committed by
GitHub
parent
3f37731175
commit
c068f2b2df
@@ -746,7 +746,7 @@ vi.mock("./doctor/shared/missing-configured-plugin-install.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("./doctor/shared/active-tool-schema-warnings.js", () => ({
|
||||
collectActiveToolSchemaProjectionWarnings: vi.fn(() => []),
|
||||
collectActiveToolSchemaProjectionWarnings: vi.fn(async () => []),
|
||||
}));
|
||||
|
||||
vi.mock("./doctor/shared/plugin-dependency-cleanup.js", () => ({
|
||||
|
||||
@@ -288,7 +288,7 @@ describe("doctor repair sequencing", () => {
|
||||
changes: [],
|
||||
warnings: [],
|
||||
});
|
||||
mocks.collectActiveToolSchemaProjectionWarnings.mockReturnValue([]);
|
||||
mocks.collectActiveToolSchemaProjectionWarnings.mockResolvedValue([]);
|
||||
mocks.collectChannelDoctorCompatibilityMutations.mockReturnValue([]);
|
||||
mocks.resolveAuthProfileOrder.mockReturnValue([]);
|
||||
mocks.resolveProfileUnusableUntilForDisplay.mockReturnValue(null);
|
||||
@@ -615,7 +615,7 @@ describe("doctor repair sequencing", () => {
|
||||
});
|
||||
|
||||
it("emits active tool schema projection warnings during doctor repair", async () => {
|
||||
mocks.collectActiveToolSchemaProjectionWarnings.mockReturnValueOnce([
|
||||
mocks.collectActiveToolSchemaProjectionWarnings.mockResolvedValueOnce([
|
||||
'- agents.main: active tool "fuzzplugin_move_angles" from plugin "fuzzplugin" has unsupported runtime input schema.',
|
||||
]);
|
||||
|
||||
|
||||
@@ -271,7 +271,7 @@ export async function runDoctorRepairSequence(params: {
|
||||
staleOAuthShadowRepair.changes.length > 0 ||
|
||||
authProfileSqliteMigration.changes.length > 0;
|
||||
|
||||
const activeToolSchemaWarnings = collectActiveToolSchemaProjectionWarnings({
|
||||
const activeToolSchemaWarnings = await collectActiveToolSchemaProjectionWarnings({
|
||||
cfg: state.candidate,
|
||||
env,
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ const toolState = vi.hoisted(() => ({
|
||||
compat?: Record<string, unknown>;
|
||||
} | null,
|
||||
resolveModelError: null as Error | null,
|
||||
resolveModel: vi.fn(),
|
||||
resolveModelAsync: vi.fn(),
|
||||
createTools: vi.fn<typeof createOpenClawCodingTools>(),
|
||||
normalizeTools: vi.fn(
|
||||
(options: { tools: AnyAgentTool[]; modelApi?: string; model?: unknown }) => options.tools,
|
||||
@@ -24,7 +24,7 @@ const toolState = vi.hoisted(() => ({
|
||||
}));
|
||||
|
||||
vi.mock("../../../agents/embedded-agent-runner/model.js", () => ({
|
||||
resolveModel: (...args: unknown[]) => toolState.resolveModel(...args),
|
||||
resolveModelAsync: (...args: unknown[]) => toolState.resolveModelAsync(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../../../agents/agent-tools.js", () => ({
|
||||
@@ -69,7 +69,7 @@ describe("active tool schema doctor warnings", () => {
|
||||
toolState.throwError = null;
|
||||
toolState.runtimeModel = null;
|
||||
toolState.resolveModelError = null;
|
||||
toolState.resolveModel.mockReset().mockImplementation(() => {
|
||||
toolState.resolveModelAsync.mockReset().mockImplementation(async () => {
|
||||
if (toolState.resolveModelError) {
|
||||
throw toolState.resolveModelError;
|
||||
}
|
||||
@@ -83,7 +83,7 @@ describe("active tool schema doctor warnings", () => {
|
||||
toolState.normalizeTools.mockReset().mockImplementation((options) => options.tools);
|
||||
});
|
||||
|
||||
it("warns with plugin ownership for active tools blocked by runtime projection", () => {
|
||||
it("warns with plugin ownership for active tools blocked by runtime projection", async () => {
|
||||
toolState.tools = [
|
||||
tool("message", { type: "object", properties: {} }),
|
||||
tool("fuzzplugin_move_angles", { type: "array", items: { type: "number" } }),
|
||||
@@ -91,7 +91,7 @@ describe("active tool schema doctor warnings", () => {
|
||||
toolState.pluginIds = { fuzzplugin_move_angles: "fuzzplugin" };
|
||||
|
||||
expect(
|
||||
collectActiveToolSchemaProjectionWarnings({
|
||||
await collectActiveToolSchemaProjectionWarnings({
|
||||
cfg: {
|
||||
plugins: {
|
||||
entries: {
|
||||
@@ -109,7 +109,7 @@ describe("active tool schema doctor warnings", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("warns about unreadable active tool entries without crashing", () => {
|
||||
it("warns about unreadable active tool entries without crashing", async () => {
|
||||
const healthy = tool("message", { type: "object", properties: {} });
|
||||
toolState.tools = new Proxy([healthy] as AnyAgentTool[], {
|
||||
get(target, property, receiver) {
|
||||
@@ -127,7 +127,7 @@ describe("active tool schema doctor warnings", () => {
|
||||
});
|
||||
|
||||
expect(
|
||||
collectActiveToolSchemaProjectionWarnings({
|
||||
await collectActiveToolSchemaProjectionWarnings({
|
||||
cfg: {},
|
||||
env: { HOME: "/tmp/openclaw-test" },
|
||||
}),
|
||||
@@ -136,14 +136,14 @@ describe("active tool schema doctor warnings", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not validate disabled plugin mode", () => {
|
||||
it("does not validate disabled plugin mode", async () => {
|
||||
toolState.tools = [
|
||||
tool("fuzzplugin_move_angles", { type: "array", items: { type: "number" } }),
|
||||
];
|
||||
toolState.pluginIds = { fuzzplugin_move_angles: "fuzzplugin" };
|
||||
|
||||
expect(
|
||||
collectActiveToolSchemaProjectionWarnings({
|
||||
await collectActiveToolSchemaProjectionWarnings({
|
||||
cfg: { plugins: { enabled: false } },
|
||||
env: { HOME: "/tmp/openclaw-test" },
|
||||
}),
|
||||
@@ -152,7 +152,47 @@ describe("active tool schema doctor warnings", () => {
|
||||
expect(toolState.normalizeTools).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("validates provider-normalized runtime schemas before reporting doctor health", () => {
|
||||
it("resolves provider discovery asynchronously before agent lifecycle publication", async () => {
|
||||
toolState.runtimeModel = {
|
||||
id: "llama-3.1-8b-instant",
|
||||
name: "Llama 3.1 8B Instant",
|
||||
provider: "groq",
|
||||
api: "openai-completions",
|
||||
};
|
||||
const cfg = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "groq/llama-3.1-8b-instant" },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
await collectActiveToolSchemaProjectionWarnings({
|
||||
cfg,
|
||||
env: { HOME: "/tmp/openclaw-test" },
|
||||
}),
|
||||
).toEqual([]);
|
||||
expect(toolState.resolveModelAsync).toHaveBeenCalledWith(
|
||||
"groq",
|
||||
"llama-3.1-8b-instant",
|
||||
expect.any(String),
|
||||
cfg,
|
||||
expect.objectContaining({
|
||||
agentId: "main",
|
||||
workspaceDir: expect.any(String),
|
||||
}),
|
||||
);
|
||||
expect(toolState.createTools).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
modelProvider: "groq",
|
||||
modelId: "llama-3.1-8b-instant",
|
||||
modelApi: "openai-completions",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("validates provider-normalized runtime schemas before reporting doctor health", async () => {
|
||||
const healthyTool = tool("message", { type: "object", properties: {} });
|
||||
const dynamicTool = tool("fuzzplugin_move_angles", { type: "object", properties: {} });
|
||||
toolState.runtimeModel = {
|
||||
@@ -186,7 +226,7 @@ describe("active tool schema doctor warnings", () => {
|
||||
});
|
||||
|
||||
expect(
|
||||
collectActiveToolSchemaProjectionWarnings({
|
||||
await collectActiveToolSchemaProjectionWarnings({
|
||||
cfg: {
|
||||
agents: {
|
||||
defaults: {
|
||||
@@ -219,14 +259,14 @@ describe("active tool schema doctor warnings", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("reports runtime schema normalization failures instead of crashing doctor", () => {
|
||||
it("reports runtime schema normalization failures instead of crashing doctor", async () => {
|
||||
toolState.tools = [tool("message", { type: "object", properties: {} })];
|
||||
toolState.normalizeTools.mockImplementation(() => {
|
||||
throw new Error("provider schema hook failed");
|
||||
});
|
||||
|
||||
expect(
|
||||
collectActiveToolSchemaProjectionWarnings({
|
||||
await collectActiveToolSchemaProjectionWarnings({
|
||||
cfg: {},
|
||||
env: { HOME: "/tmp/openclaw-test" },
|
||||
}),
|
||||
@@ -235,12 +275,12 @@ describe("active tool schema doctor warnings", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("reports runtime model context failures instead of crashing doctor", () => {
|
||||
it("reports runtime model context failures instead of crashing doctor", async () => {
|
||||
toolState.resolveModelError = new Error("provider model hook failed");
|
||||
toolState.tools = [tool("message", { type: "object", properties: {} })];
|
||||
|
||||
expect(
|
||||
collectActiveToolSchemaProjectionWarnings({
|
||||
await collectActiveToolSchemaProjectionWarnings({
|
||||
cfg: {},
|
||||
env: { HOME: "/tmp/openclaw-test" },
|
||||
}),
|
||||
@@ -251,11 +291,11 @@ describe("active tool schema doctor warnings", () => {
|
||||
expect(toolState.normalizeTools).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("reports toolset construction failures instead of crashing doctor", () => {
|
||||
it("reports toolset construction failures instead of crashing doctor", async () => {
|
||||
toolState.throwError = new Error("plugin startup failed");
|
||||
|
||||
expect(
|
||||
collectActiveToolSchemaProjectionWarnings({
|
||||
await collectActiveToolSchemaProjectionWarnings({
|
||||
cfg: { plugins: { entries: { fuzzplugin: { enabled: true } } } },
|
||||
env: { HOME: "/tmp/openclaw-test" },
|
||||
}),
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
resolveAgentWorkspaceDir,
|
||||
} from "../../../agents/agent-scope.js";
|
||||
import { createOpenClawCodingTools } from "../../../agents/agent-tools.js";
|
||||
import { resolveModel } from "../../../agents/embedded-agent-runner/model.js";
|
||||
import { resolveModelAsync } from "../../../agents/embedded-agent-runner/model.js";
|
||||
import { normalizeAgentRuntimeTools } from "../../../agents/runtime-plan/tools.js";
|
||||
import {
|
||||
filterRuntimeCompatibleTools,
|
||||
@@ -22,23 +22,34 @@ import type { ProviderRuntimeModel } from "../../../plugins/provider-runtime-mod
|
||||
import { getPluginToolMeta } from "../../../plugins/tools.js";
|
||||
import { resolveDoctorPrimaryModelRef } from "./primary-model-ref.js";
|
||||
|
||||
function resolveRuntimeModelContext(params: {
|
||||
type RuntimeModelContext = {
|
||||
modelApi?: string;
|
||||
model?: ProviderRuntimeModel;
|
||||
modelCompat?: ReturnType<typeof extractModelCompat>;
|
||||
modelContextWindowTokens?: number;
|
||||
};
|
||||
|
||||
async function resolveRuntimeModelContext(params: {
|
||||
cfg: OpenClawConfig;
|
||||
agentId: string;
|
||||
agentDir: string;
|
||||
workspaceDir: string;
|
||||
provider: string;
|
||||
modelId: string;
|
||||
}): {
|
||||
modelApi?: string;
|
||||
model?: ProviderRuntimeModel;
|
||||
modelCompat?: ReturnType<typeof extractModelCompat>;
|
||||
modelContextWindowTokens?: number;
|
||||
} {
|
||||
const model = resolveModel(params.provider, params.modelId, params.agentDir, params.cfg, {
|
||||
agentId: params.agentId,
|
||||
workspaceDir: params.workspaceDir,
|
||||
}).model as ProviderRuntimeModel | undefined;
|
||||
}): Promise<RuntimeModelContext> {
|
||||
// Doctor runs before agent lifecycle publication; async resolution prepares discovery instead
|
||||
// of reporting an unpublished synchronous runtime as a broken provider.
|
||||
const resolution = await resolveModelAsync(
|
||||
params.provider,
|
||||
params.modelId,
|
||||
params.agentDir,
|
||||
params.cfg,
|
||||
{
|
||||
agentId: params.agentId,
|
||||
workspaceDir: params.workspaceDir,
|
||||
},
|
||||
);
|
||||
const model = resolution.model as ProviderRuntimeModel | undefined;
|
||||
if (!model) {
|
||||
return {};
|
||||
}
|
||||
@@ -80,10 +91,10 @@ function readPluginId(tool: AnyAgentTool | undefined): string | undefined {
|
||||
}
|
||||
|
||||
/** Collect per-agent warnings for active plugin tools rejected by runtime schema projection. */
|
||||
export function collectActiveToolSchemaProjectionWarnings(params: {
|
||||
export async function collectActiveToolSchemaProjectionWarnings(params: {
|
||||
cfg: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): string[] {
|
||||
}): Promise<string[]> {
|
||||
if (params.cfg.plugins?.enabled === false) {
|
||||
return [];
|
||||
}
|
||||
@@ -95,9 +106,9 @@ export function collectActiveToolSchemaProjectionWarnings(params: {
|
||||
const modelRef = resolveDoctorPrimaryModelRef(params.cfg, agentConfig?.model);
|
||||
const agentDir = resolveAgentDir(params.cfg, agentId, env);
|
||||
const workspaceDir = resolveAgentWorkspaceDir(params.cfg, agentId, env);
|
||||
let runtimeModelContext: ReturnType<typeof resolveRuntimeModelContext> = {};
|
||||
let runtimeModelContext: RuntimeModelContext = {};
|
||||
try {
|
||||
runtimeModelContext = resolveRuntimeModelContext({
|
||||
runtimeModelContext = await resolveRuntimeModelContext({
|
||||
cfg: params.cfg,
|
||||
agentId,
|
||||
agentDir,
|
||||
|
||||
@@ -315,7 +315,7 @@ vi.mock("./stale-auth-order.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("./active-tool-schema-warnings.js", () => ({
|
||||
collectActiveToolSchemaProjectionWarnings: () => activeToolSchemaState.warnings,
|
||||
collectActiveToolSchemaProjectionWarnings: async () => activeToolSchemaState.warnings,
|
||||
}));
|
||||
|
||||
vi.mock("./codex-route-warnings.js", () => ({
|
||||
|
||||
@@ -722,7 +722,7 @@ export async function collectDoctorPreviewNotes(params: {
|
||||
warnings.push(...collectProfileConfiguredToolSectionWarnings(params.cfg));
|
||||
const { collectActiveToolSchemaProjectionWarnings } =
|
||||
await import("./active-tool-schema-warnings.js");
|
||||
warnings.push(...collectActiveToolSchemaProjectionWarnings({ cfg: params.cfg, env }));
|
||||
warnings.push(...(await collectActiveToolSchemaProjectionWarnings({ cfg: params.cfg, env })));
|
||||
|
||||
const channelPluginRuntime = await import("./channel-plugin-blockers.js");
|
||||
const channelPluginBlockerHits = channelPluginRuntime.scanConfiguredChannelPluginBlockers(
|
||||
|
||||
Reference in New Issue
Block a user