@volter/supercode-frontend-vscode 0.1.53 → 0.1.55

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 CHANGED
@@ -400,14 +400,35 @@ 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. Managed conversations save their native
404
- identity before sending input, as well as when a turn ends.
405
- Stop is observed during the save and checked before input, including a lease
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 finish at the end of a turn. A successful
407
+ host save acknowledges that exact runtime connection, native id and catalogue
408
+ identity; completion skips another save while all three still match. A reload
409
+ or replacement connection must obtain its own acknowledgement. Completion
410
+ refreshes the host before that comparison; a changed/cleared binding or failed
411
+ refresh invalidates the local acknowledgement and retains the save attempt.
412
+ After reload,
413
+ the exact runtime's user history/events recover that save obligation; restored
414
+ snapshots, later events and response completion retry through the same host
415
+ checks without sending input. A busy state or standalone request alone does
416
+ not establish submitted input. A failed save
417
+ does not resend input or abort a running turn. An unbound conversation whose
418
+ final save fails says the prompt was submitted, and tells the person to keep Chat
419
+ open and avoid repeating the prompt.
420
+ Stop is observed throughout the turn and checked before input, including a lease
406
421
  renewal retry. Retained request/decision records with no originating replay
407
422
  prompt appear in a separate runtime-history turn.
408
423
  The [reload investigation](../../docs/evidence/chat-pending-approval-reload-2026-10-08.md)
409
424
  separates the retained first-user failure from this source change; a live
410
425
  candidate reload and approval click remain unmeasured.
426
+ The [first-input regression investigation](../../docs/evidence/chat-first-input-binding-2026-10-08.md)
427
+ records the 0.1.52 binding-order failure, the correction and its outstanding
428
+ product reading.
429
+ The [identity-save investigation](../../docs/evidence/chat-identity-save-acknowledgement-2026-10-08.md)
430
+ names the repeated host lookup removed at completion; candidate latency and
431
+ real-editor qualification remain open.
411
432
 
412
433
  The pinned Code-OSS host needs a resource-scoping repair in its input-state change
413
434
  routing: stock code broadcasts changes to all input states of a provider. The
package/dist/extension.js CHANGED
@@ -422,6 +422,92 @@ 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 identitySaved = (connection, nativeId) => {
439
+ const save = identitySave(connection);
440
+ save.remembered = true;
441
+ const identity = controls?.state.sessions.find(session => session.id === connection.id)?.identity;
442
+ // Only a successful host save acknowledges this runtime/native-id pair.
443
+ // A restored catalogue identity alone is not a save receipt for a new connection.
444
+ save.acknowledged = identity ? { nativeId, identity } : undefined;
445
+ };
446
+ const rememberIdentity = (connection, submitted = false) => {
447
+ const save = identitySave(connection);
448
+ save.submitted ||= submitted;
449
+ const entry = controls?.state.sessions.find(session => session.id === connection.id);
450
+ const nativeId = connection.session.descriptor?.session_id;
451
+ if (!save.submitted || !controls || !entry || attachedIds.has(connection.id) || !nativeId)
452
+ return;
453
+ save.remembered ||= Boolean(entry.identity);
454
+ if (save.remembered)
455
+ return;
456
+ save.again = true;
457
+ if (save.pending)
458
+ return;
459
+ save.pending = (async () => {
460
+ do {
461
+ save.again = false;
462
+ try {
463
+ await controls.remember(connection.id, nativeId);
464
+ identitySaved(connection, nativeId);
465
+ }
466
+ catch (error) {
467
+ log.warn(`Could not yet save conversation identity: ${String(error)}`);
468
+ }
469
+ } while (save.again && !save.remembered);
470
+ })().finally(() => { save.pending = undefined; });
471
+ };
472
+ const finishIdentitySave = async (connection, onlySubmitted = false) => {
473
+ const save = identitySave(connection);
474
+ if (onlySubmitted && !save.submitted)
475
+ return;
476
+ if (!controls || attachedIds.has(connection.id))
477
+ return;
478
+ await save.pending;
479
+ // The host may have cleared a binding since its last lifecycle reply.
480
+ // Read it before treating a local acknowledgement as satisfied.
481
+ const refreshed = await controls.refresh().then(() => true, error => {
482
+ log.warn(`Could not refresh chat identity: ${String(error)}`);
483
+ return false;
484
+ });
485
+ const nativeId = connection.session.descriptor?.session_id;
486
+ const identity = controls.state.sessions.find(session => session.id === connection.id)?.identity;
487
+ const acknowledged = save.acknowledged;
488
+ const satisfied = Boolean(refreshed && nativeId && acknowledged
489
+ && acknowledged.nativeId === nativeId && acknowledged.identity === identity);
490
+ if (!satisfied) {
491
+ save.acknowledged = undefined;
492
+ save.remembered = false;
493
+ }
494
+ if (nativeId && !satisfied)
495
+ await controls.remember(connection.id, nativeId).then(() => {
496
+ identitySaved(connection, nativeId);
497
+ }, error => {
498
+ log.warn(`Could not save conversation identity: ${String(error)}`);
499
+ if (save.submitted && !save.remembered && !save.warned
500
+ && !controls.state.sessions.find(session => session.id === connection.id)?.identity) {
501
+ save.warned = true;
502
+ 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.');
503
+ }
504
+ });
505
+ controlsChanged.fire();
506
+ };
507
+ context.subscriptions.push(frontends.onDidChange(() => {
508
+ for (const connection of frontends.all)
509
+ rememberIdentity(connection);
510
+ }));
425
511
  const savedAttachments = new Map(context.workspaceState.get('supercode.externalAttachments', [])
426
512
  .map(entry => [entry.connectionId, entry]));
