mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 11:25:50 -06:00
1af8bdd9ee
Co-authored-by: Peter Steinberger <steipete@mac-studio-sf2.local>
450 lines
15 KiB
TypeScript
450 lines
15 KiB
TypeScript
// Configured OpenClaw assistant tests cover route-owned, tool-free planning.
|
|
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
|
import type { RunCliAgentParams } from "../agents/cli-runner/types.js";
|
|
import { fingerprintResolvedProviderAuth } from "../agents/execution-auth-binding.js";
|
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
|
import { planSystemAgentCommandWithConfiguredModel } from "./assistant.js";
|
|
import { SystemAgentInferenceUnavailableError } from "./inference-error.js";
|
|
import { resolveSystemAgentConfiguredRouteFromConfig } from "./inference-route.js";
|
|
import type { SystemAgentOverview } from "./overview.js";
|
|
import {
|
|
createSystemAgentVerifiedInferenceTestFixture,
|
|
installSystemAgentClaudeCliBackendTestFixture,
|
|
} from "./system-agent.test-helpers.js";
|
|
import {
|
|
createSystemAgentVerifiedInferenceBinding,
|
|
type SystemAgentVerifiedInferenceBinding,
|
|
} from "./verified-inference.js";
|
|
|
|
const inferenceMocks = vi.hoisted(() => ({
|
|
fastBindings: new WeakSet<object>(),
|
|
}));
|
|
|
|
vi.mock("../plugins/providers.js", async (importOriginal) => ({
|
|
...(await importOriginal<typeof import("../plugins/providers.js")>()),
|
|
resolveOwningPluginIdsForModelRefs: vi.fn(() => []),
|
|
resolveOwningPluginIdsForProviderRef: vi.fn(() => []),
|
|
}));
|
|
|
|
vi.mock("../agents/harness/runtime-plugin.js", async (importOriginal) => ({
|
|
...(await importOriginal<typeof import("../agents/harness/runtime-plugin.js")>()),
|
|
resolveAgentHarnessOwnerPluginIds: vi.fn(({ runtime }: { runtime: string }) =>
|
|
runtime === "codex" ? ["codex"] : [],
|
|
),
|
|
}));
|
|
|
|
vi.mock("./verified-inference.js", async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import("./verified-inference.js")>();
|
|
return {
|
|
...actual,
|
|
resolveSystemAgentVerifiedInferenceRoute: (
|
|
...args: Parameters<typeof actual.resolveSystemAgentVerifiedInferenceRoute>
|
|
) => {
|
|
// These cases own planner dispatch, not repeated inference ownership checks.
|
|
// Unmarked bindings still exercise the real resolver for every drift boundary.
|
|
if (inferenceMocks.fastBindings.has(args[0])) {
|
|
return Promise.resolve(args[0].execution);
|
|
}
|
|
return actual.resolveSystemAgentVerifiedInferenceRoute(...args);
|
|
},
|
|
};
|
|
});
|
|
|
|
function useFastVerifiedInference(
|
|
binding: SystemAgentVerifiedInferenceBinding,
|
|
): SystemAgentVerifiedInferenceBinding {
|
|
inferenceMocks.fastBindings.add(binding);
|
|
return binding;
|
|
}
|
|
|
|
let restoreCliBackendFixture: (() => void) | undefined;
|
|
|
|
beforeAll(() => {
|
|
restoreCliBackendFixture = installSystemAgentClaudeCliBackendTestFixture();
|
|
});
|
|
|
|
afterAll(() => {
|
|
restoreCliBackendFixture?.();
|
|
});
|
|
|
|
function overview(defaultModel?: string): SystemAgentOverview {
|
|
return {
|
|
config: {
|
|
path: "/tmp/openclaw.json",
|
|
exists: true,
|
|
valid: true,
|
|
issues: [],
|
|
hash: "hash",
|
|
},
|
|
agents: [],
|
|
defaultAgentId: "main",
|
|
...(defaultModel ? { defaultModel } : {}),
|
|
tools: {
|
|
codex: { command: "codex", found: false },
|
|
claude: { command: "claude", found: false },
|
|
gemini: { command: "gemini", found: false },
|
|
apiKeys: { openai: false, anthropic: false },
|
|
},
|
|
gateway: { url: "ws://127.0.0.1:18789", source: "local loopback", reachable: false },
|
|
references: {
|
|
docsUrl: "https://docs.openclaw.ai",
|
|
sourceUrl: "https://github.com/openclaw/openclaw",
|
|
},
|
|
};
|
|
}
|
|
|
|
function snapshot(config: OpenClawConfig) {
|
|
return {
|
|
path: "/tmp/openclaw.json",
|
|
exists: true,
|
|
valid: true,
|
|
hash: "hash",
|
|
config,
|
|
runtimeConfig: config,
|
|
sourceConfig: config,
|
|
issues: [],
|
|
};
|
|
}
|
|
|
|
describe("OpenClaw configured-model planner", () => {
|
|
it("rejects a low-level missing binding before config lookup or model execution", async () => {
|
|
const readConfigFileSnapshot = vi.fn();
|
|
const runCliAgent = vi.fn();
|
|
const runEmbeddedAgent = vi.fn();
|
|
|
|
await expect(
|
|
planSystemAgentCommandWithConfiguredModel({
|
|
input: "please finish setup",
|
|
overview: overview(),
|
|
verifiedInference: undefined as never,
|
|
deps: { readConfigFileSnapshot, runCliAgent, runEmbeddedAgent },
|
|
}),
|
|
).rejects.toBeInstanceOf(SystemAgentInferenceUnavailableError);
|
|
|
|
expect(readConfigFileSnapshot).not.toHaveBeenCalled();
|
|
expect(runCliAgent).not.toHaveBeenCalled();
|
|
expect(runEmbeddedAgent).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("plans through the exact verified profile instead of re-running auth selection", async () => {
|
|
const config = {
|
|
agents: {
|
|
defaults: {
|
|
model: "openai/gpt-5.5",
|
|
models: { "openai/gpt-5.5": { agentRuntime: { id: "openclaw" } } },
|
|
},
|
|
},
|
|
auth: {
|
|
profiles: { "openai:p2": { provider: "openai", mode: "api_key" } },
|
|
},
|
|
} satisfies OpenClawConfig;
|
|
const configuredRoute = await resolveSystemAgentConfiguredRouteFromConfig(config);
|
|
if (!configuredRoute) {
|
|
throw new Error("missing test route");
|
|
}
|
|
const authDeps = {
|
|
ensureAuthProfileStore: vi.fn(() => ({
|
|
version: 1,
|
|
profiles: {
|
|
"openai:p2": { type: "api_key", provider: "openai", key: "test-key" },
|
|
},
|
|
})) as never,
|
|
resolveApiKeyForProvider: vi.fn(async () => ({
|
|
apiKey: "test-key",
|
|
profileId: "openai:p2",
|
|
source: "profile:openai:p2",
|
|
mode: "api-key" as const,
|
|
})),
|
|
};
|
|
const authFingerprint = fingerprintResolvedProviderAuth({
|
|
apiKey: "test-key",
|
|
profileId: "openai:p2",
|
|
source: "profile:openai:p2",
|
|
mode: "api-key",
|
|
});
|
|
if (!authFingerprint) {
|
|
throw new Error("missing test auth fingerprint");
|
|
}
|
|
const binding = useFastVerifiedInference(
|
|
await createSystemAgentVerifiedInferenceBinding({
|
|
configuredRoute,
|
|
executionRoute: { ...configuredRoute, authProfileId: "openai:p2" },
|
|
auth: {
|
|
authProfileId: "openai:p2",
|
|
authFingerprint,
|
|
modelId: configuredRoute.model,
|
|
modelApi: "openai-responses",
|
|
},
|
|
deps: authDeps,
|
|
}),
|
|
);
|
|
const runEmbeddedAgent = vi.fn(async () => ({
|
|
payloads: [{ text: '{"reply":"Ready.","command":"gateway status"}' }],
|
|
}));
|
|
|
|
const result = await planSystemAgentCommandWithConfiguredModel({
|
|
input: "check the gateway",
|
|
overview: overview("openai/gpt-5.5"),
|
|
verifiedInference: binding,
|
|
deps: {
|
|
...authDeps,
|
|
readConfigFileSnapshot: vi.fn(async () => snapshot(config)) as never,
|
|
runEmbeddedAgent: runEmbeddedAgent as never,
|
|
createTempDir: async () => "/tmp/openclaw-planner",
|
|
removeTempDir: async () => {},
|
|
},
|
|
});
|
|
|
|
expect(result?.modelLabel).toBe("openai/gpt-5.5");
|
|
expect(runEmbeddedAgent).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
authProfileId: "openai:p2",
|
|
authProfileIdSource: "user",
|
|
config: binding.execution.runConfig,
|
|
streamParams: {
|
|
responseFormat: {
|
|
type: "object",
|
|
properties: {
|
|
reply: { type: "string" },
|
|
command: { type: "string" },
|
|
},
|
|
required: ["reply"],
|
|
additionalProperties: false,
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("fails closed before planning when the verified route loses its config", async () => {
|
|
const config = {
|
|
agents: { defaults: { model: "openai/gpt-5.5" } },
|
|
} satisfies OpenClawConfig;
|
|
const { binding, deps } = await createSystemAgentVerifiedInferenceTestFixture(config);
|
|
const runCliAgent = vi.fn();
|
|
const runEmbeddedAgent = vi.fn();
|
|
const readConfigFileSnapshot = vi.fn(async () => ({
|
|
...snapshot(config),
|
|
exists: false,
|
|
}));
|
|
|
|
await expect(
|
|
planSystemAgentCommandWithConfiguredModel({
|
|
input: "please set up my model",
|
|
overview: overview(),
|
|
verifiedInference: binding,
|
|
deps: {
|
|
...deps,
|
|
readConfigFileSnapshot: readConfigFileSnapshot as never,
|
|
runCliAgent,
|
|
runEmbeddedAgent,
|
|
},
|
|
}),
|
|
).rejects.toBeInstanceOf(SystemAgentInferenceUnavailableError);
|
|
|
|
expect(readConfigFileSnapshot).toHaveBeenCalledOnce();
|
|
expect(runCliAgent).not.toHaveBeenCalled();
|
|
expect(runEmbeddedAgent).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("rejects a model result when its owner changes during planner cleanup", async () => {
|
|
const config = {
|
|
agents: { defaults: { model: "openai/gpt-5.5" } },
|
|
} satisfies OpenClawConfig;
|
|
const changedConfig = {
|
|
agents: { defaults: { model: "anthropic/claude-opus-4-8" } },
|
|
} satisfies OpenClawConfig;
|
|
const { binding, deps } = await createSystemAgentVerifiedInferenceTestFixture(config);
|
|
let currentConfig: OpenClawConfig = config;
|
|
const runEmbeddedAgent = vi.fn(async () => ({
|
|
payloads: [{ text: '{"reply":"Ready."}' }],
|
|
}));
|
|
|
|
await expect(
|
|
planSystemAgentCommandWithConfiguredModel({
|
|
input: "is the gateway healthy",
|
|
overview: overview("openai/gpt-5.5"),
|
|
verifiedInference: binding,
|
|
deps: {
|
|
...deps,
|
|
readConfigFileSnapshot: vi.fn(async () => snapshot(currentConfig)) as never,
|
|
runEmbeddedAgent: runEmbeddedAgent as never,
|
|
createTempDir: async () => "/tmp/openclaw-planner",
|
|
removeTempDir: async () => {
|
|
currentConfig = changedConfig;
|
|
},
|
|
},
|
|
}),
|
|
).rejects.toBeInstanceOf(SystemAgentInferenceUnavailableError);
|
|
expect(runEmbeddedAgent).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("plans through the configured default agent CLI route with native tools disabled", async () => {
|
|
const config: OpenClawConfig = {
|
|
agents: {
|
|
defaults: {},
|
|
list: [
|
|
{
|
|
id: "ops",
|
|
default: true,
|
|
agentDir: "/tmp/ops-agent",
|
|
model: "claude-cli/claude-opus-4-8@claude-cli:ops",
|
|
},
|
|
],
|
|
},
|
|
};
|
|
const runCliAgent = vi.fn(async (_params: RunCliAgentParams) => ({
|
|
meta: {
|
|
finalAssistantVisibleText:
|
|
'{"reply":"I can do that.","command":"setup workspace /tmp/work"}',
|
|
},
|
|
}));
|
|
const removeTempDir = vi.fn(async () => {});
|
|
const { binding, deps } = await createSystemAgentVerifiedInferenceTestFixture(config);
|
|
useFastVerifiedInference(binding);
|
|
|
|
const result = await planSystemAgentCommandWithConfiguredModel({
|
|
input: "please finish setup",
|
|
overview: overview("claude-cli/claude-opus-4-8"),
|
|
verifiedInference: binding,
|
|
deps: {
|
|
...deps,
|
|
readConfigFileSnapshot: vi.fn(async () => snapshot(config)) as never,
|
|
runCliAgent: runCliAgent as never,
|
|
runEmbeddedAgent: vi.fn() as never,
|
|
createTempDir: async () => "/tmp/openclaw-planner",
|
|
removeTempDir,
|
|
},
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
reply: "I can do that.",
|
|
command: "setup workspace /tmp/work",
|
|
modelLabel: "claude-cli/claude-opus-4-8",
|
|
});
|
|
expect(runCliAgent).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
agentId: "ops",
|
|
provider: "claude-cli",
|
|
model: "claude-opus-4-8",
|
|
agentDir: "/tmp/ops-agent",
|
|
authProfileId: "claude-cli:ops",
|
|
executionMode: "side-question",
|
|
disableTools: true,
|
|
workspaceDir: "/tmp/openclaw-planner",
|
|
cwd: "/tmp/openclaw-planner",
|
|
cleanupCliLiveSessionOnRunEnd: true,
|
|
}),
|
|
);
|
|
expect(runCliAgent.mock.calls[0]?.[0]?.toolsAllow).toBeUndefined();
|
|
expect(removeTempDir).toHaveBeenCalledWith("/tmp/openclaw-planner");
|
|
});
|
|
|
|
it("plans through the configured default agent embedded runtime without tools", async () => {
|
|
const config: OpenClawConfig = {
|
|
agents: {
|
|
list: [
|
|
{
|
|
id: "ops",
|
|
default: true,
|
|
agentDir: "/tmp/ops-agent",
|
|
model: "openai/gpt-5.4@openai:ops",
|
|
models: { "openai/gpt-5.4": { agentRuntime: { id: "codex" } } },
|
|
},
|
|
],
|
|
},
|
|
};
|
|
const runEmbeddedAgent = vi.fn(async () => ({
|
|
payloads: [{ text: '{"reply":"Ready.","command":"gateway status"}' }],
|
|
}));
|
|
const { binding, deps } = await createSystemAgentVerifiedInferenceTestFixture(config);
|
|
useFastVerifiedInference(binding);
|
|
|
|
const result = await planSystemAgentCommandWithConfiguredModel({
|
|
input: "is the gateway healthy",
|
|
overview: overview("openai/gpt-5.4"),
|
|
verifiedInference: binding,
|
|
deps: {
|
|
...deps,
|
|
readConfigFileSnapshot: vi.fn(async () => snapshot(config)) as never,
|
|
runCliAgent: vi.fn() as never,
|
|
runEmbeddedAgent: runEmbeddedAgent as never,
|
|
createTempDir: async () => "/tmp/openclaw-planner",
|
|
removeTempDir: async () => {},
|
|
resolveAssistantTimeoutMs: () => 120_000,
|
|
},
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
reply: "Ready.",
|
|
command: "gateway status",
|
|
modelLabel: "openai/gpt-5.4",
|
|
});
|
|
expect(runEmbeddedAgent).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
provider: "openai",
|
|
model: "gpt-5.4",
|
|
agentDir: "/tmp/ops-agent",
|
|
authProfileId: "openai:ops",
|
|
authProfileIdSource: "user",
|
|
agentHarnessRuntimeOverride: "codex",
|
|
disableTools: true,
|
|
disableTrajectory: true,
|
|
toolsAllow: [],
|
|
thinkLevel: "off",
|
|
timeoutMs: 120_000,
|
|
}),
|
|
);
|
|
expect(runEmbeddedAgent).toHaveBeenCalledWith(
|
|
expect.not.objectContaining({ sessionKey: expect.anything() }),
|
|
);
|
|
});
|
|
|
|
it("carries the verified child runtime artifact into planning", async () => {
|
|
const config = {
|
|
agents: {
|
|
list: [
|
|
{
|
|
id: "ops",
|
|
default: true,
|
|
agentDir: "/tmp/ops-agent",
|
|
model: "openai/gpt-5.5",
|
|
models: { "openai/gpt-5.5": { agentRuntime: { id: "codex" } } },
|
|
},
|
|
],
|
|
},
|
|
} satisfies OpenClawConfig;
|
|
const { binding, deps } = await createSystemAgentVerifiedInferenceTestFixture(config);
|
|
useFastVerifiedInference(binding);
|
|
const runEmbeddedAgent = vi.fn(async () => ({
|
|
payloads: [{ text: '{"reply":"Ready.","command":"gateway status"}' }],
|
|
}));
|
|
|
|
await planSystemAgentCommandWithConfiguredModel({
|
|
input: "is the gateway healthy",
|
|
overview: overview("openai/gpt-5.5"),
|
|
verifiedInference: binding,
|
|
deps: {
|
|
...deps,
|
|
readConfigFileSnapshot: vi.fn(async () => snapshot(config)) as never,
|
|
runEmbeddedAgent: runEmbeddedAgent as never,
|
|
createTempDir: async () => "/tmp/openclaw-planner",
|
|
removeTempDir: async () => {},
|
|
},
|
|
});
|
|
|
|
expect(runEmbeddedAgent).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
agentHarnessRuntimeOverride: "codex",
|
|
expectedAgentHarnessRuntimeArtifact: {
|
|
harnessId: "codex",
|
|
artifact: {
|
|
id: "codex-test-artifact",
|
|
fingerprint: "codex-test-fingerprint",
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
});
|
|
});
|