Uh oh!
There was an error while loading. Please reload this page.
[orchestrator-v2] fix(orchestrator): Harden Grok v2 settlement, background task lifecycle, steer visibility, and images - #3578
Merged
juliusmarminge merged 1 commit intoJul 20, 2026
Conversation
Loading
Uh oh!
There was an error while loading. Please reload this page.
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#3580
Summary
Harden Grok orchestration v2 so root turns complete correctly, steered user
messages stay visible, Stop then continue recovers from a poisoned ACP child,
async
spawn_subagent/monitorproject real lifecycle (not start ACKs), latepost-settle Grok traffic can land as a continuation run instead of being
dropped, screenshot / image attachments actually reach Grok instead of failing
turn start, and interrupt cleanup actually contains and reaps native Grok work
(cgroup kill / owned process ledger) instead of leaving zombie shells.
Follow-up commits soften non-Stop interrupts: steer / restart_active now cancels
and re-prompts the same live session instead of killing the CLI, a steer on a
settled held turn preserves the runtime (running subagents survive), and user
Stop keeps the unchanged hard kill + respawn.
Primary commits on
fix/grok-v2abovet3code/codex-turn-mapping(thefeature story reviewers should start with):
Follow-up commits may land on top of those two. Treat them as reviewable
delta fixes, not a rewrite of the primary story. Integration snapshots should
still merge the whole branch, not tip-only cherry-pick.
The shared provider-continuation plumbing (C0) already landed in the base branch
alongside the Claude and Codex consumers.
What you'll notice
Steered messages stay visible
When you steer an in-flight Grok run, your message no longer flashes away
between server events. The timeline keeps the optimistic user row until the
committed
user_messageturn item lands.Steering also works while a run is held open for background work: pre-fix,
steering a settled turn that deferred finalize was holding (a subagent still
running) froze the thread at Working, the steer text never reached Grok, and
later messages queued forever.
Grok multi-tool turns finish with tools and final text
Grok often emits a short assistant preamble, then tools, then more text.
Speculative idle settlement used to complete the turn after a short quiet and
call
session/cancel, which froze T3 projection while the CLI kept working.This PR disables Grok idle settle and terminalizes from real provider signals.
Stop then continue recovers instead of
task_already_runningAfter you Stop a Grok run mid-turn, the next message restarts the Grok ACP child
before the recovery prompt, clearing zombie shell state.
Interrupt cleanup contains native work
Stop is not only an ACP cancel. On Linux with cgroup v2, delegated Grok launches
join a unique child cgroup and interrupt uses
cgroup.killas the authoritativecleanup. When cgroup containment is unavailable, the owned-process ledger is the
fallback (Darwin process groups / Windows
taskkillretained). ACP cancel isgeneration-aware; unexpected transport death, double-fork descendants, and
teardown races are covered by focused tests.
Steers no longer kill the Grok process
The interrupt matrix after the follow-up commits:
session/loadon the next turnsession/cancel+ same-session re-prompt; native context survives without asession/loadreplayGrok's
session/cancelis detach-and-continue: the CLI reaps the foregroundshell but re-runs the interrupted command as a background task, and only the
model can kill it (via
kill_command_or_subagent). Product call: the steeredmodel, not the client, decides whether that detached work dies, matching the
CLI's own behavior. The adapter tracks
_x.ai/task_backgrounded/task_completedinto background-task mutations and tombstones model-killedtasks so wake machinery stays truthful, and a direct Stop quarantines any late
lifecycle noise from the stopped run.
spawn_subagent and monitor project real work
Async
spawn_subagentno longer completes on the background ACK alone. T3 bindsthe child id, keeps the subagent running, shows durable
subagent.resultwhenavailable, and hydrates from TaskOutput /
get_command_or_subagent_outputwhenneeded.
monitorstays running through the start ACK, streams as commandexecution, and completes from Bash exit codes or monitor-end text.
Late “will report when finished” can wake again
After the root turn settles (including when a subagent card already has a
result), Grok may still stream a late root follow-up (tool fetch + confirmation)
without a new
session/promptfrom T3. That traffic is buffered and attached toa provider continuation run so the UI can go idle, then show Working again
with the follow-up, instead of dropping frames after finalize.
Long-running monitors initially made this path spam: Grok narrates every
monitor event (~3s apart, wider than the ~2s finalize quiet window), and each
burst opened its own continuation run with its own synthetic "Background task
completed." user row (4+ observed live). Continuation offers are now fully
suppressed while a tracked background task is still running: per-event agent
commentary AND tool re-reports buffer (Grok re-reports a running monitor as
Bash frames that already carry
exit_code: 0mid-stream, so a terminal-lookingtool status is not evidence the task ended), and exactly one continuation run
opens once the monitor actually ends, draining the buffered commentary.
In-turn monitors do not open a redundant wake
When Grok waits on a monitor and reports the listing in the same root turn
(no post-settle wait), late CLI re-reports and residual agent chatter must not
open synthetic "Background task completed." continuation runs. The follow-up
commit marks those tools handled mid-turn and suppresses residual agent-message
offers once handled work is done and nothing is still running.
Settled turns still get the injected monitor report
When Grok settles first ("I'll report when the monitor ends") and the CLI later
injects a monitor-event turn, the report could vanish: injected turns emit no
turn_completed, and the deferred-finalize debounce could fire while Grok wasstill generating the report, dropping the chunk into an already finalized turn
(thread
a8e8b0a9run 5). The run now holds until the report streams (or a 25ssafety elapses), so the listing lands as a normal assistant message.
Screenshots and image attachments work on Grok
Attaching a screenshot to a Grok turn no longer fails immediately with a generic
provider turn start error. Grok’s ACP
initializestill reportspromptCapabilities.image: false, but the agent accepts and vision-processesimage content blocks. The Grok flavor now opts in via
supportsImagePromptssoattachments are sent as ACP image blocks instead of being rejected before
session/prompt.Problem and Fix
user_messageturn item landssession/cancelfor Grok; terminalize only on real provider signalssession/promptstayed open without a reliable complete pathturn_completed(and legacy prompt-complete) for the same prompt idtask_already_runningspawn_subagentshowed completed in ~1ms on the background ACK; child work never boundmonitorstayed spinning forever or never streamed outputpersistent: truemonitors from deferred-finalize holds; they continue post-settleimage: falserestartRuntimeOnEveryInterrupt), discarding native session context and any running subagents even when the user was only redirectingsession/cancel+ same-session re-prompt; a steer on a settled held turn skips the cancel entirely and preserves the runtimesession/canceldetaches and re-runs the interrupted command as a background task; nothing tracked that work, so wake machinery could misread pending state_x.ai/task_backgrounded/task_completedinto background-task mutations; tombstone tasks the model kills viakill_command_or_subagent(notask_completedis emitted for kills)activeSessionIdstill points at the stopped session until the next turn respawns)stoppedRunQuarantineis set, and ignore child-session mutations that must not gate root wake statesession/cancel, which Grok answers by killing detached background workpromptWireSettleddeferred succeeds the moment the prompt RPC resolves; settled classification also accepts wire settlement and skips the canceltask.result) and fell back to showing the spawn promptassistantTextinto the terminal result with the same rule as live emissionrun_interrupt_resultwhose parentrun_interrupt_requestonly hard Stop emits, leaving a dangling parent link and letting timelines fold a continuing steered run as interruptedexit_code: 0re-report, reading as a clean success exitDefensive Fixes
provider-turn.restartcould swallow a failed replacement startcontinuationRequestedafter a failed or skipped dispatch blocked further offers and pinned idlestartTurnactiveTurnwas null and in-turn-handled task ids remained<monitor-event>chunks dropped later progress / end notices<monitor>block, and end notice in the chunktask_idsTaskOutput hydrated only the first idExit Code: -1read as successrunningcleared the hydration hold earlyEffect.sleep, whichit.effectTestClock freezes, hanging the CI Test job with no outputt3-acp-<pid>-*cgroup lease directories accumulated when a server process died before cleanuppopulated 0) before creating a new leaseproviderTurnslookup whose null fallback was actually the child's thread idhasRunInterruptRequestcallback threaded likeshouldFinalizeRun)run_interrupt_resultonly when unpaired (norun_interrupt_requestfor the same run); legacy plain-steer results in existing DBs stay hiddenhasUnpairedRunInterruptRequest: emits only when the request item exists and the derived result item is absent_x.ai/session/prompt_completesettled the first pending prompt for the session, which is the wrong one whenever more than one is pending<subagent_meta>/<subagent_result>blocks, stripping left nothing and the code fell back to the raw string, leaking machine tags into subagent cards and monitor outputresultFromGetOutputToolreturns null when the cleaned output is emptyuser_messageturn items, which hydrate later than the projection, so an existing thread briefly unlocked env/branch overrides on load and right after a run settledactiveMessageCounttakes the max of committed turn-item ids andserverProjection.messages.length, which hydrates atomically with the projectionV2EventTimelineRowearly-returns lifecycle items (including subagents) toV2LifecycleRow, leaving an unreachable subagent presentation branch that review bots kept flaggingv2EventPresentationsubagent branch and its unreachable styling conditions; the live V2LifecycleRow path is untouchedRoot-matched terminal signal (live evidence)
Current Grok CLI emits completion as:
{ "method": "_x.ai/session/update", "params": { "sessionId": "<root session>", "update": { "sessionUpdate": "turn_completed", "prompt_id": "t3-xai-prompt-N", "stop_reason": "end_turn" } } }Matching rules:
sessionIdequals the active ACP sessionprompt_idequals the pending T3-injected id (t3-xai-prompt-N)task-completed-*prompt ids (background tools)_x.ai/session/prompt_completefor older buildsDo not treat idle silence or subagent completions as root terminalization.
Server mechanics
settleRootTurnWhenIdle: false); speculative idle complete stays disabledsession/promptRPC result or root-matchedturn_completed/ legacyprompt_completestartTurnspawns a fresh Grok process. Non-Stop interrupts no longer restart the runtime (restartRuntimeOnEveryInterruptdropped from the Grok flavor)running+ binds child; TaskOutput / get_command hydrates; durablesubagent.resultwhen availableactiveTurnis null; offer only on completion-like evidence (terminal tool status after normalization, or agent text with no tracked background task still running); monitor-ended reminders andTaskOutputcompletion frames are the genuine end signals that clear tracked task ids (ended ids are tombstoned against straggler re-adds); attach mode skipssession/promptand drains the buffersupportsImagePrompts: trueoverrides ACPpromptCapabilities.image:falseso screenshots are sent as image content blocks_x.ai/task_backgrounded/task_completedbecome background-task mutations;kill_command_or_subagenttombstones the killed task; mutations are dropped while the Stop quarantine is active or when they belong to a child sessionDesign decisions (interrupt complexity; safe to simplify later)
Two choices below are deliberate and reviewable. If maintainers want less
surface area, each has a known downgrade path and a concrete fidelity trade-off.
1. Linux cgroup join wrapper (production spawn path)
Current landing: before
execof the real Grok/ACP binary on Linux, spawnis rewritten through
process.execPath+ a small inline-escript(
wrapCommandForLinuxCgroup). The shim writes its PID into the leasedcgroup.procs, verifies/proc/self/cgroup, strips wrapper env, thenprocess.execves into the real command so membership sticks to the same PID.This is only used when cgroup v2 containment is available; otherwise spawn is
unwrapped and interrupt falls back to the owned-process ledger (reduced
guarantee). Mac/Windows never use this path (process group /
taskkill).Why not PATH
node: the wrapper uses the running Electron/Node binary(
process.execPath+ELECTRON_RUN_AS_NODEunder desktop), so packagedAppImages do not require a separate system Node install.
Preferred cleanup if inline
-eis too opaque: replace the string with asmall checked-in
acp-cgroup-enter.mjsnext to the server (sameexecPathinvocation, no temp files, no C toolchain for production). Same semantics;
easier to review and diff.
Heavier alternative: checked-in C helper binary shipped in the desktop
artifact. Clearer argv contract and no
execvedependency on the ElectronNode build, but adds a real packaging/build matrix. Not chosen for this PR.
Complexity you can drop: if cgroup pre-exec join is too much, ship only the
ledger / process-group fallback on Linux too. Trade-off: double-fork /
reparented descendants can escape kill (the portable ledger comment already
calls this out). Headless interrupt packs would get flakier on adversarial
shell shapes.
2. Pthread spawn test helper (
.c, test-only)Current landing: keep
apps/server/scripts/acp-thread-spawn-helper.c. OneLinux live test compiles it with
cc -pthreadto fork a child from anon-leader pthread, then asserts
childrenOf(parent)still finds thatchild. That exercises the production scan of every
/proc/<pid>/task/<tid>/childrenentry (main-thread-only scans miss this).Local: if
ccis missing, the test soft-skips (suite stays green).CI: Blacksmith Ubuntu runners mirror GitHub’s Ubuntu image and often
already have
gcc, but that is not a repo contract. The Test job thereforeinstalls
build-essentialsoccis present and the fixture runs insteadof silently no-oping:
Simpler alternative (drop complexity): replace the helper with a plain
Node/
.mjsspawn("sleep", …)and rename the test to “finds a spawned child.”No compiler, always runs. Fidelity loss: a regression that only walks the
main task’s children file (not every thread) can still pass a normal spawn
fixture and only fail the pthread case. Other double-fork / cgroup-kill tests
remain; only this multi-thread discovery edge case goes soft.
Not a good substitute without rethinking the assertion: pure JS cannot
faithfully create “non-leader pthread then fork” without native code.
Validation
vp check: format clean on changed files; monorepo lint warnings only (knownVite+ stdout panic can flake the full wrapper after lint)
vp run typecheck: cleanvp test apps/web/src/components/ChatView.logic.test.ts: passvp test apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.test.ts:pass (includes image capability override)
vp test apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts: pass(wake-evidence filters, continuation offer gating, the monitor start ACK
normalization case, steer-during-hold interrupt finalize, subagent lineage
carry-over across a steering restart, injected-report hold, interrupt
cleanup fixtures, soft mid-prompt cancel + same-session re-prompt, direct
Stop dropping late background task mutations, and the production Grok flag
set still hard-killing and respawning on user Stop)
vp test apps/server/src/provider/acp/AcpSessionRuntime.processTree.test.ts:pass (cgroup / process-tree interrupt evidence; pthread fixture needs
cc,provided in CI via
build-essentialon the Test job; soft-skips locally ifccis absent)vp test apps/server/src/orchestration-v2/ProviderSessionManager.test.ts:pass (idle pin with C0)
vp test apps/server/src/provider/acp/XAiAcpExtension.test.ts: pass (asyncspawn, TaskOutput, Monitor variant cases)
live-scenarios, private serve against thisbranch / restacked AppImage):
grok-interrupt-fast: direct Stop +restart_active(the restart_activepack rewritten for soft semantics: process retained, one initialize, no
session/load, cancel + re-prompt, codeword carry-forward provingsame-session context; detach handling recorded as evidence, with a
branch-conditional
-detachvariant)grok-interrupt-soak: held subagent, mid-prompt steer during a subagenthold (new pack sampling the cancel-kills-subagent edge), duplicate Stop,
leader exit, queued follow-up (scheduler gate); re-run green on the C4 tip
Grok thread titled from scenario 1 "Live-test direct Stop…", 2026-07-17 on
orchestrator-v2 AppImage @ current snapshot; projection checked in userdata
DB for thread
6c9994b7-bb04-4fb7-a9a8-e95688a6b7d7):INTERRUPTED_OK: passRESTART_ACTIVE_OK: passQUEUE_CONTRAST_OK: pass(one late-queue practice attempt finished the sleep without a queue; retry
with queue while Working passed)
DUPLICATE_STOP_OK: passwhile queued; headless
grok-interrupt-queued-followupremains theautomated contract)
hides Stop while the card is open)
HOLD_FOLLOW_OK: pass (subagentinterrupted, no
HOLD_SUB_DONE/ forbidden shell marker)scenario numbering; 4–5 were blocked on desktop Stop UI as above.
16092557-…, 2026-07-17, scenarios 1 and 7 only):INTERRUPTED_OK: pass (sleep interrupted; noSHOULD_NOT_FINISH_CMDoutput)HOLD_FOLLOW_OK: pass (subagentinterrupted; no
HOLD_SUB_DONE/ forbidden shell marker)trees/orchestrator-v2):steer-during-hold, screenshot attach as previously reported on this PR
production Grok flag set (now also covering carryover subagent
terminalization, streamed-text preservation, and the parent thread id pin),
settled-soft wire-race test, cgroup lease sweep unit test; full server suite
(1369 tests) and typecheck green locally, and the CI Test job green on the
branch tip after each round
items while hard Stop keeps the linked pair; AcpAdapterV2 unit + it.live
tests pin no
exitCodeon interrupted commands (mock agent now re-reportsGrok's mid-stream
exit_code: 0) and real exit codes on completed ones;message_steering grok/cursor fixtures and the selection-restart integration
test assert zero
run_interrupt_*on supersede; full server suite (1373tests) and typecheck green locally
(seeded request + interrupted terminal emits the paired result with the
request as parent); existing supersede/hard-stop pins unchanged; full
server suite (1374 tests) and typecheck green locally
unpaired-superseded hidden / terminal visible; new RunExecutionService
test pins the already-paired supersede emitting nothing; full server
suite (1375 tests),
packages/sharedandpackages/client-runtimesuites, and typecheck green locally across all three packages
prompt_complete no-op (two pending prompts, neither settles) and the
meta-only get_output yielding null result / empty appendOutput; full
server suite (1377 tests), server + web typecheck, and
vp checkgreen locally
thread
b6eb3501-…): steer-while-held, direct Stop, steer mid-command,subagent-hold Stop, and steer-then-Stop orphan containment all pass;
projection DB audit confirms exactly one linked request/result pair per
hard Stop, zero interrupt items on the three steers, zero interrupted
commands with an
exitCode, and the R3 streamed-text + parent-pin behaviorintact
Out of Scope
Client presentation and desktop chrome that are not part of this Grok
settlement / interrupt PR. Called out so maintainers can track separate UI work
if desired; projection and run status still settle correctly.
+N previous tool calls(interrupted tools, Ask, etc.). Mobile keeps neutral tool rows in the work group; desktop filters neutrals out. Presentation difference only.run_interrupt_request/run_interrupt_resultfor Stops from desktop (verified in userdata). On mobile, those Interrupted rows are not shown for desktop-initiated Stops—not merely collapsed under+N previous tool calls(expanding the fold still does not surface them the way a live mobile Stop does). Live mobile Stops can show anX Interruptedwork-log row and aYou stopped after …fold. Incomplete multi-client / mobile lifecycle parity; not a missing server interrupt.--- Run interrupted ---dividerrun_interrupt_resultas a full-width system divider and leaves interrupted turns unfolded. Mobile never uses that divider chrome.workEntryPreviewprefersdetailover file_change filenamespromptId/ low-risk Grok session edge casesinProgressuntil native outputKnown limitations
After a real terminal signal, the hung
session/promptfiber may still logInterruptwhen cancelled locally; runs complete with full content (lognoise).
Builds that emit neither prompt RPC completion nor
turn_completed/prompt_completecan still under-settle (no silent idle cancel fallback).After Stop, a tool in the old shell may finish briefly before process kill
(
interruptPromptOnCancel: false); recovery still succeeds on the freshchild.
Subagent child-session tool calls (tools inside the child) are not fully
projected as nested tool items; assistant text + final hydration / result card
are covered.
Subagent wall time is provider-variable. A trivial “sleep ~5s then print a
token” general-purpose spawn is often ~10–20s end-to-end, but the child agent
can occasionally take much longer (minute-scale outliers observed). A long
Working state usually reflects child runtime, not the ~2s deferred-finalize
quiet window.
The root model may poll
get_command_or_subagent_outputwith longtimeouts, so the root run’s Working state can cover the entire child wait even
when the subagent card is the durable success signal.
Non-persistent background work still holds Working. Deferred finalize
keeps the root run open while a spawned subagent or a non-persistent monitor
tool row is still running. Persistent monitors (
persistent: true) nolonger hold that gate; they settle the root and continue via post-settle
continuation. Claude-style “go idle then reopen when background finishes” for
non-persistent work is still future work. Content that arrives during a hold
(CLI-injected monitor report) is delivered by the injected-report hold, so
this is about run status staying Working, not about lost output.
Continuation dispatch is by
threadId(the request also carriesdriverand
providerThreadIddetail for future scoping).Buffered per-event commentary drains into the continuation run as several
small assistant messages (one per burst). That is faithful to what the
provider emitted; coalescing the display would be UI-side follow-up work.
Soft-interrupt detach tracking is best-effort. Grok's
session/canceldetaches and re-runs the interrupted foreground command to full duration as
a background task; no client-side ACP method kills it (only the model can,
via
kill_command_or_subagent)._x.ai/task_backgroundedcan arrive late(~16s observed) or not at all (the model's poll-and-report shape emits
none), so lifecycle tracking begins only when a notification arrives.
Residual frames in that gap are accepted best-effort noise per the product
call above; live packs record detach handling as evidence rather than
pinning it.
Fast steers can trigger a model-side no-op flood. Steering within a
couple of seconds of a reply while background work is still held (send a
short message right after the settle reply with a subagent still running)
can push Grok into emitting no-op
run_terminal_command(true) callsabout once per second until interrupted; the same loop shows up without
steering after monitor-event injections (154 no-ops observed in one live
thread, 207 in another). The steer text and background completions still
land once the loop breaks or the run is stopped. This is xAI model
behavior, not orchestrator state; recovery is Stop.
Desktop UI: no Stop while a message is queued or an approval/question card
is open. See Out of Scope (mobile can still run those paths; headless
covers queue-then-Stop).
Mobile vs desktop interrupt presentation. Trailing work-log rows after a
mobile Stop, missing
--- Run interrupted ---dividers, and no Interruptedlifecycle chrome on mobile for desktop-initiated Stops (not just collapsed)
are client parity gaps only; server still projects interrupt items. See
Out of Scope.
Grok exact-reply EOS token leakage (model, not T3). On short
“reply with exactly TOKEN, no tools” recovery prompts, Grok sometimes
returns
TOKENplus a literal end-of-sequence marker. Observed 2026-07-16on desktop AppImage after subagent-hold Stop:
grok-4.5.general-purposesubagent that runssleep 30 && echo HOLD_SHOULD_NOT_MATTERand should returnHOLD_SUB_DONE; root waits for the subagent.Reply with exactly HOLD_FOLLOW_OK. No tools.HOLD_FOLLOW_OK<|eos|>(ASCII<|eos|>appended). Projection stored that string verbatim; subagent was
correctly
interruptedwith no forbidden shell marker.Other exact-reply markers in the same session (
INTERRUPTED_OK,RESTART_ACTIVE_OK,DUPLICATE_STOP_OK) were clean. Treat as xAI/Grokmodel leakage suitable for upstream Grok team follow-up if desired; not a
T3 interrupt/projection bug.
Stuck Working after hard interrupt on a poisoned session is reduced, not
fully gone. This PR hardens the common Stop races (idempotent interrupt,
finalize-before-kill, missing-session restart, sticky continuation flag,
process-group reap). Residual risk remains if a held turn keeps a
background/lineage entry that can no longer receive a terminal signal after
restart (no overall force-finalize cap on that path). Fresh threads and the
headless steer pack are the reliable recovery; candidate follow-ups are a
bounded hold cap and reconciling carried subagent / background-tool state on
interrupt.
Note
Harden Grok v2 prompt settlement, process containment, background task lifecycle, steer visibility, and image prompt support
makeXAiPromptCompletionRuntimein XAiAcpExtension.ts to race both_x.ai/session/prompt_completeand_x.ai/session/update(turn_completed) notifications for prompt settlement, interrupting in-flight prompt RPC fibers on external completion.AcpSessionRuntime, with platform-specific termination (cgroup kill on Linux, process-group on others); Grok runtime enables detached process-group on all platforms and descendant tracking on Linux.AcpAdapterV2with soft/hard interrupt semantics (soft preserves runtime on settled turns; hard tears down with process-group termination on Stop), post-settle wake buffering for continuation runs, native transport request correlation with admission/acknowledgement sequencing, and deferred finalization for background tool/subagent activity.isOrchestrationV2SupersededInterruptin orchestrationV2Timeline.ts to only hiderun_interrupt_resultitems when unpaired (no matchingrun_interrupt_request), correcting steer visibility in superseded attempts.acpSupportsImagePromptsto allow Grok flavor to enable image prompt blocks regardless of capability negotiation.acpRootTurnIsIdlealways returns false, unconditionally disabling speculative idle settlement; callers that relied on local idle settle will not trigger completion from that path.Macroscope summarized 542c87b.
Note
Low Risk
Changes are limited to CI workflow and test/mock scripts; no production orchestration or runtime paths are modified in this diff.
Overview
Adds CI test infrastructure so ACP process-tree live tests always compile their pthread fixture, and expands the mock ACP agent plus a small C spawn helper to exercise Grok-style interrupt, monitor, and post-settle behavior in automated tests.
The Test job now installs
build-essentialand verifiesccis onPATH, soAcpSessionRuntime.processTreetests that compileacp-thread-spawn-helper.crun in CI instead of soft-skipping when no compiler is present.acp-mock-agent.tsgains many env-flag scenarios: cancel-as-detach (task_backgrounded/task_completed), residual session/permission/elicitation callbacks after teardown, long-running terminal tools with optional realbash/sleepchildren (PID file, TERM-ignoring, detached session), duplicate empty successful Bash updates, post-settle and in-turn monitor + TaskOutput flows (including late duplicate updates), form/URL elicitation, and hang/exit variants around permission and cancel.acp-thread-spawn-helper.cis a test-only program that forkssleepfrom a worker pthread and writestid child_pidto a path, so process-tree discovery can be asserted for children not visible from the main thread alone.Reviewed by Cursor Bugbot for commit 542c87b. Bugbot is set up for automated code reviews on this repo. Configure here.