@volter/supercode-frontend-vscode 0.1.53 → 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 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. 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 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();
@@ -1030,6 +1095,8 @@ export async function activate(context) {
1030
1095
  effective: (entry) => controls && entry.id === controls.state.activeSession ? controls.state.effective ?? null : draftEffective(entry.selection),
1031
1096
  bindings: boundId,
1032
1097
  watchApprovals: (connection, stream, token) => watchApprovals(connection, stream, token, () => approvalFor(connection.id)),
1098
+ rememberIdentity,
1099
+ finishIdentitySave: (connection) => finishIdentitySave(connection, true),
1033
1100
  };
1034
1101
  context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.activateSession', async (resource) => { await connectionFor(vscode.Uri.parse(resource)); }));
1035
1102
  // A background task the chat named (sayTasks in runTurnInto), stopped through the harness's own control.
@@ -1343,12 +1410,9 @@ export async function activate(context) {
1343
1410
  const unwatchTasks = connection.session.onChange(sayTasks);
1344
1411
  let completed = false;
1345
1412
  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
- }
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.
1352
1416
  throwIfCancelled();
1353
1417
  await connection.session.runTurn(prompt, (event) => {
1354
1418
  // A turn the person stopped is marked stopped by VS Code itself; only an interruption from elsewhere is said.
@@ -1356,19 +1420,17 @@ export async function activate(context) {
1356
1420
  return;
1357
1421
  writeProjected(stream, projectEvent(event, projection), granted, state);
1358
1422
  watch.sweep();
1359
- }, { throwIfCancelled });
1423
+ rememberIdentity(connection);
1424
+ }, { throwIfCancelled, onSubmitted: () => {
1425
+ rememberIdentity(connection, true);
1426
+ } });
1360
1427
  completed = true;
1361
1428
  }
1362
1429
  finally {
1363
1430
  unwatchTasks();
1364
1431
  watch.dispose();
1365
1432
  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
- }
1433
+ await finishIdentitySave(connection);
1372
1434
  }
1373
1435
  // The model the turn ran on: what the harness's own replies name (the host watches them), else what the
1374
1436
  // runtime reports; and the effort it ran at (host `effective`).
@@ -2073,6 +2135,9 @@ function registerSessions(context, frontends, participant, granted, handler, his
2073
2135
  canRespond: Boolean(connection.session.descriptor?.actions.respond && connection.session.permissions.has('approve')),
2074
2136
  }, projectHistory(canonicalHistory, granted, pendingToolNames));
2075
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);
2076
2141
  const activeParts = [];
2077
2142
  if (live) {
2078
2143
  // Standalone requests have no user turn. Give their live response
@@ -2132,6 +2197,7 @@ function registerSessions(context, frontends, participant, granted, handler, his
2132
2197
  canRespond: Boolean(connection.session.descriptor?.actions.respond && connection.session.permissions.has('approve')),
2133
2198
  }), granted, state);
2134
2199
  watch?.sweep();
2200
+ controls?.rememberIdentity(connection);
2135
2201
  await observeActiveResponse(connection.session, token, (event) => {
2136
2202
  writeProjected(stream, projectEvent(event, {
2137
2203
  connectionId: connection.id,
@@ -2139,11 +2205,13 @@ function registerSessions(context, frontends, participant, granted, handler, his
2139
2205
  canRespond: Boolean(connection.session.descriptor?.actions.respond && connection.session.permissions.has('approve')),
2140
2206
  }), granted, state);
2141
2207
  watch?.sweep();
2208
+ controls?.rememberIdentity(connection);
2142
2209
  }, () => { connection.viewReady = true; }, renderedCursor);
2143
2210
  }
2144
2211
  finally {
2145
2212
  connection.viewReady = connection.session.descriptor?.turn_state !== 'busy' && !connection.session.pendingRequests.size;
2146
2213
  watch?.dispose();
2214
+ await controls?.finishIdentitySave(connection);
2147
2215
  }
2148
2216
  }
2149
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.53",
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",