Merge pull request #3223 from code-yeongyu/fix/core-runtime-bugs

fix(runtime): complete maxOutputTokens migration + parent variant + descendant quota
This commit is contained in:
YeonGyu-Kim
2026-04-08 17:40:28 +09:00
committed by GitHub
11 changed files with 98 additions and 28 deletions
+1 -7
View File
@@ -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 {
+74 -7
View File
@@ -218,6 +218,10 @@ function getRootDescendantCounts(manager: BackgroundManager): Map<string, number
return (manager as unknown as { rootDescendantCounts: Map<string, number> }).rootDescendantCounts
}
function getPreStartDescendantReservations(manager: BackgroundManager): Set<string> {
return (manager as unknown as { preStartDescendantReservations: Set<string> }).preStartDescendantReservations
}
function getQueuesByKey(
manager: BackgroundManager
): Map<string, Array<{ task: BackgroundTask; input: import("./types").LaunchInput }>> {
@@ -1144,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(
@@ -1178,7 +1193,7 @@ describe("BackgroundManager.notifyParentSession - notifications toggle", () => {
})
describe("BackgroundManager.notifyParentSession - variant propagation", () => {
test("should propagate variant in parent notification promptAsync body", async () => {
test("should prefer parent session variant over child task variant in parent notification promptAsync body", async () => {
//#given
const promptCalls: Array<{ body: Record<string, unknown> }> = []
const client = {
@@ -1189,16 +1204,27 @@ describe("BackgroundManager.notifyParentSession - variant propagation", () => {
return {}
},
abort: async () => ({}),
messages: async () => ({ data: [] }),
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-variant-test",
id: "task-parent-variant-wins",
sessionID: "session-child",
parentSessionID: "session-parent",
parentMessageID: "msg-parent",
description: "task with variant",
description: "task with mismatched variant",
prompt: "test",
agent: "explore",
status: "completed",
@@ -1214,7 +1240,7 @@ describe("BackgroundManager.notifyParentSession - variant propagation", () => {
//#then
expect(promptCalls).toHaveLength(1)
expect(promptCalls[0].body.variant).toBe("high")
expect(promptCalls[0].body.variant).toBe("max")
manager.shutdown()
})
@@ -1521,6 +1547,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
const task = createMockTask({
id: "task-zombie-session",
sessionID: "session-zombie-placeholder",
parentSessionID: "parent-zombie",
status: "pending",
agent: "explore",
@@ -1863,10 +1890,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,
},
})
})
@@ -2463,6 +2490,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<BackgroundTask>).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<void>
}).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"
+3 -6
View File
@@ -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
@@ -1783,6 +1779,7 @@ export class BackgroundManager {
let agent: string | undefined = task.parentAgent
let model: { providerID: string; modelID: string } | undefined
let tools: Record<string, boolean> | undefined = task.parentTools
let promptContext: ReturnType<typeof resolvePromptContextFromSessionMessages> = null
if (this.enableParentSessionNotifications) {
try {
@@ -1796,7 +1793,7 @@ export class BackgroundManager {
tools?: Record<string, boolean | "allow" | "deny" | "ask">
}
}>)
const promptContext = resolvePromptContextFromSessionMessages(
promptContext = resolvePromptContextFromSessionMessages(
messages,
task.parentSessionID,
)
@@ -1840,7 +1837,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({
@@ -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,
},
})
})
@@ -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() })
+1 -1
View File
@@ -20,12 +20,12 @@ export function applySessionPromptParams(
const promptOptions: Record<string, unknown> = {
...(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 } : {}),
})
}
@@ -18,9 +18,9 @@ describe("session-prompt-params-state", () => {
const params = {
temperature: 0.4,
topP: 0.7,
maxOutputTokens: 4096,
options: {
reasoningEffort: "high",
maxTokens: 4096,
},
}
@@ -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 () => {
+1 -1
View File
@@ -43,12 +43,12 @@ function buildPromptGenerationParams(model: DelegatedModelConfig | undefined): R
const promptOptions: Record<string, unknown> = {
...(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 } : {}),
}
}
@@ -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,
},
})
})
@@ -30,12 +30,12 @@ function buildPromptGenerationParams(model: DelegatedModelConfig | undefined): R
const promptOptions: Record<string, unknown> = {
...(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 } : {}),
}
}