@volter/supercode-frontend-vscode 0.1.52 → 0.1.54
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/README.md +15 -3
- package/dist/extension.js +86 -15
- package/dist/session.d.ts +3 -0
- package/dist/session.js +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -400,14 +400,26 @@ running response is replayed through `activeResponseCallback`, with each pending
|
|
|
400
400
|
request drawn once from the snapshot and later events followed by sequence.
|
|
401
401
|
An idle runtime with a pending request also keeps this callback open until the
|
|
402
402
|
request is resolved. Standalone requests use the explicit runtime-history marker
|
|
403
|
-
instead of reopening an earlier answer.
|
|
404
|
-
|
|
405
|
-
|
|
403
|
+
instead of reopening an earlier answer. A new conversation sends its first input
|
|
404
|
+
before trying to save its native identity: the harness creates its transcript
|
|
405
|
+
from that input. Identity saves run after input acceptance, retry with later
|
|
406
|
+
native events until one succeeds, and run again when a turn ends. After reload,
|
|
407
|
+
the exact runtime's user history/events recover that save obligation; restored
|
|
408
|
+
snapshots, later events and response completion retry through the same host
|
|
409
|
+
checks without sending input. A busy state or standalone request alone does
|
|
410
|
+
not establish submitted input. A failed save
|
|
411
|
+
does not resend input or abort a running turn. An unbound conversation whose
|
|
412
|
+
final save fails says the prompt was submitted, and tells the person to keep Chat
|
|
413
|
+
open and avoid repeating the prompt.
|
|
414
|
+
Stop is observed throughout the turn and checked before input, including a lease
|
|
406
415
|
renewal retry. Retained request/decision records with no originating replay
|
|
407
416
|
prompt appear in a separate runtime-history turn.
|
|
408
417
|
The [reload investigation](../../docs/evidence/chat-pending-approval-reload-2026-10-08.md)
|
|
409
418
|
separates the retained first-user failure from this source change; a live
|
|
410
419
|
candidate reload and approval click remain unmeasured.
|
|
420
|
+
The [first-input regression investigation](../../docs/evidence/chat-first-input-binding-2026-10-08.md)
|
|
421
|
+
records the 0.1.52 binding-order failure, the correction and its outstanding
|
|
422
|
+
product reading.
|
|
411
423
|
|
|
412
424
|
The pinned Code-OSS host needs a resource-scoping repair in its input-state change
|
|
413
425
|
routing: stock code broadcasts changes to all input states of a provider. The
|
package/dist/extension.js
CHANGED
|
@@ -422,6 +422,71 @@ export async function activate(context) {
|
|
|
422
422
|
// them, so they open as what they are: attachments, shown as their runtime
|
|
423
423
|
// reports them, and never taken for the conversation the host manages.
|
|
424
424
|
const attachedIds = new Set();
|
|
425
|
+
// A request handler can disappear on reload. Recover this obligation from
|
|
426
|
+
// the exact runtime's user history/events when its connection is reattached.
|
|
427
|
+
const identitySaves = new WeakMap();
|
|
428
|
+
const identitySave = (connection) => {
|
|
429
|
+
let save = identitySaves.get(connection);
|
|
430
|
+
if (!save) {
|
|
431
|
+
save = { submitted: false, remembered: false, again: false, warned: false };
|
|
432
|
+
identitySaves.set(connection, save);
|
|
433
|
+
}
|
|
434
|
+
save.submitted ||= connection.session.history.some(message => message.role === 'user')
|
|
435
|
+
|| connection.session.replay.some(event => event.kind === 'user_message');
|
|
436
|
+
return save;
|
|
437
|
+
};
|
|
438
|
+
const rememberIdentity = (connection, submitted = false) => {
|
|
439
|
+
const save = identitySave(connection);
|
|
440
|
+
save.submitted ||= submitted;
|
|
441
|
+
const entry = controls?.state.sessions.find(session => session.id === connection.id);
|
|
442
|
+
const nativeId = connection.session.descriptor?.session_id;
|
|
443
|
+
if (!save.submitted || !controls || !entry || attachedIds.has(connection.id) || !nativeId)
|
|
444
|
+
return;
|
|
445
|
+
save.remembered ||= Boolean(entry.identity);
|
|
446
|
+
if (save.remembered)
|
|
447
|
+
return;
|
|
448
|
+
save.again = true;
|
|
449
|
+
if (save.pending)
|
|
450
|
+
return;
|
|
451
|
+
save.pending = (async () => {
|
|
452
|
+
do {
|
|
453
|
+
save.again = false;
|
|
454
|
+
try {
|
|
455
|
+
await controls.remember(connection.id, nativeId);
|
|
456
|
+
save.remembered = true;
|
|
457
|
+
}
|
|
458
|
+
catch (error) {
|
|
459
|
+
log.warn(`Could not yet save conversation identity: ${String(error)}`);
|
|
460
|
+
}
|
|
461
|
+
} while (save.again && !save.remembered);
|
|
462
|
+
})().finally(() => { save.pending = undefined; });
|
|
463
|
+
};
|
|
464
|
+
const finishIdentitySave = async (connection, onlySubmitted = false) => {
|
|
465
|
+
const save = identitySave(connection);
|
|
466
|
+
if (onlySubmitted && !save.submitted)
|
|
467
|
+
return;
|
|
468
|
+
if (!controls || attachedIds.has(connection.id))
|
|
469
|
+
return;
|
|
470
|
+
await save.pending;
|
|
471
|
+
const nativeId = connection.session.descriptor?.session_id;
|
|
472
|
+
if (nativeId)
|
|
473
|
+
await controls.remember(connection.id, nativeId).then(() => {
|
|
474
|
+
save.remembered = true;
|
|
475
|
+
}, error => {
|
|
476
|
+
log.warn(`Could not save conversation identity: ${String(error)}`);
|
|
477
|
+
if (save.submitted && !save.remembered && !save.warned
|
|
478
|
+
&& !controls.state.sessions.find(session => session.id === connection.id)?.identity) {
|
|
479
|
+
save.warned = true;
|
|
480
|
+
void vscode.window.showWarningMessage('Your prompt was submitted, but Chat could not save this conversation for reopening. Keep this chat open; do not send the prompt again.');
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
await controls.refresh().catch(error => log.warn(`Could not refresh chat identity: ${String(error)}`));
|
|
484
|
+
controlsChanged.fire();
|
|
485
|
+
};
|
|
486
|
+
context.subscriptions.push(frontends.onDidChange(() => {
|
|
487
|
+
for (const connection of frontends.all)
|
|
488
|
+
rememberIdentity(connection);
|
|
489
|
+
}));
|
|
425
490
|
const savedAttachments = new Map(context.workspaceState.get('supercode.externalAttachments', [])
|
|
426
491
|
.map(entry => [entry.connectionId, entry]));
|
|
427
492
|
const restoringAttachments = new Map();
|
|
@@ -979,7 +1044,9 @@ export async function activate(context) {
|
|
|
979
1044
|
// A new draft's harness, not the previous runtime, owns its offered modes.
|
|
980
1045
|
const harness = resource ? controls?.state.sessions.find(session => session.id === id)?.selection.harness : draftSelection()?.harness;
|
|
981
1046
|
const canApprove = resource ? (active ? canAnswer(active) : ['claude-code', 'codex'].includes(harness ?? ''))
|
|
982
|
-
: (harness ? ['claude-code', 'codex'].includes(harness)
|
|
1047
|
+
: (harness ? ['claude-code', 'codex'].includes(harness)
|
|
1048
|
+
// the default harness decides; only with none known does the new-chat default stand in for it
|
|
1049
|
+
: controls?.state.selection.harness ? ['claude-code', 'codex'].includes(controls.state.selection.harness) : newChatDefault() === 'autoApprove');
|
|
983
1050
|
// An unbound input is a new chat, not the active conversation left behind it.
|
|
984
1051
|
const selectedPermission = resource ? currentOptions(resource).permissions : permission ?? newChatDefault();
|
|
985
1052
|
const permissionItems = [{ id: 'ask', name: canApprove ? 'Ask for approval' : 'Harness permissions', description: canApprove ? 'Show each approval requested by the harness.' : 'Approval controls are unavailable for this harness.' },
|
|
@@ -1028,6 +1095,8 @@ export async function activate(context) {
|
|
|
1028
1095
|
effective: (entry) => controls && entry.id === controls.state.activeSession ? controls.state.effective ?? null : draftEffective(entry.selection),
|
|
1029
1096
|
bindings: boundId,
|
|
1030
1097
|
watchApprovals: (connection, stream, token) => watchApprovals(connection, stream, token, () => approvalFor(connection.id)),
|
|
1098
|
+
rememberIdentity,
|
|
1099
|
+
finishIdentitySave: (connection) => finishIdentitySave(connection, true),
|
|
1031
1100
|
};
|
|
1032
1101
|
context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.activateSession', async (resource) => { await connectionFor(vscode.Uri.parse(resource)); }));
|
|
1033
1102
|
// A background task the chat named (sayTasks in runTurnInto), stopped through the harness's own control.
|
|
@@ -1067,8 +1136,9 @@ export async function activate(context) {
|
|
|
1067
1136
|
await revealManaged();
|
|
1068
1137
|
}));
|
|
1069
1138
|
context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.configure', async (supplied) => {
|
|
1139
|
+
// with a new chat's draft open, a permission chosen is the draft's, never the conversation behind it
|
|
1070
1140
|
if (supplied?.permissions) {
|
|
1071
|
-
const id = controls?.state.activeSession;
|
|
1141
|
+
const id = activeDraft() ? undefined : controls?.state.activeSession;
|
|
1072
1142
|
if (id)
|
|
1073
1143
|
choosePermission(id, turnPermission(supplied.permissions));
|
|
1074
1144
|
else
|
|
@@ -1340,12 +1410,9 @@ export async function activate(context) {
|
|
|
1340
1410
|
const unwatchTasks = connection.session.onChange(sayTasks);
|
|
1341
1411
|
let completed = false;
|
|
1342
1412
|
try {
|
|
1343
|
-
//
|
|
1344
|
-
//
|
|
1345
|
-
//
|
|
1346
|
-
if (controls && !attachedIds.has(connection.id) && connection.session.descriptor?.session_id) {
|
|
1347
|
-
await controls.remember(connection.id, connection.session.descriptor.session_id);
|
|
1348
|
-
}
|
|
1413
|
+
// A new harness transcript is created by input, so binding its identity
|
|
1414
|
+
// cannot be a prerequisite for that input. Retry the host save as native
|
|
1415
|
+
// events arrive; no save refusal submits input again or aborts its turn.
|
|
1349
1416
|
throwIfCancelled();
|
|
1350
1417
|
await connection.session.runTurn(prompt, (event) => {
|
|
1351
1418
|
// A turn the person stopped is marked stopped by VS Code itself; only an interruption from elsewhere is said.
|
|
@@ -1353,19 +1420,17 @@ export async function activate(context) {
|
|
|
1353
1420
|
return;
|
|
1354
1421
|
writeProjected(stream, projectEvent(event, projection), granted, state);
|
|
1355
1422
|
watch.sweep();
|
|
1356
|
-
|
|
1423
|
+
rememberIdentity(connection);
|
|
1424
|
+
}, { throwIfCancelled, onSubmitted: () => {
|
|
1425
|
+
rememberIdentity(connection, true);
|
|
1426
|
+
} });
|
|
1357
1427
|
completed = true;
|
|
1358
1428
|
}
|
|
1359
1429
|
finally {
|
|
1360
1430
|
unwatchTasks();
|
|
1361
1431
|
watch.dispose();
|
|
1362
1432
|
cancellation.dispose();
|
|
1363
|
-
|
|
1364
|
-
if (connection.session.descriptor?.session_id)
|
|
1365
|
-
await controls.remember(connection.id, connection.session.descriptor.session_id).catch(error => log.warn(`Could not save conversation identity: ${String(error)}`));
|
|
1366
|
-
await controls.refresh().catch(error => log.warn(`Could not refresh chat identity: ${String(error)}`));
|
|
1367
|
-
controlsChanged.fire();
|
|
1368
|
-
}
|
|
1433
|
+
await finishIdentitySave(connection);
|
|
1369
1434
|
}
|
|
1370
1435
|
// The model the turn ran on: what the harness's own replies name (the host watches them), else what the
|
|
1371
1436
|
// runtime reports; and the effort it ran at (host `effective`).
|
|
@@ -2070,6 +2135,9 @@ function registerSessions(context, frontends, participant, granted, handler, his
|
|
|
2070
2135
|
canRespond: Boolean(connection.session.descriptor?.actions.respond && connection.session.permissions.has('approve')),
|
|
2071
2136
|
}, projectHistory(canonicalHistory, granted, pendingToolNames));
|
|
2072
2137
|
const live = connection.session.descriptor?.turn_state === 'busy' || connection.session.pendingRequests.size > 0;
|
|
2138
|
+
controls?.rememberIdentity(connection);
|
|
2139
|
+
if (!live)
|
|
2140
|
+
void controls?.finishIdentitySave(connection);
|
|
2073
2141
|
const activeParts = [];
|
|
2074
2142
|
if (live) {
|
|
2075
2143
|
// Standalone requests have no user turn. Give their live response
|
|
@@ -2129,6 +2197,7 @@ function registerSessions(context, frontends, participant, granted, handler, his
|
|
|
2129
2197
|
canRespond: Boolean(connection.session.descriptor?.actions.respond && connection.session.permissions.has('approve')),
|
|
2130
2198
|
}), granted, state);
|
|
2131
2199
|
watch?.sweep();
|
|
2200
|
+
controls?.rememberIdentity(connection);
|
|
2132
2201
|
await observeActiveResponse(connection.session, token, (event) => {
|
|
2133
2202
|
writeProjected(stream, projectEvent(event, {
|
|
2134
2203
|
connectionId: connection.id,
|
|
@@ -2136,11 +2205,13 @@ function registerSessions(context, frontends, participant, granted, handler, his
|
|
|
2136
2205
|
canRespond: Boolean(connection.session.descriptor?.actions.respond && connection.session.permissions.has('approve')),
|
|
2137
2206
|
}), granted, state);
|
|
2138
2207
|
watch?.sweep();
|
|
2208
|
+
controls?.rememberIdentity(connection);
|
|
2139
2209
|
}, () => { connection.viewReady = true; }, renderedCursor);
|
|
2140
2210
|
}
|
|
2141
2211
|
finally {
|
|
2142
2212
|
connection.viewReady = connection.session.descriptor?.turn_state !== 'busy' && !connection.session.pendingRequests.size;
|
|
2143
2213
|
watch?.dispose();
|
|
2214
|
+
await controls?.finishIdentitySave(connection);
|
|
2144
2215
|
}
|
|
2145
2216
|
}
|
|
2146
2217
|
: undefined,
|
package/dist/session.d.ts
CHANGED
|
@@ -161,10 +161,13 @@ export declare class VscodeFrontendSession {
|
|
|
161
161
|
* Submit one prompt and feed every event of the resulting turn to `onEvent`,
|
|
162
162
|
* resolving when the runtime reports the turn finished. The subscription is
|
|
163
163
|
* registered BEFORE the prompt is sent, so no early delta is lost.
|
|
164
|
+
* `onSubmitted` observes the input RPC's successful return, not transcript
|
|
165
|
+
* persistence or turn completion; it runs once, after any lease retry.
|
|
164
166
|
*/
|
|
165
167
|
runTurn(prompt: string, onEvent: (event: FrontendEvent) => void, options?: {
|
|
166
168
|
onCancel?: (cancel: () => void) => void;
|
|
167
169
|
throwIfCancelled?: () => void;
|
|
170
|
+
onSubmitted?: () => void;
|
|
168
171
|
}): Promise<FrontendEvent | null>;
|
|
169
172
|
private require;
|
|
170
173
|
private notify;
|
package/dist/session.js
CHANGED
|
@@ -428,6 +428,8 @@ export class VscodeFrontendSession {
|
|
|
428
428
|
* Submit one prompt and feed every event of the resulting turn to `onEvent`,
|
|
429
429
|
* resolving when the runtime reports the turn finished. The subscription is
|
|
430
430
|
* registered BEFORE the prompt is sent, so no early delta is lost.
|
|
431
|
+
* `onSubmitted` observes the input RPC's successful return, not transcript
|
|
432
|
+
* persistence or turn completion; it runs once, after any lease retry.
|
|
431
433
|
*/
|
|
432
434
|
async runTurn(prompt, onEvent, options = {}) {
|
|
433
435
|
let settle;
|
|
@@ -480,6 +482,8 @@ export class VscodeFrontendSession {
|
|
|
480
482
|
});
|
|
481
483
|
try {
|
|
482
484
|
await this.submit(prompt, options.throwIfCancelled);
|
|
485
|
+
// Acceptance can precede the harness's first durable transcript record.
|
|
486
|
+
options.onSubmitted?.();
|
|
483
487
|
return await finished;
|
|
484
488
|
}
|
|
485
489
|
finally {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-frontend-vscode",
|
|
3
3
|
"displayName": "Volter Harness",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.54",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "VS Code chat-view frontend for an existing Volter Harness SDK runtime",
|
|
7
7
|
"publisher": "volter-ai-dev",
|