Files
boocode/openspec/changes/pty-exit-notifications/tasks.md
indifferentketchup b18de2a331 chore: snapshot working tree - pty_exited notifications + in-flight inference WIP
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).
2026-06-14 12:48:47 +00:00

3.3 KiB

1. Add PtyExitedFrame to WsFrame contract

  • 1.1 Add PtyExitedFrame Zod schema to packages/contracts/src/ws-frames.ts with 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 PtyExitedFrame to the WsFrameSchema discriminated union array
  • 1.3 Add 'pty_exited' to the KNOWN_FRAME_TYPES const 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 to apps/booterm/src/pty/registry.ts that reads the last N non-empty lines from the ring buffer
  • 2.2 Add timedOut?: boolean field to SessionMeta interface in apps/booterm/src/pty/registry.ts

3. Replace booterm onExit handler with structured frame

  • 3.1 In apps/booterm/src/ws/attach.ts, replace the handle.onExit handler to: read registry.get(pid) and getLastLines(pid, 5) BEFORE any unregister, build a structured pty_exited frame with timed_out: meta?.timedOut ?? false, send it as JSON text to the socket, then close
  • 3.2 Preserve backward compatibility: the frame type changes 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.ts sweepExpired, set meta.timedOut = true before calling killSession
  • 4.2 Do NOT call registry.unregister() in sweepExpired -- let the socket close handler do cleanup to avoid the race where onExit fires after unregister deletes metadata. The killSession call triggers the tmux exit which triggers onExit which reads metadata then closes the socket which triggers unregister.

5. Update web frontend terminal protocol

  • 5.1 Add pty_exited variant to ServerControlFrame union in apps/web/src/lib/terminal-protocol.ts with fields matching the contract: session_id, pane_id, exit_code, last_lines, session_title, session_description, parent_agent, timed_out
  • 5.2 Update parseServerFrame to recognize type: '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 for frame?.type === 'pty_exited': write \r\n\x1b[2m[process exited with code ${frame.exit_code}]\x1b[0m\r\n to xterm; if timed_out: true, write \r\n\x1b[2m[process timed out and was killed]\x1b[0m\r\n instead; if last_lines is 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_exited in KNOWN_FRAME_TYPES matches WsFrameSchema