@volter/supercode-frontend-vscode 0.1.36 → 0.1.37

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
@@ -94,9 +94,11 @@ which startable agent to use. It refreshes the host's available choices first;
94
94
  with one ready local agent and no pending native sign-in choices it opens
95
95
  immediately, even when the official ChatGPT choice is available. Otherwise the
96
96
  picker includes the official choice on supported hosts; with no choices it returns
97
- to setup. Model
98
- and reasoning are sent as empty values, leaving both at the harness's own defaults,
99
- even if the previous conversation used explicit settings. Cancelling the agent
97
+ to setup. Picking an agent sends model
98
+ and reasoning as empty values, leaving both at the harness's own defaults,
99
+ even if the previous conversation used explicit settings. Each agent whose model
100
+ and reasoning a chat can set (Claude Code, Codex) is also listed in the picker as **<agent>: model
101
+ and reasoning…**, which goes on to the model and effort choices below. Cancelling the agent
100
102
  picker starts no backend conversation.
101
103
 
102
104
  The native blank chat opens before the picker appears. First send waits for the
@@ -116,8 +118,14 @@ sign-in choice it uses that agent once ready, otherwise it keeps the draft with
116
118
  a visible sign-in error.
117
119
 
118
120
  The composer's native **Model settings** control offers **Choose model and reasoning
