@volter/supercode-frontend-vscode 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +19 -0
- package/README.md +293 -0
- package/dist/bindings.d.ts +7 -0
- package/dist/bindings.js +5 -0
- package/dist/client.d.ts +11 -0
- package/dist/client.js +5 -0
- package/dist/context.d.ts +6 -0
- package/dist/context.js +14 -0
- package/dist/credential.d.ts +17 -0
- package/dist/credential.js +53 -0
- package/dist/extension.d.ts +39 -0
- package/dist/extension.js +1131 -0
- package/dist/host-controls.d.ts +58 -0
- package/dist/host-controls.js +66 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/dist/native-session.d.ts +6 -0
- package/dist/native-session.js +41 -0
- package/dist/proposals.d.ts +24 -0
- package/dist/proposals.js +54 -0
- package/dist/session.d.ts +154 -0
- package/dist/session.js +431 -0
- package/dist/view.d.ts +176 -0
- package/dist/view.js +717 -0
- package/media/anthropic.svg +1 -0
- package/media/google.svg +1 -0
- package/media/icon.png +0 -0
- package/media/openai.svg +1 -0
- package/package.json +200 -0
- package/scripts/pack-extension.mjs +63 -0
- package/vendor/supercode-frontend/README.md +22 -0
- package/vendor/supercode-frontend/client.d.mts +106 -0
- package/vendor/supercode-frontend/client.mjs +300 -0
- package/vendor/supercode-frontend/contract.json +289 -0
- package/vendor/supercode-frontend/generated-client.d.mts +18 -0
- package/vendor/supercode-frontend/generated-client.mjs +20 -0
- package/vendor/supercode-frontend/generated.d.mts +146 -0
- package/vendor/supercode-frontend/generated.mjs +19 -0
- package/vendor/supercode-frontend/package.json +20 -0
package/NOTICE.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Third-party notice
|
|
2
|
+
|
|
3
|
+
This optional frontend has **no runtime npm dependency**. It builds against `@types/vscode`
|
|
4
|
+
(Copyright (c) Microsoft Corporation, MIT) as an exact devDependency for types only; `vscode` itself
|
|
5
|
+
is supplied by the editor at runtime and is never bundled or redistributed here.
|
|
6
|
+
|
|
7
|
+
No Microsoft source is copied into this repository. In particular the editor's
|
|
8
|
+
`vscode.proposed.*.d.ts` files are **not** vendored: `src/vscode-proposed.d.ts` is a
|
|
9
|
+
Volter Harness-authored declaration of the narrow proposed-API subset this package calls, written against
|
|
10
|
+
the published proposals rather than copied from them.
|
|
11
|
+
|
|
12
|
+
The generated attachment client under `vendor/supercode-frontend/` is this repository's own
|
|
13
|
+
`@volter/supercode-frontend`, synced by `scripts/sync-sdk.mjs` and version-pinned by
|
|
14
|
+
`package.json#supercodeFrontendContract`.
|
|
15
|
+
|
|
16
|
+
Volter Harness-authored integration and presentation code is identified by this package's source history
|
|
17
|
+
and remains under the repository's declared `MIT OR Apache-2.0` license.
|
|
18
|
+
|
|
19
|
+
The bundled provider/harness SVG marks in media/ are derived from this repository's sdk/ui/src/logo.jsx. Brand marks identify the active provider; they do not imply affiliation.
|
package/README.md
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# Volter Harness VS Code frontend
|
|
2
|
+
|
|
3
|
+
This is an optional VS Code extension that presents an **already-running** Volter Harness SDK runtime in
|
|
4
|
+
the editor's own Chat view. It uses the exact generated `@volter/supercode-frontend` client
|
|
5
|
+
for all session discovery, history, events, and semantic actions, and VS Code's own chat API for all
|
|
6
|
+
presentation — no webview, no React, no stylesheet of its own.
|
|
7
|
+
|
|
8
|
+
It does not run an agent, load context, authenticate a provider, execute tools,
|
|
9
|
+
import sessions, or write session files. The Rust/headless Volter Harness core does not depend on this
|
|
10
|
+
package or npm.
|
|
11
|
+
|
|
12
|
+
## Native chat controls
|
|
13
|
+
|
|
14
|
+
A bundling host can expose a private lifecycle endpoint through
|
|
15
|
+
`SUPERCODE_FRONTEND_HOST_URL` and `SUPERCODE_FRONTEND_HOST_CREDENTIAL_FILE`.
|
|
16
|
+
The endpoint must be loopback HTTP and the credential follows the runtime credential-file
|
|
17
|
+
checks. `GET /state` describes the selected harness/model/effort, available choices,
|
|
18
|
+
and current connection; `POST /select` asks the host to start the selected configuration
|
|
19
|
+
and returns its new scoped connection. The host owns validation, process lifetime,
|
|
20
|
+
persistence, and failure handling. Prompts and approvals still use the generated frontend client.
|
|
21
|
+
The contract is in `src/host-controls.ts`.
|
|
22
|
+
|
|
23
|
+
With that host, **Chat: New Chat: Choose Harness, Model and Reasoning** chooses
|
|
24
|
+
configuration before opening a separate conversation. A conversation's harness, model and
|
|
25
|
+
reasoning settings remain fixed: this integration does not advertise hot swapping. Native
|
|
26
|
+
Chat displays its selected model and provider icon inside the composer and its supported
|
|
27
|
+
approval policy below it. The response author identifies the harness.
|
|
28
|
+
|
|
29
|
+
The host publishes durable conversation IDs and their exact harness history identities.
|
|
30
|
+
`POST /open` resumes only the requested saved conversation; `POST /remember` binds its
|
|
31
|
+
reported native session ID to discovered history. Unknown or missing bindings fail explicitly,
|
|
32
|
+
never falling back to the currently active runtime. The host may provide an `openSessionCommand`
|
|
33
|
+
to reveal an exact resource through its native widget service. Cached native widgets should
|
|
34
|
+
notify `supercode.frontend.activateSession` when focused. The frontend owns no processes.
|
|
35
|
+
|
|
36
|
+
The permission group offers **Ask for approval** and, on an answerable interactive attachment,
|
|
37
|
+
**Auto approve**. Policies are scoped by conversation and captured for each turn; automatic
|
|
38
|
+
approval does not bypass harness sandbox rules. Unsupported modes are refused. Unanswerable
|
|
39
|
+
harnesses show **Harness permissions**. Previously answered requests cannot affect another
|
|
40
|
+
connection. Native command-button fallback limitations are documented below.
|
|
41
|
+
|
|
42
|
+
Text files and selected ranges use native Add Context and attachment chips. Their current
|
|
43
|
+
editor contents are serialized as explicitly labeled user-supplied context through the existing
|
|
44
|
+
prompt protocol. Binary text, more than 256 KiB per attachment, or more than 1 MiB total is
|
|
45
|
+
rejected without truncation. Image and tool attachments remain unavailable. Model inventories,
|
|
46
|
+
mode/tool controls, and context limits are not invented where the runtime has no contract.
|
|
47
|
+
|
|
48
|
+
These controls use native provider APIs; there is no custom composer or webview.
|
|
49
|
+
`test/bindings.test.mjs`, `test/context.test.mjs`, and `test/host-controls.test.mjs` cover
|
|
50
|
+
routing boundaries, context serialization/limits, identity and approval interpretation.
|
|
51
|
+
Live product acceptance is separate from these frontend unit tests.
|
|
52
|
+
|
|
53
|
+
## Attaching
|
|
54
|
+
|
|
55
|
+
Three paths, in precedence order.
|
|
56
|
+
|
|
57
|
+
1. **A host hands over the connection.** `activate()` returns an API a bundling product calls when
|
|
58
|
+
it already knows where its runtime is:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const supercode = vscode.extensions.getExtension('volter-ai-dev.supercode-frontend-vscode');
|
|
62
|
+
const api = await supercode.activate();
|
|
63
|
+
await api.connect({baseUrl, token, permissions: ['observe', 'interact', 'approve']});
|
|
64
|
+
// api.connections, api.disconnect(id), api.grantedProposals, api.onDidChangeConnections
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
2. **The runtime's environment handover.** At activation the extension reads
|
|
68
|
+
`SUPERCODE_FRONTEND_URL`, `SUPERCODE_FRONTEND_CLIENT_ID`, `SUPERCODE_FRONTEND_CREDENTIAL_FILE`
|
|
69
|
+
and `SUPERCODE_FRONTEND_PERMISSIONS` from the extension host's environment — the same shape
|
|
70
|
+
`crates/cli/src/frontend_client.rs` already uses to launch the pi frontend, without the `PI`
|
|
71
|
+
infix. A Code-OSS server passes its environment to its extension host, so a product that starts
|
|
72
|
+
the server hands the connection over with no pasted token. The credential is a *file*, read with
|
|
73
|
+
`sdk/frontend-pi`'s rules (a regular, private, single-linked file of 64 hex bytes, re-checked
|
|
74
|
+
across the open). It differs from pi's in one deliberate way: the token is stored in
|
|
75
|
+
`SecretStorage` **before** the one-shot file is removed, because an extension host outlives one
|
|
76
|
+
attachment and reloads.
|
|
77
|
+
|
|
78
|
+
The credential file is **read, never consumed**. `sdk/frontend-pi` unlinks it, because a pi
|
|
79
|
+
process is exactly one attachment; an extension host is not — it reloads, and a reload must not
|
|
80
|
+
lose the runtime it was handed. Nor can the token be kept instead: `SecretStorage` is in-memory
|
|
81
|
+
in a host with no credential provider (measured in VS Code Web), and there is no durable place
|
|
82
|
+
an extension may put a bearer token that is better than the runtime's own private 0600 file. The
|
|
83
|
+
base URL it was handed is remembered in `globalState`, so the next activation reconnects.
|
|
84
|
+
|
|
85
|
+
A host that could not start a runtime at all says so with the fourth variable,
|
|
86
|
+
`SUPERCODE_FRONTEND_UNAVAILABLE` (or `connect({unavailable})`): its own sentence, naming its own
|
|
87
|
+
harness and the command a person must type. Nothing is attached and that sentence is what the
|
|
88
|
+
Chat view shows. It is shown as a **session** — `chatViewsWelcome` cannot do it, because
|
|
89
|
+
`ChatViewPane.shouldShowWelcome()` in
|
|
90
|
+
`src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatViewPane.ts` begins
|
|
91
|
+
`const shouldShow = !hasCoreAgent && …`, so the whole welcome control is suppressed whenever any
|
|
92
|
+
core agent is registered for the Chat location, which a Code-OSS derivative with an agent host
|
|
93
|
+
always has; the `when` clause is never consulted. The session's `activeResponseCallback` is the
|
|
94
|
+
one door that writes into a session nobody has typed into, so the reason is on screen where the
|
|
95
|
+
eye lands rather than waiting for a prompt.
|
|
96
|
+
|
|
97
|
+
An attachment that arrives with HISTORY — a resumed runtime, `claude --resume <id>` — opens its
|
|
98
|
+
own session once, so the panel opens *on* the conversation instead of beside it. A runtime with
|
|
99
|
+
no history is left alone: there is nothing to show and stealing the panel would be rude.
|
|
100
|
+
|
|
101
|
+
3. **Settings plus `SecretStorage`.** `supercode.frontend.baseUrl` names the runtime;
|
|
102
|
+
**Volter Harness: Set Runtime Token** prompts once and stores the credential in `SecretStorage`. The
|
|
103
|
+
credential is never a setting and never a URL query. `supercode.frontend.permissions` narrows the
|
|
104
|
+
attachment; `observe` is always included, and every control is still rendered only when the
|
|
105
|
+
runtime's own descriptor advertises the matching action.
|
|
106
|
+
|
|
107
|
+
The generated client opens SSE **before** taking the attachment snapshot, so no event falls between
|
|
108
|
+
history and live. Dropping or detaching never closes the runtime.
|
|
109
|
+
|
|
110
|
+
## API proposals, and what it does without them
|
|
111
|
+
|
|
112
|
+
The extension asks for five proposals. A host grants them through
|
|
113
|
+
`product.json#extensionEnabledApiProposals` (a Code-OSS derivative that bundles this extension) or
|
|
114
|
+
`--enable-proposed-api volter-ai-dev.supercode-frontend-vscode`. **Stock VS Code grants none, and
|
|
115
|
+
everything below still works there** — the granted set is read back from the extension host at
|
|
116
|
+
runtime (`context.extension.packageJSON.enabledApiProposals` is the *filtered* description, not the
|
|
117
|
+
request), and each part declares its own fallback.
|
|
118
|
+
|
|
119
|
+
| Proposal | What it buys | Without it |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| `defaultChatParticipant` | `isDefault` — Volter Harness answers the Chat view with no `@` mention | the ordinary `@supercode` participant; a mention is required |
|
|
122
|
+
| `chatParticipantAdditions` | `ChatToolInvocationPart`, `ChatResponseConfirmationPart`, `ChatResponseWarningPart`, `codeblockUri`, `ChatResponseThinkingProgressPart` | progress lines, labeled Markdown results, typed replies, plain fenced diffs, blockquoted reasoning |
|
|
123
|
+
| `chatSessionsProvider` | the runtime's sessions in the Chat view's session picker, each reopening with its history | the participant still attaches; there is no session list |
|
|
124
|
+
| `chatParticipantPrivate` | dynamic participant identity with the harness and provider icon | static participant identity |
|
|
125
|
+
| `chatProvider` | native fixed-model identity and provider icon | host new-chat configuration |
|
|
126
|
+
|
|
127
|
+
The session participant uses the exact session type id (`supercode`). With
|
|
128
|
+
`chatParticipantPrivate`, the extension registers its dynamic identity directly and keeps
|
|
129
|
+
`canDelegate` disabled so the workbench does not pre-register a generic agent that masks the
|
|
130
|
+
model name. Model descriptors target only the `supercode` session type, whose contribution declares
|
|
131
|
+
`requiresCustomModels` so the workbench renders and forwards the native model selection. As in Code-OSS's own
|
|
132
|
+
AgentHostLanguageModelProvider, they do not expose a second direct inference endpoint:
|
|
133
|
+
the participant validates model settings against the bound conversation, and the runtime
|
|
134
|
+
owns execution. Unknown token limits remain zero, not invented. Model/reasoning changes
|
|
135
|
+
require an explicitly new conversation; they never reset an existing transcript on send.
|
|
136
|
+
The permission group uses `kind: permissions`; the embedding workbench must expose its
|
|
137
|
+
native permission-picker action for this session type. No composer CSS or bespoke toolbar
|
|
138
|
+
is supplied by this frontend.
|
|
139
|
+
|
|
140
|
+
Two participant contributions exist for one reason: the workbench refuses a `chatParticipants` entry
|
|
141
|
+
that declares `isDefault` without the grant *and skips it entirely*, so a single entry would leave a
|
|
142
|
+
stock host with no `@supercode` at all. `supercode.chat` carries no proposal-gated manifest field and
|
|
143
|
+
is `when`-gated on `!supercodeChatDefaultParticipant`; `supercode.chat.default` carries `isDefault`,
|
|
144
|
+
`locations: ["panel"]` and `modes: ["ask"]` and is gated on the inverse. The extension sets that
|
|
145
|
+
context key from the grant it actually received. A host that grants nothing logs one line naming the
|
|
146
|
+
refused proposal and runs on the plain participant. (`locations` is omitted from the plain entry on
|
|
147
|
+
purpose: it too needs `chatParticipantAdditions`, and its absence already defaults to the Chat panel
|
|
148
|
+
— `chatParticipant.contribution.ts`, `locations: isNonEmptyArray(...) ? ... : [ChatAgentLocation.Chat]`.)
|
|
149
|
+
|
|
150
|
+
## The mapping
|
|
151
|
+
|
|
152
|
+
`src/view.ts` is the whole event-to-chat-part mapping and it is pure: every input is the runtime's
|
|
153
|
+
own sequenced `FrontendEvent`, every output is a description of a VS Code chat part plus the proposal
|
|
154
|
+
it needs and the fallback it degrades to. Each arm names the producer it reads.
|
|
155
|
+
|
|
156
|
+
| Event kind | Producer | Rendered as |
|
|
157
|
+
|---|---|---|
|
|
158
|
+
| `text_delta` | `crates/runtime/src/event.rs` `AgentEvent::TextDelta` | `response.markdown` (streamed) |
|
|
159
|
+
| `user_message` | `crates/harness/src/server.rs` `submit_claimed` | the request turn in history; dropped live (VS Code already shows it) |
|
|
160
|
+
| `turn_started` / `turn_completed` / `turn_succeeded` | `submit_claimed`; `AgentEvent::TurnCompleted` | lifecycle only — the handler resolves, nothing renders |
|
|
161
|
+
| `turn_interrupted` | `submit_claimed` | an italic notice |
|
|
162
|
+
| `turn_failed` | `submit_claimed` | `ChatResponseWarningPart` → blockquote |
|
|
163
|
+
| `tool_call_started` / `tool_call_completed` | `AgentEvent::ToolCallStarted` / `ToolCallCompleted` | one `ChatToolInvocationPart` per call, updated in place with its result (`enablePartialUpdate`) → a progress line plus a labeled Markdown result |
|
|
164
|
+
| `diff_updated` | `crates/codex-frontend/src/codex_app_server_v0_144.rs` (stock Codex `turn/diff/updated`) | `codeblockUri` + a fenced `diff` block, plus one stable `reference` per file the diff names → the fenced block alone |
|
|
165
|
+
| `request` | `crates/harness/src/server.rs` `FrontendRequestBroker::publish` | a titled markdown block plus one **command button per answer** — stable API, no proposal |
|
|
166
|
+
| `cache_warning` | `AgentEvent::CacheWarning` | `ChatResponseWarningPart` → blockquote |
|
|
167
|
+
| `usage` | `AgentEvent::Usage` | a progress line |
|
|
168
|
+
| `background_output` | `AgentEvent::BackgroundOutput` | a fenced block, saying so when the runtime stopped retaining |
|
|
169
|
+
| `scheduled_prompt_started` / `_deferred` / `_completed` | declared by `frontend_descriptor`'s `display.event_kinds` | a progress line |
|
|
170
|
+
| `scheduler_error` | same | `ChatResponseWarningPart` → blockquote |
|
|
171
|
+
| `reasoning` / `thinking` | no producer in this repository; read because `crates/frontend-model/src/transcript.rs` reads them | `ChatResponseThinkingProgressPart` → blockquote |
|
|
172
|
+
| `stream_error` / `transport_error` / `watch_error` / `runtime_disconnected` | `crates/opencode-frontend/src/server.rs`; `crates/interchange/src/watch.rs`; synthesized by a frontend (`crates/frontend-tui/src/app.rs`); `stream_error` has no producer here | `ChatResponseWarningPart` → blockquote |
|
|
173
|
+
| anything else | opaque by contract | one **bounded** markdown cell that names the kind and shows the payload — never dropped |
|
|
174
|
+
|
|
175
|
+
**Why `diff_updated` is not an edit part.** Measured: the payload carries one unified-diff *string*
|
|
176
|
+
and nothing else. `ChatResponseTextEditPart` wants ranges and the runtime has already written the
|
|
177
|
+
file, so replaying them would double-apply. `ChatResponseMultiDiffPart` wants an `originalUri` and a
|
|
178
|
+
`modifiedUri` per file — two resources this frontend would have to fabricate and serve.
|
|
179
|
+
`ChatResponseExternalEditPart` brackets a callback that *performs* the edit so the editor can take a
|
|
180
|
+
"before" snapshot; ours would be a no-op after the write, making the editor's undo stop wrong. What
|
|
181
|
+
the payload does state is which files it touched (`+++ b/<path>`), so those become stable references.
|
|
182
|
+
|
|
183
|
+
**Why a request is buttons.** Two richer mechanisms were tried against a live runtime in the
|
|
184
|
+
editor's own Chat view, and both failed there, so neither ships. `ChatResponseConfirmationPart`
|
|
185
|
+
retires its own buttons on click — but the click answers by sending a **new chat request**
|
|
186
|
+
(`chatConfirmationContentPart.onDidClick`), and a Volter Harness approval is raised **while the turn is
|
|
187
|
+
still running** (`decide_approval` blocks the agent loop), so the editor refuses that request, the
|
|
188
|
+
click does nothing, and the runtime waits forever. `stream.questionCarousel` is the right shape on
|
|
189
|
+
paper — it resolves inside the running turn and the widget retires itself — and the widget did
|
|
190
|
+
render with its three options; but Submit retired it without ever delivering an answer this
|
|
191
|
+
frontend could read, leaving the runtime blocked. A preferred path that silently swallows the
|
|
192
|
+
answer is worse than a plain one that works. A `ChatResponseCommandButtonPart` runs inside the live
|
|
193
|
+
turn, needs no proposal, and carries its own answer per button — so an approval offers `Allow` / `Allow for session` / `Deny`, all three, where a
|
|
194
|
+
confirmation's one `data` payload could only ever have carried two. The command
|
|
195
|
+
(`supercode.frontend.respond`) narrates itself in the status bar, because an action whose success is
|
|
196
|
+
invisible is one nobody can trust. A pushed command button cannot be withdrawn — `chatCommandContentPart`
|
|
197
|
+
enables on `!isStale` and carries no used-state, and only `confirmation`, `mcpAuthenticationRequired`,
|
|
198
|
+
`planReview` and `questionCarousel` have one — so the buttons stay visible after the answer. They are
|
|
199
|
+
inert: the command refuses a request the runtime no longer holds and says "already resolved", and the
|
|
200
|
+
resolution sentence lands directly beneath them. An elicitation that carries a `requested_schema` gets an
|
|
201
|
+
`Answer…` button that asks for the value first; a request still pending while the session is idle
|
|
202
|
+
can also be answered by typing.
|
|
203
|
+
|
|
204
|
+
**A hosted harness runtime.** When the runtime is one Volter Harness HOSTS — a Claude Code, Codex, pi or
|
|
205
|
+
opencode session it started through `harness.v1.runtimes.start` — an approval it raises is
|
|
206
|
+
**answerable from this view** for every harness whose reply this runtime can translate
|
|
207
|
+
(`crates/harness/src/runtime/hosted.rs`): the canonical `respond {decision}` becomes that harness's
|
|
208
|
+
own envelope, which for Claude Code is `{behavior:'allow'}` / `{behavior:'deny', message}`. The
|
|
209
|
+
request states which decisions its harness protocol actually carries, so this view offers exactly
|
|
210
|
+
those and no button the runtime would refuse — Claude Code has an allow and a deny and no
|
|
211
|
+
always-allow *behavior* at all, so it gets two. A harness with no translation keeps
|
|
212
|
+
`actions.respond: false`, and then the request is rendered as an observation naming who answers it,
|
|
213
|
+
because a control appears only for an action the descriptor advertises. Tool completions name only
|
|
214
|
+
the call they answer (Claude Code's `tool_result` block carries just a `tool_use_id`), so an unnamed
|
|
215
|
+
completion keeps the name the start stated rather than becoming a tool called `tool`.
|
|
216
|
+
|
|
217
|
+
**Bounds.** The projection is bounded like `@volter/supercode-frontend-browser`'s: 64 KiB per
|
|
218
|
+
part, 256 characters per title, 2 KiB for an opaque payload, 500 history turns. When turns are
|
|
219
|
+
omitted the view says so; the runtime's own history is unchanged and authoritative. Terminal control
|
|
220
|
+
characters never reach a rendered part.
|
|
221
|
+
|
|
222
|
+
**The turn.** A chat request submits through `frontend.v2.send_input` and resolves on the first
|
|
223
|
+
`turn_succeeded` / `turn_failed` / `turn_interrupted`; the request's cancellation token calls
|
|
224
|
+
`frontend.v2.interrupt`. It *also* resolves when the descriptor goes idle after having been busy:
|
|
225
|
+
the editor holds the chat response open until the handler returns, and a frontend that hangs one
|
|
226
|
+
forever is worse than one that ends it a beat early. A sequence gap is likewise not fatal — it is
|
|
227
|
+
what the cursor exists for, so the session re-takes its snapshot rather than throwing out of the
|
|
228
|
+
live listener and stranding every in-flight turn.
|
|
229
|
+
|
|
230
|
+
**The lease.** The runtime hands ONE controller a lease and expires it
|
|
231
|
+
(`crates/harness/src/runtime_lease.rs`), so an attachment that may submit takes control on attach
|
|
232
|
+
and heartbeats at a third of the advertised TTL; `controller_required` / `lease_expired` on any
|
|
233
|
+
action is a take-control-and-retry, never something the view reports.
|
|
234
|
+
|
|
235
|
+
**The system prompt renders nothing.** It is in the runtime's canonical history because the MODEL
|
|
236
|
+
needs it; a reader does not, and a collapsed block is still a reader's problem. No chat product shows
|
|
237
|
+
one to a person, so `role: 'system'` is dropped outright and the transcript starts where the
|
|
238
|
+
conversation does.
|
|
239
|
+
|
|
240
|
+
**Session times are real or nothing.** `frontend.v2` carries no session timestamp, and the editor's
|
|
241
|
+
session list ALWAYS reads `timing.created`, rendering the epoch ("57 yrs ago") when a provider
|
|
242
|
+
supplies none — there is no path in `agentSessionsViewer` that omits the time. So the item gets the
|
|
243
|
+
true times this frontend does know: when this window attached, and the turn boundaries it watched go
|
|
244
|
+
by. Never a zero dressed up as a date.
|
|
245
|
+
|
|
246
|
+
**History.** Saved tool results use labeled Markdown and fenced output; raw HTML is
|
|
247
|
+
not supported by native history rendering. A saved call is labeled neutrally because
|
|
248
|
+
a recorded invocation does not prove that permission was granted. VS Code's chat history is request/response **pairs** and drops a response turn with no
|
|
249
|
+
request before it. A runtime transcript legitimately opens with assistant or system content — the
|
|
250
|
+
runtime compacts, so its canonical history can begin `[earlier conversation compacted: N messages]`
|
|
251
|
+
with no user turn left. Rather than drop it, the projection pairs it with a marker turn,
|
|
252
|
+
`⟨supercode runtime history⟩`, which names itself: nothing is presented as a person's words.
|
|
253
|
+
|
|
254
|
+
## Building and packaging
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
npm install && npm run build # sync the generated client, then tsc
|
|
258
|
+
npm test # build + the pure mapping over recorded events
|
|
259
|
+
npm run extension # write the VS Code extension folder to .extension/
|
|
260
|
+
node scripts/pack-extension.mjs --out .extension --vsix # and a .vsix
|
|
261
|
+
node scripts/pack-extension.mjs --out .stock --no-proposals # the stock-host manifest
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
One `package.json` cannot be both manifests: npm wants the scoped
|
|
265
|
+
`@volter/supercode-frontend-vscode`, while VS Code builds an extension id as
|
|
266
|
+
`${publisher}.${name}` and a scope would put an `@` and a `/` in it. The npm manifest is the source
|
|
267
|
+
of truth and `scripts/pack-extension.mjs` writes the extension's manifest beside the same built
|
|
268
|
+
files, with the plain name that resolves to `volter-ai-dev.supercode-frontend-vscode`. That folder is
|
|
269
|
+
what `--extensions-dir` wants; `--vsix` runs the stock packer over it.
|
|
270
|
+
|
|
271
|
+
`scripts/pack-extension.mjs` **ships inside the published package**, so a host that bundles this
|
|
272
|
+
extension into its own build runs the same transform rather than reimplementing it:
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
node node_modules/@volter/supercode-frontend-vscode/scripts/pack-extension.mjs \
|
|
276
|
+
--out <build>/extensions/supercode-chat
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
The installed package is already built (`dist/` and `vendor/` are in `files`), so the script writes
|
|
280
|
+
the extension manifest beside those bytes with no build step of its own.
|
|
281
|
+
|
|
282
|
+
Conversation creation uses `ChatSessionItemController.newChatSessionItemHandler`.
|
|
283
|
+
Permissions use `getChatSessionInputState` and its change/disposal events, with
|
|
284
|
+
per-conversation preferences persisted in workspace state. New untitled chats
|
|
285
|
+
start with Ask; old untitled bindings are read only for backwards compatibility.
|
|
286
|
+
Restored response cancellation interrupts the runtime and waits for acknowledgement.
|
|
287
|
+
Session-list updates consistently prioritize pending input over busy status.
|
|
288
|
+
|
|
289
|
+
The pinned Code-OSS host needs a resource-scoping repair in its input-state change
|
|
290
|
+
routing: stock code broadcasts changes to all input states of a provider. The
|
|
291
|
+
Volter overlay guards this fix with a native regression test. The former legacy
|
|
292
|
+
option-notification queue is no longer required. The narrow reveal/focus bridge
|
|
293
|
+
remains because the native model catalogue and participant identity are provider-wide.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Resolve only explicit saved bindings. Never substitute the currently active runtime. */
|
|
2
|
+
export declare function resolveSessionBinding(resource: {
|
|
3
|
+
authority: string;
|
|
4
|
+
toString(): string;
|
|
5
|
+
}, sessions: readonly {
|
|
6
|
+
id: string;
|
|
7
|
+
}[], aliases: ReadonlyMap<string, string>): string | undefined;
|
package/dist/bindings.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Resolve only explicit saved bindings. Never substitute the currently active runtime. */
|
|
2
|
+
export function resolveSessionBinding(resource, sessions, aliases) {
|
|
3
|
+
const candidate = sessions.some(session => session.id === resource.authority) ? resource.authority : aliases.get(resource.toString());
|
|
4
|
+
return candidate && sessions.some(session => session.id === candidate) ? candidate : undefined;
|
|
5
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FrontendClient } from '../vendor/supercode-frontend/client.mjs';
|
|
2
|
+
export type FrontendPermission = 'observe' | 'interact' | 'approve' | 'terminate';
|
|
3
|
+
export interface VscodeFrontendConnection {
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
token: string;
|
|
6
|
+
clientId?: string;
|
|
7
|
+
permissions?: FrontendPermission[];
|
|
8
|
+
fetch?: typeof globalThis.fetch;
|
|
9
|
+
}
|
|
10
|
+
/** Create the only runtime-facing object this presentation package may own. */
|
|
11
|
+
export declare function createFrontendClient(connection: VscodeFrontendConnection): FrontendClient;
|
package/dist/client.js
ADDED
package/dist/context.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function withTextContext(prompt, entries) {
|
|
2
|
+
let total = 0;
|
|
3
|
+
for (const entry of entries) {
|
|
4
|
+
const size = new TextEncoder().encode(entry.text).length;
|
|
5
|
+
if (size > 256 * 1024)
|
|
6
|
+
throw new Error(`Attachment ${entry.name} exceeds 256 KiB. Select a smaller range.`);
|
|
7
|
+
total += size;
|
|
8
|
+
if (entry.text.includes('\0'))
|
|
9
|
+
throw new Error(`Attachment ${entry.name} is binary; only text context is supported.`);
|
|
10
|
+
}
|
|
11
|
+
if (total > 1024 * 1024)
|
|
12
|
+
throw new Error('Attached text exceeds 1 MiB. Remove files or select smaller ranges.');
|
|
13
|
+
return entries.length ? `${prompt}\n\nAttached context (user-supplied document contents):\n${JSON.stringify(entries)}` : prompt;
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read one scoped runtime credential from the file the runtime handed over.
|
|
3
|
+
*
|
|
4
|
+
* The rules are `@volter/supercode-frontend-pi`'s (`bin/credential.mjs`):
|
|
5
|
+
* a regular, private, single-linked file of exactly 64 lowercase hex bytes,
|
|
6
|
+
* re-checked across the open so a path swap cannot slip a different secret in.
|
|
7
|
+
* The bytes never enter argv, a setting, or a URL.
|
|
8
|
+
*
|
|
9
|
+
* It differs from pi's in ONE way, deliberately: pi UNLINKS the file, because a
|
|
10
|
+
* pi process is exactly one attachment. An extension host is not — it reloads,
|
|
11
|
+
* and a reload must not lose the runtime it was handed. Nor can it keep the
|
|
12
|
+
* secret instead: `SecretStorage` is in-memory in a host with no credential
|
|
13
|
+
* provider (VS Code Web), and there is no durable place an extension may put a
|
|
14
|
+
* bearer token that is better than the runtime's own private 0600 file. So the
|
|
15
|
+
* file is READ and left alone; its lifetime is the runtime's to manage.
|
|
16
|
+
*/
|
|
17
|
+
export declare function readCredentialFile(path: string): Promise<string>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { constants } from 'node:fs';
|
|
2
|
+
import { lstat, open } from 'node:fs/promises';
|
|
3
|
+
/**
|
|
4
|
+
* Read one scoped runtime credential from the file the runtime handed over.
|
|
5
|
+
*
|
|
6
|
+
* The rules are `@volter/supercode-frontend-pi`'s (`bin/credential.mjs`):
|
|
7
|
+
* a regular, private, single-linked file of exactly 64 lowercase hex bytes,
|
|
8
|
+
* re-checked across the open so a path swap cannot slip a different secret in.
|
|
9
|
+
* The bytes never enter argv, a setting, or a URL.
|
|
10
|
+
*
|
|
11
|
+
* It differs from pi's in ONE way, deliberately: pi UNLINKS the file, because a
|
|
12
|
+
* pi process is exactly one attachment. An extension host is not — it reloads,
|
|
13
|
+
* and a reload must not lose the runtime it was handed. Nor can it keep the
|
|
14
|
+
* secret instead: `SecretStorage` is in-memory in a host with no credential
|
|
15
|
+
* provider (VS Code Web), and there is no durable place an extension may put a
|
|
16
|
+
* bearer token that is better than the runtime's own private 0600 file. So the
|
|
17
|
+
* file is READ and left alone; its lifetime is the runtime's to manage.
|
|
18
|
+
*/
|
|
19
|
+
export async function readCredentialFile(path) {
|
|
20
|
+
let handle;
|
|
21
|
+
try {
|
|
22
|
+
const before = await lstat(path);
|
|
23
|
+
if (!before.isFile() || before.isSymbolicLink()) {
|
|
24
|
+
throw new Error('scoped frontend credential must be a regular file');
|
|
25
|
+
}
|
|
26
|
+
handle = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
27
|
+
const metadata = await handle.stat();
|
|
28
|
+
const current = await lstat(path);
|
|
29
|
+
if (!metadata.isFile() ||
|
|
30
|
+
before.nlink !== 1 ||
|
|
31
|
+
metadata.nlink !== 1 ||
|
|
32
|
+
before.dev !== metadata.dev ||
|
|
33
|
+
before.ino !== metadata.ino ||
|
|
34
|
+
current.dev !== metadata.dev ||
|
|
35
|
+
current.ino !== metadata.ino) {
|
|
36
|
+
throw new Error('scoped frontend credential path changed while opening');
|
|
37
|
+
}
|
|
38
|
+
if (typeof process.getuid === 'function' &&
|
|
39
|
+
(metadata.uid !== process.getuid() || (metadata.mode & 0o077) !== 0)) {
|
|
40
|
+
throw new Error('scoped frontend credential file must be private to the current user');
|
|
41
|
+
}
|
|
42
|
+
if (metadata.size !== 64)
|
|
43
|
+
throw new Error('scoped frontend credential is malformed');
|
|
44
|
+
const bytes = await handle.readFile();
|
|
45
|
+
if (!bytes.every((byte) => (byte >= 48 && byte <= 57) || (byte >= 97 && byte <= 102))) {
|
|
46
|
+
throw new Error('scoped frontend credential is malformed');
|
|
47
|
+
}
|
|
48
|
+
return bytes.toString('ascii');
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
await handle?.close().catch(() => { });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as vscode from 'vscode';
|
|
2
|
+
import { type FrontendPermission } from './client.js';
|
|
3
|
+
import { type ProposalName } from './proposals.js';
|
|
4
|
+
export interface SupercodeConnectOptions {
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
token?: string;
|
|
7
|
+
permissions?: FrontendPermission[];
|
|
8
|
+
clientId?: string;
|
|
9
|
+
connectionId?: string;
|
|
10
|
+
label?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Why there is no runtime, in the HOST's own words, when it could not start
|
|
13
|
+
* one. Given this instead of a `baseUrl`, nothing is attached and the
|
|
14
|
+
* sentence is what the Chat view shows.
|
|
15
|
+
*/
|
|
16
|
+
unavailable?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface SupercodeConnectionInfo {
|
|
19
|
+
readonly id: string;
|
|
20
|
+
readonly baseUrl: string;
|
|
21
|
+
readonly label: string;
|
|
22
|
+
readonly sessionId: string | null;
|
|
23
|
+
readonly model: string | null;
|
|
24
|
+
readonly turnState: 'idle' | 'busy' | null;
|
|
25
|
+
}
|
|
26
|
+
/** What `activate` returns: the door a host uses to hand over a connection. */
|
|
27
|
+
export interface SupercodeFrontendApi {
|
|
28
|
+
/**
|
|
29
|
+
* Attach to a runtime, or — with `{unavailable}` and no `baseUrl` — say why
|
|
30
|
+
* there is none, in the host's own words, and have that shown on screen.
|
|
31
|
+
*/
|
|
32
|
+
connect(options: SupercodeConnectOptions): Promise<SupercodeConnectionInfo | undefined>;
|
|
33
|
+
disconnect(connectionId?: string): Promise<void>;
|
|
34
|
+
readonly connections: readonly SupercodeConnectionInfo[];
|
|
35
|
+
readonly grantedProposals: readonly ProposalName[];
|
|
36
|
+
onDidChangeConnections: vscode.Event<void>;
|
|
37
|
+
}
|
|
38
|
+
export declare function activate(context: vscode.ExtensionContext): Promise<SupercodeFrontendApi>;
|
|
39
|
+
export declare function deactivate(): void;
|