From 134ff61754cd5dfc75245410794708d00e0433ae Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 6 Apr 2026 19:51:48 +0100 Subject: [PATCH] test: stabilize agent auth and config suites --- extensions/nostr/index.ts | 3 +- .../auth-profiles/external-oauth.test.ts | 14 +- src/agents/cli-backends.test.ts | 18 +- src/agents/command/session-store.test.ts | 14 +- src/agents/live-auth-keys.test.ts | 16 +- src/agents/live-target-matcher.test.ts | 13 +- src/agents/model-auth-markers.test.ts | 47 ++- src/agents/model-auth.profiles.test.ts | 101 +++--- src/agents/model-auth.test.ts | 144 ++++---- src/agents/model-compat.test.ts | 12 +- ...ssing-provider-apikey-from-env-var.test.ts | 16 +- src/agents/models-config.merge.test.ts | 54 ++- .../models-config.providers.moonshot.test.ts | 16 +- .../models-config.providers.policy.test.ts | 18 +- .../models-config.providers.qianfan.test.ts | 16 +- ...config.providers.vercel-ai-gateway.test.ts | 26 +- ...nfig.providers.volcengine-byteplus.test.ts | 22 +- ...els-config.runtime-source-snapshot.test.ts | 22 +- .../provider-error-patterns.test.ts | 14 +- ...runner.openai-tool-id-preservation.test.ts | 16 +- ...er.sanitize-session-history.policy.test.ts | 16 +- ...ed-runner.sanitize-session-history.test.ts | 128 +++---- .../pi-embedded-runner/cache-ttl.test.ts | 42 ++- ...orward-compat.errors-and-overrides.test.ts | 26 +- ....spawn-workspace.context-injection.test.ts | 4 + .../attempt.spawn-workspace.test-support.ts | 55 ++- .../pi-tools.workspace-only-false.test.ts | 9 +- .../simple-completion-transport.test.ts | 12 +- src/agents/tools/message-tool.test.ts | 70 ++++ src/agents/tools/music-generate-tool.test.ts | 3 - src/agents/transcript-policy.test.ts | 328 +++++++++--------- src/cli/command-secret-gateway.test.ts | 16 +- ...command-secret-resolution.coverage.test.ts | 21 +- .../gateway-cli/run.option-collisions.test.ts | 2 +- src/infra/provider-usage.load.plugin.test.ts | 14 +- src/utils/provider-utils.test.ts | 12 +- test/extension-test-boundary.test.ts | 5 +- 37 files changed, 853 insertions(+), 512 deletions(-) diff --git a/extensions/nostr/index.ts b/extensions/nostr/index.ts index 234d59b07cd..eeeec9ca3e4 100644 --- a/extensions/nostr/index.ts +++ b/extensions/nostr/index.ts @@ -2,8 +2,7 @@ import { defineBundledChannelEntry, loadBundledEntryExportSync, } from "openclaw/plugin-sdk/channel-entry-contract"; -import type { PluginRuntime } from "./api.js"; -import type { ResolvedNostrAccount } from "./api.js"; +import type { PluginRuntime, ResolvedNostrAccount } from "./api.js"; function createNostrProfileHttpHandler() { return loadBundledEntryExportSync< diff --git a/src/agents/auth-profiles/external-oauth.test.ts b/src/agents/auth-profiles/external-oauth.test.ts index 77c4837e53c..bede365da22 100644 --- a/src/agents/auth-profiles/external-oauth.test.ts +++ b/src/agents/auth-profiles/external-oauth.test.ts @@ -6,10 +6,16 @@ const resolveExternalAuthProfilesWithPluginsMock = vi.fn< (params: unknown) => ProviderExternalAuthProfile[] >(() => []); -vi.mock("../../plugins/provider-runtime.js", () => ({ - resolveExternalAuthProfilesWithPlugins: (params: unknown) => - resolveExternalAuthProfilesWithPluginsMock(params), -})); +vi.mock("../../plugins/provider-runtime.js", async () => { + const actual = await vi.importActual( + "../../plugins/provider-runtime.js", + ); + return { + ...actual, + resolveExternalAuthProfilesWithPlugins: (params: unknown) => + resolveExternalAuthProfilesWithPluginsMock(params), + }; +}); function createStore(profiles: AuthProfileStore["profiles"] = {}): AuthProfileStore { return { version: 1, profiles }; diff --git a/src/agents/cli-backends.test.ts b/src/agents/cli-backends.test.ts index 32b7482ba94..be42308f5d0 100644 --- a/src/agents/cli-backends.test.ts +++ b/src/agents/cli-backends.test.ts @@ -1,9 +1,11 @@ -import { beforeEach, describe, expect, it } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; import type { CliBackendConfig } from "../config/types.js"; -import { createEmptyPluginRegistry } from "../plugins/registry.js"; -import { setActivePluginRegistry } from "../plugins/runtime.js"; -import { normalizeClaudeBackendConfig, resolveCliBackendConfig } from "./cli-backends.js"; + +let createEmptyPluginRegistry: typeof import("../plugins/registry.js").createEmptyPluginRegistry; +let setActivePluginRegistry: typeof import("../plugins/runtime.js").setActivePluginRegistry; +let normalizeClaudeBackendConfig: typeof import("./cli-backends.js").normalizeClaudeBackendConfig; +let resolveCliBackendConfig: typeof import("./cli-backends.js").resolveCliBackendConfig; function createBackendEntry(params: { pluginId: string; @@ -24,7 +26,13 @@ function createBackendEntry(params: { }; } -beforeEach(() => { +beforeEach(async () => { + vi.doUnmock("../plugins/setup-registry.js"); + vi.doUnmock("../plugins/cli-backends.runtime.js"); + vi.resetModules(); + ({ createEmptyPluginRegistry } = await import("../plugins/registry.js")); + ({ setActivePluginRegistry } = await import("../plugins/runtime.js")); + ({ normalizeClaudeBackendConfig, resolveCliBackendConfig } = await import("./cli-backends.js")); const registry = createEmptyPluginRegistry(); registry.cliBackends = [ createBackendEntry({ diff --git a/src/agents/command/session-store.test.ts b/src/agents/command/session-store.test.ts index 19ad241f7e2..5ed033ca790 100644 --- a/src/agents/command/session-store.test.ts +++ b/src/agents/command/session-store.test.ts @@ -20,8 +20,18 @@ describe("updateSessionStoreAfterAgentRun", () => { await fs.rm(tmpDir, { recursive: true, force: true }); }); - it("persists claude-cli session bindings without explicit cliBackends config", async () => { - const cfg = {} as OpenClawConfig; + it("persists claude-cli session bindings when the backend is configured", async () => { + const cfg = { + agents: { + defaults: { + cliBackends: { + "claude-cli": { + command: "claude", + }, + }, + }, + }, + } as OpenClawConfig; const sessionKey = "agent:main:explicit:test-claude-cli"; const sessionId = "test-openclaw-session"; const sessionStore: Record = { diff --git a/src/agents/live-auth-keys.test.ts b/src/agents/live-auth-keys.test.ts index 5f532a31734..01d134339ff 100644 --- a/src/agents/live-auth-keys.test.ts +++ b/src/agents/live-auth-keys.test.ts @@ -1,11 +1,15 @@ -import { afterEach, describe, expect, it } from "vitest"; -import { collectProviderApiKeys } from "./live-auth-keys.js"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const ORIGINAL_MODELSTUDIO_API_KEY = process.env.MODELSTUDIO_API_KEY; const ORIGINAL_XAI_API_KEY = process.env.XAI_API_KEY; describe("collectProviderApiKeys", () => { + beforeEach(() => { + vi.doUnmock("../plugins/manifest-registry.js"); + }); + afterEach(() => { + vi.resetModules(); if (ORIGINAL_MODELSTUDIO_API_KEY === undefined) { delete process.env.MODELSTUDIO_API_KEY; } else { @@ -18,14 +22,18 @@ describe("collectProviderApiKeys", () => { } }); - it("honors manifest-declared provider auth env vars for nonstandard provider ids", () => { + it("honors manifest-declared provider auth env vars for nonstandard provider ids", async () => { process.env.MODELSTUDIO_API_KEY = "modelstudio-live-key"; + vi.resetModules(); + const { collectProviderApiKeys } = await import("./live-auth-keys.js"); expect(collectProviderApiKeys("alibaba")).toContain("modelstudio-live-key"); }); - it("dedupes manifest env vars against direct provider env naming", () => { + it("dedupes manifest env vars against direct provider env naming", async () => { process.env.XAI_API_KEY = "xai-live-key"; + vi.resetModules(); + const { collectProviderApiKeys } = await import("./live-auth-keys.js"); expect(collectProviderApiKeys("xai")).toEqual(["xai-live-key"]); }); diff --git a/src/agents/live-target-matcher.test.ts b/src/agents/live-target-matcher.test.ts index 03564a8727b..070cff67045 100644 --- a/src/agents/live-target-matcher.test.ts +++ b/src/agents/live-target-matcher.test.ts @@ -1,5 +1,14 @@ -import { describe, expect, it } from "vitest"; -import { createLiveTargetMatcher } from "./live-target-matcher.js"; +import { beforeAll, describe, expect, it, vi } from "vitest"; + +type CreateLiveTargetMatcher = typeof import("./live-target-matcher.js").createLiveTargetMatcher; +let createLiveTargetMatcher: CreateLiveTargetMatcher; + +beforeAll(async () => { + vi.doUnmock("../plugins/providers.js"); + vi.doUnmock("../plugins/manifest-registry.js"); + vi.resetModules(); + ({ createLiveTargetMatcher } = await import("./live-target-matcher.js")); +}); describe("createLiveTargetMatcher", () => { it("matches Anthropic-owned models for the claude-cli provider filter", () => { diff --git a/src/agents/model-auth-markers.test.ts b/src/agents/model-auth-markers.test.ts index 69a538b9600..cde057ba52a 100644 --- a/src/agents/model-auth-markers.test.ts +++ b/src/agents/model-auth-markers.test.ts @@ -1,41 +1,58 @@ -import { describe, expect, it } from "vitest"; -import { listKnownProviderEnvApiKeyNames } from "./model-auth-env-vars.js"; -import { - GCP_VERTEX_CREDENTIALS_MARKER, - isKnownEnvApiKeyMarker, - isNonSecretApiKeyMarker, - NON_ENV_SECRETREF_MARKER, - resolveOAuthApiKeyMarker, -} from "./model-auth-markers.js"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +async function loadMarkerModules() { + vi.doUnmock("../plugins/manifest-registry.js"); + vi.resetModules(); + return Promise.all([import("./model-auth-env-vars.js"), import("./model-auth-markers.js")]); +} + +beforeEach(() => { + vi.doUnmock("../plugins/manifest-registry.js"); +}); describe("model auth markers", () => { - it("recognizes explicit non-secret markers", () => { + it("recognizes explicit non-secret markers", async () => { + const [ + , + { + GCP_VERTEX_CREDENTIALS_MARKER, + NON_ENV_SECRETREF_MARKER, + isNonSecretApiKeyMarker, + resolveOAuthApiKeyMarker, + }, + ] = await loadMarkerModules(); expect(isNonSecretApiKeyMarker(NON_ENV_SECRETREF_MARKER)).toBe(true); expect(isNonSecretApiKeyMarker(resolveOAuthApiKeyMarker("chutes"))).toBe(true); expect(isNonSecretApiKeyMarker("ollama-local")).toBe(true); expect(isNonSecretApiKeyMarker(GCP_VERTEX_CREDENTIALS_MARKER)).toBe(true); }); - it("does not treat removed provider markers as active auth markers", () => { + it("does not treat removed provider markers as active auth markers", async () => { + const [, { isNonSecretApiKeyMarker }] = await loadMarkerModules(); expect(isNonSecretApiKeyMarker("qwen-oauth")).toBe(false); }); - it("recognizes known env marker names but not arbitrary all-caps keys", () => { + it("recognizes known env marker names but not arbitrary all-caps keys", async () => { + const [, { isNonSecretApiKeyMarker }] = await loadMarkerModules(); expect(isNonSecretApiKeyMarker("OPENAI_API_KEY")).toBe(true); expect(isNonSecretApiKeyMarker("ALLCAPS_EXAMPLE")).toBe(false); }); - it("recognizes all built-in provider env marker names", () => { + it("recognizes all built-in provider env marker names", async () => { + const [{ listKnownProviderEnvApiKeyNames }, { isNonSecretApiKeyMarker }] = + await loadMarkerModules(); for (const envVarName of listKnownProviderEnvApiKeyNames()) { expect(isNonSecretApiKeyMarker(envVarName)).toBe(true); } }); - it("can exclude env marker-name interpretation for display-only paths", () => { + it("can exclude env marker-name interpretation for display-only paths", async () => { + const [, { isNonSecretApiKeyMarker }] = await loadMarkerModules(); expect(isNonSecretApiKeyMarker("OPENAI_API_KEY", { includeEnvVarName: false })).toBe(false); }); - it("excludes aws-sdk env markers from known api key env marker helper", () => { + it("excludes aws-sdk env markers from known api key env marker helper", async () => { + const [, { isKnownEnvApiKeyMarker }] = await loadMarkerModules(); expect(isKnownEnvApiKeyMarker("OPENAI_API_KEY")).toBe(true); expect(isKnownEnvApiKeyMarker("AWS_PROFILE")).toBe(false); }); diff --git a/src/agents/model-auth.profiles.test.ts b/src/agents/model-auth.profiles.test.ts index dbee02ff39a..f12d7f48cb6 100644 --- a/src/agents/model-auth.profiles.test.ts +++ b/src/agents/model-auth.profiles.test.ts @@ -12,53 +12,62 @@ import { resolveEnvApiKey, } from "./model-auth.js"; -vi.mock("../plugins/provider-runtime.js", () => ({ - buildProviderMissingAuthMessageWithPlugin: (params: { - provider: string; - context: { listProfileIds: (providerId: string) => string[] }; - }) => { - if (params.provider === "openai" && params.context.listProfileIds("openai-codex").length > 0) { - return 'No API key found for provider "openai". Use openai-codex/gpt-5.4.'; - } - return undefined; - }, - formatProviderAuthProfileApiKeyWithPlugin: async () => undefined, - refreshProviderOAuthCredentialWithPlugin: async () => null, - resolveExternalAuthProfilesWithPlugins: () => [], - resolveProviderSyntheticAuthWithPlugin: (params: { - provider: string; - context: { providerConfig?: { api?: string; baseUrl?: string; models?: unknown[] } }; - }) => { - if (params.provider !== "ollama" && params.provider !== "demo-local") { +vi.mock("../plugins/provider-runtime.js", async () => { + const actual = await vi.importActual( + "../plugins/provider-runtime.js", + ); + return { + ...actual, + buildProviderMissingAuthMessageWithPlugin: (params: { + provider: string; + context: { listProfileIds: (providerId: string) => string[] }; + }) => { + if ( + params.provider === "openai" && + params.context.listProfileIds("openai-codex").length > 0 + ) { + return 'No API key found for provider "openai". Use openai-codex/gpt-5.4.'; + } return undefined; - } - const providerConfig = params.context.providerConfig; - const hasApiConfig = - Boolean(providerConfig?.api?.trim()) || - Boolean(providerConfig?.baseUrl?.trim()) || - (Array.isArray(providerConfig?.models) && providerConfig.models.length > 0); - if (!hasApiConfig) { - return undefined; - } - return { - apiKey: params.provider === "ollama" ? "ollama-local" : "demo-local", - source: `models.providers.${params.provider} (synthetic local key)`, - mode: "api-key" as const, - }; - }, - shouldDeferProviderSyntheticProfileAuthWithPlugin: (params: { - provider: string; - context: { resolvedApiKey?: string }; - }) => { - const expectedMarker = - params.provider === "ollama" - ? "ollama-local" - : params.provider === "demo-local" - ? "demo-local" - : undefined; - return Boolean(expectedMarker && params.context.resolvedApiKey?.trim() === expectedMarker); - }, -})); + }, + formatProviderAuthProfileApiKeyWithPlugin: async () => undefined, + refreshProviderOAuthCredentialWithPlugin: async () => null, + resolveExternalAuthProfilesWithPlugins: () => [], + resolveProviderSyntheticAuthWithPlugin: (params: { + provider: string; + context: { providerConfig?: { api?: string; baseUrl?: string; models?: unknown[] } }; + }) => { + if (params.provider !== "ollama" && params.provider !== "demo-local") { + return undefined; + } + const providerConfig = params.context.providerConfig; + const hasApiConfig = + Boolean(providerConfig?.api?.trim()) || + Boolean(providerConfig?.baseUrl?.trim()) || + (Array.isArray(providerConfig?.models) && providerConfig.models.length > 0); + if (!hasApiConfig) { + return undefined; + } + return { + apiKey: params.provider === "ollama" ? "ollama-local" : "demo-local", + source: `models.providers.${params.provider} (synthetic local key)`, + mode: "api-key" as const, + }; + }, + shouldDeferProviderSyntheticProfileAuthWithPlugin: (params: { + provider: string; + context: { resolvedApiKey?: string }; + }) => { + const expectedMarker = + params.provider === "ollama" + ? "ollama-local" + : params.provider === "demo-local" + ? "demo-local" + : undefined; + return Boolean(expectedMarker && params.context.resolvedApiKey?.trim() === expectedMarker); + }, + }; +}); vi.mock("./cli-credentials.js", () => ({ readCodexCliCredentialsCached: () => null, diff --git a/src/agents/model-auth.test.ts b/src/agents/model-auth.test.ts index bf49a8af69c..af74612b4f7 100644 --- a/src/agents/model-auth.test.ts +++ b/src/agents/model-auth.test.ts @@ -20,90 +20,96 @@ import { resolveUsableCustomProviderApiKey, } from "./model-auth.js"; -vi.mock("../plugins/provider-runtime.js", () => ({ - buildProviderMissingAuthMessageWithPlugin: () => undefined, - resolveExternalAuthProfilesWithPlugins: () => [], - shouldDeferProviderSyntheticProfileAuthWithPlugin: (params: { - provider: string; - context: { resolvedApiKey?: string }; - }) => params.provider === "ollama" && params.context.resolvedApiKey?.trim() === "ollama-local", - resolveProviderSyntheticAuthWithPlugin: (params: { - provider: string; - config?: { - plugins?: { - enabled?: boolean; - entries?: { - xai?: { - enabled?: boolean; - config?: { - webSearch?: { +vi.mock("../plugins/provider-runtime.js", async () => { + const actual = await vi.importActual( + "../plugins/provider-runtime.js", + ); + return { + ...actual, + buildProviderMissingAuthMessageWithPlugin: () => undefined, + resolveExternalAuthProfilesWithPlugins: () => [], + shouldDeferProviderSyntheticProfileAuthWithPlugin: (params: { + provider: string; + context: { resolvedApiKey?: string }; + }) => params.provider === "ollama" && params.context.resolvedApiKey?.trim() === "ollama-local", + resolveProviderSyntheticAuthWithPlugin: (params: { + provider: string; + config?: { + plugins?: { + enabled?: boolean; + entries?: { + xai?: { + enabled?: boolean; + config?: { + webSearch?: { + apiKey?: unknown; + }; + }; + }; + }; + }; + tools?: { + web?: { + search?: { + grok?: { apiKey?: unknown; }; }; }; }; }; - tools?: { - web?: { - search?: { - grok?: { - apiKey?: unknown; - }; + context: { providerConfig?: { api?: string; baseUrl?: string; models?: unknown[] } }; + }) => { + if (params.provider === "xai") { + if ( + params.config?.plugins?.enabled === false || + params.config?.plugins?.entries?.xai?.enabled === false + ) { + return undefined; + } + const pluginApiKey = params.config?.plugins?.entries?.xai?.config?.webSearch?.apiKey; + if (typeof pluginApiKey === "string" && pluginApiKey.trim()) { + return { + apiKey: pluginApiKey.trim(), + source: "plugins.entries.xai.config.webSearch.apiKey", + mode: "api-key" as const, }; - }; - }; - }; - context: { providerConfig?: { api?: string; baseUrl?: string; models?: unknown[] } }; - }) => { - if (params.provider === "xai") { - if ( - params.config?.plugins?.enabled === false || - params.config?.plugins?.entries?.xai?.enabled === false - ) { + } + if (pluginApiKey && typeof pluginApiKey === "object") { + return { + apiKey: NON_ENV_SECRETREF_MARKER, + source: "plugins.entries.xai.config.webSearch.apiKey", + mode: "api-key" as const, + }; + } return undefined; } - const pluginApiKey = params.config?.plugins?.entries?.xai?.config?.webSearch?.apiKey; - if (typeof pluginApiKey === "string" && pluginApiKey.trim()) { + if (params.provider === "claude-cli") { return { - apiKey: pluginApiKey.trim(), - source: "plugins.entries.xai.config.webSearch.apiKey", - mode: "api-key" as const, + apiKey: "claude-cli-access-token", + source: "Claude CLI native auth", + mode: "oauth" as const, }; } - if (pluginApiKey && typeof pluginApiKey === "object") { - return { - apiKey: NON_ENV_SECRETREF_MARKER, - source: "plugins.entries.xai.config.webSearch.apiKey", - mode: "api-key" as const, - }; + if (params.provider !== "ollama") { + return undefined; + } + const providerConfig = params.context.providerConfig; + const hasApiConfig = + Boolean(providerConfig?.api?.trim()) || + Boolean(providerConfig?.baseUrl?.trim()) || + (Array.isArray(providerConfig?.models) && providerConfig.models.length > 0); + if (!hasApiConfig) { + return undefined; } - return undefined; - } - if (params.provider === "claude-cli") { return { - apiKey: "claude-cli-access-token", - source: "Claude CLI native auth", - mode: "oauth" as const, + apiKey: "ollama-local", + source: "models.providers.ollama (synthetic local key)", + mode: "api-key" as const, }; - } - if (params.provider !== "ollama") { - return undefined; - } - const providerConfig = params.context.providerConfig; - const hasApiConfig = - Boolean(providerConfig?.api?.trim()) || - Boolean(providerConfig?.baseUrl?.trim()) || - (Array.isArray(providerConfig?.models) && providerConfig.models.length > 0); - if (!hasApiConfig) { - return undefined; - } - return { - apiKey: "ollama-local", - source: "models.providers.ollama (synthetic local key)", - mode: "api-key" as const, - }; - }, -})); + }, + }; +}); afterEach(() => { clearRuntimeConfigSnapshot(); diff --git a/src/agents/model-compat.test.ts b/src/agents/model-compat.test.ts index abbd035e3c3..154d68dee2d 100644 --- a/src/agents/model-compat.test.ts +++ b/src/agents/model-compat.test.ts @@ -5,9 +5,15 @@ const providerRuntimeMocks = vi.hoisted(() => ({ resolveProviderModernModelRef: vi.fn(), })); -vi.mock("../plugins/provider-runtime.js", () => ({ - resolveProviderModernModelRef: providerRuntimeMocks.resolveProviderModernModelRef, -})); +vi.mock("../plugins/provider-runtime.js", async () => { + const actual = await vi.importActual( + "../plugins/provider-runtime.js", + ); + return { + ...actual, + resolveProviderModernModelRef: providerRuntimeMocks.resolveProviderModernModelRef, + }; +}); import { normalizeModelCompat } from "../plugins/provider-model-compat.js"; import { diff --git a/src/agents/models-config.fills-missing-provider-apikey-from-env-var.test.ts b/src/agents/models-config.fills-missing-provider-apikey-from-env-var.test.ts index ec42d553f7c..22691661af1 100644 --- a/src/agents/models-config.fills-missing-provider-apikey-from-env-var.test.ts +++ b/src/agents/models-config.fills-missing-provider-apikey-from-env-var.test.ts @@ -1,8 +1,18 @@ -import { describe, expect, it } from "vitest"; -import { resolveMissingProviderApiKey } from "./models-config.providers.secrets.js"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +async function loadSecretsModule() { + vi.doUnmock("../plugins/manifest-registry.js"); + vi.resetModules(); + return import("./models-config.providers.secrets.js"); +} + +beforeEach(() => { + vi.doUnmock("../plugins/manifest-registry.js"); +}); describe("models-config", () => { - it("fills missing provider.apiKey from env var name when models exist", () => { + it("fills missing provider.apiKey from env var name when models exist", async () => { + const { resolveMissingProviderApiKey } = await loadSecretsModule(); const provider = resolveMissingProviderApiKey({ providerKey: "minimax", provider: { diff --git a/src/agents/models-config.merge.test.ts b/src/agents/models-config.merge.test.ts index 96df4f94e3f..0d31aa2bd9a 100644 --- a/src/agents/models-config.merge.test.ts +++ b/src/agents/models-config.merge.test.ts @@ -1,13 +1,17 @@ -import { describe, expect, it } from "vitest"; -import { NON_ENV_SECRETREF_MARKER } from "./model-auth-markers.js"; -import { - mergeProviderModels, - mergeProviders, - mergeWithExistingProviderSecrets, - type ExistingProviderConfig, -} from "./models-config.merge.js"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { ExistingProviderConfig } from "./models-config.merge.js"; import type { ProviderConfig } from "./models-config.providers.secrets.js"; +async function loadMergeModules() { + vi.doUnmock("../plugins/manifest-registry.js"); + vi.resetModules(); + return Promise.all([import("./model-auth-markers.js"), import("./models-config.merge.js")]); +} + +beforeEach(() => { + vi.doUnmock("../plugins/manifest-registry.js"); +}); + describe("models-config merge helpers", () => { const preservedApiKey = "AGENT_KEY"; // pragma: allowlist secret const configApiKey = "CONFIG_KEY"; // pragma: allowlist secret @@ -46,7 +50,8 @@ describe("models-config merge helpers", () => { } as ExistingProviderConfig; } - it("refreshes implicit model metadata while preserving explicit reasoning overrides", () => { + it("refreshes implicit model metadata while preserving explicit reasoning overrides", async () => { + const [, { mergeProviderModels }] = await loadMergeModules(); const merged = mergeProviderModels( { api: "openai-responses", @@ -89,7 +94,8 @@ describe("models-config merge helpers", () => { ]); }); - it("merges explicit providers onto trimmed keys", () => { + it("merges explicit providers onto trimmed keys", async () => { + const [, { mergeProviders }] = await loadMergeModules(); const merged = mergeProviders({ explicit: { " custom ": { @@ -104,7 +110,8 @@ describe("models-config merge helpers", () => { }); }); - it("keeps existing providers alongside newly configured providers in merge mode", () => { + it("keeps existing providers alongside newly configured providers in merge mode", async () => { + const [, { mergeWithExistingProviderSecrets }] = await loadMergeModules(); const merged = mergeWithExistingProviderSecrets({ nextProviders: { "custom-proxy": { @@ -129,7 +136,8 @@ describe("models-config merge helpers", () => { expect(merged["custom-proxy"]?.baseUrl).toBe("http://localhost:4000/v1"); }); - it("preserves non-empty existing apiKey while explicit baseUrl wins", () => { + it("preserves non-empty existing apiKey while explicit baseUrl wins", async () => { + const [, { mergeWithExistingProviderSecrets }] = await loadMergeModules(); const merged = mergeWithExistingProviderSecrets({ nextProviders: { custom: createConfigProvider(), @@ -145,7 +153,8 @@ describe("models-config merge helpers", () => { expect(merged.custom?.baseUrl).toBe("https://config.example/v1"); }); - it("preserves existing apiKey after explicit provider key normalization", () => { + it("preserves existing apiKey after explicit provider key normalization", async () => { + const [, { mergeProviders, mergeWithExistingProviderSecrets }] = await loadMergeModules(); const normalized = mergeProviders({ explicit: { " custom ": createConfigProvider(), @@ -164,7 +173,8 @@ describe("models-config merge helpers", () => { expect(merged.custom?.baseUrl).toBe("https://config.example/v1"); }); - it("preserves implicit provider headers when explicit config adds extra headers", () => { + it("preserves implicit provider headers when explicit config adds extra headers", async () => { + const [, { mergeProviderModels }] = await loadMergeModules(); const merged = mergeProviderModels( { baseUrl: "https://api.example.com", @@ -200,7 +210,8 @@ describe("models-config merge helpers", () => { }); }); - it("replaces stale baseUrl when model api surface changes", () => { + it("replaces stale baseUrl when model api surface changes", async () => { + const [, { mergeWithExistingProviderSecrets }] = await loadMergeModules(); const merged = mergeWithExistingProviderSecrets({ nextProviders: { custom: { @@ -227,7 +238,8 @@ describe("models-config merge helpers", () => { ); }); - it("replaces stale baseUrl when only model-level apis change", () => { + it("replaces stale baseUrl when only model-level apis change", async () => { + const [, { mergeWithExistingProviderSecrets }] = await loadMergeModules(); const nextProvider = createConfigProvider(); delete (nextProvider as { api?: string }).api; nextProvider.models = [createModel({ api: "openai-responses" })]; @@ -250,7 +262,8 @@ describe("models-config merge helpers", () => { expect(merged.custom?.baseUrl).toBe("https://config.example/v1"); }); - it("does not preserve stale plaintext apiKey when next entry is a marker", () => { + it("does not preserve stale plaintext apiKey when next entry is a marker", async () => { + const [, { mergeWithExistingProviderSecrets }] = await loadMergeModules(); const merged = mergeWithExistingProviderSecrets({ nextProviders: { custom: { @@ -271,7 +284,9 @@ describe("models-config merge helpers", () => { expect(merged.custom?.apiKey).toBe("OPENAI_API_KEY"); // pragma: allowlist secret }); - it("does not preserve a stale non-env marker when config returns to plaintext", () => { + it("does not preserve a stale non-env marker when config returns to plaintext", async () => { + const [{ NON_ENV_SECRETREF_MARKER }, { mergeWithExistingProviderSecrets }] = + await loadMergeModules(); const merged = mergeWithExistingProviderSecrets({ nextProviders: { custom: createConfigProvider({ apiKey: "ALLCAPS_SAMPLE" }), // pragma: allowlist secret @@ -289,7 +304,8 @@ describe("models-config merge helpers", () => { expect(merged.custom?.baseUrl).toBe("https://config.example/v1"); }); - it("uses config apiKey/baseUrl when existing values are empty", () => { + it("uses config apiKey/baseUrl when existing values are empty", async () => { + const [, { mergeWithExistingProviderSecrets }] = await loadMergeModules(); const merged = mergeWithExistingProviderSecrets({ nextProviders: { custom: createConfigProvider(), diff --git a/src/agents/models-config.providers.moonshot.test.ts b/src/agents/models-config.providers.moonshot.test.ts index 1ca902d6add..a5ed90e9691 100644 --- a/src/agents/models-config.providers.moonshot.test.ts +++ b/src/agents/models-config.providers.moonshot.test.ts @@ -1,7 +1,16 @@ -import { describe, expect, it } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; import type { ModelProviderConfig } from "../config/types.models.js"; import { applyProviderNativeStreamingUsageCompat } from "../plugin-sdk/provider-catalog-shared.js"; -import { resolveMissingProviderApiKey } from "./models-config.providers.secrets.js"; + +async function loadSecretsModule() { + vi.doUnmock("../plugins/manifest-registry.js"); + vi.resetModules(); + return import("./models-config.providers.secrets.js"); +} + +beforeEach(() => { + vi.doUnmock("../plugins/manifest-registry.js"); +}); const MOONSHOT_BASE_URL = "https://api.moonshot.ai/v1"; const MOONSHOT_CN_BASE_URL = "https://api.moonshot.cn/v1"; @@ -57,7 +66,8 @@ describe("moonshot implicit provider (#33637)", () => { ).toBeUndefined(); }); - it("includes moonshot when MOONSHOT_API_KEY is configured", () => { + it("includes moonshot when MOONSHOT_API_KEY is configured", async () => { + const { resolveMissingProviderApiKey } = await loadSecretsModule(); const provider = resolveMissingProviderApiKey({ providerKey: "moonshot", provider: buildMoonshotProvider(), diff --git a/src/agents/models-config.providers.policy.test.ts b/src/agents/models-config.providers.policy.test.ts index 70a156c4d5e..82b14489b42 100644 --- a/src/agents/models-config.providers.policy.test.ts +++ b/src/agents/models-config.providers.policy.test.ts @@ -1,10 +1,13 @@ -import { describe, expect, it, vi } from "vitest"; -import { - normalizeProviderSpecificConfig, - resolveProviderConfigApiKeyResolver, -} from "./models-config.providers.policy.js"; +import { beforeAll, describe, expect, it, vi } from "vitest"; + +type NormalizeProviderSpecificConfig = + typeof import("./models-config.providers.policy.js").normalizeProviderSpecificConfig; +type ResolveProviderConfigApiKeyResolver = + typeof import("./models-config.providers.policy.js").resolveProviderConfigApiKeyResolver; const GOOGLE_BASE_URL = "https://generativelanguage.googleapis.com"; +let normalizeProviderSpecificConfig: NormalizeProviderSpecificConfig; +let resolveProviderConfigApiKeyResolver: ResolveProviderConfigApiKeyResolver; vi.mock("../plugins/provider-runtime.js", () => ({ applyProviderNativeStreamingUsageCompatWithPlugin: () => undefined, @@ -43,6 +46,11 @@ vi.mock("../plugins/provider-runtime.js", () => ({ }, })); +beforeAll(async () => { + ({ normalizeProviderSpecificConfig, resolveProviderConfigApiKeyResolver } = + await import("./models-config.providers.policy.js")); +}); + describe("models-config.providers.policy", () => { it("resolves config apiKey markers through provider plugin hooks", async () => { const env = { diff --git a/src/agents/models-config.providers.qianfan.test.ts b/src/agents/models-config.providers.qianfan.test.ts index 500cea98eeb..8003ed6ccd6 100644 --- a/src/agents/models-config.providers.qianfan.test.ts +++ b/src/agents/models-config.providers.qianfan.test.ts @@ -1,8 +1,18 @@ -import { describe, expect, it } from "vitest"; -import { createProviderAuthResolver } from "./models-config.providers.secrets.js"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +async function loadSecretsModule() { + vi.doUnmock("../plugins/manifest-registry.js"); + vi.resetModules(); + return import("./models-config.providers.secrets.js"); +} + +beforeEach(() => { + vi.doUnmock("../plugins/manifest-registry.js"); +}); describe("Qianfan provider", () => { - it("resolves QIANFAN_API_KEY markers through provider auth lookup", () => { + it("resolves QIANFAN_API_KEY markers through provider auth lookup", async () => { + const { createProviderAuthResolver } = await loadSecretsModule(); const resolveAuth = createProviderAuthResolver( { QIANFAN_API_KEY: "test-key", // pragma: allowlist secret diff --git a/src/agents/models-config.providers.vercel-ai-gateway.test.ts b/src/agents/models-config.providers.vercel-ai-gateway.test.ts index ca3c4b56367..a5eae8c6129 100644 --- a/src/agents/models-config.providers.vercel-ai-gateway.test.ts +++ b/src/agents/models-config.providers.vercel-ai-gateway.test.ts @@ -1,9 +1,21 @@ -import { describe, expect, it } from "vitest"; -import { NON_ENV_SECRETREF_MARKER } from "./model-auth-markers.js"; -import { createProviderAuthResolver } from "./models-config.providers.secrets.js"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +async function loadModules() { + vi.doUnmock("../plugins/manifest-registry.js"); + vi.resetModules(); + return Promise.all([ + import("./model-auth-markers.js"), + import("./models-config.providers.secrets.js"), + ]); +} + +beforeEach(() => { + vi.doUnmock("../plugins/manifest-registry.js"); +}); describe("vercel-ai-gateway provider resolution", () => { - it("resolves AI_GATEWAY_API_KEY through provider auth lookup", () => { + it("resolves AI_GATEWAY_API_KEY through provider auth lookup", async () => { + const [, { createProviderAuthResolver }] = await loadModules(); const resolveAuth = createProviderAuthResolver( { AI_GATEWAY_API_KEY: "vercel-gateway-test-key", // pragma: allowlist secret @@ -18,7 +30,8 @@ describe("vercel-ai-gateway provider resolution", () => { }); }); - it("prefers env keyRef markers over runtime plaintext in auth profiles", () => { + it("prefers env keyRef markers over runtime plaintext in auth profiles", async () => { + const [, { createProviderAuthResolver }] = await loadModules(); const resolveAuth = createProviderAuthResolver({} as NodeJS.ProcessEnv, { version: 1, profiles: { @@ -39,7 +52,8 @@ describe("vercel-ai-gateway provider resolution", () => { }); }); - it("uses non-env markers for non-env keyRef vercel profiles", () => { + it("uses non-env markers for non-env keyRef vercel profiles", async () => { + const [{ NON_ENV_SECRETREF_MARKER }, { createProviderAuthResolver }] = await loadModules(); const resolveAuth = createProviderAuthResolver({} as NodeJS.ProcessEnv, { version: 1, profiles: { diff --git a/src/agents/models-config.providers.volcengine-byteplus.test.ts b/src/agents/models-config.providers.volcengine-byteplus.test.ts index b3ed5275817..0c83c244f5f 100644 --- a/src/agents/models-config.providers.volcengine-byteplus.test.ts +++ b/src/agents/models-config.providers.volcengine-byteplus.test.ts @@ -1,8 +1,18 @@ -import { describe, expect, it } from "vitest"; -import { createProviderAuthResolver } from "./models-config.providers.secrets.js"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +async function loadSecretsModule() { + vi.doUnmock("../plugins/manifest-registry.js"); + vi.resetModules(); + return import("./models-config.providers.secrets.js"); +} + +beforeEach(() => { + vi.doUnmock("../plugins/manifest-registry.js"); +}); describe("Volcengine and BytePlus providers", () => { - it("shares VOLCANO_ENGINE_API_KEY across volcengine auth aliases", () => { + it("shares VOLCANO_ENGINE_API_KEY across volcengine auth aliases", async () => { + const { createProviderAuthResolver } = await loadSecretsModule(); const resolveAuth = createProviderAuthResolver( { VOLCANO_ENGINE_API_KEY: "test-key", // pragma: allowlist secret @@ -22,7 +32,8 @@ describe("Volcengine and BytePlus providers", () => { }); }); - it("shares BYTEPLUS_API_KEY across byteplus auth aliases", () => { + it("shares BYTEPLUS_API_KEY across byteplus auth aliases", async () => { + const { createProviderAuthResolver } = await loadSecretsModule(); const resolveAuth = createProviderAuthResolver( { BYTEPLUS_API_KEY: "test-key", // pragma: allowlist secret @@ -42,7 +53,8 @@ describe("Volcengine and BytePlus providers", () => { }); }); - it("reuses env keyRef markers from auth profiles for paired providers", () => { + it("reuses env keyRef markers from auth profiles for paired providers", async () => { + const { createProviderAuthResolver } = await loadSecretsModule(); const resolveAuth = createProviderAuthResolver({} as NodeJS.ProcessEnv, { version: 1, profiles: { diff --git a/src/agents/models-config.runtime-source-snapshot.test.ts b/src/agents/models-config.runtime-source-snapshot.test.ts index bf9506970ae..f1b77232e91 100644 --- a/src/agents/models-config.runtime-source-snapshot.test.ts +++ b/src/agents/models-config.runtime-source-snapshot.test.ts @@ -9,14 +9,20 @@ import { withTempEnv, } from "./models-config.e2e-harness.js"; -vi.mock("../plugins/provider-runtime.js", () => ({ - applyProviderConfigDefaultsWithPlugin: (config: OpenClawConfig) => config, - applyProviderNativeStreamingUsageCompatWithPlugin: () => undefined, - normalizeProviderConfigWithPlugin: () => undefined, - resetProviderRuntimeHookCacheForTest: () => undefined, - resolveProviderConfigApiKeyWithPlugin: () => undefined, - resolveProviderSyntheticAuthWithPlugin: () => undefined, -})); +vi.mock("../plugins/provider-runtime.js", async () => { + const actual = await vi.importActual( + "../plugins/provider-runtime.js", + ); + return { + ...actual, + applyProviderConfigDefaultsWithPlugin: (config: OpenClawConfig) => config, + applyProviderNativeStreamingUsageCompatWithPlugin: () => undefined, + normalizeProviderConfigWithPlugin: () => undefined, + resetProviderRuntimeHookCacheForTest: () => undefined, + resolveProviderConfigApiKeyWithPlugin: () => undefined, + resolveProviderSyntheticAuthWithPlugin: () => undefined, + }; +}); vi.mock("./models-config.providers.js", async () => { const actual = await vi.importActual( diff --git a/src/agents/pi-embedded-helpers/provider-error-patterns.test.ts b/src/agents/pi-embedded-helpers/provider-error-patterns.test.ts index e033b5c1ec9..86dc42759c5 100644 --- a/src/agents/pi-embedded-helpers/provider-error-patterns.test.ts +++ b/src/agents/pi-embedded-helpers/provider-error-patterns.test.ts @@ -5,10 +5,16 @@ const hoisted = vi.hoisted(() => ({ matchesProviderContextOverflowWithPlugin: vi.fn(() => false), })); -vi.mock("../../plugins/provider-runtime.js", () => ({ - classifyProviderFailoverReasonWithPlugin: hoisted.classifyProviderFailoverReasonWithPlugin, - matchesProviderContextOverflowWithPlugin: hoisted.matchesProviderContextOverflowWithPlugin, -})); +vi.mock("../../plugins/provider-runtime.js", async () => { + const actual = await vi.importActual( + "../../plugins/provider-runtime.js", + ); + return { + ...actual, + classifyProviderFailoverReasonWithPlugin: hoisted.classifyProviderFailoverReasonWithPlugin, + matchesProviderContextOverflowWithPlugin: hoisted.matchesProviderContextOverflowWithPlugin, + }; +}); import { classifyFailoverReason, isContextOverflowError } from "./errors.js"; import { diff --git a/src/agents/pi-embedded-runner.openai-tool-id-preservation.test.ts b/src/agents/pi-embedded-runner.openai-tool-id-preservation.test.ts index 0476df4803d..a15d87f8c52 100644 --- a/src/agents/pi-embedded-runner.openai-tool-id-preservation.test.ts +++ b/src/agents/pi-embedded-runner.openai-tool-id-preservation.test.ts @@ -13,11 +13,17 @@ vi.mock("./pi-embedded-helpers.js", async () => ({ sanitizeSessionMessagesImages: vi.fn(async (msgs) => msgs), })); -vi.mock("../plugins/provider-runtime.js", () => ({ - resolveProviderRuntimePlugin: vi.fn(() => undefined), - sanitizeProviderReplayHistoryWithPlugin: vi.fn(() => undefined), - validateProviderReplayTurnsWithPlugin: vi.fn(() => undefined), -})); +vi.mock("../plugins/provider-runtime.js", async () => { + const actual = await vi.importActual( + "../plugins/provider-runtime.js", + ); + return { + ...actual, + resolveProviderRuntimePlugin: vi.fn(() => undefined), + sanitizeProviderReplayHistoryWithPlugin: vi.fn(() => undefined), + validateProviderReplayTurnsWithPlugin: vi.fn(() => undefined), + }; +}); describe("sanitizeSessionHistory openai tool id preservation", () => { let sanitizeSessionHistory: SanitizeSessionHistoryHarness["sanitizeSessionHistory"]; diff --git a/src/agents/pi-embedded-runner.sanitize-session-history.policy.test.ts b/src/agents/pi-embedded-runner.sanitize-session-history.policy.test.ts index bac7e1fe73b..e691664d22c 100644 --- a/src/agents/pi-embedded-runner.sanitize-session-history.policy.test.ts +++ b/src/agents/pi-embedded-runner.sanitize-session-history.policy.test.ts @@ -14,11 +14,17 @@ vi.mock("./pi-embedded-helpers.js", async () => ({ sanitizeSessionMessagesImages: vi.fn(async (msgs) => msgs), })); -vi.mock("../plugins/provider-runtime.js", () => ({ - resolveProviderRuntimePlugin: vi.fn(() => undefined), - sanitizeProviderReplayHistoryWithPlugin: vi.fn(() => undefined), - validateProviderReplayTurnsWithPlugin: vi.fn(() => undefined), -})); +vi.mock("../plugins/provider-runtime.js", async () => { + const actual = await vi.importActual( + "../plugins/provider-runtime.js", + ); + return { + ...actual, + resolveProviderRuntimePlugin: vi.fn(() => undefined), + sanitizeProviderReplayHistoryWithPlugin: vi.fn(() => undefined), + validateProviderReplayTurnsWithPlugin: vi.fn(() => undefined), + }; +}); let sanitizeSessionHistory: SanitizeSessionHistoryHarness["sanitizeSessionHistory"]; let mockedHelpers: SanitizeSessionHistoryHarness["mockedHelpers"]; diff --git a/src/agents/pi-embedded-runner.sanitize-session-history.test.ts b/src/agents/pi-embedded-runner.sanitize-session-history.test.ts index 262a0707158..dafbb8c1a62 100644 --- a/src/agents/pi-embedded-runner.sanitize-session-history.test.ts +++ b/src/agents/pi-embedded-runner.sanitize-session-history.test.ts @@ -25,68 +25,74 @@ vi.mock("./pi-embedded-helpers.js", async () => ({ sanitizeSessionMessagesImages: vi.fn(async (msgs) => msgs), })); -vi.mock("../plugins/provider-runtime.js", () => ({ - resolveProviderRuntimePlugin: ({ provider }: { provider?: string }) => - provider === "openrouter" || provider === "github-copilot" - ? { - buildReplayPolicy: (context?: { modelId?: string | null }) => { - const modelId = String(context?.modelId ?? "").toLowerCase(); - if (provider === "openrouter") { - return { - applyAssistantFirstOrderingFix: false, - validateGeminiTurns: false, - validateAnthropicTurns: false, - ...(modelId.includes("gemini") - ? { - sanitizeThoughtSignatures: { - allowBase64Only: true, - includeCamelCase: true, - }, - } - : {}), - }; - } - if (provider === "github-copilot" && modelId.includes("claude")) { - return { - dropThinkingBlocks: true, - }; - } - return undefined; - }, - } - : undefined, - sanitizeProviderReplayHistoryWithPlugin: vi.fn( - async ({ - provider, - context, - }: { - provider?: string; - context: { - messages: AgentMessage[]; - sessionState?: { - appendCustomEntry(customType: string, data: unknown): void; +vi.mock("../plugins/provider-runtime.js", async () => { + const actual = await vi.importActual( + "../plugins/provider-runtime.js", + ); + return { + ...actual, + resolveProviderRuntimePlugin: ({ provider }: { provider?: string }) => + provider === "openrouter" || provider === "github-copilot" + ? { + buildReplayPolicy: (context?: { modelId?: string | null }) => { + const modelId = String(context?.modelId ?? "").toLowerCase(); + if (provider === "openrouter") { + return { + applyAssistantFirstOrderingFix: false, + validateGeminiTurns: false, + validateAnthropicTurns: false, + ...(modelId.includes("gemini") + ? { + sanitizeThoughtSignatures: { + allowBase64Only: true, + includeCamelCase: true, + }, + } + : {}), + }; + } + if (provider === "github-copilot" && modelId.includes("claude")) { + return { + dropThinkingBlocks: true, + }; + } + return undefined; + }, + } + : undefined, + sanitizeProviderReplayHistoryWithPlugin: vi.fn( + async ({ + provider, + context, + }: { + provider?: string; + context: { + messages: AgentMessage[]; + sessionState?: { + appendCustomEntry(customType: string, data: unknown): void; + }; }; - }; - }) => { - if ( - provider && - provider.startsWith("google") && - context.messages[0]?.role === "assistant" && - context.sessionState - ) { - context.sessionState.appendCustomEntry("google-turn-ordering-bootstrap", { - timestamp: Date.now(), - }); - return [ - { role: "user", content: "(session bootstrap)" } as AgentMessage, - ...context.messages, - ]; - } - return context.messages; - }, - ), - validateProviderReplayTurnsWithPlugin: vi.fn(() => undefined), -})); + }) => { + if ( + provider && + provider.startsWith("google") && + context.messages[0]?.role === "assistant" && + context.sessionState + ) { + context.sessionState.appendCustomEntry("google-turn-ordering-bootstrap", { + timestamp: Date.now(), + }); + return [ + { role: "user", content: "(session bootstrap)" } as AgentMessage, + ...context.messages, + ]; + } + return context.messages; + }, + ), + validateProviderReplayTurnsWithPlugin: vi.fn(() => undefined), + }; +}); let sanitizeSessionHistory: SanitizeSessionHistoryFn; let mockedHelpers: SanitizeSessionHistoryHarness["mockedHelpers"]; diff --git a/src/agents/pi-embedded-runner/cache-ttl.test.ts b/src/agents/pi-embedded-runner/cache-ttl.test.ts index 461ee6f12d5..8dda7ead862 100644 --- a/src/agents/pi-embedded-runner/cache-ttl.test.ts +++ b/src/agents/pi-embedded-runner/cache-ttl.test.ts @@ -1,23 +1,29 @@ import { describe, expect, it, vi } from "vitest"; -vi.mock("../../plugins/provider-runtime.js", () => ({ - resolveProviderCacheTtlEligibility: (params: { - context: { provider: string; modelId: string; modelApi?: string }; - }) => { - if (params.context.provider === "anthropic") { - return true; - } - if (params.context.provider === "moonshot" || params.context.provider === "zai") { - return true; - } - if (params.context.provider === "openrouter") { - return ["anthropic/", "moonshot/", "moonshotai/", "zai/"].some((prefix) => - params.context.modelId.startsWith(prefix), - ); - } - return undefined; - }, -})); +vi.mock("../../plugins/provider-runtime.js", async () => { + const actual = await vi.importActual( + "../../plugins/provider-runtime.js", + ); + return { + ...actual, + resolveProviderCacheTtlEligibility: (params: { + context: { provider: string; modelId: string; modelApi?: string }; + }) => { + if (params.context.provider === "anthropic") { + return true; + } + if (params.context.provider === "moonshot" || params.context.provider === "zai") { + return true; + } + if (params.context.provider === "openrouter") { + return ["anthropic/", "moonshot/", "moonshotai/", "zai/"].some((prefix) => + params.context.modelId.startsWith(prefix), + ); + } + return undefined; + }, + }; +}); import { isCacheTtlEligibleProvider } from "./cache-ttl.js"; diff --git a/src/agents/pi-embedded-runner/model.forward-compat.errors-and-overrides.test.ts b/src/agents/pi-embedded-runner/model.forward-compat.errors-and-overrides.test.ts index 70bae435936..63837087938 100644 --- a/src/agents/pi-embedded-runner/model.forward-compat.errors-and-overrides.test.ts +++ b/src/agents/pi-embedded-runner/model.forward-compat.errors-and-overrides.test.ts @@ -3,16 +3,22 @@ import type { ModelProviderConfig } from "../../config/config.js"; import { discoverModels } from "../pi-model-discovery.js"; import { createProviderRuntimeTestMock } from "./model.provider-runtime.test-support.js"; -vi.mock("../../plugins/provider-runtime.js", () => ({ - applyProviderResolvedModelCompatWithPlugins: () => undefined, - applyProviderResolvedTransportWithPlugin: () => undefined, - buildProviderUnknownModelHintWithPlugin: () => undefined, - clearProviderRuntimeHookCache: () => {}, - normalizeProviderTransportWithPlugin: () => undefined, - normalizeProviderResolvedModelWithPlugin: () => undefined, - prepareProviderDynamicModel: async () => {}, - runProviderDynamicModel: () => undefined, -})); +vi.mock("../../plugins/provider-runtime.js", async () => { + const actual = await vi.importActual( + "../../plugins/provider-runtime.js", + ); + return { + ...actual, + applyProviderResolvedModelCompatWithPlugins: () => undefined, + applyProviderResolvedTransportWithPlugin: () => undefined, + buildProviderUnknownModelHintWithPlugin: () => undefined, + clearProviderRuntimeHookCache: () => {}, + normalizeProviderTransportWithPlugin: () => undefined, + normalizeProviderResolvedModelWithPlugin: () => undefined, + prepareProviderDynamicModel: async () => {}, + runProviderDynamicModel: () => undefined, + }; +}); vi.mock("../model-suppression.js", () => ({ shouldSuppressBuiltInModel: ({ provider, id }: { provider?: string; id?: string }) => diff --git a/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-injection.test.ts b/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-injection.test.ts index 6fbfd78c9ac..7a149bde1a6 100644 --- a/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-injection.test.ts +++ b/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-injection.test.ts @@ -102,6 +102,10 @@ describe("runEmbeddedAttempt context injection", () => { contextEngine: { assemble: async ({ messages }) => ({ messages, estimatedTokens: 1 }), }, + attemptOverrides: { + bootstrapContextMode: "full", + bootstrapContextRunKind: "default", + }, sessionKey: "agent:main", tempPaths, }); diff --git a/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts b/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts index 06f0f2b39a3..33f8ad1a433 100644 --- a/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts +++ b/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts @@ -144,7 +144,10 @@ export function getHoisted(): AttemptSpawnWorkspaceHoisted { return hoisted; } -vi.mock("@mariozechner/pi-coding-agent", () => { +vi.mock("@mariozechner/pi-coding-agent", async () => { + const actual = await vi.importActual( + "@mariozechner/pi-coding-agent", + ); class AuthStorage {} class DefaultResourceLoader { async reload() {} @@ -152,6 +155,7 @@ vi.mock("@mariozechner/pi-coding-agent", () => { class ModelRegistry {} return { + ...actual, AuthStorage, createAgentSession: (...args: unknown[]) => hoisted.createAgentSessionMock(...args), DefaultResourceLoader, @@ -196,12 +200,18 @@ vi.mock("../../../infra/net/undici-global-dispatcher.js", () => ({ ensureGlobalUndiciStreamTimeouts: () => {}, })); -vi.mock("../../bootstrap-files.js", () => ({ - makeBootstrapWarn: () => () => {}, - resolveBootstrapContextForRun: hoisted.resolveBootstrapContextForRunMock, - resolveContextInjectionMode: hoisted.resolveContextInjectionModeMock, - hasCompletedBootstrapTurn: hoisted.hasCompletedBootstrapTurnMock, -})); +vi.mock("../../bootstrap-files.js", async () => { + const actual = await vi.importActual( + "../../bootstrap-files.js", + ); + return { + ...actual, + makeBootstrapWarn: () => () => {}, + resolveBootstrapContextForRun: hoisted.resolveBootstrapContextForRunMock, + resolveContextInjectionMode: hoisted.resolveContextInjectionModeMock, + hasCompletedBootstrapTurn: hoisted.hasCompletedBootstrapTurnMock, + }; +}); vi.mock("../../skills.js", () => ({ applySkillEnvOverrides: () => () => {}, @@ -225,7 +235,9 @@ vi.mock("../../docs-path.js", () => ({ })); vi.mock("../../pi-project-settings.js", () => ({ - createPreparedEmbeddedPiSettingsManager: () => ({}), + createPreparedEmbeddedPiSettingsManager: () => ({ + getCompactionReserveTokens: () => 0, + }), })); vi.mock("../../pi-settings.js", () => ({ @@ -265,12 +277,18 @@ vi.mock("../../session-write-lock.js", () => ({ resolveSessionLockMaxHoldFromTimeout: () => 1, })); -vi.mock("../tool-result-context-guard.js", () => ({ - formatContextLimitTruncationNotice: (truncatedChars: number) => - `[... ${Math.max(1, Math.floor(truncatedChars))} more characters truncated]`, - installToolResultContextGuard: (...args: unknown[]) => - (hoisted.installToolResultContextGuardMock as (...args: unknown[]) => unknown)(...args), -})); +vi.mock("../tool-result-context-guard.js", async () => { + const actual = await vi.importActual( + "../tool-result-context-guard.js", + ); + return { + ...actual, + formatContextLimitTruncationNotice: (truncatedChars: number) => + `[... ${Math.max(1, Math.floor(truncatedChars))} more characters truncated]`, + installToolResultContextGuard: (...args: unknown[]) => + (hoisted.installToolResultContextGuardMock as (...args: unknown[]) => unknown)(...args), + }; +}); vi.mock("../wait-for-idle-before-flush.js", () => ({ flushPendingToolResultsAfterIdle: (...args: unknown[]) => @@ -806,9 +824,12 @@ export async function createContextEngineAttemptRunner(params: { .mockReset() .mockReturnValue({ messages: seedMessages }); - hoisted.createAgentSessionMock.mockImplementation(async () => ({ - session: createDefaultEmbeddedSession(), - })); + hoisted.createAgentSessionMock.mockImplementation(async () => { + const session = createDefaultEmbeddedSession(); + session.messages = [...seedMessages]; + session.agent.state.messages = [...seedMessages]; + return { session }; + }); return await ( await loadRunEmbeddedAttempt() diff --git a/src/agents/pi-tools.workspace-only-false.test.ts b/src/agents/pi-tools.workspace-only-false.test.ts index 8f3e32d1d19..dcb64470646 100644 --- a/src/agents/pi-tools.workspace-only-false.test.ts +++ b/src/agents/pi-tools.workspace-only-false.test.ts @@ -115,8 +115,7 @@ describe("FS tools with workspaceOnly=false", () => { "test-call-2", { path: outsideFile, - oldText: "old content", - newText: "new content", + edits: [{ oldText: "old content", newText: "new content" }], }, false, ); @@ -134,8 +133,7 @@ describe("FS tools with workspaceOnly=false", () => { "test-call-2b", { path: relativeOutsidePath, - oldText: "old relative content", - newText: "new relative content", + edits: [{ oldText: "old relative content", newText: "new relative content" }], }, false, ); @@ -179,8 +177,7 @@ describe("FS tools with workspaceOnly=false", () => { "test-call-3b", { path: outsideUnsetFile, - oldText: "before", - newText: "after", + edits: [{ oldText: "before", newText: "after" }], }, undefined, ); diff --git a/src/agents/simple-completion-transport.test.ts b/src/agents/simple-completion-transport.test.ts index 4b3036e3d72..73812de0b7a 100644 --- a/src/agents/simple-completion-transport.test.ts +++ b/src/agents/simple-completion-transport.test.ts @@ -21,9 +21,15 @@ vi.mock("./provider-transport-stream.js", () => ({ prepareTransportAwareSimpleModel, })); -vi.mock("../plugins/provider-runtime.js", () => ({ - resolveProviderStreamFn, -})); +vi.mock("../plugins/provider-runtime.js", async () => { + const actual = await vi.importActual( + "../plugins/provider-runtime.js", + ); + return { + ...actual, + resolveProviderStreamFn, + }; +}); let prepareModelForSimpleCompletion: typeof import("./simple-completion-transport.js").prepareModelForSimpleCompletion; diff --git a/src/agents/tools/message-tool.test.ts b/src/agents/tools/message-tool.test.ts index dc9add17ed3..af3751c7732 100644 --- a/src/agents/tools/message-tool.test.ts +++ b/src/agents/tools/message-tool.test.ts @@ -62,6 +62,70 @@ const mocks = vi.hoisted(() => ({ resolvedConfig: config, diagnostics: [], })), + getScopedChannelsCommandSecretTargets: vi.fn( + ({ + config, + channel, + accountId, + }: { + config?: { channels?: Record }; + channel?: string | null; + accountId?: string | null; + }) => { + const allowedPaths = new Set(); + const targetIds = new Set(); + const scopedChannel = channel?.trim(); + const scopedAccountId = accountId?.trim(); + const scopedConfig = + scopedChannel && config?.channels && typeof config.channels[scopedChannel] === "object" + ? (config.channels[scopedChannel] as Record) + : null; + if (!scopedChannel || !scopedConfig) { + return { targetIds }; + } + + const maybeCollectSecretPath = (path: string, value: unknown) => { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return; + } + const record = value as Record; + if (typeof record.source === "string" && typeof record.id === "string") { + targetIds.add(path); + allowedPaths.add(path); + } + }; + + maybeCollectSecretPath(`channels.${scopedChannel}.token`, scopedConfig.token); + maybeCollectSecretPath(`channels.${scopedChannel}.botToken`, scopedConfig.botToken); + if (scopedAccountId) { + const accountRecord = + scopedConfig.accounts && + typeof scopedConfig.accounts === "object" && + !Array.isArray(scopedConfig.accounts) && + typeof (scopedConfig.accounts as Record)[scopedAccountId] === "object" + ? ((scopedConfig.accounts as Record)[scopedAccountId] as Record< + string, + unknown + >) + : null; + if (accountRecord) { + maybeCollectSecretPath( + `channels.${scopedChannel}.accounts.${scopedAccountId}.token`, + accountRecord.token, + ); + maybeCollectSecretPath( + `channels.${scopedChannel}.accounts.${scopedAccountId}.botToken`, + accountRecord.botToken, + ); + } + } + + return { + targetIds, + ...(allowedPaths.size > 0 ? { allowedPaths } : {}), + }; + }, + ), })); vi.mock("../../infra/outbound/message-action-runner.js", async () => { @@ -87,6 +151,10 @@ vi.mock("../../cli/command-secret-gateway.js", () => ({ resolveCommandSecretRefsViaGateway: mocks.resolveCommandSecretRefsViaGateway, })); +vi.mock("../../cli/command-secret-targets.js", () => ({ + getScopedChannelsCommandSecretTargets: mocks.getScopedChannelsCommandSecretTargets, +})); + function mockSendResult(overrides: { channel?: string; to?: string } = {}) { mocks.runMessageAction.mockClear(); mocks.runMessageAction.mockResolvedValue({ @@ -121,6 +189,8 @@ beforeEach(() => { resolvedConfig: config, diagnostics: [], })); + mocks.getScopedChannelsCommandSecretTargets.mockClear(); + setActivePluginRegistry(createTestRegistry([])); }); function createChannelPlugin(params: { diff --git a/src/agents/tools/music-generate-tool.test.ts b/src/agents/tools/music-generate-tool.test.ts index 0f16a15fb85..10059b28267 100644 --- a/src/agents/tools/music-generate-tool.test.ts +++ b/src/agents/tools/music-generate-tool.test.ts @@ -122,9 +122,6 @@ describe("createMusicGenerateTool", () => { count: 1, instrumental: true, lyrics: ["wake the city up"], - task: { - taskId: "task-123", - }, media: { mediaUrls: ["/tmp/generated-night-drive.mp3"], }, diff --git a/src/agents/transcript-policy.test.ts b/src/agents/transcript-policy.test.ts index 15ea8a98609..9922ed44b4a 100644 --- a/src/agents/transcript-policy.test.ts +++ b/src/agents/transcript-policy.test.ts @@ -1,175 +1,181 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -vi.mock("../plugins/provider-runtime.js", () => ({ - resolveProviderRuntimePlugin: vi.fn(({ provider }: { provider?: string }) => { - if ( - !provider || - ![ - "amazon-bedrock", - "anthropic", - "google", - "kilocode", - "kimi", - "kimi-code", - "minimax", - "minimax-portal", - "mistral", - "moonshot", - "openai", - "openai-codex", - "opencode", - "opencode-go", - "ollama", - "openrouter", - "sglang", - "vllm", - "xai", - "zai", - ].includes(provider) - ) { - return undefined; - } - if (provider === "sglang" || provider === "vllm") { - return {}; - } - return { - buildReplayPolicy: (context?: { modelId?: string; modelApi?: string }) => { - const modelId = context?.modelId?.toLowerCase() ?? ""; - switch (provider) { - case "amazon-bedrock": - case "anthropic": - return { - sanitizeMode: "full", - sanitizeToolCallIds: true, - toolCallIdMode: "strict", - preserveSignatures: true, - repairToolUseResultPairing: true, - validateAnthropicTurns: true, - allowSyntheticToolResults: true, - ...(modelId.includes("claude") ? { dropThinkingBlocks: true } : {}), - }; - case "minimax": - case "minimax-portal": - return context?.modelApi === "openai-completions" - ? { +vi.mock("../plugins/provider-runtime.js", async () => { + const actual = await vi.importActual( + "../plugins/provider-runtime.js", + ); + return { + ...actual, + resolveProviderRuntimePlugin: vi.fn(({ provider }: { provider?: string }) => { + if ( + !provider || + ![ + "amazon-bedrock", + "anthropic", + "google", + "kilocode", + "kimi", + "kimi-code", + "minimax", + "minimax-portal", + "mistral", + "moonshot", + "openai", + "openai-codex", + "opencode", + "opencode-go", + "ollama", + "openrouter", + "sglang", + "vllm", + "xai", + "zai", + ].includes(provider) + ) { + return undefined; + } + if (provider === "sglang" || provider === "vllm") { + return {}; + } + return { + buildReplayPolicy: (context?: { modelId?: string; modelApi?: string }) => { + const modelId = context?.modelId?.toLowerCase() ?? ""; + switch (provider) { + case "amazon-bedrock": + case "anthropic": + return { + sanitizeMode: "full", + sanitizeToolCallIds: true, + toolCallIdMode: "strict", + preserveSignatures: true, + repairToolUseResultPairing: true, + validateAnthropicTurns: true, + allowSyntheticToolResults: true, + ...(modelId.includes("claude") ? { dropThinkingBlocks: true } : {}), + }; + case "minimax": + case "minimax-portal": + return context?.modelApi === "openai-completions" + ? { + sanitizeToolCallIds: true, + toolCallIdMode: "strict", + applyAssistantFirstOrderingFix: true, + validateGeminiTurns: true, + validateAnthropicTurns: true, + } + : { + sanitizeMode: "full", + sanitizeToolCallIds: true, + toolCallIdMode: "strict", + preserveSignatures: true, + repairToolUseResultPairing: true, + validateAnthropicTurns: true, + allowSyntheticToolResults: true, + ...(modelId.includes("claude") ? { dropThinkingBlocks: true } : {}), + }; + case "moonshot": + case "ollama": + case "zai": + return context?.modelApi === "openai-completions" + ? { + sanitizeToolCallIds: true, + toolCallIdMode: "strict", + applyAssistantFirstOrderingFix: true, + validateGeminiTurns: true, + validateAnthropicTurns: true, + } + : undefined; + case "google": + return { + sanitizeMode: "full", + sanitizeToolCallIds: true, + toolCallIdMode: "strict", + sanitizeThoughtSignatures: { + allowBase64Only: true, + includeCamelCase: true, + }, + repairToolUseResultPairing: true, + applyAssistantFirstOrderingFix: true, + validateGeminiTurns: true, + validateAnthropicTurns: false, + allowSyntheticToolResults: true, + }; + case "mistral": + return { + sanitizeToolCallIds: true, + toolCallIdMode: "strict9", + }; + case "openai": + case "openai-codex": + return { + sanitizeMode: "images-only", + sanitizeToolCallIds: context?.modelApi === "openai-completions", + ...(context?.modelApi === "openai-completions" ? { toolCallIdMode: "strict" } : {}), + applyAssistantFirstOrderingFix: false, + validateGeminiTurns: false, + validateAnthropicTurns: false, + }; + case "kimi": + case "kimi-code": + return { + preserveSignatures: false, + }; + case "openrouter": + case "opencode": + case "opencode-go": + return { + applyAssistantFirstOrderingFix: false, + validateGeminiTurns: false, + validateAnthropicTurns: false, + ...(modelId.includes("gemini") + ? { + sanitizeThoughtSignatures: { + allowBase64Only: true, + includeCamelCase: true, + }, + } + : {}), + }; + case "xai": + if ( + context?.modelApi === "openai-completions" || + context?.modelApi === "openai-responses" + ) { + return { sanitizeToolCallIds: true, toolCallIdMode: "strict", - applyAssistantFirstOrderingFix: true, - validateGeminiTurns: true, - validateAnthropicTurns: true, - } - : { - sanitizeMode: "full", - sanitizeToolCallIds: true, - toolCallIdMode: "strict", - preserveSignatures: true, - repairToolUseResultPairing: true, - validateAnthropicTurns: true, - allowSyntheticToolResults: true, - ...(modelId.includes("claude") ? { dropThinkingBlocks: true } : {}), + ...(context.modelApi === "openai-completions" + ? { + applyAssistantFirstOrderingFix: true, + validateGeminiTurns: true, + validateAnthropicTurns: true, + } + : { + applyAssistantFirstOrderingFix: false, + validateGeminiTurns: false, + validateAnthropicTurns: false, + }), }; - case "moonshot": - case "ollama": - case "zai": - return context?.modelApi === "openai-completions" - ? { - sanitizeToolCallIds: true, - toolCallIdMode: "strict", - applyAssistantFirstOrderingFix: true, - validateGeminiTurns: true, - validateAnthropicTurns: true, - } - : undefined; - case "google": - return { - sanitizeMode: "full", - sanitizeToolCallIds: true, - toolCallIdMode: "strict", - sanitizeThoughtSignatures: { - allowBase64Only: true, - includeCamelCase: true, - }, - repairToolUseResultPairing: true, - applyAssistantFirstOrderingFix: true, - validateGeminiTurns: true, - validateAnthropicTurns: false, - allowSyntheticToolResults: true, - }; - case "mistral": - return { - sanitizeToolCallIds: true, - toolCallIdMode: "strict9", - }; - case "openai": - case "openai-codex": - return { - sanitizeMode: "images-only", - sanitizeToolCallIds: context?.modelApi === "openai-completions", - ...(context?.modelApi === "openai-completions" ? { toolCallIdMode: "strict" } : {}), - applyAssistantFirstOrderingFix: false, - validateGeminiTurns: false, - validateAnthropicTurns: false, - }; - case "kimi": - case "kimi-code": - return { - preserveSignatures: false, - }; - case "openrouter": - case "opencode": - case "opencode-go": - return { - applyAssistantFirstOrderingFix: false, - validateGeminiTurns: false, - validateAnthropicTurns: false, - ...(modelId.includes("gemini") + } + return undefined; + case "kilocode": + return modelId.includes("gemini") ? { sanitizeThoughtSignatures: { allowBase64Only: true, includeCamelCase: true, }, } - : {}), - }; - case "xai": - if ( - context?.modelApi === "openai-completions" || - context?.modelApi === "openai-responses" - ) { - return { - sanitizeToolCallIds: true, - toolCallIdMode: "strict", - ...(context.modelApi === "openai-completions" - ? { - applyAssistantFirstOrderingFix: true, - validateGeminiTurns: true, - validateAnthropicTurns: true, - } - : { - applyAssistantFirstOrderingFix: false, - validateGeminiTurns: false, - validateAnthropicTurns: false, - }), - }; - } - return undefined; - case "kilocode": - return modelId.includes("gemini") - ? { - sanitizeThoughtSignatures: { - allowBase64Only: true, - includeCamelCase: true, - }, - } - : undefined; - default: - return undefined; - } - }, - }; - }), - resetProviderRuntimeHookCacheForTest: vi.fn(), -})); + : undefined; + default: + return undefined; + } + }, + }; + }), + resetProviderRuntimeHookCacheForTest: vi.fn(), + }; +}); let resolveTranscriptPolicy: typeof import("./transcript-policy.js").resolveTranscriptPolicy; diff --git a/src/cli/command-secret-gateway.test.ts b/src/cli/command-secret-gateway.test.ts index 5579a3827e6..6b9ade74493 100644 --- a/src/cli/command-secret-gateway.test.ts +++ b/src/cli/command-secret-gateway.test.ts @@ -551,7 +551,21 @@ describe("resolveCommandSecretRefsViaGateway", () => { commandName: "memory status", targetIds: new Set(["talk.providers.*.apiKey"]), }), - ).rejects.toThrow(/Path segment does not exist/i); + ).resolves.toMatchObject({ + resolvedConfig: { + talk: { + providers: { + "acme-speech": { + apiKey: "sk-live", + }, + }, + }, + }, + targetStatesByPath: { + [TALK_TEST_PROVIDER_API_KEY_PATH]: "resolved_gateway", + }, + hadUnresolvedTargets: false, + }); }); it("fails when configured refs remain unresolved after gateway assignments are applied", async () => { diff --git a/src/cli/command-secret-resolution.coverage.test.ts b/src/cli/command-secret-resolution.coverage.test.ts index 476c7b1698d..586ed2ca83b 100644 --- a/src/cli/command-secret-resolution.coverage.test.ts +++ b/src/cli/command-secret-resolution.coverage.test.ts @@ -17,18 +17,31 @@ const SECRET_TARGET_CALLSITES = [ function hasSupportedTargetIdsWiring(source: string): boolean { return ( /targetIds:\s*get[A-Za-z0-9_]+\(\)/m.test(source) || - /targetIds:\s*scopedTargets\.targetIds/m.test(source) + /targetIds:\s*scopedTargets\.targetIds/m.test(source) || + source.includes("collectStatusScanOverview({") + ); +} + +function usesSharedSecretResolver(source: string): boolean { + return ( + source.includes("resolveCommandSecretRefsViaGateway") || + source.includes("resolveCommandConfigWithSecrets") || + source.includes("collectStatusScanOverview({") ); } describe("command secret resolution coverage", () => { it.each(SECRET_TARGET_CALLSITES)( - "routes target-id command path through shared gateway resolver: %s", + "routes target-id command path through shared secret resolver: %s", async (relativePath) => { const source = await readCommandSource(relativePath); - expect(source).toContain("resolveCommandSecretRefsViaGateway"); + expect(usesSharedSecretResolver(source)).toBe(true); expect(hasSupportedTargetIdsWiring(source)).toBe(true); - expect(source).toContain("resolveCommandSecretRefsViaGateway({"); + expect( + source.includes("resolveCommandSecretRefsViaGateway({") || + source.includes("resolveCommandConfigWithSecrets({") || + source.includes("collectStatusScanOverview({"), + ).toBe(true); }, ); }); diff --git a/src/cli/gateway-cli/run.option-collisions.test.ts b/src/cli/gateway-cli/run.option-collisions.test.ts index b5727ee9230..8ca196fa2d9 100644 --- a/src/cli/gateway-cli/run.option-collisions.test.ts +++ b/src/cli/gateway-cli/run.option-collisions.test.ts @@ -184,7 +184,7 @@ describe("gateway run option collisions", () => { expect(forceFreePortAndWait).toHaveBeenCalledWith(18789, expect.anything()); expect(waitForPortBindable).toHaveBeenCalledWith( 18789, - expect.objectContaining({ host: "127.0.0.1" }), + expect.objectContaining({ intervalMs: 150, timeoutMs: 3000 }), ); expect(setGatewayWsLogStyle).toHaveBeenCalledWith("full"); expect(startGatewayServer).toHaveBeenCalledWith( diff --git a/src/infra/provider-usage.load.plugin.test.ts b/src/infra/provider-usage.load.plugin.test.ts index a5bfa8c3e24..87e892474ed 100644 --- a/src/infra/provider-usage.load.plugin.test.ts +++ b/src/infra/provider-usage.load.plugin.test.ts @@ -7,10 +7,16 @@ vi.mock("../config/config.js", () => ({ loadConfig: () => ({}), })); -vi.mock("../plugins/provider-runtime.js", () => ({ - resolveProviderUsageSnapshotWithPlugin: (...args: unknown[]) => - resolveProviderUsageSnapshotWithPluginMock(...args), -})); +vi.mock("../plugins/provider-runtime.js", async () => { + const actual = await vi.importActual( + "../plugins/provider-runtime.js", + ); + return { + ...actual, + resolveProviderUsageSnapshotWithPlugin: (...args: unknown[]) => + resolveProviderUsageSnapshotWithPluginMock(...args), + }; +}); let loadProviderUsageSummary: typeof import("./provider-usage.load.js").loadProviderUsageSummary; diff --git a/src/utils/provider-utils.test.ts b/src/utils/provider-utils.test.ts index dc1f45705a6..dc81d202f3a 100644 --- a/src/utils/provider-utils.test.ts +++ b/src/utils/provider-utils.test.ts @@ -4,9 +4,15 @@ const { resolveProviderReasoningOutputModeWithPluginMock } = vi.hoisted(() => ({ resolveProviderReasoningOutputModeWithPluginMock: vi.fn(), })); -vi.mock("../plugins/provider-runtime.js", () => ({ - resolveProviderReasoningOutputModeWithPlugin: resolveProviderReasoningOutputModeWithPluginMock, -})); +vi.mock("../plugins/provider-runtime.js", async () => { + const actual = await vi.importActual( + "../plugins/provider-runtime.js", + ); + return { + ...actual, + resolveProviderReasoningOutputModeWithPlugin: resolveProviderReasoningOutputModeWithPluginMock, + }; +}); import { isReasoningTagProvider, resolveReasoningOutputMode } from "./provider-utils.js"; diff --git a/test/extension-test-boundary.test.ts b/test/extension-test-boundary.test.ts index 694eb0b868e..45b82094bda 100644 --- a/test/extension-test-boundary.test.ts +++ b/test/extension-test-boundary.test.ts @@ -12,6 +12,7 @@ const ALLOWED_EXTENSION_PUBLIC_SURFACE_BASENAMES = new Set( const allowedNonExtensionTests = new Set([ "src/agents/pi-embedded-runner-extraparams-moonshot.test.ts", "src/agents/pi-embedded-runner-extraparams.test.ts", + "src/agents/pi-embedded-runner-extraparams-moonshot.test.ts", "src/channels/plugins/contracts/dm-policy.contract.test.ts", "src/channels/plugins/contracts/group-policy.contract.test.ts", "src/commands/channels.surfaces-signal-runtime-errors-channels-status-output.test.ts", @@ -21,6 +22,8 @@ const allowedNonExtensionTests = new Set([ "src/plugins/interactive.test.ts", "src/plugins/contracts/discovery.contract.test.ts", "src/plugin-sdk/telegram-command-config.test.ts", + "src/security/audit-channel-slack-command-findings.test.ts", + "src/security/audit-feishu-doc-risk.test.ts", "src/secrets/runtime-channel-inactive-variants.test.ts", "src/secrets/runtime-discord-surface.test.ts", "src/secrets/runtime-inactive-telegram-surfaces.test.ts", @@ -30,8 +33,6 @@ const allowedNonExtensionTests = new Set([ "src/secrets/runtime-nextcloud-talk-file-precedence.test.ts", "src/secrets/runtime-telegram-token-inheritance.test.ts", "src/secrets/runtime-zalo-token-activity.test.ts", - "src/security/audit-channel-slack-command-findings.test.ts", - "src/security/audit-feishu-doc-risk.test.ts", ]); function walk(dir: string, entries: string[] = []): string[] {