427
513
  const restoringAttachments = new Map();
@@ -1030,6 +1116,8 @@ export async function activate(context) {
1030
1116
  effective: (entry) => controls && entry.id === controls.state.activeSession ? controls.state.effective ?? null : draftEffective(entry.selection),
1031
1117
  bindings: boundId,
1032
1118
  watchApprovals: (connection, stream, token) => watchApprovals(connection, stream, token, () => approvalFor(connection.id)),
1119
+ rememberIdentity,
1120
+ finishIdentitySave: (connection) => finishIdentitySave(connection, true),
1033
1121
  };
1034
1122
  context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.activateSession', async (resource) => { await connectionFor(vscode.Uri.parse(resource)); }));
1035
1123
  // A background task the chat named (sayTasks in runTurnInto), stopped through the harness's own control.
@@ -1343,12 +1431,9 @@ export async function activate(context) {
1343
1431
  const unwatchTasks = connection.session.onChange(sayTasks);
1344
1432
  let completed = false;
1345
1433
  try {
1346
- // Observe Stop throughout the save, then check the token before input.
1347
- // A cancellation while this idle runtime saves cannot be handled by an
1348
- // interrupt alone: there is no running turn to interrupt yet.
1349
- if (controls && !attachedIds.has(connection.id) && connection.session.descriptor?.session_id) {
1350
- await controls.remember(connection.id, connection.session.descriptor.session_id);
1351
- }
1434
+ // A new harness transcript is created by input, so binding its identity
1435
+ // cannot be a prerequisite for that input. Retry the host save as native
1436
+ // events arrive; no save refusal submits input again or aborts its turn.
1352
1437
  throwIfCancelled();
1353
1438
  await connection.session.runTurn(prompt, (event) => {
1354
1439
  // A turn the person stopped is marked stopped by VS Code itself; only an interruption from elsewhere is said.
@@ -1356,19 +1441,17 @@ export async function activate(context) {
1356
1441
  return;
1357
1442
  writeProjected(stream, projectEvent(event, projection), granted, state);
1358
1443
  watch.sweep();
1359
- }, { throwIfCancelled });
1444
+ rememberIdentity(connection);
1445
+ }, { throwIfCancelled, onSubmitted: () => {
1446
+ rememberIdentity(connection, true);
1447
+ } });
1360
1448
  completed = true;
1361
1449
  }
1362
1450
  finally {
1363
1451
  unwatchTasks();
1364
1452
  watch.dispose();
1365
1453
  cancellation.dispose();
1366
- if (controls && !attachedIds.has(connection.id)) {
1367
- if (connection.session.descriptor?.session_id)
1368
- await controls.remember(connection.id, connection.session.descriptor.session_id).catch(error => log.warn(`Could not save conversation identity: ${String(error)}`));
1369
- await controls.refresh().catch(error => log.warn(`Could not refresh chat identity: ${String(error)}`));
1370
- controlsChanged.fire();
1371
- }
1454
+ await finishIdentitySave(connection);
1372
1455
  }
1373
1456
  // The model the turn ran on: what the harness's own replies name (the host watches them), else what the
1374
1457
  // runtime reports; and the effort it ran at (host `effective`).
@@ -2073,6 +2156,9 @@ function registerSessions(context, frontends, participant, granted, handler, his
2073
2156
  canRespond: Boolean(connection.session.descriptor?.actions.respond && connection.session.permissions.has('approve')),
2074
2157
  }, projectHistory(canonicalHistory, granted, pendingToolNames));
2075
2158
  const live = connection.session.descriptor?.turn_state === 'busy' || connection.session.pendingRequests.size > 0;
2159
+ controls?.rememberIdentity(connection);
2160
+ if (!live)
2161
+ void controls?.finishIdentitySave(connection);
2076
2162
  const activeParts = [];
2077
2163
  if (live) {
2078
2164
  // Standalone requests have no user turn. Give their live response
@@ -2132,6 +2218,7 @@ function registerSessions(context, frontends, participant, granted, handler, his
2132
2218
  canRespond: Boolean(connection.session.descriptor?.actions.respond && connection.session.permissions.has('approve')),
2133
2219
  }), granted, state);
2134
2220
  watch?.sweep();
2221
+ controls?.rememberIdentity(connection);
2135
2222
  await observeActiveResponse(connection.session, token, (event) => {
2136
2223
  writeProjected(stream, projectEvent(event, {
2137
2224
  connectionId: connection.id,
@@ -2139,11 +2226,13 @@ function registerSessions(context, frontends, participant, granted, handler, his
2139
2226
  canRespond: Boolean(connection.session.descriptor?.actions.respond && connection.session.permissions.has('approve')),
2140
2227
  }), granted, state);
2141
2228
  watch?.sweep();
2229
+ controls?.rememberIdentity(connection);
2142
2230
  }, () => { connection.viewReady = true; }, renderedCursor);
2143
2231
  }
2144
2232
  finally {
2145
2233
  connection.viewReady = connection.session.descriptor?.turn_state !== 'busy' && !connection.session.pendingRequests.size;
2146
2234
  watch?.dispose();
2235
+ await controls?.finishIdentitySave(connection);
2147
2236
  }
2148
2237
  }
2149
2238
  : 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.53",
4
+ "version": "0.1.55",
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",