Wire RemoteAgentInvocation to use ExecutionLifecycleService.createExecution()
so remote agents can be backgrounded (Ctrl+B), subscribed to, and killed
through the same lifecycle system as shell commands.
- Create virtual execution on execute(), report ID via setExecutionIdCallback
- Stream output deltas via appendOutput() so lifecycle subscribers see live data
- Separate streaming into detached processStream() that settles the lifecycle
- Support backgrounding: handle.result resolves with backgrounded=true,
stream continues running, returns BackgroundExecutionData
- Support kill: lifecycle onKill aborts the stream via AbortController
With the idle wake-up listener in place, shell background
completions can now use 'inject' to feed output back into the
model conversation when the agent is idle.
When the agent is idle and a backgrounded execution completes, the
completion output is now automatically submitted as a new turn
instead of being silently dropped. Uses the same InjectionService
listener pattern as user steering hints.
Non-shell executions rely on the onBackground listener to register
in the UI panel. Adding their PID to backgroundedPids before firing
the listener caused them to be skipped and disappear from the UI.
Shell background completions are set to silent until the idle
wake-up listener is in place, otherwise injections get buffered
but never consumed when the agent is idle.
settleExecution now calls injectionService.addInjection() directly
when a backgrounded execution completes, removing the bridge useEffect
from AppContainer. The UI just wires the injection service once at init
via setInjectionService().
Introduce inject/notify/silent completion behaviors that control what
happens when a backgrounded execution completes:
- inject: full output injected into conversation, auto-dismiss from UI
- notify: short pointer message injected (e.g. log file path), auto-dismiss
- silent: nothing injected, stays in Ctrl+B until manually dismissed
Shell commands use 'notify' (output saved to log file), remote agents
will use 'inject' (full output reinjected). Default is 'silent' when
no formatInjection callback is provided.
Add onBackground event to ExecutionLifecycleService that fires when any
execution is moved to the background. The CLI subscribes to this event
and automatically registers background tasks in the UI — no per-tool
changes needed.
Any tool that calls ExecutionLifecycleService.createExecution() or
attachExecution() now automatically gets Ctrl+B support. Shell-specific
concerns (PTY log files) stay in ShellExecutionService.
Forward setExecutionIdCallback through SubAgentInvocation so agents
can expose their execution ID to the scheduler for backgrounding.
Route registerBackgroundTask and dismissBackgroundTask through
ExecutionLifecycleService instead of ShellExecutionService for
agnostic subscribe/onExit/kill support.
Collapse shellExecutionConfig and setExecutionIdCallback into a single
optional ExecuteOptions object on ToolInvocation.execute(). This avoids
forcing every tool implementation to accept shell-specific parameters
just to reach later positional args.
Tests cover XML tag wrapping with safety instruction, ordering
(background completions before user hints), and source filtering
to prevent background output from leaking into user hint getters.
Wrap background completion output in <background_output> XML tags with
inline instructions to treat as data, consistent with <user_input> tags
used for user steering hints.
Guard listener iteration in InjectionService.addInjection and
ExecutionLifecycleService.settleExecution with try/catch so a throwing
listener doesn't block subsequent listeners or crash the caller.
Rename getUserHints/getUserHintsAfter/getLatestHintIndex to
getInjections/getInjectionsAfter/getLatestInjectionIndex with optional
source filter so bg completions don't get formatted as user hints.
Swap unshift ordering so bg completions appear before user hints in the
message — the model sees context before the user's reaction to it.
Remove unused getLastUserHintAt().
Remove legacy onUserHint/offUserHint/addUserHint methods. All callers
now use addInjection(text, source) and onInjection/offInjection with
source-based filtering where needed.
The agent loop in local-executor now listens via onInjection (all sources)
instead of onUserHint (steering only), picking up background completions
between turns. This removes the separate bg completion useEffect, refs,
state, and callback from AppContainer entirely.
Wire ExecutionLifecycleService.setInjectionService() in Config constructor
so backgrounded executions inject directly via settleExecution instead of
routing through a useEffect bridge in AppContainer.