@volter/supercode-frontend-vscode 0.1.33 → 0.1.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,6 +24,25 @@ Gallery detection reads the host's `product.json` through `vscode.workspace.fs`
24
24
  at `vscode.env.appRoot`; hosts without an inspectable gallery configuration hide
25
25
  the choice. Neither the extension nor its credentials are bundled or copied.
26
26
 
27
+ The welcome's primary action uses `supercode.frontend.beginChatGPTSignIn`:
28
+ it starts one retained install/sidebar operation and returns a copied receipt
29
+ with its id, timestamps and current phase. This is a launch receipt, not proof
30
+ that installation, sidebar presentation or authentication succeeded. A second
31
+ begin while that operation is running returns the same id and starts no extra
32
+ installation. `supercode.frontend.chatGPTSignInStatus` reads its current phase
33
+ locally without waiting for host readiness or installation; pass the operation
34
+ id to receive only that operation (a mismatched id returns null).
35
+ Phases name the selected work before its awaited command: `checking-gallery`,
36
+ `installing-extension`, `opening-sidebar`, then `completed` or `failed`.
37
+ Completed means the official command sequence fulfilled, not signed in. Failures
38
+ remain in operation status and the existing native error notification/output.
39
+ Only the current or most recent operation is retained, for this extension-window
40
+ lifetime; reload does not restore or automatically retry it. Runtime status
41
+ inspection also includes this snapshot as `chatGPTSignIn`.
42
+ The existing `supercode.frontend.signInWithChatGPT` still awaits the same work
43
+ with its existing error-notification/fulfillment behavior, and the agent picker
44
+ continues to use it. Ordinary relay budgets and draft guards are unchanged.
45
+
27
46
  The optional host-controls state can include `setup: { ready, actions, reason,
28
47
  cwd }`. Each action supplies `kind` (`login` or `install`), `harness`, `name` and
29
48
  the exact `command` revalidated before execution. On a signed-out first open,
@@ -373,3 +392,14 @@ routing: stock code broadcasts changes to all input states of a provider. The
373
392
  Volter overlay guards this fix with a native regression test. The former legacy
374
393
  option-notification queue is no longer required. The narrow reveal/focus bridge
375
394
  remains because the native model catalogue and participant identity are provider-wide.
395
+
396
+ ### Native approval cards
397
+
398
+ A host advertising `volter.chat.inTurnApprovalCapability` version 1 presents
399
+ actual approval requests in its native confirmation card. The original offered
400
+ labels, order and response payloads are preserved. Clicking answers the pending
401
+ request inside the current turn through `supercode.frontend.respondInTurnApproval`;
402
+ it never submits a new chat prompt. The host guards the addressed conversation,
403
+ disables the choices during dispatch and retires them only after an acknowledged
404
+ response or an already-resolved result. Hosts without this capability retain
405
+ stable command buttons. Other request kinds retain their existing presentation.
@@ -2,12 +2,22 @@
2
2
  import * as vscode from 'vscode';
3
3
  import type { ChatSetupState } from './host-controls.js';
4
4
  export declare const CHATGPT_SIGN_IN = "supercode.frontend.signInWithChatGPT";
5
+ export declare const CHATGPT_SIGN_IN_BEGIN = "supercode.frontend.beginChatGPTSignIn";
6
+ export declare const CHATGPT_SIGN_IN_STATUS = "supercode.frontend.chatGPTSignInStatus";
7
+ export type ChatGPTSignInWorkPhase = 'checking-gallery' | 'installing-extension' | 'opening-sidebar';
8
+ export interface ChatGPTSignInOperation {
9
+ readonly id: string;
10
+ readonly phase: ChatGPTSignInWorkPhase | 'completed' | 'failed';
11
+ readonly startedAt: number;
12
+ readonly updatedAt: number;
13
+ readonly finishedAt?: number;
14
+ }
5
15
  /** The install command also exists in gallery-less Code-OSS. Fail closed when
6
16
  * the host's product configuration cannot be inspected through the VS Code API. */
7
17
  export declare function chatGPTAvailable(): Promise<boolean>;
8
18
  /** Only called by an explicit click. The gallery and extension own installation
9
19
  * and authentication; there is no terminal or provider credential handling here. */
