@volter/supercode-frontend-vscode 0.1.20 → 0.1.22
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 -5
- package/README.md +0 -59
- package/dist/chat-setup.d.ts +2 -4
- package/dist/chat-setup.js +1 -12
- package/dist/extension.js +66 -227
- package/dist/host-controls.d.ts +0 -9
- package/dist/session.d.ts +0 -2
- package/dist/session.js +0 -10
- package/dist/view.js +19 -56
- package/package.json +2 -8
- package/dist/request-schema.LICENSE.txt +0 -157
- package/dist/request-schema.d.ts +0 -22
- package/dist/request-schema.js +0 -8572
package/NOTICE.md
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
# Third-party notice
|
|
2
2
|
|
|
3
|
-
This optional frontend needs **no runtime npm installation**.
|
|
4
|
-
bundles Ajv 8.17.1, ajv-formats 3.0.1 and their dependencies; their license texts ship in
|
|
5
|
-
`dist/request-schema.LICENSE.txt`. esbuild bundles that one module during the build.
|
|
6
|
-
The validator does not fetch external schema references or change answer values.
|
|
7
|
-
It builds against `@types/vscode`
|
|
3
|
+
This optional frontend needs **no runtime npm installation**. It builds against `@types/vscode`
|
|
8
4
|
(Copyright (c) Microsoft Corporation, MIT) as an exact devDependency for types only; `vscode` itself
|
|
9
5
|
is supplied by the editor at runtime and is never bundled or redistributed here.
|
|
10
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
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { adaptRequestSchema, fieldControl, objectValue, requestValidator } from './request-schema.js';
|
|
2
1
|
import { resolveSessionBinding } from './bindings.js';
|
|
3
2
|
import { withTextContext } from './context.js';
|
|
4
3
|
import { randomUUID } from 'node:crypto';
|
|
5
4
|
import { ChatHostControls, runtimeIdentity, harnessDisplayName, turnPermission } from './host-controls.js';
|
|
6
|
-
import { setupParts
|
|
5
|
+
import { setupParts } from './chat-setup.js';
|
|
7
6
|
import * as vscode from 'vscode';
|
|
8
7
|
import { createFrontendClient } from './client.js';
|
|
9
8
|
import { readCredentialFile } from './credential.js';
|
|
@@ -34,44 +33,6 @@ const ENV = Object.freeze({
|
|
|
34
33
|
*/
|
|
35
34
|
unavailable: 'SUPERCODE_FRONTEND_UNAVAILABLE',
|
|
36
35
|
});
|
|
37
|
-
/** Transport progress includes setup events; it is not a user-turn count. */
|
|
38
|
-
function conversationEligibility(connection) {
|
|
39
|
-
const session = connection.session;
|
|
40
|
-
const inputs = {
|
|
41
|
-
attachmentState: session.connection,
|
|
42
|
-
turnState: session.descriptor?.turn_state ?? null,
|
|
43
|
-
pendingRequestCount: session.pendingRequests.size,
|
|
44
|
-
historyTruncated: connection.historyTruncated ?? false,
|
|
45
|
-
persistedHistoryLoaded: connection.persistedHistory !== undefined,
|
|
46
|
-
persistedHistoryCount: connection.persistedHistory?.length ?? 0,
|
|
47
|
-
persistedHistoryRoles: connection.persistedHistory?.map(message => message.role) ?? [],
|
|
48
|
-
canonicalHistoryCount: session.history.length,
|
|
49
|
-
canonicalHistoryRoles: session.history.map(message => message.role),
|
|
50
|
-
hasUserTurn: session.hasUserTurn,
|
|
51
|
-
historyCursor: session.historyCursor,
|
|
52
|
-
eventCursor: session.cursor,
|
|
53
|
-
replayLength: session.replay.length,
|
|
54
|
-
replayKinds: [...new Set(session.replay.map(event => event.kind))],
|
|
55
|
-
nativeEventKinds: [...new Set(session.replay.filter(event => event.kind === 'native_event')
|
|
56
|
-
.map(event => event.payload?.kind).filter(kind => typeof kind === 'string'))],
|
|
57
|
-
};
|
|
58
|
-
const reasons = [];
|
|
59
|
-
if (inputs.attachmentState !== 'connected')
|
|
60
|
-
reasons.push(`attachmentState=${inputs.attachmentState}`);
|
|
61
|
-
if (inputs.turnState !== 'idle')
|
|
62
|
-
reasons.push(`turnState=${inputs.turnState}`);
|
|
63
|
-
if (inputs.pendingRequestCount)
|
|
64
|
-
reasons.push(`pendingRequestCount=${inputs.pendingRequestCount}`);
|
|
65
|
-
if (inputs.historyTruncated)
|
|
66
|
-
reasons.push('saved history is truncated');
|
|
67
|
-
if (inputs.persistedHistoryRoles.some(role => role !== 'system'))
|
|
68
|
-
reasons.push('saved history contains a turn');
|
|
69
|
-
if (inputs.canonicalHistoryRoles.some(role => role !== 'system'))
|
|
70
|
-
reasons.push('canonical history contains a turn');
|
|
71
|
-
if (inputs.hasUserTurn)
|
|
72
|
-
reasons.push('a user turn has been observed');
|
|
73
|
-
return { ...inputs, eligible: reasons.length === 0, reasons };
|
|
74
|
-
}
|
|
75
36
|
function normalizePermissions(value, fallback) {
|
|
76
37
|
const raw = Array.isArray(value)
|
|
77
38
|
? value.map(String)
|
|
@@ -364,24 +325,6 @@ export async function activate(context) {
|
|
|
364
325
|
const boundId = (resource) => resolveSessionBinding(resource, controls?.state.sessions ?? [], resourceBindings);
|
|
365
326
|
let applying = Promise.resolve();
|
|
366
327
|
const frontends = new SupercodeFrontends();
|
|
367
|
-
const starterEligibility = (connection) => {
|
|
368
|
-
const conversation = conversationEligibility(connection);
|
|
369
|
-
const activeSession = controls?.state.activeSession ?? null;
|
|
370
|
-
const hostBusy = Boolean(controls?.state.busy);
|
|
371
|
-
const connectionMatches = frontends.get(connection.id) === connection;
|
|
372
|
-
const reasons = [...conversation.reasons];
|
|
373
|
-
if (!controls)
|
|
374
|
-
reasons.push('host controls unavailable');
|
|
375
|
-
if (activeSession !== connection.id)
|
|
376
|
-
reasons.push(`activeSession=${activeSession}; expected ${connection.id}`);
|
|
377
|
-
if (hostBusy)
|
|
378
|
-
reasons.push('hostBusy=true');
|
|
379
|
-
if (!connectionMatches)
|
|
380
|
-
reasons.push('connection was replaced');
|
|
381
|
-
return { ...conversation, controlsAvailable: Boolean(controls), connectionId: connection.id, activeSession, hostBusy, connectionMatches,
|
|
382
|
-
eligible: reasons.length === 0, reasons };
|
|
383
|
-
};
|
|
384
|
-
const lastStarterAttempts = new Map();
|
|
385
328
|
// Runtimes attached by address ("Set Runtime Token", "Attach to Runtime")
|
|
386
329
|
// beside a host's managed conversation. The host's catalogue does not know
|
|
387
330
|
// them, so they open as what they are: attachments, shown as their runtime
|
|
@@ -390,28 +333,6 @@ export async function activate(context) {
|
|
|
390
333
|
const managed = () => frontends.all.find((connection) => !attachedIds.has(connection.id));
|
|
391
334
|
// The attached runtime the person is looking at: the Chat names its harness, not the host's.
|
|
392
335
|
let focusedAttached;
|
|
393
|
-
const welcomeParticipants = new Set();
|
|
394
|
-
const welcomeValues = new WeakMap();
|
|
395
|
-
const starterResources = new Map();
|
|
396
|
-
const updateStarterWelcome = () => {
|
|
397
|
-
if (!granted.has('defaultChatParticipant'))
|
|
398
|
-
return;
|
|
399
|
-
const connection = managed();
|
|
400
|
-
const prompts = controls?.state.starterPrompts ?? [];
|
|
401
|
-
// This is the click guard's exact state, including host activity and connection
|
|
402
|
-
// identity. A later idle state must not revive an offer after a user turn.
|
|
403
|
-
const message = connection && !focusedAttached && !controls?.state.starterPromptsPending
|
|
404
|
-
&& prompts.length && starterEligibility(connection).eligible
|
|
405
|
-
? starterWelcome(prompts, connection.id, starterResources.get(connection.id) ?? sessionResource(connection).toString(), context.extension.packageJSON.supercodeFrontendWelcome.starterIntroduction)
|
|
406
|
-
: new vscode.MarkdownString('');
|
|
407
|
-
for (const participant of welcomeParticipants) {
|
|
408
|
-
if (welcomeValues.get(participant) === message.value)
|
|
409
|
-
continue;
|
|
410
|
-
participant.additionalWelcomeMessage = message;
|
|
411
|
-
welcomeValues.set(participant, message.value);
|
|
412
|
-
}
|
|
413
|
-
};
|
|
414
|
-
context.subscriptions.push(frontends.onDidChange(updateStarterWelcome), controlsChanged.event(updateStarterWelcome));
|
|
415
336
|
let approvalAvailable = false;
|
|
416
337
|
context.subscriptions.push(frontends.onDidChange(() => {
|
|
417
338
|
const active = managed();
|
|
@@ -574,36 +495,6 @@ export async function activate(context) {
|
|
|
574
495
|
setupTerminals.set(key, terminal);
|
|
575
496
|
terminal.show();
|
|
576
497
|
}));
|
|
577
|
-
context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.fillStarter', async (session, index, nativeResource) => {
|
|
578
|
-
if (!controls || !Number.isInteger(index) || index < 0 || index > 2)
|
|
579
|
-
throw new Error('Invalid starter selection.');
|
|
580
|
-
const connection = frontends.get(session);
|
|
581
|
-
if (!connection)
|
|
582
|
-
throw new Error(`Starter prompt unavailable: connection ${session} is absent.`);
|
|
583
|
-
const resource = nativeResource ? vscode.Uri.parse(nativeResource)
|
|
584
|
-
: vscode.Uri.from({ scheme: SESSION_SCHEME, authority: session, path: '/conversation' });
|
|
585
|
-
if (boundId(resource) !== session)
|
|
586
|
-
throw new Error('The starter has no matching conversation binding.');
|
|
587
|
-
await connection.session.attach();
|
|
588
|
-
const guard = starterEligibility(connection);
|
|
589
|
-
lastStarterAttempts.set(session, { at: new Date().toISOString(), resource: resource.toString(),
|
|
590
|
-
boundSession: boundId(resource), guard });
|
|
591
|
-
while (lastStarterAttempts.size > 50)
|
|
592
|
-
lastStarterAttempts.delete(lastStarterAttempts.keys().next().value);
|
|
593
|
-
if (!guard.eligible)
|
|
594
|
-
throw new Error(`Starter prompt unavailable: ${guard.reasons.join('; ')}.`);
|
|
595
|
-
const prompt = controls.state.starterPrompts?.[index]?.prompt;
|
|
596
|
-
if (!prompt)
|
|
597
|
-
throw new Error('This starter is no longer available.');
|
|
598
|
-
if (!controls.state.fillInputCommand)
|
|
599
|
-
throw new Error('This host does not support filling a conversation draft.');
|
|
600
|
-
// The host addresses the existing native widget, never the default participant or last focus.
|
|
601
|
-
const receipt = await vscode.commands.executeCommand(controls.state.fillInputCommand, resource.toString(), prompt);
|
|
602
|
-
if (!receipt || receipt.sessionResource !== resource.toString() || receipt.input !== prompt) {
|
|
603
|
-
throw new Error('Chat did not confirm the requested draft.');
|
|
604
|
-
}
|
|
605
|
-
return receipt;
|
|
606
|
-
}));
|
|
607
498
|
const connectionFor = async (resource) => {
|
|
608
499
|
if (!controls) {
|
|
609
500
|
const connection = frontends.get(resource.authority);
|
|
@@ -706,24 +597,6 @@ export async function activate(context) {
|
|
|
706
597
|
catalogue: () => controls?.state.sessions ?? [],
|
|
707
598
|
bindings: boundId,
|
|
708
599
|
setup: () => controls?.state.setup,
|
|
709
|
-
prepareStarters: async (session, resource, token) => {
|
|
710
|
-
// Project configuration can arrive after the runtime connects. Wait for
|
|
711
|
-
// its real answer before publishing the conversation's welcome actions.
|
|
712
|
-
const deadline = Date.now() + 30_000;
|
|
713
|
-
await controls?.refresh();
|
|
714
|
-
while (controls?.state.starterPromptsPending) {
|
|
715
|
-
if (token.isCancellationRequested)
|
|
716
|
-
throw new vscode.CancellationError();
|
|
717
|
-
if (Date.now() >= deadline)
|
|
718
|
-
throw new Error('Project configuration is still loading. Reopen Chat once the project is ready.');
|
|
719
|
-
await new Promise(resolve => setTimeout(resolve, 250));
|
|
720
|
-
await controls.refresh();
|
|
721
|
-
}
|
|
722
|
-
if (token.isCancellationRequested)
|
|
723
|
-
throw new vscode.CancellationError();
|
|
724
|
-
starterResources.set(session, resource);
|
|
725
|
-
controlsChanged.fire();
|
|
726
|
-
},
|
|
727
600
|
};
|
|
728
601
|
context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.activateSession', async (resource) => { await connectionFor(vscode.Uri.parse(resource)); }));
|
|
729
602
|
context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.openSession', async (id) => {
|
|
@@ -919,8 +792,6 @@ export async function activate(context) {
|
|
|
919
792
|
const participant = vscode.chat.createChatParticipant(id, handler);
|
|
920
793
|
participant.iconPath = icon();
|
|
921
794
|
participants.push(participant);
|
|
922
|
-
welcomeParticipants.add(participant);
|
|
923
|
-
updateStarterWelcome();
|
|
924
795
|
context.subscriptions.push(participant);
|
|
925
796
|
}
|
|
926
797
|
catch (error) {
|
|
@@ -943,8 +814,6 @@ export async function activate(context) {
|
|
|
943
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)
|
|
944
815
|
: vscode.chat.createChatParticipant(SESSION_SCHEME, handler);
|
|
945
816
|
participant.iconPath = icon();
|
|
946
|
-
welcomeParticipants.add(participant);
|
|
947
|
-
updateStarterWelcome();
|
|
948
817
|
return participant;
|
|
949
818
|
};
|
|
950
819
|
let sessionParticipant = makeParticipant();
|
|
@@ -953,7 +822,6 @@ export async function activate(context) {
|
|
|
953
822
|
if (participantKey() === previousIdentity)
|
|
954
823
|
return;
|
|
955
824
|
previousIdentity = participantKey();
|
|
956
|
-
welcomeParticipants.delete(sessionParticipant);
|
|
957
825
|
sessionParticipant.dispose();
|
|
958
826
|
sessionParticipant = makeParticipant();
|
|
959
827
|
for (const p of participants)
|
|
@@ -1053,8 +921,7 @@ export async function activate(context) {
|
|
|
1053
921
|
if (inspect === true)
|
|
1054
922
|
return {
|
|
1055
923
|
connections: frontends.all.map(c => ({ ...frontends.info(c), permissions: [...c.session.permissions],
|
|
1056
|
-
pendingRequests: [...c.session.pendingRequests.values()]
|
|
1057
|
-
lastStarterAttempt: lastStarterAttempts.get(c.id) ?? null })),
|
|
924
|
+
pendingRequests: [...c.session.pendingRequests.values()] })),
|
|
1058
925
|
selection: controls?.state.selection, activeSession: controls?.state.activeSession,
|
|
1059
926
|
busy: Boolean(controls?.state.busy || frontends.all.some(c => c.session.descriptor?.turn_state === 'busy' || c.session.pendingRequests.size)),
|
|
1060
927
|
setupHandoff: { hostReady: controls?.state.setup?.ready ?? null, complete: setupHandoffComplete(),
|
|
@@ -1474,12 +1341,6 @@ function registerSessions(context, frontends, participant, granted, handler, con
|
|
|
1474
1341
|
}
|
|
1475
1342
|
history.push(new turns.ChatResponseTurn(historyParts(oneRowPerToolCall(turn.parts ?? [])), {}, participant.id));
|
|
1476
1343
|
}
|
|
1477
|
-
// The public API cannot withdraw a rendered history command part. Keep
|
|
1478
|
-
// starters in the default participant's native welcome content instead:
|
|
1479
|
-
// Chat hides that whole surface as soon as the first request is added.
|
|
1480
|
-
if (attached && history.length === 0 && !controls?.attached(connection) && controls) {
|
|
1481
|
-
await controls.prepareStarters(connection.id, resource.toString(), _token);
|
|
1482
|
-
}
|
|
1483
1344
|
const busy = connection.session.descriptor?.turn_state === 'busy';
|
|
1484
1345
|
return {
|
|
1485
1346
|
title: connection.label,
|
|
@@ -1547,46 +1408,28 @@ async function requestContent(request) {
|
|
|
1547
1408
|
const document = await vscode.workspace.openTextDocument({ language: 'json', content: JSON.stringify(schema, null, 2) });
|
|
1548
1409
|
await vscode.window.showTextDocument(document, { preview: true, preserveFocus: true });
|
|
1549
1410
|
};
|
|
1550
|
-
const
|
|
1551
|
-
let validate;
|
|
1552
|
-
const structuredFallback = async (initial) => {
|
|
1411
|
+
const structuredInput = async () => {
|
|
1553
1412
|
await showSchema();
|
|
1554
|
-
const typed = await vscode.window.showInputBox({ title: 'Runtime request', prompt: 'Enter
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
} });
|
|
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
|
+
} } });
|
|
1563
1421
|
return typed === undefined ? undefined : JSON.parse(typed);
|
|
1564
1422
|
};
|
|
1565
|
-
|
|
1566
|
-
validate = requestValidator(adaptedSchema);
|
|
1567
|
-
}
|
|
1568
|
-
catch (error) {
|
|
1569
|
-
// Keep the structured-input path available with the original schema in
|
|
1570
|
-
// view. Never accept an answer whose constraints cannot be validated.
|
|
1571
|
-
const message = `Cannot validate this request schema: ${String(error.message)}`;
|
|
1572
|
-
validate = () => message;
|
|
1573
|
-
return structuredFallback();
|
|
1574
|
-
}
|
|
1575
|
-
const form = objectValue(adaptedSchema);
|
|
1423
|
+
const form = objectValue(schema);
|
|
1576
1424
|
const properties = objectValue(form?.properties);
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
const simpleForm = form?.type === 'object' && properties && Object.keys(form).every(key => ['$schema', 'type', 'title', 'description', 'properties', 'required', 'additionalProperties'].includes(key));
|
|
1580
|
-
if (!simpleForm || !properties)
|
|
1581
|
-
return structuredFallback();
|
|
1582
|
-
const fields = Object.entries(properties).map(([name, spec]) => ({ name, spec: objectValue(spec), control: objectValue(spec) ? fieldControl(objectValue(spec)) : undefined }));
|
|
1583
|
-
if (fields.some(field => !field.spec || !field.control))
|
|
1584
|
-
return structuredFallback();
|
|
1425
|
+
if (!properties)
|
|
1426
|
+
return structuredInput();
|
|
1585
1427
|
const content = Object.create(null);
|
|
1586
|
-
const required = Array.isArray(form
|
|
1587
|
-
for (const
|
|
1588
|
-
const field = spec;
|
|
1428
|
+
const required = Array.isArray(form?.required) ? form.required : [];
|
|
1429
|
+
for (const [name, spec] of Object.entries(properties)) {
|
|
1430
|
+
const field = objectValue(spec) ?? {};
|
|
1589
1431
|
const title = typeof field.title === 'string' ? field.title : name;
|
|
1432
|
+
const description = typeof field.description === 'string' ? field.description : undefined;
|
|
1590
1433
|
if (!required.includes(name)) {
|
|
1591
1434
|
const choice = await vscode.window.showQuickPick(['Provide a value', 'Omit this field'], { title: `${title} (optional)`, ignoreFocusOut: true });
|
|
1592
1435
|
if (!choice)
|
|
@@ -1594,67 +1437,63 @@ async function requestContent(request) {
|
|
|
1594
1437
|
if (choice === 'Omit this field')
|
|
1595
1438
|
continue;
|
|
1596
1439
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
if (select.multiple) {
|
|
1612
|
-
const selected = await vscode.window.showQuickPick(options, { title, placeHolder: typeof field.description === 'string' ? field.description : undefined, canPickMany: true, ignoreFocusOut: true });
|
|
1613
|
-
if (!selected)
|
|
1614
|
-
return undefined;
|
|
1615
|
-
answer = selected.map(choice => choice.value);
|
|
1616
|
-
}
|
|
1617
|
-
else {
|
|
1618
|
-
const selected = await vscode.window.showQuickPick(options, { title, placeHolder: typeof field.description === 'string' ? field.description : undefined, ignoreFocusOut: true });
|
|
1619
|
-
if (!selected)
|
|
1620
|
-
return undefined;
|
|
1621
|
-
answer = selected.value;
|
|
1622
|
-
}
|
|
1623
|
-
const error = validateField(answer);
|
|
1624
|
-
if (error) {
|
|
1625
|
-
await vscode.window.showWarningMessage(error);
|
|
1626
|
-
continue;
|
|
1627
|
-
}
|
|
1628
|
-
content[name] = answer;
|
|
1629
|
-
break;
|
|
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;
|
|
1630
1454
|
}
|
|
1631
1455
|
}
|
|
1632
1456
|
else {
|
|
1633
|
-
const text = control;
|
|
1634
1457
|
const parse = (value) => {
|
|
1635
|
-
if (
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
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;
|
|
1640
1466
|
};
|
|
1641
|
-
const typed = await vscode.window.showInputBox({ title, prompt:
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
} });
|
|
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
|
+
} } });
|
|
1649
1475
|
if (typed === undefined)
|
|
1650
1476
|
return undefined;
|
|
1651
1477
|
content[name] = parse(typed);
|
|
1652
1478
|
}
|
|
1653
1479
|
}
|
|
1654
|
-
const error = validate(content);
|
|
1655
|
-
if (error) {
|
|
1656
|
-
await vscode.window.showWarningMessage(error);
|
|
1657
|
-
return structuredFallback(content);
|
|
1658
|
-
}
|
|
1659
1480
|
return content;
|
|
1660
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
|
@@ -11,10 +11,6 @@ export interface ChatChoice {
|
|
|
11
11
|
description?: string;
|
|
12
12
|
autoStart?: boolean;
|
|
13
13
|
}
|
|
14
|
-
export interface ChatStarterPrompt {
|
|
15
|
-
label: string;
|
|
16
|
-
prompt: string;
|
|
17
|
-
}
|
|
18
14
|
export interface ManagedChatSession {
|
|
19
15
|
id: string;
|
|
20
16
|
selection: ChatSelection;
|
|
@@ -40,8 +36,6 @@ export interface HostState {
|
|
|
40
36
|
historyTruncated?: boolean;
|
|
41
37
|
activeSession: string | null;
|
|
42
38
|
openSessionCommand?: string;
|
|
43
|
-
/** Fill the composer of an exact native session; returns {sessionResource, input}. */
|
|
44
|
-
fillInputCommand?: string;
|
|
45
39
|
/** The native renderer supports a leading response with no visible user request. */
|
|
46
40
|
responseOnlyHistory?: boolean;
|
|
47
41
|
sessions: ManagedChatSession[];
|
|
@@ -55,9 +49,6 @@ export interface HostState {
|
|
|
55
49
|
actualModel?: string | null;
|
|
56
50
|
configurable: boolean;
|
|
57
51
|
setup?: ChatSetupState;
|
|
58
|
-
starterPrompts?: ChatStarterPrompt[];
|
|
59
|
-
/** The host's project configuration has not finished loading yet. */
|
|
60
|
-
starterPromptsPending?: boolean;
|
|
61
52
|
}
|
|
62
53
|
export interface HostSelectionResult extends HostState {
|
|
63
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.
|
|
@@ -131,7 +127,6 @@ export class VscodeFrontendSession {
|
|
|
131
127
|
this.appendTranscript(structuredClone(snapshot.replay));
|
|
132
128
|
this.pendingRequests = staged.pendingRequests;
|
|
133
129
|
this.cursor = staged.cursor;
|
|
134
|
-
this.hasUserTurn = staged.hasUserTurn;
|
|
135
130
|
this.connection = 'connected';
|
|
136
131
|
this.attachedAtMs ??= Date.now();
|
|
137
132
|
if (!this.#subscribed) {
|
|
@@ -259,11 +254,6 @@ export class VscodeFrontendSession {
|
|
|
259
254
|
throw new EventSequenceGapError(state.cursor + 1, event.sequence);
|
|
260
255
|
}
|
|
261
256
|
state.cursor = event.sequence;
|
|
262
|
-
// Hosted runtimes publish user_message + turn_started when accepting input.
|
|
263
|
-
// Native init/status notifications also consume sequence numbers, but are
|
|
264
|
-
// not user turns. Keep this independent of the bounded replay buffer.
|
|
265
|
-
if (BUSY_KINDS.has(event.kind))
|
|
266
|
-
state.hasUserTurn = true;
|
|
267
257
|
const payload = (event.payload ?? {});
|
|
268
258
|
if (event.kind === 'request' && payload.type === 'request') {
|
|
269
259
|
const request = payload.request;
|