From ed16dc06081b681a7b4aee75c535b46d14f13f47 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 8 Apr 2026 17:14:17 +0900 Subject: [PATCH 1/6] fix(chat-params): complete maxOutputTokens migration in session prompt params --- src/features/background-agent/manager.test.ts | 2 +- src/features/background-agent/spawner.test.ts | 2 +- src/shared/session-prompt-params-helpers.ts | 2 +- src/shared/session-prompt-params-state.test.ts | 2 +- src/tools/call-omo-agent/sync-executor.test.ts | 2 +- src/tools/call-omo-agent/sync-executor.ts | 2 +- src/tools/delegate-task/sync-prompt-sender.test.ts | 4 ++-- src/tools/delegate-task/sync-prompt-sender.ts | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/features/background-agent/manager.test.ts b/src/features/background-agent/manager.test.ts index 9e10e3c57..4e0e00892 100644 --- a/src/features/background-agent/manager.test.ts +++ b/src/features/background-agent/manager.test.ts @@ -1863,10 +1863,10 @@ describe("BackgroundManager.resume model persistence", () => { expect(getSessionPromptParams("session-advanced")).toEqual({ temperature: 0.25, topP: 0.55, + maxOutputTokens: 8192, options: { reasoningEffort: "high", thinking: { type: "disabled" }, - maxTokens: 8192, }, }) }) diff --git a/src/features/background-agent/spawner.test.ts b/src/features/background-agent/spawner.test.ts index d3896e55f..4c5ddeaf2 100644 --- a/src/features/background-agent/spawner.test.ts +++ b/src/features/background-agent/spawner.test.ts @@ -400,10 +400,10 @@ describe("background-agent spawner fallback model promotion", () => { expect(getSessionPromptParams("session-123")).toEqual({ temperature: 0.4, topP: 0.7, + maxOutputTokens: 4096, options: { reasoningEffort: "high", thinking: { type: "disabled" }, - maxTokens: 4096, }, }) }) diff --git a/src/shared/session-prompt-params-helpers.ts b/src/shared/session-prompt-params-helpers.ts index 7ce24c826..f50707956 100644 --- a/src/shared/session-prompt-params-helpers.ts +++ b/src/shared/session-prompt-params-helpers.ts @@ -20,12 +20,12 @@ export function applySessionPromptParams( const promptOptions: Record = { ...(model.reasoningEffort ? { reasoningEffort: model.reasoningEffort } : {}), ...(model.thinking ? { thinking: model.thinking } : {}), - ...(model.maxTokens !== undefined ? { maxTokens: model.maxTokens } : {}), } setSessionPromptParams(sessionID, { ...(model.temperature !== undefined ? { temperature: model.temperature } : {}), ...(model.top_p !== undefined ? { topP: model.top_p } : {}), + ...(model.maxTokens !== undefined ? { maxOutputTokens: model.maxTokens } : {}), ...(Object.keys(promptOptions).length > 0 ? { options: promptOptions } : {}), }) } diff --git a/src/shared/session-prompt-params-state.test.ts b/src/shared/session-prompt-params-state.test.ts index b97a80565..d52670be6 100644 --- a/src/shared/session-prompt-params-state.test.ts +++ b/src/shared/session-prompt-params-state.test.ts @@ -18,9 +18,9 @@ describe("session-prompt-params-state", () => { const params = { temperature: 0.4, topP: 0.7, + maxOutputTokens: 4096, options: { reasoningEffort: "high", - maxTokens: 4096, }, } diff --git a/src/tools/call-omo-agent/sync-executor.test.ts b/src/tools/call-omo-agent/sync-executor.test.ts index baa59fb78..404e3fee0 100644 --- a/src/tools/call-omo-agent/sync-executor.test.ts +++ b/src/tools/call-omo-agent/sync-executor.test.ts @@ -190,10 +190,10 @@ describe("executeSync", () => { expect(promptInput?.body.temperature).toBe(0.12) expect(promptInput?.body.topP).toBe(0.34) expect(promptInput?.body.options).toEqual({ - maxTokens: 5678, reasoningEffort: "medium", thinking: { type: "disabled" }, }) + expect(promptInput?.body.maxOutputTokens).toBe(5678) }) test("records metadata with description and created session id", async () => { diff --git a/src/tools/call-omo-agent/sync-executor.ts b/src/tools/call-omo-agent/sync-executor.ts index 096a80216..f0f65d7e1 100644 --- a/src/tools/call-omo-agent/sync-executor.ts +++ b/src/tools/call-omo-agent/sync-executor.ts @@ -43,12 +43,12 @@ function buildPromptGenerationParams(model: DelegatedModelConfig | undefined): R const promptOptions: Record = { ...(model.reasoningEffort ? { reasoningEffort: model.reasoningEffort } : {}), ...(model.thinking ? { thinking: model.thinking } : {}), - ...(model.maxTokens !== undefined ? { maxTokens: model.maxTokens } : {}), } return { ...(model.temperature !== undefined ? { temperature: model.temperature } : {}), ...(model.top_p !== undefined ? { topP: model.top_p } : {}), + ...(model.maxTokens !== undefined ? { maxOutputTokens: model.maxTokens } : {}), ...(Object.keys(promptOptions).length > 0 ? { options: promptOptions } : {}), } } diff --git a/src/tools/delegate-task/sync-prompt-sender.test.ts b/src/tools/delegate-task/sync-prompt-sender.test.ts index 32970e72a..f86e87997 100644 --- a/src/tools/delegate-task/sync-prompt-sender.test.ts +++ b/src/tools/delegate-task/sync-prompt-sender.test.ts @@ -277,15 +277,15 @@ bunDescribe("sendSyncPrompt", () => { bunExpect(promptArgs.body.options).toEqual({ reasoningEffort: "high", thinking: { type: "disabled" }, - maxTokens: 4096, }) + bunExpect(promptArgs.body.maxOutputTokens).toBe(4096) bunExpect(getSessionPromptParams("test-session")).toEqual({ temperature: 0.4, topP: 0.7, + maxOutputTokens: 4096, options: { reasoningEffort: "high", thinking: { type: "disabled" }, - maxTokens: 4096, }, }) }) diff --git a/src/tools/delegate-task/sync-prompt-sender.ts b/src/tools/delegate-task/sync-prompt-sender.ts index 882258d98..bd38830e5 100644 --- a/src/tools/delegate-task/sync-prompt-sender.ts +++ b/src/tools/delegate-task/sync-prompt-sender.ts @@ -30,12 +30,12 @@ function buildPromptGenerationParams(model: DelegatedModelConfig | undefined): R const promptOptions: Record = { ...(model.reasoningEffort ? { reasoningEffort: model.reasoningEffort } : {}), ...(model.thinking ? { thinking: model.thinking } : {}), - ...(model.maxTokens !== undefined ? { maxTokens: model.maxTokens } : {}), } return { ...(model.temperature !== undefined ? { temperature: model.temperature } : {}), ...(model.top_p !== undefined ? { topP: model.top_p } : {}), + ...(model.maxTokens !== undefined ? { maxOutputTokens: model.maxTokens } : {}), ...(Object.keys(promptOptions).length > 0 ? { options: promptOptions } : {}), } } From 1cf4119dd47af64242104955d593669cff7ddb9f Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 8 Apr 2026 17:15:02 +0900 Subject: [PATCH 2/6] fix(background): use parent session variant in notifyParentSession instead of child task variant --- src/features/background-agent/manager.test.ts | 65 ++++++++++++++++++- src/features/background-agent/manager.ts | 5 +- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/src/features/background-agent/manager.test.ts b/src/features/background-agent/manager.test.ts index 4e0e00892..0fcb463e3 100644 --- a/src/features/background-agent/manager.test.ts +++ b/src/features/background-agent/manager.test.ts @@ -1059,7 +1059,18 @@ describe("BackgroundManager.notifyParentSession - aborted parent", () => { prompt: promptMock, promptAsync: promptMock, abort: async () => ({}), - messages: async () => ({ data: [] }), + messages: async () => ({ + data: [{ + info: { + agent: "explore", + model: { + providerID: "anthropic", + modelID: "claude-opus-4-6", + variant: "high", + }, + }, + }], + }), }, } const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput) @@ -1219,6 +1230,58 @@ describe("BackgroundManager.notifyParentSession - variant propagation", () => { manager.shutdown() }) + test("should prefer parent session variant over child task variant in parent notification promptAsync body", async () => { + //#given + const promptCalls: Array<{ body: Record }> = [] + const client = { + session: { + prompt: async () => ({}), + promptAsync: async (args: { path: { id: string }; body: Record }) => { + promptCalls.push({ body: args.body }) + return {} + }, + abort: async () => ({}), + messages: async () => ({ + data: [{ + info: { + agent: "explore", + model: { + providerID: "anthropic", + modelID: "claude-opus-4-6", + variant: "max", + }, + }, + }], + }), + }, + } + const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput) + const task: BackgroundTask = { + id: "task-parent-variant-wins", + sessionID: "session-child", + parentSessionID: "session-parent", + parentMessageID: "msg-parent", + description: "task with mismatched variant", + prompt: "test", + agent: "explore", + status: "completed", + startedAt: new Date(), + completedAt: new Date(), + model: { providerID: "anthropic", modelID: "claude-opus-4-6", variant: "high" }, + } + getPendingByParent(manager).set("session-parent", new Set([task.id])) + + //#when + await (manager as unknown as { notifyParentSession: (task: BackgroundTask) => Promise }) + .notifyParentSession(task) + + //#then + expect(promptCalls).toHaveLength(1) + expect(promptCalls[0].body.variant).toBe("max") + + manager.shutdown() + }) + test("should not include variant in promptAsync body when task has no variant", async () => { //#given const promptCalls: Array<{ body: Record }> = [] diff --git a/src/features/background-agent/manager.ts b/src/features/background-agent/manager.ts index 1a23c3569..d06d5fcaf 100644 --- a/src/features/background-agent/manager.ts +++ b/src/features/background-agent/manager.ts @@ -1783,6 +1783,7 @@ export class BackgroundManager { let agent: string | undefined = task.parentAgent let model: { providerID: string; modelID: string } | undefined let tools: Record | undefined = task.parentTools + let promptContext: ReturnType = null if (this.enableParentSessionNotifications) { try { @@ -1796,7 +1797,7 @@ export class BackgroundManager { tools?: Record } }>) - const promptContext = resolvePromptContextFromSessionMessages( + promptContext = resolvePromptContextFromSessionMessages( messages, task.parentSessionID, ) @@ -1840,7 +1841,7 @@ export class BackgroundManager { const isTaskFailure = task.status === "error" || task.status === "cancelled" || task.status === "interrupt" const shouldReply = allComplete || isTaskFailure - const variant = task.model?.variant + const variant = promptContext?.model?.variant try { await this.client.session.promptAsync({ From 80c74c8849de1ce1b9ef9c8ed961cea5ce67fe09 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 8 Apr 2026 17:15:18 +0900 Subject: [PATCH 3/6] fix(background): prevent double-decrement of descendant quota in processKey error cleanup --- src/features/background-agent/manager.test.ts | 44 +++++++++++++++++++ src/features/background-agent/manager.ts | 4 -- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/features/background-agent/manager.test.ts b/src/features/background-agent/manager.test.ts index 0fcb463e3..67b584d4b 100644 --- a/src/features/background-agent/manager.test.ts +++ b/src/features/background-agent/manager.test.ts @@ -218,6 +218,10 @@ function getRootDescendantCounts(manager: BackgroundManager): Map }).rootDescendantCounts } +function getPreStartDescendantReservations(manager: BackgroundManager): Set { + return (manager as unknown as { preStartDescendantReservations: Set }).preStartDescendantReservations +} + function getQueuesByKey( manager: BackgroundManager ): Map> { @@ -2526,6 +2530,46 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => { expect(retryTask.status).toBe("pending") }) + test("should only roll back the failed task reservation once when siblings still exist", async () => { + // given + const concurrencyKey = "test-agent" + const task = createMockTask({ + id: "task-single-reservation-rollback", + sessionID: "session-single-reservation-rollback", + parentSessionID: "session-root", + status: "pending", + agent: "test-agent", + rootSessionID: "session-root", + }) + delete (task as Partial).sessionID + + const input = { + description: task.description, + prompt: task.prompt, + agent: task.agent, + parentSessionID: task.parentSessionID, + parentMessageID: task.parentMessageID, + } + + getTaskMap(manager).set(task.id, task) + getQueuesByKey(manager).set(concurrencyKey, [{ task, input }]) + getRootDescendantCounts(manager).set("session-root", 2) + getPreStartDescendantReservations(manager).add(task.id) + stubNotifyParentSession(manager) + + ;(manager as unknown as { + startTask: (item: { task: BackgroundTask; input: typeof input }) => Promise + }).startTask = async () => { + throw new Error("session create failed") + } + + // when + await processKeyForTest(manager, concurrencyKey) + + // then + expect(getRootDescendantCounts(manager).get("session-root")).toBe(1) + }) + test("should keep the next queued task when the first task is cancelled during session creation", async () => { // given const firstSessionID = "ses-first-cancelled-during-create" diff --git a/src/features/background-agent/manager.ts b/src/features/background-agent/manager.ts index d06d5fcaf..bd8ff2477 100644 --- a/src/features/background-agent/manager.ts +++ b/src/features/background-agent/manager.ts @@ -422,10 +422,6 @@ export class BackgroundManager { this.concurrencyManager.release(key) } - if (item.task.rootSessionID) { - this.unregisterRootDescendant(item.task.rootSessionID) - } - removeTaskToastTracking(item.task.id) // Abort the orphaned session if one was created before the error From 119c23342a8f1565e53e1c707c58dacee8823eb4 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 8 Apr 2026 17:25:54 +0900 Subject: [PATCH 4/6] test(background): fix variant propagation test to match parent-context resolution --- src/features/background-agent/manager.test.ts | 68 ++++--------------- 1 file changed, 14 insertions(+), 54 deletions(-) diff --git a/src/features/background-agent/manager.test.ts b/src/features/background-agent/manager.test.ts index 67b584d4b..5a3a430e6 100644 --- a/src/features/background-agent/manager.test.ts +++ b/src/features/background-agent/manager.test.ts @@ -1063,18 +1063,7 @@ describe("BackgroundManager.notifyParentSession - aborted parent", () => { prompt: promptMock, promptAsync: promptMock, abort: async () => ({}), - messages: async () => ({ - data: [{ - info: { - agent: "explore", - model: { - providerID: "anthropic", - modelID: "claude-opus-4-6", - variant: "high", - }, - }, - }], - }), + messages: async () => ({ data: [] }), }, } const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput) @@ -1159,7 +1148,18 @@ describe("BackgroundManager.notifyParentSession - notifications toggle", () => { prompt: promptMock, promptAsync: promptMock, abort: async () => ({}), - messages: async () => ({ data: [] }), + messages: async () => ({ + data: [{ + info: { + agent: "explore", + model: { + providerID: "anthropic", + modelID: "claude-opus-4-6", + variant: "high", + }, + }, + }], + }), }, } const manager = new BackgroundManager( @@ -1193,47 +1193,6 @@ describe("BackgroundManager.notifyParentSession - notifications toggle", () => { }) describe("BackgroundManager.notifyParentSession - variant propagation", () => { - test("should propagate variant in parent notification promptAsync body", async () => { - //#given - const promptCalls: Array<{ body: Record }> = [] - const client = { - session: { - prompt: async () => ({}), - promptAsync: async (args: { path: { id: string }; body: Record }) => { - promptCalls.push({ body: args.body }) - return {} - }, - abort: async () => ({}), - messages: async () => ({ data: [] }), - }, - } - const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput) - const task: BackgroundTask = { - id: "task-variant-test", - sessionID: "session-child", - parentSessionID: "session-parent", - parentMessageID: "msg-parent", - description: "task with variant", - prompt: "test", - agent: "explore", - status: "completed", - startedAt: new Date(), - completedAt: new Date(), - model: { providerID: "anthropic", modelID: "claude-opus-4-6", variant: "high" }, - } - getPendingByParent(manager).set("session-parent", new Set([task.id])) - - //#when - await (manager as unknown as { notifyParentSession: (task: BackgroundTask) => Promise }) - .notifyParentSession(task) - - //#then - expect(promptCalls).toHaveLength(1) - expect(promptCalls[0].body.variant).toBe("high") - - manager.shutdown() - }) - test("should prefer parent session variant over child task variant in parent notification promptAsync body", async () => { //#given const promptCalls: Array<{ body: Record }> = [] @@ -1588,6 +1547,7 @@ describe("BackgroundManager.tryCompleteTask", () => { const task = createMockTask({ id: "task-zombie-session", + sessionID: "session-zombie-placeholder", parentSessionID: "parent-zombie", status: "pending", agent: "explore", From dc1546c613f41715eec43057f0b1d96f1b68a282 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 8 Apr 2026 17:30:43 +0900 Subject: [PATCH 5/6] fix(ci): isolate mock.module tests per-file to prevent cross-contamination --- script/run-ci-tests.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/script/run-ci-tests.ts b/script/run-ci-tests.ts index 5466885ce..10cf80b21 100644 --- a/script/run-ci-tests.ts +++ b/script/run-ci-tests.ts @@ -29,13 +29,7 @@ async function usesModuleMock(rootDirectory: string, testFile: string): Promise< } function toIsolatedTarget(testFile: string): string { - const pathSegments = testFile.split("/") - - if (pathSegments.length <= 3) { - return testFile - } - - return pathSegments.slice(0, -1).join("/") + return testFile } function isCoveredByTarget(testFile: string, isolatedTarget: string): boolean { From d53be83634768172832ca52d603a33227fcfa266 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 8 Apr 2026 17:36:23 +0900 Subject: [PATCH 6/6] test(tmux): add missing tmux exports to zombie-pane mock module --- src/features/tmux-subagent/zombie-pane.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/features/tmux-subagent/zombie-pane.test.ts b/src/features/tmux-subagent/zombie-pane.test.ts index 42fcfb760..267c03cb3 100644 --- a/src/features/tmux-subagent/zombie-pane.test.ts +++ b/src/features/tmux-subagent/zombie-pane.test.ts @@ -40,10 +40,22 @@ mock.module("./action-executor", () => ({ mock.module("../../shared/tmux", () => ({ isInsideTmux: mockIsInsideTmux, getCurrentPaneId: mockGetCurrentPaneId, + isServerRunning: mock(async () => true), + resetServerCheck: mock(() => {}), + markServerRunningInProcess: mock(() => {}), + getPaneDimensions: mock(async () => ({ width: 220, height: 44 })), + spawnTmuxPane: mock(async () => ({ success: true, paneId: "%1" })), + closeTmuxPane: mock(async () => ({ success: true })), + replaceTmuxPane: mock(async () => ({ success: true, paneId: "%1" })), + spawnTmuxWindow: mock(async () => ({ success: true, windowId: "@1" })), + spawnTmuxSession: mock(async () => ({ success: true, sessionId: "mock" })), + applyLayout: mock(async () => ({ success: true })), + enforceMainPaneWidth: mock(async () => ({ success: true })), POLL_INTERVAL_BACKGROUND_MS: 10, SESSION_READY_POLL_INTERVAL_MS: 10, SESSION_READY_TIMEOUT_MS: 50, SESSION_MISSING_GRACE_MS: 1_000, + SESSION_TIMEOUT_MS: 600_000, })) afterAll(() => { mock.restore() })