feat(dev): supervise every runtime in headless mode - #2138
Conversation
--mode headless without --agent now runs all of the project's runtimes at once with attributed output, matching the supervised behavior browser mode gets from the Inspector. One agent crashing leaves the others running, and the command exits non-zero only when nothing is left running and something failed. --agent keeps the direct single-runtime path where a crash fails the command.
|
Claude Security Review: no high-confidence findings. (run) |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## refactor #2138 +/- ##
============================================
+ Coverage 97.19% 97.22% +0.02%
============================================
Files 471 471
Lines 28731 28740 +9
============================================
+ Hits 27925 27942 +17
+ Misses 806 798 -8 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
AgentCore Harness Review
Verdict: Looks good
The change cleanly extends headless mode to supervise every runtime by reusing DevSupervisor instead of restricting to a single agent. I traced the new loop in src/handlers/project/dev/index.ts (lines 225–240) against DevSupervisor.launch/pump/events, and the phase/event ordering holds up:
entry.phase = "starting"is set synchronously inlaunch()before any await, so the initialPromise.allSettled(runtimes.map(start))guarantees no agent looks "idle" when the loop first iterates.- Failure events are pushed after
entry.phase = "failed", sosnapshot()on each iteration is consistent with what the user just saw rendered. - The
every(!starting && !running)+some(failed)predicate correctly avoids tearing down peers when one agent dies but others are still running, and only escalates toSilentCLIErrorwhen the whole cohort is down and at least one failed. - Single-runtime
--mode headlesswithout--agentnow falls into this path and exits gracefully when the lone runtime stops — a strict improvement over the previous hard-error. - Cleanup: since the throw only fires once no agent is still
starting/running, there are no orphan children whenSilentCLIErrorpropagates and the finally block closes the collector (verified bystate.closed === 1in the new test).
Test coverage looks appropriate: stayingRunner is a good addition and models a real dev server better than captureRunner for these cases, the removed --mode headless requires --agent and --port with several runtimes is rejected assertions are correctly folded into the parameterized cases / new tests, and the new tests exercise attributed output, per-runtime OTEL env rewrite, partial-failure survival, and total-failure exit.
One minor UX nit worth being aware of (not a blocker): when both agents fail nearly simultaneously and both failure events land in the supervisor queue before the outer loop drains it, the break can fire after the first yield and the second agent's failure line is never rendered. Users still get non-zero exit and the first failure message, but if you wanted symmetry you could drain remaining failed-phase events from supervisor.snapshot() before throwing. Fine to defer.
Nothing here that needs to change before merging.
| const starts = Promise.allSettled( | ||
| runtimes.map((runtime) => supervisor.start(runtime.name)), | ||
| ); | ||
| for await (const { agentName, event } of supervisor.events()) { |
There was a problem hiding this comment.
I think normal Ctrl-C gets reported as a crash in this supervised headless path. The production process runner rejects with the child signal's UserCancellationError, so the supervisor emits Agent 'orders' crashed: Operation cancelled by user before the command exits 130. Could the supervisor treat an error from an already-aborted child signal as a normal stop? A test where the runner throws input.signal.reason on abort would cover the production behavior better than stayingRunner, which returns cleanly.
What
--mode headlesswithout--agentnow supervises every runtime in the project, streaming attributed[name]output to the terminal, the same behavior browser mode gets from the Inspector. Follows the discussion on #2086: multi-agent terminal supervision (useful for A2A testing) was the interim behavior onrefactorbetween #2041 and #2086, and this restores it as a first-class mode instead of requiring--agent.--mode headless(no--agent): all runtimes start eagerly, output is[name]-attributed, one agent crashing leaves the others running, and the command exits non-zero only when nothing is left running and something failed.--mode headless --agent <name>: unchanged, the direct single-runtime path where a crash fails the command with a real exit code (scripts and CI rely on this).Verification
project dev --mode headlessstarted both agents eagerly on distinct ports with[bob]/[alice]attributed output, and SIGTERM tore both down.refactor(DataTable.tsx, usePagedList.tsx).