@volter/supercode-frontend-vscode 0.1.20 → 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 CHANGED
@@ -1,10 +1,6 @@
1
1
  # Third-party notice
2
2
 
3
- This optional frontend needs **no runtime npm installation**. Its form-schema validator
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
@@ -1,6 +1,4 @@
1
- /** Native Chat presentation; the host supplies inventory, terminal actions and project data. */
1
+ /** Native Chat presentation; the host supplies inventory and terminal actions. */
2
2
  import * as vscode from 'vscode';
3
- import type { ChatSetupState, ChatStarterPrompt } from './host-controls.js';
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;
@@ -1,4 +1,4 @@
1
- /** Native Chat presentation; the host supplies inventory, terminal actions and project data. */
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, starterWelcome } from './chat-setup.js';
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()], starterEligibility: starterEligibility(c),
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 adaptedSchema = adaptRequestSchema(schema, payload?.mode);
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 an answer matching the displayed JSON schema.', value: initial === undefined ? undefined : JSON.stringify(initial), ignoreFocusOut: true, validateInput: value => {
1555
- try {
1556
- const parsed = JSON.parse(value);
1557
- return objectValue(parsed) ? validate(parsed) : 'Enter a JSON object';
1558
- }
1559
- catch {
1560
- return 'Enter valid JSON';
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
- try {
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
- // Cross-field conditions, refs and nested schemas use the complete validated
1578
- // JSON fallback rather than controls that would erase their semantics.
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.required) ? form.required : [];
1587
- for (const { name, spec, control } of fields) {
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
- let validateField;
1598
- try {
1599
- validateField = requestValidator(field);
1600
- }
1601
- catch {
1602
- // A field can require the root's dialect or references. The full-form
1603
- // validator already compiled, so the JSON fallback retains them.
1604
- return structuredFallback(content);
1605
- }
1606
- if (control.kind === 'select') {
1607
- const select = control;
1608
- while (true) {
1609
- const options = select.choices.map(choice => ({ ...choice }));
1610
- let answer;
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 (text.type === 'string')
1636
- return value;
1637
- if (!value.trim() || !Number.isFinite(Number(value)))
1638
- throw new Error('Enter a valid number');
1639
- return Number(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;
1640
1466
  };
1641
- const typed = await vscode.window.showInputBox({ title, prompt: typeof field.description === 'string' ? field.description : undefined, ignoreFocusOut: true, validateInput: value => {
1642
- try {
1643
- return validateField(parse(value));
1644
- }
1645
- catch (error) {
1646
- return error.message;
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
+ }
@@ -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;
package/dist/view.js CHANGED
@@ -1,4 +1,3 @@
1
- import { typedRequestContent } from './request-schema.js';
2
1
  /**
3
2
  * The bounded projection this presentation package renders. Mirrors
4
3
  * `@volter/supercode-frontend-browser`'s rule: the view is size-bounded
@@ -192,7 +191,7 @@ export function typedAnswer(request, reply) {
192
191
  return { kind, request_id: request.id, action: 'cancel' };
193
192
  if (['decline', 'no'].includes(normalized))
194
193
  return { kind, request_id: request.id, action: 'decline' };
195
- return { kind, request_id: request.id, action: 'accept', content: typedRequestContent(request, reply) };
194
+ return { kind, request_id: request.id, action: 'accept', content: reply };
196
195
  }
197
196
  /** The decisions a runtime said its harness accepts, when it said so. */
198
197
  export function acceptedDecisions(request) {
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.20",
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 && node scripts/build-request-schema.mjs",
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
  },
@@ -194,14 +194,8 @@
194
194
  "devDependencies": {
195
195
  "@types/node": "22.10.0",
196
196
  "@types/vscode": "1.138.0",
197
- "ajv": "8.18.0",
198
- "ajv-formats": "3.0.1",
199
- "esbuild": "0.28.2",
200
197
  "typescript": "5.6.3"
201
198
  },
202
- "supercodeFrontendWelcome": {
203
- "starterIntroduction": "Choose an idea to edit before sending:"
204
- },
205
199
  "supercodeFrontendContract": {
206
200
  "name": "@volter/supercode-frontend",
207
201
  "version": "0.2.7",