10
- export declare function signInWithChatGPT(): Promise<void>;
20
+ export declare function signInWithChatGPT(report?: (phase: ChatGPTSignInWorkPhase) => void): Promise<void>;
11
21
  /** Empty-chat welcome content, rendered by Code-OSS's ChatViewWelcomePart.
12
22
  * Only the official extension gets the primary button; native local sign-in
13
23
  * actions remain ordinary links. Commands and host diagnostic paths stay out
@@ -1,6 +1,8 @@
1
1
  /** Native Chat presentation; the host supplies inventory and terminal actions. */
2
2
  import * as vscode from 'vscode';
3
3
  export const CHATGPT_SIGN_IN = 'supercode.frontend.signInWithChatGPT';
4
+ export const CHATGPT_SIGN_IN_BEGIN = 'supercode.frontend.beginChatGPTSignIn';
5
+ export const CHATGPT_SIGN_IN_STATUS = 'supercode.frontend.chatGPTSignInStatus';
4
6
  const CODEX_EXTENSION = 'openai.chatgpt';
5
7
  const INSTALL_EXTENSION = 'workbench.extensions.installExtension';
6
8
  /** The install command also exists in gallery-less Code-OSS. Fail closed when
@@ -20,16 +22,19 @@ export async function chatGPTAvailable() {
20
22
  }
21
23
  /** Only called by an explicit click. The gallery and extension own installation
22
24
  * and authentication; there is no terminal or provider credential handling here. */