119
- for a new chat…** for Claude Code and Codex. This optional path retains the model
120
- list, exact model ID entry and reasoning choices. Only completing the choices
121
+ for a new chat…** for Claude Code and Codex, and **Chat: Model and Reasoning: Open New
122
+ Chat** does the same from anywhere (with no conversation open it asks for the agent
123
+ first). The models and each model's reasoning efforts are the host's
124
+ `modelsByHarness` choices, which a host reads from the harness itself (Model Editor:
125
+ Claude Code's `--help`, Codex's models cache), so levels such as `xhigh` and `max`
126
+ appear as the harness offers them; a host that sends no efforts gets low/medium/high.
127
+ Exact model ID entry remains. When a turn ends, its reply notes the model it ran on
128
+ (as the harness's replies name it) and the effort it was given. Only completing the choices
121
129
  configures the new blank chat; its backend starts on first send. Cancelling a
122
130
  choice starts no backend and leaves the blank chat available.
123
131
  A conversation's harness, model and reasoning remain fixed: this integration does
package/dist/extension.js CHANGED
@@ -11,6 +11,8 @@ import { observeActiveResponse, sessionStatus } from './native-session.js';
11
11
  import { RequestNotPendingError, VscodeFrontendSession } from './session.js';
12
12
  import { acceptedDecisions, opaqueText, projectEvent, projectPendingRequests, projectHistory, projectReplayTurns, resolveParts, resolutionSentence, typedAnswer, } from './view.js';
13
13
  const SESSION_SCHEME = 'supercode';
14
+ /** The harnesses whose model and reasoning effort a new chat can set (the host passes them to the CLI). */
15
+ const CONFIGURABLE_HARNESSES = ['claude-code', 'codex'];
14
16
  const PARTICIPANT_ID = 'supercode.chat';
15
17
  const DEFAULT_PARTICIPANT_ID = 'supercode.chat.default';
16
18
  /** `package.json` gates the `isDefault` participant on this key. */
@@ -739,6 +741,44 @@ export async function activate(context) {
739
741
  throw new vscode.CancellationError();
740
742
  return draft;
741
743
  };
744
+ // A new chat's model and reasoning effort, offered from what the harness itself lists (the host reads its models
745
+ // and each model's efforts from the harness). Escape cancels; "Harness default" passes nothing, so the harness's
746
+ // own settings decide. A host that sends no efforts gets the levels every configurable harness takes.
747
+ // A single pick that opens on the current value (showQuickPick's `picked` applies only to multi-select).
748
+ const pickWithCurrent = (items, title, placeholder, currentId) => {
749
+ const quick = vscode.window.createQuickPick();
750
+ quick.title = title;
751
+ quick.placeholder = placeholder;
752
+ quick.items = items;
753
+ quick.activeItems = items.filter(item => item.id === currentId);
754
+ return new Promise(resolve => {
755
+ quick.onDidAccept(() => resolve(quick.selectedItems[0]));
756
+ quick.onDidHide(() => resolve(undefined));
757
+ quick.show();
758
+ }).finally(() => quick.dispose());
759
+ };
760
+ const pickModelAndEffort = async (harness, current) => {
761
+ const choices = controls?.state.modelsByHarness?.[harness] ?? [{ id: '', name: 'Harness default' }];
762
+ const name = harnessDisplayName(harness);
763
+ const items = [...choices.map(model => ({ label: model.name, description: [model.id, model.description].filter(Boolean).join(' · ') || undefined, id: model.id, efforts: model.efforts })),
764
+ { label: 'Enter an exact model ID…', description: undefined, id: '__custom__', efforts: choices.find(model => model.id === '')?.efforts }];
765
+ const picked = await pickWithCurrent(items, `New chat · ${name} model`, 'Model availability depends on your harness account.', current?.model ?? '');
766
+ if (!picked)
767
+ return;
768
+ let model = picked.id;
769
+ if (picked.id === '__custom__') {
770
+ const typed = await vscode.window.showInputBox({ title: `New chat · ${name} exact model`, prompt: 'Model ID or CLI alias supported by this harness account.', value: current?.model });
771
+ if (typed === undefined)
772
+ return;
773
+ model = typed.trim();
774
+ }
775
+ const levels = picked.efforts ?? ['low', 'medium', 'high'];
776
+ const efforts = [{ label: 'Harness default', id: '' }, ...levels.map(id => ({ label: id, id }))];
777
+ const effort = levels.length === 0 ? efforts[0] : await pickWithCurrent(efforts, `New chat · ${name} reasoning effort`, model ? `For ${model}` : 'For the harness default model', current?.effort ?? '');
778
+ if (!effort)
779
+ return;
780
+ return { model, effort: effort.id };
781
+ };
742
782
  const chooseNewChat = async (pick) => {
743
783
  if (pendingDraft?.choosing)
744
784
  throw new Error('Finish choosing an agent before starting another chat.');
@@ -880,13 +920,17 @@ export async function activate(context) {
880
920
  // Sign-in choices stay visible alongside agents that can be explicitly started.
881
921
  const login = controls.state.setup?.actions.filter(action => action.kind === 'login') ?? [];
882
922
  const localChoices = [
883
- ...login.map(action => ({ label: action.name, id: action.harness, description: 'Sign in…', login: true, autoStart: false })),
923
+ ...login.map(action => ({ label: action.name, id: action.harness, description: 'Sign in…', login: true, autoStart: false, configure: false })),
884
924
  ...controls.state.harnesses.filter(h => !login.some(action => action.harness === h.id))
885
- .map(h => ({ label: h.name, id: h.id, description: h.description, login: false, autoStart: h.autoStart === true })),
925
+ .map(h => ({ label: h.name, id: h.id, description: h.description, login: false, autoStart: h.autoStart === true, configure: false })),
886
926
  ];
927
+ // Each agent whose model and reasoning a chat can set also offers that, right where the agent is chosen.
928
+ const modelChoices = localChoices.filter(choice => !choice.login && CONFIGURABLE_HARNESSES.includes(choice.id))
929
+ .map(choice => ({ ...choice, label: `${choice.label}: model and reasoning…`, description: 'Choose the model and reasoning effort', autoStart: false, configure: true }));
887
930
  const choices = [
888
- ...(offerChatGPT ? [{ label: 'Sign in with ChatGPT', id: CHATGPT_SIGN_IN, description: 'Official Codex extension', login: false, autoStart: false }] : []),
931
+ ...(offerChatGPT ? [{ label: 'Sign in with ChatGPT', id: CHATGPT_SIGN_IN, description: 'Official Codex extension', login: false, autoStart: false, configure: false }] : []),
889
932
  ...localChoices,
933
+ ...modelChoices,
890
934
  ];
891
935
  if (!choices.length) {
892
936
  await revealSetup();
@@ -895,9 +939,13 @@ export async function activate(context) {
895
939
  // A sole sign-in option still requires a deliberate click before opening its terminal.
896
940
  // The official choice must not disable the existing sole-local-agent shortcut.
897
941
  await chooseNewChat(async () => {
898
- const harness = localChoices.length === 1 && localChoices[0].autoStart ? localChoices[0] : await vscode.window.showQuickPick(choices, { title: 'New chat · Agent', placeHolder: 'Choose your coding agent. Model and reasoning use its defaults.' });
942
+ const harness = localChoices.length === 1 && localChoices[0].autoStart ? localChoices[0] : await vscode.window.showQuickPick(choices, { title: 'New chat · Agent', placeHolder: 'Choose your coding agent. Its default model and reasoning apply unless you choose them.' });
899
943
  if (!harness)
900
944
  return { kind: 'cancelled' };
945
+ if (harness.configure) {
946
+ const chosen = await pickModelAndEffort(harness.id);
947
+ return chosen ? { kind: 'agent', selection: { harness: harness.id, ...chosen } } : { kind: 'cancelled' };
948
+ }
901
949
  if (harness.id === CHATGPT_SIGN_IN) {
902
950
  await vscode.commands.executeCommand(CHATGPT_SIGN_IN);
903
951
  return { kind: 'signIn' };
@@ -917,31 +965,24 @@ export async function activate(context) {
917
965
  const bound = resource ? boundId(resource) : undefined;
918
966
  if (resource && (!bound || (sessionId && bound !== sessionId)))
919
967
  throw new Error('Model settings have no matching conversation binding.');
920
- const id = bound ?? controls.state.activeSession;
921
- const selected = controls.state.sessions.find(session => session.id === id);
922
- if (!selected || selected.id !== controls.state.activeSession)
923
- throw new Error('Open this conversation before choosing model settings.');
924
- const selection = { ...selected.selection };
925
- if (!['claude-code', 'codex'].includes(selection.harness))
926
- throw new Error('This harness uses its own model defaults; configuration is not advertised.');
968
+ // The conversation it was opened from sets the agent and the current values; with none open (a fresh project,
969
+ // the command palette) the person picks the agent first. Either way the choice opens a new chat.
970
+ const selected = controls.state.sessions.find(session => session.id === (bound ?? controls.state.activeSession));
971
+ // An agent that still needs sign-in is set up from the agent picker first, as there.
972
+ const needsSignIn = new Set((controls.state.setup?.actions ?? []).filter(action => action.kind === 'login').map(action => action.harness));
973
+ const configurable = controls.state.harnesses.filter(harness => CONFIGURABLE_HARNESSES.includes(harness.id) && !needsSignIn.has(harness.id));
974
+ if (!configurable.length && !(selected && CONFIGURABLE_HARNESSES.includes(selected.selection.harness)))
975
+ throw new Error('No agent on this machine takes a model or reasoning choice here; its own defaults apply.');
927
976
  await chooseNewChat(async () => {
928
- const choices = controls.state.modelsByHarness?.[selection.harness] ?? [{ id: '', name: 'Harness default' }];
929
- const picked = await vscode.window.showQuickPick([...choices.map(model => ({ label: model.name, id: model.id })), { label: 'Enter an exact model ID…', id: '__custom__' }], { title: 'New chat · Model', placeHolder: 'Model availability depends on your harness account.' });
930
- if (!picked)
931
- return { kind: 'cancelled' };
932
- if (picked.id === '__custom__') {
933
- const model = await vscode.window.showInputBox({ title: 'New chat · Exact model', prompt: 'Model ID or CLI alias supported by this harness account.', value: selection.model });
934
- if (model === undefined)
977
+ let harness = selected && CONFIGURABLE_HARNESSES.includes(selected.selection.harness) ? selected.selection.harness : undefined;
978
+ if (!harness) {
979
+ const agent = configurable.length === 1 ? configurable[0] : await vscode.window.showQuickPick(configurable.map(choice => ({ ...choice, label: choice.name })), { title: 'New chat · Agent', placeHolder: 'Choose the agent whose model and reasoning to set.' });
980
+ if (!agent)
935
981
  return { kind: 'cancelled' };
936
- selection.model = model.trim();
982
+ harness = agent.id;
937
983
  }
938
- else
939
- selection.model = picked.id;
940
- const effort = await vscode.window.showQuickPick([{ label: 'Harness default', id: '' }, ...['low', 'medium', 'high'].map(id => ({ label: id, id }))], { title: 'New chat · Reasoning effort' });
941
- if (!effort)
942
- return { kind: 'cancelled' };
943
- selection.effort = effort.id;
944
- return { kind: 'agent', selection };
984
+ const chosen = await pickModelAndEffort(harness, selected?.selection.harness === harness ? selected.selection : undefined);
985
+ return chosen ? { kind: 'agent', selection: { harness, ...chosen } } : { kind: 'cancelled' };
945
986
  });
946
987
  }));
947
988
  context.subscriptions.push({ dispose: () => frontends.dispose() });
@@ -977,20 +1018,29 @@ export async function activate(context) {
977
1018
  // Its card is already in the stream (the event that raised it was written first), so say it was answered.
978
1019
  const payload = pending.payload && typeof pending.payload === 'object' && !Array.isArray(pending.payload) ? pending.payload : {};
979
1020
  const named = [payload.title, payload.tool, payload.tool_name].find((value) => typeof value === 'string');
1021
+ // The answer can settle after the turn's response has closed, when writing to it throws: the note is then dropped.
1022
+ const note = (text) => { try {
1023
+ stream.markdown(text);
1024
+ }
1025
+ catch {
1026
+ log.info(text.trim());
1027
+ } };
980
1028
  void connection.session.respond({ kind: 'approval', request_id: pending.id, decision: 'allow' }).then(() => {
981
- stream.markdown(`\n_auto-approved: ${opaqueText(named ?? `request #${pending.id}`, 200)}_\n`);
1029
+ note(`\n_auto-approved: ${opaqueText(named ?? `request #${pending.id}`, 200)}_\n`);
982
1030
  }, error => {
983
- stream.markdown(`\nApproval failed: ${String(error.message || error)}. Use the approval buttons to answer.\n`);
1031
+ note(`\nApproval failed: ${String(error.message || error)}. Use the approval buttons to answer.\n`);
984
1032
  });
985
1033
  }
986
1034
  };
987
1035
  const sweeps = runningTurns.get(connection.id) ?? new Set();
988
1036
  runningTurns.set(connection.id, sweeps.add(sweep));
1037
+ let completed = false;
989
1038
  try {
990
1039
  await connection.session.runTurn(prompt, (event) => {
991
1040
  writeProjected(stream, projectEvent(event, projection), granted, state);
992
1041
  sweep();
993
1042
  });
1043
+ completed = true;
994
1044
  }
995
1045
  finally {
996
1046
  sweeps.delete(sweep);
@@ -1003,6 +1053,12 @@ export async function activate(context) {
1003
1053
  await controls.refresh().catch(error => log.warn(`Could not refresh chat identity: ${String(error)}`));
1004
1054
  controlsChanged.fire();
1005
1055
  }
1056
+ // The model the turn ran on: what the harness's own replies name (the host watches them), else what the
1057
+ // runtime reports; and the effort it was given.
1058
+ const ran = (controls?.state.activeSession === connection.id ? controls.state.actualModel : undefined) || connection.session.descriptor?.model;
1059
+ const effort = controls?.state.sessions.find(session => session.id === connection.id)?.selection.effort;
1060
+ if (completed && ran && !token.isCancellationRequested)
1061
+ stream.markdown(`\n\n_Ran on ${opaqueText(ran, 120)}${effort ? ` · ${opaqueText(effort, 24)} effort` : ''}_`);
1006
1062
  }
1007
1063
  };
1008
1064
  const handler = async (request, chatContext, stream, token) => {
@@ -5,11 +5,13 @@ export interface ChatSelection {
5
5
  model: string;
6
6
  effort: string;
7
7
  }
8
+ /** `efforts`: the reasoning efforts a model takes, as the host read them from the harness (absent from older hosts). */
8
9
  export interface ChatChoice {
9
10
  id: string;
10
11
  name: string;
11
12
  description?: string;
12
13
  autoStart?: boolean;
14
+ efforts?: string[];
13
15
  }
14
16
  export interface ManagedChatSession {
15
17
  id: string;
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.36",
4
+ "version": "0.1.37",
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",