@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 +14 -1
- package/dist/extension.js +50 -31
- package/dist/view.d.ts +1 -1
- package/dist/view.js +69 -6
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
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
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
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
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
353
|
-
:
|
|
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.
|
|
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",
|