Files
openclaw/src/system-agent/system-agent.test.ts
T
Pavan Kumar Gondhi d5e89de906 fix(synology-chat): deliver attachments without forwarding source URLs [AI] (#119941)
* fix(synology-chat): host outbound attachments

* fix(synology-chat): isolate hosted media routes

* fix(synology-chat): harden hosted media limits

* fix(synology-chat): inspect hosted media content

* fix(synology-chat): reject ambiguous media routes

* fix(synology-chat): retain indeterminate media capabilities

* fix(synology-chat): mask public callback URLs

* fix(synology-chat): align callback sensitivity metadata

* fix(synology-chat): reserve media capability query keys

* fix(synology-chat): release rejected staged media

* fix(synology-chat): preserve ambiguous media handoffs

* fix(synology-chat): scan complete active media preamble

* fix(synology-chat): report validated attachment readiness

* fix(synology-chat): bound public media capability probes

* fix(synology-chat): bound hosted media responses

* test(synology-chat): model response headers read-only

* fix(synology-chat): bound hosted media reads

* fix(synology-chat): bound hosted media delivery

* fix(synology-chat): retain media through active serves

* fix(plugin-sdk): lease hosted media readers atomically

* fix(synology-chat): close hosted media review gaps

* test(synology-chat): clean up hosted media state

* fix(system-agent): canonicalize sensitive config paths

* fix(system-agent): honor runtime config sensitivity hints

* fix(config): inherit sensitive metadata

* fix(synology-chat): close latest review findings

* test(system-agent): keep config recovery checks lint-clean

* fix(system-agent): redact structured config secrets

* fix(synology-chat): harden active-content sniffing

* fix(security): close hosted media review gaps

* fix(security): close remaining hosted media review findings

* fix(system-agent): narrow dynamic owner ids safely

* fix(system-agent): narrow dynamic channel ids safely

* test(channels): isolate hosted media proofs

* fix(security): close config and hosted media review gaps

* test(plugin-sdk): split outbound media retention coverage

* test(plugin-sdk): isolate capacity store fixtures

* test(system-agent): assert config secrecy invariant
2026-08-14 17:05:20 -05:00

496 lines
16 KiB
TypeScript

// OpenClaw tests cover main rescue and audit command behavior.
import { beforeAll, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { SystemAgentInferenceUnavailableError } from "./inference-error.js";
import type { SystemAgentCommandDeps } from "./operations.js";
import type { SystemAgentOverview } from "./overview.js";
import { runSystemAgent, type RunSystemAgentOptions } from "./system-agent.js";
import { createSystemAgentTestRuntime } from "./system-agent.runtime.test-support.js";
import { createSystemAgentVerifiedInferenceTestFixture } from "./system-agent.test-helpers.js";
import type { SystemAgentVerifiedInferenceBinding } from "./verified-inference.js";
const inferenceMocks = vi.hoisted(() => ({
sharedVerifiedInference: undefined as SystemAgentVerifiedInferenceBinding | undefined,
}));
vi.mock("../plugins/providers.js", async (importOriginal) => ({
...(await importOriginal<typeof import("../plugins/providers.js")>()),
resolveOwningPluginIdsForModelRefs: vi.fn(() => []),
resolveOwningPluginIdsForProviderRef: vi.fn(() => []),
}));
vi.mock("./verified-inference.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./verified-inference.js")>();
return {
...actual,
resolveSystemAgentVerifiedInferenceRoute: (
...args: Parameters<typeof actual.resolveSystemAgentVerifiedInferenceRoute>
) => {
// Ordinary runner cases share one immutable verified fixture. Distinct
// bindings still use the real resolver for route and apply-boundary drift.
if (args[0] === inferenceMocks.sharedVerifiedInference) {
return Promise.resolve(args[0].execution);
}
return actual.resolveSystemAgentVerifiedInferenceRoute(...args);
},
};
});
const overview: SystemAgentOverview = {
defaultAgentId: "main",
defaultModel: "openai/gpt-5.5",
agents: [{ id: "main", isDefault: true, model: "openai/gpt-5.5" }],
config: { path: "/tmp/openclaw.json", exists: true, valid: true, issues: [], hash: null },
tools: {
codex: { command: "codex", found: false, error: "not found" },
claude: { command: "claude", found: false, error: "not found" },
gemini: { command: "gemini", found: false, error: "not found" },
apiKeys: { openai: true, anthropic: false },
},
gateway: {
url: "ws://127.0.0.1:18789",
source: "local loopback",
reachable: false,
error: "offline",
},
references: {
docsUrl: "https://docs.openclaw.ai",
sourceUrl: "https://github.com/openclaw/openclaw",
},
};
const systemAgentOverviewDeps = {
formatOverview: () => "Default model: openai/gpt-5.5",
loadOverview: async () => overview,
};
const verifiedConfig = {
agents: { defaults: { model: "openai/gpt-5.5" } },
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
apiKey: "test-key",
auth: "api-key",
models: [],
},
},
},
} satisfies OpenClawConfig;
function configSnapshot(config: OpenClawConfig) {
return {
exists: true,
valid: true,
path: "/tmp/openclaw.json",
hash: "h",
config,
runtimeConfig: config,
sourceConfig: config,
issues: [],
};
}
let sharedVerifiedFixture: Awaited<
ReturnType<typeof createSystemAgentVerifiedInferenceTestFixture>
>;
beforeAll(async () => {
sharedVerifiedFixture = await createSystemAgentVerifiedInferenceTestFixture(verifiedConfig);
inferenceMocks.sharedVerifiedInference = sharedVerifiedFixture.binding;
});
function createVerifiedRunOptions(
deps: SystemAgentCommandDeps = {},
options: { revalidate?: boolean } = {},
) {
const fixture = sharedVerifiedFixture;
return {
verifiedInference: options.revalidate ? { ...fixture.binding } : fixture.binding,
deps: {
...fixture.deps,
readConfigFileSnapshot: vi.fn(async () => configSnapshot(verifiedConfig)) as never,
...deps,
},
};
}
describe("runSystemAgent", () => {
it("rejects a missing inference binding before any runner side effect", async () => {
const { runtime } = createSystemAgentTestRuntime();
const loadOverview = vi.fn(async () => overview);
const planWithAssistant = vi.fn(async () => ({ command: "restart gateway" }));
const runGatewayRestart = vi.fn(async () => {});
const runInteractiveTui = vi.fn(async () => {});
const common = {
deps: { loadOverview, runGatewayRestart },
planWithAssistant,
runInteractiveTui,
input: { isTTY: true } as unknown as NodeJS.ReadableStream,
output: { isTTY: true } as unknown as NodeJS.WritableStream,
};
const withoutBinding = (opts: Omit<RunSystemAgentOptions, "verifiedInference">) =>
opts as RunSystemAgentOptions;
await expect(
runSystemAgent(withoutBinding({ ...common, json: true }), runtime),
).rejects.toBeInstanceOf(SystemAgentInferenceUnavailableError);
await expect(
runSystemAgent(withoutBinding({ ...common, message: "please make things nicer" }), runtime),
).rejects.toBeInstanceOf(SystemAgentInferenceUnavailableError);
await expect(
runSystemAgent(withoutBinding({ ...common, message: "restart gateway", yes: true }), runtime),
).rejects.toBeInstanceOf(SystemAgentInferenceUnavailableError);
await expect(runSystemAgent(withoutBinding(common), runtime)).rejects.toBeInstanceOf(
SystemAgentInferenceUnavailableError,
);
expect(loadOverview).not.toHaveBeenCalled();
expect(planWithAssistant).not.toHaveBeenCalled();
expect(runGatewayRestart).not.toHaveBeenCalled();
expect(runInteractiveTui).not.toHaveBeenCalled();
});
it("uses the assistant planner only to choose typed operations", async () => {
const { runtime, lines } = createSystemAgentTestRuntime();
let runGatewayRestartCalls = 0;
let onReadyCalls = 0;
const verified = createVerifiedRunOptions({
runGatewayRestart: async () => {
runGatewayRestartCalls += 1;
},
});
await runSystemAgent(
{
...verified,
message: "the local bridge looks sleepy, poke it",
onReady: () => {
onReadyCalls += 1;
},
planWithAssistant: async () => ({
reply: "I can queue a Gateway restart.",
command: "restart gateway",
modelLabel: "openai/gpt-5.5",
}),
...systemAgentOverviewDeps,
},
runtime,
);
expect(runGatewayRestartCalls).toBe(0);
expect(onReadyCalls).toBe(0);
expect(lines.join("\n")).toContain("[openclaw] planner: openai/gpt-5.5");
expect(lines.join("\n")).toContain("[openclaw] interpreted: restart gateway");
expect(lines.join("\n")).toContain("Plan: restart the Gateway. Say yes to apply.");
expect(lines.indexOf("Default model: openai/gpt-5.5")).toBeLessThan(
lines.findIndex((line) => line.includes("[openclaw] planner:")),
);
});
it.each([
"config set gateway.auth.token=very-secret",
"config set gateway.auth.token=very-secret please",
String.raw`config set gateway.auth.token\ very-secret please`,
"config set gateway.auth.tokenabcDEF123 please",
"config set gateway.auth.token_abcDEF123 please",
"config set gateway.auth.token$abcDEF123 please",
"config set-ref gateway.auth.tokenabcDEF123 env GATEWAY_TOKEN",
'config set gateway.auth["token:very-secret"] please',
])(
"keeps malformed config write %s away from the one-shot assistant planner",
async (message) => {
const { runtime, lines } = createSystemAgentTestRuntime();
const planWithAssistant = vi.fn(async () => ({ command: "restart gateway" }));
await runSystemAgent(
{
...createVerifiedRunOptions(),
message,
planWithAssistant,
...systemAgentOverviewDeps,
},
runtime,
);
expect(planWithAssistant).not.toHaveBeenCalled();
expect(lines.join("\n")).toContain("Invalid config path");
expect(lines.join("\n")).not.toContain("very-secret");
expect(lines.join("\n")).not.toContain("abcDEF123");
},
);
it("does not apply a one-shot plan after the verified route changes", async () => {
const { runtime } = createSystemAgentTestRuntime();
const changedConfig = {
agents: { defaults: { model: "anthropic/claude-opus-4-8" } },
} satisfies OpenClawConfig;
const readConfigFileSnapshot = vi
.fn()
.mockResolvedValueOnce(configSnapshot(verifiedConfig))
.mockResolvedValue(configSnapshot(changedConfig));
const runGatewayRestart = vi.fn(async () => {});
const verified = createVerifiedRunOptions(
{
readConfigFileSnapshot: readConfigFileSnapshot as never,
runGatewayRestart,
},
{ revalidate: true },
);
await expect(
runSystemAgent(
{
...verified,
message: "the bridge looks sleepy, restart it",
yes: true,
planWithAssistant: async () => ({ command: "restart gateway" }),
...systemAgentOverviewDeps,
},
runtime,
),
).rejects.toBeInstanceOf(SystemAgentInferenceUnavailableError);
expect(runGatewayRestart).not.toHaveBeenCalled();
});
it("rechecks one-shot authority at the persistent apply boundary", async () => {
const { runtime } = createSystemAgentTestRuntime();
const changedConfig = {
agents: { defaults: { model: "anthropic/claude-opus-4-8" } },
} satisfies OpenClawConfig;
const readConfigFileSnapshot = vi
.fn()
.mockResolvedValueOnce(configSnapshot(verifiedConfig))
.mockResolvedValueOnce(configSnapshot(verifiedConfig))
.mockResolvedValueOnce(configSnapshot(verifiedConfig))
.mockResolvedValue(configSnapshot(changedConfig));
const runGatewayRestart = vi.fn(async () => {});
const verified = createVerifiedRunOptions(
{
readConfigFileSnapshot: readConfigFileSnapshot as never,
runGatewayRestart,
},
{ revalidate: true },
);
await expect(
runSystemAgent(
{
...verified,
message: "the bridge looks sleepy, restart it",
yes: true,
planWithAssistant: async () => ({ command: "restart gateway" }),
...systemAgentOverviewDeps,
},
runtime,
),
).rejects.toBeInstanceOf(SystemAgentInferenceUnavailableError);
expect(readConfigFileSnapshot).toHaveBeenCalledTimes(4);
expect(runGatewayRestart).not.toHaveBeenCalled();
});
it("keeps exact one-shot parsing ahead of the assistant planner", async () => {
const { runtime, lines } = createSystemAgentTestRuntime();
let plannerCalls = 0;
let onReadyCalls = 0;
const verified = createVerifiedRunOptions();
await runSystemAgent(
{
...verified,
message: "models",
planWithAssistant: async () => {
plannerCalls += 1;
return { command: "restart gateway" };
},
onReady: () => {
onReadyCalls += 1;
},
...systemAgentOverviewDeps,
},
runtime,
);
expect(plannerCalls).toBe(0);
expect(onReadyCalls).toBe(0);
expect(lines.join("\n")).toContain("Default model:");
});
it("prints an explicit one-shot overview exactly once", async () => {
const { runtime, lines } = createSystemAgentTestRuntime();
const verified = createVerifiedRunOptions();
await runSystemAgent(
{
...verified,
message: "overview",
formatOverview: () => "formatted overview",
loadOverview: async () => overview,
},
runtime,
);
expect(lines).toEqual(["formatted overview"]);
});
it.each([
{ name: "no plan", plan: null },
{ name: "invalid command", plan: { command: "invent a new operation" } },
])("fails a fuzzy one-shot when inference returns $name", async ({ plan }) => {
const { runtime } = createSystemAgentTestRuntime();
const verified = createVerifiedRunOptions();
await expect(
runSystemAgent(
{
...verified,
message: "please make things nicer",
planWithAssistant: async () => plan,
...systemAgentOverviewDeps,
},
runtime,
),
).rejects.toBeInstanceOf(SystemAgentInferenceUnavailableError);
});
it("prints a valid reply-only one-shot plan", async () => {
const { runtime, lines } = createSystemAgentTestRuntime();
const verified = createVerifiedRunOptions();
await runSystemAgent(
{
...verified,
message: "explain the current setup",
planWithAssistant: async () => ({ reply: "The current setup is healthy." }),
...systemAgentOverviewDeps,
},
runtime,
);
expect(lines).toEqual(["Default model: openai/gpt-5.5", "", "The current setup is healthy."]);
});
it("does not print a reply-only plan after its inference owner drifts", async () => {
const { runtime, lines } = createSystemAgentTestRuntime();
const changedConfig = {
agents: { defaults: { model: "anthropic/claude-opus-4-8" } },
} satisfies OpenClawConfig;
let currentConfig: OpenClawConfig = verifiedConfig;
const verified = createVerifiedRunOptions(
{
readConfigFileSnapshot: vi.fn(async () => configSnapshot(currentConfig)) as never,
},
{ revalidate: true },
);
await expect(
runSystemAgent(
{
...verified,
message: "explain the current setup",
planWithAssistant: async () => {
currentConfig = changedConfig;
return { reply: "stale reply" };
},
...systemAgentOverviewDeps,
},
runtime,
),
).rejects.toBeInstanceOf(SystemAgentInferenceUnavailableError);
expect(lines).not.toContain("stale reply");
});
it("starts interactive OpenClaw in the TUI shell", async () => {
const { runtime, lines } = createSystemAgentTestRuntime();
let runInteractiveTuiCalls = 0;
let onReadyCalls = 0;
const verified = createVerifiedRunOptions();
await runSystemAgent(
{
...verified,
input: { isTTY: true } as unknown as NodeJS.ReadableStream,
output: { isTTY: true } as unknown as NodeJS.WritableStream,
runInteractiveTui: async () => {
runInteractiveTuiCalls += 1;
},
onReady: () => {
onReadyCalls += 1;
},
},
runtime,
);
expect(runInteractiveTuiCalls).toBe(1);
expect(onReadyCalls).toBe(1);
expect(lines.join("\n")).not.toContain("Say: status");
});
it("prints the formatted overview exactly once when interactive mode is disabled", async () => {
const { runtime, lines } = createSystemAgentTestRuntime();
let loadOverviewCalls = 0;
let runInteractiveTuiCalls = 0;
const verified = createVerifiedRunOptions();
await runSystemAgent(
{
...verified,
interactive: false,
loadOverview: async () => {
loadOverviewCalls += 1;
return overview;
},
formatOverview: () => "formatted overview",
runInteractiveTui: async () => {
runInteractiveTuiCalls += 1;
},
},
runtime,
);
expect(loadOverviewCalls).toBe(1);
expect(runInteractiveTuiCalls).toBe(0);
expect(lines).toEqual(["formatted overview"]);
});
it.each([
{
name: "stdin is not a TTY",
input: { isTTY: false } as unknown as NodeJS.ReadableStream,
output: { isTTY: true } as unknown as NodeJS.WritableStream,
interactive: true,
},
{
name: "stdout is not a TTY",
input: { isTTY: true } as unknown as NodeJS.ReadableStream,
output: { isTTY: false } as unknown as NodeJS.WritableStream,
interactive: true,
},
])("exits non-zero when $name", async ({ input, output, interactive }) => {
const { runtime, lines } = createSystemAgentTestRuntime();
let runInteractiveTuiCalls = 0;
const verified = createVerifiedRunOptions();
await expect(
runSystemAgent(
{
...verified,
input,
output,
interactive,
runInteractiveTui: async () => {
runInteractiveTuiCalls += 1;
},
},
runtime,
),
).rejects.toThrow("exit 1");
expect(runInteractiveTuiCalls).toBe(0);
expect(lines.join("\n")).toContain(
"OpenClaw needs an interactive TTY. Use --message for one command.",
);
});
});