feat(booterm): structured pty_exited WS notifications. Plan-validated, impl-validated, code-reviewed green (contracts build clean, contracts test 29/29, booterm + web typecheck clean). wip: in-progress inference/provider refactor (agents.ts, provider.ts, new llama-providers.ts, removed llama-args-validator), plus arena, dispatcher, compaction, schema changes. openspec: pty-exit-notifications complete; x-agent-flags planned (not yet implemented).
3.3 KiB
3.3 KiB
1. Add PtyExitedFrame to WsFrame contract
- 1.1 Add
PtyExitedFrameZod schema topackages/contracts/src/ws-frames.tswith fields:type(literal'pty_exited'),session_id(z.string().min(1).max(64), NOT uuid -- booterm IDs are[a-zA-Z0-9_-]{1,64}),pane_id(z.string().min(1).max(64), same),exit_code(int),last_lines(string array),session_title(nullable optional),session_description(nullable optional),parent_agent(nullable optional),timed_out(boolean) - 1.2 Add
PtyExitedFrameto theWsFrameSchemadiscriminated union array - 1.3 Add
'pty_exited'to theKNOWN_FRAME_TYPESconst array - 1.4 Rebuild
@boocode/contracts(pnpm -C packages/contracts build)
2. Add getLastLines helper to booterm registry
- 2.1 Add
getLastLines(paneId: string, n: number): string[]function toapps/booterm/src/pty/registry.tsthat reads the last N non-empty lines from the ring buffer - 2.2 Add
timedOut?: booleanfield toSessionMetainterface inapps/booterm/src/pty/registry.ts
3. Replace booterm onExit handler with structured frame
- 3.1 In
apps/booterm/src/ws/attach.ts, replace thehandle.onExithandler to: readregistry.get(pid)andgetLastLines(pid, 5)BEFORE any unregister, build a structuredpty_exitedframe withtimed_out: meta?.timedOut ?? false, send it as JSON text to the socket, then close - 3.2 Preserve backward compatibility: the frame
typechanges from'exit'to'pty_exited'-- the old bare exit frame is replaced (not additive)
4. Wire timed_out flag in sweepExpired (pre-wiring)
- 4.1 In
apps/booterm/src/pty/manager.tssweepExpired, setmeta.timedOut = truebefore callingkillSession - 4.2 Do NOT call
registry.unregister()insweepExpired-- let the socketclosehandler do cleanup to avoid the race whereonExitfires after unregister deletes metadata. ThekillSessioncall triggers the tmux exit which triggersonExitwhich reads metadata then closes the socket which triggersunregister.
5. Update web frontend terminal protocol
- 5.1 Add
pty_exitedvariant toServerControlFrameunion inapps/web/src/lib/terminal-protocol.tswith fields matching the contract:session_id,pane_id,exit_code,last_lines,session_title,session_description,parent_agent,timed_out - 5.2 Update
parseServerFrameto recognizetype: 'pty_exited'and return the structured frame
6. Handle pty_exited in useTerminalSocket
- 6.1 In
apps/web/src/hooks/terminal/useTerminalSocket.ts, add a handler forframe?.type === 'pty_exited': write\r\n\x1b[2m[process exited with code ${frame.exit_code}]\x1b[0m\r\nto xterm; iftimed_out: true, write\r\n\x1b[2m[process timed out and was killed]\x1b[0m\r\ninstead; iflast_linesis non-empty, write the last line to xterm as-is - 6.2 Ensure the legacy
{type: 'exit', code: N}handler still works (no regression)
7. Verify
- 7.1 Run
pnpm -C packages/contracts build-- no type errors - 7.2 Run
pnpm -C apps/booterm typecheck-- no type errors - 7.3 Run
npx tsc -p apps/web/tsconfig.app.json --noEmit-- no type errors - 7.4 Grep source for
pty_exited-- should appear in contracts, booterm, and web - 7.5 Run contracts drift test:
pnpm -C packages/contracts test--pty_exitedin KNOWN_FRAME_TYPES matches WsFrameSchema