Repository navigation
Tracking: terminal output rendering — work streams for agent team pickup #152
Description
Architecture (established from relay source + CLI capabilities)
Relay supports two spawn transports. The default for Claude/Codex is PTY, but transport: 'headless' (piped stdin/stdout) works for any CLI — relay just forwards the chunks:
| Runtime | CLIs | How structured output works |
|---|---|---|
pty |
Any CLI (default for Claude, Codex, Grok) | ANSI escape sequences → xterm renders raw |
headless |
Any CLI (default for OpenCode) | piped stdin/stdout → relay forwards chunks via worker_stream |
Claude — no relay changes needed
Pear passes --output-format stream-json --include-partial-messages as spawn args. Relay spawns Claude as a piped subprocess. Claude outputs NDJSON to stdout. Relay forwards chunks via worker_stream → broker:pty-chunk IPC. Pear renderer parses NDJSON and formats for xterm (V1) or custom UI (V2).
NDJSON shape to parse:
{ "event": { "type": "content_block_delta", "delta": { "type": "text_delta", "text": "..." } } }Codex — relay has native app-server support
codex_session.rs in the relay broker uses .stdin(Stdio::piped()).stdout(Stdio::piped()) — Codex's app-server protocol is already understood by relay. Spawn with transport: 'headless' and relay handles the rest.
OpenCode — headless via HTTP SSE (different mechanism)
OpenCode runs opencode serve --port N and exposes /event SSE. Events: message.delta, file.changed, permission.requested.
Grok — PTY only (for now)
No structured output format available from xAI yet. Tracked in #141.
What "no PTY" means for user-facing features
| Feature | PTY (current) | Headless (Claude/Codex/OpenCode) |
|---|---|---|
Slash commands (/clear, /compact) |
✓ typed into PTY stdin | ✓ written to piped stdin — Claude/Codex honor them |
| Image/screenshot paste | ✓ xterm clipboard → PTY | ✗ no clipboard path — needs explicit upload UI |
| Trust/permission dialogs | ✓ relay auto-handles | Claude stream-json: no interactive prompts; --dangerously-skip-permissions or MCP trust needed |
Relay agent-relay view attach |
✓ PTY snapshot | ✗ headless has no view |
Image paste is the main regression risk when moving off PTY. For V1, this is acceptable if documented. V2 can add an explicit image attach button.
Work streams
Stream 1 — Merge immediately (safe, no dependencies) ✅
- PR feat: improve terminal visual quality #142 — terminal visual quality (lineHeight, letterSpacing, bar cursor, 10k scrollback, tab fade)
- PR feat: add OpenCode to spawn dialogs and empty-state UI #143 — OpenCode in spawn dialogs and empty-state UI (UI-only)
- PR feat: add Grok as first-class harness #138 — Grok as first-class harness (PTY, correct for now)
Merge order: #142 and #143 first, then #138 (resolve trivial 3-way conflict in SpawnAgentDialog, TerminalPane, SpawnAgentCli).
Stream 2 — Claude headless + NDJSON rendering (V1)
Goal: spawn Claude with --output-format stream-json, parse NDJSON chunks in the renderer, display clean formatted text in xterm. No raw JSON visible.
Steps:
broker.ts: route Claude spawns throughspawnCli({ transport: 'headless', args: ['--output-format', 'stream-json', '--include-partial-messages'] }).- Track
runtimeper agent session inBrokerManager. - Renderer: for headless Claude agents, parse
broker:pty-chunkNDJSON before passing to xterm — extractcontent_block_delta.text_delta.text, emit clean text only. - Handle trust/permissions: either
--dangerously-skip-permissionsflag or confirm MCP trust approach in relay.
Note: PR #139 has the right shape for this work but has two blockers — relay 8.3.0 lockfile not updated, and the NDJSON parsing (step 3) is absent so raw JSON hits xterm. Fix both before merging.
Stream 3 — Codex headless + app-server rendering (V1)
Relay's broker already understands Codex's app-server protocol (codex_session.rs). Steps:
broker.ts: route Codex spawns throughspawnCli({ transport: 'headless' }).- Parse Codex app-server event chunks in renderer:
AgentMessageDelta,FileChangePatchUpdated,ApplyPatchApprovalParams. - Emit formatted text to xterm.
Can be done in same PR as Stream 2 or separately.
Stream 4 — OpenCode headless routing + SSE rendering (V1)
PR #140 has the right idea but depends on PR #139. Once #139 is fixed and merged:
- Route OpenCode through
spawnCli({ transport: 'headless' }). - Parse
message.deltaSSE chunks → formatted xterm text.
Stream 5 — V2 native Pear UI (all headless CLIs)
No xterm for headless agents. Replace terminal pane with React components:
message.delta→ message bubblesfile.changed→ diff cardspermission.requested→ native approval dialogs- Claude tool use events → collapsible tool call cards
Separate PR track, not blocking V1.
Stream 6 — Grok headless (blocked on xAI, tracked in #141)
Nothing to build until xAI ships structured output. When it arrives, follows Stream 2 pattern.
PRs at a glance
| PR | Title | Status | Action |
|---|---|---|---|
| #138 | Grok harness | Open, ready | Merge after #142/#143 |
| #139 | relay 8.3 + headless Claude/Codex routing | ⛔ DO NOT MERGE yet | Fix two blockers: (1) run npm install and commit lockfile, (2) add NDJSON parsing in renderer before merging |
| #140 | OpenCode headless routing | ⛔ DO NOT MERGE yet | Depends on #139 being fixed first |
| #141 | Grok headless | Open issue | Blocked on xAI |
| #142 | Terminal visual tweaks | Open, ready | Merge first |
| #143 | OpenCode in spawn UI | Open, ready | Merge first |
| #146 | Cloud workspace key | Draft | Separate track (#125) |
| #132 | Slack integration fixes | Open | Separate track |
Agent team pickup checklist
- Merge feat: improve terminal visual quality #142
- Merge feat: add OpenCode to spawn dialogs and empty-state UI #143
- Merge feat: add Grok as first-class harness #138 (resolve 3-way conflict)
- Fix ⛔ DO NOT MERGE — feat: upgrade @agent-relay to 8.3.0, wire headless spawn routing and runtime tracking #139: run
npm install, commit lockfile; add NDJSON renderer parsing (Stream 2 step 3) - Fix ⛔ DO NOT MERGE — feat: wire OpenCode into spawn dialogs and empty-state UI (headless routing) #140: rebase on fixed ⛔ DO NOT MERGE — feat: upgrade @agent-relay to 8.3.0, wire headless spawn routing and runtime tracking #139; add OpenCode SSE rendering
- Merge ⛔ DO NOT MERGE — feat: upgrade @agent-relay to 8.3.0, wire headless spawn routing and runtime tracking #139 → merge ⛔ DO NOT MERGE — feat: wire OpenCode into spawn dialogs and empty-state UI (headless routing) #140
- Stream 3: Codex app-server rendering (can be added to ⛔ DO NOT MERGE — feat: upgrade @agent-relay to 8.3.0, wire headless spawn routing and runtime tracking #139 or separate PR)
- Stream 5: V2 native UI (separate PR track, not blocking)
- Watch feat: enable headless spawn for Grok once relay confirms streaming-json support #141 for xAI announcement
Activity