@volter/supercode-frontend-vscode 0.1.56 → 0.1.58

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
@@ -125,7 +125,13 @@ The host publishes durable conversation IDs and their exact harness history iden
125
125
  `POST /open` resumes only the requested saved conversation; `POST /remember` binds its
126
126
  reported native session ID to discovered history. Unknown or missing bindings fail explicitly,
127
127
  never falling back to the currently active runtime. The host may provide an `openSessionCommand`
128
- to reveal an exact resource through its native widget service. Cached native widgets should
128
+ to reveal an exact resource through its native widget service.
129
+ Native session-list updates reuse unchanged row objects, and readiness refreshes
130
+ skip unchanged composer option groups. Transcript and approval events remain
131
+ immediate. This removes redundant native publications; the
132
+ [Windows typing-lag cause and candidate latency remain unmeasured](../../docs/evidence/chat-composer-update-churn-2026-10-09.md).
133
+
134
+ Cached native widgets should
129
135
  notify `supercode.frontend.focusSession` on every native focused-resource change,
130
136
  including untitled resources and leaving harness Chat (`undefined` when there is
131
137
  no focused session). This retires abandoned draft choices even when returning to
@@ -443,6 +449,13 @@ remains because the native model catalogue and participant identity are provider
443
449
 
444
450
  ### Native approval cards
445
451
 
452
+ A file approval names the harness-supplied path and shows its proposed replacement
453
+ text, new contents or patch before the answer buttons. Other requests show their
454
+ supplied arguments. Preview bounds are labelled, and a request with no supplied
455
+ preview says so. The frontend reads no workspace file to invent missing context
456
+ and never applies the preview. This source correction's real-editor qualification
457
+ is recorded separately in [the first-walk findings](../../docs/evidence/chat-first-walk-approvals-2026-10-09.md).
458
+
446
459
  A host advertising `volter.chat.inTurnApprovalCapability` version 1 presents
447
460
  actual approval requests in its native confirmation card. The original offered
448
461
  labels, order and response payloads are preserved. Clicking answers the pending
package/dist/extension.js CHANGED
@@ -255,7 +255,10 @@ function writePart(stream, part, state) {
255
255
  }
256
256
  // Stable API on purpose: a command button runs INSIDE the live turn,
257
257
  // which is when a supercode approval is raised. See `view.ts`.
