From 20e13bba4d093095f13e98ed6df60c3d58f7c455 Mon Sep 17 00:00:00 2001 From: BenStullsBets Date: Fri, 26 Jun 2026 04:34:10 -0700 Subject: [PATCH] feat(#60): liveProgressUi host relay + revealOutput setting (INV-45) Co-Authored-By: Claude Opus 4.8 (1M context) --- package.json | 10 +++++++ src/liveProgressUi.ts | 65 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 src/liveProgressUi.ts diff --git a/package.json b/package.json index dbd4c74..354d627 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,16 @@ "onStartupFinished" ], "contributes": { + "configuration": { + "title": "Cowriting", + "properties": { + "cowriting.liveProgress.revealOutput": { + "type": "boolean", + "default": true, + "description": "When Claude is editing, reveal the \"Cowriting: Claude\" output channel (without stealing focus) as soon as Claude starts producing text, so you can read the output as it streams." + } + } + }, "commands": [ { "command": "cowriting.showClineSdkInfo", diff --git a/src/liveProgressUi.ts b/src/liveProgressUi.ts new file mode 100644 index 0000000..d693c41 --- /dev/null +++ b/src/liveProgressUi.ts @@ -0,0 +1,65 @@ +/** + * liveProgressUi.ts — host-side relay of TurnProgress snapshots to VS Code UI + * (#60, spec coauthoring-live-progress.md §3.4). The ONLY surfaces are the + * existing withProgress notification (an activity line) and a dedicated + * OutputChannel streaming the full assistant text — no new network/webview + * surface (INV-45). vscode-only; all pure logic lives in turnProgress.ts. + */ +import * as vscode from "vscode"; +import { formatProgressLine, type TurnProgressSnapshot } from "./turnProgress"; + +const CHANNEL_NAME = "Cowriting: Claude"; + +export interface TurnUi { + /** Pass to runEditTurn's opts.onProgress. */ + onProgress: (snapshot: TurnProgressSnapshot) => void; + /** Pass to runEditTurn's opts.signal — fired when the user cancels the notification. */ + signal: AbortSignal; +} + +export class LiveProgressUi { + readonly channel: vscode.OutputChannel; + + constructor() { + this.channel = vscode.window.createOutputChannel(CHANNEL_NAME); + } + + /** + * Begin one turn's UI. Writes the per-turn header to the channel and returns + * the onProgress relay + an AbortSignal linked to the notification's cancel + * token. The channel APPENDS (it doubles as a debug log of recent turns, spec + * §3.5); it auto-reveals (without stealing focus) on the first streamed text, + * gated by `cowriting.liveProgress.revealOutput`. + */ + begin( + instruction: string, + progress: vscode.Progress<{ message?: string }>, + token: vscode.CancellationToken, + ): TurnUi { + const controller = new AbortController(); + token.onCancellationRequested(() => controller.abort()); + this.channel.appendLine(`── asking: ${instruction} ──`); + + let revealed = false; + const reveal = (): void => { + if (revealed) return; + revealed = true; + const cfg = vscode.workspace.getConfiguration("cowriting"); + if (cfg.get("liveProgress.revealOutput", true)) this.channel.show(true); + }; + + const onProgress = (s: TurnProgressSnapshot): void => { + progress.report({ message: formatProgressLine(s) }); + if (s.textDelta) { + this.channel.append(s.textDelta); + reveal(); + } + }; + + return { onProgress, signal: controller.signal }; + } + + dispose(): void { + this.channel.dispose(); + } +}