@volter/supercode-frontend-vscode 0.1.58 → 0.1.61

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.
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The workbench's tools, for the harness agents in its Chat (ADR 0255).
3
+ *
4
+ * The host names the tools and where they run (`HostState.workbenchTools`: a tag, a socket and a token), and lists
5
+ * them to its runtimes itself, through `supercode mcp workbench`. That relay calls them here: this serves, in the
6
+ * extension host where `vscode.lm` exists, one call of a tool `vscode.lm.tools` lists with the tag, through
7
+ * Code-OSS's own tool service. With no endpoint it serves nothing.
8
+ *
9
+ * The wire is one JSON line each way per connection: `{token, name, input}` → `{text, isError}`.
10
+ */
11
+ import { rmSync } from 'node:fs';
12
+ import { createServer } from 'node:net';
13
+ import * as vscode from 'vscode';
14
+ export class WorkbenchTools {
15
+ #served;
16
+ #applying = Promise.resolve();
17
+ /** Serve on the endpoint the host names, or nothing when it names none. A changed endpoint replaces the server.
18
+ * Calls apply in order, so two quick state replies cannot race a close against a listen. */
19
+ serve(endpoint) {
20
+ const applying = this.#applying.then(() => this.apply(endpoint));
21
+ this.#applying = applying.catch(() => undefined);
22
+ return applying;
23
+ }
24
+ async apply(endpoint) {
25
+ const current = this.#served?.endpoint;
26
+ if (current?.tag === endpoint?.tag && current?.socket === endpoint?.socket && current?.token === endpoint?.token)
27
+ return;
28
+ this.close();
29
+ if (!endpoint)
30
+ return;
31
+ // A socket left by an earlier extension host of this window is this window's own.
32
+ if (process.platform !== 'win32')
33
+ rmSync(endpoint.socket, { force: true });
34
+ const server = createServer(socket => this.answer(socket, endpoint));
35
+ await new Promise((resolve, reject) => { server.once('error', reject); server.listen(endpoint.socket, resolve); });
36
+ this.#served = { endpoint, server };
37
+ }
38
+ answer(socket, endpoint) {
39
+ let buffer = '';
40
+ socket.setEncoding('utf8');
41
+ socket.on('data', chunk => {
42
+ buffer += chunk;
43
+ const end = buffer.indexOf('\n');
44
+ if (end < 0)
45
+ return;
46
+ const line = buffer.slice(0, end);
47
+ buffer = '';
48
+ void this.handle(line, endpoint).then(reply => socket.end(`${JSON.stringify(reply)}\n`));
49
+ });
50
+ socket.on('error', () => socket.destroy());
51
+ }
52
+ async handle(line, { tag, token }) {
53
+ let request;
54
+ try {
55
+ request = JSON.parse(line);
56
+ }
57
+ catch {
58
+ return { text: 'The call was not JSON.', isError: true };
59
+ }
60
+ if (request.token !== token)
61
+ return { text: 'The call was refused.', isError: true };
62
+ const name = request.name;
63
+ if (typeof name !== 'string' || !vscode.lm.tools.some(tool => tool.name === name && tool.tags.includes(tag))) {
64
+ return { text: `The workbench has no tool ${String(name)}.`, isError: true };
65
+ }
66
+ try {
67
+ const input = request.input && typeof request.input === 'object' ? request.input : {};
68
+ const result = await vscode.lm.invokeTool(name, { input, toolInvocationToken: undefined });
69
+ return { text: result.content.map(part => part instanceof vscode.LanguageModelTextPart ? part.value : '').join(''), isError: false };
70
+ }
71
+ catch (error) {
72
+ return { text: error instanceof Error ? error.message : String(error), isError: true };
73
+ }
74
+ }
75
+ close() {
76
+ const served = this.#served;
77
+ this.#served = undefined;
78
+ served?.server.close();
79
+ }
80
+ dispose() { this.close(); }
81
+ }
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 3.5 26 18" fill="#d97757"><path fill-rule="evenodd" d="M20.998 10.949H24v3.102h-3v3.028h-1.487V20H18v-2.921h-1.487V20H15v-2.921H9V20H7.488v-2.921H6V20H4.487v-2.921H3V14.05H0V10.95h3V5h17.998v5.949zM6 10.949h1.488V8.102H6v2.847zm10.51 0H18V8.102h-1.49v2.847z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 3.5 26 18" fill="#d97757"><path fill-rule="evenodd" clip-rule="evenodd" d="M20.998 10.949H24v3.102h-3v3.028h-1.487V20H18v-2.921h-1.487V20H15v-2.921H9V20H7.488v-2.921H6V20H4.487v-2.921H3V14.05H0V10.95h3V5h17.998v5.949zM6 10.949h1.488V8.102H6v2.847zm10.51 0H18V8.102h-1.49v2.847z"/></svg>
package/media/google.svg CHANGED
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#8b8df8"><path fill-rule="evenodd" d="M12 24C12 17.373 6.627 12 0 12 6.627 12 12 6.627 12 0c0 6.627 5.373 12 12 12-6.627 0-12 5.373-12 12z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#8b8df8"><path d="M12 24C12 17.373 6.627 12 0 12 6.627 12 12 6.627 12 0c0 6.627 5.373 12 12 12-6.627 0-12 5.373-12 12z"/></svg>
package/media/openai.svg CHANGED
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 -1 26 26" fill="#aaaaaa"><path fill-rule="evenodd" d="M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 -1 26 26" fill="#aaaaaa"><path fill-rule="evenodd" clip-rule="evenodd" d="M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z"/></svg>
package/media/xai.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 -1 26 26" fill="#aaaaaa"><path d="M9.27 15.29l7.978-5.897c.391-.29.95-.177 1.137.272.98 2.369.542 5.215-1.41 7.169-1.951 1.954-4.667 2.382-7.149 1.406l-2.711 1.257c3.889 2.661 8.611 2.003 11.562-.953 2.341-2.344 3.066-5.539 2.388-8.42l.006.007c-.983-4.232.242-5.924 2.75-9.383.06-.082.12-.164.179-.248l-3.301 3.305v-.01L9.267 15.292M7.623 16.723c-2.792-2.67-2.31-6.801.071-9.184 1.761-1.763 4.647-2.483 7.166-1.425l2.705-1.25a7.808 7.808 0 00-1.829-1A8.975 8.975 0 005.984 5.83c-2.533 2.536-3.33 6.436-1.962 9.764 1.022 2.487-.653 4.246-2.34 6.022-.599.63-1.199 1.259-1.682 1.925l7.62-6.815"/></svg>
package/package.json CHANGED
@@ -1,9 +1,14 @@
1
1
  {
2
2
  "name": "@volter/supercode-frontend-vscode",
3
3
  "displayName": "Volter Harness",
4
- "version": "0.1.58",
4
+ "version": "0.1.61",
5
5
  "type": "module",
6
6
  "description": "VS Code chat-view frontend for an existing Volter Harness SDK runtime",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/volter-ai/supercode.git",
10
+ "directory": "sdk/frontend-vscode"
11
+ },
7
12
  "publisher": "volter-ai-dev",
