Skip to content

Conversation

@wesm
Copy link
Collaborator

@wesm wesm commented Jan 26, 2026

Summary

Implements real-time streaming output for running jobs in the TUI (#97). Users can now press t on a running job to see live agent output, similar to tail -f.

Features

  • Live output streaming: View agent output in real-time via SSE polling
  • Memory-bounded buffering: Per-job and global limits prevent unbounded memory growth
  • Output normalization: Agent-specific output (Claude, Codex, Gemini) normalized to consistent format
  • Scroll navigation: Arrow keys, page up/down, g to toggle top/bottom
  • Follow mode: Auto-scrolls to bottom when new output arrives (toggle with g)

Key Components

  • OutputBuffer: Thread-safe ring buffer with per-job and global memory limits
  • OutputNormalizer: Converts agent JSON output to display-friendly lines
  • /api/job/output: New endpoint supporting both polling and streaming modes
  • TUI tail view with scroll, follow mode, and job status display

Implementation Details

  • Streaming uses Server-Sent Events with 500ms poll interval
  • Long lines truncated at per-job limit with "..." suffix and discard-until-newline
  • Global memory accounting synchronized to prevent race conditions
  • ANSI-aware text truncation (truncate before styling)
  • Proper cleanup when jobs complete or are canceled

Test plan

  • Run go test ./... - all tests pass
  • Manual testing of tail view navigation (arrows, page up/down, g toggle)
  • Verify no rendering artifacts on scroll
  • Test output preservation when job completes
  • Test memory limits with verbose agent output

Closes #97

🤖 Generated with Claude Code

wesm and others added 13 commits January 26, 2026 06:50
Adds 't' key binding in the TUI to view live output from running agents.
This helps users monitor agent progress and identify when an agent is
stuck or going in circles, allowing early cancellation to save tokens.

New components:
- OutputBuffer: Thread-safe ring buffer for capturing agent output with
  memory limits (512KB per job, 4MB total)
- Output normalizers: Convert agent-specific formats to readable text
  - Claude: Parses stream-json, extracts content, shows [Tool: X] indicators
  - OpenCode: Strips ANSI codes, filters tool call JSON
  - Generic: Default handler for other agents
- API endpoint: GET /api/job/output for polling and streaming modes
- TUI tail view with scrolling, cancel support, and auto-scroll

Key bindings in tail view:
- ↑/↓/j/k: Scroll
- PgUp/PgDn: Page scroll
- g/G: Top/bottom
- x: Cancel the running job
- esc/q: Return to queue

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix tail view content being cleared when job completes (preserve lines
  when server returns empty response after buffer closure)
- Fix inconsistent visibleLines calculation (use m.height - 4 consistently)
- Add tests for AddCommentToJob with all job states (queued, running,
  done, failed, canceled)
- Add API endpoint tests for commenting on in-progress/failed jobs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix potential panic when SessionID < 8 chars in normalize.go
- Enforce maxTotal global memory limit (was tracked but not enforced)
- Add tests for short session IDs and global memory limit

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The status line was showing ranges past actual line count (e.g., "[6-15 of 12 lines]")
because it used linesWritten which includes padding. Now computes displayEnd correctly.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix JSON field mismatch: change OutputLine.Type tag from "type" to
  "line_type" to match what TUI expects (tool/error styling now works)
- Prevent streaming hang for completed jobs: return immediate complete
  response instead of subscribing to non-existent buffer
- Drop oversized lines that exceed per-job limit on their own
- Add test for oversized line handling

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Test invalid job_id returns 400
- Test non-existent job returns 404
- Test missing job_id returns 400
- Test polling mode for running job (has_more=true)
- Test polling mode for completed job (has_more=false)
- Test streaming mode for completed job returns immediate complete
  response (verifies fix for streaming hang)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fix error handling in TestHandleJobOutput tests:
- Check errors from GetOrCreateRepo, GetOrCreateCommit, and EnqueueJob
  instead of ignoring them with blank identifiers
- Capture db.Exec result and verify RowsAffected == 1 to ensure the
  UPDATE actually modified the job status

This prevents silent setup failures from causing misleading test behavior.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix per-job eviction to check global limit before evicting lines,
  preserving existing content when new lines would exceed global limit
- Add TUI tests for tail output preservation when job completes
- Remove completed plan document

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change g key to toggle between top/bottom in tail view (user request)
- Fix stream completion status to reflect actual job status (failed/canceled)
- Cap outputWriter line buffer to maxPerJob to prevent unbounded growth
- Fix ANSI-aware truncation by truncating text before applying styles
- Handle time.Parse errors with fallback to current time

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Move global total update after eviction to prevent concurrent overshoot
- Add discard mode to outputWriter to prevent repeated truncated fragments
- Fix test assertions to use Fatalf before indexing (prevents panic)
- Add tests for multi-write long line discard and per-job eviction edge case

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Return tea.ClearScreen for page up/down and g toggle to force Bubbletea
to do a full repaint, preventing ghosting artifacts when scrolling.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Skip ellipsis when maxLine < 4 (no room for content + "...").
Add test covering maxLine values 3, 4, 5, and 10.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
TestKillDaemonSkipsHTTPForNonLoopback was failing on Windows CI at 202ms
with a 200ms threshold. Increase to 300ms to account for CI variability.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@wesm wesm merged commit 66010ec into main Jan 26, 2026
7 checks passed
@wesm wesm deleted the tail branch January 26, 2026 14:36
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.

support live stream of agent thinking chaing while review is in progress to see if its on the right track

2 participants