2026-02-19 01:32:30 +01:00
import { tool , type ToolDefinition } from "@opencode-ai/plugin"
import type { BackgroundOutputManager , BackgroundOutputClient } from "./clients"
import { BACKGROUND_WAIT_DESCRIPTION } from "./constants"
import { delay } from "./delay"
import { formatTaskResult } from "./task-result-format"
import { formatTaskStatus } from "./task-status-format"
const DEFAULT_TIMEOUT_MS = 120 _000
const MAX_TIMEOUT_MS = 600 _000
const TERMINAL_STATUSES = new Set ( [ "completed" , "error" , "cancelled" , "interrupt" ] )
function isTerminal ( status : string ) : boolean {
return TERMINAL_STATUSES . has ( status )
}
export function createBackgroundWait ( manager : BackgroundOutputManager , client : BackgroundOutputClient ) : ToolDefinition {
return tool ( {
description : BACKGROUND_WAIT_DESCRIPTION ,
args : {
task_ids : tool.schema.array ( tool . schema . string ( ) ) . describe ( "Task IDs to monitor — returns when ANY one reaches a terminal state" ) ,
timeout : tool.schema.number ( ) . optional ( ) . describe ( "Max wait in ms. Default: 120000 (2 min). The tool returns immediately when any task finishes, so large values are fine." ) ,
} ,
2026-02-20 21:10:54 +01:00
async execute ( args : { task_ids : string [ ] ; timeout? : number } , toolContext? : unknown ) {
const abort = ( toolContext as { abort? : AbortSignal } | undefined ) ? . abort
2026-02-19 01:32:30 +01:00
const taskIds = args . task_ids
if ( ! taskIds || taskIds . length === 0 ) {
return "Error: task_ids array is required and must not be empty."
}
const timeoutMs = Math . min ( args . timeout ? ? DEFAULT_TIMEOUT_MS , MAX_TIMEOUT_MS )
const alreadyTerminal = findFirstTerminal ( manager , taskIds )
if ( alreadyTerminal ) {
return await buildCompletionResult ( alreadyTerminal , manager , client , taskIds )
}
const startTime = Date . now ( )
while ( Date . now ( ) - startTime < timeoutMs ) {
2026-02-20 21:10:54 +01:00
if ( abort ? . aborted ) {
return buildProgressSummary ( manager , taskIds , true )
}
2026-02-19 01:32:30 +01:00
await delay ( 1000 )
const found = findFirstTerminal ( manager , taskIds )
if ( found ) {
return await buildCompletionResult ( found , manager , client , taskIds )
}
}
return buildProgressSummary ( manager , taskIds , true )
} ,
} )
}
function findFirstTerminal ( manager : BackgroundOutputManager , taskIds : string [ ] ) : { id : string ; status : string } | undefined {
for ( const id of taskIds ) {
const task = manager . getTask ( id )
if ( ! task ) continue
if ( isTerminal ( task . status ) ) {
return { id , status : task.status }
}
}
return undefined
}
async function buildCompletionResult (
completed : { id : string ; status : string } ,
manager : BackgroundOutputManager ,
client : BackgroundOutputClient ,
allIds : string [ ] ,
) : Promise < string > {
const task = manager . getTask ( completed . id )
if ( ! task ) return ` Task was deleted: ${ completed . id } `
const taskResult = task . status === "completed"
? await formatTaskResult ( task , client )
: formatTaskStatus ( task )
const summary = buildProgressSummary ( manager , allIds , false )
const remaining = allIds . filter ( ( id ) = > ! isTerminal ( manager . getTask ( id ) ? . status ? ? "" ) )
const lines = [ summary , "" , "---" , "" , taskResult ]
if ( remaining . length > 0 ) {
const idList = remaining . map ( ( id ) = > ` " ${ id } " ` ) . join ( ", " )
lines . push ( "" , ` ** ${ remaining . length } task ${ remaining . length === 1 ? "" : "s" } still running.** Call background_wait again with task_ids: [ ${ idList } ] ` )
} else {
lines . push ( "" , "**All tasks complete.** Proceed with synthesis." )
}
return lines . join ( "\n" )
}
function buildProgressSummary ( manager : BackgroundOutputManager , taskIds : string [ ] , isTimeout : boolean ) : string {
const done = taskIds . filter ( ( id ) = > isTerminal ( manager . getTask ( id ) ? . status ? ? "" ) )
const total = taskIds . length
const header = isTimeout
? ` ## Still Waiting: [ ${ progressBar ( done . length , total ) } ] ${ done . length } / ${ total } `
: ` ## Council Progress: [ ${ progressBar ( done . length , total ) } ] ${ done . length } / ${ total } `
const lines = [ header , "" ]
for ( const id of taskIds ) {
const t = manager . getTask ( id )
if ( ! t ) {
lines . push ( ` - \` ${ id } \` — not found ` )
continue
}
const marker = isTerminal ( t . status ) ? "received" : "waiting..."
lines . push ( ` - ${ t . description || t . id } — ${ marker } ` )
}
if ( isTimeout ) {
const remaining = taskIds . filter ( ( id ) = > ! isTerminal ( manager . getTask ( id ) ? . status ? ? "" ) )
if ( remaining . length > 0 ) {
const idList = remaining . map ( ( id ) = > ` " ${ id } " ` ) . join ( ", " )
lines . push ( "" , ` **Timeout — tasks still running.** Call background_wait again with task_ids: [ ${ idList } ] ` )
}
}
return lines . join ( "\n" )
}
function progressBar ( done : number , total : number ) : string {
const filled = "#" . repeat ( done )
const empty = "-" . repeat ( total - done )
return ` ${ filled } ${ empty } `
}