@volter/supercode-frontend-vscode 0.1.18 → 0.1.21
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 +0 -59
- package/dist/chat-setup.d.ts +2 -4
- package/dist/chat-setup.js +1 -12
- package/dist/extension.js +159 -157
- package/dist/host-controls.d.ts +1 -9
- package/dist/session.d.ts +0 -2
- package/dist/session.js +8 -11
- package/dist/view.js +28 -3
- package/package.json +2 -5
package/NOTICE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Third-party notice
|
|
2
2
|
|
|
3
|
-
This optional frontend
|
|
3
|
+
This optional frontend needs **no runtime npm installation**. It builds against `@types/vscode`
|
|
4
4
|
(Copyright (c) Microsoft Corporation, MIT) as an exact devDependency for types only; `vscode` itself
|
|
5
5
|
is supplied by the editor at runtime and is never bundled or redistributed here.
|
|
6
6
|
|
package/README.md
CHANGED
|
@@ -34,65 +34,6 @@ Changed setup resources refresh
|
|
|
34
34
|
Code-OSS's cached status history after an install. Hosts without these optional
|
|
35
35
|
fields keep the existing runtime-attachment behavior.
|
|
36
36
|
|
|
37
|
-
`starterPrompts?: {label: string; prompt: string}[]` is project data supplied by
|
|
38
|
-
the host. An eligible new conversation exposes at most three short command links
|
|
39
|
-
in the default participant's native welcome content, outside the transcript.
|
|
40
|
-
The host's `fillInputCommand` receives the exact native session resource and full
|
|
41
|
-
prompt and must return `{sessionResource, input}` after filling that existing
|
|
42
|
-
widget, without submitting. A missing or mismatched receipt is an error.
|
|
43
|
-
The untargeted partial-query command is not used. Links never send or write a turn.
|
|
44
|
-
Presentation and clicks use the same `starterEligibility` calculation; runtime
|
|
45
|
-
and host state changes update the welcome message, and a user turn clears it.
|
|
46
|
-
Native Chat also hides its welcome surface immediately when a request is added.
|
|
47
|
-
Eligibility requires a successful attachment, an idle runtime, no pending request,
|
|
48
|
-
no saved/canonical non-system messages or truncated saved history, and no observed
|
|
49
|
-
user turn. Accepted-input events (`user_message`, `turn_started`) and busy snapshots
|
|
50
|
-
record turn evidence that survives re-attachment and replay eviction. Setup/status
|
|
51
|
-
events can advance cursors without disqualifying a new conversation; system-only
|
|
52
|
-
history also stays eligible. The checks run again after
|
|
53
|
-
project data loads and before filling; a failed attachment never offers starters.
|
|
54
|
-
`supercode.frontend.status(true)` includes each connection's `starterEligibility`:
|
|
55
|
-
attachment state, turn state, pending count, saved-history presence/truncation/count
|
|
56
|
-
and roles, canonical count/roles, sticky turn evidence, both cursors, replay count
|
|
57
|
-
and kinds, active host ID, host busy state, connection identity check, and rejection
|
|
58
|
-
reasons. `lastStarterAttempt` preserves the exact post-attachment guard snapshot
|
|
59
|
-
and native binding from the latest click. These contain no message bodies or tokens.
|
|
60
|
-
A rejected click names its failed conditions instead of a generic new/idle error.
|
|
61
|
-
The pinned Chat session API exposes immutable history, without withdrawal or
|
|
62
|
-
live enablement of a rendered command part. Starters therefore never enter history.
|
|
63
|
-
`defaultChatParticipant.additionalWelcomeMessage` supplies trusted Markdown with
|
|
64
|
-
only `supercode.frontend.fillStarter` allowed; labels are escaped and command
|
|
65
|
-
arguments are encoded. Hosts without that proposal get no starter offer rather
|
|
66
|
-
than permanent transcript buttons. Existing setup actions keep their native status
|
|
67
|
-
presentation. No new workbench API or editor-side contribution is required.
|
|
68
|
-
The pinned Code-OSS `f16dc165` has no `suggestedPrompts` field in either
|
|
69
|
-
`vscode.proposed.defaultChatParticipant.d.ts` or the internal
|
|
70
|
-
`IChatViewWelcomeContent`. `ChatViewWelcomePart` renders `additionalMessage` as
|
|
71
|
-
Markdown; its optional `firstLinkToButton` converts only one anchor and the
|
|
72
|
-
participant welcome never enables that option. The editor's native overlay can
|
|
73
|
-
render standalone trusted command links as welcome buttons without a new extension
|
|
74
|
-
API; other hosts keep Markdown links. The two introductory lines live together in
|
|
75
|
-
this extension's `package.json`: `chatSessions.welcomeMessage` and
|
|
76
|
-
`supercodeFrontendWelcome.starterIntroduction`. Hosts own their project prompt data.
|
|
77
|
-
If `starterPromptsPending` is true, the project configuration is still loading.
|
|
78
|
-
Empty-conversation resolution refreshes the host until that declaration arrives
|
|
79
|
-
(cancellable, with a 30-second timeout), so native Chat does not cache an empty
|
|
80
|
-
offer before the project adapter loads.
|
|
81
|
-
|
|
82
|
-
W32/W37 passed starter filling and W37 passed healthy refresh with draft retention.
|
|
83
|
-
W44 passed a real boost turn and exposed a presentation defect: transcript starter
|
|
84
|
-
buttons remained after the turn even though the guard rejected them. W50 passed
|
|
85
|
-
the native welcome fix at `29d8fac99`: all three pointer fills, one real HUD turn,
|
|
86
|
-
first-turn disappearance, return-to-idle suppression, and no starters when reopening
|
|
87
|
-
the earlier conversation with seven history entries. The shorter introductory copy
|
|
88
|
-
is source-only; it needs a frontend build plus a workbench overlay copy.
|
|
89
|
-
The accompanying native button and New Session repairs require a Code-OSS compile.
|
|
90
|
-
W50 also found that the generic native New Session action
|
|
91
|
-
bypasses this provider's creation command; its owning host must route that action
|
|
92
|
-
through `chatSessions/newSession` rather than opening an unbound untitled resource.
|
|
93
|
-
Signed-out/install transitions and failed-discovery/attachment recovery remain
|
|
94
|
-
unverified live. A published frontend and rebuilt workbench are needed for npx users.
|
|
95
|
-
|
|
96
37
|
A bundling host can expose a private lifecycle endpoint through
|
|
97
38
|
`SUPERCODE_FRONTEND_HOST_URL` and `SUPERCODE_FRONTEND_HOST_CREDENTIAL_FILE`.
|
|
98
39
|
The endpoint must be loopback HTTP and the credential follows the runtime credential-file
|
package/dist/chat-setup.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
/** Native Chat presentation; the host supplies inventory
|
|
1
|
+
/** Native Chat presentation; the host supplies inventory and terminal actions. */
|
|
2
2
|
import * as vscode from 'vscode';
|
|
3
|
-
import type { ChatSetupState
|
|
3
|
+
import type { ChatSetupState } from './host-controls.js';
|
|
4
4
|
export declare function setupParts(setup: ChatSetupState): Array<vscode.ChatResponseMarkdownPart | vscode.ChatResponseCommandButtonPart>;
|
|
5
|
-
/** Welcome content is owned by the empty-state renderer, never transcript history. */
|
|
6
|
-
export declare function starterWelcome(prompts: readonly ChatStarterPrompt[], session: string, resource: string, introduction: string): vscode.MarkdownString;
|
package/dist/chat-setup.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Native Chat presentation; the host supplies inventory
|
|
1
|
+
/** Native Chat presentation; the host supplies inventory and terminal actions. */
|
|
2
2
|
import * as vscode from 'vscode';
|
|
3
3
|
export function setupParts(setup) {
|
|
4
4
|
const parts = [
|
|
@@ -16,14 +16,3 @@ export function setupParts(setup) {
|
|
|
16
16
|
parts.push(new vscode.ChatResponseCommandButtonPart({ title: 'Check again', command: 'supercode.frontend.refreshSetup' }));
|
|
17
17
|
return parts;
|
|
18
18
|
}
|
|
19
|
-
/** Welcome content is owned by the empty-state renderer, never transcript history. */
|
|
20
|
-
export function starterWelcome(prompts, session, resource, introduction) {
|
|
21
|
-
const message = new vscode.MarkdownString().appendText(introduction).appendMarkdown('\n\n');
|
|
22
|
-
message.isTrusted = { enabledCommands: ['supercode.frontend.fillStarter'] };
|
|
23
|
-
for (const [index, starter] of prompts.slice(0, 3).entries()) {
|
|
24
|
-
const args = encodeURIComponent(JSON.stringify([session, index, resource]));
|
|
25
|
-
message.appendMarkdown('[').appendText(starter.label)
|
|
26
|
-
.appendMarkdown(`](command:supercode.frontend.fillStarter?${args})\n\n`);
|
|
27
|
-
}
|
|
28
|
-
return message;
|
|
29
|
-
}
|
package/dist/extension.js
CHANGED
|
@@ -2,7 +2,7 @@ import { resolveSessionBinding } from './bindings.js';
|
|
|
2
2
|
import { withTextContext } from './context.js';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { ChatHostControls, runtimeIdentity, harnessDisplayName, turnPermission } from './host-controls.js';
|
|
5
|
-
import { setupParts
|
|
5
|
+
import { setupParts } from './chat-setup.js';
|
|
6
6
|
import * as vscode from 'vscode';
|
|
7
7
|
import { createFrontendClient } from './client.js';
|
|
8
8
|
import { readCredentialFile } from './credential.js';
|
|
@@ -33,44 +33,6 @@ const ENV = Object.freeze({
|
|
|
33
33
|
*/
|
|
34
34
|
unavailable: 'SUPERCODE_FRONTEND_UNAVAILABLE',
|
|
35
35
|
});
|
|
36
|
-
/** Transport progress includes setup events; it is not a user-turn count. */
|
|
37
|
-
function conversationEligibility(connection) {
|
|
38
|
-
const session = connection.session;
|
|
39
|
-
const inputs = {
|
|
40
|
-
attachmentState: session.connection,
|
|
41
|
-
turnState: session.descriptor?.turn_state ?? null,
|
|
42
|
-
pendingRequestCount: session.pendingRequests.size,
|
|
43
|
-
historyTruncated: connection.historyTruncated ?? false,
|
|
44
|
-
persistedHistoryLoaded: connection.persistedHistory !== undefined,
|
|
45
|
-
persistedHistoryCount: connection.persistedHistory?.length ?? 0,
|
|
46
|
-
persistedHistoryRoles: connection.persistedHistory?.map(message => message.role) ?? [],
|
|
47
|
-
canonicalHistoryCount: session.history.length,
|
|
48
|
-
canonicalHistoryRoles: session.history.map(message => message.role),
|
|
49
|
-
hasUserTurn: session.hasUserTurn,
|
|
50
|
-
historyCursor: session.historyCursor,
|
|
51
|
-
eventCursor: session.cursor,
|
|
52
|
-
replayLength: session.replay.length,
|
|
53
|
-
replayKinds: [...new Set(session.replay.map(event => event.kind))],
|
|
54
|
-
nativeEventKinds: [...new Set(session.replay.filter(event => event.kind === 'native_event')
|
|
55
|
-
.map(event => event.payload?.kind).filter(kind => typeof kind === 'string'))],
|
|
56
|
-
};
|
|
57
|
-
const reasons = [];
|
|
58
|
-
if (inputs.attachmentState !== 'connected')
|
|
59
|
-
reasons.push(`attachmentState=${inputs.attachmentState}`);
|
|
60
|
-
if (inputs.turnState !== 'idle')
|
|
61
|
-
reasons.push(`turnState=${inputs.turnState}`);
|
|
62
|
-
if (inputs.pendingRequestCount)
|
|
63
|
-
reasons.push(`pendingRequestCount=${inputs.pendingRequestCount}`);
|
|
64
|
-
if (inputs.historyTruncated)
|
|
65
|
-
reasons.push('saved history is truncated');
|
|
66
|
-
if (inputs.persistedHistoryRoles.some(role => role !== 'system'))
|
|
67
|
-
reasons.push('saved history contains a turn');
|
|
68
|
-
if (inputs.canonicalHistoryRoles.some(role => role !== 'system'))
|
|
69
|
-
reasons.push('canonical history contains a turn');
|
|
70
|
-
if (inputs.hasUserTurn)
|
|
71
|
-
reasons.push('a user turn has been observed');
|
|
72
|
-
return { ...inputs, eligible: reasons.length === 0, reasons };
|
|
73
|
-
}
|
|
74
36
|
function normalizePermissions(value, fallback) {
|
|
75
37
|
const raw = Array.isArray(value)
|
|
76
38
|
? value.map(String)
|
|
@@ -363,24 +325,6 @@ export async function activate(context) {
|
|
|
363
325
|
const boundId = (resource) => resolveSessionBinding(resource, controls?.state.sessions ?? [], resourceBindings);
|
|
364
326
|
let applying = Promise.resolve();
|
|
365
327
|
const frontends = new SupercodeFrontends();
|
|
366
|
-
const starterEligibility = (connection) => {
|
|
367
|
-
const conversation = conversationEligibility(connection);
|
|
368
|
-
const activeSession = controls?.state.activeSession ?? null;
|
|
369
|
-
const hostBusy = Boolean(controls?.state.busy);
|
|
370
|
-
const connectionMatches = frontends.get(connection.id) === connection;
|
|
371
|
-
const reasons = [...conversation.reasons];
|
|
372
|
-
if (!controls)
|
|
373
|
-
reasons.push('host controls unavailable');
|
|
374
|
-
if (activeSession !== connection.id)
|
|
375
|
-
reasons.push(`activeSession=${activeSession}; expected ${connection.id}`);
|
|
376
|
-
if (hostBusy)
|
|
377
|
-
reasons.push('hostBusy=true');
|
|
378
|
-
if (!connectionMatches)
|
|
379
|
-
reasons.push('connection was replaced');
|
|
380
|
-
return { ...conversation, controlsAvailable: Boolean(controls), connectionId: connection.id, activeSession, hostBusy, connectionMatches,
|
|
381
|
-
eligible: reasons.length === 0, reasons };
|
|
382
|
-
};
|
|
383
|
-
const lastStarterAttempts = new Map();
|
|
384
328
|
// Runtimes attached by address ("Set Runtime Token", "Attach to Runtime")
|
|
385
329
|
// beside a host's managed conversation. The host's catalogue does not know
|
|
386
330
|
// them, so they open as what they are: attachments, shown as their runtime
|
|
@@ -389,28 +333,6 @@ export async function activate(context) {
|
|
|
389
333
|
const managed = () => frontends.all.find((connection) => !attachedIds.has(connection.id));
|
|
390
334
|
// The attached runtime the person is looking at: the Chat names its harness, not the host's.
|
|
391
335
|
let focusedAttached;
|
|
392
|
-
const welcomeParticipants = new Set();
|
|
393
|
-
const welcomeValues = new WeakMap();
|
|
394
|
-
const starterResources = new Map();
|
|
395
|
-
const updateStarterWelcome = () => {
|
|
396
|
-
if (!granted.has('defaultChatParticipant'))
|
|
397
|
-
return;
|
|
398
|
-
const connection = managed();
|
|
399
|
-
const prompts = controls?.state.starterPrompts ?? [];
|
|
400
|
-
// This is the click guard's exact state, including host activity and connection
|
|
401
|
-
// identity. A later idle state must not revive an offer after a user turn.
|
|
402
|
-
const message = connection && !focusedAttached && !controls?.state.starterPromptsPending
|
|
403
|
-
&& prompts.length && starterEligibility(connection).eligible
|
|
404
|
-
? starterWelcome(prompts, connection.id, starterResources.get(connection.id) ?? sessionResource(connection).toString(), context.extension.packageJSON.supercodeFrontendWelcome.starterIntroduction)
|
|
405
|
-
: new vscode.MarkdownString('');
|
|
406
|
-
for (const participant of welcomeParticipants) {
|
|
407
|
-
if (welcomeValues.get(participant) === message.value)
|
|
408
|
-
continue;
|
|
409
|
-
participant.additionalWelcomeMessage = message;
|
|
410
|
-
welcomeValues.set(participant, message.value);
|
|
411
|
-
}
|
|
412
|
-
};
|
|
413
|
-
context.subscriptions.push(frontends.onDidChange(updateStarterWelcome), controlsChanged.event(updateStarterWelcome));
|
|
414
336
|
let approvalAvailable = false;
|
|
415
337
|
context.subscriptions.push(frontends.onDidChange(() => {
|
|
416
338
|
const active = managed();
|
|
@@ -573,36 +495,6 @@ export async function activate(context) {
|
|
|
573
495
|
setupTerminals.set(key, terminal);
|
|
574
496
|
terminal.show();
|
|
575
497
|
}));
|
|
576
|
-
context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.fillStarter', async (session, index, nativeResource) => {
|
|
577
|
-
if (!controls || !Number.isInteger(index) || index < 0 || index > 2)
|
|
578
|
-
throw new Error('Invalid starter selection.');
|
|
579
|
-
const connection = frontends.get(session);
|
|
580
|
-
if (!connection)
|
|
581
|
-
throw new Error(`Starter prompt unavailable: connection ${session} is absent.`);
|
|
582
|
-
const resource = nativeResource ? vscode.Uri.parse(nativeResource)
|
|
583
|
-
: vscode.Uri.from({ scheme: SESSION_SCHEME, authority: session, path: '/conversation' });
|
|
584
|
-
if (boundId(resource) !== session)
|
|
585
|
-
throw new Error('The starter has no matching conversation binding.');
|
|
586
|
-
await connection.session.attach();
|
|
587
|
-
const guard = starterEligibility(connection);
|
|
588
|
-
lastStarterAttempts.set(session, { at: new Date().toISOString(), resource: resource.toString(),
|
|
589
|
-
boundSession: boundId(resource), guard });
|
|
590
|
-
while (lastStarterAttempts.size > 50)
|
|
591
|
-
lastStarterAttempts.delete(lastStarterAttempts.keys().next().value);
|
|
592
|
-
if (!guard.eligible)
|
|
593
|
-
throw new Error(`Starter prompt unavailable: ${guard.reasons.join('; ')}.`);
|
|
594
|
-
const prompt = controls.state.starterPrompts?.[index]?.prompt;
|
|
595
|
-
if (!prompt)
|
|
596
|
-
throw new Error('This starter is no longer available.');
|
|
597
|
-
if (!controls.state.fillInputCommand)
|
|
598
|
-
throw new Error('This host does not support filling a conversation draft.');
|
|
599
|
-
// The host addresses the existing native widget, never the default participant or last focus.
|
|
600
|
-
const receipt = await vscode.commands.executeCommand(controls.state.fillInputCommand, resource.toString(), prompt);
|
|
601
|
-
if (!receipt || receipt.sessionResource !== resource.toString() || receipt.input !== prompt) {
|
|
602
|
-
throw new Error('Chat did not confirm the requested draft.');
|
|
603
|
-
}
|
|
604
|
-
return receipt;
|
|
605
|
-
}));
|
|
606
498
|
const connectionFor = async (resource) => {
|
|
607
499
|
if (!controls) {
|
|
608
500
|
const connection = frontends.get(resource.authority);
|
|
@@ -705,24 +597,6 @@ export async function activate(context) {
|
|
|
705
597
|
catalogue: () => controls?.state.sessions ?? [],
|
|
706
598
|
bindings: boundId,
|
|
707
599
|
setup: () => controls?.state.setup,
|
|
708
|
-
prepareStarters: async (session, resource, token) => {
|
|
709
|
-
// Project configuration can arrive after the runtime connects. Wait for
|
|
710
|
-
// its real answer before publishing the conversation's welcome actions.
|
|
711
|
-
const deadline = Date.now() + 30_000;
|
|
712
|
-
await controls?.refresh();
|
|
713
|
-
while (controls?.state.starterPromptsPending) {
|
|
714
|
-
if (token.isCancellationRequested)
|
|
715
|
-
throw new vscode.CancellationError();
|
|
716
|
-
if (Date.now() >= deadline)
|
|
717
|
-
throw new Error('Project configuration is still loading. Reopen Chat once the project is ready.');
|
|
718
|
-
await new Promise(resolve => setTimeout(resolve, 250));
|
|
719
|
-
await controls.refresh();
|
|
720
|
-
}
|
|
721
|
-
if (token.isCancellationRequested)
|
|
722
|
-
throw new vscode.CancellationError();
|
|
723
|
-
starterResources.set(session, resource);
|
|
724
|
-
controlsChanged.fire();
|
|
725
|
-
},
|
|
726
600
|
};
|
|
727
601
|
context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.activateSession', async (resource) => { await connectionFor(vscode.Uri.parse(resource)); }));
|
|
728
602
|
context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.openSession', async (id) => {
|
|
@@ -748,16 +622,25 @@ export async function activate(context) {
|
|
|
748
622
|
return;
|
|
749
623
|
}
|
|
750
624
|
await controls.refresh();
|
|
751
|
-
//
|
|
752
|
-
|
|
753
|
-
const choices =
|
|
625
|
+
// Sign-in choices stay visible alongside agents that can be explicitly started.
|
|
626
|
+
const login = controls.state.setup?.actions.filter(action => action.kind === 'login') ?? [];
|
|
627
|
+
const choices = [
|
|
628
|
+
...login.map(action => ({ label: action.name, id: action.harness, description: 'Sign in…', login: true, autoStart: false })),
|
|
629
|
+
...controls.state.harnesses.filter(h => !login.some(action => action.harness === h.id))
|
|
630
|
+
.map(h => ({ label: h.name, id: h.id, description: h.description, login: false, autoStart: h.autoStart === true })),
|
|
631
|
+
];
|
|
754
632
|
if (!choices.length) {
|
|
755
633
|
await revealSetup();
|
|
756
634
|
return;
|
|
757
635
|
}
|
|
758
|
-
|
|
636
|
+
// A sole sign-in option still requires a deliberate click before opening its terminal.
|
|
637
|
+
const harness = choices.length === 1 && choices[0].autoStart ? choices[0] : await vscode.window.showQuickPick(choices, { title: 'New chat · Agent', placeHolder: 'Choose your coding agent. Model and reasoning use its defaults.' });
|
|
759
638
|
if (!harness)
|
|
760
639
|
return;
|
|
640
|
+
if (harness.login) {
|
|
641
|
+
await vscode.commands.executeCommand('supercode.frontend.setup', 'login', harness.id);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
761
644
|
await enqueueSelection({ harness: harness.id, model: '', effort: '' });
|
|
762
645
|
}));
|
|
763
646
|
context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.configureModel', async (resource, sessionId) => {
|
|
@@ -874,9 +757,14 @@ export async function activate(context) {
|
|
|
874
757
|
// and an elicitation's free-text content).
|
|
875
758
|
const pending = pendingRequestOf(connection);
|
|
876
759
|
if (pending) {
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
760
|
+
try {
|
|
761
|
+
const answer = typedAnswer(pending, request.prompt);
|
|
762
|
+
await connection.session.respond(answer);
|
|
763
|
+
stream.markdown(`_answered request #${pending.id}: ${opaqueText(answer, 256)}_`);
|
|
764
|
+
}
|
|
765
|
+
catch (error) {
|
|
766
|
+
stream.markdown(`Could not answer request #${pending.id}: ${opaqueText(String(error.message), 512)}`);
|
|
767
|
+
}
|
|
880
768
|
return {};
|
|
881
769
|
}
|
|
882
770
|
if (request.prompt.trim().length === 0) {
|
|
@@ -904,8 +792,6 @@ export async function activate(context) {
|
|
|
904
792
|
const participant = vscode.chat.createChatParticipant(id, handler);
|
|
905
793
|
participant.iconPath = icon();
|
|
906
794
|
participants.push(participant);
|
|
907
|
-
welcomeParticipants.add(participant);
|
|
908
|
-
updateStarterWelcome();
|
|
909
795
|
context.subscriptions.push(participant);
|
|
910
796
|
}
|
|
911
797
|
catch (error) {
|
|
@@ -928,8 +814,6 @@ export async function activate(context) {
|
|
|
928
814
|
? vscode.chat.createDynamicChatParticipant(SESSION_SCHEME, { name: current.name.replace(/[^a-zA-Z0-9]/g, ''), fullName: current.name, publisherName: 'Volter', description: 'Ask a question or describe a task' }, handler)
|
|
929
815
|
: vscode.chat.createChatParticipant(SESSION_SCHEME, handler);
|
|
930
816
|
participant.iconPath = icon();
|
|
931
|
-
welcomeParticipants.add(participant);
|
|
932
|
-
updateStarterWelcome();
|
|
933
817
|
return participant;
|
|
934
818
|
};
|
|
935
819
|
let sessionParticipant = makeParticipant();
|
|
@@ -938,7 +822,6 @@ export async function activate(context) {
|
|
|
938
822
|
if (participantKey() === previousIdentity)
|
|
939
823
|
return;
|
|
940
824
|
previousIdentity = participantKey();
|
|
941
|
-
welcomeParticipants.delete(sessionParticipant);
|
|
942
825
|
sessionParticipant.dispose();
|
|
943
826
|
sessionParticipant = makeParticipant();
|
|
944
827
|
for (const p of participants)
|
|
@@ -971,17 +854,13 @@ export async function activate(context) {
|
|
|
971
854
|
return;
|
|
972
855
|
}
|
|
973
856
|
let response = answer.response;
|
|
974
|
-
if (answer.promptForContent) {
|
|
975
|
-
const content = await vscode.window.showInputBox({
|
|
976
|
-
title: 'Volter Harness runtime request',
|
|
977
|
-
prompt: 'The runtime asked for a value; a button cannot carry one.',
|
|
978
|
-
ignoreFocusOut: true,
|
|
979
|
-
});
|
|
980
|
-
if (content === undefined)
|
|
981
|
-
return;
|
|
982
|
-
response = { ...response, content };
|
|
983
|
-
}
|
|
984
857
|
try {
|
|
858
|
+
if (answer.promptForContent) {
|
|
859
|
+
const content = await requestContent(request);
|
|
860
|
+
if (content === undefined)
|
|
861
|
+
return;
|
|
862
|
+
response = { ...response, content };
|
|
863
|
+
}
|
|
985
864
|
await connection.session.respond(response);
|
|
986
865
|
void vscode.window.setStatusBarMessage(`Volter Harness: ${resolutionSentence(response, request, answer.requestId)}`, 4000);
|
|
987
866
|
}
|
|
@@ -1042,8 +921,7 @@ export async function activate(context) {
|
|
|
1042
921
|
if (inspect === true)
|
|
1043
922
|
return {
|
|
1044
923
|
connections: frontends.all.map(c => ({ ...frontends.info(c), permissions: [...c.session.permissions],
|
|
1045
|
-
pendingRequests: [...c.session.pendingRequests.values()]
|
|
1046
|
-
lastStarterAttempt: lastStarterAttempts.get(c.id) ?? null })),
|
|
924
|
+
pendingRequests: [...c.session.pendingRequests.values()] })),
|
|
1047
925
|
selection: controls?.state.selection, activeSession: controls?.state.activeSession,
|
|
1048
926
|
busy: Boolean(controls?.state.busy || frontends.all.some(c => c.session.descriptor?.turn_state === 'busy' || c.session.pendingRequests.size)),
|
|
1049
927
|
setupHandoff: { hostReady: controls?.state.setup?.ready ?? null, complete: setupHandoffComplete(),
|
|
@@ -1463,12 +1341,6 @@ function registerSessions(context, frontends, participant, granted, handler, con
|
|
|
1463
1341
|
}
|
|
1464
1342
|
history.push(new turns.ChatResponseTurn(historyParts(oneRowPerToolCall(turn.parts ?? [])), {}, participant.id));
|
|
1465
1343
|
}
|
|
1466
|
-
// The public API cannot withdraw a rendered history command part. Keep
|
|
1467
|
-
// starters in the default participant's native welcome content instead:
|
|
1468
|
-
// Chat hides that whole surface as soon as the first request is added.
|
|
1469
|
-
if (attached && history.length === 0 && !controls?.attached(connection) && controls) {
|
|
1470
|
-
await controls.prepareStarters(connection.id, resource.toString(), _token);
|
|
1471
|
-
}
|
|
1472
1344
|
const busy = connection.session.descriptor?.turn_state === 'busy';
|
|
1473
1345
|
return {
|
|
1474
1346
|
title: connection.label,
|
|
@@ -1495,3 +1367,133 @@ function registerSessions(context, frontends, participant, granted, handler, con
|
|
|
1495
1367
|
},
|
|
1496
1368
|
}, participant, { supportsInterruptions: true }));
|
|
1497
1369
|
}
|
|
1370
|
+
/** Collect each question through the existing Chat answer button, preserving ids and secrets. */
|
|
1371
|
+
async function requestContent(request) {
|
|
1372
|
+
const payload = request.payload;
|
|
1373
|
+
if (Array.isArray(payload?.questions)) {
|
|
1374
|
+
const answers = Object.create(null);
|
|
1375
|
+
for (const value of payload.questions) {
|
|
1376
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || typeof value.id !== 'string')
|
|
1377
|
+
continue;
|
|
1378
|
+
const question = value;
|
|
1379
|
+
const options = Array.isArray(question.options) ? question.options.flatMap(option => {
|
|
1380
|
+
if (!option || typeof option !== 'object' || Array.isArray(option) || typeof option.label !== 'string')
|
|
1381
|
+
return [];
|
|
1382
|
+
return [{ label: option.label, description: typeof option.description === 'string' ? option.description : undefined, custom: false }];
|
|
1383
|
+
}) : [];
|
|
1384
|
+
let answer;
|
|
1385
|
+
if (options.length) {
|
|
1386
|
+
if (question.isOther === true)
|
|
1387
|
+
options.push({ label: 'Write an answer…', description: undefined, custom: true });
|
|
1388
|
+
const chosen = await vscode.window.showQuickPick(options, { title: String(question.header ?? 'Codex question'), placeHolder: String(question.question ?? ''), ignoreFocusOut: true });
|
|
1389
|
+
if (!chosen)
|
|
1390
|
+
return undefined;
|
|
1391
|
+
if (!chosen.custom)
|
|
1392
|
+
answer = chosen.label;
|
|
1393
|
+
}
|
|
1394
|
+
if (answer === undefined) {
|
|
1395
|
+
answer = await vscode.window.showInputBox({ title: String(question.header ?? 'Codex question'), prompt: String(question.question ?? ''), password: question.isSecret === true, ignoreFocusOut: true });
|
|
1396
|
+
}
|
|
1397
|
+
if (answer === undefined)
|
|
1398
|
+
return undefined;
|
|
1399
|
+
answers[value.id] = answer;
|
|
1400
|
+
}
|
|
1401
|
+
return answers;
|
|
1402
|
+
}
|
|
1403
|
+
const schema = payload?.requested_schema;
|
|
1404
|
+
if (schema === undefined || schema === null) {
|
|
1405
|
+
return vscode.window.showInputBox({ title: 'Runtime request', prompt: 'Enter your answer.', ignoreFocusOut: true });
|
|
1406
|
+
}
|
|
1407
|
+
const showSchema = async () => {
|
|
1408
|
+
const document = await vscode.workspace.openTextDocument({ language: 'json', content: JSON.stringify(schema, null, 2) });
|
|
1409
|
+
await vscode.window.showTextDocument(document, { preview: true, preserveFocus: true });
|
|
1410
|
+
};
|
|
1411
|
+
const structuredInput = async () => {
|
|
1412
|
+
await showSchema();
|
|
1413
|
+
const typed = await vscode.window.showInputBox({ title: 'Runtime request', prompt: 'Enter your answer as JSON.', ignoreFocusOut: true,
|
|
1414
|
+
validateInput: value => { try {
|
|
1415
|
+
JSON.parse(value);
|
|
1416
|
+
return undefined;
|
|
1417
|
+
}
|
|
1418
|
+
catch {
|
|
1419
|
+
return 'Enter valid JSON';
|
|
1420
|
+
} } });
|
|
1421
|
+
return typed === undefined ? undefined : JSON.parse(typed);
|
|
1422
|
+
};
|
|
1423
|
+
const form = objectValue(schema);
|
|
1424
|
+
const properties = objectValue(form?.properties);
|
|
1425
|
+
if (!properties)
|
|
1426
|
+
return structuredInput();
|
|
1427
|
+
const content = Object.create(null);
|
|
1428
|
+
const required = Array.isArray(form?.required) ? form.required : [];
|
|
1429
|
+
for (const [name, spec] of Object.entries(properties)) {
|
|
1430
|
+
const field = objectValue(spec) ?? {};
|
|
1431
|
+
const title = typeof field.title === 'string' ? field.title : name;
|
|
1432
|
+
const description = typeof field.description === 'string' ? field.description : undefined;
|
|
1433
|
+
if (!required.includes(name)) {
|
|
1434
|
+
const choice = await vscode.window.showQuickPick(['Provide a value', 'Omit this field'], { title: `${title} (optional)`, ignoreFocusOut: true });
|
|
1435
|
+
if (!choice)
|
|
1436
|
+
return undefined;
|
|
1437
|
+
if (choice === 'Omit this field')
|
|
1438
|
+
continue;
|
|
1439
|
+
}
|
|
1440
|
+
const multiple = field.type === 'array';
|
|
1441
|
+
const options = fieldChoices(multiple ? objectValue(field.items) ?? {} : field);
|
|
1442
|
+
if (options.length) {
|
|
1443
|
+
if (multiple) {
|
|
1444
|
+
const selected = await vscode.window.showQuickPick(options, { title, placeHolder: description, canPickMany: true, ignoreFocusOut: true });
|
|
1445
|
+
if (!selected)
|
|
1446
|
+
return undefined;
|
|
1447
|
+
content[name] = selected.map(choice => choice.value);
|
|
1448
|
+
}
|
|
1449
|
+
else {
|
|
1450
|
+
const selected = await vscode.window.showQuickPick(options, { title, placeHolder: description, ignoreFocusOut: true });
|
|
1451
|
+
if (!selected)
|
|
1452
|
+
return undefined;
|
|
1453
|
+
content[name] = selected.value;
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
else {
|
|
1457
|
+
const parse = (value) => {
|
|
1458
|
+
if (field.type === 'number' || field.type === 'integer') {
|
|
1459
|
+
if (!value.trim() || !Number.isFinite(Number(value)))
|
|
1460
|
+
throw new Error('Enter a number');
|
|
1461
|
+
return Number(value);
|
|
1462
|
+
}
|
|
1463
|
+
if (field.type === 'array' || field.type === 'object')
|
|
1464
|
+
return JSON.parse(value);
|
|
1465
|
+
return value;
|
|
1466
|
+
};
|
|
1467
|
+
const typed = await vscode.window.showInputBox({ title, prompt: description, ignoreFocusOut: true,
|
|
1468
|
+
validateInput: value => { try {
|
|
1469
|
+
parse(value);
|
|
1470
|
+
return undefined;
|
|
1471
|
+
}
|
|
1472
|
+
catch {
|
|
1473
|
+
return field.type === 'number' || field.type === 'integer' ? 'Enter a number' : 'Enter valid JSON';
|
|
1474
|
+
} } });
|
|
1475
|
+
if (typed === undefined)
|
|
1476
|
+
return undefined;
|
|
1477
|
+
content[name] = parse(typed);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
return content;
|
|
1481
|
+
}
|
|
1482
|
+
function objectValue(value) {
|
|
1483
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
1484
|
+
}
|
|
1485
|
+
function fieldChoices(field) {
|
|
1486
|
+
if (Array.isArray(field.enum))
|
|
1487
|
+
return field.enum.map((value, index) => ({ value,
|
|
1488
|
+
label: Array.isArray(field.enumNames) && typeof field.enumNames[index] === 'string' ? field.enumNames[index] : String(value) }));
|
|
1489
|
+
const variants = Array.isArray(field.oneOf) ? field.oneOf : field.anyOf;
|
|
1490
|
+
if (Array.isArray(variants))
|
|
1491
|
+
return variants.flatMap(value => {
|
|
1492
|
+
const option = objectValue(value);
|
|
1493
|
+
return option && Object.hasOwn(option, 'const') ? [{ value: option.const,
|
|
1494
|
+
label: typeof option.title === 'string' ? option.title : String(option.const) }] : [];
|
|
1495
|
+
});
|
|
1496
|
+
if (field.type === 'boolean')
|
|
1497
|
+
return [{ label: 'True', value: true }, { label: 'False', value: false }];
|
|
1498
|
+
return [];
|
|
1499
|
+
}
|
package/dist/host-controls.d.ts
CHANGED
|
@@ -9,10 +9,7 @@ export interface ChatChoice {
|
|
|
9
9
|
id: string;
|
|
10
10
|
name: string;
|
|
11
11
|
description?: string;
|
|
12
|
-
|
|
13
|
-
export interface ChatStarterPrompt {
|
|
14
|
-
label: string;
|
|
15
|
-
prompt: string;
|
|
12
|
+
autoStart?: boolean;
|
|
16
13
|
}
|
|
17
14
|
export interface ManagedChatSession {
|
|
18
15
|
id: string;
|
|
@@ -39,8 +36,6 @@ export interface HostState {
|
|
|
39
36
|
historyTruncated?: boolean;
|
|
40
37
|
activeSession: string | null;
|
|
41
38
|
openSessionCommand?: string;
|
|
42
|
-
/** Fill the composer of an exact native session; returns {sessionResource, input}. */
|
|
43
|
-
fillInputCommand?: string;
|
|
44
39
|
/** The native renderer supports a leading response with no visible user request. */
|
|
45
40
|
responseOnlyHistory?: boolean;
|
|
46
41
|
sessions: ManagedChatSession[];
|
|
@@ -54,9 +49,6 @@ export interface HostState {
|
|
|
54
49
|
actualModel?: string | null;
|
|
55
50
|
configurable: boolean;
|
|
56
51
|
setup?: ChatSetupState;
|
|
57
|
-
starterPrompts?: ChatStarterPrompt[];
|
|
58
|
-
/** The host's project configuration has not finished loading yet. */
|
|
59
|
-
starterPromptsPending?: boolean;
|
|
60
52
|
}
|
|
61
53
|
export interface HostSelectionResult extends HostState {
|
|
62
54
|
connection: Record<string, string>;
|
package/dist/session.d.ts
CHANGED
|
@@ -77,8 +77,6 @@ export declare class VscodeFrontendSession {
|
|
|
77
77
|
history: ChatMessage[];
|
|
78
78
|
historyCursor: number;
|
|
79
79
|
cursor: number;
|
|
80
|
-
/** Semantic evidence, retained even when replay is evicted or re-attached. */
|
|
81
|
-
hasUserTurn: boolean;
|
|
82
80
|
/**
|
|
83
81
|
* Every event this attachment has seen, in order — NOT just the last
|
|
84
82
|
* snapshot's. A re-attach asks `afterSequence: cursor`, so its `replay` is
|
package/dist/session.js
CHANGED
|
@@ -44,8 +44,6 @@ export class VscodeFrontendSession {
|
|
|
44
44
|
history = [];
|
|
45
45
|
historyCursor = 0;
|
|
46
46
|
cursor = 0;
|
|
47
|
-
/** Semantic evidence, retained even when replay is evicted or re-attached. */
|
|
48
|
-
hasUserTurn = false;
|
|
49
47
|
/**
|
|
50
48
|
* Every event this attachment has seen, in order — NOT just the last
|
|
51
49
|
* snapshot's. A re-attach asks `afterSequence: cursor`, so its `replay` is
|
|
@@ -105,8 +103,6 @@ export class VscodeFrontendSession {
|
|
|
105
103
|
descriptor: structuredClone(snapshot.descriptor),
|
|
106
104
|
pendingRequests: new Map([...this.pendingRequests].map(([id, request]) => [id, structuredClone(request)])),
|
|
107
105
|
cursor: Math.max(this.cursor, snapshot.history_cursor),
|
|
108
|
-
hasUserTurn: this.hasUserTurn || snapshot.descriptor.turn_state === 'busy'
|
|
109
|
-
|| snapshot.history.some(message => message.role !== 'system'),
|
|
110
106
|
};
|
|
111
107
|
// Validate and project the whole attachment before any observable field
|
|
112
108
|
// moves or a live listener is registered.
|
|
@@ -121,7 +117,9 @@ export class VscodeFrontendSession {
|
|
|
121
117
|
// dead id. An approval can only be outstanding while a turn is running,
|
|
122
118
|
// because `decide_approval` blocks the agent loop, so an idle runtime has
|
|
123
119
|
// none no matter what its replay still carries.
|
|
124
|
-
|
|
120
|
+
// Hosted Codex also receives standalone MCP elicitations. Its host retains
|
|
121
|
+
// request/resolved pairs without the native broker's turn compaction.
|
|
122
|
+
if (staged.descriptor?.turn_state !== 'busy' && staged.descriptor?.source_harness !== 'codex')
|
|
125
123
|
staged.pendingRequests.clear();
|
|
126
124
|
this.descriptor = staged.descriptor;
|
|
127
125
|
this.history = structuredClone(snapshot.history);
|
|
@@ -129,7 +127,6 @@ export class VscodeFrontendSession {
|
|
|
129
127
|
this.appendTranscript(structuredClone(snapshot.replay));
|
|
130
128
|
this.pendingRequests = staged.pendingRequests;
|
|
131
129
|
this.cursor = staged.cursor;
|
|
132
|
-
this.hasUserTurn = staged.hasUserTurn;
|
|
133
130
|
this.connection = 'connected';
|
|
134
131
|
this.attachedAtMs ??= Date.now();
|
|
135
132
|
if (!this.#subscribed) {
|
|
@@ -257,11 +254,6 @@ export class VscodeFrontendSession {
|
|
|
257
254
|
throw new EventSequenceGapError(state.cursor + 1, event.sequence);
|
|
258
255
|
}
|
|
259
256
|
state.cursor = event.sequence;
|
|
260
|
-
// Hosted runtimes publish user_message + turn_started when accepting input.
|
|
261
|
-
// Native init/status notifications also consume sequence numbers, but are
|
|
262
|
-
// not user turns. Keep this independent of the bounded replay buffer.
|
|
263
|
-
if (BUSY_KINDS.has(event.kind))
|
|
264
|
-
state.hasUserTurn = true;
|
|
265
257
|
const payload = (event.payload ?? {});
|
|
266
258
|
if (event.kind === 'request' && payload.type === 'request') {
|
|
267
259
|
const request = payload.request;
|
|
@@ -278,6 +270,11 @@ export class VscodeFrontendSession {
|
|
|
278
270
|
}
|
|
279
271
|
state.pendingRequests.delete(payload.request_id);
|
|
280
272
|
}
|
|
273
|
+
if (event.kind === 'runtime_disconnected') {
|
|
274
|
+
state.pendingRequests.clear();
|
|
275
|
+
if (state.descriptor)
|
|
276
|
+
state.descriptor = { ...state.descriptor, connection_state: 'shutting_down', turn_state: 'idle', actions: { ...state.descriptor.actions, submit: false, steer: false, interrupt: false, respond: false } };
|
|
277
|
+
}
|
|
281
278
|
if (state === this) {
|
|
282
279
|
if (BUSY_KINDS.has(event.kind))
|
|
283
280
|
this.lastTurnStartedMs = Date.now();
|
package/dist/view.js
CHANGED
|
@@ -111,8 +111,10 @@ export function requestSummary(request) {
|
|
|
111
111
|
// naming a runtime uses and falls back to bounded JSON.
|
|
112
112
|
for (const field of ['subject', 'message', 'tool', 'tool_name', 'summary', 'prompt', 'reason']) {
|
|
113
113
|
const value = fields[field];
|
|
114
|
-
if (typeof value === 'string')
|
|
115
|
-
|
|
114
|
+
if (typeof value === 'string') {
|
|
115
|
+
const url = typeof fields.url === 'string' ? `\n\n${fields.url}` : '';
|
|
116
|
+
return clamp(stripControls(value + url), VSCODE_CHAT_LIMITS.partCharacters);
|
|
117
|
+
}
|
|
116
118
|
}
|
|
117
119
|
}
|
|
118
120
|
return opaqueText(payload);
|
|
@@ -121,6 +123,18 @@ export function requestSummary(request) {
|
|
|
121
123
|
export function requestAnswers(request, connectionId) {
|
|
122
124
|
const base = { connectionId, requestId: request.id };
|
|
123
125
|
if (request.kind === 'approval') {
|
|
126
|
+
const options = request.payload && typeof request.payload === 'object' && !Array.isArray(request.payload)
|
|
127
|
+
? request.payload.options : undefined;
|
|
128
|
+
if (Array.isArray(options)) {
|
|
129
|
+
return options.flatMap(option => {
|
|
130
|
+
if (!option || typeof option !== 'object' || Array.isArray(option) || typeof option.id !== 'string' || typeof option.label !== 'string')
|
|
131
|
+
return [];
|
|
132
|
+
const response = ['allow', 'allow_for_session', 'deny'].includes(option.id)
|
|
133
|
+
? { kind: 'approval', request_id: request.id, decision: option.id }
|
|
134
|
+
: { kind: 'other', request_id: request.id, action: 'accept', content: { option_id: option.id } };
|
|
135
|
+
return [{ ...base, label: option.label, response }];
|
|
136
|
+
});
|
|
137
|
+
}
|
|
124
138
|
const all = [
|
|
125
139
|
{ ...base, label: 'Allow', response: { kind: 'approval', request_id: request.id, decision: 'allow' } },
|
|
126
140
|
{
|
|
@@ -161,6 +175,9 @@ export function requestAnswers(request, connectionId) {
|
|
|
161
175
|
export function typedAnswer(request, reply) {
|
|
162
176
|
const normalized = reply.trim().toLowerCase();
|
|
163
177
|
if (request.kind === 'approval') {
|
|
178
|
+
if (['cancel', 'allow_always', 'execpolicy'].includes(normalized) || /^network_\d+$/.test(normalized)) {
|
|
179
|
+
return { kind: 'other', request_id: request.id, action: 'accept', content: { option_id: normalized } };
|
|
180
|
+
}
|
|
164
181
|
if (['allow for session', 'allow_for_session', 'session'].includes(normalized)) {
|
|
165
182
|
return { kind: 'approval', request_id: request.id, decision: 'allow_for_session' };
|
|
166
183
|
}
|
|
@@ -194,6 +211,8 @@ export function requestNeedsTypedReply(request) {
|
|
|
194
211
|
const payload = request.payload;
|
|
195
212
|
if (!payload || typeof payload !== 'object' || Array.isArray(payload))
|
|
196
213
|
return false;
|
|
214
|
+
if (Array.isArray(payload.questions))
|
|
215
|
+
return true;
|
|
197
216
|
const schema = payload.requested_schema;
|
|
198
217
|
return schema !== undefined && schema !== null;
|
|
199
218
|
}
|
|
@@ -211,6 +230,12 @@ export function resolutionSentence(response, request, requestId) {
|
|
|
211
230
|
const verb = { allow: 'Allowed', allow_for_session: 'Allowed for session', deny: 'Denied' }[response.decision];
|
|
212
231
|
return `${verb ?? 'Resolved'}${suffix}`;
|
|
213
232
|
}
|
|
233
|
+
if (response.kind === 'other' && response.content && typeof response.content === 'object' && !Array.isArray(response.content) && typeof response.content.option_id === 'string') {
|
|
234
|
+
const options = request?.payload && typeof request.payload === 'object' && !Array.isArray(request.payload) ? request.payload.options : undefined;
|
|
235
|
+
const option = Array.isArray(options) ? options.find(value => value && typeof value === 'object' && !Array.isArray(value) && value.id === response.content.option_id) : undefined;
|
|
236
|
+
if (option && typeof option === 'object' && !Array.isArray(option) && typeof option.label === 'string')
|
|
237
|
+
return `Selected “${option.label}” for${suffix}`;
|
|
238
|
+
}
|
|
214
239
|
const verb = { accept: 'Accepted', decline: 'Declined', cancel: 'Cancelled' }[response.action];
|
|
215
240
|
const content = typeof response.content === 'string' && response.content.length > 0
|
|
216
241
|
? ` — ${clamp(stripControls(response.content), 160)}`
|
|
@@ -419,7 +444,7 @@ export function projectEvent(event, context) {
|
|
|
419
444
|
// runtime is left blocked. A preferred path that silently swallows
|
|
420
445
|
// the answer is worse than a plain one that works.
|
|
421
446
|
// A command button runs inside the live turn and carries its own answer,
|
|
422
|
-
// so
|
|
447
|
+
// so the runtime's offered answers remain usable. It cannot be withdrawn
|
|
423
448
|
// once pushed (`chatCommandContentPart` enables on `!isStale` and has no
|
|
424
449
|
// used-state), so `supercode.frontend.respond` refuses a request that is
|
|
425
450
|
// no longer pending and SAYS so instead of sending a stale answer.
|
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.21",
|
|
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",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"media"
|
|
24
24
|
],
|
|
25
25
|
"scripts": {
|
|
26
|
-
"build": "node scripts/sync-sdk.mjs && node scripts/fetch-icon.mjs && tsc -p tsconfig.build.json",
|
|
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
27
|
"extension": "npm run build && node scripts/pack-extension.mjs",
|
|
28
28
|
"prepack": "npm run build"
|
|
29
29
|
},
|
|
@@ -196,9 +196,6 @@
|
|
|
196
196
|
"@types/vscode": "1.138.0",
|
|
197
197
|
"typescript": "5.6.3"
|
|
198
198
|
},
|
|
199
|
-
"supercodeFrontendWelcome": {
|
|
200
|
-
"starterIntroduction": "Choose an idea to edit before sending:"
|
|
201
|
-
},
|
|
202
199
|
"supercodeFrontendContract": {
|
|
203
200
|
"name": "@volter/supercode-frontend",
|
|
204
201
|
"version": "0.2.7",
|