refactor(background-agent): normalize task ID field naming

Rename BackgroundTask and attempt ID fields to camelCase across background-agent consumers while moving BackgroundManager construction to a single config object.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-05-02 03:01:03 +09:00
parent 054ade9ced
commit da251c9b30
57 changed files with 1299 additions and 1327 deletions
@@ -3,28 +3,28 @@ import type { BackgroundTask, BackgroundTaskAttempt, BackgroundTaskStatus } from
type TerminalAttemptStatus = Extract<BackgroundTaskStatus, "completed" | "error" | "cancelled" | "interrupt">
function toAttemptModel(model: DelegatedModelConfig | undefined): Pick<BackgroundTaskAttempt, "providerID" | "modelID" | "variant"> {
function toAttemptModel(model: DelegatedModelConfig | undefined): Pick<BackgroundTaskAttempt, "providerId" | "modelId" | "variant"> {
return {
providerID: model?.providerID,
modelID: model?.modelID,
providerId: model?.providerID,
modelId: model?.modelID,
variant: model?.variant,
}
}
function toTaskModel(attempt: BackgroundTaskAttempt): DelegatedModelConfig | undefined {
if (!attempt.providerID || !attempt.modelID) {
if (!attempt.providerId || !attempt.modelId) {
return undefined
}
return {
providerID: attempt.providerID,
modelID: attempt.modelID,
providerID: attempt.providerId,
modelID: attempt.modelId,
...(attempt.variant ? { variant: attempt.variant } : {}),
}
}
function getAttemptIndex(task: BackgroundTask, attemptID: string): number {
return task.attempts?.findIndex((attempt) => attempt.attemptID === attemptID) ?? -1
return task.attempts?.findIndex((attempt) => attempt.attemptId === attemptID) ?? -1
}
function getAttempt(task: BackgroundTask, attemptID: string): BackgroundTaskAttempt | undefined {
@@ -54,9 +54,9 @@ export function ensureCurrentAttempt(
}
const attempt: BackgroundTaskAttempt = {
attemptID: `att_${crypto.randomUUID().slice(0, 8)}`,
attemptId: `att_${crypto.randomUUID().slice(0, 8)}`,
attemptNumber: (task.attempts?.length ?? 0) + 1,
sessionID: task.sessionID,
sessionId: task.sessionId,
...toAttemptModel(model),
status: task.status,
error: task.error,
@@ -65,7 +65,7 @@ export function ensureCurrentAttempt(
}
task.attempts = [...(task.attempts ?? []), attempt]
task.currentAttemptID = attempt.attemptID
task.currentAttemptID = attempt.attemptId
return attempt
}
@@ -76,7 +76,7 @@ export function projectTaskFromCurrentAttempt(task: BackgroundTask): BackgroundT
}
task.status = currentAttempt.status
task.sessionID = currentAttempt.sessionID
task.sessionId = currentAttempt.sessionId
task.startedAt = currentAttempt.startedAt
task.completedAt = currentAttempt.completedAt
task.error = currentAttempt.error
@@ -87,16 +87,16 @@ export function projectTaskFromCurrentAttempt(task: BackgroundTask): BackgroundT
export function startAttempt(task: BackgroundTask, model: DelegatedModelConfig | undefined): BackgroundTaskAttempt {
const attempt: BackgroundTaskAttempt = {
attemptID: `att_${crypto.randomUUID().slice(0, 8)}`,
attemptId: `att_${crypto.randomUUID().slice(0, 8)}`,
attemptNumber: (task.attempts?.length ?? 0) + 1,
...toAttemptModel(model),
status: "pending",
}
task.attempts = [...(task.attempts ?? []), attempt]
task.currentAttemptID = attempt.attemptID
task.currentAttemptID = attempt.attemptId
task.status = "pending"
task.sessionID = undefined
task.sessionId = undefined
task.startedAt = undefined
task.completedAt = undefined
task.error = undefined
@@ -121,13 +121,13 @@ export function bindAttemptSession(
return undefined
}
attempt.sessionID = sessionID
attempt.sessionId = sessionID
attempt.status = "running"
attempt.startedAt = new Date()
attempt.completedAt = undefined
attempt.error = undefined
attempt.providerID = model?.providerID ?? attempt.providerID
attempt.modelID = model?.modelID ?? attempt.modelID
attempt.providerId = model?.providerID ?? attempt.providerId
attempt.modelId = model?.modelID ?? attempt.modelId
attempt.variant = model?.variant ?? attempt.variant
return getCurrentAttempt(projectTaskFromCurrentAttempt(task))
@@ -170,5 +170,5 @@ export function scheduleRetryAttempt(
}
export function findAttemptBySession(task: BackgroundTask, sessionID: string): BackgroundTaskAttempt | undefined {
return task.attempts?.find((attempt) => attempt.sessionID === sessionID)
return task.attempts?.find((attempt) => attempt.sessionId === sessionID)
}
@@ -164,20 +164,20 @@ Use \`background_output(task_id="<id>")\` to retrieve each result.
status: "completed",
attempts: [
{
attemptID: "att-1",
attemptId: "att-1",
attemptNumber: 1,
sessionID: "ses-primary",
providerID: "genai-proxy-openai",
modelID: "gpt-5.4-mini",
sessionId: "ses-primary",
providerId: "genai-proxy-openai",
modelId: "gpt-5.4-mini",
status: "error",
error: "Forbidden: Selected provider is forbidden",
},
{
attemptID: "att-2",
attemptId: "att-2",
attemptNumber: 2,
sessionID: "ses-fallback",
providerID: "anthropic",
modelID: "claude-haiku-4.5",
sessionId: "ses-fallback",
providerId: "anthropic",
modelId: "claude-haiku-4.5",
status: "completed",
},
],
@@ -193,20 +193,20 @@ Use \`background_output(task_id="<id>")\` to retrieve each result.
status: "completed",
attempts: [
{
attemptID: "att-1",
attemptId: "att-1",
attemptNumber: 1,
sessionID: "ses-primary",
providerID: "genai-proxy-openai",
modelID: "gpt-5.4-mini",
sessionId: "ses-primary",
providerId: "genai-proxy-openai",
modelId: "gpt-5.4-mini",
status: "error",
error: "Forbidden: Selected provider is forbidden",
},
{
attemptID: "att-2",
attemptId: "att-2",
attemptNumber: 2,
sessionID: "ses-fallback",
providerID: "anthropic",
modelID: "claude-haiku-4.5",
sessionId: "ses-fallback",
providerId: "anthropic",
modelId: "claude-haiku-4.5",
status: "completed",
},
],
@@ -11,16 +11,16 @@ export interface BackgroundTaskNotificationTask {
}
function formatAttemptModel(attempt: BackgroundTaskAttempt): string {
if (attempt.providerID && attempt.modelID) {
return `${attempt.providerID}/${attempt.modelID}`
if (attempt.providerId && attempt.modelId) {
return `${attempt.providerId}/${attempt.modelId}`
}
if (attempt.modelID) {
return attempt.modelID
if (attempt.modelId) {
return attempt.modelId
}
if (attempt.providerID) {
return attempt.providerID
if (attempt.providerId) {
return attempt.providerId
}
return "unknown-model"
@@ -34,7 +34,7 @@ function formatAttemptTimeline(task: BackgroundTaskNotificationTask): string {
const lines = task.attempts
.map((attempt) => {
const attemptLines = [
` - Attempt ${attempt.attemptNumber}${attempt.status.toUpperCase()}${formatAttemptModel(attempt)}${attempt.sessionID ?? "unknown"}`,
` - Attempt ${attempt.attemptNumber}${attempt.status.toUpperCase()}${formatAttemptModel(attempt)}${attempt.sessionId ?? "unknown"}`,
]
if (attempt.status !== "completed" && attempt.error) {
@@ -22,24 +22,24 @@ function createBackgroundManager(config?: { defaultConcurrency?: number }): Back
Reflect.set(client.session, "prompt", async () => ({ data: { info: {}, parts: [] } }))
Reflect.set(client.session, "promptAsync", async () => ({ data: undefined }))
const manager = new BackgroundManager({
const manager = new BackgroundManager({ pluginContext: {
$: {} as PluginInput["$"],
client,
directory,
project: {} as PluginInput["project"],
serverUrl: new URL("http://localhost"),
worktree: directory,
}, config)
}, config: config })
managersToShutdown.push(manager)
return manager
}
function createMockTask(overrides: Partial<BackgroundTask> & { id: string; parentSessionID: string }): BackgroundTask {
function createMockTask(overrides: Partial<BackgroundTask> & { id: string; parentSessionId: string }): BackgroundTask {
return {
id: overrides.id,
sessionID: overrides.sessionID,
parentSessionID: overrides.parentSessionID,
parentMessageID: overrides.parentMessageID ?? "parent-message-id",
sessionId: overrides.sessionId,
parentSessionId: overrides.parentSessionId,
parentMessageId: overrides.parentMessageId ?? "parent-message-id",
description: overrides.description ?? "test task",
prompt: overrides.prompt ?? "test prompt",
agent: overrides.agent ?? "test-agent",
@@ -90,12 +90,12 @@ describe("BackgroundManager.cancelTask cleanup", () => {
const manager = createBackgroundManager()
const task = createMockTask({
id: "task-skip-notification-cleanup",
parentSessionID: "parent-session-skip-notification-cleanup",
sessionID: "session-skip-notification-cleanup",
parentSessionId: "parent-session-skip-notification-cleanup",
sessionId: "session-skip-notification-cleanup",
})
getTaskMap(manager).set(task.id, task)
getPendingByParent(manager).set(task.parentSessionID, new Set([task.id]))
getPendingByParent(manager).set(task.parentSessionId, new Set([task.id]))
// when
const cancelled = await manager.cancelTask(task.id, {
@@ -105,7 +105,7 @@ describe("BackgroundManager.cancelTask cleanup", () => {
// then
expect(cancelled).toBe(true)
expect(getPendingByParent(manager).get(task.parentSessionID)).toBeUndefined()
expect(getPendingByParent(manager).get(task.parentSessionId)).toBeUndefined()
runScheduledCleanup(manager, task.id)
expect(manager.getTask(task.id)).toBeUndefined()
})
@@ -115,12 +115,12 @@ describe("BackgroundManager.cancelTask cleanup", () => {
const manager = createBackgroundManager()
const task = createMockTask({
id: "task-notify-cleanup",
parentSessionID: "parent-session-notify-cleanup",
sessionID: "session-notify-cleanup",
parentSessionId: "parent-session-notify-cleanup",
sessionId: "session-notify-cleanup",
})
getTaskMap(manager).set(task.id, task)
getPendingByParent(manager).set(task.parentSessionID, new Set([task.id]))
getPendingByParent(manager).set(task.parentSessionId, new Set([task.id]))
// when
const cancelled = await manager.cancelTask(task.id, {
@@ -143,13 +143,13 @@ describe("BackgroundManager.cancelTask cleanup", () => {
const runningTask = createMockTask({
id: "task-running-before-cancel",
parentSessionID: "parent-session-concurrency-cleanup",
sessionID: "session-running-before-cancel",
parentSessionId: "parent-session-concurrency-cleanup",
sessionId: "session-running-before-cancel",
concurrencyKey,
})
const pendingTask = createMockTask({
id: "task-pending-after-cancel",
parentSessionID: runningTask.parentSessionID,
parentSessionId: runningTask.parentSessionId,
status: "pending",
startedAt: undefined,
queuedAt: new Date(),
@@ -159,20 +159,20 @@ describe("BackgroundManager.cancelTask cleanup", () => {
agent: pendingTask.agent,
description: pendingTask.description,
model: pendingTask.model,
parentMessageID: pendingTask.parentMessageID,
parentSessionID: pendingTask.parentSessionID,
parentMessageId: pendingTask.parentMessageId,
parentSessionId: pendingTask.parentSessionId,
prompt: pendingTask.prompt,
}
getTaskMap(manager).set(runningTask.id, runningTask)
getTaskMap(manager).set(pendingTask.id, pendingTask)
getPendingByParent(manager).set(runningTask.parentSessionID, new Set([runningTask.id, pendingTask.id]))
getPendingByParent(manager).set(runningTask.parentSessionId, new Set([runningTask.id, pendingTask.id]))
getQueuesByKey(manager).set(concurrencyKey, [{ input: queuedInput, task: pendingTask }])
Reflect.set(manager, "startTask", async ({ task }: { task: BackgroundTask; input: LaunchInput }) => {
task.status = "running"
task.startedAt = new Date()
task.sessionID = "session-started-after-cancel"
task.sessionId = "session-started-after-cancel"
task.concurrencyKey = concurrencyKey
task.concurrencyGroup = concurrencyKey
})
@@ -8,9 +8,9 @@ import type { BackgroundTask } from "./types"
function createRunningTask(startedAt: Date): BackgroundTask {
return {
id: "task-1",
sessionID: "ses-1",
parentSessionID: "parent-ses-1",
parentMessageID: "msg-1",
sessionId: "ses-1",
parentSessionId: "parent-ses-1",
parentMessageId: "msg-1",
description: "test",
prompt: "test",
agent: "explore",
@@ -69,8 +69,8 @@ function createMockTask(overrides: Partial<BackgroundTask> = {}): BackgroundTask
prompt: "test prompt",
agent: "sisyphus-junior",
status: "error",
parentSessionID: "parent-session-1",
parentMessageID: "parent-message-1",
parentSessionId: "parent-session-1",
parentMessageId: "parent-message-1",
fallbackChain: [
{ model: "fallback-model-1", providers: ["provider-a"], variant: undefined },
{ model: "fallback-model-2", providers: ["provider-b"], variant: undefined },
@@ -174,13 +174,13 @@ describe("tryFallbackRetry", () => {
test("clears sessionID and startedAt", async () => {
const args = createDefaultArgs({
sessionID: "old-session",
sessionId: "old-session",
startedAt: new Date(),
})
await tryFallbackRetry(args)
expect(args.task.sessionID).toBeUndefined()
expect(args.task.sessionId).toBeUndefined()
expect(args.task.startedAt).toBeUndefined()
})
@@ -217,7 +217,7 @@ describe("tryFallbackRetry", () => {
})
test("aborts existing session", async () => {
const args = createDefaultArgs({ sessionID: "session-to-abort" })
const args = createDefaultArgs({ sessionId: "session-to-abort" })
await tryFallbackRetry(args)
@@ -227,7 +227,7 @@ describe("tryFallbackRetry", () => {
})
test("waits for session abort before resolving", async () => {
const args = createDefaultArgs({ sessionID: "session-to-abort" })
const args = createDefaultArgs({ sessionId: "session-to-abort" })
const deferred = createDeferredPromise()
args.abortMock.mockImplementationOnce(() => deferred.promise)
@@ -263,15 +263,15 @@ describe("tryFallbackRetry", () => {
test("finalizes the failed attempt, creates a new pending attempt, and enqueues its explicit attemptID", async () => {
const args = createDefaultArgs({
status: "running",
sessionID: "session-attempt-1",
sessionId: "session-attempt-1",
startedAt: new Date("2026-04-27T00:00:00.000Z"),
attempts: [
{
attemptID: "attempt-1",
attemptId: "attempt-1",
attemptNumber: 1,
sessionID: "session-attempt-1",
providerID: "provider-a",
modelID: "original-model",
sessionId: "session-attempt-1",
providerId: "provider-a",
modelId: "original-model",
status: "running",
startedAt: new Date("2026-04-27T00:00:00.000Z"),
},
@@ -283,8 +283,8 @@ describe("tryFallbackRetry", () => {
expect(args.task.attempts).toHaveLength(2)
expect(args.task.attempts?.[0]).toMatchObject({
attemptID: "attempt-1",
sessionID: "session-attempt-1",
attemptId: "attempt-1",
sessionId: "session-attempt-1",
status: "error",
error: "model overloaded",
})
@@ -293,11 +293,11 @@ describe("tryFallbackRetry", () => {
const nextAttempt = args.task.attempts?.[1]
expect(nextAttempt).toBeDefined()
expect(nextAttempt?.attemptNumber).toBe(2)
expect(nextAttempt?.providerID).toBe("provider-a")
expect(nextAttempt?.modelID).toBe("fallback-model-1")
expect(nextAttempt?.providerId).toBe("provider-a")
expect(nextAttempt?.modelId).toBe("fallback-model-1")
expect(nextAttempt?.status).toBe("pending")
expect(args.task.currentAttemptID).toBe(nextAttempt?.attemptID)
expect(args.task.currentAttemptID).toBe(nextAttempt?.attemptId)
expect(args.task.status).toBe("pending")
expect(args.task.model).toEqual({
providerID: "provider-a",
@@ -308,7 +308,7 @@ describe("tryFallbackRetry", () => {
const key = `${args.task.model!.providerID}/${args.task.model!.modelID}`
const queue = args.queuesByKey.get(key)
expect(queue).toBeDefined()
expect((queue?.[0] as QueueItem & { attemptID?: string })?.attemptID).toBe(nextAttempt?.attemptID)
expect((queue?.[0] as QueueItem & { attemptID?: string })?.attemptID).toBe(nextAttempt?.attemptId)
})
})
@@ -363,7 +363,7 @@ describe("tryFallbackRetry", () => {
describe("#given task without session", () => {
test("skips session abort", async () => {
const args = createDefaultArgs({ sessionID: undefined })
const args = createDefaultArgs({ sessionId: undefined })
await tryFallbackRetry(args)
@@ -124,7 +124,7 @@ export async function tryFallbackRetry(args: {
idleDeferralTimers.delete(task.id)
}
const previousSessionID = task.sessionID
const previousSessionID = task.sessionId
const previousModel = task.model
const transformedModelId = transformModelForProvider(providerID, nextFallback.model)
@@ -134,7 +134,7 @@ export async function tryFallbackRetry(args: {
variant: nextFallback.variant,
}
task.attemptCount = selectedAttemptCount
const failedAttemptID = ensureCurrentAttempt(task, previousModel).attemptID
const failedAttemptID = ensureCurrentAttempt(task, previousModel).attemptId
const nextAttempt = failedAttemptID
? scheduleRetryAttempt(task, failedAttemptID, nextModel, errorInfo.message)
: undefined
@@ -165,8 +165,8 @@ export async function tryFallbackRetry(args: {
description: task.description,
prompt: task.prompt,
agent: task.agent,
parentSessionID: task.parentSessionID,
parentMessageID: task.parentMessageID,
parentSessionId: task.parentSessionId,
parentMessageId: task.parentMessageId,
parentModel: task.parentModel,
parentAgent: task.parentAgent,
parentTools: task.parentTools,
@@ -180,7 +180,7 @@ export async function tryFallbackRetry(args: {
await abortWithTimeout(client, previousSessionID).catch(() => {})
}
queue.push({ task, input: retryInput, attemptID: nextAttempt.attemptID })
queue.push({ task, input: retryInput, attemptID: nextAttempt.attemptId })
queuesByKey.set(key, queue)
processKey(key)
return true
@@ -16,14 +16,14 @@ function createManager(config?: BackgroundTaskConfig): BackgroundManager {
},
}
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput, config)
const manager = new BackgroundManager({ pluginContext: { client, directory: tmpdir() } as unknown as PluginInput, config: config })
const testManager = manager as unknown as {
enqueueNotificationForParent: (sessionID: string, fn: () => Promise<void>) => Promise<void>
enqueueNotificationForParent: (sessionId: string, fn: () => Promise<void>) => Promise<void>
notifyParentSession: (task: BackgroundTask) => Promise<void>
tasks: Map<string, BackgroundTask>
}
testManager.enqueueNotificationForParent = async (_sessionID, fn) => {
testManager.enqueueNotificationForParent = async (_sessionId: sessionID, fn) => {
await fn()
}
testManager.notifyParentSession = async () => {}
@@ -49,9 +49,9 @@ describe("BackgroundManager circuit breaker", () => {
})
const task: BackgroundTask = {
id: "task-loop-1",
sessionID: "session-loop-1",
parentSessionID: "parent-1",
parentMessageID: "msg-1",
sessionId: "session-loop-1",
parentSessionId: "parent-1",
parentMessageId: "msg-1",
description: "Looping task",
prompt: "loop",
agent: "explore",
@@ -67,7 +67,7 @@ describe("BackgroundManager circuit breaker", () => {
for (let i = 0; i < 20; i++) {
manager.handleEvent({
type: "message.part.updated",
properties: { sessionID: task.sessionID, type: "tool", tool: "read" },
properties: { sessionID: task.sessionId, type: "tool", tool: "read" },
})
}
@@ -87,9 +87,9 @@ describe("BackgroundManager circuit breaker", () => {
})
const task: BackgroundTask = {
id: "task-diverse-1",
sessionID: "session-diverse-1",
parentSessionID: "parent-1",
parentMessageID: "msg-1",
sessionId: "session-diverse-1",
parentSessionId: "parent-1",
parentMessageId: "msg-1",
description: "Healthy task",
prompt: "work",
agent: "explore",
@@ -116,7 +116,7 @@ describe("BackgroundManager circuit breaker", () => {
]) {
manager.handleEvent({
type: "message.part.updated",
properties: { sessionID: task.sessionID, type: "tool", tool: toolName },
properties: { sessionID: task.sessionId, type: "tool", tool: toolName },
})
}
@@ -137,9 +137,9 @@ describe("BackgroundManager circuit breaker", () => {
})
const task: BackgroundTask = {
id: "task-cap-1",
sessionID: "session-cap-1",
parentSessionID: "parent-1",
parentMessageID: "msg-1",
sessionId: "session-cap-1",
parentSessionId: "parent-1",
parentMessageId: "msg-1",
description: "Backstop task",
prompt: "work",
agent: "explore",
@@ -155,7 +155,7 @@ describe("BackgroundManager circuit breaker", () => {
for (let i = 0; i < 3; i++) {
manager.handleEvent({
type: "message.part.updated",
properties: { sessionID: task.sessionID, type: "tool", tool: "read" },
properties: { sessionID: task.sessionId, type: "tool", tool: "read" },
})
}
@@ -176,9 +176,9 @@ describe("BackgroundManager circuit breaker", () => {
})
const task: BackgroundTask = {
id: "task-dedupe-1",
sessionID: "session-dedupe-1",
parentSessionID: "parent-1",
parentMessageID: "msg-1",
sessionId: "session-dedupe-1",
parentSessionId: "parent-1",
parentMessageId: "msg-1",
description: "Dedupe task",
prompt: "work",
agent: "explore",
@@ -197,7 +197,7 @@ describe("BackgroundManager circuit breaker", () => {
properties: {
part: {
id: "tool-1",
sessionID: task.sessionID,
sessionID: task.sessionId,
type: "tool",
tool: "bash",
state: { status: "running" },
@@ -223,9 +223,9 @@ describe("BackgroundManager circuit breaker", () => {
})
const task: BackgroundTask = {
id: "task-diff-files-1",
sessionID: "session-diff-files-1",
parentSessionID: "parent-1",
parentMessageID: "msg-1",
sessionId: "session-diff-files-1",
parentSessionId: "parent-1",
parentMessageId: "msg-1",
description: "Reading different files",
prompt: "work",
agent: "explore",
@@ -243,7 +243,7 @@ describe("BackgroundManager circuit breaker", () => {
type: "message.part.updated",
properties: {
part: {
sessionID: task.sessionID,
sessionID: task.sessionId,
type: "tool",
tool: "read",
state: { status: "running", input: { filePath: `/src/file-${i}.ts` } },
@@ -268,9 +268,9 @@ describe("BackgroundManager circuit breaker", () => {
})
const task: BackgroundTask = {
id: "task-same-file-1",
sessionID: "session-same-file-1",
parentSessionID: "parent-1",
parentMessageID: "msg-1",
sessionId: "session-same-file-1",
parentSessionId: "parent-1",
parentMessageId: "msg-1",
description: "Reading same file repeatedly",
prompt: "work",
agent: "explore",
@@ -288,7 +288,7 @@ describe("BackgroundManager circuit breaker", () => {
type: "message.part.updated",
properties: {
part: {
sessionID: task.sessionID,
sessionID: task.sessionId,
type: "tool",
tool: "read",
state: { status: "running", input: { filePath: "/src/same.ts" } },
@@ -315,9 +315,9 @@ describe("BackgroundManager circuit breaker", () => {
})
const task: BackgroundTask = {
id: "task-disabled-1",
sessionID: "session-disabled-1",
parentSessionID: "parent-1",
parentMessageID: "msg-1",
sessionId: "session-disabled-1",
parentSessionId: "parent-1",
parentMessageId: "msg-1",
description: "Disabled circuit breaker task",
prompt: "work",
agent: "explore",
@@ -334,7 +334,7 @@ describe("BackgroundManager circuit breaker", () => {
manager.handleEvent({
type: "message.part.updated",
properties: {
sessionID: task.sessionID,
sessionID: task.sessionId,
type: "tool",
tool: "read",
},
@@ -358,9 +358,9 @@ describe("BackgroundManager circuit breaker", () => {
})
const task: BackgroundTask = {
id: "task-cap-disabled-1",
sessionID: "session-cap-disabled-1",
parentSessionID: "parent-1",
parentMessageID: "msg-1",
sessionId: "session-cap-disabled-1",
parentSessionId: "parent-1",
parentMessageId: "msg-1",
description: "Backstop task with disabled circuit breaker",
prompt: "work",
agent: "explore",
@@ -376,7 +376,7 @@ describe("BackgroundManager circuit breaker", () => {
for (const toolName of ["read", "grep", "edit"]) {
manager.handleEvent({
type: "message.part.updated",
properties: { sessionID: task.sessionID, type: "tool", tool: toolName },
properties: { sessionID: task.sessionId, type: "tool", tool: toolName },
})
}
@@ -21,15 +21,15 @@ describe("BackgroundManager session permission", () => {
},
}
const directory = tmpdir()
const manager = new BackgroundManager({ client, directory } as unknown as PluginInput)
const manager = new BackgroundManager({ pluginContext: { client, directory } as unknown as PluginInput })
// when
await manager.launch({
description: "Test task",
prompt: "Do something",
agent: "explore",
parentSessionID: "ses_parent",
parentMessageID: "msg_parent",
parentSessionId: "ses_parent",
parentMessageId: "msg_parent",
})
await new Promise((resolve) => setTimeout(resolve, 50))
manager.shutdown()
@@ -62,15 +62,15 @@ describe("BackgroundManager session permission", () => {
abort: async () => ({}),
},
}
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput)
const manager = new BackgroundManager({ pluginContext: { client, directory: tmpdir() } as unknown as PluginInput })
// when
await manager.launch({
description: "Test task",
prompt: "Do something",
agent: "explore",
parentSessionID: "ses_parent",
parentMessageID: "msg_parent",
parentSessionId: "ses_parent",
parentMessageId: "msg_parent",
sessionPermission: [
{ permission: "question", action: "deny", pattern: "*" },
],
@@ -20,10 +20,10 @@ function createDeferredPromise(): {
}
}
function createTask(overrides: Partial<BackgroundTask> & { id: string; sessionID: string }): BackgroundTask {
function createTask(overrides: Partial<BackgroundTask> & { id: string; sessionId: string }): BackgroundTask {
return {
parentSessionID: "parent-session",
parentMessageID: "parent-message",
parentSessionId: "parent-session",
parentMessageId: "parent-message",
description: "test task",
prompt: "test prompt",
agent: "explore",
@@ -34,7 +34,7 @@ function createTask(overrides: Partial<BackgroundTask> & { id: string; sessionID
}
function createBackgroundManager(): BackgroundManager {
return new BackgroundManager({
return new BackgroundManager({ pluginContext: {
client: {
session: {
abort: async () => ({}),
@@ -47,7 +47,7 @@ function createBackgroundManager(): BackgroundManager {
worktree: tmpdir(),
serverUrl: new URL("https://example.com"),
$: {} as never,
} as never)
} as never })
}
describe("BackgroundManager shutdown global cleanup", () => {
@@ -74,14 +74,14 @@ describe("BackgroundManager shutdown global cleanup", () => {
"task-running-shutdown-cleanup",
createTask({
id: "task-running-shutdown-cleanup",
sessionID: runningSessionID,
sessionId: runningSessionID,
}),
],
[
"task-completed-shutdown-cleanup",
createTask({
id: "task-completed-shutdown-cleanup",
sessionID: completedSessionID,
sessionId: completedSessionID,
status: "completed",
completedAt: new Date(),
}),
@@ -119,7 +119,7 @@ describe("BackgroundManager shutdown global cleanup", () => {
"task-running-await-shutdown",
createTask({
id: "task-running-await-shutdown",
sessionID: runningSessionID,
sessionId: runningSessionID,
}),
],
])
@@ -18,7 +18,7 @@ function createManagerWithStatus(statusImpl: () => Promise<{ data: Record<string
},
}
return new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput)
return new BackgroundManager({ pluginContext: { client, directory: tmpdir() } as unknown as PluginInput })
}
describe("BackgroundManager polling overlap", () => {
@@ -56,12 +56,12 @@ describe("BackgroundManager polling overlap", () => {
})
function createRunningTask(sessionID: string): BackgroundTask {
function createRunningTask(sessionId: string): BackgroundTask {
return {
id: `bg_test_${sessionID}`,
sessionID,
parentSessionID: "parent-session",
parentMessageID: "parent-msg",
id: `bg_test_${sessionId}`,
sessionId,
parentSessionId: "parent-session",
parentMessageId: "parent-msg",
description: "test task",
prompt: "test",
agent: "explore",
@@ -98,9 +98,7 @@ function createManagerWithClient(clientOverrides: Record<string, unknown> = {}):
},
}
return new BackgroundManager(
{ client, directory: tmpdir() } as unknown as PluginInput,
undefined,
{ enableParentSessionNotifications: false },
{ pluginContext: { client, directory: tmpdir() } as unknown as PluginInput, config: undefined, enableParentSessionNotifications: false },
)
}
File diff suppressed because it is too large Load Diff
+182 -181
View File
@@ -129,12 +129,12 @@ interface Todo {
id: string
}
function formatAttemptModelSummary(attempt: Pick<BackgroundTaskAttempt, "providerID" | "modelID"> | undefined): string | undefined {
if (!attempt?.providerID || !attempt.modelID) {
function formatAttemptModelSummary(attempt: Pick<BackgroundTaskAttempt, "providerId" | "modelId"> | undefined): string | undefined {
if (!attempt?.providerId || !attempt.modelId) {
return undefined
}
return `${attempt.providerID}/${attempt.modelID}`
return `${attempt.providerId}/${attempt.modelId}`
}
function getPreviousAttempt(task: BackgroundTask, attemptID: string | undefined): BackgroundTaskAttempt | undefined {
@@ -142,7 +142,7 @@ function getPreviousAttempt(task: BackgroundTask, attemptID: string | undefined)
return undefined
}
const attemptIndex = task.attempts.findIndex((attempt) => attempt.attemptID === attemptID)
const attemptIndex = task.attempts.findIndex((attempt) => attempt.attemptId === attemptID)
if (attemptIndex <= 0) {
return undefined
}
@@ -173,6 +173,16 @@ export type OnSubagentSessionCreated = (event: SubagentSessionCreatedEvent) => P
const MAX_TASK_REMOVAL_RESCHEDULES = 6
export interface BackgroundManagerConfig {
pluginContext: PluginInput
config?: BackgroundTaskConfig
tmuxConfig?: TmuxConfig
onSubagentSessionCreated?: OnSubagentSessionCreated
onShutdown?: () => void | Promise<void>
enableParentSessionNotifications?: boolean
modelFallbackControllerAccessor?: ModelFallbackControllerAccessor
}
export class BackgroundManager {
@@ -207,26 +217,17 @@ export class BackgroundManager {
readonly taskHistory = new TaskHistory()
private cachedCircuitBreakerSettings?: CircuitBreakerSettings
constructor(
ctx: PluginInput,
config?: BackgroundTaskConfig,
options?: {
tmuxConfig?: TmuxConfig
onSubagentSessionCreated?: OnSubagentSessionCreated
onShutdown?: () => void | Promise<void>
enableParentSessionNotifications?: boolean
modelFallbackControllerAccessor?: ModelFallbackControllerAccessor
}
) {
constructor(config: BackgroundManagerConfig) {
const { pluginContext, ...options } = config
this.tasks = new Map()
this.tasksByParentSession = new Map()
this.notifications = new Map()
this.pendingNotifications = new Map()
this.pendingByParent = new Map()
this.client = ctx.client
this.directory = ctx.directory
this.concurrencyManager = new ConcurrencyManager(config)
this.config = config
this.client = pluginContext.client
this.directory = pluginContext.directory
this.concurrencyManager = new ConcurrencyManager(options.config)
this.config = options.config
this.tmuxEnabled = options?.tmuxConfig?.enabled ?? false
this.onSubagentSessionCreated = options?.onSubagentSessionCreated
this.onShutdown = options?.onShutdown
@@ -317,36 +318,36 @@ export class BackgroundManager {
return
}
if (!task.rootSessionID) {
if (!task.rootSessionId) {
return
}
this.unregisterRootDescendant(task.rootSessionID)
this.unregisterRootDescendant(task.rootSessionId)
}
private addTask(task: BackgroundTask): void {
this.tasks.set(task.id, task)
if (!task.parentSessionID) {
if (!task.parentSessionId) {
return
}
const taskIDs = this.tasksByParentSession.get(task.parentSessionID) ?? new Set<string>()
const taskIDs = this.tasksByParentSession.get(task.parentSessionId) ?? new Set<string>()
taskIDs.add(task.id)
this.tasksByParentSession.set(task.parentSessionID, taskIDs)
this.tasksByParentSession.set(task.parentSessionId, taskIDs)
}
private removeTask(task: BackgroundTask): void {
this.tasks.delete(task.id)
this.removeTaskFromParentIndex(task.id, task.parentSessionID)
this.removeTaskFromParentIndex(task.id, task.parentSessionId)
}
private updateTaskParent(task: BackgroundTask, parentSessionID: string): void {
if (task.parentSessionID === parentSessionID) {
if (task.parentSessionId === parentSessionID) {
return
}
this.removeTaskFromParentIndex(task.id, task.parentSessionID)
task.parentSessionID = parentSessionID
this.removeTaskFromParentIndex(task.id, task.parentSessionId)
task.parentSessionId = parentSessionID
const taskIDs = this.tasksByParentSession.get(parentSessionID) ?? new Set<string>()
taskIDs.add(task.id)
this.tasksByParentSession.set(parentSessionID, taskIDs)
@@ -373,18 +374,18 @@ export class BackgroundManager {
agent: input.agent,
model: input.model,
description: input.description,
parentSessionID: input.parentSessionID,
parentSessionID: input.parentSessionId,
})
if (!input.agent || input.agent.trim() === "") {
throw new Error("Agent parameter is required")
}
const spawnReservation = await this.reserveSubagentSpawn(input.parentSessionID)
const spawnReservation = await this.reserveSubagentSpawn(input.parentSessionId)
try {
log("[background-agent] spawn guard passed", {
parentSessionID: input.parentSessionID,
parentSessionID: input.parentSessionId,
rootSessionID: spawnReservation.spawnContext.rootSessionID,
childDepth: spawnReservation.spawnContext.childDepth,
descendantCount: spawnReservation.descendantCount,
@@ -395,15 +396,15 @@ export class BackgroundManager {
id: `bg_${crypto.randomUUID().slice(0, 8)}`,
status: "pending",
queuedAt: new Date(),
rootSessionID: spawnReservation.spawnContext.rootSessionID,
rootSessionId: spawnReservation.spawnContext.rootSessionID,
// Do NOT set startedAt - will be set when running
// Do NOT set sessionID - will be set when running
description: input.description,
prompt: input.prompt,
agent: input.agent,
spawnDepth: spawnReservation.spawnContext.childDepth,
parentSessionID: input.parentSessionID,
parentMessageID: input.parentMessageID,
parentSessionId: input.parentSessionId,
parentMessageId: input.parentMessageId,
parentModel: input.parentModel,
parentAgent: input.parentAgent,
parentTools: input.parentTools,
@@ -415,19 +416,19 @@ export class BackgroundManager {
const firstAttempt = startAttempt(task, input.model)
this.addTask(task)
this.taskHistory.record(input.parentSessionID, { id: task.id, agent: input.agent, description: input.description, status: "pending", category: input.category })
this.taskHistory.record(input.parentSessionId, { id: task.id, agent: input.agent, description: input.description, status: "pending", category: input.category })
// Track for batched notifications immediately (pending state)
if (input.parentSessionID) {
const pending = this.pendingByParent.get(input.parentSessionID) ?? new Set()
if (input.parentSessionId) {
const pending = this.pendingByParent.get(input.parentSessionId) ?? new Set()
pending.add(task.id)
this.pendingByParent.set(input.parentSessionID, pending)
this.pendingByParent.set(input.parentSessionId, pending)
}
// Add to queue
const key = this.getConcurrencyKeyFromInput(input)
const queue = this.queuesByKey.get(key) ?? []
queue.push({ task, input, attemptID: firstAttempt.attemptID })
queue.push({ task, input, attemptID: firstAttempt.attemptId })
this.queuesByKey.set(key, queue)
log("[background-agent] Task queued:", { taskId: task.id, key, queueLength: queue.length })
@@ -506,12 +507,12 @@ export class BackgroundManager {
removeTaskToastTracking(item.task.id)
// Abort the orphaned session if one was created before the error
if (item.task.sessionID) {
await this.abortSessionWithLogging(item.task.sessionID, "startTask error cleanup")
if (item.task.sessionId) {
await this.abortSessionWithLogging(item.task.sessionId, "startTask error cleanup")
}
this.markForNotification(item.task)
this.enqueueNotificationForParent(item.task.parentSessionID, () => this.notifyParentSession(item.task)).catch(err => {
this.enqueueNotificationForParent(item.task.parentSessionId, () => this.notifyParentSession(item.task)).catch(err => {
log("[background-agent] Failed to notify on startTask error:", err)
})
}
@@ -523,7 +524,7 @@ export class BackgroundManager {
private async startTask(item: QueueItem): Promise<void> {
const { task, input } = item
const attemptID = item.attemptID ?? ensureCurrentAttempt(task, input.model).attemptID
const attemptID = item.attemptID ?? ensureCurrentAttempt(task, input.model).attemptId
log("[background-agent] Starting task:", {
taskId: task.id,
@@ -534,7 +535,7 @@ export class BackgroundManager {
const concurrencyKey = this.getConcurrencyKeyFromInput(input)
const parentSession = await this.client.session.get({
path: { id: input.parentSessionID },
path: { id: input.parentSessionId },
query: { directory: this.directory },
}).catch((err) => {
log(`[background-agent] Failed to get parent session: ${err}`)
@@ -545,7 +546,7 @@ export class BackgroundManager {
const createResult = await this.client.session.create({
body: {
parentID: input.parentSessionID,
parentID: input.parentSessionId,
title: `${input.description} (@${input.agent} subagent)`,
...(input.sessionPermission ? { permission: input.sessionPermission } : {}),
} as Record<string, unknown>,
@@ -578,14 +579,14 @@ export class BackgroundManager {
tmuxEnabled: this.tmuxEnabled,
isInsideTmux: isInsideTmux(),
sessionID,
parentID: input.parentSessionID,
parentID: input.parentSessionId,
})
if (this.onSubagentSessionCreated && this.tmuxEnabled && isInsideTmux()) {
log("[background-agent] Invoking tmux callback NOW", { sessionID })
await this.onSubagentSessionCreated({
sessionID,
parentID: input.parentSessionID,
parentID: input.parentSessionId,
title: input.description,
}).catch((err) => {
log("[background-agent] Failed to spawn tmux pane:", err)
@@ -599,8 +600,8 @@ export class BackgroundManager {
if (this.tasks.get(task.id)?.status === "cancelled") {
await this.abortSessionWithLogging(sessionID, "cancelled during tmux setup")
subagentSessions.delete(sessionID)
if (task.rootSessionID) {
this.unregisterRootDescendant(task.rootSessionID)
if (task.rootSessionId) {
this.unregisterRootDescendant(task.rootSessionId)
}
this.concurrencyManager.release(concurrencyKey)
return
@@ -610,8 +611,8 @@ export class BackgroundManager {
if (!boundAttempt) {
await this.abortSessionWithLogging(sessionID, "stale attempt binding cleanup")
subagentSessions.delete(sessionID)
if (task.rootSessionID) {
this.unregisterRootDescendant(task.rootSessionID)
if (task.rootSessionId) {
this.unregisterRootDescendant(task.rootSessionId)
}
this.concurrencyManager.release(concurrencyKey)
return
@@ -627,8 +628,8 @@ export class BackgroundManager {
if (task.retryNotification) {
const attemptNumber = boundAttempt.attemptNumber
const retrySessionUrl = buildLocalSessionUrl(parentDirectory, sessionID)
const previousAttempt = getPreviousAttempt(task, boundAttempt.attemptID)
const failedSessionID = previousAttempt?.sessionID ?? task.retryNotification.previousSessionID
const previousAttempt = getPreviousAttempt(task, boundAttempt.attemptId)
const failedSessionID = previousAttempt?.sessionId ?? task.retryNotification.previousSessionID
const failedSessionLine = failedSessionID
? `\n- Failed session: \`${failedSessionID}\``
: ""
@@ -642,7 +643,7 @@ export class BackgroundManager {
: ""
const retryModel = formatAttemptModelSummary(boundAttempt) ?? task.retryNotification.nextModel
this.queuePendingNotification(
task.parentSessionID,
task.parentSessionId,
`<system-reminder>
[BACKGROUND TASK RETRY SESSION READY]
**ID:** \`${task.id}\`
@@ -657,7 +658,7 @@ The fallback retry session is now created and can be inspected directly.
task.retryNotification = undefined
}
this.taskHistory.record(input.parentSessionID, { id: task.id, sessionID, agent: input.agent, description: input.description, status: "running", category: input.category, startedAt: task.startedAt })
this.taskHistory.record(input.parentSessionId, { id: task.id, sessionID, agent: input.agent, description: input.description, status: "running", category: input.category, startedAt: task.startedAt })
this.startPolling()
log("[background-agent] Launching task:", { taskId: task.id, sessionID, agent: input.agent })
@@ -764,8 +765,8 @@ The fallback retry session is now created and can be inspected directly.
existingTask.error = terminalError
existingTask.completedAt = new Date()
}
if (existingTask.rootSessionID) {
this.unregisterRootDescendant(existingTask.rootSessionID)
if (existingTask.rootSessionId) {
this.unregisterRootDescendant(existingTask.rootSessionId)
}
if (existingTask.concurrencyKey) {
this.concurrencyManager.release(existingTask.concurrencyKey)
@@ -779,7 +780,7 @@ The fallback retry session is now created and can be inspected directly.
await this.abortSessionWithLogging(sessionID, "launch error cleanup")
this.markForNotification(existingTask)
this.enqueueNotificationForParent(existingTask.parentSessionID, () => this.notifyParentSession(existingTask)).catch(err => {
this.enqueueNotificationForParent(existingTask.parentSessionId, () => this.notifyParentSession(existingTask)).catch(err => {
log("[background-agent] Failed to notify on error:", err)
})
}
@@ -795,7 +796,7 @@ The fallback retry session is now created and can be inspected directly.
if (!taskIDs) {
const result: BackgroundTask[] = []
for (const task of this.tasks.values()) {
if (task.parentSessionID === sessionID) {
if (task.parentSessionId === sessionID) {
result.push(task)
}
}
@@ -818,8 +819,8 @@ The fallback retry session is now created and can be inspected directly.
for (const child of directChildren) {
result.push(child)
if (child.sessionID) {
const descendants = this.getAllDescendantTasks(child.sessionID)
if (child.sessionId) {
const descendants = this.getAllDescendantTasks(child.sessionId)
result.push(...descendants)
}
}
@@ -829,7 +830,7 @@ The fallback retry session is now created and can be inspected directly.
findBySession(sessionID: string): BackgroundTask | undefined {
for (const task of this.tasks.values()) {
if (task.sessionID === sessionID) {
if (task.sessionId === sessionID) {
return task
}
if (findAttemptBySession(task, sessionID)) {
@@ -850,14 +851,14 @@ The fallback retry session is now created and can be inspected directly.
return {
task,
attemptID: undefined,
isCurrent: task.sessionID === sessionID,
isCurrent: task.sessionId === sessionID,
}
}
return {
task,
attemptID: attempt.attemptID,
isCurrent: task.currentAttemptID === attempt.attemptID,
attemptID: attempt.attemptId,
isCurrent: task.currentAttemptID === attempt.attemptId,
}
}
@@ -874,8 +875,8 @@ The fallback retry session is now created and can be inspected directly.
*/
async trackTask(input: {
taskId: string
sessionID: string
parentSessionID: string
sessionId: string
parentSessionId: string
description: string
agent?: string
parentAgent?: string
@@ -885,10 +886,10 @@ The fallback retry session is now created and can be inspected directly.
if (existingTask) {
// P2 fix: Clean up old parent's pending set BEFORE changing parent
// Otherwise cleanupPendingByParent would use the new parent ID
const parentChanged = input.parentSessionID !== existingTask.parentSessionID
const parentChanged = input.parentSessionId !== existingTask.parentSessionId
if (parentChanged) {
this.cleanupPendingByParent(existingTask) // Clean from OLD parent
this.updateTaskParent(existingTask, input.parentSessionID)
this.updateTaskParent(existingTask, input.parentSessionId)
}
if (input.parentAgent !== undefined) {
existingTask.parentAgent = input.parentAgent
@@ -897,22 +898,22 @@ The fallback retry session is now created and can be inspected directly.
existingTask.concurrencyGroup = input.concurrencyKey ?? existingTask.agent
}
if (existingTask.sessionID) {
subagentSessions.add(existingTask.sessionID)
if (existingTask.sessionId) {
subagentSessions.add(existingTask.sessionId)
}
this.startPolling()
// Track for batched notifications if task is pending or running
if (existingTask.status === "pending" || existingTask.status === "running") {
const pending = this.pendingByParent.get(input.parentSessionID) ?? new Set()
const pending = this.pendingByParent.get(input.parentSessionId) ?? new Set()
pending.add(existingTask.id)
this.pendingByParent.set(input.parentSessionID, pending)
this.pendingByParent.set(input.parentSessionId, pending)
} else if (!parentChanged) {
// Only clean up if parent didn't change (already cleaned above if it did)
this.cleanupPendingByParent(existingTask)
}
log("[background-agent] External task already registered:", { taskId: existingTask.id, sessionID: existingTask.sessionID, status: existingTask.status })
log("[background-agent] External task already registered:", { taskId: existingTask.id, sessionID: existingTask.sessionId, status: existingTask.status })
return existingTask
}
@@ -926,9 +927,9 @@ The fallback retry session is now created and can be inspected directly.
const task: BackgroundTask = {
id: input.taskId,
sessionID: input.sessionID,
parentSessionID: input.parentSessionID,
parentMessageID: "",
sessionId: input.sessionId,
parentSessionId: input.parentSessionId,
parentMessageId: "",
description: input.description,
prompt: "",
agent: input.agent || "task",
@@ -944,17 +945,17 @@ The fallback retry session is now created and can be inspected directly.
}
this.addTask(task)
subagentSessions.add(input.sessionID)
subagentSessions.add(input.sessionId)
this.startPolling()
this.taskHistory.record(input.parentSessionID, { id: task.id, sessionID: input.sessionID, agent: input.agent || "task", description: input.description, status: "running", startedAt: task.startedAt })
this.taskHistory.record(input.parentSessionId, { id: task.id, sessionID: input.sessionId, agent: input.agent || "task", description: input.description, status: "running", startedAt: task.startedAt })
if (input.parentSessionID) {
const pending = this.pendingByParent.get(input.parentSessionID) ?? new Set()
if (input.parentSessionId) {
const pending = this.pendingByParent.get(input.parentSessionId) ?? new Set()
pending.add(task.id)
this.pendingByParent.set(input.parentSessionID, pending)
this.pendingByParent.set(input.parentSessionId, pending)
}
log("[background-agent] Registered external task:", { taskId: task.id, sessionID: input.sessionID })
log("[background-agent] Registered external task:", { taskId: task.id, sessionID: input.sessionId })
return task
}
@@ -965,14 +966,14 @@ The fallback retry session is now created and can be inspected directly.
throw new Error(`Task not found for session: ${input.sessionId}`)
}
if (!existingTask.sessionID) {
if (!existingTask.sessionId) {
throw new Error(`Task has no sessionID: ${existingTask.id}`)
}
if (existingTask.status === "running") {
log("[background-agent] Resume skipped - task already running:", {
taskId: existingTask.id,
sessionID: existingTask.sessionID,
sessionID: existingTask.sessionId,
})
return existingTask
}
@@ -993,8 +994,8 @@ The fallback retry session is now created and can be inspected directly.
existingTask.status = "running"
existingTask.completedAt = undefined
existingTask.error = undefined
this.updateTaskParent(existingTask, input.parentSessionID)
existingTask.parentMessageID = input.parentMessageID
this.updateTaskParent(existingTask, input.parentSessionId)
existingTask.parentMessageId = input.parentMessageId
existingTask.parentModel = input.parentModel
existingTask.parentAgent = input.parentAgent
if (input.parentTools) {
@@ -1012,14 +1013,14 @@ The fallback retry session is now created and can be inspected directly.
}
this.startPolling()
if (existingTask.sessionID) {
subagentSessions.add(existingTask.sessionID)
if (existingTask.sessionId) {
subagentSessions.add(existingTask.sessionId)
}
if (input.parentSessionID) {
const pending = this.pendingByParent.get(input.parentSessionID) ?? new Set()
if (input.parentSessionId) {
const pending = this.pendingByParent.get(input.parentSessionId) ?? new Set()
pending.add(existingTask.id)
this.pendingByParent.set(input.parentSessionID, pending)
this.pendingByParent.set(input.parentSessionId, pending)
}
const toastManager = getTaskToastManager()
@@ -1032,10 +1033,10 @@ The fallback retry session is now created and can be inspected directly.
})
}
log("[background-agent] Resuming task:", { taskId: existingTask.id, sessionID: existingTask.sessionID })
log("[background-agent] Resuming task:", { taskId: existingTask.id, sessionID: existingTask.sessionId })
log("[background-agent] Resuming task - calling prompt (fire-and-forget) with:", {
sessionID: existingTask.sessionID,
sessionID: existingTask.sessionId,
agent: existingTask.agent,
model: existingTask.model,
promptLength: input.prompt.length,
@@ -1052,11 +1053,11 @@ The fallback retry session is now created and can be inspected directly.
const resumeVariant = existingTask.model?.variant
if (existingTask.model) {
applySessionPromptParams(existingTask.sessionID!, existingTask.model)
applySessionPromptParams(existingTask.sessionId!, existingTask.model)
}
this.client.session.promptAsync({
path: { id: existingTask.sessionID },
path: { id: existingTask.sessionId },
body: {
agent: existingTask.agent,
...(resumeModel ? { model: resumeModel } : {}),
@@ -1068,7 +1069,7 @@ The fallback retry session is now created and can be inspected directly.
question: false,
...getAgentToolRestrictions(existingTask.agent),
}
setSessionTools(existingTask.sessionID!, tools)
setSessionTools(existingTask.sessionId!, tools)
return tools
})(),
parts: [createInternalAgentTextPart(input.prompt)],
@@ -1087,8 +1088,8 @@ The fallback retry session is now created and can be inspected directly.
const errorMessage = errorInfo.message ?? (error instanceof Error ? error.message : String(error))
existingTask.error = errorMessage
existingTask.completedAt = new Date()
if (existingTask.rootSessionID) {
this.unregisterRootDescendant(existingTask.rootSessionID)
if (existingTask.rootSessionId) {
this.unregisterRootDescendant(existingTask.rootSessionId)
}
// Release concurrency on error to prevent slot leaks
@@ -1101,12 +1102,12 @@ The fallback retry session is now created and can be inspected directly.
// Abort the session to prevent infinite polling hang
// Awaited to prevent dangling promise during subagent teardown (Bun/WebKit SIGABRT)
if (existingTask.sessionID) {
await this.abortSessionWithLogging(existingTask.sessionID, "resume error cleanup")
if (existingTask.sessionId) {
await this.abortSessionWithLogging(existingTask.sessionId, "resume error cleanup")
}
this.markForNotification(existingTask)
this.enqueueNotificationForParent(existingTask.parentSessionID, () => this.notifyParentSession(existingTask)).catch(err => {
this.enqueueNotificationForParent(existingTask.parentSessionId, () => this.notifyParentSession(existingTask)).catch(err => {
log("[background-agent] Failed to notify on resume error:", err)
})
})
@@ -1386,25 +1387,25 @@ The fallback retry session is now created and can be inspected directly.
const deletedSessionIDs = new Set<string>([sessionID])
for (const task of tasksToCancel.values()) {
if (task.sessionID) {
deletedSessionIDs.add(task.sessionID)
if (task.sessionId) {
deletedSessionIDs.add(task.sessionId)
}
}
for (const task of tasksToCancel.values()) {
parentSessionsToClear.add(task.parentSessionID)
parentSessionsToClear.add(task.parentSessionId)
if (task.status === "running" || task.status === "pending") {
void this.cancelTask(task.id, {
source: "session.deleted",
reason: "Session deleted",
}).then(() => {
if (deletedSessionIDs.has(task.parentSessionID)) {
this.pendingNotifications.delete(task.parentSessionID)
if (deletedSessionIDs.has(task.parentSessionId)) {
this.pendingNotifications.delete(task.parentSessionId)
}
}).catch(err => {
if (deletedSessionIDs.has(task.parentSessionID)) {
this.pendingNotifications.delete(task.parentSessionID)
if (deletedSessionIDs.has(task.parentSessionId)) {
this.pendingNotifications.delete(task.parentSessionId)
}
log("[background-agent] Failed to cancel task on session.deleted:", { taskId: task.id, error: err })
})
@@ -1449,8 +1450,8 @@ The fallback retry session is now created and can be inspected directly.
}): Promise<void> {
const { task, errorInfo, errorMessage, errorName } = args
if (!task.fallbackChain && task.sessionID) {
const sessionFallbackChain = this.modelFallbackControllerAccessor?.getSessionFallbackChain(task.sessionID)
if (!task.fallbackChain && task.sessionId) {
const sessionFallbackChain = this.modelFallbackControllerAccessor?.getSessionFallbackChain(task.sessionId)
if (sessionFallbackChain?.length) {
task.fallbackChain = sessionFallbackChain
}
@@ -1490,10 +1491,10 @@ The fallback retry session is now created and can be inspected directly.
task.error = errorMsg
task.completedAt = new Date()
}
if (task.rootSessionID) {
this.unregisterRootDescendant(task.rootSessionID)
if (task.rootSessionId) {
this.unregisterRootDescendant(task.rootSessionId)
}
this.taskHistory.record(task.parentSessionID, { id: task.id, sessionID: task.sessionID, agent: task.agent, description: task.description, status: "error", category: task.category, startedAt: task.startedAt, completedAt: task.completedAt })
this.taskHistory.record(task.parentSessionId, { id: task.id, sessionID: task.sessionId, agent: task.agent, description: task.description, status: "error", category: task.category, startedAt: task.startedAt, completedAt: task.completedAt })
if (task.concurrencyKey) {
this.concurrencyManager.release(task.concurrencyKey)
@@ -1519,12 +1520,12 @@ The fallback retry session is now created and can be inspected directly.
toastManager.removeTask(task.id)
}
this.scheduleTaskRemoval(task.id)
if (task.sessionID) {
SessionCategoryRegistry.remove(task.sessionID)
if (task.sessionId) {
SessionCategoryRegistry.remove(task.sessionId)
}
this.markForNotification(task)
this.enqueueNotificationForParent(task.parentSessionID, () => this.notifyParentSession(task)).catch(err => {
this.enqueueNotificationForParent(task.parentSessionId, () => this.notifyParentSession(task)).catch(err => {
log("[background-agent] Error in notifyParentSession for errored task:", { taskId: task.id, error: err })
})
}
@@ -1534,7 +1535,7 @@ The fallback retry session is now created and can be inspected directly.
errorInfo: { name?: string; message?: string },
source: string,
): Promise<boolean> {
const previousSessionID = task.sessionID
const previousSessionID = task.sessionId
const result = tryFallbackRetry({
task,
errorInfo,
@@ -1546,15 +1547,15 @@ The fallback retry session is now created and can be inspected directly.
processKey: (key: string) => this.processKey(key),
onRetrying: ({ task, source }) => {
const currentAttempt = getCurrentAttempt(task)
const previousAttempt = getPreviousAttempt(task, currentAttempt?.attemptID)
const previousAttempt = getPreviousAttempt(task, currentAttempt?.attemptId)
const sourceText = source ? ` via ${source}` : ""
const failedSessionLine = previousAttempt?.sessionID ? `\n- Failed session: \`${previousAttempt.sessionID}\`` : ""
const failedSessionLine = previousAttempt?.sessionId ? `\n- Failed session: \`${previousAttempt.sessionId}\`` : ""
const failedModel = formatAttemptModelSummary(previousAttempt)
const failedModelLine = failedModel ? `\n- Failed model: \`${failedModel}\`` : ""
const failedErrorLine = previousAttempt?.error ? `\n- Error: ${previousAttempt.error}` : ""
const nextModel = formatAttemptModelSummary(currentAttempt)
this.queuePendingNotification(
task.parentSessionID,
task.parentSessionId,
`<system-reminder>
[BACKGROUND TASK RETRYING]
**ID:** \`${task.id}\`
@@ -1576,9 +1577,9 @@ The task was re-queued on a fallback model after a retryable failure.
}
markForNotification(task: BackgroundTask): void {
const queue = this.notifications.get(task.parentSessionID) ?? []
const queue = this.notifications.get(task.parentSessionId) ?? []
queue.push(task)
this.notifications.set(task.parentSessionID, queue)
this.notifications.set(task.parentSessionId, queue)
}
getPendingNotifications(sessionID: string): BackgroundTask[] {
@@ -1695,12 +1696,12 @@ The task was re-queued on a fallback model after a retryable failure.
* Cleans up the parent entry if no pending tasks remain.
*/
private cleanupPendingByParent(task: BackgroundTask): void {
if (!task.parentSessionID) return
const pending = this.pendingByParent.get(task.parentSessionID)
if (!task.parentSessionId) return
const pending = this.pendingByParent.get(task.parentSessionId)
if (pending) {
pending.delete(task.id)
if (pending.size === 0) {
this.pendingByParent.delete(task.parentSessionID)
this.pendingByParent.delete(task.parentSessionId)
}
}
}
@@ -1724,8 +1725,8 @@ The task was re-queued on a fallback model after a retryable failure.
const task = this.tasks.get(taskId)
if (!task) return
if (task.parentSessionID) {
const siblings = this.getTasksByParentSession(task.parentSessionID)
if (task.parentSessionId) {
const siblings = this.getTasksByParentSession(task.parentSessionId)
const runningOrPendingSiblings = siblings.filter(
sibling => sibling.id !== taskId && (sibling.status === "running" || sibling.status === "pending"),
)
@@ -1739,10 +1740,10 @@ The task was re-queued on a fallback model after a retryable failure.
this.clearNotificationsForTask(taskId)
this.removeTask(task)
this.clearTaskHistoryWhenParentTasksGone(task.parentSessionID)
if (task.sessionID) {
subagentSessions.delete(task.sessionID)
SessionCategoryRegistry.remove(task.sessionID)
this.clearTaskHistoryWhenParentTasksGone(task.parentSessionId)
if (task.sessionId) {
subagentSessions.delete(task.sessionId)
SessionCategoryRegistry.remove(task.sessionId)
}
log("[background-agent] Removed completed task from memory:", taskId)
}, TASK_CLEANUP_DELAY_MS)
@@ -1791,10 +1792,10 @@ The task was re-queued on a fallback model after a retryable failure.
task.error = reason
}
}
if (wasRunning && task.rootSessionID) {
this.unregisterRootDescendant(task.rootSessionID)
if (wasRunning && task.rootSessionId) {
this.unregisterRootDescendant(task.rootSessionId)
}
this.taskHistory.record(task.parentSessionID, { id: task.id, sessionID: task.sessionID, agent: task.agent, description: task.description, status: "cancelled", category: task.category, startedAt: task.startedAt, completedAt: task.completedAt })
this.taskHistory.record(task.parentSessionId, { id: task.id, sessionID: task.sessionId, agent: task.agent, description: task.description, status: "cancelled", category: task.category, startedAt: task.startedAt, completedAt: task.completedAt })
if (task.concurrencyKey) {
this.concurrencyManager.release(task.concurrencyKey)
@@ -1813,11 +1814,11 @@ The task was re-queued on a fallback model after a retryable failure.
this.idleDeferralTimers.delete(task.id)
}
if (abortSession && task.sessionID) {
if (abortSession && task.sessionId) {
// Awaited to prevent dangling promise during subagent teardown (Bun/WebKit SIGABRT)
await this.abortSessionWithLogging(task.sessionID, `task cancellation (${source})`)
await this.abortSessionWithLogging(task.sessionId, `task cancellation (${source})`)
SessionCategoryRegistry.remove(task.sessionID)
SessionCategoryRegistry.remove(task.sessionId)
}
removeTaskToastTracking(task.id)
@@ -1832,7 +1833,7 @@ The task was re-queued on a fallback model after a retryable failure.
this.markForNotification(task)
try {
await this.enqueueNotificationForParent(task.parentSessionID, () => this.notifyParentSession(task))
await this.enqueueNotificationForParent(task.parentSessionId, () => this.notifyParentSession(task))
log(`[background-agent] Task cancelled via ${source}:`, task.id)
} catch (err) {
log("[background-agent] Error in notifyParentSession for cancelled task:", { taskId: task.id, error: err })
@@ -1911,10 +1912,10 @@ The task was re-queued on a fallback model after a retryable failure.
task.status = "completed"
task.completedAt = new Date()
}
this.taskHistory.record(task.parentSessionID, { id: task.id, sessionID: task.sessionID, agent: task.agent, description: task.description, status: "completed", category: task.category, startedAt: task.startedAt, completedAt: task.completedAt })
this.taskHistory.record(task.parentSessionId, { id: task.id, sessionID: task.sessionId, agent: task.agent, description: task.description, status: "completed", category: task.category, startedAt: task.startedAt, completedAt: task.completedAt })
if (task.rootSessionID) {
this.unregisterRootDescendant(task.rootSessionID)
if (task.rootSessionId) {
this.unregisterRootDescendant(task.rootSessionId)
}
removeTaskToastTracking(task.id)
@@ -1933,15 +1934,15 @@ The task was re-queued on a fallback model after a retryable failure.
this.idleDeferralTimers.delete(task.id)
}
if (task.sessionID) {
if (task.sessionId) {
// Awaited to prevent dangling promise during subagent teardown (Bun/WebKit SIGABRT)
await this.abortSessionWithLogging(task.sessionID, `task completion (${source})`)
await this.abortSessionWithLogging(task.sessionId, `task completion (${source})`)
SessionCategoryRegistry.remove(task.sessionID)
SessionCategoryRegistry.remove(task.sessionId)
}
try {
await this.enqueueNotificationForParent(task.parentSessionID, () => this.notifyParentSession(task))
await this.enqueueNotificationForParent(task.parentSessionId, () => this.notifyParentSession(task))
log(`[background-agent] Task completed via ${source}:`, task.id)
} catch (err) {
log("[background-agent] Error in notifyParentSession:", { taskId: task.id, error: err })
@@ -1966,10 +1967,10 @@ The task was re-queued on a fallback model after a retryable failure.
})
}
if (!this.completedTaskSummaries.has(task.parentSessionID)) {
this.completedTaskSummaries.set(task.parentSessionID, [])
if (!this.completedTaskSummaries.has(task.parentSessionId)) {
this.completedTaskSummaries.set(task.parentSessionId, [])
}
this.completedTaskSummaries.get(task.parentSessionID)!.push({
this.completedTaskSummaries.get(task.parentSessionId)!.push({
id: task.id,
description: task.description,
status: task.status,
@@ -1978,7 +1979,7 @@ The task was re-queued on a fallback model after a retryable failure.
})
// Update pending tracking and check if all tasks complete
const pendingSet = this.pendingByParent.get(task.parentSessionID)
const pendingSet = this.pendingByParent.get(task.parentSessionId)
let allComplete = false
let remainingCount = 0
if (pendingSet) {
@@ -1986,21 +1987,21 @@ The task was re-queued on a fallback model after a retryable failure.
remainingCount = pendingSet.size
allComplete = remainingCount === 0
if (allComplete) {
this.pendingByParent.delete(task.parentSessionID)
this.pendingByParent.delete(task.parentSessionId)
}
} else {
remainingCount = Array.from(this.tasks.values())
.filter(t => t.parentSessionID === task.parentSessionID && t.id !== task.id && (t.status === "running" || t.status === "pending"))
.filter(t => t.parentSessionId === task.parentSessionId && t.id !== task.id && (t.status === "running" || t.status === "pending"))
.length
allComplete = remainingCount === 0
}
const completedTasks = allComplete
? (this.completedTaskSummaries.get(task.parentSessionID) ?? [{ id: task.id, description: task.description, status: task.status, error: task.error, attempts: cloneAttempts(task) }])
? (this.completedTaskSummaries.get(task.parentSessionId) ?? [{ id: task.id, description: task.description, status: task.status, error: task.error, attempts: cloneAttempts(task) }])
: []
if (allComplete) {
this.completedTaskSummaries.delete(task.parentSessionID)
this.completedTaskSummaries.delete(task.parentSessionId)
}
const statusText = task.status === "completed"
@@ -2026,7 +2027,7 @@ The task was re-queued on a fallback model after a retryable failure.
if (this.enableParentSessionNotifications) {
try {
const messagesResp = await this.client.session.messages({ path: { id: task.parentSessionID } })
const messagesResp = await this.client.session.messages({ path: { id: task.parentSessionId } })
const messages = normalizeSDKResponse(messagesResp, [] as Array<{
info?: {
agent?: string
@@ -2038,7 +2039,7 @@ The task was re-queued on a fallback model after a retryable failure.
}>)
promptContext = resolvePromptContextFromSessionMessages(
messages,
task.parentSessionID,
task.parentSessionId,
)
const normalizedTools = isRecord(promptContext?.tools)
? normalizePromptTools(promptContext.tools)
@@ -2055,12 +2056,12 @@ The task was re-queued on a fallback model after a retryable failure.
if (isAbortedSessionError(error)) {
log("[background-agent] Parent session aborted while loading messages; using messageDir fallback:", {
taskId: task.id,
parentSessionID: task.parentSessionID,
parentSessionID: task.parentSessionId,
})
}
const messageDir = join(MESSAGE_STORAGE, task.parentSessionID)
const messageDir = join(MESSAGE_STORAGE, task.parentSessionId)
const currentMessage = messageDir
? findNearestMessageExcludingCompaction(messageDir, task.parentSessionID)
? findNearestMessageExcludingCompaction(messageDir, task.parentSessionId)
: null
agent = currentMessage?.agent ?? task.parentAgent
model = currentMessage?.model?.providerID && currentMessage?.model?.modelID
@@ -2069,7 +2070,7 @@ The task was re-queued on a fallback model after a retryable failure.
tools = normalizePromptTools(currentMessage?.tools) ?? tools
}
const resolvedTools = resolveInheritedPromptTools(task.parentSessionID, tools)
const resolvedTools = resolveInheritedPromptTools(task.parentSessionId, tools)
log("[background-agent] notifyParentSession context:", {
taskId: task.id,
@@ -2084,7 +2085,7 @@ The task was re-queued on a fallback model after a retryable failure.
try {
await this.client.session.promptAsync({
path: { id: task.parentSessionID },
path: { id: task.parentSessionId },
body: {
noReply: !shouldReply,
...(agent !== undefined ? { agent } : {}),
@@ -2104,9 +2105,9 @@ The task was re-queued on a fallback model after a retryable failure.
if (isAbortedSessionError(error)) {
log("[background-agent] Parent session aborted while sending notification; continuing cleanup:", {
taskId: task.id,
parentSessionID: task.parentSessionID,
parentSessionID: task.parentSessionId,
})
this.queuePendingNotification(task.parentSessionID, notification)
this.queuePendingNotification(task.parentSessionId, notification)
} else {
log("[background-agent] Failed to send notification:", error)
}
@@ -2114,7 +2115,7 @@ The task was re-queued on a fallback model after a retryable failure.
} else {
log("[background-agent] Parent session notifications disabled, skipping prompt injection:", {
taskId: task.id,
parentSessionID: task.parentSessionID,
parentSessionID: task.parentSessionId,
})
}
@@ -2141,10 +2142,10 @@ The task was re-queued on a fallback model after a retryable failure.
task.status = "error"
task.error = errorMessage
task.completedAt = new Date()
if (!wasPending && task.rootSessionID) {
this.unregisterRootDescendant(task.rootSessionID)
if (!wasPending && task.rootSessionId) {
this.unregisterRootDescendant(task.rootSessionId)
}
this.taskHistory.record(task.parentSessionID, { id: task.id, sessionID: task.sessionID, agent: task.agent, description: task.description, status: "error", category: task.category, startedAt: task.startedAt, completedAt: task.completedAt })
this.taskHistory.record(task.parentSessionId, { id: task.id, sessionID: task.sessionId, agent: task.agent, description: task.description, status: "error", category: task.category, startedAt: task.startedAt, completedAt: task.completedAt })
if (task.concurrencyKey) {
this.concurrencyManager.release(task.concurrencyKey)
task.concurrencyKey = undefined
@@ -2177,7 +2178,7 @@ The task was re-queued on a fallback model after a retryable failure.
}
this.cleanupPendingByParent(task)
this.markForNotification(task)
this.enqueueNotificationForParent(task.parentSessionID, () => this.notifyParentSession(task)).catch(err => {
this.enqueueNotificationForParent(task.parentSessionId, () => this.notifyParentSession(task)).catch(err => {
log("[background-agent] Error in notifyParentSession for stale-pruned task:", { taskId: task.id, error: err })
})
},
@@ -2193,7 +2194,7 @@ The task was re-queued on a fallback model after a retryable failure.
directory: this.directory,
config: this.config,
concurrencyManager: this.concurrencyManager,
notifyParentSession: (task) => this.enqueueNotificationForParent(task.parentSessionID, () => this.notifyParentSession(task)),
notifyParentSession: (task) => this.enqueueNotificationForParent(task.parentSessionId, () => this.notifyParentSession(task)),
sessionStatuses: allStatuses,
})
}
@@ -2210,10 +2211,10 @@ The task was re-queued on a fallback model after a retryable failure.
task.error = errorMessage
task.completedAt = new Date()
}
if (task.rootSessionID) {
this.unregisterRootDescendant(task.rootSessionID)
if (task.rootSessionId) {
this.unregisterRootDescendant(task.rootSessionId)
}
this.taskHistory.record(task.parentSessionID, { id: task.id, sessionID: task.sessionID, agent: task.agent, description: task.description, status: "error", category: task.category, startedAt: task.startedAt, completedAt: task.completedAt })
this.taskHistory.record(task.parentSessionId, { id: task.id, sessionID: task.sessionId, agent: task.agent, description: task.description, status: "error", category: task.category, startedAt: task.startedAt, completedAt: task.completedAt })
if (task.concurrencyKey) {
this.concurrencyManager.release(task.concurrencyKey)
task.concurrencyKey = undefined
@@ -2234,12 +2235,12 @@ The task was re-queued on a fallback model after a retryable failure.
this.clearNotificationsForTask(task.id)
removeTaskToastTracking(task.id)
this.scheduleTaskRemoval(task.id)
if (task.sessionID) {
SessionCategoryRegistry.remove(task.sessionID)
if (task.sessionId) {
SessionCategoryRegistry.remove(task.sessionId)
}
this.markForNotification(task)
this.enqueueNotificationForParent(task.parentSessionID, () => this.notifyParentSession(task)).catch(err => {
this.enqueueNotificationForParent(task.parentSessionId, () => this.notifyParentSession(task)).catch(err => {
log("[background-agent] Error in notifyParentSession for crashed task:", { taskId: task.id, error: err })
})
}
@@ -2258,7 +2259,7 @@ The task was re-queued on a fallback model after a retryable failure.
for (const task of this.tasks.values()) {
if (task.status !== "running") continue
const sessionID = task.sessionID
const sessionID = task.sessionId
if (!sessionID) continue
try {
@@ -2360,14 +2361,14 @@ The task was re-queued on a fallback model after a retryable failure.
// Abort all running sessions to prevent zombie processes (#1240)
for (const task of this.tasks.values()) {
if (task.sessionID) {
trackedSessionIDs.add(task.sessionID)
if (task.sessionId) {
trackedSessionIDs.add(task.sessionId)
}
if (task.status === "running" && task.sessionID) {
if (task.status === "running" && task.sessionId) {
abortRequests.push({
sessionID: task.sessionID,
promise: abortWithTimeout(this.client, task.sessionID),
sessionID: task.sessionId,
promise: abortWithTimeout(this.client, task.sessionId),
})
}
}
@@ -7,9 +7,9 @@ import { MIN_IDLE_TIME_MS } from "./constants"
function createRunningTask(overrides: Partial<BackgroundTask> = {}): BackgroundTask {
return {
id: "task-1",
sessionID: "ses-idle-1",
parentSessionID: "parent-ses-1",
parentMessageID: "msg-1",
sessionId: "ses-idle-1",
parentSessionId: "parent-ses-1",
parentMessageId: "msg-1",
description: "test idle handler",
prompt: "test",
agent: "explore",
@@ -91,7 +91,7 @@ describe("handleSessionIdleBackgroundEvent", () => {
//#when
handleSessionIdleBackgroundEvent({
properties: { sessionID: task.sessionID! },
properties: { sessionID: task.sessionId! },
findBySession: () => task,
idleDeferralTimers: new Map(),
validateSessionHasOutput: () => Promise.resolve(true),
@@ -113,7 +113,7 @@ describe("handleSessionIdleBackgroundEvent", () => {
//#when
handleSessionIdleBackgroundEvent({
properties: { sessionID: task.sessionID! },
properties: { sessionID: task.sessionId! },
findBySession: () => task,
idleDeferralTimers: new Map(),
validateSessionHasOutput: () => Promise.resolve(true),
@@ -141,7 +141,7 @@ describe("handleSessionIdleBackgroundEvent", () => {
//#when
handleSessionIdleBackgroundEvent({
properties: { sessionID: task.sessionID! },
properties: { sessionID: task.sessionId! },
findBySession: () => task,
idleDeferralTimers,
validateSessionHasOutput: () => Promise.resolve(true),
@@ -175,7 +175,7 @@ describe("handleSessionIdleBackgroundEvent", () => {
//#when
handleSessionIdleBackgroundEvent({
properties: { sessionID: task.sessionID! },
properties: { sessionID: task.sessionId! },
findBySession: () => task,
idleDeferralTimers,
validateSessionHasOutput: () => Promise.resolve(true),
@@ -206,7 +206,7 @@ describe("handleSessionIdleBackgroundEvent", () => {
//#when
handleSessionIdleBackgroundEvent({
properties: { sessionID: task.sessionID! },
properties: { sessionID: task.sessionId! },
findBySession: () => task,
idleDeferralTimers,
validateSessionHasOutput: () => Promise.resolve(true),
@@ -217,7 +217,7 @@ describe("handleSessionIdleBackgroundEvent", () => {
//#then - wait for deferred timer
await new Promise((resolve) => setTimeout(resolve, remainingMs + 50))
expect(emitIdleEvent).toHaveBeenCalledWith(task.sessionID)
expect(emitIdleEvent).toHaveBeenCalledWith(task.sessionId)
expect(idleDeferralTimers.has(task.id)).toBe(false)
} finally {
Date.now = realDateNow
@@ -233,7 +233,7 @@ describe("handleSessionIdleBackgroundEvent", () => {
//#when
handleSessionIdleBackgroundEvent({
properties: { sessionID: task.sessionID! },
properties: { sessionID: task.sessionId! },
findBySession: () => task,
idleDeferralTimers: new Map(),
validateSessionHasOutput: () => Promise.resolve(true),
@@ -254,7 +254,7 @@ describe("handleSessionIdleBackgroundEvent", () => {
//#when
handleSessionIdleBackgroundEvent({
properties: { sessionID: task.sessionID! },
properties: { sessionID: task.sessionId! },
findBySession: () => task,
idleDeferralTimers: new Map(),
validateSessionHasOutput: () => Promise.resolve(false),
@@ -275,7 +275,7 @@ describe("handleSessionIdleBackgroundEvent", () => {
//#when
handleSessionIdleBackgroundEvent({
properties: { sessionID: task.sessionID! },
properties: { sessionID: task.sessionId! },
findBySession: () => task,
idleDeferralTimers: new Map(),
validateSessionHasOutput: () => Promise.resolve(true),
@@ -296,7 +296,7 @@ describe("handleSessionIdleBackgroundEvent", () => {
//#when
handleSessionIdleBackgroundEvent({
properties: { sessionID: task.sessionID! },
properties: { sessionID: task.sessionId! },
findBySession: () => task,
idleDeferralTimers: new Map(),
validateSessionHasOutput: async () => {
@@ -320,7 +320,7 @@ describe("handleSessionIdleBackgroundEvent", () => {
//#when
handleSessionIdleBackgroundEvent({
properties: { sessionID: task.sessionID! },
properties: { sessionID: task.sessionId! },
findBySession: () => task,
idleDeferralTimers: new Map(),
validateSessionHasOutput: () => Promise.resolve(true),
+44 -44
View File
@@ -37,8 +37,8 @@ describe("background-agent spawner agent-not-found fallback", () => {
description: "Implement feature",
prompt: "Please implement the break-even analysis",
agent: "Sisyphus-Junior",
parentSessionID: "ses_parent",
parentMessageID: "msg_parent",
parentSessionId: "ses_parent",
parentMessageId: "msg_parent",
})
const item = {
@@ -47,8 +47,8 @@ describe("background-agent spawner agent-not-found fallback", () => {
description: task.description,
prompt: task.prompt,
agent: task.agent,
parentSessionID: task.parentSessionID,
parentMessageID: task.parentMessageID,
parentSessionId: task.parentSessionId,
parentMessageId: task.parentMessageId,
parentModel: task.parentModel,
parentAgent: task.parentAgent,
model: task.model,
@@ -109,8 +109,8 @@ describe("background-agent spawner agent-not-found fallback", () => {
description: "Implement feature",
prompt: "Do work",
agent: "Sisyphus-Junior",
parentSessionID: "ses_parent",
parentMessageID: "msg_parent",
parentSessionId: "ses_parent",
parentMessageId: "msg_parent",
})
const item = {
@@ -119,8 +119,8 @@ describe("background-agent spawner agent-not-found fallback", () => {
description: task.description,
prompt: task.prompt,
agent: task.agent,
parentSessionID: task.parentSessionID,
parentMessageID: task.parentMessageID,
parentSessionId: task.parentSessionId,
parentMessageId: task.parentMessageId,
},
}
@@ -162,8 +162,8 @@ describe("background-agent spawner agent-not-found fallback", () => {
description: "Implement feature",
prompt: "Do work",
agent: "Sisyphus-Junior",
parentSessionID: "ses_parent",
parentMessageID: "msg_parent",
parentSessionId: "ses_parent",
parentMessageId: "msg_parent",
})
const item = {
@@ -172,8 +172,8 @@ describe("background-agent spawner agent-not-found fallback", () => {
description: task.description,
prompt: task.prompt,
agent: task.agent,
parentSessionID: task.parentSessionID,
parentMessageID: task.parentMessageID,
parentSessionId: task.parentSessionId,
parentMessageId: task.parentMessageId,
},
}
@@ -221,8 +221,8 @@ describe("background-agent spawner agent-not-found fallback", () => {
description: "Test task",
prompt: "Do work",
agent: "Sisyphus-Junior",
parentSessionID: "ses_parent",
parentMessageID: "msg_parent",
parentSessionId: "ses_parent",
parentMessageId: "msg_parent",
})
const item = {
@@ -231,8 +231,8 @@ describe("background-agent spawner agent-not-found fallback", () => {
description: task.description,
prompt: task.prompt,
agent: task.agent,
parentSessionID: task.parentSessionID,
parentMessageID: task.parentMessageID,
parentSessionId: task.parentSessionId,
parentMessageId: task.parentMessageId,
parentModel: task.parentModel,
parentAgent: task.parentAgent,
model: task.model,
@@ -284,8 +284,8 @@ describe("background-agent spawner agent-not-found fallback", () => {
description: "Test task",
prompt: "Do work",
agent: "Custom-Agent",
parentSessionID: "ses_parent",
parentMessageID: "msg_parent",
parentSessionId: "ses_parent",
parentMessageId: "msg_parent",
})
const item = {
@@ -294,8 +294,8 @@ describe("background-agent spawner agent-not-found fallback", () => {
description: task.description,
prompt: task.prompt,
agent: task.agent,
parentSessionID: task.parentSessionID,
parentMessageID: task.parentMessageID,
parentSessionId: task.parentSessionId,
parentMessageId: task.parentMessageId,
parentModel: task.parentModel,
parentAgent: task.parentAgent,
model: task.model,
@@ -353,8 +353,8 @@ describe("background-agent spawner fallback model promotion", () => {
description: "Test task",
prompt: "Do the thing",
agent: "oracle",
parentSessionID: "parent-1",
parentMessageID: "message-1",
parentSessionId: "parent-1",
parentMessageId: "message-1",
model: {
providerID: "openai",
modelID: "gpt-5.4",
@@ -371,8 +371,8 @@ describe("background-agent spawner fallback model promotion", () => {
description: "Test task",
prompt: "Do the thing",
agent: "oracle",
parentSessionID: "parent-1",
parentMessageID: "message-1",
parentSessionId: "parent-1",
parentMessageId: "message-1",
model: task.model,
}
@@ -427,8 +427,8 @@ describe("background-agent spawner fallback model promotion", () => {
description: "Test task",
prompt: "Do work",
agent: "sisyphus-junior",
parentSessionID: "ses_parent",
parentMessageID: "msg_parent",
parentSessionId: "ses_parent",
parentMessageId: "msg_parent",
model: { providerID: "openai", modelID: "gpt-5.4", variant: "medium" },
})
@@ -438,8 +438,8 @@ describe("background-agent spawner fallback model promotion", () => {
description: task.description,
prompt: task.prompt,
agent: task.agent,
parentSessionID: task.parentSessionID,
parentMessageID: task.parentMessageID,
parentSessionId: task.parentSessionId,
parentMessageId: task.parentMessageId,
parentModel: task.parentModel,
parentAgent: task.parentAgent,
model: task.model,
@@ -486,8 +486,8 @@ describe("background-agent spawner fallback model promotion", () => {
description: "Test task",
prompt: "Do work",
agent: "sisyphus-junior",
parentSessionID: "ses_parent",
parentMessageID: "msg_parent",
parentSessionId: "ses_parent",
parentMessageId: "msg_parent",
})
const item = {
@@ -496,8 +496,8 @@ describe("background-agent spawner fallback model promotion", () => {
description: task.description,
prompt: task.prompt,
agent: task.agent,
parentSessionID: task.parentSessionID,
parentMessageID: task.parentMessageID,
parentSessionId: task.parentSessionId,
parentMessageId: task.parentMessageId,
parentModel: task.parentModel,
parentAgent: task.parentAgent,
model: task.model,
@@ -542,8 +542,8 @@ describe("background-agent spawner fallback model promotion", () => {
description: "Test task",
prompt: "Do work",
agent: "\u200Bsisyphus-junior",
parentSessionID: "ses_parent",
parentMessageID: "msg_parent",
parentSessionId: "ses_parent",
parentMessageId: "msg_parent",
})
const item = {
@@ -552,8 +552,8 @@ describe("background-agent spawner fallback model promotion", () => {
description: task.description,
prompt: task.prompt,
agent: task.agent,
parentSessionID: task.parentSessionID,
parentMessageID: task.parentMessageID,
parentSessionId: task.parentSessionId,
parentMessageId: task.parentMessageId,
parentModel: task.parentModel,
parentAgent: task.parentAgent,
model: task.model,
@@ -596,8 +596,8 @@ describe("background-agent spawner fallback model promotion", () => {
description: "Legacy ZWSP",
prompt: "Do work",
agent: "\u200B\u200BHephaestus - Deep Agent",
parentSessionID: "ses_parent",
parentMessageID: "msg_parent",
parentSessionId: "ses_parent",
parentMessageId: "msg_parent",
})
const item = {
@@ -606,8 +606,8 @@ describe("background-agent spawner fallback model promotion", () => {
description: task.description,
prompt: task.prompt,
agent: task.agent,
parentSessionID: task.parentSessionID,
parentMessageID: task.parentMessageID,
parentSessionId: task.parentSessionId,
parentMessageId: task.parentMessageId,
parentModel: task.parentModel,
parentAgent: task.parentAgent,
model: task.model,
@@ -665,8 +665,8 @@ describe("background-agent spawner tmux callback ordering", () => {
description: "Blocking tmux test",
prompt: "Do work",
agent: "general",
parentSessionID: "ses_parent",
parentMessageID: "msg_parent",
parentSessionId: "ses_parent",
parentMessageId: "msg_parent",
})
const item = {
@@ -675,8 +675,8 @@ describe("background-agent spawner tmux callback ordering", () => {
description: task.description,
prompt: task.prompt,
agent: task.agent,
parentSessionID: task.parentSessionID,
parentMessageID: task.parentMessageID,
parentSessionId: task.parentSessionId,
parentMessageId: task.parentMessageId,
},
}
+17 -17
View File
@@ -58,8 +58,8 @@ export function createTask(input: LaunchInput): BackgroundTask {
description: input.description,
prompt: input.prompt,
agent: input.agent,
parentSessionID: input.parentSessionID,
parentMessageID: input.parentMessageID,
parentSessionId: input.parentSessionId,
parentMessageId: input.parentMessageId,
parentModel: input.parentModel,
parentAgent: input.parentAgent,
model: input.model,
@@ -84,7 +84,7 @@ export async function startTask(
: input.agent
const parentSession = await client.session.get({
path: { id: input.parentSessionID },
path: { id: input.parentSessionId },
query: { directory },
}).catch((err) => {
log(`[background-agent] Failed to get parent session: ${err}`)
@@ -95,7 +95,7 @@ export async function startTask(
const createResult = await client.session.create({
body: {
parentID: input.parentSessionID,
parentID: input.parentSessionId,
...(input.sessionPermission ? { permission: input.sessionPermission } : {}),
} as Record<string, unknown>,
query: {
@@ -116,7 +116,7 @@ export async function startTask(
task.status = "running"
task.startedAt = new Date()
task.sessionID = sessionID
task.sessionId = sessionID
task.progress = {
toolCalls: 0,
lastUpdate: new Date(),
@@ -199,14 +199,14 @@ export async function startTask(
tmuxEnabled,
isInsideTmux: isInsideTmux(),
sessionID,
parentID: input.parentSessionID,
parentID: input.parentSessionId,
})
if (onSubagentSessionCreated && tmuxEnabled && isInsideTmux()) {
log("[background-agent] Invoking tmux callback (fire-and-forget)", { sessionID })
void onSubagentSessionCreated({
sessionID,
parentID: input.parentSessionID,
parentID: input.parentSessionId,
title: input.description,
}).catch((err) => {
log("[background-agent] Failed to spawn tmux pane:", err)
@@ -223,14 +223,14 @@ export async function resumeTask(
): Promise<void> {
const { client, concurrencyManager, onTaskError } = ctx
if (!task.sessionID) {
if (!task.sessionId) {
throw new Error(`Task has no sessionID: ${task.id}`)
}
if (task.status === "running") {
log("[background-agent] Resume skipped - task already running:", {
taskId: task.id,
sessionID: task.sessionID,
sessionID: task.sessionId,
})
return
}
@@ -243,8 +243,8 @@ export async function resumeTask(
task.status = "running"
task.completedAt = undefined
task.error = undefined
task.parentSessionID = input.parentSessionID
task.parentMessageID = input.parentMessageID
task.parentSessionId = input.parentSessionId
task.parentMessageId = input.parentMessageId
task.parentModel = input.parentModel
task.parentAgent = input.parentAgent
task.startedAt = new Date()
@@ -254,7 +254,7 @@ export async function resumeTask(
lastUpdate: new Date(),
}
subagentSessions.add(task.sessionID)
subagentSessions.add(task.sessionId)
const toastManager = getTaskToastManager()
if (toastManager) {
@@ -266,10 +266,10 @@ export async function resumeTask(
})
}
log("[background-agent] Resuming task:", { taskId: task.id, sessionID: task.sessionID })
log("[background-agent] Resuming task:", { taskId: task.id, sessionID: task.sessionId })
log("[background-agent] Resuming task - calling prompt (fire-and-forget) with:", {
sessionID: task.sessionID,
sessionID: task.sessionId,
agent: task.agent,
model: task.model,
promptLength: input.prompt.length,
@@ -283,7 +283,7 @@ export async function resumeTask(
: undefined
const resumeVariant = task.model?.variant
applySessionPromptParams(task.sessionID, task.model)
applySessionPromptParams(task.sessionId, task.model)
const resumeBody = {
agent: task.agent,
@@ -299,7 +299,7 @@ export async function resumeTask(
}
client.session.promptAsync({
path: { id: task.sessionID },
path: { id: task.sessionId },
body: resumeBody,
}).catch(async (error) => {
if (isAgentNotFoundError(error) && task.agent !== FALLBACK_AGENT) {
@@ -310,7 +310,7 @@ export async function resumeTask(
})
try {
await promptWithModelSuggestionRetry(client, {
path: { id: task.sessionID! },
path: { id: task.sessionId! },
body: buildFallbackBody(resumeBody, FALLBACK_AGENT),
})
task.agent = FALLBACK_AGENT
+11 -11
View File
@@ -14,7 +14,7 @@ export class TaskStateManager {
}
findBySession(sessionID: string): BackgroundTask | undefined {
for (const task of this.tasks.values()) {
if (task.sessionID === sessionID) {
if (task.sessionId === sessionID) {
return task
}
}
@@ -23,7 +23,7 @@ export class TaskStateManager {
getTasksByParentSession(sessionID: string): BackgroundTask[] {
const result: BackgroundTask[] = []
for (const task of this.tasks.values()) {
if (task.parentSessionID === sessionID) {
if (task.parentSessionId === sessionID) {
result.push(task)
}
}
@@ -36,8 +36,8 @@ export class TaskStateManager {
for (const child of directChildren) {
result.push(child)
if (child.sessionID) {
const descendants = this.getAllDescendantTasks(child.sessionID)
if (child.sessionId) {
const descendants = this.getAllDescendantTasks(child.sessionId)
result.push(...descendants)
}
}
@@ -79,8 +79,8 @@ export class TaskStateManager {
removeTask(taskId: string): void {
const task = this.tasks.get(taskId)
if (task?.sessionID) {
subagentSessions.delete(task.sessionID)
if (task?.sessionId) {
subagentSessions.delete(task.sessionId)
}
this.tasks.delete(taskId)
}
@@ -92,20 +92,20 @@ export class TaskStateManager {
}
cleanupPendingByParent(task: BackgroundTask): void {
if (!task.parentSessionID) return
const pending = this.pendingByParent.get(task.parentSessionID)
if (!task.parentSessionId) return
const pending = this.pendingByParent.get(task.parentSessionId)
if (pending) {
pending.delete(task.id)
if (pending.size === 0) {
this.pendingByParent.delete(task.parentSessionID)
this.pendingByParent.delete(task.parentSessionId)
}
}
}
markForNotification(task: BackgroundTask): void {
const queue = this.notifications.get(task.parentSessionID) ?? []
const queue = this.notifications.get(task.parentSessionId) ?? []
queue.push(task)
this.notifications.set(task.parentSessionID, queue)
this.notifications.set(task.parentSessionId, queue)
}
getPendingNotifications(sessionID: string): BackgroundTask[] {
@@ -29,13 +29,13 @@ afterEach(() => {
fakeTimers = undefined
})
function createTask(overrides: Partial<BackgroundTask> & { id: string; parentSessionID: string }): BackgroundTask {
function createTask(overrides: Partial<BackgroundTask> & { id: string; parentSessionId: string }): BackgroundTask {
const id = overrides.id
const parentSessionID = overrides.parentSessionID
const { id: _ignoredID, parentSessionID: _ignoredParentSessionID, ...rest } = overrides
const parentSessionID = overrides.parentSessionId
const { id: _ignoredID, parentSessionId: _ignoredParentSessionID, ...rest } = overrides
return {
parentMessageID: overrides.parentMessageID ?? "parent-message-id",
parentMessageId: overrides.parentMessageId ?? "parent-message-id",
description: overrides.description ?? overrides.id,
prompt: overrides.prompt ?? `Prompt for ${overrides.id}`,
agent: overrides.agent ?? "test-agent",
@@ -43,7 +43,7 @@ function createTask(overrides: Partial<BackgroundTask> & { id: string; parentSes
startedAt: overrides.startedAt ?? new Date("2026-03-11T00:00:00.000Z"),
...rest,
id,
parentSessionID,
parentSessionId: parentSessionID,
}
}
@@ -74,9 +74,7 @@ function createManager(enableParentSessionNotifications: boolean): {
}
const manager = new BackgroundManager(
ctx,
undefined,
{ enableParentSessionNotifications }
{ pluginContext: ctx, config: undefined, enableParentSessionNotifications }
)
Reflect.set(manager, "client", client)
@@ -162,13 +160,13 @@ describe("BackgroundManager.notifyParentSession cleanup scheduling", () => {
const { manager } = createManager(false)
managerUnderTest = manager
fakeTimers = installFakeTimers()
const taskA = createTask({ id: "task-a", parentSessionID: "parent-1", description: "task A", status: "completed", completedAt: new Date() })
const taskB = createTask({ id: "task-b", parentSessionID: "parent-1", description: "task B", status: "running" })
const taskC = createTask({ id: "task-c", parentSessionID: "parent-1", description: "task C", status: "pending" })
const taskA = createTask({ id: "task-a", parentSessionId: "parent-1", description: "task A", status: "completed", completedAt: new Date() })
const taskB = createTask({ id: "task-b", parentSessionId: "parent-1", description: "task B", status: "running" })
const taskC = createTask({ id: "task-c", parentSessionId: "parent-1", description: "task C", status: "pending" })
getTasks(manager).set(taskA.id, taskA)
getTasks(manager).set(taskB.id, taskB)
getTasks(manager).set(taskC.id, taskC)
getPendingByParent(manager).set(taskA.parentSessionID, new Set([taskA.id, taskB.id, taskC.id]))
getPendingByParent(manager).set(taskA.parentSessionId, new Set([taskA.id, taskB.id, taskC.id]))
// when
await notifyParentSessionForTest(manager, taskA)
@@ -204,11 +202,11 @@ describe("BackgroundManager.notifyParentSession cleanup scheduling", () => {
const { manager, promptAsyncCalls } = createManager(true)
managerUnderTest = manager
fakeTimers = installFakeTimers()
const taskA = createTask({ id: "task-a", parentSessionID: "parent-1", description: "task A", status: "completed", completedAt: new Date("2026-03-11T00:01:00.000Z") })
const taskB = createTask({ id: "task-b", parentSessionID: "parent-1", description: "task B", status: "running" })
const taskA = createTask({ id: "task-a", parentSessionId: "parent-1", description: "task A", status: "completed", completedAt: new Date("2026-03-11T00:01:00.000Z") })
const taskB = createTask({ id: "task-b", parentSessionId: "parent-1", description: "task B", status: "running" })
getTasks(manager).set(taskA.id, taskA)
getTasks(manager).set(taskB.id, taskB)
getPendingByParent(manager).set(taskA.parentSessionID, new Set([taskA.id, taskB.id]))
getPendingByParent(manager).set(taskA.parentSessionId, new Set([taskA.id, taskB.id]))
await notifyParentSessionForTest(manager, taskA)
taskB.status = "completed"
@@ -242,9 +240,9 @@ describe("BackgroundManager.notifyParentSession cleanup scheduling", () => {
const { manager } = createManager(false)
managerUnderTest = manager
fakeTimers = installFakeTimers()
const task = createTask({ id: "task-a", parentSessionID: "parent-1", description: "task A", status: "completed", completedAt: new Date("2026-03-11T00:01:00.000Z") })
const task = createTask({ id: "task-a", parentSessionId: "parent-1", description: "task A", status: "completed", completedAt: new Date("2026-03-11T00:01:00.000Z") })
getTasks(manager).set(task.id, task)
getPendingByParent(manager).set(task.parentSessionID, new Set([task.id]))
getPendingByParent(manager).set(task.parentSessionId, new Set([task.id]))
await notifyParentSessionForTest(manager, task)
const cleanupTimer = getRequiredTimer(manager, task.id)
@@ -29,20 +29,20 @@ function createManager(): BackgroundManager {
$: {} as PluginInput["$"],
}
const manager = new BackgroundManager(ctx)
const manager = new BackgroundManager({ pluginContext: ctx })
Reflect.set(manager, "client", client)
return manager
}
function createTask(overrides: Partial<BackgroundTask> & { id: string; parentSessionID: string }): BackgroundTask {
function createTask(overrides: Partial<BackgroundTask> & { id: string; parentSessionId: string }): BackgroundTask {
const { id, parentSessionID, ...rest } = overrides
return {
...rest,
id,
parentSessionID,
parentMessageID: rest.parentMessageID ?? "parent-message-id",
parentMessageId: rest.parentMessageId ?? "parent-message-id",
description: rest.description ?? id,
prompt: rest.prompt ?? `Prompt for ${id}`,
agent: rest.agent ?? "test-agent",
@@ -118,12 +118,12 @@ describe("task history cleanup", () => {
managerUnderTest = manager
const staleTask = createTask({
id: "task-stale",
parentSessionID: "parent-1",
parentSessionId: "parent-1",
startedAt: new Date(Date.now() - 31 * 60 * 1000),
})
const liveTask = createTask({
id: "task-live",
parentSessionID: "parent-2",
parentSessionId: "parent-2",
startedAt: new Date(),
})
@@ -33,9 +33,9 @@ describe("checkAndInterruptStaleTasks", () => {
function createRunningTask(overrides: Partial<BackgroundTask> = {}): BackgroundTask {
return {
id: "task-1",
sessionID: "ses-1",
parentSessionID: "parent-ses-1",
parentMessageID: "msg-1",
sessionId: "ses-1",
parentSessionId: "parent-ses-1",
parentMessageId: "msg-1",
description: "test",
prompt: "test",
agent: "explore",
@@ -745,8 +745,8 @@ describe("pruneStaleTasksAndNotifications", () => {
function createTerminalTask(overrides: Partial<BackgroundTask> = {}): BackgroundTask {
return {
id: "terminal-task",
parentSessionID: "parent",
parentMessageID: "msg",
parentSessionId: "parent",
parentMessageId: "msg",
description: "terminal",
prompt: "terminal",
agent: "explore",
@@ -762,8 +762,8 @@ describe("pruneStaleTasksAndNotifications", () => {
const tasks = new Map<string, BackgroundTask>()
const oldTask: BackgroundTask = {
id: "old-task",
parentSessionID: "parent",
parentMessageID: "msg",
parentSessionId: "parent",
parentMessageId: "msg",
description: "old",
prompt: "old",
agent: "explore",
@@ -791,8 +791,8 @@ describe("pruneStaleTasksAndNotifications", () => {
const tasks = new Map<string, BackgroundTask>()
const activeTask: BackgroundTask = {
id: "active-task",
parentSessionID: "parent",
parentMessageID: "msg",
parentSessionId: "parent",
parentMessageId: "msg",
description: "active",
prompt: "active",
agent: "oracle",
@@ -824,8 +824,8 @@ describe("pruneStaleTasksAndNotifications", () => {
const tasks = new Map<string, BackgroundTask>()
const staleTask: BackgroundTask = {
id: "stale-task",
parentSessionID: "parent",
parentMessageID: "msg",
parentSessionId: "parent",
parentMessageId: "msg",
description: "stale",
prompt: "stale",
agent: "oracle",
@@ -857,8 +857,8 @@ describe("pruneStaleTasksAndNotifications", () => {
const tasks = new Map<string, BackgroundTask>()
const task: BackgroundTask = {
id: "custom-ttl-task",
parentSessionID: "parent",
parentMessageID: "msg",
parentSessionId: "parent",
parentMessageId: "msg",
description: "custom",
prompt: "custom",
agent: "explore",
@@ -887,8 +887,8 @@ describe("pruneStaleTasksAndNotifications", () => {
const tasks = new Map<string, BackgroundTask>()
const task: BackgroundTask = {
id: "within-ttl-task",
parentSessionID: "parent",
parentMessageID: "msg",
parentSessionId: "parent",
parentMessageId: "msg",
description: "within",
prompt: "within",
agent: "explore",
@@ -944,7 +944,7 @@ describe("pruneStaleTasksAndNotifications", () => {
//#given
const task = createTerminalTask()
const tasks = new Map<string, BackgroundTask>([[task.id, task]])
const notifications = new Map<string, BackgroundTask[]>([[task.parentSessionID, [task]]])
const notifications = new Map<string, BackgroundTask[]>([[task.parentSessionId, [task]]])
const pruned: string[] = []
//#when
@@ -957,6 +957,6 @@ describe("pruneStaleTasksAndNotifications", () => {
//#then
expect(pruned).toEqual([])
expect(tasks.has(task.id)).toBe(true)
expect(notifications.has(task.parentSessionID)).toBe(false)
expect(notifications.has(task.parentSessionId)).toBe(false)
})
})
+1 -1
View File
@@ -131,7 +131,7 @@ export async function checkAndInterruptStaleTasks(args: {
if (task.status !== "running") continue
const startedAt = task.startedAt
const sessionID = task.sessionID
const sessionID = task.sessionId
if (!startedAt || !sessionID) continue
const sessionStatus = sessionStatuses?.[sessionID]?.type
+12 -12
View File
@@ -29,11 +29,11 @@ export interface TaskProgress {
export type BackgroundTaskAttemptStatus = BackgroundTaskStatus
export interface BackgroundTaskAttempt {
attemptID: string
attemptId: string
attemptNumber: number
sessionID?: string
providerID?: string
modelID?: string
sessionId?: string
providerId?: string
modelId?: string
variant?: string
status: BackgroundTaskAttemptStatus
error?: string
@@ -43,10 +43,10 @@ export interface BackgroundTaskAttempt {
export interface BackgroundTask {
id: string
sessionID?: string
rootSessionID?: string
parentSessionID: string
parentMessageID: string
sessionId?: string
rootSessionId?: string
parentSessionId: string
parentMessageId: string
description: string
prompt: string
agent: string
@@ -101,8 +101,8 @@ export interface LaunchInput {
description: string
prompt: string
agent: string
parentSessionID: string
parentMessageID: string
parentSessionId: string
parentMessageId: string
parentModel?: { providerID: string; modelID: string }
parentAgent?: string
parentTools?: Record<string, boolean>
@@ -119,8 +119,8 @@ export interface LaunchInput {
export interface ResumeInput {
sessionId: string
prompt: string
parentSessionID: string
parentMessageID: string
parentSessionId: string
parentMessageId: string
parentModel?: { providerID: string; modelID: string }
parentAgent?: string
parentTools?: Record<string, boolean>
@@ -23,7 +23,7 @@ function createManager(responses: TaskSnapshot[]) {
describe("waitForTaskSessionID", () => {
test("#given task already has a session id #when waiting #then it returns immediately", async () => {
// given
const manager = createManager([{ sessionID: "ses_ready_123", status: "running" }])
const manager = createManager([{ sessionId: "ses_ready_123", status: "running" }])
// when
const sessionID = await waitForTaskSessionID(manager, "bg_ready")
@@ -37,7 +37,7 @@ describe("waitForTaskSessionID", () => {
const manager = createManager([
{ status: "running" },
{ status: "running" },
{ sessionID: "ses_late_123", status: "running" },
{ sessionId: "ses_late_123", status: "running" },
])
// when
@@ -5,7 +5,7 @@ type SessionWaitTerminalStatus = Extract<BackgroundTaskStatus, "error" | "cancel
type AbortSignalLike = { aborted: boolean }
interface TaskReader {
getTask(taskID: string): { sessionID?: string; status?: BackgroundTaskStatus } | undefined
getTask(taskID: string): { sessionId?: string; status?: BackgroundTaskStatus } | undefined
}
export interface WaitForTaskSessionIDOptions {
@@ -39,8 +39,8 @@ export async function waitForTaskSessionID(
}
const initialTask = manager.getTask(taskID)
if (initialTask?.sessionID) {
return initialTask.sessionID
if (initialTask?.sessionId) {
return initialTask.sessionId
}
if (isTerminalStatus(initialTask?.status)) {
return undefined
@@ -56,8 +56,8 @@ export async function waitForTaskSessionID(
await waitForInterval(intervalMs)
const task = manager.getTask(taskID)
if (task?.sessionID) {
return task.sessionID
if (task?.sessionId) {
return task.sessionId
}
if (isTerminalStatus(task?.status)) {
return undefined