fork of prime agent with changes i want but are not upstreamable probqbly (and some fixes)

fix(coding-agent): repair corrupt REPL protocol frames (#1839) master

* refactor(coding-agent): unify kernel shutdown * docs(coding-agent): carry over shutdown rationale comments * fix(coding-agent): repair corrupt REPL protocol * fix(coding-agent): cancel the final kernel snapshot before disposal cleanup * fix(coding-agent): block new executions from splicing ahead of the final dispose snapshot flushSnapshotForDispose captured the execution queue while the kernel state was still 'running', so a concurrent execute() (which has no per-cell timeout) could enqueue after the capture; captureSnapshot then queued behind it and its timeout never started, hanging dispose() and shutdown({snapshot:true}) forever. Claim the flush before capturing the queue: non-internal requests are rejected while the final snapshot is flushing, keeping every wait on the dispose path bounded. * fix(coding-agent): close protocol-repair races and bound the repair - Gate queued requests behind an in-flight repair: a request whose queue slot opens during a repair releases the slot and requeues after the repair, so queued executes/snapshots can no longer run on the empty replacement kernel ahead of restoreState (and overwrite its result or clobber the last good snapshot). - Race the repair wait in execute() with opts.signal so an aborted call returns its aborted result promptly instead of pending through repair. - Bound the repair's restore with REPAIR_RESTORE_TIMEOUT_MS so a stalled replacement cannot wedge protocolRepairPromise (and every later execute) forever; a timed-out restore discards the replacement and returns to clean idle. - Stop respawn-looping when the replacement kernel itself emits a corrupt frame during repair: discard it and settle at clean idle instead of superseding into an unbounded chain of repairs. * fix(coding-agent): re-bootstrap repaired kernels and gate busy-waiting cells - A protocol-repaired kernel restored only the user namespace, so live handles (rlm, bash, skills) were missing and the next cell failed with NameErrors. KernelManagerOptions gains bootstrapCode (the provisioner passes its buildRlmBootstrapCode output); the repair re-runs it after restore, bounded like the restore, and discards the replacement to clean idle when it fails. - The repair requeue gate only ran right after taking the queue slot, so a cell busy-waiting on an interrupted execution when corruption arrived could proceed onto the unrestored replacement while blocking the repair's restore behind its slot. The gate now sits after the busy-wait (still exempting the repair's own requests), covering both windows. - Fold the repeated kill-to-clean-idle teardown into killChildToIdle and rename REPAIR_RESTORE_TIMEOUT_MS to REPAIR_STEP_TIMEOUT_MS since it now bounds restore and bootstrap. * fix(coding-agent): join concurrent teardowns into one final snapshot flush Concurrent dispose() and shutdown({ snapshot: true }) (e.g. a session dispose racing the SIGINT handler) each ran flushSnapshotForDispose: the first finisher's finally cleared flushingSnapshotForDispose while the second was still flushing, dropping the execution guard early and enqueueing a duplicate final snapshot behind the first. Memoize the in-flight flush so every teardown joins the same bounded flush. * fix(coding-agent): close protocol repair gaps for schema-invalid frames, startup chunks, and rebootstrap - Fail the protocol on JSON object frames that violate the frame schema (unknown event kind; done/host_request without a string id) instead of silently dropping them, which left the active request unsettled forever. The protocol version handshake is exact, so unknown kinds are corruption, not forward compatibility. - A corrupt frame sharing one stdout chunk with ready no longer lets the startup mark the corrupt child running: failProtocolFrame records the error while starting and doStart fails the start. - A fresh kernel started lazily after a discarded repair re-runs the configured runtime bootstrap before serving user code, so rlm/bash/skills handles are never silently missing. * test(coding-agent): reconcile chain-collapse suites and cover teardown racing the lazy re-bootstrap - Point the graceful-shutdown flush spies at captureSnapshot: the merged final flush (kernel-snapshot-dispose-timeout) snapshots through the bounded captureSnapshot path, not snapshotState, so the old spies let real snapshot requests hit the mocked wire and leaked fake timers. - The superseded repair's booting replacement is now hard-killed without a protocol shutdown (single-kernel-shutdown only drains running kernels), so it may die before journaling its spawn; assert spawn-count stability after teardown instead of an exact boot count. - New composition test: a teardown racing an in-flight lazy re-bootstrap stays bounded, spawns nothing new, and settles the raced cell. * fix(coding-agent): re-check the final-flush guard after the re-bootstrap wait - An external execute admitted before the final dispose flush could park on the lazy re-bootstrap, then take its queue slot after the flush captured pendingExecutions - splicing between the captured queue and the final snapshot and unbounding the teardown. Re-check the flush guard right after the ensure await; unit test proves the re-check is load-bearing. - Deflake the teardown-vs-re-bootstrap composition test: the fake runtime journals its spawn before ready, so poll isRunning before the teardown to race the re-bootstrap deterministically instead of the boot. * fix(coding-agent): close bot-review gaps in protocol repair and teardown seams - Terminal frames (done/host_request) with an EMPTY string id now fail the protocol like id-less ones: the runtime mints uuid hex ids and echoes the host's uuids, so an empty id is corruption that previously slipped past the validator into the silent-drop hang. - An execution aborted while waiting on the lazy re-bootstrap settles promptly: the wait races the abort signal (same pattern as waitForProtocolRepair) and the aborted request returns before parking on the queue slot behind the still-running bootstrap. - A failed kernel startup awaits its own teardown (final snapshot flush included) before surfacing the error, so a replacement provisioner gated on this dispose can no longer race the still-flushing kernel over the same snapshot files. - restart() during the final dispose flush is rejected instead of taking a queue slot and joining the owning shutdown, which could deadlock the flush's snapshot behind the held slot. * test(coding-agent): cover empty-string host_request ids in the frame validator Kills the surviving mutant that relaxed the host_request half of the non-empty-id requirement back to typeof-only: an empty-id host_request now has a corruption test mirroring the empty-id done one (protocol fails and the kernel is repaired instead of the awaiting cell hanging). * fix(coding-agent): restore the saved namespace on lazily reprovisioned kernels After a discarded repair whose failure never implicated the snapshot (the repair start died, or only its bootstrap failed), the lazily started fresh kernel re-ran the bootstrap but silently abandoned the user's saved namespace forever. Track restore separately: killChildToIdle marks both pending; the two snapshot-culprit discard paths (clean restore failure, replacement corrupted mid-repair) clear the restore flag so a poisoned snapshot is never retried; the lazy path restores one-shot best-effort (ordinary startup semantics) before bootstrapping, and a teardown's final snapshot never triggers reprovisioning. Restore-attempt journaling in the fake runtime pins the no-retry and no-double-restore semantics. * fix(coding-agent): protect the saved snapshot from unrestored kernels; phase-aware corruption attribution - The final dispose flush skips its snapshot while pendingRestore is set: a kernel that never restored the saved namespace must not overwrite it (the on-disk snapshot is strictly fresher). One interleaving-independent condition instead of joining the reprovision promise, which would reintroduce the 30-60s teardown stall the teardown guard prevents. The lazy one-shot clear moves after the corruption check so the flag also covers a flush racing an in-flight lazy restore. - The repair give-up path attributes corruption by phase: performRestore clears pendingRestore on success, so the flag still being set means the corruption struck at or before the restore (snapshot stays the prime suspect, loop-safe); corruption strictly after a successful restore keeps the namespace restorable at the cost of at most one bounded restore per later attempt.