#60: live turn progress (activity line + token count + OutputChannel stream + cancel) #61

Merged
benstull merged 7 commits from s60-live-progress into main 2026-06-26 11:53:11 +00:00
Owner

Implements #60 — surface Claude's live output/progress during the opaque "asking Claude…" status. Built from the graduated Solution Design coauthoring-live-progress.md (brainstorming session 0055).

What

Both Ask-Claude entry points (editSelection + the preview askClaude) ran the turn as one opaque promise. Now, while a turn runs:

  • the existing notification shows a live activity linethinking…writing… (N chars)running <tool>…, plus a running token count (· 1.2k tokens);
  • a shared "Cowriting: Claude" OutputChannel streams the full assistant text (auto-reveals without stealing focus on first delta, gated by the new cowriting.liveProgress.revealOutput setting; appends per-turn);
  • the notification is cancellableagent.abort() → "cancelled", proposing nothing.

The @cline/sdk already streamed all of this (assistant-text-delta, tool-*, usage-updated) via agent.subscribe() — the extension just never subscribed.

How (architecture)

  • src/turnProgress.ts (new, pure) — reduceTurnProgress(state, AgentRuntimeEvent) → snapshot, the unit-tested core. No vscode, AgentRuntimeEvent type-only.
  • runEditTurn gains onProgress + AbortSignal — subscribes/abort-wires; result path untouched; stays vscode-free.
  • src/liveProgressUi.ts (new) — host relay (notification line + OutputChannel).
  • Both call sites relay snapshots; the preview's injectable editTurn seam widens to accept opts? (back-compat — existing stubs ignore it).

Invariants

INV-43 (liveTurn/turnProgress stay vscode-free) · INV-44 (purely additive — proposals unchanged) · INV-45 (no new network/webview surface) · INV-46 (pure reducer) · INV-47 (cancel reflects, proposes nothing).

Tests

237 unit (new turnProgress reducer + runEditTurn progress/cancel) + 2 new host-E2E (progress is additive; abort proposes nothing). Self-reviewed by a subagent; fixed a Medium (pre-aborted-signal no-op) + Low (throwing relay) it found.

Note: the host-E2E undo-suite flake (F10 #38, broken executeCommand("undo") in the local .vscode-test, per sessions 0048/0049) is pre-existing and unrelated.

🤖 Generated with Claude Code

Implements **#60** — surface Claude's live output/progress during the opaque "asking Claude…" status. Built from the graduated Solution Design `coauthoring-live-progress.md` (brainstorming session 0055). ## What Both Ask-Claude entry points (`editSelection` + the preview `askClaude`) ran the turn as one opaque promise. Now, while a turn runs: - the existing **notification** shows a live **activity line** — `thinking…` → `writing… (N chars)` → `running <tool>…`, plus a running **token count** (`· 1.2k tokens`); - a shared **"Cowriting: Claude" OutputChannel** streams the full assistant text (auto-reveals without stealing focus on first delta, gated by the new `cowriting.liveProgress.revealOutput` setting; appends per-turn); - the notification is **cancellable** → `agent.abort()` → "cancelled", proposing nothing. The `@cline/sdk` already streamed all of this (`assistant-text-delta`, `tool-*`, `usage-updated`) via `agent.subscribe()` — the extension just never subscribed. ## How (architecture) - **`src/turnProgress.ts`** (new, pure) — `reduceTurnProgress(state, AgentRuntimeEvent) → snapshot`, the unit-tested core. No `vscode`, `AgentRuntimeEvent` type-only. - **`runEditTurn`** gains `onProgress` + `AbortSignal` — subscribes/abort-wires; result path untouched; stays `vscode`-free. - **`src/liveProgressUi.ts`** (new) — host relay (notification line + OutputChannel). - Both call sites relay snapshots; the preview's injectable `editTurn` seam widens to accept `opts?` (back-compat — existing stubs ignore it). ## Invariants INV-43 (liveTurn/turnProgress stay vscode-free) · INV-44 (purely additive — proposals unchanged) · INV-45 (no new network/webview surface) · INV-46 (pure reducer) · INV-47 (cancel reflects, proposes nothing). ## Tests 237 unit (new `turnProgress` reducer + `runEditTurn` progress/cancel) + 2 new host-E2E (progress is additive; abort proposes nothing). Self-reviewed by a subagent; fixed a Medium (pre-aborted-signal no-op) + Low (throwing relay) it found. > Note: the host-E2E undo-suite flake (`F10 #38`, broken `executeCommand("undo")` in the local `.vscode-test`, per sessions 0048/0049) is pre-existing and unrelated. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
benstull added 7 commits 2026-06-26 11:51:42 +00:00
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
editSelection (extension.ts) and preview askClaude (trackChangesPreview.ts)
both run the turn cancellable, relay TurnProgress via the shared LiveProgressUi,
and thread onProgress/signal through runEditAndPropose. The EditTurn seam widens
to accept opts (back-compat: existing stubs ignore it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review findings (Medium+Low): a signal already aborted before runEditTurn is
called could not be honored by agent.abort() (the SDK AbortController isn't
created until run()), so the turn ran to completion — now it throws before
run() (INV-47). And a throwing onProgress relay is swallowed so observability
can never fail the turn (INV-44). Strengthened the unit + E2E to prove both.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
benstull merged commit 644885c6ec into main 2026-06-26 11:53:11 +00:00
Sign in to join this conversation.