@volter/supercode-frontend-vscode 0.1.57 → 0.1.60
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/NOTICE.md +1 -1
- package/README.md +119 -10
- package/dist/chat-setup.js +3 -2
- package/dist/extension.js +241 -45
- package/dist/host-controls.d.ts +4 -0
- package/dist/host-controls.js +4 -1
- package/dist/native-session.d.ts +2 -2
- package/dist/native-session.js +10 -1
- package/dist/session.d.ts +9 -0
- package/dist/session.js +134 -22
- package/dist/setup-flow.js +10 -2
- package/dist/view.d.ts +5 -3
- package/dist/view.js +103 -16
- package/media/anthropic.svg +1 -1
- package/media/google.svg +1 -1
- package/media/openai.svg +1 -1
- package/media/xai.svg +1 -0
- package/package.json +34 -9
- package/vendor/supercode-frontend/README.md +27 -0
- package/vendor/supercode-frontend/client.d.mts +7 -1
- package/vendor/supercode-frontend/client.mjs +85 -11
- package/vendor/supercode-frontend/contract.json +7 -2
- package/vendor/supercode-frontend/generated-client.d.mts +1 -1
- package/vendor/supercode-frontend/generated-client.mjs +1 -1
- package/vendor/supercode-frontend/generated.d.mts +7 -0
- package/vendor/supercode-frontend/package.json +2 -1
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)
|
|
@@ -377,12 +440,26 @@ export function projectEvent(event, context) {
|
|
|
377
440
|
const payload = payloadOf(event);
|
|
378
441
|
switch (event.kind) {
|
|
379
442
|
// crates/harness/src/server.rs `submit_claimed` — the runtime's echo of the
|
|
380
|
-
// prompt
|
|
381
|
-
//
|
|
382
|
-
|
|
443
|
+
// initial prompt, before `turn_started`. VS Code renders that request turn.
|
|
444
|
+
// A rejoined turn has no submitting handler; later runtime input is visible
|
|
445
|
+
// within its live response and remains a user turn in canonical history.
|
|
446
|
+
case 'user_message': {
|
|
447
|
+
if (context.liveUserMessages) {
|
|
448
|
+
const body = text(payload.text);
|
|
449
|
+
const automatic = automaticPrompt(body);
|
|
450
|
+
if (automatic)
|
|
451
|
+
return automatic.notice
|
|
452
|
+
? [stable({ part: 'markdown', value: `\n\n${automatic.notice}\n\n` })]
|
|
453
|
+
: [];
|
|
454
|
+
const prefix = '\n\n**Additional input**\n\n';
|
|
455
|
+
const suffix = '\n\n';
|
|
456
|
+
const value = clamp(stripControls(body), VSCODE_CHAT_LIMITS.partCharacters - prefix.length - suffix.length);
|
|
457
|
+
return value ? [stable({ part: 'markdown', value: `${prefix}${value}${suffix}` })] : [];
|
|
458
|
+
}
|
|
383
459
|
return context.echoOwnPrompts
|
|
384
460
|
? [stable({ part: 'markdown', value: clamp(stripControls(text(payload.text)), VSCODE_CHAT_LIMITS.partCharacters) })]
|
|
385
461
|
: [];
|
|
462
|
+
}
|
|
386
463
|
// crates/harness/src/server.rs `submit_claimed`, and
|
|
387
464
|
// crates/runtime/src/event.rs `AgentEvent::TurnCompleted` (one model
|
|
388
465
|
// round-trip). Lifecycle only — the turn resolves on these, never renders.
|
|
@@ -487,7 +564,7 @@ export function projectEvent(event, context) {
|
|
|
487
564
|
const answers = requestAnswers(request, context.connectionId);
|
|
488
565
|
const subject = requestSubject(request);
|
|
489
566
|
const title = request.kind === 'approval'
|
|
490
|
-
? 'Approval required'
|
|
567
|
+
? subject ? `Allow ${stripControls(subject)}?` : 'Approval required'
|
|
491
568
|
: subject ? `Approve ${subject}` : `Assistant request #${request.id}`;
|
|
492
569
|
// The host capability decides whether these original answers can use
|
|
493
570
|
// a native in-turn confirmation card. Stock confirmation clicks start a
|
|
@@ -545,11 +622,11 @@ export function projectEvent(event, context) {
|
|
|
545
622
|
return [stable({ part: 'warning', value: 'The turn was interrupted.' })];
|
|
546
623
|
// crates/harness/src/server.rs `submit_claimed` emits `turn_failed`. The
|
|
547
624
|
// other four are the error kinds the donor-neutral reader in
|
|
548
|
-
//
|
|
625
|
+
// sdk/interchange/frontend-transcript.mjs treats identically:
|
|
549
626
|
// `transport_error` from crates/opencode-frontend/src/server.rs,
|
|
550
627
|
// `watch_error` from crates/interchange/src/watch.rs, and
|
|
551
628
|
// `runtime_disconnected`, which a frontend synthesizes for itself
|
|
552
|
-
// (
|
|
629
|
+
// (sdk/interchange/frontend-app.mjs). `stream_error` has no producer in this
|
|
553
630
|
// repository and is read only because that reader reads it.
|
|
554
631
|
case 'turn_failed':
|
|
555
632
|
case 'stream_error':
|
|
@@ -566,7 +643,7 @@ export function projectEvent(event, context) {
|
|
|
566
643
|
}
|
|
567
644
|
// Declared by the runtime descriptor's `display.event_kinds`
|
|
568
645
|
// (crates/harness/src/server.rs `frontend_descriptor`); read here the way
|
|
569
|
-
//
|
|
646
|
+
// sdk/interchange/frontend-transcript.mjs reads them.
|
|
570
647
|
case 'scheduled_prompt_started':
|
|
571
648
|
case 'scheduled_prompt_deferred':
|
|
572
649
|
case 'scheduled_prompt_completed':
|
|
@@ -591,7 +668,7 @@ export function projectEvent(event, context) {
|
|
|
591
668
|
case 'warning':
|
|
592
669
|
return [stable({ part: 'warning', value: clamp(stripControls(text(payload.message)), VSCODE_CHAT_LIMITS.partCharacters) })];
|
|
593
670
|
// Not in this runtime's declared set; read the way
|
|
594
|
-
//
|
|
671
|
+
// sdk/interchange/frontend-transcript.mjs reads a donor that emits them.
|
|
595
672
|
case 'reasoning':
|
|
596
673
|
case 'thinking': {
|
|
597
674
|
const body = clamp(stripControls(text(payload.text ?? payload.summary, opaqueText(event.payload))), VSCODE_CHAT_LIMITS.partCharacters);
|
|
@@ -641,8 +718,10 @@ function messageText(message) {
|
|
|
641
718
|
if (!entry || typeof entry !== 'object' || Array.isArray(entry))
|
|
642
719
|
return '';
|
|
643
720
|
const part = entry;
|
|
721
|
+
// Opaque transcript parts can carry structured text. Use the same bounded
|
|
722
|
+
// JSON rendering as live payloads rather than JavaScript's object coercion.
|
|
644
723
|
if (['text', 'input_text', 'output_text'].includes(String(part.type)))
|
|
645
|
-
return
|
|
724
|
+
return text(part.text);
|
|
646
725
|
if (['image', 'image_url', 'input_image'].includes(String(part.type)))
|
|
647
726
|
return '[image]';
|
|
648
727
|
return '';
|
|
@@ -652,8 +731,8 @@ function messageText(message) {
|
|
|
652
731
|
}
|
|
653
732
|
/**
|
|
654
733
|
* A "user" message no person typed: Claude Code's `<task-notification>` (a background task reporting back) or the
|
|
655
|
-
* editor's automatic tripwire nudge.
|
|
656
|
-
*
|
|
734
|
+
* editor's automatic tripwire nudge or Claude's unmarked interruption record. None is shown as the person's words.
|
|
735
|
+
* A task notification and interruption are short notices; a nudge is not shown. Undefined for a person's message.
|
|
657
736
|
*/
|
|
658
737
|
export function automaticPrompt(body) {
|
|
659
738
|
const trimmed = body.trimStart();
|
|
@@ -665,6 +744,9 @@ export function automaticPrompt(body) {
|
|
|
665
744
|
}
|
|
666
745
|
if (trimmed.startsWith('[Volter editor tripwire'))
|
|
667
746
|
return {};
|
|
747
|
+
// Claude persists this reserved notice as an unmarked user-role record.
|
|
748
|
+
if (trimmed.trimEnd() === '[Request interrupted by user]')
|
|
749
|
+
return { notice: '_Turn interrupted._' };
|
|
668
750
|
return undefined;
|
|
669
751
|
}
|
|
670
752
|
export function projectReplayTurns(events, granted, context, history = []) {
|
|
@@ -794,6 +876,11 @@ export function projectHistory(history, granted, pendingToolNames = new Set()) {
|
|
|
794
876
|
pushAssistant([{ part: 'markdown', value: `\n\n${automatic.notice}\n\n` }]);
|
|
795
877
|
continue;
|
|
796
878
|
}
|
|
879
|
+
// The codec preserves the harness's provenance. Image-coordinate notes,
|
|
880
|
+
// compact summaries and injected context are not the person's words.
|
|
881
|
+
if (message.metadata?.isMeta === 'true' || message.metadata?.isCompactSummary === 'true' ||
|
|
882
|
+
message.metadata?.promptSource === 'system')
|
|
883
|
+
continue;
|
|
797
884
|
toolRows.clear();
|
|
798
885
|
turns.push({ role: 'user', prompt: clamp(stripControls(body), VSCODE_CHAT_LIMITS.partCharacters) });
|
|
799
886
|
continue;
|
package/media/anthropic.svg
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 3.5 26 18" fill="#d97757"><path fill-rule="evenodd" d="M20.998 10.949H24v3.102h-3v3.028h-1.487V20H18v-2.921h-1.487V20H15v-2.921H9V20H7.488v-2.921H6V20H4.487v-2.921H3V14.05H0V10.95h3V5h17.998v5.949zM6 10.949h1.488V8.102H6v2.847zm10.51 0H18V8.102h-1.49v2.847z"/></svg>
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 3.5 26 18" fill="#d97757"><path fill-rule="evenodd" clip-rule="evenodd" d="M20.998 10.949H24v3.102h-3v3.028h-1.487V20H18v-2.921h-1.487V20H15v-2.921H9V20H7.488v-2.921H6V20H4.487v-2.921H3V14.05H0V10.95h3V5h17.998v5.949zM6 10.949h1.488V8.102H6v2.847zm10.51 0H18V8.102h-1.49v2.847z"/></svg>
|
package/media/google.svg
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#8b8df8"><path
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#8b8df8"><path d="M12 24C12 17.373 6.627 12 0 12 6.627 12 12 6.627 12 0c0 6.627 5.373 12 12 12-6.627 0-12 5.373-12 12z"/></svg>
|
package/media/openai.svg
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 -1 26 26" fill="#aaaaaa"><path fill-rule="evenodd" d="M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z"/></svg>
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 -1 26 26" fill="#aaaaaa"><path fill-rule="evenodd" clip-rule="evenodd" d="M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z"/></svg>
|
package/media/xai.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 -1 26 26" fill="#aaaaaa"><path d="M9.27 15.29l7.978-5.897c.391-.29.95-.177 1.137.272.98 2.369.542 5.215-1.41 7.169-1.951 1.954-4.667 2.382-7.149 1.406l-2.711 1.257c3.889 2.661 8.611 2.003 11.562-.953 2.341-2.344 3.066-5.539 2.388-8.42l.006.007c-.983-4.232.242-5.924 2.75-9.383.06-.082.12-.164.179-.248l-3.301 3.305v-.01L9.267 15.292M7.623 16.723c-2.792-2.67-2.31-6.801.071-9.184 1.761-1.763 4.647-2.483 7.166-1.425l2.705-1.25a7.808 7.808 0 00-1.829-1A8.975 8.975 0 005.984 5.83c-2.533 2.536-3.33 6.436-1.962 9.764 1.022 2.487-.653 4.246-2.34 6.022-.599.63-1.199 1.259-1.682 1.925l7.62-6.815"/></svg>
|
package/package.json
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-frontend-vscode",
|
|
3
|
+
"devEngines": {
|
|
4
|
+
"packageManager": {
|
|
5
|
+
"name": "pnpm",
|
|
6
|
+
"version": ">=12.15.0",
|
|
7
|
+
"onFail": "error"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
3
10
|
"displayName": "Volter Harness",
|
|
4
|
-
"version": "0.1.
|
|
11
|
+
"version": "0.1.60",
|
|
5
12
|
"type": "module",
|
|
6
13
|
"description": "VS Code chat-view frontend for an existing Volter Harness SDK runtime",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/volter-ai/supercode.git",
|
|
17
|
+
"directory": "sdk/frontend-vscode"
|
|
18
|
+
},
|
|
7
19
|
"publisher": "volter-ai-dev",
|
|
8
20
|
"icon": "media/icon.png",
|
|
9
21
|
"main": "./dist/extension.js",
|
|
@@ -22,11 +34,6 @@
|
|
|
22
34
|
"NOTICE.md",
|
|
23
35
|
"media"
|
|
24
36
|
],
|
|
25
|
-
"scripts": {
|
|
26
|
-
"build": "node -e \"require('node:fs').rmSync('dist', {recursive:true, force:true})\" && node scripts/sync-sdk.mjs && node scripts/fetch-icon.mjs && tsc -p tsconfig.build.json",
|
|
27
|
-
"extension": "npm run build && node scripts/pack-extension.mjs",
|
|
28
|
-
"prepack": "npm run build"
|
|
29
|
-
},
|
|
30
37
|
"engines": {
|
|
31
38
|
"node": ">=20",
|
|
32
39
|
"vscode": "^1.138.0"
|
|
@@ -50,6 +57,20 @@
|
|
|
50
57
|
"chatProvider"
|
|
51
58
|
],
|
|
52
59
|
"contributes": {
|
|
60
|
+
"taskDefinitions": [
|
|
61
|
+
{
|
|
62
|
+
"type": "supercode-setup",
|
|
63
|
+
"required": [
|
|
64
|
+
"operation"
|
|
65
|
+
],
|
|
66
|
+
"properties": {
|
|
67
|
+
"operation": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"description": "The identity of this coding-agent installation."
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
],
|
|
53
74
|
"chatParticipants": [
|
|
54
75
|
{
|
|
55
76
|
"id": "supercode.chat",
|
|
@@ -190,8 +211,12 @@
|
|
|
190
211
|
},
|
|
191
212
|
"supercodeFrontendContract": {
|
|
192
213
|
"name": "@volter/supercode-frontend",
|
|
193
|
-
"version": "0.2.
|
|
214
|
+
"version": "0.2.12",
|
|
194
215
|
"mode": "generated-artifact"
|
|
195
216
|
},
|
|
196
|
-
"license": "MIT OR Apache-2.0"
|
|
197
|
-
|
|
217
|
+
"license": "MIT OR Apache-2.0",
|
|
218
|
+
"scripts": {
|
|
219
|
+
"build": "node -e \"require('node:fs').rmSync('dist', {recursive:true, force:true})\" && node scripts/sync-sdk.mjs && node scripts/fetch-icon.mjs && node scripts/sync-marks.mjs && tsc -p tsconfig.build.json",
|
|
220
|
+
"extension": "pnpm run build && node scripts/pack-extension.mjs"
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -16,6 +16,33 @@ race; HTTP uses bearer authentication plus an opaque client id and permission
|
|
|
16
16
|
narrowing. Dropping or detaching this client never closes the runtime;
|
|
17
17
|
`close()` is a separate terminate-authorized action.
|
|
18
18
|
|
|
19
|
+
Attachment snapshots may include `pending_requests`, the complete open-request
|
|
20
|
+
set at the snapshot boundary. An empty array means no request remains open;
|
|
21
|
+
absence denotes an older runtime with replay-derived state. IDs and payloads
|
|
22
|
+
come from the runtime; responses still use its existing exactly-once door.
|
|
23
|
+
|
|
24
|
+
`snapshot_cursor` is the full capture boundary of the descriptor and request
|
|
25
|
+
set, taken under the native projection lock. It differs from `history_cursor`,
|
|
26
|
+
which covers only canonical and retired display history. Transports return
|
|
27
|
+
unfiltered snapshot replay even when an existing stream has advanced.
|
|
28
|
+
`mergeFrontendSnapshot` joins later retained observations in sequence order;
|
|
29
|
+
an evicted suffix refuses attachment with `event_gap`. The view installs the
|
|
30
|
+
authoritative request set at capture before applying later requests, resolutions
|
|
31
|
+
and native turn boundaries. Legacy snapshots without the added cursor retain
|
|
32
|
+
their full replay boundary, and absence of the request set retains legacy
|
|
33
|
+
replay-derived behavior. These display fields grant no response authority.
|
|
34
|
+
|
|
35
|
+
`history_events` contains original sequenced events retired from replay since
|
|
36
|
+
canonical history was last captured. `history_cursor` covers canonical history
|
|
37
|
+
and these historical events; live/replay delivery begins after that boundary.
|
|
38
|
+
Historical events are display data, not events to send back to a harness. Both
|
|
39
|
+
native projection paths retain up to 4096 retired events plus 4096 replay events.
|
|
40
|
+
`history_events_dropped` counts older events outside that display window. A
|
|
41
|
+
frontend must show that omission rather than claim a complete transcript.
|
|
42
|
+
Canonical message history remains bounded separately. Open requests survive
|
|
43
|
+
independently of both event windows. Older clients need updating to render these
|
|
44
|
+
additive snapshot fields. See the [source and compilation record](../../docs/evidence/frontend-pending-snapshot-2026-10-10.md).
|
|
45
|
+
|
|
19
46
|
`FrontendWebSocketClient.connect()` requires a header-capable socket factory
|
|
20
47
|
and passes the bearer token only in `Authorization`. It deliberately has no
|
|
21
48
|
query-token fallback: browser clients that cannot set upgrade headers use the
|
|
@@ -33,6 +33,12 @@ export class FrontendClientError extends Error {
|
|
|
33
33
|
extensions: Record<string, unknown>;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/** Native snapshot plus contiguous later retained events; refuses an evicted suffix. */
|
|
37
|
+
export function mergeFrontendSnapshot(snapshot: FrontendAttachSnapshot, observed?: readonly FrontendEvent[], cursor?: number): {
|
|
38
|
+
captureCursor: number; cursor: number; replay: FrontendEvent[]; later: FrontendEvent[];
|
|
39
|
+
};
|
|
40
|
+
export function projectFrontendDescriptor(descriptor: FrontendRuntimeDescriptor | null, event: FrontendEvent): FrontendRuntimeDescriptor | null;
|
|
41
|
+
|
|
36
42
|
export class FrontendClient extends GeneratedFrontendClient<RequestOptions> {
|
|
37
43
|
constructor(options: FrontendClientOptions);
|
|
38
44
|
readonly clientId: string;
|
|
@@ -43,7 +49,7 @@ export class FrontendClient extends GeneratedFrontendClient<RequestOptions> {
|
|
|
43
49
|
attach(limit?: number, options?: RequestOptions & {afterSequence?: number}): Promise<FrontendAttachSnapshot>;
|
|
44
50
|
sendInput(prompt: string, options?: RequestOptions): Promise<{accepted:boolean}>;
|
|
45
51
|
submit(prompt: string, options?: RequestOptions): Promise<{reply:string}>;
|
|
46
|
-
interrupt(options?: RequestOptions): Promise<{interrupted:boolean}>;
|
|
52
|
+
interrupt(options?: RequestOptions & {expectedTurn?: string}): Promise<{interrupted:boolean}>;
|
|
47
53
|
steer(prompt: string, options?: RequestOptions): Promise<{queued:boolean}>;
|
|
48
54
|
respond(response: FrontendResponse, options?: RequestOptions): Promise<{accepted:boolean}>;
|
|
49
55
|
invoke(operation: FrontendOperationInvocation, options?: RequestOptions): Promise<FrontendOperationResult>;
|
|
@@ -69,6 +69,79 @@ function validateDescriptor(descriptor) {
|
|
|
69
69
|
return descriptor;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
function validateSnapshot(snapshot) {
|
|
73
|
+
validateDescriptor(snapshot?.descriptor);
|
|
74
|
+
if (!Array.isArray(snapshot.history) || !Array.isArray(snapshot.replay)
|
|
75
|
+
|| !Number.isSafeInteger(snapshot.history_cursor) || snapshot.history_cursor < 0
|
|
76
|
+
|| (snapshot.history_events !== undefined && !Array.isArray(snapshot.history_events))
|
|
77
|
+
|| (snapshot.history_events_dropped !== undefined
|
|
78
|
+
&& (!Number.isSafeInteger(snapshot.history_events_dropped) || snapshot.history_events_dropped < 0))) {
|
|
79
|
+
throw new FrontendClientError({name:'transport', message:'invalid frontend attachment snapshot'});
|
|
80
|
+
}
|
|
81
|
+
let previous = 0;
|
|
82
|
+
for (const event of snapshot.history_events ?? []) {
|
|
83
|
+
if (!Number.isSafeInteger(event?.sequence) || event.sequence <= previous
|
|
84
|
+
|| (previous > 0 && event.sequence !== previous + 1)
|
|
85
|
+
|| event.sequence > snapshot.history_cursor || typeof event.kind !== 'string') {
|
|
86
|
+
throw new FrontendClientError({name:'transport', message:'invalid frontend historical event boundary'});
|
|
87
|
+
}
|
|
88
|
+
previous = event.sequence;
|
|
89
|
+
}
|
|
90
|
+
if (previous > 0 && previous !== snapshot.history_cursor) {
|
|
91
|
+
throw new FrontendClientError({name:'transport', message:'incomplete frontend historical event boundary'});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Join a captured snapshot to retained later observations without changing
|
|
96
|
+
* the native capture point. A missing bounded suffix refuses attachment. */
|
|
97
|
+
export function mergeFrontendSnapshot(snapshot, observed = [], cursor = 0) {
|
|
98
|
+
validateSnapshot(snapshot);
|
|
99
|
+
const captureCursor = snapshot.snapshot_cursor ?? Math.max(snapshot.history_cursor,
|
|
100
|
+
...snapshot.replay.map(event => event.sequence));
|
|
101
|
+
if (!Number.isSafeInteger(captureCursor) || captureCursor < snapshot.history_cursor
|
|
102
|
+
|| !Number.isSafeInteger(cursor) || cursor < 0) {
|
|
103
|
+
throw new FrontendClientError({name:'transport', message:'invalid frontend snapshot capture boundary'});
|
|
104
|
+
}
|
|
105
|
+
const events = new Map();
|
|
106
|
+
for (const event of snapshot.replay) {
|
|
107
|
+
if (!Number.isSafeInteger(event?.sequence) || event.sequence <= snapshot.history_cursor
|
|
108
|
+
|| event.sequence > captureCursor || typeof event.kind !== 'string') {
|
|
109
|
+
throw new FrontendClientError({name:'transport', message:'invalid frontend snapshot replay boundary'});
|
|
110
|
+
}
|
|
111
|
+
events.set(event.sequence, event);
|
|
112
|
+
}
|
|
113
|
+
for (const event of observed) {
|
|
114
|
+
if (event.sequence > captureCursor) events.set(event.sequence, event);
|
|
115
|
+
}
|
|
116
|
+
const replay = [...events.values()].sort((a, b) => a.sequence - b.sequence);
|
|
117
|
+
const later = replay.filter(event => event.sequence > captureCursor);
|
|
118
|
+
let projected = captureCursor;
|
|
119
|
+
for (const event of later) {
|
|
120
|
+
if (!Number.isSafeInteger(event.sequence) || event.sequence !== projected + 1) {
|
|
121
|
+
throw new FrontendClientError({name:'event_gap', message:`frontend snapshot needs event ${projected + 1}, received ${event.sequence}`});
|
|
122
|
+
}
|
|
123
|
+
projected = event.sequence;
|
|
124
|
+
}
|
|
125
|
+
if (projected < cursor) {
|
|
126
|
+
throw new FrontendClientError({name:'event_gap', message:`frontend snapshot lost retained events ${projected + 1} through ${cursor}`});
|
|
127
|
+
}
|
|
128
|
+
return {captureCursor, cursor:projected, replay, later};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Project only native lifecycle events; a model round-trip does not idle a turn. */
|
|
132
|
+
export function projectFrontendDescriptor(descriptor, event) {
|
|
133
|
+
if (!descriptor) return descriptor;
|
|
134
|
+
if (event.kind === 'runtime_disconnected') {
|
|
135
|
+
return {...descriptor, connection_state:'shutting_down', turn_state:'idle',
|
|
136
|
+
actions:{...descriptor.actions, submit:false, steer:false, interrupt:false, respond:false}};
|
|
137
|
+
}
|
|
138
|
+
if (['turn_succeeded', 'turn_failed', 'turn_interrupted'].includes(event.kind)) {
|
|
139
|
+
return {...descriptor, turn_state:'idle'};
|
|
140
|
+
}
|
|
141
|
+
if (['turn_started', 'user_message'].includes(event.kind)) return {...descriptor, turn_state:'busy'};
|
|
142
|
+
return descriptor;
|
|
143
|
+
}
|
|
144
|
+
|
|
72
145
|
export class FrontendClient extends FrontendEmitter {
|
|
73
146
|
#baseUrl;
|
|
74
147
|
#token;
|
|
@@ -132,13 +205,12 @@ export class FrontendClient extends FrontendEmitter {
|
|
|
132
205
|
this.stopEvents();
|
|
133
206
|
throw error;
|
|
134
207
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
for (const event of snapshot.replay) this.#lastSequence = Math.max(this.#lastSequence, event.sequence);
|
|
208
|
+
validateSnapshot(snapshot);
|
|
209
|
+
// Replay remains the native snapshot's, even if a running stream already
|
|
210
|
+
// delivered some of it. View adapters need that capture boundary to merge
|
|
211
|
+
// their later observations; the stream's delivery cursor cannot erase it.
|
|
212
|
+
const {captureCursor} = mergeFrontendSnapshot(snapshot);
|
|
213
|
+
this.#lastSequence = Math.max(this.#lastSequence, afterSequence, captureCursor);
|
|
142
214
|
return snapshot;
|
|
143
215
|
}
|
|
144
216
|
async #openEventStream(afterSequence, signal) {
|
|
@@ -288,10 +360,12 @@ export class FrontendWebSocketClient extends FrontendEmitter {
|
|
|
288
360
|
async describe() { return validateDescriptor(await this.rpc(FRONTEND_METHODS.describe)); }
|
|
289
361
|
async attach(limit = 1000, {afterSequence = 0} = {}) {
|
|
290
362
|
const snapshot = await this.rpc(FRONTEND_METHODS.attach, {limit, after_sequence:afterSequence});
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
363
|
+
validateSnapshot(snapshot);
|
|
364
|
+
// Replay remains the native snapshot's, even if a running stream already
|
|
365
|
+
// delivered some of it. View adapters need that capture boundary to merge
|
|
366
|
+
// their later observations; the stream's delivery cursor cannot erase it.
|
|
367
|
+
const {captureCursor} = mergeFrontendSnapshot(snapshot);
|
|
368
|
+
this.#lastSequence = Math.max(this.#lastSequence, afterSequence, captureCursor);
|
|
295
369
|
return snapshot;
|
|
296
370
|
}
|
|
297
371
|
async detach() { const result = await this.rpc(FRONTEND_METHODS.detach); this.closeTransport(); return result; }
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"types": {
|
|
29
29
|
"JsonValue": {"kind": "json"},
|
|
30
30
|
"Empty": {"kind": "object", "fields": {}},
|
|
31
|
+
"InterruptParams": {"kind": "object", "fields": {"expected_turn": {"type": "string", "optional": true}}},
|
|
31
32
|
"ChatRole": {"kind": "enum", "values": ["system", "user", "assistant", "tool"]},
|
|
32
33
|
"FunctionCall": {
|
|
33
34
|
"kind": "object",
|
|
@@ -143,7 +144,11 @@
|
|
|
143
144
|
"descriptor": {"type": "FrontendRuntimeDescriptor"},
|
|
144
145
|
"history": {"type": {"kind": "array", "items": "ChatMessage"}},
|
|
145
146
|
"history_cursor": {"type": "u64"},
|
|
146
|
-
"
|
|
147
|
+
"snapshot_cursor": {"type": "u64", "optional": true, "nullable": true},
|
|
148
|
+
"history_events": {"type": {"kind": "array", "items": "FrontendEvent"}, "optional": true},
|
|
149
|
+
"history_events_dropped": {"type": "u64", "optional": true},
|
|
150
|
+
"replay": {"type": {"kind": "array", "items": "FrontendEvent"}},
|
|
151
|
+
"pending_requests": {"type": {"kind": "array", "items": "FrontendRequest"}, "optional": true, "nullable": true}
|
|
147
152
|
}
|
|
148
153
|
},
|
|
149
154
|
"FrontendApprovalDecision": {"kind": "enum", "values": ["deny", "allow", "allow_for_session"]},
|
|
@@ -260,7 +265,7 @@
|
|
|
260
265
|
{"id": "attach", "wire": "frontend.v2.attach", "sdk_action": "events", "params": "AttachParams", "result": "FrontendAttachSnapshot", "transports": ["local", "http", "websocket", "acp"]},
|
|
261
266
|
{"id": "send_input", "wire": "frontend.v2.send_input", "sdk_action": "input", "params": "MultimodalPromptParams", "result": "Accepted", "transports": ["local", "http", "websocket", "acp"]},
|
|
262
267
|
{"id": "submit", "wire": "frontend.v2.submit", "sdk_action": "input", "params": "MultimodalPromptParams", "result": "Reply", "compatibility": "completion_waiting_alias", "transports": ["local", "http", "websocket", "acp"]},
|
|
263
|
-
{"id": "interrupt", "wire": "frontend.v2.interrupt", "sdk_action": "interrupt", "params": "
|
|
268
|
+
{"id": "interrupt", "wire": "frontend.v2.interrupt", "sdk_action": "interrupt", "params": "InterruptParams", "result": "Interrupted", "transports": ["local", "http", "websocket", "acp"]},
|
|
264
269
|
{"id": "steer", "wire": "frontend.v2.steer", "sdk_action": "steer", "params": "PromptParams", "result": "Queued", "transports": ["local", "http", "websocket", "acp"]},
|
|
265
270
|
{"id": "stop_task", "wire": "frontend.v2.stop_task", "sdk_action": "stop_task", "params": "TaskParams", "result": "Stopped", "transports": ["local", "http", "websocket", "acp"]},
|
|
266
271
|
{"id": "respond", "wire": "frontend.v2.respond", "sdk_action": "respond", "params": "RespondParams", "result": "Accepted", "transports": ["local", "http", "websocket", "acp"]},
|
|
@@ -6,7 +6,7 @@ export abstract class GeneratedFrontendClient<Options = unknown> {
|
|
|
6
6
|
attach(limit?: number, options?: Options & {afterSequence?: number}): Promise<FrontendAttachSnapshot>;
|
|
7
7
|
sendInput(prompt: string, options?: Options & {imageUrls?: string[]}): Promise<Accepted>;
|
|
8
8
|
submit(prompt: string, options?: Options & {imageUrls?: string[]}): Promise<Reply>;
|
|
9
|
-
interrupt(options?: Options): Promise<Interrupted>;
|
|
9
|
+
interrupt(options?: Options & {expectedTurn?: string}): Promise<Interrupted>;
|
|
10
10
|
steer(prompt: string, options?: Options): Promise<Queued>;
|
|
11
11
|
stopTask(taskId: string, options?: Options): Promise<Stopped>;
|
|
12
12
|
respond(response: FrontendResponse, options?: Options): Promise<Accepted>;
|
|
@@ -7,7 +7,7 @@ export class GeneratedFrontendClient {
|
|
|
7
7
|
attach(limit = 1000, { afterSequence, ...options } = {}) { return this.rpc(FRONTEND_METHODS.attach, { limit, ...(afterSequence === undefined ? {} : {after_sequence:afterSequence}) }, options); }
|
|
8
8
|
sendInput(prompt, { imageUrls, ...options } = {}) { return this.rpc(FRONTEND_METHODS.send_input, { prompt, ...(imageUrls?.length ? {image_urls:imageUrls} : {}) }, options); }
|
|
9
9
|
submit(prompt, { imageUrls, ...options } = {}) { return this.rpc(FRONTEND_METHODS.submit, { prompt, ...(imageUrls?.length ? {image_urls:imageUrls} : {}) }, options); }
|
|
10
|
-
interrupt(options) { return this.rpc(FRONTEND_METHODS.interrupt, {}, options); }
|
|
10
|
+
interrupt({ expectedTurn, ...options } = {}) { return this.rpc(FRONTEND_METHODS.interrupt, { ...(expectedTurn === undefined ? {} : { expected_turn: expectedTurn }) }, options); }
|
|
11
11
|
steer(prompt, options) { return this.rpc(FRONTEND_METHODS.steer, { prompt }, options); }
|
|
12
12
|
stopTask(taskId, options) { return this.rpc(FRONTEND_METHODS.stop_task, { task_id: taskId }, options); }
|
|
13
13
|
respond(response, options) { return this.rpc(FRONTEND_METHODS.respond, { response }, options); }
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
export type JsonPrimitive = string | number | boolean | null;
|
|
3
3
|
export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue };
|
|
4
4
|
export type Empty = Record<string, never>;
|
|
5
|
+
export interface InterruptParams {
|
|
6
|
+
expected_turn?: string;
|
|
7
|
+
}
|
|
5
8
|
export type ChatRole = "system" | "user" | "assistant" | "tool";
|
|
6
9
|
export interface FunctionCall {
|
|
7
10
|
name: string;
|
|
@@ -79,7 +82,11 @@ export interface FrontendAttachSnapshot {
|
|
|
79
82
|
descriptor: FrontendRuntimeDescriptor;
|
|
80
83
|
history: ChatMessage[];
|
|
81
84
|
history_cursor: number;
|
|
85
|
+
snapshot_cursor?: number | null;
|
|
86
|
+
history_events?: FrontendEvent[];
|
|
87
|
+
history_events_dropped?: number;
|
|
82
88
|
replay: FrontendEvent[];
|
|
89
|
+
pending_requests?: FrontendRequest[] | null;
|
|
83
90
|
}
|
|
84
91
|
export type FrontendApprovalDecision = "deny" | "allow" | "allow_for_session";
|
|
85
92
|
export type FrontendElicitationAction = "accept" | "decline" | "cancel";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-frontend",
|
|
3
|
-
"version": "
|
|
3
|
+
"devEngines": { "packageManager": { "name": "pnpm", "version": ">=12.15.0", "onFail": "error" } },
|
|
4
|
+
"version": "0.2.12",
|
|
4
5
|
"type": "module",
|
|
5
6
|
"description": "Generated language-neutral attachment client for Volter Harness SDK runtimes",
|
|
6
7
|
"exports": {
|