@volter/supercode-frontend-vscode 0.1.40 → 0.1.41

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.
Files changed (2) hide show
  1. package/dist/extension.js +55 -29
  2. package/package.json +1 -1
package/dist/extension.js CHANGED
@@ -435,6 +435,16 @@ export async function activate(context) {
435
435
  // reports them, and never taken for the conversation the host manages.
436
436
  const attachedIds = new Set();
437
437
  const managed = () => frontends.all.find((connection) => !attachedIds.has(connection.id));
438
+ // The host runs one conversation at a time, so a running or waiting turn refuses a new or another chat. This is
439
+ // the sentence that refusal says to the person; each caller picks the one place it is shown (see its uses).
440
+ const busyRefusal = (active, action) => {
441
+ if (!active)
442
+ return;
443
+ if (active.session.pendingRequests.size)
444
+ return `The current chat is waiting for your answer. Answer it or cancel the turn before ${action}.`;
445
+ if (active.session.descriptor?.turn_state === 'busy')
446
+ return `A turn is still running in the current chat. Let it finish or cancel it before ${action}.`;
447
+ };
438
448
  // The attached runtime the person is looking at: the Chat names its harness, not the host's.
439
449
  let focusedAttached;
440
450
  let approvalAvailable = false;
@@ -676,9 +686,11 @@ export async function activate(context) {
676
686
  return existing.pending;
677
687
  const pending = applying.then(async () => {
678
688
  const active = managed();
679
- if (active && active.id !== id && (active.session.descriptor?.turn_state === 'busy' || active.session.pendingRequests.size))
680
- throw new Error('Finish or cancel the active turn before opening another conversation.');
681
689
  if (active && active.id !== id) {
690
+ // Thrown as is: the workbench shows a focus command's error, and a send writes it into the chat.
691
+ const refusal = busyRefusal(active, 'opening another conversation');
692
+ if (refusal)
693
+ throw new Error(refusal);
682
694
  // Capture the exact saved transcript while its runtime still belongs
683
695
  // to the host. Subsequent background content reads need no activation.
684
696
  await saveManagedHistory(active);
@@ -716,8 +728,12 @@ export async function activate(context) {
716
728
  if (!controls)
717
729
  throw new Error('This host does not expose harness or model configuration.');
718
730
  const active = managed();
719
- if (active?.session.descriptor?.turn_state === 'busy' || active?.session.pendingRequests.size)
720
- throw new Error('Finish or cancel the current turn before starting a new chat.');
731
+ // A send from a blank chat lands here, and the workbench does not show its error: say it, then refuse.
732
+ const refusal = busyRefusal(active, 'starting a new chat');
733
+ if (refusal) {
734
+ void vscode.window.showWarningMessage(refusal);
735
+ throw new Error(refusal);
736
+ }
721
737
  if (active)
722
738
  await saveManagedHistory(active);
723
739
  const state = await controls.select(selection);
@@ -728,9 +744,13 @@ export async function activate(context) {
728
744
  const openNewChat = async () => {
729
745
  if (!controls || !granted.has('chatSessionsProvider'))
730
746
  throw new Error('This host does not expose native conversation creation.');
731
- const active = managed();
732
- if (active?.session.descriptor?.turn_state === 'busy' || active?.session.pendingRequests.size)
733
- throw new Error('Finish or cancel the current turn before starting a new chat.');
747
+ // Its callers (the new-chat pickers) swallow the cancellation, so the warning is the one message shown. An error
748
+ // would reach the person as a bare "Canceled" or not at all through the contributed-command boundary.
749
+ const refusal = busyRefusal(managed(), 'starting a new chat');
750
+ if (refusal) {
751
+ void vscode.window.showWarningMessage(refusal);
752
+ throw new vscode.CancellationError();
753
+ }
734
754
  // Code-OSS materializes this untitled session on first send, awaiting our
735
755
  // newChatSessionItemHandler before accepting/clearing the draft. Never boot
736
756
  // beside the old widget and reveal another resource after it accepts input.
@@ -791,20 +811,23 @@ export async function activate(context) {
791
811
  const chooseNewChat = async (pick) => {
792
812
  if (pendingDraft?.choosing)
793
813
  throw new Error('Finish choosing an agent before starting another chat.');
794
- // Open before the picker: a send can beat its result across the extension-
795
- // host boundary. Materialization awaits this exact draft's choice too.
796
- const draft = await openNewChat();
797
- const picking = (async () => {
798
- const choice = await pick();
799
- if (activeDraft() !== draft)
800
- throw new vscode.CancellationError();
801
- draft.choice = choice;
802
- controlsChanged.fire();
803
- if (choice.kind !== 'agent')
804
- throw new vscode.CancellationError();
805
- })();
806
- draft.choosing = picking;
814
+ let draft;
815
+ let picking;
807
816
  try {
817
+ // Open before the picker: a send can beat its result across the extension-
818
+ // host boundary. Materialization awaits this exact draft's choice too.
819
+ // Inside the try: a busy refusal is a cancellation already explained.
820
+ const opened = draft = await openNewChat();
821
+ picking = (async () => {
822
+ const choice = await pick();
823
+ if (activeDraft() !== opened)
824
+ throw new vscode.CancellationError();
825
+ opened.choice = choice;
826
+ controlsChanged.fire();
827
+ if (choice.kind !== 'agent')
828
+ throw new vscode.CancellationError();
829
+ })();
830
+ opened.choosing = picking;
808
831
  await picking;
809
832
  }
810
833
  catch (error) {
@@ -812,9 +835,9 @@ export async function activate(context) {
812
835
  throw error;
813
836
  }
814
837
  finally {
815
- if (draft.choosing === picking)
838
+ if (draft && draft.choosing === picking)
816
839
  draft.choosing = undefined;
817
- if (draft.choice?.kind === 'signIn') {
840
+ if (draft?.choice?.kind === 'signIn') {
818
841
  void refreshSetup().catch(error => log.warn(`Could not refresh sign-in: ${String(error)}`)).finally(pollSetup);
819
842
  }
820
843
  }
@@ -967,17 +990,20 @@ export async function activate(context) {
967
990
  return { kind: 'agent', selection: { harness: harness.id, model: '', effort: '' } };
968
991
  });
969
992
  }));
970
- context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.configureModel', async (resource, sessionId) => {
993
+ context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.configureModel', async (resource, _sessionId) => {
971
994
  if (!controls || focusedAttached)
972
995
  throw new Error('Model settings require a host-managed conversation.');
973
996
  await controls.refresh();
974
- // The native session-options picker prepends its exact resource to command arguments.
975
- const bound = resource ? boundId(resource) : undefined;
976
- if (resource && (!bound || (sessionId && bound !== sessionId)))
977
- throw new Error('Model settings have no matching conversation binding.');
997
+ // The native session-options picker prepends its widget's resource to command arguments, so the resource is the
998
+ // conversation the person clicked from. `sessionId` is not used: the workbench keeps one option-group list per
999
+ // session type, last written by whichever input state updated, so any chat's dropdown can carry another
1000
+ // conversation's id. A resource with no binding (a blank draft) is no conversation; only the command palette,
1001
+ // which passes no resource, falls back to the active one. Either way the choice opens a new chat.
1002
+ const bound = resource ? boundId(resource) : controls.state.activeSession ?? undefined;
978
1003
  // The conversation it was opened from sets the agent and the current values; with none open (a fresh project,
979
- // the command palette) the person picks the agent first. Either way the choice opens a new chat.
980
- const selected = controls.state.sessions.find(session => session.id === (bound ?? controls.state.activeSession));
1004
+ // the command palette) the person picks the agent first. A running turn refuses the new chat before any picker
1005
+ // opens, and says so (`openNewChat`).
1006
+ const selected = controls.state.sessions.find(session => session.id === bound);
981
1007
  // An agent that still needs sign-in is set up from the agent picker first, as there.
982
1008
  const needsSignIn = new Set((controls.state.setup?.actions ?? []).filter(action => action.kind === 'login').map(action => action.harness));
983
1009
  const configurable = controls.state.harnesses.filter(harness => CONFIGURABLE_HARNESSES.includes(harness.id) && !needsSignIn.has(harness.id));
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.40",
4
+ "version": "0.1.41",
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",