fix: improve background task completion detection and message extraction (#638)
* 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
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,12 @@ interface EventInput {
|
||||
event: Event
|
||||
}
|
||||
|
||||
/**
|
||||
* Background notification hook - handles event routing to BackgroundManager.
|
||||
*
|
||||
* Notifications are now delivered directly via session.prompt({ noReply })
|
||||
* from the manager, so this hook only needs to handle event routing.
|
||||
*/
|
||||
export function createBackgroundNotificationHook(manager: BackgroundManager) {
|
||||
const eventHandler = async ({ event }: EventInput) => {
|
||||
manager.handleEvent(event)
|
||||
|
||||
@@ -14,6 +14,7 @@ export { createThinkModeHook } from "./think-mode";
|
||||
export { createClaudeCodeHooksHook } from "./claude-code-hooks";
|
||||
export { createRulesInjectorHook } from "./rules-injector";
|
||||
export { createBackgroundNotificationHook } from "./background-notification"
|
||||
export { createBackgroundCompactionHook } from "./background-compaction"
|
||||
export { createAutoUpdateCheckerHook } from "./auto-update-checker";
|
||||
|
||||
export { createAgentUsageReminderHook } from "./agent-usage-reminder";
|
||||
|
||||
Reference in New Issue
Block a user