23
- export async function signInWithChatGPT() {
25
+ export async function signInWithChatGPT(report) {
26
+ report?.('checking-gallery');
24
27
  if (!await chatGPTAvailable())
25
28
  throw new Error('This host does not provide an extension gallery.');
26
29
  if (!vscode.extensions.getExtension(CODEX_EXTENSION)) {
27
30
  // enable makes the workbench await the running extension registry, not
28
31
  // just the installed inventory, before we dispatch the contributed command.
32
+ report?.('installing-extension');
29
33
  await vscode.commands.executeCommand(INSTALL_EXTENSION, CODEX_EXTENSION, { enable: true });
30
34
  }
31
35
  // openai.chatgpt contributes this command, not a public sign-in command.
32
36
  // Revealing its sidebar presents its own Sign in with ChatGPT when signed out.
37
+ report?.('opening-sidebar');
33
38
  await vscode.commands.executeCommand('chatgpt.openSidebar');
34
39
  }
35
40
  /** Empty-chat welcome content, rendered by Code-OSS's ChatViewWelcomePart.
@@ -38,9 +43,9 @@ export async function signInWithChatGPT() {
38
43
  * of the welcome, and the host still revalidates every action on click. */
39
44
  export function setupWelcome(setup, chatGPT = false) {
40
45
  const welcome = new vscode.MarkdownString();
41
- welcome.isTrusted = { enabledCommands: [CHATGPT_SIGN_IN, 'supercode.frontend.setup', 'supercode.frontend.refreshSetup'] };
46
+ welcome.isTrusted = { enabledCommands: [CHATGPT_SIGN_IN_BEGIN, 'supercode.frontend.setup', 'supercode.frontend.refreshSetup'] };
42
47
  if (chatGPT) {
43
- welcome.appendMarkdown(`[Sign in with ChatGPT](command:${CHATGPT_SIGN_IN})\n\n`);
48
+ welcome.appendMarkdown(`[Sign in with ChatGPT](command:${CHATGPT_SIGN_IN_BEGIN})\n\n`);
44
49
  welcome.appendMarkdown('Use the official Codex extension.');
45
50
  welcome.appendMarkdown('\n\n');
46
51
  }
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 { CHATGPT_SIGN_IN, chatGPTAvailable, setupWelcome, signInWithChatGPT } from './chat-setup.js';
5
+ import { CHATGPT_SIGN_IN, CHATGPT_SIGN_IN_BEGIN, CHATGPT_SIGN_IN_STATUS, chatGPTAvailable, setupWelcome, signInWithChatGPT } 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';
@@ -184,6 +184,7 @@ class SupercodeFrontends {
184
184
  this.#changed.dispose();
185
185
  }
186
186
  }
187
+ let nativeInTurnApprovalCards = false;
187
188
  function writeProjected(stream, projected, granted, state) {
188
189
  for (const candidate of projected) {
189
190
  const parts = resolveParts([candidate], granted);
@@ -235,6 +236,12 @@ function writePart(stream, part, state) {
235
236
  return;
236
237
  }
237
238
  case 'answers':
239
+ if (part.approval && nativeInTurnApprovalCards && part.answers.length > 0) {
240
+ stream.confirmation(part.title, part.message, {
241
+ kind: 'supercode.inTurnApproval', version: 1, answers: part.answers,
242
+ }, part.answers.map(answer => answer.label));
243
+ return;
244
+ }
238
245
  // Stable API on purpose: a command button runs INSIDE the live turn,
239
246
  // which is when a supercode approval is raised. See `view.ts`.
240
247
  stream.markdown(`\n\n**${part.title}**\n\n${part.message}\n\n`);
@@ -300,16 +307,53 @@ export async function activate(context) {
300
307
  // description, so this is the grant, not the request.
301
308
  const granted = grantedProposals(context.extension.packageJSON);
302
309
  log.info(`Volter Harness: proposals requested ${REQUESTED_PROPOSALS.join(', ')}; granted ${granted.size ? [...granted].join(', ') : 'none (stable chat parts only)'}`);
310
+ // Explicit workbench support is required: stock confirmation clicks send
311
+ // a new prompt. A missing capability keeps the stable in-turn buttons.
312
+ if (granted.has('chatParticipantAdditions')) {
313
+ try {
314
+ const commands = await vscode.commands.getCommands(true);
315
+ if (commands.includes('volter.chat.inTurnApprovalCapability')) {
316
+ const capability = await vscode.commands.executeCommand('volter.chat.inTurnApprovalCapability');
317
+ nativeInTurnApprovalCards = capability?.version === 1;
318
+ }
319
+ }
320
+ catch (error) {
321
+ log.warn(`Native in-turn approval cards unavailable: ${String(error)}`);
322
+ }
323
+ }
303
324
  const controls = await ChatHostControls.fromEnvironment();
304
325
  const offerChatGPT = await chatGPTAvailable();
305
326
  let signingInWithChatGPT;
306
- context.subscriptions.push(vscode.commands.registerCommand(CHATGPT_SIGN_IN, () => {
307
- signingInWithChatGPT ??= signInWithChatGPT().catch(error => {
327
+ let chatGPTSignInOperation;
328
+ const signInSnapshot = () => chatGPTSignInOperation ? { ...chatGPTSignInOperation } : null;
329
+ const startChatGPTSignIn = () => {
330
+ if (signingInWithChatGPT)
331
+ return signingInWithChatGPT;
332
+ const startedAt = Date.now();
333
+ const operation = { id: randomUUID(), startedAt };
334
+ const update = (phase) => {
335
+ const updatedAt = Date.now();
336
+ chatGPTSignInOperation = { ...operation, phase, updatedAt,
337
+ ...(phase === 'completed' || phase === 'failed' ? { finishedAt: updatedAt } : {}) };
338
+ };
339
+ signingInWithChatGPT = signInWithChatGPT(phase => update(phase)).then(() => {
340
+ // Fulfilled install/sidebar commands do not establish authentication.
341
+ update('completed');
342
+ }).catch(error => {
343
+ update('failed');
308
344
  log.error(`Could not open ChatGPT sign-in: ${String(error)}`);
309
345
  void vscode.window.showErrorMessage(`Could not open ChatGPT sign-in: ${error instanceof Error ? error.message : String(error)}`);
310
346
  }).finally(() => { signingInWithChatGPT = undefined; });
311
347
  return signingInWithChatGPT;
312
- }));
348
+ };
349
+ context.subscriptions.push(
350
+ // Preserve the completion-awaiting command used by the agent picker.
351
+ vscode.commands.registerCommand(CHATGPT_SIGN_IN, startChatGPTSignIn),
352
+ // A click starts one retained task. Its receipt is not install/login success.
353
+ vscode.commands.registerCommand(CHATGPT_SIGN_IN_BEGIN, () => {
354
+ void startChatGPTSignIn();
355
+ return signInSnapshot();
356
+ }), vscode.commands.registerCommand(CHATGPT_SIGN_IN_STATUS, (operationId) => operationId === undefined || operationId === chatGPTSignInOperation?.id ? signInSnapshot() : null));
313
357
  const controlsChanged = new vscode.EventEmitter();
314
358
  context.subscriptions.push(controlsChanged);
315
359
  let permission = 'ask';
@@ -1059,34 +1103,36 @@ export async function activate(context) {
1059
1103
  */
1060
1104
  async function answerRequest(answer) {
1061
1105
  if (!answer || typeof answer.requestId !== 'number')
1062
- return;
1106
+ return { status: 'unavailable' };
1063
1107
  const connection = frontends.get(answer.connectionId);
1064
1108
  if (!connection) {
1065
1109
  void vscode.window.showWarningMessage('Volter Harness is no longer attached to that runtime.');
1066
- return;
1110
+ return { status: 'unavailable' };
1067
1111
  }
1068
1112
  const request = connection.session.pendingRequests.get(answer.requestId);
1069
1113
  if (!request) {
1070
1114
  void vscode.window.showInformationMessage(`Volter Harness: request #${answer.requestId} is already resolved.`);
1071
- return;
1115
+ return { status: 'resolved' };
1072
1116
  }
1073
1117
  let response = answer.response;
1074
1118
  try {
1075
1119
  if (answer.promptForContent) {
1076
1120
  const content = await requestContent(request);
1077
1121
  if (content === undefined)
1078
- return;
1122
+ return { status: 'cancelled' };
1079
1123
  response = { ...response, content };
1080
1124
  }
1081
1125
  await connection.session.respond(response);
1082
1126
  void vscode.window.setStatusBarMessage(`Volter Harness: ${resolutionSentence(response, request, answer.requestId)}`, 4000);
1127
+ return { status: 'responded' };
1083
1128
  }
1084
1129
  catch (error) {
1085
1130
  if (error instanceof RequestNotPendingError) {
1086
1131
  void vscode.window.showInformationMessage(`Volter Harness: request #${answer.requestId} is already resolved.`);
1087
- return;
1132
+ return { status: 'resolved' };
1088
1133
  }
1089
1134
  void vscode.window.showErrorMessage(`Volter Harness could not answer request #${answer.requestId}: ${String(error?.message ?? error)}`);
1135
+ return { status: 'failed' };
1090
1136
  }
1091
1137
  }
1092
1138
  const secretKey = (baseUrl) => `supercode.frontend.token:${baseUrl}`;
@@ -1132,11 +1178,12 @@ export async function activate(context) {
1132
1178
  // The one door an answer button uses. It answers the runtime's pending
1133
1179
  // request through `frontend.v2.respond` and narrates itself, because a
1134
1180
  // click whose effect is invisible is a click nobody can trust.
1135
- vscode.commands.registerCommand('supercode.frontend.respond', (answer) => answerRequest(answer)), vscode.commands.registerCommand('supercode.frontend.status', async (inspect) => {
1181
+ vscode.commands.registerCommand('supercode.frontend.respond', async (answer) => { await answerRequest(answer); }), vscode.commands.registerCommand('supercode.frontend.respondInTurnApproval', (answer) => answerRequest(answer)), vscode.commands.registerCommand('supercode.frontend.status', async (inspect) => {
1136
1182
  if (controls)
1137
1183
  await controls.refresh();
1138
1184
  if (inspect === true)
1139
1185
  return {
1186
+ chatGPTSignIn: signInSnapshot(),
1140
1187
  connections: frontends.all.map(c => ({ ...frontends.info(c), permissions: [...c.session.permissions],
1141
1188
  pendingRequests: [...c.session.pendingRequests.values()] })),
1142
1189
  selection: controls?.state.selection, activeSession: controls?.state.activeSession,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { createFrontendClient, type FrontendPermission, type VscodeFrontendConnection } from './client.js';
2
2
  export { readCredentialFile } from './credential.js';
3
+ export type { ChatGPTSignInWorkPhase, ChatGPTSignInOperation } from './chat-setup.js';
3
4
  export { grantedProposals, PROPOSAL_EFFECTS, REQUESTED_PROPOSALS, type ProposalName, } from './proposals.js';
4
5
  export { EventSequenceGapError, LEASE_ERROR_NAMES, RequestNotPendingError, TURN_TERMINAL_KINDS, VscodeFrontendSession, type VscodeFrontendActions, type VscodeRuntimeClient, } from './session.js';
5
6
  export { HISTORY_MARKER_PROMPT, VSCODE_CHAT_LIMITS, acceptedDecisions, diffPaths, fence, opaqueText, projectEvent, projectPendingRequests, projectHistory, projectReplayTurns, requestAnswers, requestNeedsTypedReply, requestSubject, requestSummary, resolutionSentence, resolveParts, toolSubject, typedAnswer, type ChatAnswer, type ChatPart, type ProjectedPart, type ProjectedTurn, type ProjectionContext, } from './view.js';
package/dist/view.d.ts CHANGED
@@ -15,15 +15,10 @@ export declare const VSCODE_CHAT_LIMITS: Readonly<{
15
15
  /**
16
16
  * What one answer button sends back to the runtime.
17
17
  *
18
- * MEASURED: `ChatResponseConfirmationPart` is the wrong mechanism here. The
19
- * editor answers a confirmation by sending a NEW chat request carrying the
20
- * part's data (`chatConfirmationContentPart`), and a supercode approval is
21
- * raised WHILE the turn is still running — `decide_approval` in
22
- * crates/harness/src/server.rs blocks the agent loop until it is answered — so
23
- * the editor refuses that second request and the click does nothing. An
24
- * answer is therefore a COMMAND button: it runs inside the live turn, it is
25
- * stable API (no proposal), and each button carries its own answer instead of
26
- * the one payload a confirmation splits across accepted/rejected.
18
+ * Stock confirmations send a new chat request and cannot answer a running
19
+ * harness turn. Hosts advertising the versioned in-turn approval capability
20
+ * reuse their native confirmation card while dispatching this exact answer.
21
+ * Other hosts retain stable command buttons inside the running turn.
27
22
  */
28
23
  export interface ChatAnswer {
29
24
  connectionId: string;
@@ -67,6 +62,7 @@ export type ChatPart = {
67
62
  resultText?: string;
68
63
  } | {
69
64
  part: 'answers';
65
+ approval: boolean;
70
66
  title: string;
71
67
  message: string;
72
68
  answers: ChatAnswer[];
package/dist/view.js CHANGED
@@ -389,25 +389,9 @@ export function projectEvent(event, context) {
389
389
  const title = request.kind === 'approval'
390
390
  ? 'Approval required'
391
391
  : subject ? `Approve ${subject}` : `Assistant request #${request.id}`;
392
- // Command buttons, and MEASURED rather than assumed. Two richer
393
- // mechanisms were tried against a live runtime in the editor's own Chat
394
- // view and both failed, so neither ships:
395
- // `ChatResponseConfirmationPart` retires its own buttons on click, but
396
- // the click answers by sending a NEW chat request, and a supercode
397
- // approval is raised while the turn still runs (`decide_approval`
398
- // blocks the agent loop) — the editor refuses that request, so the
399
- // click does nothing and the runtime waits forever.
400
- // `stream.questionCarousel` resolves inside the running turn and the
401
- // widget retires itself — the right shape on paper. Observed: the
402
- // widget renders with its options, Submit retires it, and the
403
- // promise never delivers an answer this frontend can read, so the
404
- // runtime is left blocked. A preferred path that silently swallows
405
- // the answer is worse than a plain one that works.
406
- // A command button runs inside the live turn and carries its own answer,
407
- // so the runtime's offered answers remain usable. It cannot be withdrawn
408
- // once pushed (`chatCommandContentPart` enables on `!isStale` and has no
409
- // used-state), so `supercode.frontend.respond` refuses a request that is
410
- // no longer pending and SAYS so instead of sending a stale answer.
392
+ // The host capability decides whether these original answers can use
393
+ // a native in-turn confirmation card. Stock confirmation clicks start a
394
+ // new chat request, so stable command buttons remain the fallback.
411
395
  if (context.canRespond === false || (request.payload && typeof request.payload === 'object' && !Array.isArray(request.payload) && request.payload.observe_only === true)) {
412
396
  return [stable({
413
397
  part: 'markdown',
@@ -416,6 +400,7 @@ export function projectEvent(event, context) {
416
400
  }
417
401
  return [stable({
418
402
  part: 'answers',
403
+ approval: request.kind === 'approval',
419
404
  title: clamp(title, VSCODE_CHAT_LIMITS.titleCharacters),
420
405
  message: summary,
421
406
  answers,
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.33",
4
+ "version": "0.1.35",
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",