feat(vscode): add a VS Code extension (ACP client, no reverse-engineering involved) - #336
Conversation
New editors/vscode/ TypeScript extension package: manifest declaring the claurst.openChat/newSession/stopSession commands and an executablePath setting, plus tsconfig and .gitignore entries for the generated out/ and node_modules/.
Speaks the same newline-delimited JSON-RPC 2.0 wire format implemented in src-rust/crates/acp/src/connection.rs: spawns the configured claurst executable with `acp`, does the initialize/session-new handshake, sends session/prompt, and forwards session/update notifications (text/thought chunks, tool call start/update) plus incoming session/request_permission requests to caller-supplied callbacks. Field names and enum tags (sessionUpdate, outcome, toolCallId, etc.) were verified directly against the agent-client-protocol-schema crate source rather than guessed, and the handshake/prompt/tool-call/ permission round trip was smoke-tested against a real running claurst binary before committing.
Vanilla HTML/CSS/JS webview (no framework) rendering streamed agent text, italicized thinking chunks, and colored tool-call status lines, themed via VS Code's CSS custom properties so it matches light/dark.
Owns one webview panel and its AcpClient/session: starts a session against the first workspace folder's cwd, relays session/update events to the webview, and turns incoming permission requests into a VS Code quick pick (defaulting to the least-privileged option on dismissal).
Registers the three commands against ChatPanel and documents setup, commands, and scope (single session, no inline diffs/@mentions yet) for contributors.
Without .vscode/launch.json the "extensionHost" debug type never appears in VS Code's debugger picker, so F5 has nothing to run. Add launch.json (runs an Extension Development Host against this folder) and tasks.json (npm run compile as the pre-launch build step). Carve out an exception in the root .gitignore's blanket ".vscode/" rule for this one nested directory — it's required project config for authoring the extension, not personal editor state.
Needed to read back Config-tool output (e.g. `model = "claude-opus-5"`)
from the wire without a second round trip. content is an internally-
tagged ToolCallContent array; pull the first {type:"content",
content:{type:"text"}} entry's text into a new resultText field.
…tate Replaces the flat message list with aligned user/agent bubbles, a header row of clickable model/provider/effort pills, in-place tool-call updates keyed by toolCallId (instead of a new line per update), an auto-resizing input box, and a Send/Stop toggle driven by turn state.
…/provider controls
- startSession() no longer blocks and shows an error toast when no
workspace folder is open; it falls back to the user's home
directory, matching how a plain terminal session behaves.
- After session start, silently asks the agent (via the Config tool,
added in a companion PR) to report model/provider/effort and
populates the header pills, without cluttering the visible
transcript (a `silent` flag suppresses event forwarding to the
webview during that one priming turn).
- Clicking a header pill opens a quick pick / input box and sends a
prompt engineered to reliably trigger a single Config tool call
("Use the Config tool to set ... ") rather than a conversational
reply, echoing the action as a user-style bubble.
- Every real prompt now signals turnEnded on completion or failure so
the webview can re-enable Send / hide Stop.
Requires the Config tool's provider/effort support from PR Kuberwastaken#337 —
without it, the header pills stay on their placeholder text and the
pill click handlers report a "no such setting" tool error.
|
UI is functional but basic (vanilla HTML/CSS/JS, no design pass). If anyone wants to take the visual design further — better bubble/tool-call styling, icons, animations, general polish — very welcome. I've done my part on the protocol/functionality side; UI contributions appreciated. |
Kuberwastaken
left a comment
There was a problem hiding this comment.
Thanks for building this against the ACP server rather than reverse-engineering anything; the acpClient.ts routing matches crates/acp. Can't merge it in this form though:
- The model/provider/effort pills don't work against main:
captureStatusFromToolResultrequirestitle === 'Config', but ourtool_call_updatenever carries a title (prompt.rs builds status+content only and the schema skips None), and the initialtool_callhas no content. So the pills never populate and the silent priming turn at session start is a paid LLM call for nothing. Same root cause makesupsertToolCallreplace every tool line with "(tool call)" on completion. - Even when parsed, "set X via a prompt that asks the model to call the Config tool" only writes settings.json; the ACP runtime builds its QueryConfig once per process (runtime.rs:90), so the running session keeps the old model/effort. That needs a real mechanism (restart the child after a set, or a protocol-level option), not prompt injection. Drop the pills + priming turn from this PR and keep it to the chat/permission MVP.
- On quick-pick dismissal default to the reject/cancel option (or
outcome: "cancelled"), notoptions[0]— that's currently "allow once" for Bash/Edit approvals. - Add a CI job (
npm ci && npm run compile, path-filtered to editors/vscode/) and at least a unit test for the line router; and confirm it has been run once in a real Extension Development Host.
Separately I need to decide whether an npm project belongs in this repo at all vs a claurst-vscode repo linked from the README; will follow up once the above is addressed.
…ed at process start
|
All four addressed:
No opinion from me on the repo-location question (this repo vs. a linked `claurst-vscode` repo) — happy to move it if that's the call. |
|
Merged — this is nice work, and the ACP wiring holds up. I spot-checked every method and field against Four things I'm fixing up in follow-up commits rather than another review round:
Thanks for the contribution — and for doing it against the protocol instead of reverse-engineering anything. |
Summary
No corresponding issue — issue #57 ("Discuss: Future of claurst") and #186 touch on editor integration generally, but this wasn't filed as its own request. Opening it as a concrete proposal.
Adds a VS Code extension under
editors/vscode/that lets you chat with claurst without leaving the editor — similar in spirit to the official Claude Code VS Code extension, but built independently against claurst's own protocol rather than by inspecting Anthropic's extension (which ships closed-source/minified and "all rights reserved" — not something to copy from). Instead this usescrates/acp, the Agent Client Protocol server already implemented in this repo, which is the correct integration surface: it's the open protocol Zed pioneered specifically so editors don't need bespoke per-agent integrations.How it works
The extension spawns
claurst acpas a child process and speaks the same newline-delimited JSON-RPC 2.0 wire format implemented insrc-rust/crates/acp/src/connection.rs:initialize/session/newhandshake with the workspace folder ascwd.session/promptsends the user's message.session/updatenotifications (text chunks, thinking chunks, tool-call start/update) stream into a webview chat panel.session/request_permissionrequests surface as a VS Code quick pick; the chosen option is written back over stdio.Every field name and enum tag used in
acpClient.ts(sessionUpdate,outcome,toolCallId, etc.) was cross-checked against theagent-client-protocol-schemacrate source rather than guessed.Commands
session/cancel.Config:
claurst.executablePath(default"claurst", resolved fromPATH).Update: UI overhaul + working folder + model/effort/provider controls
toolCallIdinstead of one line per update), auto-resizing input, and a Send/Stop toggle driven by turn state.Configtool call (Use the Config tool to set "effort" to "high".) rather than relying on a conversational reply. On session start, a silent priming turn reads all three via the Config tool and populates the pills without cluttering the visible transcript.Configtool didn't supportprovideroreffortat all before that PR.Test plan
npm install && npm run compileineditors/vscode/— clean TypeScript build, no errors.AcpClientdirectly against a real runningclaurstbinary (not just compiled — actually exercised):initialize→session/new→session/promptround-trips correctly, streamed text chunks arrive, and a tool-use turn (Bash) correctly triggers atool_callupdate, asession/request_permissionround-trip (approved via the same code path the quick pick uses), atool_call_updatetocompleted, and the tool's real output streaming back.Scope / follow-ups
MVP: single session, no inline diffs, no
@filementions, no multi-session tabs. Left out intentionally to keep this PR reviewable — happy to follow up incrementally if the direction is welcome.