From e0030382614f4dc7e4d552def6fd2306afe53988 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 13 Mar 2026 18:19:46 +0000 Subject: [PATCH] refactor: share agent snapshot and scope test fixtures --- ...els-config.runtime-source-snapshot.test.ts | 389 +++++++----------- .../openclaw-tools.subagents.scope.test.ts | 145 +++---- 2 files changed, 210 insertions(+), 324 deletions(-) diff --git a/src/agents/models-config.runtime-source-snapshot.test.ts b/src/agents/models-config.runtime-source-snapshot.test.ts index cc033fb56a6..a80ac010e86 100644 --- a/src/agents/models-config.runtime-source-snapshot.test.ts +++ b/src/agents/models-config.runtime-source-snapshot.test.ts @@ -16,47 +16,137 @@ import { readGeneratedModelsJson } from "./models-config.test-utils.js"; installModelsConfigTestHooks(); +function createOpenAiApiKeySourceConfig(): OpenClawConfig { + return { + models: { + providers: { + openai: { + baseUrl: "https://api.openai.com/v1", + apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" }, // pragma: allowlist secret + api: "openai-completions" as const, + models: [], + }, + }, + }, + }; +} + +function createOpenAiApiKeyRuntimeConfig(): OpenClawConfig { + return { + models: { + providers: { + openai: { + baseUrl: "https://api.openai.com/v1", + apiKey: "sk-runtime-resolved", // pragma: allowlist secret + api: "openai-completions" as const, + models: [], + }, + }, + }, + }; +} + +function createOpenAiHeaderSourceConfig(): OpenClawConfig { + return { + models: { + providers: { + openai: { + baseUrl: "https://api.openai.com/v1", + api: "openai-completions" as const, + headers: { + Authorization: { + source: "env", + provider: "default", + id: "OPENAI_HEADER_TOKEN", // pragma: allowlist secret + }, + "X-Tenant-Token": { + source: "file", + provider: "vault", + id: "/providers/openai/tenantToken", + }, + }, + models: [], + }, + }, + }, + }; +} + +function createOpenAiHeaderRuntimeConfig(): OpenClawConfig { + return { + models: { + providers: { + openai: { + baseUrl: "https://api.openai.com/v1", + api: "openai-completions" as const, + headers: { + Authorization: "Bearer runtime-openai-token", + "X-Tenant-Token": "runtime-tenant-token", + }, + models: [], + }, + }, + }, + }; +} + +function withGatewayTokenMode(config: OpenClawConfig): OpenClawConfig { + return { + ...config, + gateway: { + auth: { + mode: "token", + }, + }, + }; +} + +async function withGeneratedModelsFromRuntimeSource( + params: { + sourceConfig: OpenClawConfig; + runtimeConfig: OpenClawConfig; + candidateConfig?: OpenClawConfig; + }, + runAssertions: () => Promise, +) { + await withTempHome(async () => { + try { + setRuntimeConfigSnapshot(params.runtimeConfig, params.sourceConfig); + await ensureOpenClawModelsJson(params.candidateConfig ?? loadConfig()); + await runAssertions(); + } finally { + clearRuntimeConfigSnapshot(); + clearConfigCache(); + } + }); +} + +async function expectGeneratedProviderApiKey(providerId: string, expected: string) { + const parsed = await readGeneratedModelsJson<{ + providers: Record; + }>(); + expect(parsed.providers[providerId]?.apiKey).toBe(expected); +} + +async function expectGeneratedOpenAiHeaderMarkers() { + const parsed = await readGeneratedModelsJson<{ + providers: Record }>; + }>(); + expect(parsed.providers.openai?.headers?.Authorization).toBe( + "secretref-env:OPENAI_HEADER_TOKEN", // pragma: allowlist secret + ); + expect(parsed.providers.openai?.headers?.["X-Tenant-Token"]).toBe(NON_ENV_SECRETREF_MARKER); +} + describe("models-config runtime source snapshot", () => { it("uses runtime source snapshot markers when passed the active runtime config", async () => { - await withTempHome(async () => { - const sourceConfig: OpenClawConfig = { - models: { - providers: { - openai: { - baseUrl: "https://api.openai.com/v1", - apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" }, // pragma: allowlist secret - api: "openai-completions" as const, - models: [], - }, - }, - }, - }; - const runtimeConfig: OpenClawConfig = { - models: { - providers: { - openai: { - baseUrl: "https://api.openai.com/v1", - apiKey: "sk-runtime-resolved", // pragma: allowlist secret - api: "openai-completions" as const, - models: [], - }, - }, - }, - }; - - try { - setRuntimeConfigSnapshot(runtimeConfig, sourceConfig); - await ensureOpenClawModelsJson(loadConfig()); - - const parsed = await readGeneratedModelsJson<{ - providers: Record; - }>(); - expect(parsed.providers.openai?.apiKey).toBe("OPENAI_API_KEY"); // pragma: allowlist secret - } finally { - clearRuntimeConfigSnapshot(); - clearConfigCache(); - } - }); + await withGeneratedModelsFromRuntimeSource( + { + sourceConfig: createOpenAiApiKeySourceConfig(), + runtimeConfig: createOpenAiApiKeyRuntimeConfig(), + }, + async () => expectGeneratedProviderApiKey("openai", "OPENAI_API_KEY"), // pragma: allowlist secret + ); }); it("uses non-env marker from runtime source snapshot for file refs", async () => { @@ -103,30 +193,8 @@ describe("models-config runtime source snapshot", () => { it("projects cloned runtime configs onto source snapshot when preserving provider auth", async () => { await withTempHome(async () => { - const sourceConfig: OpenClawConfig = { - models: { - providers: { - openai: { - baseUrl: "https://api.openai.com/v1", - apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" }, // pragma: allowlist secret - api: "openai-completions" as const, - models: [], - }, - }, - }, - }; - const runtimeConfig: OpenClawConfig = { - models: { - providers: { - openai: { - baseUrl: "https://api.openai.com/v1", - apiKey: "sk-runtime-resolved", // pragma: allowlist secret - api: "openai-completions" as const, - models: [], - }, - }, - }, - }; + const sourceConfig = createOpenAiApiKeySourceConfig(); + const runtimeConfig = createOpenAiApiKeyRuntimeConfig(); const clonedRuntimeConfig: OpenClawConfig = { ...runtimeConfig, agents: { @@ -139,11 +207,7 @@ describe("models-config runtime source snapshot", () => { try { setRuntimeConfigSnapshot(runtimeConfig, sourceConfig); await ensureOpenClawModelsJson(clonedRuntimeConfig); - - const parsed = await readGeneratedModelsJson<{ - providers: Record; - }>(); - expect(parsed.providers.openai?.apiKey).toBe("OPENAI_API_KEY"); // pragma: allowlist secret + await expectGeneratedProviderApiKey("openai", "OPENAI_API_KEY"); // pragma: allowlist secret } finally { clearRuntimeConfigSnapshot(); clearConfigCache(); @@ -152,121 +216,27 @@ describe("models-config runtime source snapshot", () => { }); it("uses header markers from runtime source snapshot instead of resolved runtime values", async () => { - await withTempHome(async () => { - const sourceConfig: OpenClawConfig = { - models: { - providers: { - openai: { - baseUrl: "https://api.openai.com/v1", - api: "openai-completions" as const, - headers: { - Authorization: { - source: "env", - provider: "default", - id: "OPENAI_HEADER_TOKEN", // pragma: allowlist secret - }, - "X-Tenant-Token": { - source: "file", - provider: "vault", - id: "/providers/openai/tenantToken", - }, - }, - models: [], - }, - }, - }, - }; - const runtimeConfig: OpenClawConfig = { - models: { - providers: { - openai: { - baseUrl: "https://api.openai.com/v1", - api: "openai-completions" as const, - headers: { - Authorization: "Bearer runtime-openai-token", - "X-Tenant-Token": "runtime-tenant-token", - }, - models: [], - }, - }, - }, - }; - - try { - setRuntimeConfigSnapshot(runtimeConfig, sourceConfig); - await ensureOpenClawModelsJson(loadConfig()); - - const parsed = await readGeneratedModelsJson<{ - providers: Record }>; - }>(); - expect(parsed.providers.openai?.headers?.Authorization).toBe( - "secretref-env:OPENAI_HEADER_TOKEN", // pragma: allowlist secret - ); - expect(parsed.providers.openai?.headers?.["X-Tenant-Token"]).toBe(NON_ENV_SECRETREF_MARKER); - } finally { - clearRuntimeConfigSnapshot(); - clearConfigCache(); - } - }); + await withGeneratedModelsFromRuntimeSource( + { + sourceConfig: createOpenAiHeaderSourceConfig(), + runtimeConfig: createOpenAiHeaderRuntimeConfig(), + }, + expectGeneratedOpenAiHeaderMarkers, + ); }); it("keeps source markers when runtime projection is skipped for incompatible top-level shape", async () => { await withTempHome(async () => { - const sourceConfig: OpenClawConfig = { - models: { - providers: { - openai: { - baseUrl: "https://api.openai.com/v1", - apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" }, // pragma: allowlist secret - api: "openai-completions" as const, - models: [], - }, - }, - }, - gateway: { - auth: { - mode: "token", - }, - }, - }; - const runtimeConfig: OpenClawConfig = { - models: { - providers: { - openai: { - baseUrl: "https://api.openai.com/v1", - apiKey: "sk-runtime-resolved", // pragma: allowlist secret - api: "openai-completions" as const, - models: [], - }, - }, - }, - gateway: { - auth: { - mode: "token", - }, - }, - }; + const sourceConfig = withGatewayTokenMode(createOpenAiApiKeySourceConfig()); + const runtimeConfig = withGatewayTokenMode(createOpenAiApiKeyRuntimeConfig()); const incompatibleCandidate: OpenClawConfig = { - models: { - providers: { - openai: { - baseUrl: "https://api.openai.com/v1", - apiKey: "sk-runtime-resolved", // pragma: allowlist secret - api: "openai-completions" as const, - models: [], - }, - }, - }, + ...createOpenAiApiKeyRuntimeConfig(), }; try { setRuntimeConfigSnapshot(runtimeConfig, sourceConfig); await ensureOpenClawModelsJson(incompatibleCandidate); - - const parsed = await readGeneratedModelsJson<{ - providers: Record; - }>(); - expect(parsed.providers.openai?.apiKey).toBe("OPENAI_API_KEY"); // pragma: allowlist secret + await expectGeneratedProviderApiKey("openai", "OPENAI_API_KEY"); // pragma: allowlist secret } finally { clearRuntimeConfigSnapshot(); clearConfigCache(); @@ -276,81 +246,16 @@ describe("models-config runtime source snapshot", () => { it("keeps source header markers when runtime projection is skipped for incompatible top-level shape", async () => { await withTempHome(async () => { - const sourceConfig: OpenClawConfig = { - models: { - providers: { - openai: { - baseUrl: "https://api.openai.com/v1", - api: "openai-completions" as const, - headers: { - Authorization: { - source: "env", - provider: "default", - id: "OPENAI_HEADER_TOKEN", // pragma: allowlist secret - }, - "X-Tenant-Token": { - source: "file", - provider: "vault", - id: "/providers/openai/tenantToken", - }, - }, - models: [], - }, - }, - }, - gateway: { - auth: { - mode: "token", - }, - }, - }; - const runtimeConfig: OpenClawConfig = { - models: { - providers: { - openai: { - baseUrl: "https://api.openai.com/v1", - api: "openai-completions" as const, - headers: { - Authorization: "Bearer runtime-openai-token", - "X-Tenant-Token": "runtime-tenant-token", - }, - models: [], - }, - }, - }, - gateway: { - auth: { - mode: "token", - }, - }, - }; + const sourceConfig = withGatewayTokenMode(createOpenAiHeaderSourceConfig()); + const runtimeConfig = withGatewayTokenMode(createOpenAiHeaderRuntimeConfig()); const incompatibleCandidate: OpenClawConfig = { - models: { - providers: { - openai: { - baseUrl: "https://api.openai.com/v1", - api: "openai-completions" as const, - headers: { - Authorization: "Bearer runtime-openai-token", - "X-Tenant-Token": "runtime-tenant-token", - }, - models: [], - }, - }, - }, + ...createOpenAiHeaderRuntimeConfig(), }; try { setRuntimeConfigSnapshot(runtimeConfig, sourceConfig); await ensureOpenClawModelsJson(incompatibleCandidate); - - const parsed = await readGeneratedModelsJson<{ - providers: Record }>; - }>(); - expect(parsed.providers.openai?.headers?.Authorization).toBe( - "secretref-env:OPENAI_HEADER_TOKEN", // pragma: allowlist secret - ); - expect(parsed.providers.openai?.headers?.["X-Tenant-Token"]).toBe(NON_ENV_SECRETREF_MARKER); + await expectGeneratedOpenAiHeaderMarkers(); } finally { clearRuntimeConfigSnapshot(); clearConfigCache(); diff --git a/src/agents/openclaw-tools.subagents.scope.test.ts b/src/agents/openclaw-tools.subagents.scope.test.ts index c985f1712e1..fc233015064 100644 --- a/src/agents/openclaw-tools.subagents.scope.test.ts +++ b/src/agents/openclaw-tools.subagents.scope.test.ts @@ -17,6 +17,63 @@ function writeStore(storePath: string, store: Record) { fs.writeFileSync(storePath, JSON.stringify(store, null, 2), "utf-8"); } +function seedLeafOwnedChildSession(storePath: string, leafKey = "agent:main:subagent:leaf") { + const childKey = `${leafKey}:subagent:child`; + writeStore(storePath, { + [leafKey]: { + sessionId: "leaf-session", + updatedAt: Date.now(), + spawnedBy: "agent:main:main", + subagentRole: "leaf", + subagentControlScope: "none", + }, + [childKey]: { + sessionId: "child-session", + updatedAt: Date.now(), + spawnedBy: leafKey, + subagentRole: "leaf", + subagentControlScope: "none", + }, + }); + + addSubagentRunForTests({ + runId: "run-child", + childSessionKey: childKey, + controllerSessionKey: leafKey, + requesterSessionKey: leafKey, + requesterDisplayKey: leafKey, + task: "impossible child", + cleanup: "keep", + createdAt: Date.now() - 30_000, + startedAt: Date.now() - 30_000, + }); + + return { + childKey, + tool: createSubagentsTool({ agentSessionKey: leafKey }), + }; +} + +async function expectLeafSubagentControlForbidden(params: { + storePath: string; + action: "kill" | "steer"; + callId: string; + message?: string; +}) { + const { childKey, tool } = seedLeafOwnedChildSession(params.storePath); + const result = await tool.execute(params.callId, { + action: params.action, + target: childKey, + ...(params.message ? { message: params.message } : {}), + }); + + expect(result.details).toMatchObject({ + status: "forbidden", + error: "Leaf subagents cannot control other sessions.", + }); + expect(callGatewayMock).not.toHaveBeenCalled(); +} + describe("openclaw-tools: subagents scope isolation", () => { let storePath = ""; @@ -151,95 +208,19 @@ describe("openclaw-tools: subagents scope isolation", () => { }); it("leaf subagents cannot kill even explicitly-owned child sessions", async () => { - const leafKey = "agent:main:subagent:leaf"; - const childKey = `${leafKey}:subagent:child`; - - writeStore(storePath, { - [leafKey]: { - sessionId: "leaf-session", - updatedAt: Date.now(), - spawnedBy: "agent:main:main", - subagentRole: "leaf", - subagentControlScope: "none", - }, - [childKey]: { - sessionId: "child-session", - updatedAt: Date.now(), - spawnedBy: leafKey, - subagentRole: "leaf", - subagentControlScope: "none", - }, - }); - - addSubagentRunForTests({ - runId: "run-child", - childSessionKey: childKey, - controllerSessionKey: leafKey, - requesterSessionKey: leafKey, - requesterDisplayKey: leafKey, - task: "impossible child", - cleanup: "keep", - createdAt: Date.now() - 30_000, - startedAt: Date.now() - 30_000, - }); - - const tool = createSubagentsTool({ agentSessionKey: leafKey }); - const result = await tool.execute("call-leaf-kill", { + await expectLeafSubagentControlForbidden({ + storePath, action: "kill", - target: childKey, + callId: "call-leaf-kill", }); - - expect(result.details).toMatchObject({ - status: "forbidden", - error: "Leaf subagents cannot control other sessions.", - }); - expect(callGatewayMock).not.toHaveBeenCalled(); }); it("leaf subagents cannot steer even explicitly-owned child sessions", async () => { - const leafKey = "agent:main:subagent:leaf"; - const childKey = `${leafKey}:subagent:child`; - - writeStore(storePath, { - [leafKey]: { - sessionId: "leaf-session", - updatedAt: Date.now(), - spawnedBy: "agent:main:main", - subagentRole: "leaf", - subagentControlScope: "none", - }, - [childKey]: { - sessionId: "child-session", - updatedAt: Date.now(), - spawnedBy: leafKey, - subagentRole: "leaf", - subagentControlScope: "none", - }, - }); - - addSubagentRunForTests({ - runId: "run-child", - childSessionKey: childKey, - controllerSessionKey: leafKey, - requesterSessionKey: leafKey, - requesterDisplayKey: leafKey, - task: "impossible child", - cleanup: "keep", - createdAt: Date.now() - 30_000, - startedAt: Date.now() - 30_000, - }); - - const tool = createSubagentsTool({ agentSessionKey: leafKey }); - const result = await tool.execute("call-leaf-steer", { + await expectLeafSubagentControlForbidden({ + storePath, action: "steer", - target: childKey, + callId: "call-leaf-steer", message: "continue", }); - - expect(result.details).toMatchObject({ - status: "forbidden", - error: "Leaf subagents cannot control other sessions.", - }); - expect(callGatewayMock).not.toHaveBeenCalled(); }); });