@volter/supercode-frontend-vscode 0.1.29 → 0.1.31
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/dist/extension.js +127 -48
- package/dist/host-controls.d.ts +4 -0
- package/dist/host-controls.js +16 -8
- package/package.json +1 -1
package/dist/extension.js
CHANGED
|
@@ -323,6 +323,7 @@ export async function activate(context) {
|
|
|
323
323
|
let applying = Promise.resolve();
|
|
324
324
|
let pendingDraft;
|
|
325
325
|
let focusedResource;
|
|
326
|
+
let focusGeneration = 0;
|
|
326
327
|
const activeDraft = () => pendingDraft?.resource && pendingDraft.resource.toString() === focusedResource?.toString() ? pendingDraft : undefined;
|
|
327
328
|
const draftSelection = () => {
|
|
328
329
|
const choice = activeDraft()?.choice;
|
|
@@ -333,8 +334,10 @@ export async function activate(context) {
|
|
|
333
334
|
focusedResource = resource;
|
|
334
335
|
if (pendingDraft?.resource && pendingDraft.resource.toString() !== resource?.toString())
|
|
335
336
|
pendingDraft = undefined;
|
|
336
|
-
if (changed)
|
|
337
|
+
if (changed) {
|
|
338
|
+
focusGeneration++;
|
|
337
339
|
controlsChanged.fire();
|
|
340
|
+
}
|
|
338
341
|
};
|
|
339
342
|
const frontends = new SupercodeFrontends();
|
|
340
343
|
// Runtimes attached by address ("Set Runtime Token", "Attach to Runtime")
|
|
@@ -388,11 +391,12 @@ export async function activate(context) {
|
|
|
388
391
|
const id = `${selection.harness}/${encodeURIComponent(model.id || '__default__')}`;
|
|
389
392
|
modelSelections.set(id, { ...selection, model: model.id });
|
|
390
393
|
const provider = runtimeIdentity(selection.harness, selection.model).provider;
|
|
391
|
-
|
|
394
|
+
const name = model.id ? model.name : runtimeIdentity(selection.harness, model.id).name;
|
|
395
|
+
return { id, name, family: selection.harness, version: '1',
|
|
392
396
|
statusIcon: new vscode.ThemeIcon(provider === 'anthropic' ? 'claude' : provider === 'openai' ? 'openai' : provider === 'google' ? 'google-gemini' : 'sparkle'),
|
|
393
397
|
maxInputTokens: 0, maxOutputTokens: 0, capabilities: {},
|
|
394
398
|
targetChatSessionType: SESSION_SCHEME, isUserSelectable: true, isDefault: model.id === selection.model,
|
|
395
|
-
tooltip: `${
|
|
399
|
+
tooltip: `${name} via ${harnessDisplayName(selection.harness)}${selection.effort ? ` · ${selection.effort} reasoning` : ''}. Model is fixed for this conversation. Use Model settings to choose model and reasoning for a new conversation.`,
|
|
396
400
|
};
|
|
397
401
|
});
|
|
398
402
|
},
|
|
@@ -406,11 +410,20 @@ export async function activate(context) {
|
|
|
406
410
|
const id = resource ? boundId(resource) : controls?.state.activeSession;
|
|
407
411
|
return { permissions: id ? permissions.get(id) ?? 'ask' : permission };
|
|
408
412
|
};
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
413
|
+
const savedConnections = new Map();
|
|
414
|
+
const saveManagedHistory = async (connection) => {
|
|
415
|
+
const state = await controls.open(connection.id);
|
|
416
|
+
if (state.activeSession !== connection.id)
|
|
417
|
+
throw new Error('The conversation changed before its history could be saved.');
|
|
418
|
+
connection.persistedHistory = state.history;
|
|
419
|
+
connection.historyTruncated = state.historyTruncated;
|
|
420
|
+
savedConnections.set(connection.id, connection);
|
|
421
|
+
};
|
|
422
|
+
const connectManaged = async (state = controls.state) => {
|
|
423
|
+
const env = state.connection;
|
|
424
|
+
if (!env || !state.activeSession)
|
|
412
425
|
throw new Error('The requested conversation has no runtime connection.');
|
|
413
|
-
const id =
|
|
426
|
+
const id = state.activeSession;
|
|
414
427
|
const existing = frontends.get(id);
|
|
415
428
|
if (existing && existing.baseUrl === env.SUPERCODE_FRONTEND_URL && existing.session.connection !== 'closed') {
|
|
416
429
|
if (existing.session.connection !== 'connected')
|
|
@@ -421,7 +434,7 @@ export async function activate(context) {
|
|
|
421
434
|
await frontends.disconnect(connection.id);
|
|
422
435
|
const connection = await frontends.connect({ connectionId: id, baseUrl: env.SUPERCODE_FRONTEND_URL,
|
|
423
436
|
token: await readCredentialFile(env.SUPERCODE_FRONTEND_CREDENTIAL_FILE), clientId: env.SUPERCODE_FRONTEND_CLIENT_ID,
|
|
424
|
-
permissions: normalizePermissions(env.SUPERCODE_FRONTEND_PERMISSIONS, ['observe']), label: runtimeIdentity(
|
|
437
|
+
permissions: normalizePermissions(env.SUPERCODE_FRONTEND_PERMISSIONS, ['observe']), label: runtimeIdentity(state.selection.harness, state.selection.model || state.actualModel).name }, log);
|
|
425
438
|
if (!connection)
|
|
426
439
|
throw new Error('Could not connect to the requested conversation.');
|
|
427
440
|
return connection;
|
|
@@ -458,17 +471,20 @@ export async function activate(context) {
|
|
|
458
471
|
};
|
|
459
472
|
let refreshingSetup;
|
|
460
473
|
const refreshSetup = () => {
|
|
461
|
-
|
|
474
|
+
if (refreshingSetup)
|
|
475
|
+
return refreshingSetup;
|
|
476
|
+
const pending = applying.then(async () => {
|
|
462
477
|
if (!controls)
|
|
463
478
|
return;
|
|
464
479
|
// Discover and publish readiness even while a blank chat holds a draft.
|
|
465
480
|
// Only the automatic reveal is suppressed; discovery must never stall.
|
|
466
481
|
await controls.refresh();
|
|
482
|
+
const state = controls.state;
|
|
467
483
|
controlsChanged.fire();
|
|
468
484
|
const preserveDraft = focusedResource?.path.startsWith('/untitled-');
|
|
469
|
-
if (
|
|
485
|
+
if (state.setup?.ready) {
|
|
470
486
|
setupRevealed = false;
|
|
471
|
-
await connectManaged();
|
|
487
|
+
await connectManaged(state);
|
|
472
488
|
controlsChanged.fire();
|
|
473
489
|
if (!preserveDraft && !setupHandoffComplete())
|
|
474
490
|
await revealManaged();
|
|
@@ -479,7 +495,9 @@ export async function activate(context) {
|
|
|
479
495
|
if (!preserveDraft)
|
|
480
496
|
await revealSetup();
|
|
481
497
|
}
|
|
482
|
-
})
|
|
498
|
+
});
|
|
499
|
+
applying = pending.then(() => undefined, () => undefined);
|
|
500
|
+
refreshingSetup = pending.finally(() => { refreshingSetup = undefined; });
|
|
483
501
|
return refreshingSetup;
|
|
484
502
|
};
|
|
485
503
|
const setupTerminals = new Map();
|
|
@@ -512,8 +530,8 @@ export async function activate(context) {
|
|
|
512
530
|
setupTerminals.set(key, terminal);
|
|
513
531
|
terminal.show();
|
|
514
532
|
}));
|
|
515
|
-
const
|
|
516
|
-
|
|
533
|
+
const restoring = new Map();
|
|
534
|
+
const connectionFor = async (resource, activate = true) => {
|
|
517
535
|
if (!controls) {
|
|
518
536
|
const connection = frontends.get(resource.authority);
|
|
519
537
|
if (!connection)
|
|
@@ -521,7 +539,7 @@ export async function activate(context) {
|
|
|
521
539
|
return connection;
|
|
522
540
|
}
|
|
523
541
|
const attached = attachedIds.has(resource.authority) ? frontends.get(resource.authority) : undefined;
|
|
524
|
-
if (focusedAttached !== attached) {
|
|
542
|
+
if (activate && focusedAttached !== attached) {
|
|
525
543
|
focusedAttached = attached;
|
|
526
544
|
controlsChanged.fire();
|
|
527
545
|
}
|
|
@@ -530,26 +548,55 @@ export async function activate(context) {
|
|
|
530
548
|
const id = boundId(resource);
|
|
531
549
|
if (!id)
|
|
532
550
|
throw new Error('This chat has no backend session binding. Start a new chat; no message was sent.');
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
551
|
+
// Content refresh is not a focus change. Replaying a previously connected
|
|
552
|
+
// conversation must not retire the runtime the person just selected.
|
|
553
|
+
if (!activate) {
|
|
554
|
+
const saved = frontends.get(id) ?? savedConnections.get(id);
|
|
555
|
+
if (saved)
|
|
556
|
+
return saved;
|
|
557
|
+
}
|
|
558
|
+
const existing = restoring.get(id);
|
|
559
|
+
// Coalesce adjacent requests only. A later A after a queued B must still
|
|
560
|
+
// activate A last, rather than borrowing an earlier A's completion.
|
|
561
|
+
if (existing?.completion === applying)
|
|
562
|
+
return existing.pending;
|
|
563
|
+
const pending = applying.then(async () => {
|
|
564
|
+
const active = managed();
|
|
565
|
+
if (active && active.id !== id && (active.session.descriptor?.turn_state === 'busy' || active.session.pendingRequests.size))
|
|
566
|
+
throw new Error('Finish or cancel the active turn before opening another conversation.');
|
|
567
|
+
if (active && active.id !== id) {
|
|
568
|
+
// Capture the exact saved transcript while its runtime still belongs
|
|
569
|
+
// to the host. Subsequent background content reads need no activation.
|
|
570
|
+
await saveManagedHistory(active);
|
|
571
|
+
}
|
|
572
|
+
let state;
|
|
573
|
+
try {
|
|
574
|
+
state = await controls.open(id);
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
log.error(`Could not restore conversation ${id}: ${String(error)}`);
|
|
578
|
+
throw error;
|
|
579
|
+
}
|
|
580
|
+
if (state.activeSession !== id)
|
|
581
|
+
throw new Error('The host did not restore the requested conversation.');
|
|
582
|
+
const connection = await connectManaged(state);
|
|
583
|
+
connection.persistedHistory = state.history;
|
|
584
|
+
connection.historyTruncated = state.historyTruncated;
|
|
585
|
+
savedConnections.set(id, connection);
|
|
586
|
+
if (active !== connection)
|
|
587
|
+
controlsChanged.fire();
|
|
588
|
+
return connection;
|
|
589
|
+
});
|
|
590
|
+
const completion = pending.then(() => undefined, () => undefined);
|
|
591
|
+
restoring.set(id, { pending, completion });
|
|
592
|
+
applying = completion;
|
|
536
593
|
try {
|
|
537
|
-
await
|
|
594
|
+
return await pending;
|
|
538
595
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
596
|
+
finally {
|
|
597
|
+
if (restoring.get(id)?.pending === pending)
|
|
598
|
+
restoring.delete(id);
|
|
542
599
|
}
|
|
543
|
-
const history = controls.state.history;
|
|
544
|
-
const historyTruncated = controls.state.historyTruncated;
|
|
545
|
-
const connection = await connectManaged();
|
|
546
|
-
connection.persistedHistory = history;
|
|
547
|
-
connection.historyTruncated = historyTruncated;
|
|
548
|
-
// Resolving an already-focused session must not republish its catalogue
|
|
549
|
-
// while the native widget is still opening that same resource.
|
|
550
|
-
if (active !== connection)
|
|
551
|
-
controlsChanged.fire();
|
|
552
|
-
return connection;
|
|
553
600
|
};
|
|
554
601
|
async function applySelection(selection) {
|
|
555
602
|
if (!controls)
|
|
@@ -557,9 +604,12 @@ export async function activate(context) {
|
|
|
557
604
|
const active = managed();
|
|
558
605
|
if (active?.session.descriptor?.turn_state === 'busy' || active?.session.pendingRequests.size)
|
|
559
606
|
throw new Error('Finish or cancel the current turn before starting a new chat.');
|
|
560
|
-
|
|
561
|
-
|
|
607
|
+
if (active)
|
|
608
|
+
await saveManagedHistory(active);
|
|
609
|
+
const state = await controls.select(selection);
|
|
610
|
+
const connection = await connectManaged(state);
|
|
562
611
|
controlsChanged.fire();
|
|
612
|
+
return connection;
|
|
563
613
|
}
|
|
564
614
|
const openNewChat = async () => {
|
|
565
615
|
if (!controls || !granted.has('chatSessionsProvider'))
|
|
@@ -578,6 +628,7 @@ export async function activate(context) {
|
|
|
578
628
|
const draft = { resource: vscode.Uri.from({ scheme: SESSION_SCHEME, path: `/untitled-${randomUUID()}` }) };
|
|
579
629
|
pendingDraft = draft;
|
|
580
630
|
focusedResource = draft.resource;
|
|
631
|
+
focusGeneration++;
|
|
581
632
|
focusedAttached = undefined;
|
|
582
633
|
controlsChanged.fire();
|
|
583
634
|
await vscode.commands.executeCommand(controls.state.openSessionCommand, draft.resource.toString());
|
|
@@ -672,9 +723,8 @@ export async function activate(context) {
|
|
|
672
723
|
const selection = choice?.kind === 'agent' ? choice.selection
|
|
673
724
|
: { harness: loginHarness ?? controls.state.selection.harness, model: '', effort: '' };
|
|
674
725
|
const pending = applying.then(() => applySelection(selection));
|
|
675
|
-
applying = pending.
|
|
676
|
-
await pending;
|
|
677
|
-
const connection = frontends.get(controls.state.activeSession);
|
|
726
|
+
applying = pending.then(() => undefined, () => undefined);
|
|
727
|
+
const connection = await pending;
|
|
678
728
|
savePermission(connection.id, turnPermission(inputState.groups.find(group => group.id === 'permissions')?.selected?.id));
|
|
679
729
|
if (pendingDraft === draft) {
|
|
680
730
|
pendingDraft = undefined;
|
|
@@ -682,8 +732,7 @@ export async function activate(context) {
|
|
|
682
732
|
}
|
|
683
733
|
return connection;
|
|
684
734
|
},
|
|
685
|
-
resolve: connectionFor,
|
|
686
|
-
focus: focusSession,
|
|
735
|
+
resolve: (resource) => connectionFor(resource, false),
|
|
687
736
|
attached: (connection) => attachedIds.has(connection.id),
|
|
688
737
|
catalogue: () => controls?.state.sessions ?? [],
|
|
689
738
|
bindings: boundId,
|
|
@@ -695,7 +744,12 @@ export async function activate(context) {
|
|
|
695
744
|
focusSession(resource ? vscode.Uri.parse(resource) : undefined);
|
|
696
745
|
}));
|
|
697
746
|
context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.openSession', async (id) => {
|
|
698
|
-
|
|
747
|
+
const resource = vscode.Uri.from({ scheme: SESSION_SCHEME, authority: id, path: '/conversation' });
|
|
748
|
+
focusSession(resource);
|
|
749
|
+
const generation = focusGeneration;
|
|
750
|
+
await connectionFor(resource);
|
|
751
|
+
if (generation !== focusGeneration)
|
|
752
|
+
return;
|
|
699
753
|
await revealManaged();
|
|
700
754
|
}));
|
|
701
755
|
context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.configure', async (supplied) => {
|
|
@@ -945,7 +999,28 @@ export async function activate(context) {
|
|
|
945
999
|
p.iconPath = icon();
|
|
946
1000
|
};
|
|
947
1001
|
context.subscriptions.push({ dispose: () => sessionParticipant.dispose() }, controlsChanged.event(updateIdentity), frontends.onDidChange(updateIdentity));
|
|
948
|
-
|
|
1002
|
+
// Replayed turns outlive the active composer participant. Keep their
|
|
1003
|
+
// author tied to the saved harness, including passive background loads.
|
|
1004
|
+
const historyParticipants = new Map();
|
|
1005
|
+
const historyParticipant = (connection) => {
|
|
1006
|
+
if (!granted.has('chatParticipantPrivate'))
|
|
1007
|
+
return sessionParticipant.id;
|
|
1008
|
+
const harness = connection.session.descriptor?.source_harness
|
|
1009
|
+
?? controls?.state.sessions.find(entry => entry.id === connection.id)?.selection.harness;
|
|
1010
|
+
if (!harness)
|
|
1011
|
+
return sessionParticipant.id;
|
|
1012
|
+
let author = historyParticipants.get(harness);
|
|
1013
|
+
if (!author) {
|
|
1014
|
+
const name = harnessDisplayName(harness);
|
|
1015
|
+
author = vscode.chat.createDynamicChatParticipant(`${SESSION_SCHEME}.history.${encodeURIComponent(harness)}`, { name: name.replace(/[^a-zA-Z0-9]/g, ''), fullName: name, publisherName: 'Volter' }, handler);
|
|
1016
|
+
const provider = runtimeIdentity(harness, '').provider;
|
|
1017
|
+
author.iconPath = provider ? vscode.Uri.joinPath(context.extensionUri, 'media', `${provider}.svg`) : new vscode.ThemeIcon('hubot');
|
|
1018
|
+
historyParticipants.set(harness, author);
|
|
1019
|
+
context.subscriptions.push(author);
|
|
1020
|
+
}
|
|
1021
|
+
return author.id;
|
|
1022
|
+
};
|
|
1023
|
+
registerSessions(context, frontends, sessionParticipant, granted, handler, historyParticipant, controls ? providerControls : undefined);
|
|
949
1024
|
}
|
|
950
1025
|
catch (error) {
|
|
951
1026
|
log.warn(`Volter Harness chat sessions were not registered: ${String(error?.message ?? error)}`);
|
|
@@ -1315,7 +1390,7 @@ function applyTiming(item, connection) {
|
|
|
1315
1390
|
lastRequestEnded: connection.session.lastTurnEndedMs ?? undefined,
|
|
1316
1391
|
};
|
|
1317
1392
|
}
|
|
1318
|
-
function registerSessions(context, frontends, participant, granted, handler, controls) {
|
|
1393
|
+
function registerSessions(context, frontends, participant, granted, handler, historyParticipant, controls) {
|
|
1319
1394
|
const turns = vscode;
|
|
1320
1395
|
const items = () => {
|
|
1321
1396
|
return (controls?.catalogue() ?? []).filter(entry => !frontends.get(entry.id)).map(entry => {
|
|
@@ -1369,12 +1444,15 @@ function registerSessions(context, frontends, participant, granted, handler, con
|
|
|
1369
1444
|
}));
|
|
1370
1445
|
}
|
|
1371
1446
|
context.subscriptions.push(vscode.chat.registerChatSessionContentProvider(SESSION_SCHEME, {
|
|
1372
|
-
provideChatSessionContent: async (resource,
|
|
1373
|
-
|
|
1447
|
+
provideChatSessionContent: async (resource, token, { inputState }) => {
|
|
1448
|
+
if (token.isCancellationRequested)
|
|
1449
|
+
throw new vscode.CancellationError();
|
|
1374
1450
|
const boundHandler = (request, chatContext, stream, token) => handler(request, { ...chatContext, chatSessionContext: { chatSessionItem: { resource, label: 'Conversation' } } }, stream, token);
|
|
1375
1451
|
// Only a native untitled resource can create a conversation. Existing
|
|
1376
1452
|
// resources must resolve their exact saved binding; there is no fallback.
|
|
1377
1453
|
const connection = controls ? (resource.path.startsWith('/untitled-') && !controls.bindings(resource) ? undefined : await controls.resolve(resource)) : frontends.get(resource.authority);
|
|
1454
|
+
if (token.isCancellationRequested)
|
|
1455
|
+
throw new vscode.CancellationError();
|
|
1378
1456
|
if (!connection) {
|
|
1379
1457
|
return { history: [], requestHandler: boundHandler };
|
|
1380
1458
|
}
|
|
@@ -1387,13 +1465,14 @@ function registerSessions(context, frontends, participant, granted, handler, con
|
|
|
1387
1465
|
// `state.history`), so the copy taken when this window attached is
|
|
1388
1466
|
// stale as soon as a turn runs. The generated client reuses the
|
|
1389
1467
|
// already-open event stream, so this is a snapshot, not a reconnect.
|
|
1390
|
-
const attached = await connection.session.attach().then(() => true).catch(() => {
|
|
1468
|
+
const attached = frontends.get(connection.id) === connection && await connection.session.attach().then(() => true).catch(() => {
|
|
1391
1469
|
// An unreachable runtime replays what this window last saw.
|
|
1392
1470
|
return false;
|
|
1393
1471
|
});
|
|
1394
1472
|
// The attach snapshot IS the session's history: the runtime's own
|
|
1395
1473
|
// canonical transcript, replayed as turns.
|
|
1396
1474
|
const history = [];
|
|
1475
|
+
const authorId = historyParticipant(connection);
|
|
1397
1476
|
// A hosted harness runtime has no canonical ChatMessage history —
|
|
1398
1477
|
// `HostedHarnessRuntime::attach` returns an empty one and puts the
|
|
1399
1478
|
// conversation in `replay`. Read whichever the runtime actually
|
|
@@ -1419,13 +1498,13 @@ function registerSessions(context, frontends, participant, granted, handler, con
|
|
|
1419
1498
|
canRespond: connection.session.descriptor?.actions.respond !== false,
|
|
1420
1499
|
});
|
|
1421
1500
|
if (connection.historyTruncated)
|
|
1422
|
-
history.push(new turns.ChatRequestTurn('[Earlier conversation history]', undefined, [],
|
|
1501
|
+
history.push(new turns.ChatRequestTurn('[Earlier conversation history]', undefined, [], authorId, []), new turns.ChatResponseTurn([new vscode.ChatResponseMarkdownPart('_Showing the latest 120 saved messages. Earlier context remains in the harness session._')], {}, authorId));
|
|
1423
1502
|
for (const turn of projected) {
|
|
1424
1503
|
if (turn.role === 'user') {
|
|
1425
|
-
history.push(new turns.ChatRequestTurn(turn.prompt ?? '', undefined, [],
|
|
1504
|
+
history.push(new turns.ChatRequestTurn(turn.prompt ?? '', undefined, [], authorId, []));
|
|
1426
1505
|
continue;
|
|
1427
1506
|
}
|
|
1428
|
-
history.push(new turns.ChatResponseTurn(historyParts(oneRowPerToolCall(turn.parts ?? [])), {},
|
|
1507
|
+
history.push(new turns.ChatResponseTurn(historyParts(oneRowPerToolCall(turn.parts ?? [])), {}, authorId));
|
|
1429
1508
|
}
|
|
1430
1509
|
const busy = connection.session.descriptor?.turn_state === 'busy';
|
|
1431
1510
|
return {
|
package/dist/host-controls.d.ts
CHANGED
|
@@ -55,6 +55,7 @@ export declare class ChatHostControls {
|
|
|
55
55
|
readonly baseUrl: string;
|
|
56
56
|
private readonly credential;
|
|
57
57
|
state: HostState;
|
|
58
|
+
private updating;
|
|
58
59
|
private constructor();
|
|
59
60
|
static fromEnvironment(env?: NodeJS.ProcessEnv): Promise<ChatHostControls | undefined>;
|
|
60
61
|
refresh(): Promise<void>;
|
|
@@ -67,6 +68,9 @@ export declare class ChatHostControls {
|
|
|
67
68
|
arguments: string[];
|
|
68
69
|
env?: Record<string, string>;
|
|
69
70
|
}>;
|
|
71
|
+
/** Publish complete host replies in request order; a late refresh must not
|
|
72
|
+
* overwrite the connection returned by a newer lifecycle operation. */
|
|
73
|
+
private update;
|
|
70
74
|
private request;
|
|
71
75
|
}
|
|
72
76
|
/** Approval policy is captured for one turn, never inferred from connection privileges. */
|
package/dist/host-controls.js
CHANGED
|
@@ -3,6 +3,7 @@ export class ChatHostControls {
|
|
|
3
3
|
baseUrl;
|
|
4
4
|
credential;
|
|
5
5
|
state;
|
|
6
|
+
updating = Promise.resolve();
|
|
6
7
|
constructor(baseUrl, credential, state) {
|
|
7
8
|
this.baseUrl = baseUrl;
|
|
8
9
|
this.credential = credential;
|
|
@@ -19,21 +20,28 @@ export class ChatHostControls {
|
|
|
19
20
|
host.state = await host.request('/state');
|
|
20
21
|
return host;
|
|
21
22
|
}
|
|
22
|
-
async refresh() {
|
|
23
|
+
async refresh() { await this.update('/state'); }
|
|
23
24
|
async select(selection) {
|
|
24
|
-
|
|
25
|
-
this.state = result;
|
|
26
|
-
return result;
|
|
25
|
+
return this.update('/select', selection);
|
|
27
26
|
}
|
|
28
|
-
async remember(id, nativeId) {
|
|
27
|
+
async remember(id, nativeId) { await this.update('/remember', { id, nativeId }); }
|
|
29
28
|
async open(id) {
|
|
30
|
-
|
|
31
|
-
this.state = result;
|
|
32
|
-
return result;
|
|
29
|
+
return this.update('/open', { id });
|
|
33
30
|
}
|
|
34
31
|
async setup(kind, harness) {
|
|
35
32
|
return this.request('/setup', { kind, harness });
|
|
36
33
|
}
|
|
34
|
+
/** Publish complete host replies in request order; a late refresh must not
|
|
35
|
+
* overwrite the connection returned by a newer lifecycle operation. */
|
|
36
|
+
update(path, selection) {
|
|
37
|
+
const pending = this.updating.then(async () => {
|
|
38
|
+
const result = await this.request(path, selection);
|
|
39
|
+
this.state = result;
|
|
40
|
+
return result;
|
|
41
|
+
});
|
|
42
|
+
this.updating = pending.then(() => undefined, () => undefined);
|
|
43
|
+
return pending;
|
|
44
|
+
}
|
|
37
45
|
async request(path, selection) {
|
|
38
46
|
const response = await fetch(this.baseUrl + path, {
|
|
39
47
|
method: selection ? 'POST' : 'GET', headers: { Authorization: `Bearer ${this.credential}`, 'Content-Type': 'application/json' },
|
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.31",
|
|
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",
|