@volter/supercode-frontend-vscode 0.1.37 → 0.1.39

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
@@ -160,7 +160,8 @@ default is never stored as a choice (a pick equal to the default follows the def
160
160
  changes), and an Auto approve is never stored for a conversation that cannot answer. Conversations opened before 0.1.36 saved their picker's
161
161
  **Ask** when opened and keep it. Auto approve approves the requests the harness would ask you about, including requests to run
162
162
  outside its sandbox (Codex's approval requests are its sandbox escalations); the harness's deny
163
- rules still apply. Each request it answers is noted in the turn ("auto-approved: …"). Unsupported modes are refused. Unanswerable
163
+ rules still apply. Each request it answers is noted in the turn ("auto-approved: …"). A turn
164
+ re-joined after a page reload (the conversation's active response) is answered the same way. Unsupported modes are refused. Unanswerable
164
165
  harnesses show **Harness permissions**. Previously answered requests cannot affect another
165
166
  connection. Native command-button fallback limitations are documented below.
166
167
 
package/dist/extension.js CHANGED
@@ -238,6 +238,8 @@ function writePart(stream, part, state) {
238
238
  return;
239
239
  }
240
240
  case 'answers':
241
+ if (part.approval && part.answers.length > 0 && state.quiet?.(part.answers[0].requestId))
242
+ return;
241
243
  if (part.approval && nativeInTurnApprovalCards && part.answers.length > 0) {
242
244
  stream.confirmation(part.title, part.message, {
243
245
  kind: 'supercode.inTurnApproval', version: 1, answers: part.answers,
@@ -369,6 +371,7 @@ export async function activate(context) {
369
371
  };
370
372
  // The approval sweeps of each conversation's running turns, so a picker change reaches a turn already running.
371
373
  const runningTurns = new Map();
374
+ const answeredRequests = new Map();
372
375
  // The host's default for a conversation no one has chosen for (Model Editor declares Auto approve).
373
376
  const hostDefault = () => controls?.state.defaultPermission === 'autoApprove' ? 'autoApprove' : 'ask';
374
377
  const canAnswer = (connection) => Boolean(connection.session.descriptor?.actions.respond && connection.session.permissions.has('approve'));
@@ -882,6 +885,7 @@ export async function activate(context) {
882
885
  attached: (connection) => attachedIds.has(connection.id),
883
886
  catalogue: () => controls?.state.sessions ?? [],
884
887
  bindings: boundId,
888
+ watchApprovals: (connection, stream, token) => watchApprovals(connection, stream, token, () => approvalFor(connection.id)),
885
889
  };
886
890
  context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.activateSession', async (resource) => { await connectionFor(vscode.Uri.parse(resource)); }));
887
891
  // The host's native focus event is authoritative even when a cached model
@@ -986,25 +990,30 @@ export async function activate(context) {
986
990
  });
987
991
  }));
988
992
  context.subscriptions.push({ dispose: () => frontends.dispose() });
989
- const runTurnInto = async (connection, prompt, stream, token, approval) => {
990
- if (approval() === 'autoApprove' && !canAnswer(connection)) {
991
- throw new Error('This harness does not support automatic approval through Chat. Select Harness permissions.');
992
- }
993
- const answered = new Set();
994
- const state = { toolInputs: new Map(), toolNames: new Map() };
995
- const projection = {
996
- connectionId: connection.id,
997
- pendingRequest: (requestId) => connection.session.pendingRequests.get(requestId) ?? connection.session.resolvedRequests.get(requestId),
998
- canRespond: connection.session.descriptor?.actions.respond !== false,
993
+ // Auto-answers a conversation's approvals while a response is written into `stream`: read at every event
994
+ // (`sweep`) and on every picker change for that conversation, so switching to Auto approve mid-turn answers
995
+ // the approvals already waiting and the ones after them. Used by a turn this window runs and by a turn it
996
+ // re-joins after a reload (the session's active response), whose original handler is gone.
997
+ const watchApprovals = (connection, stream, token, approval) => {
998
+ // One answered set per conversation, shared by every watch on it, so a re-joined response and a live turn
999
+ // never both answer the same request (the second answer would be refused and noted as a failure).
1000
+ const answered = answeredRequests.get(connection.id) ?? new Set();
1001
+ answeredRequests.set(connection.id, answered);
1002
+ // Requests whose card was not shown because this watch was going to answer them.
1003
+ const unshown = new Set();
1004
+ const answers = (requestId) => {
1005
+ if (approval() !== 'autoApprove' || token.isCancellationRequested || !canAnswer(connection))
1006
+ return false;
1007
+ const pending = connection.session.pendingRequests.get(requestId);
1008
+ // Hidden only when this watch's own sweep will answer it: one another watch claimed keeps its card here.
1009
+ if (!pending || pending.kind !== 'approval' || answered.has(requestId))
1010
+ return false;
1011
+ const decisions = acceptedDecisions(pending);
1012
+ if (decisions && !decisions.includes('allow'))
1013
+ return false;
1014
+ unshown.add(requestId);
1015
+ return true;
999
1016
  };
1000
- const cancellation = token.onCancellationRequested(() => {
1001
- void connection.session.interrupt().catch(() => {
1002
- // The runtime may already be idle; interrupting twice is not an error
1003
- // this view reports.
1004
- });
1005
- });
1006
- // Read at every event and on every picker change, so switching to Auto approve mid-turn
1007
- // answers the approvals already waiting and the ones after them.
1008
1017
  const sweep = () => {
1009
1018
  if (approval() !== 'autoApprove' || token.isCancellationRequested || !canAnswer(connection))
1010
1019
  return;
@@ -1026,26 +1035,65 @@ export async function activate(context) {
1026
1035
  log.info(text.trim());
1027
1036
  } };
1028
1037
  void connection.session.respond({ kind: 'approval', request_id: pending.id, decision: 'allow' }).then(() => {
1038
+ // A card that was never shown needs no note: its resolution line says it was allowed.
1039
+ if (unshown.has(pending.id))
1040
+ return;
1029
1041
  note(`\n_auto-approved: ${opaqueText(named ?? `request #${pending.id}`, 200)}_\n`);
1030
1042
  }, error => {
1031
1043
  note(`\nApproval failed: ${String(error.message || error)}. Use the approval buttons to answer.\n`);
1044
+ // A card this watch hid is shown now: a typed reply cannot reach a running turn, its buttons can.
1045
+ if (unshown.delete(pending.id) && connection.session.pendingRequests.has(pending.id)) {
1046
+ try {
1047
+ writeProjected(stream, projectPendingRequests([pending], {
1048
+ connectionId: connection.id,
1049
+ canRespond: connection.session.descriptor?.actions.respond !== false,
1050
+ }), granted, { toolInputs: new Map(), toolNames: new Map() });
1051
+ }
1052
+ catch {
1053
+ log.info(`Approval card for request #${pending.id} could not be shown: the response has closed`);
1054
+ }
1055
+ }
1032
1056
  });
1033
1057
  }
1034
1058
  };
1035
1059
  const sweeps = runningTurns.get(connection.id) ?? new Set();
1036
1060
  runningTurns.set(connection.id, sweeps.add(sweep));
1061
+ return { sweep, answers, dispose: () => {
1062
+ sweeps.delete(sweep);
1063
+ if (!sweeps.size && runningTurns.get(connection.id) === sweeps) {
1064
+ runningTurns.delete(connection.id);
1065
+ answeredRequests.delete(connection.id);
1066
+ }
1067
+ } };
1068
+ };
1069
+ const runTurnInto = async (connection, prompt, stream, token, approval) => {
1070
+ if (approval() === 'autoApprove' && !canAnswer(connection)) {
1071
+ throw new Error('This harness does not support automatic approval through Chat. Select Harness permissions.');
1072
+ }
1073
+ const state = { toolInputs: new Map(), toolNames: new Map() };
1074
+ const projection = {
1075
+ connectionId: connection.id,
1076
+ pendingRequest: (requestId) => connection.session.pendingRequests.get(requestId) ?? connection.session.resolvedRequests.get(requestId),
1077
+ canRespond: connection.session.descriptor?.actions.respond !== false,
1078
+ };
1079
+ const cancellation = token.onCancellationRequested(() => {
1080
+ void connection.session.interrupt().catch(() => {
1081
+ // The runtime may already be idle; interrupting twice is not an error
1082
+ // this view reports.
1083
+ });
1084
+ });
1085
+ const watch = watchApprovals(connection, stream, token, approval);
1086
+ state.quiet = watch.answers;
1037
1087
  let completed = false;
1038
1088
  try {
1039
1089
  await connection.session.runTurn(prompt, (event) => {
1040
1090
  writeProjected(stream, projectEvent(event, projection), granted, state);
1041
- sweep();
1091
+ watch.sweep();
1042
1092
  });
1043
1093
  completed = true;
1044
1094
  }
1045
1095
  finally {
1046
- sweeps.delete(sweep);
1047
- if (!sweeps.size && runningTurns.get(connection.id) === sweeps)
1048
- runningTurns.delete(connection.id);
1096
+ watch.dispose();
1049
1097
  cancellation.dispose();
1050
1098
  if (controls && !attachedIds.has(connection.id)) {
1051
1099
  if (connection.session.descriptor?.session_id)
@@ -1686,18 +1734,29 @@ function registerSessions(context, frontends, participant, granted, handler, his
1686
1734
  requestHandler: boundHandler,
1687
1735
  activeResponseCallback: busy
1688
1736
  ? async (stream, token) => {
1689
- const state = { toolInputs: new Map(), toolNames: new Map() };
1690
- writeProjected(stream, projectPendingRequests(connection.session.pendingRequests.values(), {
1691
- connectionId: connection.id,
1692
- canRespond: connection.session.descriptor?.actions.respond !== false,
1693
- }), granted, state);
1694
- await observeActiveResponse(connection.session, token, (event) => {
1695
- writeProjected(stream, projectEvent(event, {
1737
+ // A turn re-joined after a reload has lost the handler that ran it, so its approvals are
1738
+ // answered here under the conversation's mode (t_39dac582): Auto approve answers the cards
1739
+ // already waiting and later ones; Ask leaves them for the person, as before.
1740
+ const watch = controls && !controls.attached(connection) ? controls.watchApprovals(connection, stream, token) : undefined;
1741
+ try {
1742
+ const state = { toolInputs: new Map(), toolNames: new Map(), quiet: watch?.answers };
1743
+ writeProjected(stream, projectPendingRequests(connection.session.pendingRequests.values(), {
1696
1744
  connectionId: connection.id,
1697
- pendingRequest: (id) => connection.session.pendingRequests.get(id) ?? connection.session.resolvedRequests.get(id),
1698
1745
  canRespond: connection.session.descriptor?.actions.respond !== false,
1699
1746
  }), granted, state);
1700
- });
1747
+ watch?.sweep();
1748
+ await observeActiveResponse(connection.session, token, (event) => {
1749
+ writeProjected(stream, projectEvent(event, {
1750
+ connectionId: connection.id,
1751
+ pendingRequest: (id) => connection.session.pendingRequests.get(id) ?? connection.session.resolvedRequests.get(id),
1752
+ canRespond: connection.session.descriptor?.actions.respond !== false,
1753
+ }), granted, state);
1754
+ watch?.sweep();
1755
+ });
1756
+ }
1757
+ finally {
1758
+ watch?.dispose();
1759
+ }
1701
1760
  }
1702
1761
  : undefined,
1703
1762
  };
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.37",
4
+ "version": "0.1.39",
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",