@volter/supercode-frontend-vscode 0.1.58 → 0.1.60
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 +1 -1
- package/README.md +112 -10
- package/dist/chat-setup.js +3 -2
- package/dist/extension.js +237 -44
- package/dist/host-controls.d.ts +4 -0
- package/dist/host-controls.js +4 -1
- package/dist/native-session.d.ts +2 -2
- package/dist/native-session.js +10 -1
- package/dist/session.d.ts +9 -0
- package/dist/session.js +134 -22
- package/dist/setup-flow.js +10 -2
- package/dist/view.d.ts +4 -2
- package/dist/view.js +34 -10
- package/media/anthropic.svg +1 -1
- package/media/google.svg +1 -1
- package/media/openai.svg +1 -1
- package/media/xai.svg +1 -0
- package/package.json +34 -9
- package/vendor/supercode-frontend/README.md +27 -0
- package/vendor/supercode-frontend/client.d.mts +7 -1
- package/vendor/supercode-frontend/client.mjs +85 -11
- package/vendor/supercode-frontend/contract.json +7 -2
- package/vendor/supercode-frontend/generated-client.d.mts +1 -1
- package/vendor/supercode-frontend/generated-client.mjs +1 -1
- package/vendor/supercode-frontend/generated.d.mts +7 -0
- package/vendor/supercode-frontend/package.json +2 -1
package/dist/native-session.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { TURN_TERMINAL_KINDS } from './session.js';
|
|
2
2
|
export function sessionStatus(session) {
|
|
3
|
-
return session.pendingRequests.size ? 'NeedsInput' : session.descriptor?.turn_state === 'busy' ? 'InProgress' : 'Completed';
|
|
3
|
+
return session.pendingRequests.size ? 'NeedsInput' : session.failure ? 'Failed' : session.descriptor?.turn_state === 'busy' ? 'InProgress' : 'Completed';
|
|
4
4
|
}
|
|
5
5
|
/** Follow a restored turn, including the interval between snapshot and callback. */
|
|
6
6
|
export async function observeActiveResponse(session, token, write, onSubscribed, afterSequence) {
|
|
7
7
|
let unsubscribe = () => { };
|
|
8
|
+
let unfail = () => { };
|
|
9
|
+
let unwatch = () => { };
|
|
8
10
|
let cancellation;
|
|
9
11
|
try {
|
|
10
12
|
await new Promise((resolve, reject) => {
|
|
@@ -43,6 +45,11 @@ export async function observeActiveResponse(session, token, write, onSubscribed,
|
|
|
43
45
|
resolve();
|
|
44
46
|
};
|
|
45
47
|
unsubscribe = session.onEvent(consume);
|
|
48
|
+
unfail = session.onFailure(reject);
|
|
49
|
+
unwatch = session.onChange(() => {
|
|
50
|
+
if (!cancelling && session.descriptor?.turn_state === 'idle' && !session.pendingRequests.size)
|
|
51
|
+
resolve();
|
|
52
|
+
});
|
|
46
53
|
if (afterSequence !== undefined) {
|
|
47
54
|
for (const event of session.replay)
|
|
48
55
|
if (event.sequence > afterSequence)
|
|
@@ -58,6 +65,8 @@ export async function observeActiveResponse(session, token, write, onSubscribed,
|
|
|
58
65
|
}
|
|
59
66
|
finally {
|
|
60
67
|
unsubscribe();
|
|
68
|
+
unfail();
|
|
69
|
+
unwatch();
|
|
61
70
|
cancellation?.dispose();
|
|
62
71
|
}
|
|
63
72
|
}
|
package/dist/session.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { FrontendPermission } from './client.js';
|
|
|
5
5
|
* here is a second protocol: every member is `@volter/supercode-frontend`'s.
|
|
6
6
|
*/
|
|
7
7
|
export interface VscodeRuntimeClient {
|
|
8
|
+
readonly lastSequence?: number;
|
|
8
9
|
describe(): Promise<FrontendRuntimeDescriptor>;
|
|
9
10
|
attach(limit?: number, options?: {
|
|
10
11
|
afterSequence?: number;
|
|
@@ -85,6 +86,8 @@ export declare class VscodeFrontendSession {
|
|
|
85
86
|
descriptor: FrontendRuntimeDescriptor | null;
|
|
86
87
|
history: ChatMessage[];
|
|
87
88
|
historyCursor: number;
|
|
89
|
+
historyEvents: FrontendEvent[];
|
|
90
|
+
historyEventsDropped: number;
|
|
88
91
|
cursor: number;
|
|
89
92
|
/**
|
|
90
93
|
* Every event this attachment has seen, in order — NOT just the last
|
|
@@ -108,6 +111,8 @@ export declare class VscodeFrontendSession {
|
|
|
108
111
|
*/
|
|
109
112
|
resolvedRequests: Map<number, FrontendRequest>;
|
|
110
113
|
connection: 'disconnected' | 'connected' | 'closed';
|
|
114
|
+
/** Exact transport/recovery failure, retained until a fresh attachment succeeds. */
|
|
115
|
+
failure: Error | null;
|
|
111
116
|
/**
|
|
112
117
|
* `frontend.v2`'s descriptor and history carry NO timestamp, so the only
|
|
113
118
|
* true times this frontend knows are its own: when this window attached, and
|
|
@@ -142,6 +147,9 @@ export declare class VscodeFrontendSession {
|
|
|
142
147
|
/** Subscribe to every event this session projects, from now on. */
|
|
143
148
|
onEvent(listener: (event: FrontendEvent) => void): () => void;
|
|
144
149
|
onChange(observer: () => void): () => void;
|
|
150
|
+
/** Surface an actual stream/recovery error without inventing a native event or ending its turn. */
|
|
151
|
+
reportFailure(error: unknown): void;
|
|
152
|
+
onFailure(listener: (error: Error) => void): () => void;
|
|
145
153
|
private projectEvent;
|
|
146
154
|
/** What this attachment may do: permission AND the descriptor's own advertisement. */
|
|
147
155
|
actions(): VscodeFrontendActions;
|
|
@@ -169,6 +177,7 @@ export declare class VscodeFrontendSession {
|
|
|
169
177
|
throwIfCancelled?: () => void;
|
|
170
178
|
onSubmitted?: () => void;
|
|
171
179
|
}): Promise<FrontendEvent | null>;
|
|
180
|
+
private requireOpenAttachment;
|
|
172
181
|
private require;
|
|
173
182
|
private notify;
|
|
174
183
|
}
|
package/dist/session.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mergeFrontendSnapshot } from '../vendor/supercode-frontend/client.mjs';
|
|
1
2
|
/** How long a turn waits on a connection that dropped before saying it was lost (the follow loop's re-attach backs
|
|
2
3
|
* off to 15 s; this outlasts a few of its attempts). */
|
|
3
4
|
const DISCONNECT_GRACE_MS = 60_000;
|
|
@@ -46,6 +47,8 @@ export class VscodeFrontendSession {
|
|
|
46
47
|
descriptor = null;
|
|
47
48
|
history = [];
|
|
48
49
|
historyCursor = 0;
|
|
50
|
+
historyEvents = [];
|
|
51
|
+
historyEventsDropped = 0;
|
|
49
52
|
cursor = 0;
|
|
50
53
|
/**
|
|
51
54
|
* Every event this attachment has seen, in order — NOT just the last
|
|
@@ -69,6 +72,8 @@ export class VscodeFrontendSession {
|
|
|
69
72
|
*/
|
|
70
73
|
resolvedRequests = new Map();
|
|
71
74
|
connection = 'disconnected';
|
|
75
|
+
/** Exact transport/recovery failure, retained until a fresh attachment succeeds. */
|
|
76
|
+
failure = null;
|
|
72
77
|
/**
|
|
73
78
|
* `frontend.v2`'s descriptor and history carry NO timestamp, so the only
|
|
74
79
|
* true times this frontend knows are its own: when this window attached, and
|
|
@@ -82,9 +87,12 @@ export class VscodeFrontendSession {
|
|
|
82
87
|
#subscribed = false;
|
|
83
88
|
#following = null;
|
|
84
89
|
#heartbeat = null;
|
|
90
|
+
#takingControl = null;
|
|
91
|
+
#disposed = false;
|
|
85
92
|
#resyncing = false;
|
|
86
93
|
#listeners = new Set();
|
|
87
94
|
#changeObservers = new Set();
|
|
95
|
+
#failureListeners = new Set();
|
|
88
96
|
#eventListener = (event) => {
|
|
89
97
|
this.applyEvent(event);
|
|
90
98
|
};
|
|
@@ -106,17 +114,49 @@ export class VscodeFrontendSession {
|
|
|
106
114
|
* BEFORE the snapshot RPC, so no event can fall between the two.
|
|
107
115
|
*/
|
|
108
116
|
async attach(limit = 1000) {
|
|
109
|
-
|
|
117
|
+
if (this.#disposed)
|
|
118
|
+
throw new Error('frontend attachment is disposed');
|
|
119
|
+
const snapshot = await this.client.attach(limit, { afterSequence: this.cursor }).catch(error => {
|
|
120
|
+
this.reportFailure(error);
|
|
121
|
+
throw error;
|
|
122
|
+
});
|
|
123
|
+
if (this.#disposed) {
|
|
124
|
+
this.client.stopEvents();
|
|
125
|
+
throw new Error('frontend attachment is disposed');
|
|
126
|
+
}
|
|
127
|
+
const recovery = mergeFrontendSnapshot(snapshot, this.replay, Math.max(this.cursor, this.client.lastSequence ?? 0));
|
|
128
|
+
const authoritative = Array.isArray(snapshot.pending_requests);
|
|
110
129
|
const staged = {
|
|
111
130
|
descriptor: structuredClone(snapshot.descriptor),
|
|
112
|
-
pendingRequests: new Map([...this.pendingRequests
|
|
131
|
+
pendingRequests: new Map((authoritative ? snapshot.pending_requests : [...this.pendingRequests.values()])
|
|
132
|
+
.map(request => [request.id, structuredClone(request)])),
|
|
113
133
|
backgroundTasks: structuredClone(this.backgroundTasks),
|
|
114
|
-
cursor: Math.max(this.cursor, snapshot.history_cursor),
|
|
134
|
+
cursor: authoritative ? recovery.captureCursor : Math.max(this.cursor, snapshot.history_cursor),
|
|
115
135
|
};
|
|
116
136
|
// Validate and project the whole attachment before any observable field
|
|
117
137
|
// moves or a live listener is registered.
|
|
118
|
-
|
|
119
|
-
|
|
138
|
+
if (authoritative) {
|
|
139
|
+
// Snapshot state is authoritative at capture, before any later events.
|
|
140
|
+
// Background listings have no separate snapshot field; retain their
|
|
141
|
+
// latest original event without replaying historical request state.
|
|
142
|
+
for (const event of recovery.replay) {
|
|
143
|
+
const payload = (event.payload ?? {});
|
|
144
|
+
if (event.kind === 'background_tasks' && Array.isArray(payload.tasks)) {
|
|
145
|
+
staged.backgroundTasks = payload.tasks
|
|
146
|
+
.filter(task => task && typeof task.id === 'string')
|
|
147
|
+
.map(task => ({ id: task.id, kind: String(task.kind ?? 'task'), description: String(task.description ?? '') }));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
for (const event of recovery.later)
|
|
151
|
+
this.projectEvent(staged, event);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// Legacy runtimes retain the previous replay-derived request behavior.
|
|
155
|
+
if (this.cursor > recovery.captureCursor)
|
|
156
|
+
staged.descriptor = structuredClone(this.descriptor);
|
|
157
|
+
for (const event of recovery.replay)
|
|
158
|
+
this.projectEvent(staged, event);
|
|
159
|
+
}
|
|
120
160
|
// A request seen in the REPLAY is transcript, not an open ask. The runtime
|
|
121
161
|
// re-sequences its request history at every turn boundary
|
|
122
162
|
// (`compact_frontend_request_history`), and that compaction can keep a
|
|
@@ -132,12 +172,12 @@ export class VscodeFrontendSession {
|
|
|
132
172
|
// reads idle while the agent loop is blocked on an approval it raised. Clearing that one left the agent waiting
|
|
133
173
|
// forever with nothing to answer it (2026-10-07). Compaction happens only at a turn boundary, so a request raised
|
|
134
174
|
// AFTER the replay's last boundary and not resolved since is still open, idle or not; only older ones are dropped.
|
|
135
|
-
if (staged.descriptor?.turn_state !== 'busy' && staged.descriptor?.source_harness !== 'codex') {
|
|
175
|
+
if (!authoritative && staged.descriptor?.turn_state !== 'busy' && staged.descriptor?.source_harness !== 'codex') {
|
|
136
176
|
// A turn's END, not `turn_completed`: that one is a model round-trip and fires mid-turn.
|
|
137
177
|
const BOUNDARIES = new Set(['turn_succeeded', 'turn_failed', 'turn_interrupted']);
|
|
138
178
|
let lastBoundary = -1;
|
|
139
179
|
const raisedAt = new Map();
|
|
140
|
-
for (const event of
|
|
180
|
+
for (const event of recovery.replay) {
|
|
141
181
|
if (BOUNDARIES.has(event.kind))
|
|
142
182
|
lastBoundary = Math.max(lastBoundary, event.sequence);
|
|
143
183
|
const payload = (event.payload ?? {});
|
|
@@ -156,22 +196,41 @@ export class VscodeFrontendSession {
|
|
|
156
196
|
this.descriptor = staged.descriptor;
|
|
157
197
|
this.history = structuredClone(snapshot.history);
|
|
158
198
|
this.historyCursor = snapshot.history_cursor;
|
|
159
|
-
this.
|
|
199
|
+
this.historyEvents = structuredClone(snapshot.history_events ?? []);
|
|
200
|
+
this.historyEventsDropped = snapshot.history_events_dropped ?? 0;
|
|
201
|
+
this.appendTranscript(structuredClone(recovery.replay));
|
|
160
202
|
this.pendingRequests = staged.pendingRequests;
|
|
161
203
|
this.backgroundTasks = staged.backgroundTasks;
|
|
204
|
+
const previousCursor = this.cursor;
|
|
162
205
|
this.cursor = staged.cursor;
|
|
163
206
|
this.connection = 'connected';
|
|
207
|
+
this.failure = null;
|
|
164
208
|
this.attachedAtMs ??= Date.now();
|
|
165
209
|
if (!this.#subscribed) {
|
|
166
210
|
this.client.on('event', this.#eventListener);
|
|
167
211
|
this.client.on('disconnect', this.#disconnectListener);
|
|
168
212
|
this.#subscribed = true;
|
|
169
213
|
}
|
|
214
|
+
// Recovery delivers original unseen events at their original sequences.
|
|
215
|
+
// Historical display data never goes through the live-state projector;
|
|
216
|
+
// the snapshot's request map and descriptor remain authoritative.
|
|
217
|
+
for (const event of [...this.historyEvents, ...recovery.replay]) {
|
|
218
|
+
if (event.sequence <= previousCursor)
|
|
219
|
+
continue;
|
|
220
|
+
for (const listener of [...this.#listeners]) {
|
|
221
|
+
try {
|
|
222
|
+
listener(event);
|
|
223
|
+
}
|
|
224
|
+
catch { /* A view cannot stop recovery. */ }
|
|
225
|
+
}
|
|
226
|
+
}
|
|
170
227
|
this.notify();
|
|
171
228
|
return snapshot;
|
|
172
229
|
}
|
|
173
230
|
/** Start consuming the already-open live stream. Resolves when it ends. */
|
|
174
231
|
follow() {
|
|
232
|
+
if (this.#disposed)
|
|
233
|
+
return Promise.resolve();
|
|
175
234
|
if (!this.#following) {
|
|
176
235
|
this.#following = this.client.startEvents({ afterSequence: this.cursor }).finally(() => {
|
|
177
236
|
this.#following = null;
|
|
@@ -184,10 +243,23 @@ export class VscodeFrontendSession {
|
|
|
184
243
|
* never call this; only a permission that can submit needs the lease.
|
|
185
244
|
*/
|
|
186
245
|
async takeControlAndHold() {
|
|
246
|
+
this.requireOpenAttachment();
|
|
187
247
|
if (!this.permissions.has('interact'))
|
|
188
248
|
return;
|
|
189
|
-
|
|
190
|
-
|
|
249
|
+
if (this.#takingControl)
|
|
250
|
+
return this.#takingControl;
|
|
251
|
+
const taking = this.client.takeControl().then((lease) => {
|
|
252
|
+
this.requireOpenAttachment();
|
|
253
|
+
this.startHeartbeat(lease.lease_ttl_ms);
|
|
254
|
+
});
|
|
255
|
+
this.#takingControl = taking;
|
|
256
|
+
try {
|
|
257
|
+
await taking;
|
|
258
|
+
}
|
|
259
|
+
finally {
|
|
260
|
+
if (this.#takingControl === taking)
|
|
261
|
+
this.#takingControl = null;
|
|
262
|
+
}
|
|
191
263
|
}
|
|
192
264
|
startHeartbeat(ttlMs) {
|
|
193
265
|
this.stopHeartbeat();
|
|
@@ -205,6 +277,10 @@ export class VscodeFrontendSession {
|
|
|
205
277
|
this.#heartbeat = null;
|
|
206
278
|
}
|
|
207
279
|
dispose() {
|
|
280
|
+
if (this.#disposed)
|
|
281
|
+
return;
|
|
282
|
+
this.#disposed = true;
|
|
283
|
+
this.connection = 'closed';
|
|
208
284
|
this.stopHeartbeat();
|
|
209
285
|
this.client.stopEvents();
|
|
210
286
|
if (this.#subscribed) {
|
|
@@ -212,8 +288,10 @@ export class VscodeFrontendSession {
|
|
|
212
288
|
this.client.off('disconnect', this.#disconnectListener);
|
|
213
289
|
this.#subscribed = false;
|
|
214
290
|
}
|
|
291
|
+
this.notify();
|
|
215
292
|
this.#listeners.clear();
|
|
216
293
|
this.#changeObservers.clear();
|
|
294
|
+
this.#failureListeners.clear();
|
|
217
295
|
}
|
|
218
296
|
/** Keep the transcript in order and free of the duplicates a re-attach brings. */
|
|
219
297
|
appendTranscript(events) {
|
|
@@ -225,8 +303,8 @@ export class VscodeFrontendSession {
|
|
|
225
303
|
this.replay.push(event);
|
|
226
304
|
}
|
|
227
305
|
this.replay.sort((left, right) => left.sequence - right.sequence);
|
|
228
|
-
if (this.replay.length >
|
|
229
|
-
this.replay = this.replay.slice(-
|
|
306
|
+
if (this.replay.length > 4096)
|
|
307
|
+
this.replay = this.replay.slice(-4096);
|
|
230
308
|
}
|
|
231
309
|
applyEvent(event) {
|
|
232
310
|
let applied;
|
|
@@ -264,9 +342,7 @@ export class VscodeFrontendSession {
|
|
|
264
342
|
return;
|
|
265
343
|
this.#resyncing = true;
|
|
266
344
|
void this.attach()
|
|
267
|
-
.catch(
|
|
268
|
-
// An unreachable runtime is reported by the next action, not here.
|
|
269
|
-
})
|
|
345
|
+
.catch(error => this.reportFailure(error))
|
|
270
346
|
.finally(() => {
|
|
271
347
|
this.#resyncing = false;
|
|
272
348
|
});
|
|
@@ -280,6 +356,29 @@ export class VscodeFrontendSession {
|
|
|
280
356
|
this.#changeObservers.add(observer);
|
|
281
357
|
return () => this.#changeObservers.delete(observer);
|
|
282
358
|
}
|
|
359
|
+
/** Surface an actual stream/recovery error without inventing a native event or ending its turn. */
|
|
360
|
+
reportFailure(error) {
|
|
361
|
+
if (this.connection === 'closed')
|
|
362
|
+
return;
|
|
363
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
364
|
+
if (this.failure?.message === failure.message && this.connection === 'disconnected')
|
|
365
|
+
return;
|
|
366
|
+
this.failure = failure;
|
|
367
|
+
this.connection = 'disconnected';
|
|
368
|
+
for (const listener of [...this.#failureListeners]) {
|
|
369
|
+
try {
|
|
370
|
+
listener(failure);
|
|
371
|
+
}
|
|
372
|
+
catch { /* A view cannot stop recovery. */ }
|
|
373
|
+
}
|
|
374
|
+
this.notify();
|
|
375
|
+
}
|
|
376
|
+
onFailure(listener) {
|
|
377
|
+
this.#failureListeners.add(listener);
|
|
378
|
+
if (this.failure)
|
|
379
|
+
listener(this.failure);
|
|
380
|
+
return () => this.#failureListeners.delete(listener);
|
|
381
|
+
}
|
|
283
382
|
projectEvent(state, event) {
|
|
284
383
|
if (event.sequence <= state.cursor)
|
|
285
384
|
return false;
|
|
@@ -344,6 +443,10 @@ export class VscodeFrontendSession {
|
|
|
344
443
|
}
|
|
345
444
|
/** Run one controller action, re-taking an expired lease exactly once. */
|
|
346
445
|
async withControl(action) {
|
|
446
|
+
// An early picker registration can expose actions while the initial lease
|
|
447
|
+
// is arriving. Wait for that attempt, then let the runtime enforce its lease.
|
|
448
|
+
await this.#takingControl?.catch(() => undefined);
|
|
449
|
+
this.requireOpenAttachment();
|
|
347
450
|
try {
|
|
348
451
|
return await action();
|
|
349
452
|
}
|
|
@@ -351,8 +454,8 @@ export class VscodeFrontendSession {
|
|
|
351
454
|
const name = error?.name ?? '';
|
|
352
455
|
if (!LEASE_ERROR_NAMES.includes(name) || !this.permissions.has('interact'))
|
|
353
456
|
throw error;
|
|
354
|
-
|
|
355
|
-
this.
|
|
457
|
+
await this.takeControlAndHold();
|
|
458
|
+
this.requireOpenAttachment();
|
|
356
459
|
return action();
|
|
357
460
|
}
|
|
358
461
|
}
|
|
@@ -402,6 +505,8 @@ export class VscodeFrontendSession {
|
|
|
402
505
|
}
|
|
403
506
|
}
|
|
404
507
|
async detach() {
|
|
508
|
+
// Do not let a pending acquisition succeed after this client detached.
|
|
509
|
+
await this.#takingControl?.catch(() => undefined);
|
|
405
510
|
this.require('detach');
|
|
406
511
|
await this.client.detach();
|
|
407
512
|
this.connection = 'disconnected';
|
|
@@ -415,8 +520,7 @@ export class VscodeFrontendSession {
|
|
|
415
520
|
}
|
|
416
521
|
async takeover() {
|
|
417
522
|
this.require('takeover');
|
|
418
|
-
|
|
419
|
-
this.startHeartbeat(lease.lease_ttl_ms);
|
|
523
|
+
await this.takeControlAndHold();
|
|
420
524
|
this.notify();
|
|
421
525
|
}
|
|
422
526
|
async heartbeat() {
|
|
@@ -447,14 +551,15 @@ export class VscodeFrontendSession {
|
|
|
447
551
|
settle?.(event);
|
|
448
552
|
}
|
|
449
553
|
});
|
|
554
|
+
const unfail = this.onFailure(error => fail?.(error));
|
|
450
555
|
// A turn that never sees its terminal event must still end: the editor
|
|
451
556
|
// holds the chat response open until this handler resolves, and a frontend
|
|
452
557
|
// that hangs one is worse than one that ends it a beat early. The
|
|
453
558
|
// descriptor going idle after the submit is the second, independent signal.
|
|
454
559
|
let sawBusy = false;
|
|
455
|
-
//
|
|
456
|
-
//
|
|
457
|
-
//
|
|
560
|
+
// Explicit transport/recovery failures end this view through onFailure;
|
|
561
|
+
// the native turn can still be running. A disconnect with no explicit
|
|
562
|
+
// failure gets a grace period while the follow loop reattaches.
|
|
458
563
|
let down;
|
|
459
564
|
const lost = () => fail?.(new Error('The connection to this conversation\'s runtime was lost before its turn ended; the turn may still be running. Reopen the chat to see where it stands.'));
|
|
460
565
|
finished.catch(() => undefined); // a loss while the submit is still in flight is answered by the submit's own error
|
|
@@ -490,9 +595,16 @@ export class VscodeFrontendSession {
|
|
|
490
595
|
clearTimeout(down);
|
|
491
596
|
unsubscribe();
|
|
492
597
|
unwatch();
|
|
598
|
+
unfail();
|
|
493
599
|
}
|
|
494
600
|
}
|
|
601
|
+
requireOpenAttachment() {
|
|
602
|
+
if (this.#disposed || this.connection === 'closed')
|
|
603
|
+
throw new Error('frontend attachment is closed');
|
|
604
|
+
}
|
|
495
605
|
require(action) {
|
|
606
|
+
if (this.failure && action === 'submit')
|
|
607
|
+
throw this.failure;
|
|
496
608
|
if (!this.actions()[action])
|
|
497
609
|
throw new Error(`frontend action ${action} is unavailable`);
|
|
498
610
|
}
|
package/dist/setup-flow.js
CHANGED
|
@@ -93,21 +93,29 @@ export class ChatSetupFlow {
|
|
|
93
93
|
this.processes.set(operation.id, process);
|
|
94
94
|
let ended = false;
|
|
95
95
|
let exitCode;
|
|
96
|
+
let exitRefreshesRemaining = 2;
|
|
96
97
|
void process.exited.then(code => { ended = true; exitCode = code; });
|
|
97
98
|
// Readiness comes only from the host's native inventory. A successful
|
|
98
99
|
// process exit, a browser opening, and a protocol handshake are not login.
|
|
99
100
|
while (current()) {
|
|
101
|
+
const refreshingAfterExit = ended;
|
|
100
102
|
await this.refresh(operation);
|
|
101
103
|
if (setupAgents(this.host.state()).some(a => a.harness === operation.harness && a.signedIn))
|
|
102
104
|
break;
|
|
103
|
-
|
|
105
|
+
// The inventory can settle after the native login process exits. Give
|
|
106
|
+
// it two reads begun after that exit, keeping inventory authoritative
|
|
107
|
+
// even for an exit code of zero and retiring cancelled operations.
|
|
108
|
+
if (refreshingAfterExit && --exitRefreshesRemaining === 0)
|
|
104
109
|
throw new Error(`Sign-in ended ${exitCode === undefined ? 'without an exit status' : `with exit code ${exitCode}`}, without a signed-in account.`);
|
|
105
110
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
106
111
|
}
|
|
107
112
|
}
|
|
108
113
|
if (!current())
|
|
109
114
|
return;
|
|
110
|
-
|
|
115
|
+
// The account the host names for the agent now signed in (one signed in from Cyclotron's Accounts menu, say), else
|
|
116
|
+
// the provider its native sign-in belongs to.
|
|
117
|
+
const signedIn = setupAgents(this.host.state()).find(a => a.harness === operation.harness && a.signedIn);
|
|
118
|
+
this.notice = signedIn?.account || (operation.harness === 'codex' ? 'ChatGPT' : operation.harness === 'claude-code' ? 'Claude' : undefined);
|
|
111
119
|
this.update(operation, 'signed-in');
|
|
112
120
|
// Retire only the terminal this click launched. Its native inventory has
|
|
113
121
|
// already confirmed login; closing it never supplies that confirmation.
|
package/dist/view.d.ts
CHANGED
|
@@ -133,6 +133,8 @@ export interface ProjectionContext {
|
|
|
133
133
|
connectionId: string;
|
|
134
134
|
/** Render the runtime's echo of a prompt (history replay does; live does not). */
|
|
135
135
|
echoOwnPrompts?: boolean;
|
|
136
|
+
/** Show input arriving in an observed turn, without inventing a native request turn. */
|
|
137
|
+
liveUserMessages?: boolean;
|
|
136
138
|
/** The request a `request_resolved` answers, so its sentence can name it. */
|
|
137
139
|
pendingRequest?: (requestId: number) => FrontendRequest | undefined;
|
|
138
140
|
/**
|
|
@@ -172,8 +174,8 @@ export interface ProjectedTurn {
|
|
|
172
174
|
}
|
|
173
175
|
/**
|
|
174
176
|
* A "user" message no person typed: Claude Code's `<task-notification>` (a background task reporting back) or the
|
|
175
|
-
* editor's automatic tripwire nudge.
|
|
176
|
-
*
|
|
177
|
+
* editor's automatic tripwire nudge or Claude's unmarked interruption record. None is shown as the person's words.
|
|
178
|
+
* A task notification and interruption are short notices; a nudge is not shown. Undefined for a person's message.
|
|
177
179
|
*/
|
|
178
180
|
export declare function automaticPrompt(body: string): {
|
|
179
181
|
notice?: string;
|
package/dist/view.js
CHANGED
|
@@ -440,12 +440,26 @@ export function projectEvent(event, context) {
|
|
|
440
440
|
const payload = payloadOf(event);
|
|
441
441
|
switch (event.kind) {
|
|
442
442
|
// crates/harness/src/server.rs `submit_claimed` — the runtime's echo of the
|
|
443
|
-
// prompt
|
|
444
|
-
//
|
|
445
|
-
|
|
443
|
+
// initial prompt, before `turn_started`. VS Code renders that request turn.
|
|
444
|
+
// A rejoined turn has no submitting handler; later runtime input is visible
|
|
445
|
+
// within its live response and remains a user turn in canonical history.
|
|
446
|
+
case 'user_message': {
|
|
447
|
+
if (context.liveUserMessages) {
|
|
448
|
+
const body = text(payload.text);
|
|
449
|
+
const automatic = automaticPrompt(body);
|
|
450
|
+
if (automatic)
|
|
451
|
+
return automatic.notice
|
|
452
|
+
? [stable({ part: 'markdown', value: `\n\n${automatic.notice}\n\n` })]
|
|
453
|
+
: [];
|
|
454
|
+
const prefix = '\n\n**Additional input**\n\n';
|
|
455
|
+
const suffix = '\n\n';
|
|
456
|
+
const value = clamp(stripControls(body), VSCODE_CHAT_LIMITS.partCharacters - prefix.length - suffix.length);
|
|
457
|
+
return value ? [stable({ part: 'markdown', value: `${prefix}${value}${suffix}` })] : [];
|
|
458
|
+
}
|
|
446
459
|
return context.echoOwnPrompts
|
|
447
460
|
? [stable({ part: 'markdown', value: clamp(stripControls(text(payload.text)), VSCODE_CHAT_LIMITS.partCharacters) })]
|
|
448
461
|
: [];
|
|
462
|
+
}
|
|
449
463
|
// crates/harness/src/server.rs `submit_claimed`, and
|
|
450
464
|
// crates/runtime/src/event.rs `AgentEvent::TurnCompleted` (one model
|
|
451
465
|
// round-trip). Lifecycle only — the turn resolves on these, never renders.
|
|
@@ -608,11 +622,11 @@ export function projectEvent(event, context) {
|
|
|
608
622
|
return [stable({ part: 'warning', value: 'The turn was interrupted.' })];
|
|
609
623
|
// crates/harness/src/server.rs `submit_claimed` emits `turn_failed`. The
|
|
610
624
|
// other four are the error kinds the donor-neutral reader in
|
|
611
|
-
//
|
|
625
|
+
// sdk/interchange/frontend-transcript.mjs treats identically:
|
|
612
626
|
// `transport_error` from crates/opencode-frontend/src/server.rs,
|
|
613
627
|
// `watch_error` from crates/interchange/src/watch.rs, and
|
|
614
628
|
// `runtime_disconnected`, which a frontend synthesizes for itself
|
|
615
|
-
// (
|
|
629
|
+
// (sdk/interchange/frontend-app.mjs). `stream_error` has no producer in this
|
|
616
630
|
// repository and is read only because that reader reads it.
|
|
617
631
|
case 'turn_failed':
|
|
618
632
|
case 'stream_error':
|
|
@@ -629,7 +643,7 @@ export function projectEvent(event, context) {
|
|
|
629
643
|
}
|
|
630
644
|
// Declared by the runtime descriptor's `display.event_kinds`
|
|
631
645
|
// (crates/harness/src/server.rs `frontend_descriptor`); read here the way
|
|
632
|
-
//
|
|
646
|
+
// sdk/interchange/frontend-transcript.mjs reads them.
|
|
633
647
|
case 'scheduled_prompt_started':
|
|
634
648
|
case 'scheduled_prompt_deferred':
|
|
635
649
|
case 'scheduled_prompt_completed':
|
|
@@ -654,7 +668,7 @@ export function projectEvent(event, context) {
|
|
|
654
668
|
case 'warning':
|
|
655
669
|
return [stable({ part: 'warning', value: clamp(stripControls(text(payload.message)), VSCODE_CHAT_LIMITS.partCharacters) })];
|
|
656
670
|
// Not in this runtime's declared set; read the way
|
|
657
|
-
//
|
|
671
|
+
// sdk/interchange/frontend-transcript.mjs reads a donor that emits them.
|
|
658
672
|
case 'reasoning':
|
|
659
673
|
case 'thinking': {
|
|
660
674
|
const body = clamp(stripControls(text(payload.text ?? payload.summary, opaqueText(event.payload))), VSCODE_CHAT_LIMITS.partCharacters);
|
|
@@ -704,8 +718,10 @@ function messageText(message) {
|
|
|
704
718
|
if (!entry || typeof entry !== 'object' || Array.isArray(entry))
|
|
705
719
|
return '';
|
|
706
720
|
const part = entry;
|
|
721
|
+
// Opaque transcript parts can carry structured text. Use the same bounded
|
|
722
|
+
// JSON rendering as live payloads rather than JavaScript's object coercion.
|
|
707
723
|
if (['text', 'input_text', 'output_text'].includes(String(part.type)))
|
|
708
|
-
return
|
|
724
|
+
return text(part.text);
|
|
709
725
|
if (['image', 'image_url', 'input_image'].includes(String(part.type)))
|
|
710
726
|
return '[image]';
|
|
711
727
|
return '';
|
|
@@ -715,8 +731,8 @@ function messageText(message) {
|
|
|
715
731
|
}
|
|
716
732
|
/**
|
|
717
733
|
* A "user" message no person typed: Claude Code's `<task-notification>` (a background task reporting back) or the
|
|
718
|
-
* editor's automatic tripwire nudge.
|
|
719
|
-
*
|
|
734
|
+
* editor's automatic tripwire nudge or Claude's unmarked interruption record. None is shown as the person's words.
|
|
735
|
+
* A task notification and interruption are short notices; a nudge is not shown. Undefined for a person's message.
|
|
720
736
|
*/
|
|
721
737
|
export function automaticPrompt(body) {
|
|
722
738
|
const trimmed = body.trimStart();
|
|
@@ -728,6 +744,9 @@ export function automaticPrompt(body) {
|
|
|
728
744
|
}
|
|
729
745
|
if (trimmed.startsWith('[Volter editor tripwire'))
|
|
730
746
|
return {};
|
|
747
|
+
// Claude persists this reserved notice as an unmarked user-role record.
|
|
748
|
+
if (trimmed.trimEnd() === '[Request interrupted by user]')
|
|
749
|
+
return { notice: '_Turn interrupted._' };
|
|
731
750
|
return undefined;
|
|
732
751
|
}
|
|
733
752
|
export function projectReplayTurns(events, granted, context, history = []) {
|
|
@@ -857,6 +876,11 @@ export function projectHistory(history, granted, pendingToolNames = new Set()) {
|
|
|
857
876
|
pushAssistant([{ part: 'markdown', value: `\n\n${automatic.notice}\n\n` }]);
|
|
858
877
|
continue;
|
|
859
878
|
}
|
|
879
|
+
// The codec preserves the harness's provenance. Image-coordinate notes,
|
|
880
|
+
// compact summaries and injected context are not the person's words.
|
|
881
|
+
if (message.metadata?.isMeta === 'true' || message.metadata?.isCompactSummary === 'true' ||
|
|
882
|
+
message.metadata?.promptSource === 'system')
|
|
883
|
+
continue;
|
|
860
884
|
toolRows.clear();
|
|
861
885
|
turns.push({ role: 'user', prompt: clamp(stripControls(body), VSCODE_CHAT_LIMITS.partCharacters) });
|
|
862
886
|
continue;
|
package/media/anthropic.svg
CHANGED
|
@@ -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
|
|
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,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-frontend-vscode",
|
|
3
|
+
"devEngines": {
|
|
4
|
+
"packageManager": {
|
|
5
|
+
"name": "pnpm",
|
|
6
|
+
"version": ">=12.15.0",
|
|
7
|
+
"onFail": "error"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
3
10
|
"displayName": "Volter Harness",
|
|
4
|
-
"version": "0.1.
|
|
11
|
+
"version": "0.1.60",
|
|
5
12
|
"type": "module",
|
|
6
13
|
"description": "VS Code chat-view frontend for an existing Volter Harness SDK runtime",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/volter-ai/supercode.git",
|
|
17
|
+
"directory": "sdk/frontend-vscode"
|
|
18
|
+
},
|
|
7
19
|
"publisher": "volter-ai-dev",
|
|
8
20
|
"icon": "media/icon.png",
|
|
9
21
|
"main": "./dist/extension.js",
|
|
@@ -22,11 +34,6 @@
|
|
|
22
34
|
"NOTICE.md",
|
|
23
35
|
"media"
|
|
24
36
|
],
|
|
25
|
-
"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",
|
|
27
|
-
"extension": "npm run build && node scripts/pack-extension.mjs",
|
|
28
|
-
"prepack": "npm run build"
|
|
29
|
-
},
|
|
30
37
|
"engines": {
|
|
31
38
|
"node": ">=20",
|
|
32
39
|
"vscode": "^1.138.0"
|
|
@@ -50,6 +57,20 @@
|
|
|
50
57
|
"chatProvider"
|
|
51
58
|
],
|
|
52
59
|
"contributes": {
|
|
60
|
+
"taskDefinitions": [
|
|
61
|
+
{
|
|
62
|
+
"type": "supercode-setup",
|
|
63
|
+
"required": [
|
|
64
|
+
"operation"
|
|
65
|
+
],
|
|
66
|
+
"properties": {
|
|
67
|
+
"operation": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"description": "The identity of this coding-agent installation."
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
],
|
|
53
74
|
"chatParticipants": [
|
|
54
75
|
{
|
|
55
76
|
"id": "supercode.chat",
|
|
@@ -190,8 +211,12 @@
|
|
|
190
211
|
},
|
|
191
212
|
"supercodeFrontendContract": {
|
|
192
213
|
"name": "@volter/supercode-frontend",
|
|
193
|
-
"version": "0.2.
|
|
214
|
+
"version": "0.2.12",
|
|
194
215
|
"mode": "generated-artifact"
|
|
195
216
|
},
|
|
196
|
-
"license": "MIT OR Apache-2.0"
|
|
197
|
-
|
|
217
|
+
"license": "MIT OR Apache-2.0",
|
|
218
|
+
"scripts": {
|
|
219
|
+
"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",
|
|
220
|
+
"extension": "pnpm run build && node scripts/pack-extension.mjs"
|
|
221
|
+
}
|
|
222
|
+
}
|