* fix: background task completion detection and silent notifications - Fix TS2742 by adding explicit ToolDefinition type annotations - Add stability detection (3 consecutive stable polls after 10s minimum) - Remove early continue when sessionStatus is undefined - Add silent notification system via tool.execute.after hook injection - Change task retention from 200ms to 5 minutes for background_output retrieval - Fix formatTaskResult to sort messages by time descending Fixes hanging background tasks that never complete due to missing sessionStatus. * fix: improve background task completion detection and message extraction - Add stability-based completion detection (10s min + 3 stable polls) - Fix message extraction to recognize 'reasoning' parts from thinking models - Switch from promptAsync() to prompt() for proper agent initialization - Remove model parameter from prompt body (use agent's configured model) - Add fire-and-forget prompt pattern for sisyphus_task sync mode - Add silent notification via tool.execute.after hook injection - Fix indentation issues in manager.ts and index.ts Incorporates fixes from: - PR #592: Stability detection mechanism - PR #610: Model parameter passing (partially) - PR #628: Completion detection improvements Known limitation: Thinking models (e.g. claude-*-thinking-*) cause JSON Parse errors in child sessions. Use non-thinking models for background agents until OpenCode core resolves this. * fix: add tool_result handling and pendingByParent tracking for resume/external tasks Addresses code review feedback from PR #638: P1: Add tool_result type to validateSessionHasOutput() to prevent false negatives for tool-only background tasks that would otherwise timeout after 30 minutes despite having valid results. P2: Add pendingByParent tracking to resume() and registerExternalTask() to prevent premature 'ALL COMPLETE' notifications when mixing launched and resumed tasks. * fix: address code review feedback - log messages, model passthrough, sorting, race condition - Fix misleading log messages: 'promptAsync' -> 'prompt (fire-and-forget)' - Restore model passthrough in launch() for Sisyphus category configs - Fix call-omo-agent sorting: use time.created number instead of String(time) - Fix race condition: check promptError inside polling loop, not just after 100ms
86 lines
2.7 KiB
TypeScript
86 lines
2.7 KiB
TypeScript
import type { BackgroundManager } from "../../features/background-agent"
|
|
|
|
interface CompactingInput {
|
|
sessionID: string
|
|
}
|
|
|
|
interface CompactingOutput {
|
|
context: string[]
|
|
prompt?: string
|
|
}
|
|
|
|
/**
|
|
* Background agent compaction hook - preserves task state during context compaction.
|
|
*
|
|
* When OpenCode compacts session context to save tokens, this hook injects
|
|
* information about running and recently completed background tasks so the
|
|
* agent doesn't lose awareness of delegated work.
|
|
*/
|
|
export function createBackgroundCompactionHook(manager: BackgroundManager) {
|
|
return {
|
|
"experimental.session.compacting": async (
|
|
input: CompactingInput,
|
|
output: CompactingOutput
|
|
): Promise<void> => {
|
|
const { sessionID } = input
|
|
|
|
// Get running tasks for this session
|
|
const running = manager.getRunningTasks()
|
|
.filter(t => t.parentSessionID === sessionID)
|
|
.map(t => ({
|
|
id: t.id,
|
|
agent: t.agent,
|
|
description: t.description,
|
|
startedAt: t.startedAt,
|
|
}))
|
|
|
|
// Get recently completed tasks (still in memory within 5-min retention)
|
|
const completed = manager.getCompletedTasks()
|
|
.filter(t => t.parentSessionID === sessionID)
|
|
.slice(-10) // Last 10 completed
|
|
.map(t => ({
|
|
id: t.id,
|
|
agent: t.agent,
|
|
description: t.description,
|
|
status: t.status,
|
|
}))
|
|
|
|
// Early exit if nothing to preserve
|
|
if (running.length === 0 && completed.length === 0) return
|
|
|
|
const sections: string[] = ["<background-tasks>"]
|
|
|
|
// Running tasks section
|
|
if (running.length > 0) {
|
|
sections.push("## Running Background Tasks")
|
|
sections.push("")
|
|
for (const t of running) {
|
|
const elapsed = Math.floor((Date.now() - t.startedAt.getTime()) / 1000)
|
|
sections.push(`- **\`${t.id}\`** (${t.agent}): ${t.description} [${elapsed}s elapsed]`)
|
|
}
|
|
sections.push("")
|
|
sections.push("> **Note:** You WILL be notified when tasks complete.")
|
|
sections.push("> Do NOT poll - continue productive work.")
|
|
sections.push("")
|
|
}
|
|
|
|
// Completed tasks section
|
|
if (completed.length > 0) {
|
|
sections.push("## Recently Completed Tasks")
|
|
sections.push("")
|
|
for (const t of completed) {
|
|
const statusEmoji = t.status === "completed" ? "✅" : t.status === "error" ? "❌" : "⏱️"
|
|
sections.push(`- ${statusEmoji} **\`${t.id}\`**: ${t.description}`)
|
|
}
|
|
sections.push("")
|
|
}
|
|
|
|
sections.push("## Retrieval")
|
|
sections.push('Use `background_output(task_id="<id>")` to retrieve task results.')
|
|
sections.push("</background-tasks>")
|
|
|
|
output.context.push(sections.join("\n"))
|
|
}
|
|
}
|
|
}
|