feat(atlas): add background task session tracking with retry scheduling

- Add background-launch-session-tracking.ts to persist delegated sessions
- Add task-context.ts for task context resolution utilities
- Modify idle-event.ts to schedule retries when background tasks are running
- Update tool-execute-after.ts to integrate session tracking
- Add comprehensive tests for background task retry behavior

🤖 Generated with assistance of OhMyOpenCode
This commit is contained in:
YeonGyu-Kim
2026-04-05 14:59:03 +09:00
parent 90407a9789
commit ccfc54ab38
6 changed files with 454 additions and 53 deletions
+12 -46
View File
@@ -4,16 +4,17 @@ import {
getPlanProgress,
getTaskSessionState,
readBoulderState,
readCurrentTopLevelTask,
upsertTaskSessionState,
} from "../../features/boulder-state"
import { log } from "../../shared/logger"
import { isCallerOrchestrator } from "../../shared/session-utils"
import { syncBackgroundLaunchSessionTracking } from "./background-launch-session-tracking"
import { collectGitDiffStats, formatFileChanges } from "../../shared/git-worktree"
import { shouldPauseForFinalWaveApproval } from "./final-wave-approval-gate"
import { HOOK_NAME } from "./hook-name"
import { DIRECT_WORK_REMINDER } from "./system-reminder-templates"
import { isSisyphusPath } from "./sisyphus-path"
import { resolvePreferredSessionId, resolveTaskContext } from "./task-context"
import { extractSessionIdFromMetadata, extractSessionIdFromOutput, validateSubagentSessionId } from "./subagent-session-id"
import {
buildCompletionGate,
@@ -23,50 +24,7 @@ import {
} from "./verification-reminders"
import { isWriteOrEditToolName } from "./write-edit-tool-policy"
import type { PendingTaskRef, SessionState } from "./types"
import type { ToolExecuteAfterInput, ToolExecuteAfterOutput, TrackedTopLevelTaskRef } from "./types"
function resolvePreferredSessionId(currentSessionId?: string, trackedSessionId?: string): string {
return currentSessionId ?? trackedSessionId ?? "<session_id>"
}
function resolveTaskContext(
pendingTaskRef: PendingTaskRef | undefined,
planPath: string,
): {
currentTask: TrackedTopLevelTaskRef | null
shouldSkipTaskSessionUpdate: boolean
shouldIgnoreCurrentSessionId: boolean
} {
if (!pendingTaskRef) {
return {
currentTask: readCurrentTopLevelTask(planPath),
shouldSkipTaskSessionUpdate: false,
shouldIgnoreCurrentSessionId: false,
}
}
if (pendingTaskRef.kind === "track") {
return {
currentTask: pendingTaskRef.task,
shouldSkipTaskSessionUpdate: false,
shouldIgnoreCurrentSessionId: false,
}
}
if (pendingTaskRef.reason === "explicit_resume") {
return {
currentTask: readCurrentTopLevelTask(planPath),
shouldSkipTaskSessionUpdate: true,
shouldIgnoreCurrentSessionId: true,
}
}
return {
currentTask: pendingTaskRef.task,
shouldSkipTaskSessionUpdate: true,
shouldIgnoreCurrentSessionId: true,
}
}
import type { ToolExecuteAfterInput, ToolExecuteAfterOutput } from "./types"
export function createToolExecuteAfterHandler(input: {
ctx: PluginInput
@@ -116,15 +74,23 @@ export function createToolExecuteAfterHandler(input: {
if (toolInput.callID) {
pendingTaskRefs.delete(toolInput.callID)
}
const boulderState = readBoulderState(ctx.directory)
const isBackgroundLaunch = outputStr.includes("Background task launched") || outputStr.includes("Background task continued")
|| outputStr.includes("Background delegate launched")
|| outputStr.includes("Background agent task launched")
if (isBackgroundLaunch) {
await syncBackgroundLaunchSessionTracking({
ctx,
boulderState,
toolInput,
toolOutput,
pendingTaskRef,
metadataSessionId,
})
return
}
if (toolOutput.output && typeof toolOutput.output === "string") {
const boulderState = readBoulderState(ctx.directory)
const worktreePath = boulderState?.worktree_path?.trim()
const verificationDirectory = worktreePath ? worktreePath : ctx.directory
const gitStats = collectGitDiffStats(verificationDirectory)