dc04733bee
Extract sendPostCompactionContinuation to dedicated file — council members now resume after compaction instead of silently failing. Refresh lastUpdate before async validation in both idle handler and polling path to prevent stale timeout from racing with completion detection. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
112 lines
3.5 KiB
TypeScript
112 lines
3.5 KiB
TypeScript
import { log } from "../../shared"
|
|
import { MIN_IDLE_TIME_MS } from "./constants"
|
|
import type { BackgroundTask } from "./types"
|
|
|
|
function getString(obj: Record<string, unknown>, key: string): string | undefined {
|
|
const value = obj[key]
|
|
return typeof value === "string" ? value : undefined
|
|
}
|
|
|
|
export function handleSessionIdleBackgroundEvent(args: {
|
|
properties: Record<string, unknown>
|
|
findBySession: (sessionID: string) => BackgroundTask | undefined
|
|
idleDeferralTimers: Map<string, ReturnType<typeof setTimeout>>
|
|
recentlyCompactedSessions?: Set<string>
|
|
onPostCompactionIdle?: (task: BackgroundTask, sessionID: string) => void
|
|
validateSessionHasOutput: (sessionID: string) => Promise<boolean>
|
|
checkSessionTodos: (sessionID: string) => Promise<boolean>
|
|
tryCompleteTask: (task: BackgroundTask, source: string) => Promise<boolean>
|
|
emitIdleEvent: (sessionID: string) => void
|
|
}): void {
|
|
const {
|
|
properties,
|
|
findBySession,
|
|
idleDeferralTimers,
|
|
recentlyCompactedSessions,
|
|
onPostCompactionIdle,
|
|
validateSessionHasOutput,
|
|
checkSessionTodos,
|
|
tryCompleteTask,
|
|
emitIdleEvent,
|
|
} = args
|
|
|
|
const sessionID = getString(properties, "sessionID")
|
|
if (!sessionID) return
|
|
|
|
const task = findBySession(sessionID)
|
|
if (!task || task.status !== "running") return
|
|
|
|
if (recentlyCompactedSessions?.has(sessionID)) {
|
|
recentlyCompactedSessions.delete(sessionID)
|
|
log("[background-agent] Skipping post-compaction session.idle:", { taskId: task.id, sessionID })
|
|
onPostCompactionIdle?.(task, sessionID)
|
|
return
|
|
}
|
|
|
|
const startedAt = task.startedAt
|
|
if (!startedAt) return
|
|
|
|
const elapsedMs = Date.now() - startedAt.getTime()
|
|
if (elapsedMs < MIN_IDLE_TIME_MS) {
|
|
const remainingMs = MIN_IDLE_TIME_MS - elapsedMs
|
|
if (!idleDeferralTimers.has(task.id)) {
|
|
log("[background-agent] Deferring early session.idle:", {
|
|
elapsedMs,
|
|
remainingMs,
|
|
taskId: task.id,
|
|
})
|
|
const timer = setTimeout(() => {
|
|
idleDeferralTimers.delete(task.id)
|
|
emitIdleEvent(sessionID)
|
|
}, remainingMs)
|
|
idleDeferralTimers.set(task.id, timer)
|
|
} else {
|
|
log("[background-agent] session.idle already deferred:", { elapsedMs, taskId: task.id })
|
|
}
|
|
return
|
|
}
|
|
|
|
// Refresh lastUpdate to prevent stale timeout from racing with this async validation.
|
|
// Without this, checkAndInterruptStaleTasks can kill the task synchronously
|
|
// while validateSessionHasOutput is still awaiting an API response.
|
|
if (task.progress) {
|
|
task.progress.lastUpdate = new Date()
|
|
}
|
|
|
|
validateSessionHasOutput(sessionID)
|
|
.then(async (hasValidOutput) => {
|
|
if (task.status !== "running") {
|
|
log("[background-agent] Task status changed during validation, skipping:", {
|
|
taskId: task.id,
|
|
status: task.status,
|
|
})
|
|
return
|
|
}
|
|
|
|
if (!hasValidOutput) {
|
|
log("[background-agent] Session.idle but no valid output yet, waiting:", task.id)
|
|
return
|
|
}
|
|
|
|
const hasIncompleteTodos = await checkSessionTodos(sessionID)
|
|
|
|
if (task.status !== "running") {
|
|
log("[background-agent] Task status changed during todo check, skipping:", {
|
|
taskId: task.id,
|
|
status: task.status,
|
|
})
|
|
return
|
|
}
|
|
|
|
if (hasIncompleteTodos) {
|
|
log("[background-agent] Task has incomplete todos, waiting for todo-continuation:", task.id)
|
|
return
|
|
}
|
|
|
|
await tryCompleteTask(task, "session.idle event")
|
|
})
|
|
.catch((err) => {
|
|
log("[background-agent] Error in session.idle handler:", err)
|
|
})
|
|
}
|