BenStullsBets a482082529 fix(#59): disable claude-code IDE auto-connect to stop spurious macOS Apple-Events prompt
The claude-code provider spawns the bundled `claude` CLI via
@cline/sdk → ai-sdk-provider-claude-code → @anthropic-ai/claude-agent-sdk.
On macOS, when that subprocess is launched from inside the VS Code extension
host it performs IDE auto-connect, which shells out to `osascript`
(`tell application …`) to identify/activate the editor — raising the macOS
"control other applications" (Apple Events / Automation) permission prompt.

There is no AppleScript/osascript anywhere in the JS dependency tree (the only
match is the build-only `vite`); the Apple Event is sent by the spawned `claude`
binary, which contains 26 osascript calls. Every one is user-action-gated
(clipboard image, screenshot, terminal-setup, Claude-Desktop deep link, browser
detection) except the IDE auto-connect path, which is the only one that fires
without user action when launched inside an IDE — exactly the reported symptom
(appears on use, harmless when declined, since we never use the connection).

Fix: spawn the agent with the macOS IDE integration disabled via the provider's
`options.env` passthrough — `CLAUDE_CODE_AUTO_CONNECT_IDE=0` hard-returns out of
the binary's auto-connect gate (`mK("0") === true`), and
`CLAUDE_CODE_IDE_SKIP_AUTO_INSTALL=1` skips the extension-install deep link.
`options.env` is seeded with the full `process.env` so the default environment
inheritance is unchanged (login under HOME, proxy/CA vars preserved) — only the
two switches are added. The env flows end to end into the spawned child
(verified through the provider's getBaseProcessEnv merge and the agent SDK's
spawn). runEditTurn is the sole agent-spawn site, so this covers every flow.

Unit test asserts the agent is constructed with the two switches set and the
rest of the environment preserved. 250 unit + typecheck + build green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 13:11:13 -07:00

vscode-cowriting-plugin

Non-shippable proof-of-concept (Feature #2 of Epic #1): a standalone VS Code extension that drives @cline/sdk — validating Approach A (own coauthoring extension on the Cline SDK, no fork).

What it does

Registers one command, Cowriting: Show Cline SDK Info, which loads @cline/sdk and shows the SDK build version plus the agent's builtin tool catalog (a pure, key-free SDK call) in a notification and the "Cowriting (Cline SDK)" output channel.

Features shipped so far: F2 region-anchored threads (Feature #4), F3 live human/Claude attribution (Feature #6), F4 propose/accept diff flow (Feature #12), F5 cross-rung sidecar contract (Feature #14), F6 diff-view toggle (Feature #17), F10 interactive review — write left / review right (Feature #29), and F11 — the preview toolbar as the primary interaction surface (Feature #43).

Architecture

  • CommonJS extension bundled with esbuild (src/extension.tsout/extension.cjs).
  • @cline/sdk is ESM-only (Node ≥22) and uses createRequire(import.meta.url), so it is not bundled — it is shipped in node_modules and loaded at runtime via dynamic import() from the vscode-free src/cline.ts.

Run it (F5)

  1. npm install
  2. npm run build
  3. Press F5 (or Run → "Run Extension") to launch the Extension Development Host. It opens the committed sandbox/ playground as its workspace (not the repo itself — VS Code won't open one folder in two windows, #8); start with sandbox/playground.md.
  4. In the new window: Cmd/Ctrl+Shift+P"Cowriting: Show Cline SDK Info".

F2 — Region-anchored threads (Feature #4)

Attach durable, region-anchored discussion threads to any document. Threads render in the native VS Code Comments gutter and persist as git-native sidecars under .threads/<doc-path>.json (plain, diffable JSON — no server).

  • Create: select text → run "Cowriting: Add Coauthoring Thread on Selection" (or the Comments gutter "+").
  • Reply / Resolve: use the native Comments reply box and the thread's Resolve/Reopen actions.
  • Survives edits, reload, and external change (git pull): a hybrid anchor (durable content fingerprint + live offset tracking) re-resolves the thread. If the anchored text can't be confidently re-found, the thread is shown as orphaned at its last-known line — never silently moved.

Design: vscode-cowriting-plugin-content/specs/coauthoring-inner-loop.md. No live @cline/sdk turn and no credentials are involved in F2.

F3 — Live human/Claude attribution (Feature #6)

As you and Claude coauthor, every span in the buffer carries an author: human edits render with a subtle left border, Claude-authored spans with a background tint. Text that predates tracking stays plain — the honest record. Edits that touch a boundary (split, merge, partial overwrite) are handled char-precisely.

Attribution is persisted git-natively in the same .threads/ sidecars (attributions[], sharing the same anchors fingerprints as F2 threads). On reload, fingerprints re-resolve spans against the current document; spans that can't be confidently re-found are orphaned (status-bar count + "Cowriting Attribution" output channel) rather than silently moved or discarded.

Commands

  • Cowriting: Ask Claude to Edit Selection — select text → enter an instruction → a live @cline/sdk turn runs on the built-in claude-code provider (rides your local Claude Code Pro/Max login; the extension stores no credentials). As of F4 the turn ends in a proposal (see below); accepted text lands as a Claude-attributed span.
  • Cowriting: Toggle Attribution — show/hide attribution decorations.
  • cowriting.applyAgentEdit (palette-hidden) — the single machine-edit ingress seam. Tests drive this directly so CI requires no LLM.

Design: vscode-cowriting-plugin-content/specs/coauthoring-attribution.md.

F4 — Propose/accept diff flow (Feature #12)

Claude's edits arrive as pending proposals — propose-by-default, the document never changes without your say-so. A proposal renders two ways at once: an amber tint on the target range, and a "Claude proposes" comment thread showing a fenced diff of current → proposed text with two actions:

  • ✓ Accept Proposal — applies the replacement through the applyAgentEdit seam, so it lands Claude-attributed (F3) — and the proposal disappears.
  • ✗ Reject Proposal — the document is untouched; the proposal disappears.

Pending proposals persist git-natively in the same sidecar (proposals[], sharing the F2/F3 anchors fingerprints), survive reload, and re-anchor as surrounding text changes. If the target text itself changes, the proposal goes stale (status-bar count, accept disabled — never applied by guess); undo the change and it becomes decidable again. cowriting.proposeAgentEdit (palette-hidden) is the programmatic propose ingress E2E drives — no LLM in CI.

Design: vscode-cowriting-plugin-content/specs/coauthoring-propose-accept.md. Live smoke: docs/MANUAL-SMOKE-F4.md.

F5 — Cross-rung format + round-trip (Feature #14)

The .threads/ sidecar is the published cross-rung contract: any rung of the ladder (this editor → Gitea substrate → rfc-app) reads and writes the same record per the contract; git push/pull is the transport, no re-homing ever.

  • Normative contract (INV-14): vscode-cowriting-plugin-content/specs/coauthoring-sidecar-contract.md — format changes land there first, then schema, then code.
  • Machine-checkable half: schemas/coauthoring-sidecar.schema.json; validate any sidecar from any rung with node scripts/validate-sidecar.mjs <file>. The unit suite validates every serialized artifact — contract drift fails CI.
  • Identity crosses rungs: Provenance.email (git's own join key — populated from the workspace git config when available) and agent.onBehalfOf (who the machine acted for).
  • Writers preserve unknown fields (INV-15): rewriting a sidecar never destroys another rung's data — unknown keys survive, after known keys, sorted.
  • Newer-major sidecars are read-only (INV-16): the editor renders what it understands, warns once, and writes nothing (the store refuses as a backstop).
  • Deterministic merge (INV-17): src/mergeArtifacts.ts — union-by-id, documented tie-breaks, every resolved divergence surfaced in conflicts.
  • The round-trip is proven, not asserted: scripts/crossrung-reply.mjs is a self-contained conforming foreign writer (the Gitea-rung stand-in); host E2E drives editor-thread → stand-in reply → external-change → the reply renders in the thread.

Design: vscode-cowriting-plugin-content/specs/coauthoring-cross-rung-format.md.

F6 — Diff-view toggle (Feature #17, #19)

Ctrl+Alt+D (the same chord on macOS — not Cmd; or Cowriting: Toggle Diff View) flips the focused document into a native vscode.diff against a coauthoring baseline — the readonly baseline on the left, your live, editable document on the right (so you keep writing inside the diff; toggling again closes it). The diff answers "what did I change?" in one keystroke instead of git archaeology.

  • Works on any file (#19): any document you can edit — a file inside or outside the workspace folder, or an untitled scratch buffer. Only a non-text-editor focus warns. (Untitled buffers diff in-memory; saving makes the baseline persist.)
  • Machine-factored baseline (INV-18): the baseline initializes when a doc is first seen and advances automatically at every machine landing (every successful applyAgentEdit seam apply — INV-9). So text Claude landed never shows as a change; everything the diff shows is operator-authored by construction — no attribution filtering.
  • Pin on demand: Cowriting: Pin Diff Baseline to Now resets the baseline to the current buffer for a deliberate "review my next pass" epoch; the diff tab title names the epoch (opened / Claude landed / pinned).
  • Pure view, repo-free (INV-19): the baseline snapshot lives in VS Code global extension storage, keyed by a hash of the document URI, never the repo — .threads/, the cross-rung contract (INV-14..17), and SCHEMA_VERSION are untouched. Storage-unavailable degrades to in-memory baselines + one warning.
  • No LLM in CI: host E2E (test/e2e/suite/diffView.test.ts) drives the same programmatic seam ingress (propose + accept) the F4 suite uses.

Design: vscode-cowriting-plugin-content/specs/coauthoring-diff-view.md. Live smoke: docs/MANUAL-SMOKE-F6.md.

F7 — Rendered track-changes preview (Feature #21)

Ctrl+Alt+R (or Cowriting: Open Track-Changes Preview) opens a read-only webview beside a Markdown editor that renders the document and marks what changed since the F6 baseline — the "track changes" / "suggesting mode" altitude rather than a raw-text split-diff:

  • Prose additions are highlighted (<ins>), deletions struck (<del>), refined to the word.
  • Code and mermaid fences are diffed whole (atomic, INV-23): a changed or added one renders fully with a small "changed" badge; a removed one renders struck. Mermaid fences render as diagrams (mermaid runs in the webview). Intra-diagram node/edge diffing is deferred (#22).
  • It updates live as you and Claude edit (debounced), and re-bases when Claude lands an edit (baseline advances, INV-18) or you pin — so accepted text drops its marks. It reuses the F6 baseline and adds no persistence (pure read-only, INV-20).
  • The webview is sealed (INV-21): local bundled assets only, strict CSP with a per-load nonce, no network/CDN, no LLM. Mermaid is bundled into the webview asset only, never the extension-host bundle.
  • The render engine (src/trackChangesModel.ts) is a pure, vscode-free function (INV-22), unit-tested with no editor and no webview; host E2E (test/e2e/suite/trackChangesPreview.test.ts) drives the same programmatic propose/accept seam with no LLM.

F7 is markdown-only; for any other file (incl. code), use F6's diff toggle (Ctrl+Alt+D). The webview's visual rendering (mermaid, theming) is verified by the manual smoke, not the sealed-sandbox E2E.

Design: vscode-cowriting-plugin-content/specs/coauthoring-rendered-preview.md. Live smoke: docs/MANUAL-SMOKE-F7.md.

F9 — Authorship view in the preview (Feature ~#27)

The rendered preview (F7) gains a second mode, switched by a [ Track changes | Authorship ] toggle in its header. Authorship mode re-renders the current document with each span colored by its F3 author — Claude (blue) vs you (green), inline and char-precise — with a legend. Unlike track-changes (which diffs against the F6 baseline, and so hides Claude's text once the baseline advances past a landing), authorship reads F3 attribution directly, so Claude's contributions stay visible. Code/mermaid fences carry a block-level author badge (atomic). Read-only, sealed webview, no new persistence (INV-26..28).

Design: docs/superpowers/specs/2026-06-11-authorship-preview-design.md. Live smoke: docs/MANUAL-SMOKE-F9.md.

F8 — Out-of-workspace authoring (Feature #25)

"Ask Claude to Edit Selection" (and F2 threads / F3 attribution / F4 propose-accept) now work on any document the editor shows — saved in the workspace folder, saved outside it, or untitled — matching the already-universal F6 diff and F7 preview. Authoring is no longer gated to in-workspace files, and the commands are live even with no folder open.

Persistence is hybrid (one SidecarStore abstraction, routed per-document):

  • an in-workspace file keeps its committable .threads/<repo-rel>.json sidecar, byte-for-byte unchanged (INV-2) — the only home the F5 cross-rung contract ever sees;
  • an out-of-workspace file or untitled buffer stores its coauthoring artifact in VS Code global storage keyed by sha256(uri) (the same home and key F6's baseline uses, INV-19/24); untitled buffers are in-memory only (lost on reload/save).

A global-storage artifact is not a committed file, so it is never cross-rung-shareable (INV-25), and renaming/moving the file orphans its artifact (the key is the URI hash) — both stated as design contract, not discovered later. Routing leaves the in-workspace path untouched, so rollback is a plain PR revert with zero data migration.

Design: vscode-cowriting-plugin-content/specs/coauthoring-out-of-workspace.md. Live smoke: docs/MANUAL-SMOKE-F8.md.

F10 — Interactive review: write left / review right (Feature #29)

A clean, zero-annotation editor on the left; the rendered preview on the right as the single interactive review surface. The editor carries no attribution tint, no in-editor proposal threads, and no diff — all review lives in the preview, toggled by the Annotations switch in its header (on by default). Open it via Ctrl+Alt+R, the editor title-bar button, or right-click a markdown file in the Explorer / its editor tab → "Open Cowriting Review Panel" (#41) — the right-click acts on the clicked document, opening it first if needed.

In the on-state the preview shows green = human / blue = Claude / strikethrough = deleted, and surfaces each of Claude's pending F4 proposals as a blue cw-proposal block with ✓ / ✗ buttons: accepts (the replacement lands Claude-attributed via the seam and the baseline advances past it), rejects (the block vanishes, the document untouched). With no preview open, a status-bar indicator shows the pending-proposal count and opens the review when clicked. Toggle Annotations off for clean rendered markdown. Read-only, sealed webview, no new persistence (INV-32..34).

F6's two-pane diff and F9's authorship view are retained only as data layers (the baseline the preview diffs against; the F3 attribution that colors it) — they are no longer separate user surfaces.

Design: vscode-cowriting-plugin-content/specs/coauthoring-interactive-review.md. Live smoke: docs/MANUAL-SMOKE-F10.md.

F11 — Preview toolbar as the primary interaction surface (Feature #43)

The review preview's header toolbar becomes the cockpit for the inner loop. Beside the existing Annotations switch it gains two controls:

  • ⌖ Pin baseline — pins the previewed document's review baseline to now and clears the change-marks (homes the previously-orphaned pinDiffBaseline command, which is reachable from the palette again too).
  • ✦ Ask Claude… — one adaptive button. Its label flips on the preview's selection: Edit Selection when text is selected in the rendered preview, Edit Document otherwise. Clicking it opens a host input box for the instruction (the LLM turn and prompt stay host-side — the sealed webview gains no LLM/credential surface), then surfaces the result as F4 proposals — one for a selection (mapped back to its source block-union), or one per changed hunk for a whole-document rewrite (diffToHunks), each independently ✓/✗-able.

The pure render layer emits data-src-start/data-src-end on every block in both modes (INV-36); the webview's only mapping duty is walking a selection to its nearest data-src ancestor. Right-clicking a markdown tab → Open Review Preview is the minimal gateway making the surface reachable end to end (#41/#42 expand it). Edit controls are disabled on a non-authorable doc (reading stays allowed). No new model, no new persistence — pin via the F6 store, edits via the F4 propose/accept seam with F3 attribution (INV-35..37).

Design: docs/superpowers/specs/2026-06-12-f11-preview-toolbar-interaction-surface.md. Live smoke: docs/MANUAL-SMOKE-F11.md.

Develop

  • npm run watch — rebuild on change.
  • npx vitest run — unit suite (SDK driver, schema, store, anchorer, thread mutations, attribution split/merge).
  • npm run test:e2e@vscode/test-electron host E2E (create → reply → resolve → persist → reload → re-anchor → orphan; drives cowriting.applyAgentEdit directly — no LLM required).
  • npm run smoke:live — scripted live-turn smoke test for F3; requires Claude Code installed and signed in. See docs/MANUAL-SMOKE-F3.md.
  • npm run typecheck — type-check without emit.
S
Description
Plugin for collaborative writing with machines
Readme 3.4 MiB
Languages
TypeScript 97.7%
JavaScript 1.9%
CSS 0.4%