@volter/supercode-frontend-vscode 0.1.57 → 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 +119 -10
- package/dist/chat-setup.js +3 -2
- package/dist/extension.js +241 -45
- 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 +5 -3
- package/dist/view.js +103 -16
- 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
|
@@ -107,7 +107,7 @@ export declare function toolTitles(toolName: string, argumentsText: string): {
|
|
|
107
107
|
};
|
|
108
108
|
/** The command line of a shell tool call, read from its own arguments; undefined for any other tool. */
|
|
109
109
|
export declare function shellCommand(toolName: string, argumentsText: string): string | undefined;
|
|
110
|
-
/** The
|
|
110
|
+
/** The information this view shows for a request payload. */
|
|
111
111
|
export declare function requestSummary(request: FrontendRequest): string;
|
|
112
112
|
/** The answers this view offers for a request, one button each. */
|
|
113
113
|
export declare function requestAnswers(request: FrontendRequest, connectionId: string): ChatAnswer[];
|
|
@@ -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;
|