258
- stream.markdown(`\n\n**${part.title}**\n\n${part.message}\n\n`);
258
+ // Native confirmation titles are plain text; escape only when the same
259
+ // title is embedded in the Markdown fallback (including Windows paths).
260
+ const title = new vscode.MarkdownString().appendText(part.title).value;
261
+ stream.markdown(`\n\n**${title}**\n\n${part.message}\n\n`);
259
262
  for (const answer of part.answers) {
260
263
  stream.button({
261
264
  title: answer.label,
@@ -2019,43 +2022,54 @@ function historyText(part) {
2019
2022
  return `**${part.title}**\n\n${part.message}`;
2020
2023
  }
2021
2024
  }
2022
- /**
2023
- * The editor's session list ALWAYS reads `timing.created` and renders the epoch
2024
- * ("57 yrs ago") when a provider supplies none — measured in
2025
- * `agentSessionsViewer`, which has no path that omits the time. `frontend.v2`
2026
- * carries no session timestamp of its own, so the true times this frontend
2027
- * knows are its own: when this window attached, and the turn boundaries it
2028
- * watched. Those are what the item gets.
2029
- */
2030
- function applyTiming(item, connection) {
2031
- const created = connection.session.attachedAtMs;
2032
- if (created === null)
2033
- return;
2034
- item.timing = {
2035
- created,
2036
- lastRequestStarted: connection.session.lastTurnStartedMs ?? undefined,
2037
- lastRequestEnded: connection.session.lastTurnEndedMs ?? undefined,
2038
- };
2039
- }
2040
2025
  function registerSessions(context, frontends, participant, granted, handler, historyParticipant, controls) {
2041
2026
  const turns = vscode;
2042
2027
  const items = () => {
2043
2028
  return (controls?.catalogue() ?? []).filter(entry => !frontends.get(entry.id)).map(entry => {
2044
- const item = controller.createChatSessionItem(vscode.Uri.from({ scheme: SESSION_SCHEME, authority: entry.id, path: '/conversation' }), entry.title);
2045
- item.description = runtimeIdentity(entry.selection.harness, entry.selection.model, controls?.effective(entry)).name;
2046
- item.status = vscode.ChatSessionStatus.Completed;
2047
- item.timing = { created: entry.created };
2048
- return item;
2029
+ return { resource: vscode.Uri.from({ scheme: SESSION_SCHEME, authority: entry.id, path: '/conversation' }), label: entry.title,
2030
+ description: runtimeIdentity(entry.selection.harness, entry.selection.model, controls?.effective(entry)).name,
2031
+ status: vscode.ChatSessionStatus.Completed, timing: { created: entry.created } };
2049
2032
  });
2050
2033
  };
2051
- const connectedItem = (connection) => {
2052
- const item = controller.createChatSessionItem(sessionResource(connection), connection.label);
2053
- item.description = connection.label;
2054
- applyTiming(item, connection);
2055
- item.status = vscode.ChatSessionStatus[sessionStatus(connection.session)];
2034
+ const connectedRow = (connection) => ({
2035
+ resource: sessionResource(connection), label: connection.label, description: connection.label,
2036
+ timing: connection.session.attachedAtMs === null ? undefined : {
2037
+ created: connection.session.attachedAtMs,
2038
+ lastRequestStarted: connection.session.lastTurnStartedMs ?? undefined,
2039
+ lastRequestEnded: connection.session.lastTurnEndedMs ?? undefined,
2040
+ },
2041
+ status: vscode.ChatSessionStatus[sessionStatus(connection.session)],
2042
+ });
2043
+ const rowKey = (row) => JSON.stringify([
2044
+ row.label, row.description, row.status,
2045
+ row.timing?.created, row.timing?.lastRequestStarted, row.timing?.lastRequestEnded,
2046
+ ]);
2047
+ const cachedItems = new Map();
2048
+ const itemFor = (row) => {
2049
+ const resource = row.resource.toString();
2050
+ const key = rowKey(row);
2051
+ const cached = cachedItems.get(resource);
2052
+ if (cached?.key === key)
2053
+ return cached.item;
2054
+ // Native collection diffs compare object identity. Recreating an unchanged
2055
+ // item on each text delta sends a catalogue update and invalidates the
2056
+ // workbench's resolved-session cache while the person types in Chat.
2057
+ const item = controller.createChatSessionItem(row.resource, row.label);
2058
+ item.description = row.description;
2059
+ item.timing = row.timing;
2060
+ item.status = row.status;
2061
+ cachedItems.set(resource, { key, item });
2056
2062
  return item;
2057
2063
  };
2058
- const refreshItems = () => controller.items.replace(items().concat(frontends.all.map(connectedItem)));
2064
+ const connectedItem = (connection) => itemFor(connectedRow(connection));
2065
+ const refreshItems = () => {
2066
+ const rows = items().concat(frontends.all.map(connectedRow));
2067
+ const retained = new Set(rows.map(row => row.resource.toString()));
2068
+ for (const resource of cachedItems.keys())
2069
+ if (!retained.has(resource))
2070
+ cachedItems.delete(resource);
2071
+ controller.items.replace(rows.map(itemFor));
2072
+ };
2059
2073
  const controller = vscode.chat.createChatSessionItemController(SESSION_SCHEME, async () => { refreshItems(); });
2060
2074
  context.subscriptions.push(controller, frontends.onDidChange(refreshItems));
2061
2075
  if (controls) {
@@ -2099,7 +2113,12 @@ function registerSessions(context, frontends, participant, granted, handler, his
2099
2113
  for (const state of states) {
2100
2114
  const groups = controls.options(state.sessionResource);
2101
2115
  const value = draftPermissions.get(state);
2102
- state.groups = state.sessionResource || !value ? groups : applyOptions(groups, [{ optionId: 'permissions', value }]);
2116
+ const next = state.sessionResource || !value ? groups : applyOptions(groups, [{ optionId: 'permissions', value }]);
2117
+ // The native setter always publishes to the composer, even when its
2118
+ // values match. Readiness polls must not rebuild an unchanged picker.
2119
+ // Compare the live input state so a person's selection still wins.
2120
+ if (JSON.stringify(state.groups) !== JSON.stringify(next))
2121
+ state.groups = next;
2103
2122
  }
2104
2123
  }));
2105
2124
  }
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 summary line this view shows for a request payload. */
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[];
package/dist/view.js CHANGED
@@ -195,11 +195,75 @@ export function shellCommand(toolName, argumentsText) {
195
195
  catch { /* not JSON */ }
196
196
  return undefined;
197
197
  }
198
- /** The summary line this view shows for a request payload. */
198
+ /** The harness-supplied arguments of an approval; no files are read to reconstruct an edit. */
199
+ function approvalArguments(fields) {
200
+ const value = fields.arguments ?? fields.raw_args ?? fields.input;
201
+ if (typeof value !== 'string')
202
+ return value;
203
+ try {
204
+ return JSON.parse(value);
205
+ }
206
+ catch {
207
+ return value;
208
+ }
209
+ }
210
+ /** An approval preview is display text, never a patch to apply or an answer to the request. */
211
+ function approvalSummary(fields) {
212
+ const args = approvalArguments(fields);
213
+ const tool = text(fields.tool ?? fields.tool_name);
214
+ const values = args && typeof args === 'object' && !Array.isArray(args) ? args : undefined;
215
+ const path = values ? text(values.file_path ?? values.path ?? values.file ?? values.notebook_path) : '';
216
+ const heading = [tool, path || text(fields.subject ?? fields.message ?? fields.reason)].filter(Boolean)
217
+ .map(value => plain(clamp(stripControls(value), VSCODE_CHAT_LIMITS.titleCharacters))).join(' · ');
218
+ // Bound before adding fences, so truncation cannot leave an open code block.
219
+ const preview = (value, language = '') => {
220
+ const cleaned = stripControls(value);
221
+ const limited = clamp(cleaned, 24 * 1024);
222
+ return fence(limited, language) + (limited !== cleaned ? '\n\n_Preview truncated; the harness request is unchanged._' : '');
223
+ };
224
+ const changes = [];
225
+ const edits = values && Array.isArray(values.edits) ? values.edits : values ? [values] : [];
226
+ for (const edit of edits.slice(0, 4)) {
227
+ if (!edit || typeof edit !== 'object' || Array.isArray(edit))
228
+ continue;
229
+ if (typeof edit.old_string === 'string' && typeof edit.new_string === 'string') {
230
+ changes.push(`**Replace${edit.replace_all === true ? ' every match' : ''}**\n\n${preview(edit.old_string)}\n\n**With**\n\n${preview(edit.new_string)}`);
231
+ }
232
+ }
233
+ if (edits.length > 4)
234
+ changes.push('_Additional edits omitted from this preview; the harness request is unchanged._');
235
+ if (!changes.length && values) {
236
+ const patch = values.patch ?? values.diff;
237
+ const contents = values.content ?? values.new_source;
238
+ if (typeof patch === 'string')
239
+ changes.push(preview(patch, 'diff'));
240
+ else if (typeof contents === 'string')
241
+ changes.push(`**Proposed contents**\n\n${preview(contents)}`);
242
+ }
243
+ if (!changes.length && args !== undefined && args !== null) {
244
+ changes.push(preview(typeof args === 'string' ? args : JSON.stringify(args, null, 2), typeof args === 'string' ? '' : 'json'));
245
+ }
246
+ if (!changes.length)
247
+ changes.push('_The harness supplied no change preview._');
248
+ // Each edit is separately bounded; the overall card is bounded too. Retain
249
+ // complete fenced sections rather than cutting through their markup.
250
+ let result = heading;
251
+ for (const change of changes) {
252
+ if (result.length + change.length > VSCODE_CHAT_LIMITS.partCharacters - 128) {
253
+ result += '\n\n_Additional preview omitted; the harness request is unchanged._';
254
+ break;
255
+ }
256
+ result += `\n\n${change}`;
257
+ }
258
+ return result;
259
+ }
260
+ /** The information this view shows for a request payload. */
199
261
  export function requestSummary(request) {
200
262
  const payload = request.payload;
201
263
  if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
202
264
  const fields = payload;
265
+ if (request.kind === 'approval')
266
+ return approvalSummary(fields);
203
267
  // `decide_approval` in crates/harness/src/server.rs publishes
204
268
  // `{tool, subject, raw_args}` and `ask_elicitation` publishes
205
269
  // `{message, requested_schema}`; the contract's own conformance fixture
@@ -348,11 +412,10 @@ export function requestSubject(request) {
348
412
  const tool = typeof fields.tool === 'string' ? fields.tool
349
413
  : typeof fields.tool_name === 'string' ? fields.tool_name
350
414
  : undefined;
415
+ const args = approvalArguments(fields);
351
416
  const subject = typeof fields.subject === 'string' ? fields.subject
352
- : typeof fields.raw_args === 'string' ? fields.raw_args
353
- : typeof fields.arguments === 'object' && fields.arguments !== null && !Array.isArray(fields.arguments)
354
- ? toolSubject(JSON.stringify(fields.arguments))
355
- : undefined;
417
+ : args && typeof args === 'object' ? toolSubject(JSON.stringify(args))
418
+ : undefined;
356
419
  if (tool && subject)
357
420
  return `${tool} on ${clamp(stripControls(subject), 120)}`;
358
421
  if (tool)
@@ -487,7 +550,7 @@ export function projectEvent(event, context) {
487
550
  const answers = requestAnswers(request, context.connectionId);
488
551
  const subject = requestSubject(request);
489
552
  const title = request.kind === 'approval'
490
- ? 'Approval required'
553
+ ? subject ? `Allow ${stripControls(subject)}?` : 'Approval required'
491
554
  : subject ? `Approve ${subject}` : `Assistant request #${request.id}`;
492
555
  // The host capability decides whether these original answers can use
493
556
  // a native in-turn confirmation card. Stock confirmation clicks start a
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.56",
4
+ "version": "0.1.58",
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",