From 091f670ea0611dc3cc303624d8a0ddc1d14d6d0c Mon Sep 17 00:00:00 2001 From: can1357 Date: Thu, 30 Jul 2026 01:28:07 +0200 Subject: [PATCH] style: apply biome formatting to merged changes --- .../test/auth-storage-codex-selection.test.ts | 168 +++++++++--------- .../test/openai-codex-responses-lite.test.ts | 96 +++++----- .../catalog/test/issue-1617-repro.test.ts | 28 +-- packages/catalog/test/issue-887-repro.test.ts | 15 +- .../catalog/test/litellm-provider.test.ts | 103 +++++------ .../coding-agent/test/advisor/advisor.test.ts | 104 +++++------ .../test/agent-session-acp-permission.test.ts | 89 +++++----- ...ent-session-bash-session-ownership.test.ts | 111 ++++++------ .../test/interactive-mode-plan-review.test.ts | 15 +- .../internal-urls/memory-protocol.test.ts | 40 ++--- packages/coding-agent/test/rpc.test.ts | 5 +- .../sdk-async-job-manager-singleton.test.ts | 7 +- .../test/task/task-preflight.test.ts | 25 +-- .../coding-agent/test/title-generator.test.ts | 33 ++-- .../test/tools/bash-interceptor.test.ts | 34 ++-- 15 files changed, 439 insertions(+), 434 deletions(-) diff --git a/packages/ai/test/auth-storage-codex-selection.test.ts b/packages/ai/test/auth-storage-codex-selection.test.ts index 365e7da0b..358143c80 100644 --- a/packages/ai/test/auth-storage-codex-selection.test.ts +++ b/packages/ai/test/auth-storage-codex-selection.test.ts @@ -1746,39 +1746,36 @@ describe("AuthStorage codex oauth ranking", () => { test.each([ ["gpt-5.6-terra", "free", "enterprise"], ["gpt-5.6-terra-pro", "go", "pro"], - ])( - "%s keeps a less-used %s account in ordinary ranking ahead of %s", - async (modelId, lowUsagePlan, highUsagePlan) => { - if (!authStorage) throw new Error("test setup failed"); + ])("%s keeps a less-used %s account in ordinary ranking ahead of %s", async (modelId, lowUsagePlan, highUsagePlan) => { + if (!authStorage) throw new Error("test setup failed"); - await authStorage.set("openai-codex", [ - { type: "oauth", ...createCredential("acct-low-usage", "low-usage@example.com") }, - { type: "oauth", ...createCredential("acct-high-usage", "high-usage@example.com") }, - ]); + await authStorage.set("openai-codex", [ + { type: "oauth", ...createCredential("acct-low-usage", "low-usage@example.com") }, + { type: "oauth", ...createCredential("acct-high-usage", "high-usage@example.com") }, + ]); - usageByAccount.set( - "acct-low-usage", - createCodexUsageReport({ - accountId: "acct-low-usage", - primary: { usedFraction: 0.01, resetInMs: 30 * 60 * 1000 }, - secondary: { usedFraction: 0.01, resetInMs: 6 * 24 * 60 * 60 * 1000 }, - metadata: { planType: lowUsagePlan, email: "low-usage@example.com" }, - }), - ); - usageByAccount.set( - "acct-high-usage", - createCodexUsageReport({ - accountId: "acct-high-usage", - primary: { usedFraction: 0.8, resetInMs: 30 * 60 * 1000 }, - secondary: { usedFraction: 0.8, resetInMs: 6 * 24 * 60 * 60 * 1000 }, - metadata: { planType: highUsagePlan, email: "high-usage@example.com" }, - }), - ); + usageByAccount.set( + "acct-low-usage", + createCodexUsageReport({ + accountId: "acct-low-usage", + primary: { usedFraction: 0.01, resetInMs: 30 * 60 * 1000 }, + secondary: { usedFraction: 0.01, resetInMs: 6 * 24 * 60 * 60 * 1000 }, + metadata: { planType: lowUsagePlan, email: "low-usage@example.com" }, + }), + ); + usageByAccount.set( + "acct-high-usage", + createCodexUsageReport({ + accountId: "acct-high-usage", + primary: { usedFraction: 0.8, resetInMs: 30 * 60 * 1000 }, + secondary: { usedFraction: 0.8, resetInMs: 6 * 24 * 60 * 60 * 1000 }, + metadata: { planType: highUsagePlan, email: "high-usage@example.com" }, + }), + ); - const apiKey = await authStorage.getApiKey("openai-codex", undefined, { modelId }); - expect(apiKey).toBe("api-acct-low-usage"); - }, - ); + const apiKey = await authStorage.getApiKey("openai-codex", undefined, { modelId }); + expect(apiKey).toBe("api-acct-low-usage"); + }); test("keeps an eligible Codex session credential when usage headroom makes its sibling rank better", async () => { if (!authStorage) throw new Error("test setup failed"); @@ -2365,65 +2362,62 @@ describe("AuthStorage codex oauth ranking", () => { test.each([ ["gpt-5.6-sol", 0.06, 0.09, true, false, 1, 1, false, true, "spark"], ["gpt-5.3-codex-spark", 1, 1, false, true, 0.06, 0.09, true, false, "chat"], - ] as const)( - "reports %s healthy after splitting a legacy shared block when only its meter has headroom", - async (modelId, chatPrimary, chatSecondary, chatAllowed, chatLimitReached, sparkPrimary, sparkSecondary, sparkAllowed, sparkLimitReached, remainingBlockScope) => { - if (!authStorage || !store?.listCredentialBlocks) throw new Error("test setup failed"); - await authStorage.set("openai-codex", [ - { type: "oauth", ...createCredential("acct-legacy-meter", "legacy-meter@example.com") }, - ]); - const [row] = store.listAuthCredentials("openai-codex"); - if (!row) throw new Error("expected credential row"); - const blockedUntilMs = Date.now() + WEEK_MS; - insertLegacyCodexSharedBlock( - dbPath, - row.id, - blockedUntilMs, - Math.floor((Date.now() - STALE_BLOCK_GUARD_MS) / 1000), - ); - usageByAccount.set( - "acct-legacy-meter", - addSparkUsage( - createCodexUsageReport({ + ] as const)("reports %s healthy after splitting a legacy shared block when only its meter has headroom", async (modelId, chatPrimary, chatSecondary, chatAllowed, chatLimitReached, sparkPrimary, sparkSecondary, sparkAllowed, sparkLimitReached, remainingBlockScope) => { + if (!authStorage || !store?.listCredentialBlocks) throw new Error("test setup failed"); + await authStorage.set("openai-codex", [ + { type: "oauth", ...createCredential("acct-legacy-meter", "legacy-meter@example.com") }, + ]); + const [row] = store.listAuthCredentials("openai-codex"); + if (!row) throw new Error("expected credential row"); + const blockedUntilMs = Date.now() + WEEK_MS; + insertLegacyCodexSharedBlock( + dbPath, + row.id, + blockedUntilMs, + Math.floor((Date.now() - STALE_BLOCK_GUARD_MS) / 1000), + ); + usageByAccount.set( + "acct-legacy-meter", + addSparkUsage( + createCodexUsageReport({ + accountId: "acct-legacy-meter", + primary: { usedFraction: chatPrimary, resetInMs: FIVE_HOUR_MS }, + secondary: { usedFraction: chatSecondary, resetInMs: WEEK_MS }, + metadata: { + allowed: chatAllowed, + limitReached: chatLimitReached, + planType: "pro", + email: "legacy-meter@example.com", accountId: "acct-legacy-meter", - primary: { usedFraction: chatPrimary, resetInMs: FIVE_HOUR_MS }, - secondary: { usedFraction: chatSecondary, resetInMs: WEEK_MS }, - metadata: { - allowed: chatAllowed, - limitReached: chatLimitReached, - planType: "pro", - email: "legacy-meter@example.com", - accountId: "acct-legacy-meter", - }, - }), - sparkPrimary, - sparkSecondary, - { allowed: sparkAllowed, limitReached: sparkLimitReached }, - ), - ); - - const health = await authStorage.getModelUsageHealth("openai-codex", { - modelId, - reserveFraction: 0.1, - }); - - expect(health).toMatchObject({ - state: "healthy", - accounts: [ - { - credentialId: row.id, - credentialType: "oauth", - state: "healthy", }, - ], - }); - expect(health.accounts[0]?.remainingFraction).toBeCloseTo(0.91, 10); - expect(store.listCredentialBlocks([row.id]).map(block => [block.blockScope, block.blockedUntilMs])).toEqual([ - [remainingBlockScope, blockedUntilMs], - ]); - expect(readLegacyCodexSharedBlock(dbPath, row.id)).toBe(blockedUntilMs); - }, - ); + }), + sparkPrimary, + sparkSecondary, + { allowed: sparkAllowed, limitReached: sparkLimitReached }, + ), + ); + + const health = await authStorage.getModelUsageHealth("openai-codex", { + modelId, + reserveFraction: 0.1, + }); + + expect(health).toMatchObject({ + state: "healthy", + accounts: [ + { + credentialId: row.id, + credentialType: "oauth", + state: "healthy", + }, + ], + }); + expect(health.accounts[0]?.remainingFraction).toBeCloseTo(0.91, 10); + expect(store.listCredentialBlocks([row.id]).map(block => [block.blockScope, block.blockedUntilMs])).toEqual([ + [remainingBlockScope, blockedUntilMs], + ]); + expect(readLegacyCodexSharedBlock(dbPath, row.id)).toBe(blockedUntilMs); + }); test("deletes only the recovered persisted Codex meter block", async () => { if (!authStorage || !store?.upsertCredentialBlock || !store.getCredentialBlock) { diff --git a/packages/ai/test/openai-codex-responses-lite.test.ts b/packages/ai/test/openai-codex-responses-lite.test.ts index 3f4ff7a74..bc54b79b4 100644 --- a/packages/ai/test/openai-codex-responses-lite.test.ts +++ b/packages/ai/test/openai-codex-responses-lite.test.ts @@ -201,24 +201,25 @@ describe("openai-codex reasoning.context", () => { // gpt-5.1-codex / gpt-5.3-codex / gpt-5.3-codex-spark reject `all_turns` // ("Unsupported value: 'all_turns' is not supported with this model"). - it.each(["gpt-5.1-codex", "gpt-5.3-codex", "gpt-5.3-codex-spark"])( - "omits the all_turns default for pre-5.4 model %s", - async modelId => { - const model = createCodexModel(modelId); + it.each([ + "gpt-5.1-codex", + "gpt-5.3-codex", + "gpt-5.3-codex-spark", + ])("omits the all_turns default for pre-5.4 model %s", async modelId => { + const model = createCodexModel(modelId); - const defaulted = await transformRequestBody({ model: model.id }, model, { reasoningEffort: "medium" }); - expect(defaulted.reasoning).toBeDefined(); - expect(defaulted.reasoning?.context).toBeUndefined(); - expect("context" in (defaulted.reasoning ?? {})).toBe(false); + const defaulted = await transformRequestBody({ model: model.id }, model, { reasoningEffort: "medium" }); + expect(defaulted.reasoning).toBeDefined(); + expect(defaulted.reasoning?.context).toBeUndefined(); + expect("context" in (defaulted.reasoning ?? {})).toBe(false); - // A supported override (current_turn/auto) is still honored. - const overridden = await transformRequestBody({ model: model.id }, model, { - reasoningEffort: "medium", - reasoningContext: "current_turn", - }); - expect(overridden.reasoning?.context).toBe("current_turn"); - }, - ); + // A supported override (current_turn/auto) is still honored. + const overridden = await transformRequestBody({ model: model.id }, model, { + reasoningEffort: "medium", + reasoningContext: "current_turn", + }); + expect(overridden.reasoning?.context).toBe("current_turn"); + }); it("suppresses an explicit all_turns override on a pre-5.4 model", async () => { const model = createCodexModel("gpt-5.3-codex-spark"); @@ -254,23 +255,24 @@ describe("openai-codex reasoning.summary", () => { // gpt-5.1-codex / gpt-5.3-codex / gpt-5.3-codex-spark reject `reasoning.summary` // ("Unsupported parameter: 'reasoning.summary' is not supported with this model"). - it.each(["gpt-5.1-codex", "gpt-5.3-codex", "gpt-5.3-codex-spark"])( - "omits reasoning.summary for pre-5.4 model %s", - async modelId => { - const model = createCodexModel(modelId); + it.each([ + "gpt-5.1-codex", + "gpt-5.3-codex", + "gpt-5.3-codex-spark", + ])("omits reasoning.summary for pre-5.4 model %s", async modelId => { + const model = createCodexModel(modelId); - const defaulted = await transformRequestBody({ model: model.id }, model, { reasoningEffort: "medium" }); - expect(defaulted.reasoning).toBeDefined(); - expect("summary" in (defaulted.reasoning ?? {})).toBe(false); + const defaulted = await transformRequestBody({ model: model.id }, model, { reasoningEffort: "medium" }); + expect(defaulted.reasoning).toBeDefined(); + expect("summary" in (defaulted.reasoning ?? {})).toBe(false); - // Even an explicit summary level is suppressed on unsupported ids. - const forced = await transformRequestBody({ model: model.id }, model, { - reasoningEffort: "medium", - reasoningSummary: "detailed", - }); - expect("summary" in (forced.reasoning ?? {})).toBe(false); - }, - ); + // Even an explicit summary level is suppressed on unsupported ids. + const forced = await transformRequestBody({ model: model.id }, model, { + reasoningEffort: "medium", + reasoningSummary: "detailed", + }); + expect("summary" in (forced.reasoning ?? {})).toBe(false); + }); }); describe("openai-codex Responses Lite input shaping", () => { @@ -375,21 +377,23 @@ describe("openai-codex Responses Lite input shaping", () => { expect(disabled.tools).toBeUndefined(); }); - it.each(["gpt-5.3-codex-spark", "gpt-5.6-luna", "gpt-5.6-terra", "gpt-5.6-sol"])( - "preserves a forced computer function through Lite for %s", - async modelId => { - const model = createCodexModel(modelId); - const computer = { type: "function", name: "computer", parameters: { type: "object" } }; - const other = { type: "function", name: "read", parameters: { type: "object" } }; - const body = await transformRequestBody( - { model: model.id, tools: [computer, other], tool_choice: { type: "function", name: "computer" } }, - model, - { responsesLite: true }, - ); - expect(body.input?.[0]).toEqual({ type: "additional_tools", role: "developer", tools: [computer] }); - expect(body.tool_choice).toBe("required"); - }, - ); + it.each([ + "gpt-5.3-codex-spark", + "gpt-5.6-luna", + "gpt-5.6-terra", + "gpt-5.6-sol", + ])("preserves a forced computer function through Lite for %s", async modelId => { + const model = createCodexModel(modelId); + const computer = { type: "function", name: "computer", parameters: { type: "object" } }; + const other = { type: "function", name: "read", parameters: { type: "object" } }; + const body = await transformRequestBody( + { model: model.id, tools: [computer, other], tool_choice: { type: "function", name: "computer" } }, + model, + { responsesLite: true }, + ); + expect(body.input?.[0]).toEqual({ type: "additional_tools", role: "developer", tools: [computer] }); + expect(body.tool_choice).toBe("required"); + }); it("moves instructions and tools into input items under lite", async () => { const model = createCodexModel("gpt-5.6-terra"); diff --git a/packages/catalog/test/issue-1617-repro.test.ts b/packages/catalog/test/issue-1617-repro.test.ts index e80ae88da..101a2f93c 100644 --- a/packages/catalog/test/issue-1617-repro.test.ts +++ b/packages/catalog/test/issue-1617-repro.test.ts @@ -38,23 +38,23 @@ describe("opencode-zen/-go resolver routes MiniMax M3 to openai-completions (iss const npmAnthropic: ModelsDevModel = { provider: { npm: "@ai-sdk/anthropic" }, tool_call: true }; describe("opencode-zen", () => { - test.each([["minimax-m3"], ["minimax-m3-free"]])( - "%s resolves to openai-completions on /v1/chat/completions", - modelId => { - const resolved = zenDescriptor?.resolveApi?.(modelId, npmAnthropic); - expect(resolved).toEqual({ api: "openai-completions", baseUrl: OPENCODE_ZEN_BASE }); - }, - ); + test.each([ + ["minimax-m3"], + ["minimax-m3-free"], + ])("%s resolves to openai-completions on /v1/chat/completions", modelId => { + const resolved = zenDescriptor?.resolveApi?.(modelId, npmAnthropic); + expect(resolved).toEqual({ api: "openai-completions", baseUrl: OPENCODE_ZEN_BASE }); + }); }); describe("opencode-go", () => { - test.each([["minimax-m3"], ["minimax-m3-free"]])( - "%s resolves to openai-completions on /v1/chat/completions", - modelId => { - const resolved = goDescriptor?.resolveApi?.(modelId, npmAnthropic); - expect(resolved).toEqual({ api: "openai-completions", baseUrl: OPENCODE_GO_BASE }); - }, - ); + test.each([ + ["minimax-m3"], + ["minimax-m3-free"], + ])("%s resolves to openai-completions on /v1/chat/completions", modelId => { + const resolved = goDescriptor?.resolveApi?.(modelId, npmAnthropic); + expect(resolved).toEqual({ api: "openai-completions", baseUrl: OPENCODE_GO_BASE }); + }); }); test("opencode-zen /v1/models refresh routes a freshly-discovered M3 to openai-completions", async () => { diff --git a/packages/catalog/test/issue-887-repro.test.ts b/packages/catalog/test/issue-887-repro.test.ts index e91d728a4..09f445c0e 100644 --- a/packages/catalog/test/issue-887-repro.test.ts +++ b/packages/catalog/test/issue-887-repro.test.ts @@ -26,13 +26,14 @@ describe("opencode-go resolver routes 404-ing ids to openai-completions (issue # // would route them to /v1/messages on opencode.ai/zen/go which 404s. const npmAnthropic: ModelsDevModel = { provider: { npm: "@ai-sdk/anthropic" }, tool_call: true }; - test.each([["minimax-m2.7"], ["qwen3.5-plus"], ["qwen3.6-plus"]])( - "%s resolves to openai-completions on /v1/chat/completions", - modelId => { - const resolved = descriptor?.resolveApi?.(modelId, npmAnthropic); - expect(resolved).toEqual({ api: "openai-completions", baseUrl: OPENCODE_GO_BASE }); - }, - ); + test.each([ + ["minimax-m2.7"], + ["qwen3.5-plus"], + ["qwen3.6-plus"], + ])("%s resolves to openai-completions on /v1/chat/completions", modelId => { + const resolved = descriptor?.resolveApi?.(modelId, npmAnthropic); + expect(resolved).toEqual({ api: "openai-completions", baseUrl: OPENCODE_GO_BASE }); + }); test("minimax-m2.5 (control: works empirically) also resolves to openai-completions", () => { // models.dev currently lists minimax-m2.5 without an explicit provider.npm, diff --git a/packages/catalog/test/litellm-provider.test.ts b/packages/catalog/test/litellm-provider.test.ts index 24183fd24..ea2f2c3a7 100644 --- a/packages/catalog/test/litellm-provider.test.ts +++ b/packages/catalog/test/litellm-provider.test.ts @@ -428,60 +428,61 @@ describe("LiteLLM provider discovery", () => { expect(models?.find(model => model.id === "params-tools")?.supportsTools).toBe(true); }); - test.each([["all-team-models"], ["all-proxy-models"], ["no-default-models"]])( - "falls back from %s placeholder to v2 model info", - async sentinelModelId => { - const calls: string[] = []; - const fetchMock = vi.fn(async (input: string | URL | Request) => { - const url = inputUrl(input); - calls.push(url); - if (url === MODELS_DEV_URL) { - return Response.json({}); - } - if (url === "http://primary:4000/model_group/info") { - return Response.json({ data: [makeLiteLLMSentinelPlaceholder(sentinelModelId)] }); - } - if (url === "http://primary:4000/v2/model/info") { - return Response.json({ - data: [ - { - model_name: "example-real-model", - model_info: { - max_input_tokens: 200_000, - max_output_tokens: 12_000, - supports_vision: false, - supports_reasoning: true, - }, + test.each([ + ["all-team-models"], + ["all-proxy-models"], + ["no-default-models"], + ])("falls back from %s placeholder to v2 model info", async sentinelModelId => { + const calls: string[] = []; + const fetchMock = vi.fn(async (input: string | URL | Request) => { + const url = inputUrl(input); + calls.push(url); + if (url === MODELS_DEV_URL) { + return Response.json({}); + } + if (url === "http://primary:4000/model_group/info") { + return Response.json({ data: [makeLiteLLMSentinelPlaceholder(sentinelModelId)] }); + } + if (url === "http://primary:4000/v2/model/info") { + return Response.json({ + data: [ + { + model_name: "example-real-model", + model_info: { + max_input_tokens: 200_000, + max_output_tokens: 12_000, + supports_vision: false, + supports_reasoning: true, }, - ], - }); - } - if (url === "http://primary:4000/v1/models") { - throw new Error("/v1/models should not be called when v2 metadata succeeds"); - } - throw new Error(`Unexpected URL: ${url}`); - }) as FetchImpl; - const options = litellmModelManagerOptions({ - apiKey: "sk-rich", - baseUrl: "http://primary:4000/v1", - fetch: fetchMock, - }); + }, + ], + }); + } + if (url === "http://primary:4000/v1/models") { + throw new Error("/v1/models should not be called when v2 metadata succeeds"); + } + throw new Error(`Unexpected URL: ${url}`); + }) as FetchImpl; + const options = litellmModelManagerOptions({ + apiKey: "sk-rich", + baseUrl: "http://primary:4000/v1", + fetch: fetchMock, + }); - const models = await options.fetchDynamicModels?.(); + const models = await options.fetchDynamicModels?.(); - expect(calls).toContain("http://primary:4000/model_group/info"); - expect(calls).toContain("http://primary:4000/v2/model/info"); - expect(calls).not.toContain("http://primary:4000/v1/models"); - expect(models?.map(model => model.id)).toEqual(["example-real-model"]); - expect(models?.[0]).toMatchObject({ - id: "example-real-model", - contextWindow: 200_000, - maxTokens: 12_000, - input: ["text"], - reasoning: true, - }); - }, - ); + expect(calls).toContain("http://primary:4000/model_group/info"); + expect(calls).toContain("http://primary:4000/v2/model/info"); + expect(calls).not.toContain("http://primary:4000/v1/models"); + expect(models?.map(model => model.id)).toEqual(["example-real-model"]); + expect(models?.[0]).toMatchObject({ + id: "example-real-model", + contextWindow: 200_000, + maxTokens: 12_000, + input: ["text"], + reasoning: true, + }); + }); test("filters all-team-models placeholder from mixed model_group info", async () => { const calls: string[] = []; diff --git a/packages/coding-agent/test/advisor/advisor.test.ts b/packages/coding-agent/test/advisor/advisor.test.ts index 9ebf05c48..265c26f8e 100644 --- a/packages/coding-agent/test/advisor/advisor.test.ts +++ b/packages/coding-agent/test/advisor/advisor.test.ts @@ -4035,60 +4035,60 @@ describe("advisor", () => { expect(promptInputs[1]).toContain("keep me"); }); - it.each(["success", "error"] as const)( - "releases blocked %s hooks so reset can run replacement work", - async hookKind => { - const hookStarted = Promise.withResolvers(); - const releaseHook = Promise.withResolvers(); - const replacementPromptStarted = Promise.withResolvers(); - let promptCalls = 0; - let hookCalls = 0; - const blockHook = async () => { - if (++hookCalls !== 1) return; - hookStarted.resolve(); - await releaseHook.promise; - }; - const agent: AdvisorAgent = { - prompt: async () => { - promptCalls++; - if (promptCalls === 1 && hookKind === "error") throw new Error("provider failure"); - if (promptCalls === 2) replacementPromptStarted.resolve(); - }, - abort: () => {}, - reset: () => {}, - state: { messages: [] }, - }; - const runtime = new AdvisorRuntime(agent, { - snapshotMessages: () => [], - enqueueAdvice: () => {}, - ...(hookKind === "success" - ? { onTurnSuccess: blockHook } - : { - onTurnError: async () => { - await blockHook(); - return false; - }, - }), - }); + it.each([ + "success", + "error", + ] as const)("releases blocked %s hooks so reset can run replacement work", async hookKind => { + const hookStarted = Promise.withResolvers(); + const releaseHook = Promise.withResolvers(); + const replacementPromptStarted = Promise.withResolvers(); + let promptCalls = 0; + let hookCalls = 0; + const blockHook = async () => { + if (++hookCalls !== 1) return; + hookStarted.resolve(); + await releaseHook.promise; + }; + const agent: AdvisorAgent = { + prompt: async () => { + promptCalls++; + if (promptCalls === 1 && hookKind === "error") throw new Error("provider failure"); + if (promptCalls === 2) replacementPromptStarted.resolve(); + }, + abort: () => {}, + reset: () => {}, + state: { messages: [] }, + }; + const runtime = new AdvisorRuntime(agent, { + snapshotMessages: () => [], + enqueueAdvice: () => {}, + ...(hookKind === "success" + ? { onTurnSuccess: blockHook } + : { + onTurnError: async () => { + await blockHook(); + return false; + }, + }), + }); - runtime.onTurnEnd([{ role: "user", content: "old session", timestamp: 1 } as AgentMessage]); - await hookStarted.promise; - const pause = runtime.pauseForSessionTransition(); - const pausedQuickly = await Promise.race([pause.then(() => true), Bun.sleep(50).then(() => false)]); - runtime.reset(); - runtime.onTurnEnd([{ role: "user", content: "replacement session", timestamp: 2 } as AgentMessage]); - const replacementRan = await Promise.race([ - replacementPromptStarted.promise.then(() => true), - Bun.sleep(50).then(() => false), - ]); - releaseHook.resolve(); - await pause; - runtime.dispose(); + runtime.onTurnEnd([{ role: "user", content: "old session", timestamp: 1 } as AgentMessage]); + await hookStarted.promise; + const pause = runtime.pauseForSessionTransition(); + const pausedQuickly = await Promise.race([pause.then(() => true), Bun.sleep(50).then(() => false)]); + runtime.reset(); + runtime.onTurnEnd([{ role: "user", content: "replacement session", timestamp: 2 } as AgentMessage]); + const replacementRan = await Promise.race([ + replacementPromptStarted.promise.then(() => true), + Bun.sleep(50).then(() => false), + ]); + releaseHook.resolve(); + await pause; + runtime.dispose(); - expect(pausedQuickly).toBe(true); - expect(replacementRan).toBe(true); - }, - ); + expect(pausedQuickly).toBe(true); + expect(replacementRan).toBe(true); + }); it("aborts retry backoff before pausing for a session transition", async () => { const recoveryStarted = Promise.withResolvers(); const agent: AdvisorAgent = { diff --git a/packages/coding-agent/test/agent-session-acp-permission.test.ts b/packages/coding-agent/test/agent-session-acp-permission.test.ts index b02f7141c..6e44e630a 100644 --- a/packages/coding-agent/test/agent-session-acp-permission.test.ts +++ b/packages/coding-agent/test/agent-session-acp-permission.test.ts @@ -849,57 +849,56 @@ it("allow_always: caches decision and calls bridge only once for subsequent exec expect(bashTool.executeCalls).toBe(2); }); -it.each(boundaryCases)( - "%s permission decisions prompt again after a successful %s session boundary", - async (decision, transition) => { - const bashTool = makeFakeTool("bash"); - const bridge = makeBridge({ outcome: "selected", optionId: decision, kind: decision }); - const permissionSpy = spyOn(bridge, "requestPermission"); - session = await createSession([bashTool], bridge, {}, { persist: true }); +it.each( + boundaryCases, +)("%s permission decisions prompt again after a successful %s session boundary", async (decision, transition) => { + const bashTool = makeFakeTool("bash"); + const bridge = makeBridge({ outcome: "selected", optionId: decision, kind: decision }); + const permissionSpy = spyOn(bridge, "requestPermission"); + session = await createSession([bashTool], bridge, {}, { persist: true }); - await session.setActiveToolsByName(["bash"]); - const wrappedBash = session.agent.state.tools.find(tool => tool.name === "bash"); - if (!wrappedBash) throw new Error("Expected wrapped bash tool"); + await session.setActiveToolsByName(["bash"]); + const wrappedBash = session.agent.state.tools.find(tool => tool.name === "bash"); + if (!wrappedBash) throw new Error("Expected wrapped bash tool"); - for (let callIndex = 0; callIndex < 2; callIndex++) { - if (callIndex === 1) { - if (transition === "new") { - expect(await session.newSession()).toBe(true); - } else { - const targetId = `permission-target-${Bun.nanoseconds()}`; - const targetPath = `${tempDir.path()}/${targetId}.jsonl`; - await Bun.write( - targetPath, - `${JSON.stringify({ - type: "session", - version: 3, - id: targetId, - timestamp: new Date().toISOString(), - cwd: tempDir.path(), - })}\n`, - ); - expect(await session.switchSession(targetPath)).toBe(true); - } - } - - const execution = wrappedBash.execute( - `call-${callIndex}`, - { command: "echo boundary" }, - undefined, - undefined as never, - undefined as never, - ); - if (decision === "reject_always") { - await expect(execution).rejects.toThrow(/rejected by user/); + for (let callIndex = 0; callIndex < 2; callIndex++) { + if (callIndex === 1) { + if (transition === "new") { + expect(await session.newSession()).toBe(true); } else { - await execution; + const targetId = `permission-target-${Bun.nanoseconds()}`; + const targetPath = `${tempDir.path()}/${targetId}.jsonl`; + await Bun.write( + targetPath, + `${JSON.stringify({ + type: "session", + version: 3, + id: targetId, + timestamp: new Date().toISOString(), + cwd: tempDir.path(), + })}\n`, + ); + expect(await session.switchSession(targetPath)).toBe(true); } } - expect(permissionSpy).toHaveBeenCalledTimes(2); - expect(bashTool.executeCalls).toBe(decision === "allow_always" ? 2 : 0); - }, -); + const execution = wrappedBash.execute( + `call-${callIndex}`, + { command: "echo boundary" }, + undefined, + undefined as never, + undefined as never, + ); + if (decision === "reject_always") { + await expect(execution).rejects.toThrow(/rejected by user/); + } else { + await execution; + } + } + + expect(permissionSpy).toHaveBeenCalledTimes(2); + expect(bashTool.executeCalls).toBe(decision === "allow_always" ? 2 : 0); +}); // --------------------------------------------------------------------------- // 4. Read tool not gated: bridge never called even when bridge is set diff --git a/packages/coding-agent/test/agent-session-bash-session-ownership.test.ts b/packages/coding-agent/test/agent-session-bash-session-ownership.test.ts index 46e02e995..fd056dd73 100644 --- a/packages/coding-agent/test/agent-session-bash-session-ownership.test.ts +++ b/packages/coding-agent/test/agent-session-bash-session-ownership.test.ts @@ -203,67 +203,68 @@ describe("AgentSession bash session ownership", () => { ).toBe(true); }); - it.each(["new", "switch", "branch"] as const)( - "records a late bash result in its original session after %s", - async transition => { - const sessionDir = path.join(tempDir.path(), "sessions"); - const { completion, emitUserBash, extensionRunner } = createGatedBashRunner(); - createSession(SessionManager.create(tempDir.path(), sessionDir), extensionRunner); - const oldSessionFile = await seedPersistedSession(); - const oldSessionId = session.sessionId; + it.each([ + "new", + "switch", + "branch", + ] as const)("records a late bash result in its original session after %s", async transition => { + const sessionDir = path.join(tempDir.path(), "sessions"); + const { completion, emitUserBash, extensionRunner } = createGatedBashRunner(); + createSession(SessionManager.create(tempDir.path(), sessionDir), extensionRunner); + const oldSessionFile = await seedPersistedSession(); + const oldSessionId = session.sessionId; - const bashPromise = session.executeBash("old-session-command"); - expect(emitUserBash).toHaveBeenCalledTimes(1); + const bashPromise = session.executeBash("old-session-command"); + expect(emitUserBash).toHaveBeenCalledTimes(1); - switch (transition) { - case "new": - await session.newSession(); - break; - case "switch": { - const targetManager = SessionManager.create(tempDir.path(), sessionDir); - targetManager.appendMessage({ role: "user", content: "target", timestamp: Date.now() }); - targetManager.appendMessage(createAssistantMessage("target reply")); - await targetManager.ensureOnDisk(); - const targetFile = targetManager.getSessionFile(); - if (!targetFile) throw new Error("Expected target session file"); - await targetManager.close(); - await session.switchSession(targetFile); - break; - } - case "branch": { - const userEntry = session.sessionManager - .getEntries() - .find(entry => entry.type === "message" && entry.message.role === "user"); - if (!userEntry) throw new Error("Expected user entry for branch"); - await session.branch(userEntry.id); - break; - } + switch (transition) { + case "new": + await session.newSession(); + break; + case "switch": { + const targetManager = SessionManager.create(tempDir.path(), sessionDir); + targetManager.appendMessage({ role: "user", content: "target", timestamp: Date.now() }); + targetManager.appendMessage(createAssistantMessage("target reply")); + await targetManager.ensureOnDisk(); + const targetFile = targetManager.getSessionFile(); + if (!targetFile) throw new Error("Expected target session file"); + await targetManager.close(); + await session.switchSession(targetFile); + break; } + case "branch": { + const userEntry = session.sessionManager + .getEntries() + .find(entry => entry.type === "message" && entry.message.role === "user"); + if (!userEntry) throw new Error("Expected user entry for branch"); + await session.branch(userEntry.id); + break; + } + } - expect(session.sessionId).not.toBe(oldSessionId); - completion.resolve({ result: bashResult }); - await bashPromise; + expect(session.sessionId).not.toBe(oldSessionId); + completion.resolve({ result: bashResult }); + await bashPromise; - expect( - session.messages.some( - message => message.role === "bashExecution" && message.command === "old-session-command", - ), - ).toBe(false); + expect( + session.messages.some( + message => message.role === "bashExecution" && message.command === "old-session-command", + ), + ).toBe(false); - const oldSession = await SessionManager.open(oldSessionFile, sessionDir, undefined, { - initialCwd: tempDir.path(), - suppressBreadcrumb: true, - }); - additionalManagers.push(oldSession); - const oldMessages = oldSession.getBranch().flatMap(entry => (entry.type === "message" ? [entry.message] : [])); - expect(oldMessages.slice(-3).map(message => message.role)).toEqual(["user", "assistant", "bashExecution"]); - expect(oldMessages.at(-1)).toMatchObject({ - role: "bashExecution", - command: "old-session-command", - output: "old-output", - }); - }, - ); + const oldSession = await SessionManager.open(oldSessionFile, sessionDir, undefined, { + initialCwd: tempDir.path(), + suppressBreadcrumb: true, + }); + additionalManagers.push(oldSession); + const oldMessages = oldSession.getBranch().flatMap(entry => (entry.type === "message" ? [entry.message] : [])); + expect(oldMessages.slice(-3).map(message => message.role)).toEqual(["user", "assistant", "bashExecution"]); + expect(oldMessages.at(-1)).toMatchObject({ + role: "bashExecution", + command: "old-session-command", + output: "old-output", + }); + }); it("stores minimized bash output with the originating session", async () => { const sessionDir = path.join(tempDir.path(), "sessions"); diff --git a/packages/coding-agent/test/interactive-mode-plan-review.test.ts b/packages/coding-agent/test/interactive-mode-plan-review.test.ts index a2fb1fa55..49ae9e43a 100644 --- a/packages/coding-agent/test/interactive-mode-plan-review.test.ts +++ b/packages/coding-agent/test/interactive-mode-plan-review.test.ts @@ -1878,13 +1878,14 @@ describe("InteractiveMode plan review rendering", () => { // `#approvePlan`'s `finally`. No aborted message_end is required to consume it, // so a stranded flag could otherwise silence the next unrelated abort. One // parametrized case per outcome keeps ok/cancelled/failed each covered. - it.each(["ok", "cancelled", "failed"] as const)( - "B1-B3: Approve and compact context + %s outcome → flag cleared by finally", - async outcome => { - await approveWithCompact(outcome); - expect(session.isPlanInternalAbortPending).toBe(false); - }, - ); + it.each([ + "ok", + "cancelled", + "failed", + ] as const)("B1-B3: Approve and compact context + %s outcome → flag cleared by finally", async outcome => { + await approveWithCompact(outcome); + expect(session.isPlanInternalAbortPending).toBe(false); + }); it("B4: Approve and compact context + handleCompactCommand throws → showError surfaces the failure AND flag cleared by finally before the outer catch", async () => { // `handlePlanApproval` wraps `#approvePlan` in a try/catch diff --git a/packages/coding-agent/test/internal-urls/memory-protocol.test.ts b/packages/coding-agent/test/internal-urls/memory-protocol.test.ts index 6bd8f71c8..f8b1b36cf 100644 --- a/packages/coding-agent/test/internal-urls/memory-protocol.test.ts +++ b/packages/coding-agent/test/internal-urls/memory-protocol.test.ts @@ -315,27 +315,27 @@ describe("MemoryProtocolHandler", () => { }); }); - it.each(["memory://root/skills/**/../*.md", "memory://root/skills/**/%2e%2e/*.md"])( - "rejects traversal in a memory glob suffix: %s", - async pattern => { - await withMemoryFixture(async ({ cwd }) => { - await expect(createGlobTool(cwd).execute("memory-glob-traversal", { path: pattern })).rejects.toThrow( - /traversal/i, - ); - }); - }, - ); + it.each([ + "memory://root/skills/**/../*.md", + "memory://root/skills/**/%2e%2e/*.md", + ])("rejects traversal in a memory glob suffix: %s", async pattern => { + await withMemoryFixture(async ({ cwd }) => { + await expect(createGlobTool(cwd).execute("memory-glob-traversal", { path: pattern })).rejects.toThrow( + /traversal/i, + ); + }); + }); - it.each(["memory://root/skills/**/demo%2fnested/*.md", "memory://root/skills/**/demo%5cnested/*.md"])( - "rejects encoded separators in a memory glob suffix: %s", - async pattern => { - await withMemoryFixture(async ({ cwd }) => { - await expect(createGlobTool(cwd).execute("memory-glob-separator", { path: pattern })).rejects.toThrow( - /encoded path separator/i, - ); - }); - }, - ); + it.each([ + "memory://root/skills/**/demo%2fnested/*.md", + "memory://root/skills/**/demo%5cnested/*.md", + ])("rejects encoded separators in a memory glob suffix: %s", async pattern => { + await withMemoryFixture(async ({ cwd }) => { + await expect(createGlobTool(cwd).execute("memory-glob-separator", { path: pattern })).rejects.toThrow( + /encoded path separator/i, + ); + }); + }); it("throws clear error for missing files", async () => { await withMemoryFixture(async () => { diff --git a/packages/coding-agent/test/rpc.test.ts b/packages/coding-agent/test/rpc.test.ts index c2c7ed1db..41e764d05 100644 --- a/packages/coding-agent/test/rpc.test.ts +++ b/packages/coding-agent/test/rpc.test.ts @@ -322,10 +322,7 @@ describe("RPC fast mode with unsupported Fireworks model and priority tier", () beforeEach(async () => { sessionDir = path.join(os.tmpdir(), `omp-rpc-fast-mode-test-${Snowflake.next()}`); - await Bun.write( - path.join(sessionDir, "config.yml"), - ["providers:", " fireworksTier: priority", ""].join("\n"), - ); + await Bun.write(path.join(sessionDir, "config.yml"), ["providers:", " fireworksTier: priority", ""].join("\n")); client = new RpcClient({ cliPath: path.join(import.meta.dir, "..", "src", "cli.ts"), cwd: path.join(import.meta.dir, ".."), diff --git a/packages/coding-agent/test/sdk-async-job-manager-singleton.test.ts b/packages/coding-agent/test/sdk-async-job-manager-singleton.test.ts index a6c8bb479..9bc0c2517 100644 --- a/packages/coding-agent/test/sdk-async-job-manager-singleton.test.ts +++ b/packages/coding-agent/test/sdk-async-job-manager-singleton.test.ts @@ -6,8 +6,8 @@ import { AsyncJobManager } from "@oh-my-pi/pi-coding-agent/async/job-manager"; import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { createAgentSession, type ExtensionFactory } from "@oh-my-pi/pi-coding-agent/sdk"; -import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import type { AsyncJobSnapshot } from "@oh-my-pi/pi-coding-agent/session/agent-session"; +import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; import { type } from "arktype"; @@ -40,10 +40,7 @@ describe("AsyncJobManager singleton across concurrent top-level sessions", () => AsyncJobManager.resetForTests(); }); - async function spawnTopLevelSession( - extraSettings?: Record, - extensions: ExtensionFactory[] = [], - ) { + async function spawnTopLevelSession(extraSettings?: Record, extensions: ExtensionFactory[] = []) { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), `pi-sdk-async-singleton-${Snowflake.next()}-`)); tempDirs.push(tempDir); const cwd = path.join(tempDir, `project-${Snowflake.next()}`); diff --git a/packages/coding-agent/test/task/task-preflight.test.ts b/packages/coding-agent/test/task/task-preflight.test.ts index 97c70880b..d1c017234 100644 --- a/packages/coding-agent/test/task/task-preflight.test.ts +++ b/packages/coding-agent/test/task/task-preflight.test.ts @@ -97,19 +97,22 @@ describe("task async preflight", () => { spawns: "scout", expectation: "Cannot spawn 'task'", }, - ])( - "returns $name policy errors before registering an async job", - async ({ name, params, settings, spawns, expectation }) => { - mockDiscovery(); - const jobs = manager(); - const tool = await TaskTool.create(createSession({ manager: jobs, settings, spawns })); + ])("returns $name policy errors before registering an async job", async ({ + name, + params, + settings, + spawns, + expectation, + }) => { + mockDiscovery(); + const jobs = manager(); + const tool = await TaskTool.create(createSession({ manager: jobs, settings, spawns })); - const result = await tool.execute("preflight", params as TaskParams); + const result = await tool.execute("preflight", params as TaskParams); - expect(textOf(result)).toContain(expectation); - expect(jobs.getJob(name)).toBeUndefined(); - }, - ); + expect(textOf(result)).toContain(expectation); + expect(jobs.getJob(name)).toBeUndefined(); + }); it("rejects an invalid async batch atomically before dispatching any item", async () => { mockDiscovery(); diff --git a/packages/coding-agent/test/title-generator.test.ts b/packages/coding-agent/test/title-generator.test.ts index a8a8317a1..58ff02529 100644 --- a/packages/coding-agent/test/title-generator.test.ts +++ b/packages/coding-agent/test/title-generator.test.ts @@ -427,24 +427,25 @@ describe("title generator", () => { expect(title).toBe("Fix login button on mobile"); }); - it.each(["Here's a thinking process:", "Thinking process:", "Reasoning process:"])( - "rejects a markerless prose thinking preamble: %s", - async responseText => { - const model = getModelFor("deepseek", "deepseek-v4-pro"); - vi.spyOn(ai, "completeSimple").mockResolvedValue({ - stopReason: "stop", - content: [{ type: "text", text: responseText }], - } as never); + it.each([ + "Here's a thinking process:", + "Thinking process:", + "Reasoning process:", + ])("rejects a markerless prose thinking preamble: %s", async responseText => { + const model = getModelFor("deepseek", "deepseek-v4-pro"); + vi.spyOn(ai, "completeSimple").mockResolvedValue({ + stopReason: "stop", + content: [{ type: "text", text: responseText }], + } as never); - const title = await generateSessionTitle( - "the login button is broken on mobile", - createRegistry(model), - createSettings(model), - ); + const title = await generateSessionTitle( + "the login button is broken on mobile", + createRegistry(model), + createSettings(model), + ); - expect(title).toBeNull(); - }, - ); + expect(title).toBeNull(); + }); it("preserves a markerless title that mentions a tag", async () => { const model = getModelFor("deepseek", "deepseek-v4-pro"); diff --git a/packages/coding-agent/test/tools/bash-interceptor.test.ts b/packages/coding-agent/test/tools/bash-interceptor.test.ts index d9309ccb8..07ab41939 100644 --- a/packages/coding-agent/test/tools/bash-interceptor.test.ts +++ b/packages/coding-agent/test/tools/bash-interceptor.test.ts @@ -114,21 +114,27 @@ describe("default echo/printf redirect rule", () => { describe("default hub start rules", () => { const tools = ["hub"]; - it.each(["bun run dev", "vite --host 0.0.0.0", "lldb ./app", "bun test --watch", "nohup server", "server &"])( - "routes %s to hub start", - command => { - const result = checkBashInterception(command, tools, DEFAULT_BASH_INTERCEPTOR_RULES); - expect(result.block).toBe(true); - expect(result.suggestedTool).toBe("hub"); - }, - ); + it.each([ + "bun run dev", + "vite --host 0.0.0.0", + "lldb ./app", + "bun test --watch", + "nohup server", + "server &", + ])("routes %s to hub start", command => { + const result = checkBashInterception(command, tools, DEFAULT_BASH_INTERCEPTOR_RULES); + expect(result.block).toBe(true); + expect(result.suggestedTool).toBe("hub"); + }); - it.each(["git diff -w", "docker compose up -d", "bun test", "printf 'server &'"])( - "does not misclassify finite command %s", - command => { - expect(checkBashInterception(command, tools, DEFAULT_BASH_INTERCEPTOR_RULES).block).toBe(false); - }, - ); + it.each([ + "git diff -w", + "docker compose up -d", + "bun test", + "printf 'server &'", + ])("does not misclassify finite command %s", command => { + expect(checkBashInterception(command, tools, DEFAULT_BASH_INTERCEPTOR_RULES).block).toBe(false); + }); }); describe("BashTool argument validation", () => {