8
13
  "icon": "media/icon.png",
9
14
  "main": "./dist/extension.js",
@@ -23,7 +28,7 @@
23
28
  "media"
24
29
  ],
25
30
  "scripts": {
26
- "build": "node -e \"require('node:fs').rmSync('dist', {recursive:true, force:true})\" && node scripts/sync-sdk.mjs && node scripts/fetch-icon.mjs && tsc -p tsconfig.build.json",
31
+ "build": "node -e \"require('node:fs').rmSync('dist', {recursive:true, force:true})\" && node scripts/sync-sdk.mjs && node scripts/fetch-icon.mjs && node scripts/sync-marks.mjs && tsc -p tsconfig.build.json",
27
32
  "extension": "npm run build && node scripts/pack-extension.mjs",
28
33
  "prepack": "npm run build"
29
34
  },
@@ -50,6 +55,20 @@
50
55
  "chatProvider"
51
56
  ],
52
57
  "contributes": {
58
+ "taskDefinitions": [
59
+ {
60
+ "type": "supercode-setup",
61
+ "required": [
62
+ "operation"
63
+ ],
64
+ "properties": {
65
+ "operation": {
66
+ "type": "string",
67
+ "description": "The identity of this coding-agent installation."
68
+ }
69
+ }
70
+ }
71
+ ],
53
72
  "chatParticipants": [
54
73
  {
55
74
  "id": "supercode.chat",
@@ -190,7 +209,7 @@
190
209
  },
191
210
  "supercodeFrontendContract": {
192
211
  "name": "@volter/supercode-frontend",
193
- "version": "0.2.9",
212
+ "version": "0.2.12",
194
213
  "mode": "generated-artifact"
195
214
  },
196
215
  "license": "MIT OR Apache-2.0"
@@ -16,6 +16,33 @@ race; HTTP uses bearer authentication plus an opaque client id and permission
16
16
  narrowing. Dropping or detaching this client never closes the runtime;
17
17
  `close()` is a separate terminate-authorized action.
18
18
 
19
+ Attachment snapshots may include `pending_requests`, the complete open-request
20
+ set at the snapshot boundary. An empty array means no request remains open;
21
+ absence denotes an older runtime with replay-derived state. IDs and payloads
22
+ come from the runtime; responses still use its existing exactly-once door.
23
+
24
+ `snapshot_cursor` is the full capture boundary of the descriptor and request
25
+ set, taken under the native projection lock. It differs from `history_cursor`,
26
+ which covers only canonical and retired display history. Transports return
27
+ unfiltered snapshot replay even when an existing stream has advanced.
28
+ `mergeFrontendSnapshot` joins later retained observations in sequence order;
29
+ an evicted suffix refuses attachment with `event_gap`. The view installs the
30
+ authoritative request set at capture before applying later requests, resolutions
31
+ and native turn boundaries. Legacy snapshots without the added cursor retain
32
+ their full replay boundary, and absence of the request set retains legacy
33
+ replay-derived behavior. These display fields grant no response authority.
34
+
35
+ `history_events` contains original sequenced events retired from replay since
36
+ canonical history was last captured. `history_cursor` covers canonical history
37
+ and these historical events; live/replay delivery begins after that boundary.
38
+ Historical events are display data, not events to send back to a harness. Both
39
+ native projection paths retain up to 4096 retired events plus 4096 replay events.
40
+ `history_events_dropped` counts older events outside that display window. A
41
+ frontend must show that omission rather than claim a complete transcript.
42
+ Canonical message history remains bounded separately. Open requests survive
43
+ independently of both event windows. Older clients need updating to render these
44
+ additive snapshot fields. See the [source and compilation record](../../docs/evidence/frontend-pending-snapshot-2026-10-10.md).
45
+
19
46
  `FrontendWebSocketClient.connect()` requires a header-capable socket factory
20
47
  and passes the bearer token only in `Authorization`. It deliberately has no
21
48
  query-token fallback: browser clients that cannot set upgrade headers use the
@@ -33,6 +33,12 @@ export class FrontendClientError extends Error {
33
33
  extensions: Record<string, unknown>;
34
34
  }
35
35
 
36
+ /** Native snapshot plus contiguous later retained events; refuses an evicted suffix. */
37
+ export function mergeFrontendSnapshot(snapshot: FrontendAttachSnapshot, observed?: readonly FrontendEvent[], cursor?: number): {
38
+ captureCursor: number; cursor: number; replay: FrontendEvent[]; later: FrontendEvent[];
39
+ };
40
+ export function projectFrontendDescriptor(descriptor: FrontendRuntimeDescriptor | null, event: FrontendEvent): FrontendRuntimeDescriptor | null;
41
+
36
42
  export class FrontendClient extends GeneratedFrontendClient<RequestOptions> {
37
43
  constructor(options: FrontendClientOptions);
38
44
  readonly clientId: string;
@@ -43,7 +49,7 @@ export class FrontendClient extends GeneratedFrontendClient<RequestOptions> {
43
49
  attach(limit?: number, options?: RequestOptions & {afterSequence?: number}): Promise<FrontendAttachSnapshot>;
44
50
  sendInput(prompt: string, options?: RequestOptions): Promise<{accepted:boolean}>;
45
51
  submit(prompt: string, options?: RequestOptions): Promise<{reply:string}>;
46
- interrupt(options?: RequestOptions): Promise<{interrupted:boolean}>;
52
+ interrupt(options?: RequestOptions & {expectedTurn?: string}): Promise<{interrupted:boolean}>;
47
53
  steer(prompt: string, options?: RequestOptions): Promise<{queued:boolean}>;
48
54
  respond(response: FrontendResponse, options?: RequestOptions): Promise<{accepted:boolean}>;
49
55
  invoke(operation: FrontendOperationInvocation, options?: RequestOptions): Promise<FrontendOperationResult>;
@@ -69,6 +69,79 @@ function validateDescriptor(descriptor) {
69
69
  return descriptor;
70
70
  }
71
71
 
72
+ function validateSnapshot(snapshot) {
73
+ validateDescriptor(snapshot?.descriptor);
74
+ if (!Array.isArray(snapshot.history) || !Array.isArray(snapshot.replay)
75
+ || !Number.isSafeInteger(snapshot.history_cursor) || snapshot.history_cursor < 0
76
+ || (snapshot.history_events !== undefined && !Array.isArray(snapshot.history_events))
77
+ || (snapshot.history_events_dropped !== undefined
78
+ && (!Number.isSafeInteger(snapshot.history_events_dropped) || snapshot.history_events_dropped < 0))) {
79
+ throw new FrontendClientError({name:'transport', message:'invalid frontend attachment snapshot'});
80
+ }
81
+ let previous = 0;
82
+ for (const event of snapshot.history_events ?? []) {
83
+ if (!Number.isSafeInteger(event?.sequence) || event.sequence <= previous
84
+ || (previous > 0 && event.sequence !== previous + 1)
85
+ || event.sequence > snapshot.history_cursor || typeof event.kind !== 'string') {
86
+ throw new FrontendClientError({name:'transport', message:'invalid frontend historical event boundary'});
87
+ }
88
+ previous = event.sequence;
89
+ }
90
+ if (previous > 0 && previous !== snapshot.history_cursor) {
91
+ throw new FrontendClientError({name:'transport', message:'incomplete frontend historical event boundary'});
92
+ }
93
+ }
94
+
95
+ /** Join a captured snapshot to retained later observations without changing
96
+ * the native capture point. A missing bounded suffix refuses attachment. */
97
+ export function mergeFrontendSnapshot(snapshot, observed = [], cursor = 0) {
98
+ validateSnapshot(snapshot);
99
+ const captureCursor = snapshot.snapshot_cursor ?? Math.max(snapshot.history_cursor,
100
+ ...snapshot.replay.map(event => event.sequence));
101
+ if (!Number.isSafeInteger(captureCursor) || captureCursor < snapshot.history_cursor
102
+ || !Number.isSafeInteger(cursor) || cursor < 0) {
103
+ throw new FrontendClientError({name:'transport', message:'invalid frontend snapshot capture boundary'});
104
+ }
105
+ const events = new Map();
106
+ for (const event of snapshot.replay) {
107
+ if (!Number.isSafeInteger(event?.sequence) || event.sequence <= snapshot.history_cursor
108
+ || event.sequence > captureCursor || typeof event.kind !== 'string') {
109
+ throw new FrontendClientError({name:'transport', message:'invalid frontend snapshot replay boundary'});
110
+ }
111
+ events.set(event.sequence, event);
112
+ }
113
+ for (const event of observed) {
114
+ if (event.sequence > captureCursor) events.set(event.sequence, event);
115
+ }
116
+ const replay = [...events.values()].sort((a, b) => a.sequence - b.sequence);
117
+ const later = replay.filter(event => event.sequence > captureCursor);
118
+ let projected = captureCursor;
119
+ for (const event of later) {
120
+ if (!Number.isSafeInteger(event.sequence) || event.sequence !== projected + 1) {
121
+ throw new FrontendClientError({name:'event_gap', message:`frontend snapshot needs event ${projected + 1}, received ${event.sequence}`});
122
+ }
123
+ projected = event.sequence;
124
+ }
125
+ if (projected < cursor) {
126
+ throw new FrontendClientError({name:'event_gap', message:`frontend snapshot lost retained events ${projected + 1} through ${cursor}`});
127
+ }
128
+ return {captureCursor, cursor:projected, replay, later};
129
+ }
130
+
131
+ /** Project only native lifecycle events; a model round-trip does not idle a turn. */
132
+ export function projectFrontendDescriptor(descriptor, event) {
133
+ if (!descriptor) return descriptor;
134
+ if (event.kind === 'runtime_disconnected') {
135
+ return {...descriptor, connection_state:'shutting_down', turn_state:'idle',
136
+ actions:{...descriptor.actions, submit:false, steer:false, interrupt:false, respond:false}};
137
+ }
138
+ if (['turn_succeeded', 'turn_failed', 'turn_interrupted'].includes(event.kind)) {
139
+ return {...descriptor, turn_state:'idle'};
140
+ }
141
+ if (['turn_started', 'user_message'].includes(event.kind)) return {...descriptor, turn_state:'busy'};
142
+ return descriptor;
143
+ }
144
+
72
145
  export class FrontendClient extends FrontendEmitter {
73
146
  #baseUrl;
74
147
  #token;
@@ -132,13 +205,12 @@ export class FrontendClient extends FrontendEmitter {
132
205
  this.stopEvents();
133
206
  throw error;
134
207
  }
135
- validateDescriptor(snapshot?.descriptor);
136
- if (!Array.isArray(snapshot.history) || !Array.isArray(snapshot.replay) || !Number.isSafeInteger(snapshot.history_cursor)) {
137
- throw new FrontendClientError({ name: 'transport', message: 'invalid frontend attachment snapshot' });
138
- }
139
- this.#lastSequence = Math.max(this.#lastSequence, afterSequence, snapshot.history_cursor);
140
- snapshot.replay = snapshot.replay.filter((event) => event.sequence > this.#lastSequence);
141
- for (const event of snapshot.replay) this.#lastSequence = Math.max(this.#lastSequence, event.sequence);
208
+ validateSnapshot(snapshot);
209
+ // Replay remains the native snapshot's, even if a running stream already
210
+ // delivered some of it. View adapters need that capture boundary to merge
211
+ // their later observations; the stream's delivery cursor cannot erase it.
212
+ const {captureCursor} = mergeFrontendSnapshot(snapshot);
213
+ this.#lastSequence = Math.max(this.#lastSequence, afterSequence, captureCursor);
142
214
  return snapshot;
143
215
  }
144
216
  async #openEventStream(afterSequence, signal) {
@@ -288,10 +360,12 @@ export class FrontendWebSocketClient extends FrontendEmitter {
288
360
  async describe() { return validateDescriptor(await this.rpc(FRONTEND_METHODS.describe)); }
289
361
  async attach(limit = 1000, {afterSequence = 0} = {}) {
290
362
  const snapshot = await this.rpc(FRONTEND_METHODS.attach, {limit, after_sequence:afterSequence});
291
- validateDescriptor(snapshot?.descriptor);
292
- this.#lastSequence = Math.max(this.#lastSequence, afterSequence, snapshot.history_cursor);
293
- snapshot.replay = snapshot.replay.filter((event) => event.sequence > this.#lastSequence);
294
- for (const event of snapshot.replay) this.#lastSequence = Math.max(this.#lastSequence, event.sequence);
363
+ validateSnapshot(snapshot);
364
+ // Replay remains the native snapshot's, even if a running stream already
365
+ // delivered some of it. View adapters need that capture boundary to merge
366
+ // their later observations; the stream's delivery cursor cannot erase it.
367
+ const {captureCursor} = mergeFrontendSnapshot(snapshot);
368
+ this.#lastSequence = Math.max(this.#lastSequence, afterSequence, captureCursor);
295
369
  return snapshot;
296
370
  }
297
371
  async detach() { const result = await this.rpc(FRONTEND_METHODS.detach); this.closeTransport(); return result; }
@@ -28,6 +28,7 @@
28
28
  "types": {
29
29
  "JsonValue": {"kind": "json"},
30
30
  "Empty": {"kind": "object", "fields": {}},
31
+ "InterruptParams": {"kind": "object", "fields": {"expected_turn": {"type": "string", "optional": true}}},
31
32
  "ChatRole": {"kind": "enum", "values": ["system", "user", "assistant", "tool"]},
32
33
  "FunctionCall": {
33
34
  "kind": "object",
@@ -143,7 +144,11 @@
143
144
  "descriptor": {"type": "FrontendRuntimeDescriptor"},
144
145
  "history": {"type": {"kind": "array", "items": "ChatMessage"}},
145
146
  "history_cursor": {"type": "u64"},
146
- "replay": {"type": {"kind": "array", "items": "FrontendEvent"}}
147
+ "snapshot_cursor": {"type": "u64", "optional": true, "nullable": true},
148
+ "history_events": {"type": {"kind": "array", "items": "FrontendEvent"}, "optional": true},
149
+ "history_events_dropped": {"type": "u64", "optional": true},
150
+ "replay": {"type": {"kind": "array", "items": "FrontendEvent"}},
151
+ "pending_requests": {"type": {"kind": "array", "items": "FrontendRequest"}, "optional": true, "nullable": true}
147
152
  }
148
153
  },
149
154
  "FrontendApprovalDecision": {"kind": "enum", "values": ["deny", "allow", "allow_for_session"]},
@@ -260,7 +265,7 @@
260
265
  {"id": "attach", "wire": "frontend.v2.attach", "sdk_action": "events", "params": "AttachParams", "result": "FrontendAttachSnapshot", "transports": ["local", "http", "websocket", "acp"]},
261
266
  {"id": "send_input", "wire": "frontend.v2.send_input", "sdk_action": "input", "params": "MultimodalPromptParams", "result": "Accepted", "transports": ["local", "http", "websocket", "acp"]},
262
267
  {"id": "submit", "wire": "frontend.v2.submit", "sdk_action": "input", "params": "MultimodalPromptParams", "result": "Reply", "compatibility": "completion_waiting_alias", "transports": ["local", "http", "websocket", "acp"]},
263
- {"id": "interrupt", "wire": "frontend.v2.interrupt", "sdk_action": "interrupt", "params": "Empty", "result": "Interrupted", "transports": ["local", "http", "websocket", "acp"]},
268
+ {"id": "interrupt", "wire": "frontend.v2.interrupt", "sdk_action": "interrupt", "params": "InterruptParams", "result": "Interrupted", "transports": ["local", "http", "websocket", "acp"]},
264
269
  {"id": "steer", "wire": "frontend.v2.steer", "sdk_action": "steer", "params": "PromptParams", "result": "Queued", "transports": ["local", "http", "websocket", "acp"]},
265
270
  {"id": "stop_task", "wire": "frontend.v2.stop_task", "sdk_action": "stop_task", "params": "TaskParams", "result": "Stopped", "transports": ["local", "http", "websocket", "acp"]},
266
271
  {"id": "respond", "wire": "frontend.v2.respond", "sdk_action": "respond", "params": "RespondParams", "result": "Accepted", "transports": ["local", "http", "websocket", "acp"]},
@@ -6,7 +6,7 @@ export abstract class GeneratedFrontendClient<Options = unknown> {
6
6
  attach(limit?: number, options?: Options & {afterSequence?: number}): Promise<FrontendAttachSnapshot>;
7
7
  sendInput(prompt: string, options?: Options & {imageUrls?: string[]}): Promise<Accepted>;
8
8
  submit(prompt: string, options?: Options & {imageUrls?: string[]}): Promise<Reply>;
9
- interrupt(options?: Options): Promise<Interrupted>;
9
+ interrupt(options?: Options & {expectedTurn?: string}): Promise<Interrupted>;
10
10
  steer(prompt: string, options?: Options): Promise<Queued>;
11
11
  stopTask(taskId: string, options?: Options): Promise<Stopped>;
12
12
  respond(response: FrontendResponse, options?: Options): Promise<Accepted>;
@@ -7,7 +7,7 @@ export class GeneratedFrontendClient {
7
7
  attach(limit = 1000, { afterSequence, ...options } = {}) { return this.rpc(FRONTEND_METHODS.attach, { limit, ...(afterSequence === undefined ? {} : {after_sequence:afterSequence}) }, options); }
8
8
  sendInput(prompt, { imageUrls, ...options } = {}) { return this.rpc(FRONTEND_METHODS.send_input, { prompt, ...(imageUrls?.length ? {image_urls:imageUrls} : {}) }, options); }
9
9
  submit(prompt, { imageUrls, ...options } = {}) { return this.rpc(FRONTEND_METHODS.submit, { prompt, ...(imageUrls?.length ? {image_urls:imageUrls} : {}) }, options); }
10
- interrupt(options) { return this.rpc(FRONTEND_METHODS.interrupt, {}, options); }
10
+ interrupt({ expectedTurn, ...options } = {}) { return this.rpc(FRONTEND_METHODS.interrupt, { ...(expectedTurn === undefined ? {} : { expected_turn: expectedTurn }) }, options); }
11
11
  steer(prompt, options) { return this.rpc(FRONTEND_METHODS.steer, { prompt }, options); }
12
12
  stopTask(taskId, options) { return this.rpc(FRONTEND_METHODS.stop_task, { task_id: taskId }, options); }
13
13
  respond(response, options) { return this.rpc(FRONTEND_METHODS.respond, { response }, options); }
@@ -2,6 +2,9 @@
2
2
  export type JsonPrimitive = string | number | boolean | null;
3
3
  export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue };
4
4
  export type Empty = Record<string, never>;
5
+ export interface InterruptParams {
6
+ expected_turn?: string;
7
+ }
5
8
  export type ChatRole = "system" | "user" | "assistant" | "tool";
6
9
  export interface FunctionCall {
7
10
  name: string;
@@ -79,7 +82,11 @@ export interface FrontendAttachSnapshot {
79
82
  descriptor: FrontendRuntimeDescriptor;
80
83
  history: ChatMessage[];
81
84
  history_cursor: number;
85
+ snapshot_cursor?: number | null;
86
+ history_events?: FrontendEvent[];
87
+ history_events_dropped?: number;
82
88
  replay: FrontendEvent[];
89
+ pending_requests?: FrontendRequest[] | null;
83
90
  }
84
91
  export type FrontendApprovalDecision = "deny" | "allow" | "allow_for_session";
85
92
  export type FrontendElicitationAction = "accept" | "decline" | "cancel";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-frontend",
3
- "version": "0.2.9",
3
+ "version": "0.2.12",
4
4
  "type": "module",
5
5
  "description": "Generated language-neutral attachment client for Volter Harness SDK runtimes",
6
6
  "exports": {