@volter/supercode-frontend-vscode 0.1.44 → 0.1.46
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 +18 -0
- package/dist/extension.d.ts +10 -0
- package/dist/extension.js +140 -26
- package/dist/index.d.ts +1 -1
- package/dist/native-session.d.ts +1 -1
- package/dist/native-session.js +20 -3
- package/package.json +2 -2
- package/vendor/supercode-frontend/contract.json +7 -2
- package/vendor/supercode-frontend/generated.d.mts +3 -3
- package/vendor/supercode-frontend/package.json +1 -1
package/README.md
CHANGED
|
@@ -430,3 +430,21 @@ it never submits a new chat prompt. The host guards the addressed conversation,
|
|
|
430
430
|
disables the choices during dispatch and retires them only after an acknowledged
|
|
431
431
|
response or an already-resolved result. Hosts without this capability retain
|
|
432
432
|
stable command buttons. Other request kinds retain their existing presentation.
|
|
433
|
+
|
|
434
|
+
## External runtime handoff
|
|
435
|
+
|
|
436
|
+
`supercode.frontend.connect(handoff)` accepts a `SupercodeRuntimeHandoff` with
|
|
437
|
+
`baseUrl`, `clientId`, `connectionId`, `credentialFile`, optional permissions and
|
|
438
|
+
label. Credentials are read from the private file; command arguments contain no
|
|
439
|
+
raw token. External attachments retain their exact identity across reloads.
|
|
440
|
+
`supercode.frontend.openSession(connectionId)` opens that conversation;
|
|
441
|
+
`supercode.frontend.status(true)` reports connection state, conversation view
|
|
442
|
+
readiness/resource and rendered event cursor. View readiness follows history
|
|
443
|
+
restoration and live-response subscription, rather than a socket alone.
|
|
444
|
+
`supercode.frontend.disconnect(connectionId)` also forgets the saved attachment.
|
|
445
|
+
IDs belonging to managed conversations cannot be reused for external attachments.
|
|
446
|
+
Calling `connect` without arguments retains the interactive settings flow.
|
|
447
|
+
|
|
448
|
+
These are ordinary connection/view APIs for any external runtime. Playback or
|
|
449
|
+
recording policy belongs to its runtime/controller; a host needs no replay-specific
|
|
450
|
+
commands. [Replay recording](../../docs/guides/replay-recording.md) illustrates a consumer.
|
package/dist/extension.d.ts
CHANGED
|
@@ -15,6 +15,12 @@ export interface SupercodeConnectOptions {
|
|
|
15
15
|
*/
|
|
16
16
|
unavailable?: string;
|
|
17
17
|
}
|
|
18
|
+
/** An external runtime handoff carries a private credential file, never a token in arguments. */
|
|
19
|
+
export interface SupercodeRuntimeHandoff extends Omit<SupercodeConnectOptions, 'token' | 'unavailable'> {
|
|
20
|
+
baseUrl: string;
|
|
21
|
+
clientId: string;
|
|
22
|
+
credentialFile: string;
|
|
23
|
+
}
|
|
18
24
|
export interface SupercodeConnectionInfo {
|
|
19
25
|
readonly id: string;
|
|
20
26
|
readonly baseUrl: string;
|
|
@@ -22,6 +28,10 @@ export interface SupercodeConnectionInfo {
|
|
|
22
28
|
readonly sessionId: string | null;
|
|
23
29
|
readonly model: string | null;
|
|
24
30
|
readonly turnState: 'idle' | 'busy' | null;
|
|
31
|
+
readonly connectionState: 'disconnected' | 'connected' | 'closed';
|
|
32
|
+
readonly viewReady: boolean;
|
|
33
|
+
readonly viewResource: string | null;
|
|
34
|
+
readonly cursor: number;
|
|
25
35
|
}
|
|
26
36
|
/** What `activate` returns: the door a host uses to hand over a connection. */
|
|
27
37
|
export interface SupercodeFrontendApi {
|
package/dist/extension.js
CHANGED
|
@@ -87,6 +87,10 @@ class SupercodeFrontends {
|
|
|
87
87
|
sessionId: connection.session.descriptor?.session_id ?? null,
|
|
88
88
|
model: connection.session.descriptor?.model ?? null,
|
|
89
89
|
turnState: connection.session.descriptor?.turn_state ?? null,
|
|
90
|
+
connectionState: connection.session.connection,
|
|
91
|
+
viewReady: connection.viewReady === true && connection.session.connection === 'connected',
|
|
92
|
+
viewResource: connection.viewResource ?? null,
|
|
93
|
+
cursor: connection.session.cursor,
|
|
90
94
|
};
|
|
91
95
|
}
|
|
92
96
|
async connect(options, log) {
|
|
@@ -411,7 +415,7 @@ export async function activate(context) {
|
|
|
411
415
|
const boundId = (resource) => resolveSessionBinding(resource, controls?.state.sessions ?? [], resourceBindings);
|
|
412
416
|
let applying = Promise.resolve();
|
|
413
417
|
// the reason the last send from a new chat made no conversation, said by that draft's request handler
|
|
414
|
-
|
|
418
|
+
const refusedSends = new Map(); // by the draft's resource ('' when the send named none)
|
|
415
419
|
let pendingDraft;
|
|
416
420
|
let focusedResource;
|
|
417
421
|
let focusGeneration = 0;
|
|
@@ -436,6 +440,37 @@ export async function activate(context) {
|
|
|
436
440
|
// them, so they open as what they are: attachments, shown as their runtime
|
|
437
441
|
// reports them, and never taken for the conversation the host manages.
|
|
438
442
|
const attachedIds = new Set();
|
|
443
|
+
const savedAttachments = new Map(context.workspaceState.get('supercode.externalAttachments', [])
|
|
444
|
+
.map(entry => [entry.connectionId, entry]));
|
|
445
|
+
const restoringAttachments = new Map();
|
|
446
|
+
const attachmentSecretKey = (id) => `supercode.frontend.attachment:${id}`;
|
|
447
|
+
const saveAttachments = () => context.workspaceState.update('supercode.externalAttachments', [...savedAttachments.values()]);
|
|
448
|
+
const restoreAttachment = (id) => {
|
|
449
|
+
const existing = frontends.get(id);
|
|
450
|
+
if (existing)
|
|
451
|
+
return Promise.resolve(existing);
|
|
452
|
+
const pending = restoringAttachments.get(id);
|
|
453
|
+
if (pending)
|
|
454
|
+
return pending;
|
|
455
|
+
const saved = savedAttachments.get(id);
|
|
456
|
+
if (!saved)
|
|
457
|
+
return Promise.resolve(undefined);
|
|
458
|
+
const restoration = (async () => {
|
|
459
|
+
const token = saved.credentialFile ? await readCredentialFile(saved.credentialFile) : await context.secrets.get(attachmentSecretKey(id));
|
|
460
|
+
if (!token)
|
|
461
|
+
throw new Error('The saved runtime credential is unavailable. Attach to the runtime again.');
|
|
462
|
+
const connection = await frontends.connect({ ...saved, token }, log);
|
|
463
|
+
if (connection)
|
|
464
|
+
attachedIds.add(id);
|
|
465
|
+
return connection;
|
|
466
|
+
})().finally(() => restoringAttachments.delete(id));
|
|
467
|
+
restoringAttachments.set(id, restoration);
|
|
468
|
+
return restoration;
|
|
469
|
+
};
|
|
470
|
+
for (const id of savedAttachments.keys()) {
|
|
471
|
+
attachedIds.add(id);
|
|
472
|
+
await restoreAttachment(id).catch(error => log.warn(`Could not restore runtime attachment ${id}: ${String(error)}`));
|
|
473
|
+
}
|
|
439
474
|
const managed = () => frontends.all.find((connection) => !attachedIds.has(connection.id));
|
|
440
475
|
// The host runs one conversation at a time, so a running or waiting turn refuses a new or another chat. This is
|
|
441
476
|
// the sentence that refusal says to the person; each caller picks the one place it is shown (see its uses).
|
|
@@ -448,7 +483,8 @@ export async function activate(context) {
|
|
|
448
483
|
return `A turn is still running in the current chat. Let it finish or cancel it before ${action}.`;
|
|
449
484
|
};
|
|
450
485
|
// The attached runtime the person is looking at: the Chat names its harness, not the host's.
|
|
451
|
-
|
|
486
|
+
const restoredFocus = context.workspaceState.get('supercode.focusedAttachment');
|
|
487
|
+
let focusedAttached = restoredFocus ? frontends.get(restoredFocus) : undefined;
|
|
452
488
|
let approvalAvailable = false;
|
|
453
489
|
context.subscriptions.push(frontends.onDidChange(() => {
|
|
454
490
|
const active = managed();
|
|
@@ -661,7 +697,25 @@ export async function activate(context) {
|
|
|
661
697
|
terminal.show();
|
|
662
698
|
}));
|
|
663
699
|
const restoring = new Map();
|
|
664
|
-
const connectionFor = async (resource, activate = true) => {
|
|
700
|
+
const connectionFor = async (resource, activate = true, token) => {
|
|
701
|
+
if (savedAttachments.has(resource.authority)) {
|
|
702
|
+
let attached;
|
|
703
|
+
while (!attached) {
|
|
704
|
+
if (token?.isCancellationRequested)
|
|
705
|
+
throw new vscode.CancellationError();
|
|
706
|
+
try {
|
|
707
|
+
attached = await restoreAttachment(resource.authority);
|
|
708
|
+
if (!attached)
|
|
709
|
+
throw new Error('This runtime attachment is no longer saved.');
|
|
710
|
+
}
|
|
711
|
+
catch (error) {
|
|
712
|
+
log.warn(`Waiting for runtime attachment ${resource.authority}: ${String(error)}`);
|
|
713
|
+
if (!token)
|
|
714
|
+
throw error;
|
|
715
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
665
719
|
if (!controls) {
|
|
666
720
|
const connection = frontends.get(resource.authority);
|
|
667
721
|
if (!connection)
|
|
@@ -671,6 +725,7 @@ export async function activate(context) {
|
|
|
671
725
|
const attached = attachedIds.has(resource.authority) ? frontends.get(resource.authority) : undefined;
|
|
672
726
|
if (activate && focusedAttached !== attached) {
|
|
673
727
|
focusedAttached = attached;
|
|
728
|
+
await context.workspaceState.update('supercode.focusedAttachment', attached?.id);
|
|
674
729
|
controlsChanged.fire();
|
|
675
730
|
}
|
|
676
731
|
if (attached)
|
|
@@ -734,12 +789,10 @@ export async function activate(context) {
|
|
|
734
789
|
if (!controls)
|
|
735
790
|
throw new Error('This host does not expose harness or model configuration.');
|
|
736
791
|
const active = managed();
|
|
737
|
-
//
|
|
792
|
+
// its caller (a new chat's first send) says the refusal in that chat
|
|
738
793
|
const refusal = busyRefusal(active, 'starting a new chat');
|
|
739
|
-
if (refusal)
|
|
740
|
-
void vscode.window.showWarningMessage(refusal);
|
|
794
|
+
if (refusal)
|
|
741
795
|
throw new Error(refusal);
|
|
742
|
-
}
|
|
743
796
|
if (active)
|
|
744
797
|
await saveManagedHistory(active);
|
|
745
798
|
const state = await controls.select(selection);
|
|
@@ -851,7 +904,9 @@ export async function activate(context) {
|
|
|
851
904
|
if (busy)
|
|
852
905
|
throw new Error(busy);
|
|
853
906
|
const draft = activeDraft();
|
|
854
|
-
|
|
907
|
+
// the picker ends in a cancellation for a sign-in choice too: the draft's choice says which (sign-in is refused
|
|
908
|
+
// below, in the chat); a draft that went away is the real cancellation
|
|
909
|
+
await draft?.choosing?.catch(() => undefined);
|
|
855
910
|
if (draft && activeDraft() !== draft)
|
|
856
911
|
throw new vscode.CancellationError();
|
|
857
912
|
await controls.refresh();
|
|
@@ -928,11 +983,11 @@ export async function activate(context) {
|
|
|
928
983
|
catch (error) {
|
|
929
984
|
if (error instanceof vscode.CancellationError)
|
|
930
985
|
throw error;
|
|
931
|
-
|
|
986
|
+
refusedSends.set(activeDraft()?.resource.toString() ?? '', String(error?.message ?? error));
|
|
932
987
|
return undefined;
|
|
933
988
|
}
|
|
934
989
|
},
|
|
935
|
-
resolve: (resource) => connectionFor(resource, false),
|
|
990
|
+
resolve: (resource, token) => connectionFor(resource, false, token),
|
|
936
991
|
attached: (connection) => attachedIds.has(connection.id),
|
|
937
992
|
catalogue: () => controls?.state.sessions ?? [],
|
|
938
993
|
bindings: boundId,
|
|
@@ -951,7 +1006,13 @@ export async function activate(context) {
|
|
|
951
1006
|
await connectionFor(resource);
|
|
952
1007
|
if (generation !== focusGeneration)
|
|
953
1008
|
return;
|
|
954
|
-
|
|
1009
|
+
if (attachedIds.has(id)) {
|
|
1010
|
+
if (!controls?.state.openSessionCommand)
|
|
1011
|
+
throw new Error('The host cannot reveal this conversation.');
|
|
1012
|
+
await vscode.commands.executeCommand(controls.state.openSessionCommand, resource.toString());
|
|
1013
|
+
}
|
|
1014
|
+
else
|
|
1015
|
+
await revealManaged();
|
|
955
1016
|
}));
|
|
956
1017
|
context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.configure', async (supplied) => {
|
|
957
1018
|
if (supplied?.permissions) {
|
|
@@ -1177,9 +1238,11 @@ export async function activate(context) {
|
|
|
1177
1238
|
const handler = async (request, chatContext, stream, token) => {
|
|
1178
1239
|
await applying;
|
|
1179
1240
|
const requestedResource = chatContext.chatSessionContext?.chatSessionItem?.resource;
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1241
|
+
const refusedKey = requestedResource?.path.startsWith('/untitled-') && !boundId(requestedResource)
|
|
1242
|
+
? [requestedResource.toString(), ''].find(key => refusedSends.has(key)) : undefined;
|
|
1243
|
+
if (refusedKey !== undefined) {
|
|
1244
|
+
const refusal = refusedSends.get(refusedKey);
|
|
1245
|
+
refusedSends.delete(refusedKey);
|
|
1183
1246
|
// thrown as is: the workbench writes a handler's error into the chat
|
|
1184
1247
|
throw new Error(`${refusal} Nothing was sent; send it again in a new chat.`);
|
|
1185
1248
|
}
|
|
@@ -1389,7 +1452,17 @@ export async function activate(context) {
|
|
|
1389
1452
|
await settings.update('baseUrl', baseUrl, vscode.ConfigurationTarget.Global);
|
|
1390
1453
|
await context.secrets.store(secretKey(baseUrl), token);
|
|
1391
1454
|
await connectFromSettings();
|
|
1392
|
-
}), vscode.commands.registerCommand('supercode.frontend.connect', () =>
|
|
1455
|
+
}), vscode.commands.registerCommand('supercode.frontend.connect', async (handoff) => {
|
|
1456
|
+
if (handoff === undefined)
|
|
1457
|
+
return connectFromSettings();
|
|
1458
|
+
if (!handoff || typeof handoff.baseUrl !== 'string' || typeof handoff.clientId !== 'string' || !handoff.clientId.trim() || typeof handoff.credentialFile !== 'string') {
|
|
1459
|
+
throw new TypeError('A runtime handoff needs baseUrl, clientId and credentialFile.');
|
|
1460
|
+
}
|
|
1461
|
+
const token = await readCredentialFile(handoff.credentialFile);
|
|
1462
|
+
const connection = await connectExternal({ baseUrl: handoff.baseUrl, token, clientId: handoff.clientId,
|
|
1463
|
+
connectionId: handoff.connectionId, permissions: handoff.permissions, label: handoff.label }, handoff.credentialFile);
|
|
1464
|
+
return connection ? frontends.info(connection) : undefined;
|
|
1465
|
+
}),
|
|
1393
1466
|
// The create command the core's own `chatSessions/newSession` menu invokes
|
|
1394
1467
|
// for this session type (`chatSessions.contribution.ts`
|
|
1395
1468
|
// `_resolveCreateSubMenuCommandId`). It opens a chat on the supercode
|
|
@@ -1402,7 +1475,7 @@ export async function activate(context) {
|
|
|
1402
1475
|
}
|
|
1403
1476
|
const where = position === 'editor' ? 'editor' : 'sidebar';
|
|
1404
1477
|
await vscode.commands.executeCommand(`workbench.action.chat.openNewChatSessionInPlace.${SESSION_SCHEME}`, where);
|
|
1405
|
-
}), vscode.commands.registerCommand('supercode.frontend.disconnect', () =>
|
|
1478
|
+
}), vscode.commands.registerCommand('supercode.frontend.disconnect', (id) => disconnectExternal(id)),
|
|
1406
1479
|
// The one door an answer button uses. It answers the runtime's pending
|
|
1407
1480
|
// request through `frontend.v2.respond` and narrates itself, because a
|
|
1408
1481
|
// click whose effect is invisible is a click nobody can trust.
|
|
@@ -1432,6 +1505,40 @@ export async function activate(context) {
|
|
|
1432
1505
|
: REQUESTED_PROPOSALS.map((name) => `${name} not granted: ${PROPOSAL_EFFECTS[name].without}`).join('\n'),
|
|
1433
1506
|
});
|
|
1434
1507
|
}));
|
|
1508
|
+
async function connectExternal(options, credentialFile) {
|
|
1509
|
+
const id = options.connectionId ?? options.clientId;
|
|
1510
|
+
if (id && controls?.state.sessions.some(session => session.id === id))
|
|
1511
|
+
throw new Error('An external runtime cannot replace a host-managed conversation.');
|
|
1512
|
+
const existing = id ? frontends.get(id) : undefined;
|
|
1513
|
+
if (existing && existing.baseUrl !== options.baseUrl)
|
|
1514
|
+
throw new Error('This connection identity already belongs to another runtime.');
|
|
1515
|
+
const connection = await frontends.connect(options, log);
|
|
1516
|
+
if (connection && options.token) {
|
|
1517
|
+
if (!credentialFile)
|
|
1518
|
+
await context.secrets.store(attachmentSecretKey(connection.id), options.token);
|
|
1519
|
+
savedAttachments.set(connection.id, { baseUrl: connection.baseUrl, clientId: options.clientId ?? connection.id,
|
|
1520
|
+
connectionId: connection.id, permissions: [...connection.session.permissions], label: connection.label, credentialFile });
|
|
1521
|
+
attachedIds.add(connection.id);
|
|
1522
|
+
await saveAttachments();
|
|
1523
|
+
controlsChanged.fire();
|
|
1524
|
+
}
|
|
1525
|
+
return connection;
|
|
1526
|
+
}
|
|
1527
|
+
async function disconnectExternal(id) {
|
|
1528
|
+
const ids = id ? [id] : [...savedAttachments.keys()];
|
|
1529
|
+
for (const target of ids) {
|
|
1530
|
+
savedAttachments.delete(target);
|
|
1531
|
+
attachedIds.delete(target);
|
|
1532
|
+
await context.secrets.delete(attachmentSecretKey(target));
|
|
1533
|
+
if (focusedAttached?.id === target)
|
|
1534
|
+
focusedAttached = undefined;
|
|
1535
|
+
}
|
|
1536
|
+
await saveAttachments();
|
|
1537
|
+
if (!focusedAttached)
|
|
1538
|
+
await context.workspaceState.update('supercode.focusedAttachment', undefined);
|
|
1539
|
+
await frontends.disconnect(id);
|
|
1540
|
+
controlsChanged.fire();
|
|
1541
|
+
}
|
|
1435
1542
|
async function connectFromSettings() {
|
|
1436
1543
|
const settings = vscode.workspace.getConfiguration('supercode.frontend');
|
|
1437
1544
|
const baseUrl = settings.get('baseUrl')?.trim() || context.globalState.get(HANDED_OVER_KEY);
|
|
@@ -1442,16 +1549,11 @@ export async function activate(context) {
|
|
|
1442
1549
|
log.info('Volter Harness: a base URL is configured but no token is stored; run Volter Harness: Set Runtime Token.');
|
|
1443
1550
|
return;
|
|
1444
1551
|
}
|
|
1445
|
-
|
|
1446
|
-
.connect({ baseUrl, token, permissions: normalizePermissions(settings.get('permissions'), ['observe']) }, log)
|
|
1552
|
+
await connectExternal({ baseUrl, token, permissions: normalizePermissions(settings.get('permissions'), ['observe']) })
|
|
1447
1553
|
.catch((error) => {
|
|
1448
1554
|
log.error(`Volter Harness could not attach to ${baseUrl}: ${String(error?.message ?? error)}`);
|
|
1449
1555
|
return undefined;
|
|
1450
1556
|
});
|
|
1451
|
-
if (controls && connection) {
|
|
1452
|
-
attachedIds.add(connection.id);
|
|
1453
|
-
controlsChanged.fire();
|
|
1454
|
-
}
|
|
1455
1557
|
}
|
|
1456
1558
|
/**
|
|
1457
1559
|
* The handover path: a runtime (or a product that launched this editor's
|
|
@@ -1505,6 +1607,12 @@ export async function activate(context) {
|
|
|
1505
1607
|
async function openResumedSession() {
|
|
1506
1608
|
if (!granted.has('chatSessionsProvider'))
|
|
1507
1609
|
return;
|
|
1610
|
+
const focusedId = context.workspaceState.get('supercode.focusedAttachment');
|
|
1611
|
+
const attached = focusedId ? frontends.get(focusedId) : undefined;
|
|
1612
|
+
if (attached && controls?.state.openSessionCommand) {
|
|
1613
|
+
await vscode.commands.executeCommand('supercode.frontend.openSession', attached.id);
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
1508
1616
|
if (controls) {
|
|
1509
1617
|
await refreshSetup();
|
|
1510
1618
|
return;
|
|
@@ -1552,10 +1660,10 @@ export async function activate(context) {
|
|
|
1552
1660
|
context.subscriptions.push({ dispose: () => { setupDisposed = true; clearTimeout(setupTimer); } });
|
|
1553
1661
|
return {
|
|
1554
1662
|
connect: async (options) => {
|
|
1555
|
-
const connection = await
|
|
1663
|
+
const connection = await connectExternal(options);
|
|
1556
1664
|
return connection ? frontends.info(connection) : undefined;
|
|
1557
1665
|
},
|
|
1558
|
-
disconnect: (connectionId) =>
|
|
1666
|
+
disconnect: (connectionId) => disconnectExternal(connectionId),
|
|
1559
1667
|
get connections() {
|
|
1560
1668
|
return frontends.all.map((connection) => frontends.info(connection));
|
|
1561
1669
|
},
|
|
@@ -1754,12 +1862,14 @@ function registerSessions(context, frontends, participant, granted, handler, his
|
|
|
1754
1862
|
const boundHandler = (request, chatContext, stream, token) => handler(request, { ...chatContext, chatSessionContext: { chatSessionItem: { resource, label: 'Conversation' } } }, stream, token);
|
|
1755
1863
|
// Only a native untitled resource can create a conversation. Existing
|
|
1756
1864
|
// resources must resolve their exact saved binding; there is no fallback.
|
|
1757
|
-
const connection = controls ? (resource.path.startsWith('/untitled-') && !controls.bindings(resource) ? undefined : await controls.resolve(resource)) : frontends.get(resource.authority);
|
|
1865
|
+
const connection = controls ? (resource.path.startsWith('/untitled-') && !controls.bindings(resource) ? undefined : await controls.resolve(resource, token)) : frontends.get(resource.authority);
|
|
1758
1866
|
if (token.isCancellationRequested)
|
|
1759
1867
|
throw new vscode.CancellationError();
|
|
1760
1868
|
if (!connection) {
|
|
1761
1869
|
return { history: [], requestHandler: boundHandler };
|
|
1762
1870
|
}
|
|
1871
|
+
connection.viewReady = false;
|
|
1872
|
+
connection.viewResource = resource.toString();
|
|
1763
1873
|
if (controls && !controls.attached(connection)) {
|
|
1764
1874
|
controls.setPermission(resource, inputState.groups.find(group => group.id === 'permissions')?.selected?.id);
|
|
1765
1875
|
inputState.groups = controls.options(resource);
|
|
@@ -1811,6 +1921,9 @@ function registerSessions(context, frontends, participant, granted, handler, his
|
|
|
1811
1921
|
history.push(new turns.ChatResponseTurn(historyParts(oneRowPerToolCall(turn.parts ?? [])), {}, authorId));
|
|
1812
1922
|
}
|
|
1813
1923
|
const busy = connection.session.descriptor?.turn_state === 'busy';
|
|
1924
|
+
const renderedCursor = connection.session.cursor;
|
|
1925
|
+
if (!busy)
|
|
1926
|
+
connection.viewReady = attached;
|
|
1814
1927
|
return {
|
|
1815
1928
|
title: connection.label,
|
|
1816
1929
|
options: controls?.attached(connection) ? undefined : controls?.currentOptions(resource),
|
|
@@ -1836,9 +1949,10 @@ function registerSessions(context, frontends, participant, granted, handler, his
|
|
|
1836
1949
|
canRespond: connection.session.descriptor?.actions.respond !== false,
|
|
1837
1950
|
}), granted, state);
|
|
1838
1951
|
watch?.sweep();
|
|
1839
|
-
});
|
|
1952
|
+
}, () => { connection.viewReady = true; }, renderedCursor);
|
|
1840
1953
|
}
|
|
1841
1954
|
finally {
|
|
1955
|
+
connection.viewReady = connection.session.descriptor?.turn_state !== 'busy';
|
|
1842
1956
|
watch?.dispose();
|
|
1843
1957
|
}
|
|
1844
1958
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,4 +4,4 @@ export type { ChatGPTSignInWorkPhase, ChatGPTSignInOperation } from './chat-setu
|
|
|
4
4
|
export { grantedProposals, PROPOSAL_EFFECTS, REQUESTED_PROPOSALS, type ProposalName, } from './proposals.js';
|
|
5
5
|
export { EventSequenceGapError, LEASE_ERROR_NAMES, RequestNotPendingError, TURN_TERMINAL_KINDS, VscodeFrontendSession, type VscodeFrontendActions, type VscodeRuntimeClient, } from './session.js';
|
|
6
6
|
export { HISTORY_MARKER_PROMPT, VSCODE_CHAT_LIMITS, acceptedDecisions, diffPaths, fence, opaqueText, projectEvent, projectPendingRequests, projectHistory, projectReplayTurns, requestAnswers, requestNeedsTypedReply, requestSubject, requestSummary, resolutionSentence, resolveParts, toolSubject, typedAnswer, type ChatAnswer, type ChatPart, type ProjectedPart, type ProjectedTurn, type ProjectionContext, } from './view.js';
|
|
7
|
-
export type { SupercodeConnectOptions, SupercodeConnectionInfo, SupercodeFrontendApi, } from './extension.js';
|
|
7
|
+
export type { SupercodeConnectOptions, SupercodeRuntimeHandoff, SupercodeConnectionInfo, SupercodeFrontendApi, } from './extension.js';
|
package/dist/native-session.d.ts
CHANGED
|
@@ -3,4 +3,4 @@ import type { FrontendEvent } from '../vendor/supercode-frontend/generated.mjs';
|
|
|
3
3
|
import { type VscodeFrontendSession } from './session.js';
|
|
4
4
|
export declare function sessionStatus(session: Pick<VscodeFrontendSession, 'descriptor' | 'pendingRequests'>): 'NeedsInput' | 'InProgress' | 'Completed';
|
|
5
5
|
/** Follow a restored turn, including the interval between snapshot and callback. */
|
|
6
|
-
export declare function observeActiveResponse(session: Pick<VscodeFrontendSession, 'descriptor' | 'onEvent' | 'interrupt'>, token: CancellationToken, write: (event: FrontendEvent) => void): Promise<void>;
|
|
6
|
+
export declare function observeActiveResponse(session: Pick<VscodeFrontendSession, 'descriptor' | 'onEvent' | 'interrupt' | 'actions' | 'replay'>, token: CancellationToken, write: (event: FrontendEvent) => void, onSubscribed?: () => void, afterSequence?: number): Promise<void>;
|
package/dist/native-session.js
CHANGED
|
@@ -3,7 +3,7 @@ export function sessionStatus(session) {
|
|
|
3
3
|
return session.pendingRequests.size ? 'NeedsInput' : session.descriptor?.turn_state === 'busy' ? 'InProgress' : 'Completed';
|
|
4
4
|
}
|
|
5
5
|
/** Follow a restored turn, including the interval between snapshot and callback. */
|
|
6
|
-
export async function observeActiveResponse(session, token, write) {
|
|
6
|
+
export async function observeActiveResponse(session, token, write, onSubscribed, afterSequence) {
|
|
7
7
|
let unsubscribe = () => { };
|
|
8
8
|
let cancellation;
|
|
9
9
|
try {
|
|
@@ -13,10 +13,20 @@ export async function observeActiveResponse(session, token, write) {
|
|
|
13
13
|
if (cancelling)
|
|
14
14
|
return;
|
|
15
15
|
cancelling = true;
|
|
16
|
+
// Closing an observed view cannot interrupt a runtime that grants no
|
|
17
|
+
// interrupt action. Stop observing without inventing control authority.
|
|
18
|
+
if (!session.actions().interrupt) {
|
|
19
|
+
resolve();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
16
22
|
// Wait for the runtime acknowledgement; a stopped UI is not a stopped harness.
|
|
17
23
|
void session.interrupt().then(resolve, reject);
|
|
18
24
|
};
|
|
19
|
-
|
|
25
|
+
let delivered = afterSequence ?? 0;
|
|
26
|
+
const consume = (event) => {
|
|
27
|
+
if (afterSequence !== undefined && event.sequence <= delivered)
|
|
28
|
+
return;
|
|
29
|
+
delivered = event.sequence;
|
|
20
30
|
try {
|
|
21
31
|
write(event);
|
|
22
32
|
}
|
|
@@ -26,7 +36,14 @@ export async function observeActiveResponse(session, token, write) {
|
|
|
26
36
|
}
|
|
27
37
|
if (!cancelling && TURN_TERMINAL_KINDS.includes(event.kind))
|
|
28
38
|
resolve();
|
|
29
|
-
}
|
|
39
|
+
};
|
|
40
|
+
unsubscribe = session.onEvent(consume);
|
|
41
|
+
if (afterSequence !== undefined) {
|
|
42
|
+
for (const event of session.replay)
|
|
43
|
+
if (event.sequence > afterSequence)
|
|
44
|
+
consume(event);
|
|
45
|
+
}
|
|
46
|
+
onSubscribed?.();
|
|
30
47
|
cancellation = token.onCancellationRequested(cancel);
|
|
31
48
|
if (token.isCancellationRequested)
|
|
32
49
|
cancel();
|
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.46",
|
|
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",
|
|
@@ -190,7 +190,7 @@
|
|
|
190
190
|
},
|
|
191
191
|
"supercodeFrontendContract": {
|
|
192
192
|
"name": "@volter/supercode-frontend",
|
|
193
|
-
"version": "0.2.
|
|
193
|
+
"version": "0.2.8",
|
|
194
194
|
"mode": "generated-artifact"
|
|
195
195
|
},
|
|
196
196
|
"license": "MIT OR Apache-2.0"
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"argument_hint": {"type": "string", "optional": true, "nullable": true}
|
|
74
74
|
}
|
|
75
75
|
},
|
|
76
|
-
"FrontendOperationKind": {"kind": "enum", "values": ["prompt", "file", "model", "session", "subagent", "image", "reduction"]},
|
|
76
|
+
"FrontendOperationKind": {"kind": "enum", "values": ["prompt", "file", "model", "session", "subagent", "image", "reduction", "replay"]},
|
|
77
77
|
"FrontendOperationDescriptor": {
|
|
78
78
|
"kind": "object",
|
|
79
79
|
"fields": {
|
|
@@ -175,6 +175,10 @@
|
|
|
175
175
|
"prompt": {
|
|
176
176
|
"operation_id": {"type": "string"},
|
|
177
177
|
"arguments": {"type": "string"}
|
|
178
|
+
},
|
|
179
|
+
"replay": {
|
|
180
|
+
"operation_id": {"type": "string"},
|
|
181
|
+
"control": {"type": "string"}
|
|
178
182
|
}
|
|
179
183
|
}
|
|
180
184
|
},
|
|
@@ -182,7 +186,8 @@
|
|
|
182
186
|
"kind": "tagged_union",
|
|
183
187
|
"tag": "kind",
|
|
184
188
|
"variants": {
|
|
185
|
-
"prompt": {"reply": {"type": "string"}}
|
|
189
|
+
"prompt": {"reply": {"type": "string"}},
|
|
190
|
+
"replay": {"status": {"type": "JsonValue"}}
|
|
186
191
|
}
|
|
187
192
|
},
|
|
188
193
|
"RuntimeControllerLease": {
|
|
@@ -32,7 +32,7 @@ export interface FrontendCommandDescriptor {
|
|
|
32
32
|
description: string | null;
|
|
33
33
|
argument_hint?: string | null;
|
|
34
34
|
}
|
|
35
|
-
export type FrontendOperationKind = "prompt" | "file" | "model" | "session" | "subagent" | "image" | "reduction";
|
|
35
|
+
export type FrontendOperationKind = "prompt" | "file" | "model" | "session" | "subagent" | "image" | "reduction" | "replay";
|
|
36
36
|
export interface FrontendOperationDescriptor {
|
|
37
37
|
id: string;
|
|
38
38
|
kind: FrontendOperationKind;
|
|
@@ -84,8 +84,8 @@ export interface FrontendAttachSnapshot {
|
|
|
84
84
|
export type FrontendApprovalDecision = "deny" | "allow" | "allow_for_session";
|
|
85
85
|
export type FrontendElicitationAction = "accept" | "decline" | "cancel";
|
|
86
86
|
export type FrontendResponse = { kind: "approval"; request_id: number; decision: FrontendApprovalDecision } | { kind: "elicitation"; request_id: number; action: FrontendElicitationAction; content?: JsonValue | null } | { kind: "other"; request_id: number; action: FrontendElicitationAction; content?: JsonValue | null };
|
|
87
|
-
export type FrontendOperationInvocation = { kind: "prompt"; operation_id: string; arguments: string };
|
|
88
|
-
export type FrontendOperationResult = { kind: "prompt"; reply: string };
|
|
87
|
+
export type FrontendOperationInvocation = { kind: "prompt"; operation_id: string; arguments: string } | { kind: "replay"; operation_id: string; control: string };
|
|
88
|
+
export type FrontendOperationResult = { kind: "prompt"; reply: string } | { kind: "replay"; status: JsonValue };
|
|
89
89
|
export interface RuntimeControllerLease {
|
|
90
90
|
client_id: string;
|
|
91
91
|
expires_at_ms: number;
|