The FE derived 'working' purely as activeSession && !pendingWidget, so the
final workflow turn — the only one that ends without a follow-up gate —
left the spinner on forever (observed live: 21592s after F8 approve).
- SessionBridge maps Pi's agent_end -> SSE system_event {event: agent_end}
- PiProcessManager notifies the client (info error + synthetic agent_end)
when the child dies unexpectedly; expected teardowns stay silent
- sessionStore tracks agentActive (on: user entry/text_delta/ui_request,
off: agent_end); AppShell working now requires it; resume sets it
optimistically
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pi 0.73 rpc mode is headless and runs tools without an approval gate; the
removed --approve flag made pi exit with 'Unknown option: --approve', breaking
every session spawn. Spawn args are now just --mode rpc. +regression test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- spawnFor now tears down any existing runtime for the same session id before
the cap check, so resume/respawn neither leaks the old child nor falsely hits
maxPiProcesses
- exit handler is identity-checked (captures rt) so a stale child's late exit
cannot evict a newer runtime
- SSE pending re-emit now sends the full ClientEvent shape
{ type: "ui_request", ui_request } to match hub.publish live events
- tests: same-id respawn replaces runtime (count 1); old child exit does not
evict new runtime; sse-hub re-emit asserts unified shape
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add PiProcessManager.resume(sessionId, tht): reads provider/model/thinking
from tht.sessionShow() and calls spawnFor with those values
- Add POST /sessions/:id/resume route: calls mgr.resume then re-wires
bridge.onClientEvent → hub.publish
- Confirm venv PATH already present in real spawn (no change needed)
- Add e2e test: POST /sessions → SSE receives ui_request via pendingWidget
re-emit → POST /sessions/s1/response → 204 (all over real HTTP against
fake-pi-rpc)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>