Skip to content

feat: Phase 9 — Polish & Extensibility#41

Closed
robinojw wants to merge 10 commits intomainfrom
feat/phase-9-polish-extensibility
Closed

feat: Phase 9 — Polish & Extensibility#41
robinojw wants to merge 10 commits intomainfrom
feat/phase-9-polish-extensibility

Conversation

@robinojw
Copy link
Copy Markdown
Owner

Summary

  • Add status bar component showing connection state, thread count, and selected thread
  • Wire app-server lifecycle into TUI startup (spawn, handshake, ReadLoop in Init)
  • Add n key handler to create new threads via app-server JSON-RPC
  • Graceful shutdown with deferred client.Stop() cleanup
  • End-to-end integration test covering full client→store flow

Test Plan

  • All 80 unit tests pass with -race flag
  • Build compiles cleanly (go build ./cmd/dj)
  • Integration test compiles with -tags=integration
  • No files exceed 300-line limit
  • Manual: ./dj launches, connects to app-server, status bar shows "Connected"
  • Manual: Press n to create thread, verify card appears
  • Manual: Ctrl+C exits cleanly without zombie processes

robinojw added 10 commits March 17, 2026 14:15
… init timeout

Three root causes fixed:
- SetProgram modified wrong copy (value type): moved router/bridge
  setup to main.go where program reference is available directly
- 'n' key didn't check client.Running(), causing broken pipe on
  disconnected client
- Init() used context.Background() with no timeout, hanging silently
  if app-server didn't respond; now uses 10s timeout
client.Running() reflects Start() was called, not that the process is
alive. When the app-server dies after Start(), Running() still returns
true and CreateThread writes to a dead pipe.

Fix: track connected bool in AppModel, set only on AppServerConnectedMsg.
Check synchronously in handleRune before spawning any async command.
No race condition possible since Update runs single-threaded.
Status bar now shows three states:
- "Connecting to app-server..." (amber) while Init handshake runs
- "Connected" (green) after successful handshake
- "Disconnected — requires codex CLI (codex app-server)" (red) on failure

Canvas shows "Waiting for app-server connection..." instead of
"Press 'n' to create one" when not connected. Pressing 'n' while
disconnected shows "waiting for app-server — is codex CLI installed?"
The Codex App Server wire format omits "jsonrpc":"2.0" from all
messages, uses different method names (thread/start, thread/archive,
turn/start), different response shapes (nested thread object), and
different notification names (item/started, item/agentMessage/delta,
turn/started, turn/completed). The initialized notification also
requires params: {}. Fixed default args to remove unnecessary
--listen stdio:// flag.
The real Codex CLI uses `codex proto` (not `codex app-server`) and
speaks a Submission Queue / Event Queue protocol over JSONL, not
JSON-RPC 2.0.

- Submissions: {"id":"<string>","op":{"type":"<op_type>",...}}
- Events: {"id":"<string>","msg":{"type":"<event_type>",...}}
- No handshake — server sends session_configured on startup
- Op types: user_turn, interrupt, exec_approval, shutdown
- Event types: session_configured, task_started, task_complete,
  agent_message_delta, exec_command_begin/output_delta/end, error
- Default args changed from ["app-server"] to ["proto"]
- Simplified client (removed Call, pending, Dispatch, Initialize)
- EventRouter routes by msg.type instead of JSON-RPC method
- Bridge maps SQ/EQ events to Bubble Tea messages
- App auto-creates thread on session_configured, no handshake needed
@robinojw robinojw closed this Mar 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant