@volter/supercode-frontend-vscode 0.1.41 → 0.1.43
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 +9 -2
- package/dist/extension.js +57 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -155,7 +155,8 @@ The permission group offers **Ask for approval** and, on an answerable interacti
|
|
|
155
155
|
A lifecycle host may start the conversations it creates in **Auto approve**: it records
|
|
156
156
|
`defaultPermission: "autoApprove"` on each such conversation and declares `newChatPermission` for the new
|
|
157
157
|
chat's picker (Model Editor does). A conversation without its own recorded default asks, so one that existed
|
|
158
|
-
before a host began doing this keeps its behaviour.
|
|
158
|
+
before a host began doing this keeps its behaviour. A new blank chat does not inherit the previous conversation's permission choice.
|
|
159
|
+
It applies to Claude Code and Codex where the runtime answers approvals
|
|
159
160
|
through Chat, every other harness keeps its own prompts, and a person's explicit pick always
|
|
160
161
|
wins. Only a pick that differs from the default (or replaces an earlier pick) is saved, so the
|
|
161
162
|
default is never stored as a choice (a pick equal to the default follows the default if it later
|
|
@@ -403,7 +404,13 @@ the extension manifest beside those bytes with no build step of its own.
|
|
|
403
404
|
Conversation creation uses `ChatSessionItemController.newChatSessionItemHandler`.
|
|
404
405
|
Permissions use `getChatSessionInputState` and its change/disposal events, with
|
|
405
406
|
per-conversation preferences persisted in workspace state. New untitled chats
|
|
406
|
-
start with
|
|
407
|
+
start with the host's new-chat default and offer modes for the selected draft's harness,
|
|
408
|
+
independently of the previous runtime. The paired native host exposes actual
|
|
409
|
+
`initialSessionOptions` separately from its type-wide previous input state, so an
|
|
410
|
+
explicit draft choice survives materialization. Draft input groups refresh after
|
|
411
|
+
agent selection without overwriting the person's choice. Stock hosts without
|
|
412
|
+
that optional context retain their previous-state contract; old untitled bindings
|
|
413
|
+
are read only for backwards compatibility.
|
|
407
414
|
Restored response cancellation interrupts the runtime and waits for acknowledgement.
|
|
408
415
|
Session-list updates consistently prioritize pending input over busy status.
|
|
409
416
|
|
package/dist/extension.js
CHANGED
|
@@ -410,6 +410,8 @@ export async function activate(context) {
|
|
|
410
410
|
const resourceBindings = new Map(context.workspaceState.get('supercode.sessionBindings', []));
|
|
411
411
|
const boundId = (resource) => resolveSessionBinding(resource, controls?.state.sessions ?? [], resourceBindings);
|
|
412
412
|
let applying = Promise.resolve();
|
|
413
|
+
// the reason the last send from a new chat made no conversation, said by that draft's request handler
|
|
414
|
+
let refusedSend;
|
|
413
415
|
let pendingDraft;
|
|
414
416
|
let focusedResource;
|
|
415
417
|
let focusGeneration = 0;
|
|
@@ -506,7 +508,7 @@ export async function activate(context) {
|
|
|
506
508
|
}));
|
|
507
509
|
}
|
|
508
510
|
const currentOptions = (resource) => {
|
|
509
|
-
const id = resource ? boundId(resource) : controls?.state.activeSession;
|
|
511
|
+
const id = resource ? boundId(resource) : activeDraft() ? undefined : controls?.state.activeSession;
|
|
510
512
|
return { permissions: id ? approvalFor(id) : permission ?? newChatDefault() };
|
|
511
513
|
};
|
|
512
514
|
const savedConnections = new Map();
|
|
@@ -606,8 +608,12 @@ export async function activate(context) {
|
|
|
606
608
|
const applied = pending.then(() => undefined, () => undefined);
|
|
607
609
|
applying = applied;
|
|
608
610
|
const revealed = pending.then(async (target) => {
|
|
611
|
+
// A blank chat the workbench restored keeps the focus, except from a conversation whose turn runs or waits for an
|
|
612
|
+
// answer: after a reload mid-turn, that is the chat a person needs (its Stop button, its pending approval).
|
|
613
|
+
const live = target?.kind === 'managed' && Boolean(frontends.get(target.id)?.session.pendingRequests.size
|
|
614
|
+
|| frontends.get(target.id)?.session.descriptor?.turn_state === 'busy');
|
|
609
615
|
if (!target || !controls || applying !== applied || target.generation !== focusGeneration
|
|
610
|
-
|| focusedResource?.path.startsWith('/untitled-'))
|
|
616
|
+
|| (focusedResource?.path.startsWith('/untitled-') && !live))
|
|
611
617
|
return;
|
|
612
618
|
if (target.kind === 'managed') {
|
|
613
619
|
if ((controls.state.setup && !controls.state.setup.ready) || controls.state.activeSession !== target.id
|
|
@@ -746,11 +752,7 @@ export async function activate(context) {
|
|
|
746
752
|
throw new Error('This host does not expose native conversation creation.');
|
|
747
753
|
// Its callers (the new-chat pickers) swallow the cancellation, so the warning is the one message shown. An error
|
|
748
754
|
// would reach the person as a bare "Canceled" or not at all through the contributed-command boundary.
|
|
749
|
-
|
|
750
|
-
if (refusal) {
|
|
751
|
-
void vscode.window.showWarningMessage(refusal);
|
|
752
|
-
throw new vscode.CancellationError();
|
|
753
|
-
}
|
|
755
|
+
// A turn running elsewhere does not stop a draft: its first send is refused there, in the chat (`create`).
|
|
754
756
|
// Code-OSS materializes this untitled session on first send, awaiting our
|
|
755
757
|
// newChatSessionItemHandler before accepting/clearing the draft. Never boot
|
|
756
758
|
// beside the old widget and reveal another resource after it accepts input.
|
|
@@ -852,11 +854,16 @@ export async function activate(context) {
|
|
|
852
854
|
if (controls?.state.setup && !controls.state.setup.ready)
|
|
853
855
|
return [];
|
|
854
856
|
const id = resource ? boundId(resource) : undefined;
|
|
855
|
-
const active =
|
|
856
|
-
|
|
857
|
+
const active = id ? frontends.get(id) : undefined;
|
|
858
|
+
// A new draft's harness, not the previous runtime, owns its offered modes.
|
|
859
|
+
const harness = resource ? controls?.state.sessions.find(session => session.id === id)?.selection.harness : draftSelection()?.harness;
|
|
860
|
+
const canApprove = resource ? (active ? canAnswer(active) : ['claude-code', 'codex'].includes(harness ?? ''))
|
|
861
|
+
: (harness ? ['claude-code', 'codex'].includes(harness) : newChatDefault() === 'autoApprove' || ['claude-code', 'codex'].includes(controls?.state.selection.harness ?? ''));
|
|
862
|
+
// An unbound input is a new chat, not the active conversation left behind it.
|
|
863
|
+
const selectedPermission = resource ? currentOptions(resource).permissions : permission ?? newChatDefault();
|
|
857
864
|
const permissionItems = [{ id: 'ask', name: canApprove ? 'Ask for approval' : 'Harness permissions', description: canApprove ? 'Show each approval requested by the harness.' : 'Approval controls are unavailable for this harness.' },
|
|
858
865
|
...((canApprove || (!active && resource && currentOptions(resource).permissions === 'autoApprove')) ? [{ id: 'autoApprove', name: 'Auto approve', description: 'Approve the requests the harness would ask you about, including requests to run outside its sandbox. Its deny rules still apply.' }] : [])];
|
|
859
|
-
const groups = [{ id: 'permissions', name: 'Permissions', kind: 'permissions', items: permissionItems, selected: permissionItems.find(i => i.id ===
|
|
866
|
+
const groups = [{ id: 'permissions', name: 'Permissions', kind: 'permissions', items: permissionItems, selected: permissionItems.find(i => i.id === selectedPermission) ?? permissionItems[0] }];
|
|
860
867
|
const selected = controls?.state.sessions.find(session => session.id === id);
|
|
861
868
|
if (selected && ['claude-code', 'codex'].includes(selected.selection.harness)) {
|
|
862
869
|
// Native session-option commands remain reachable for nondelegating providers,
|
|
@@ -881,6 +888,13 @@ export async function activate(context) {
|
|
|
881
888
|
create: async (inputState) => {
|
|
882
889
|
if (!controls)
|
|
883
890
|
throw new Error('The host does not expose conversation creation.');
|
|
891
|
+
// A send while another turn runs makes no conversation: the host then runs the request on the draft itself, and
|
|
892
|
+
// its handler says why in the chat. A rejection here reaches nobody (the host's acceptInput drops it).
|
|
893
|
+
const busy = busyRefusal(managed(), 'starting a new chat');
|
|
894
|
+
if (busy) {
|
|
895
|
+
refusedSend = busy;
|
|
896
|
+
return undefined;
|
|
897
|
+
}
|
|
884
898
|
const draft = activeDraft();
|
|
885
899
|
await draft?.choosing;
|
|
886
900
|
if (draft && activeDraft() !== draft)
|
|
@@ -901,7 +915,8 @@ export async function activate(context) {
|
|
|
901
915
|
applying = pending.then(() => undefined, () => undefined);
|
|
902
916
|
const connection = await pending;
|
|
903
917
|
// The picker shows the host's default until someone changes it; only a change is the person's choice.
|
|
904
|
-
const
|
|
918
|
+
const selected = inputState.groups.find(group => group.id === 'permissions')?.selected?.id;
|
|
919
|
+
const picked = selected === 'ask' || selected === 'autoApprove' ? selected : permission ?? newChatDefault();
|
|
905
920
|
choosePermission(connection.id, picked, permission !== undefined ? null : newChatDefault());
|
|
906
921
|
permission = undefined;
|
|
907
922
|
if (pendingDraft === draft) {
|
|
@@ -1155,6 +1170,12 @@ export async function activate(context) {
|
|
|
1155
1170
|
const handler = async (request, chatContext, stream, token) => {
|
|
1156
1171
|
await applying;
|
|
1157
1172
|
const requestedResource = chatContext.chatSessionContext?.chatSessionItem?.resource;
|
|
1173
|
+
if (refusedSend && requestedResource?.path.startsWith('/untitled-') && !boundId(requestedResource)) {
|
|
1174
|
+
const refusal = refusedSend;
|
|
1175
|
+
refusedSend = undefined;
|
|
1176
|
+
// thrown as is: the workbench writes a handler's error into the chat
|
|
1177
|
+
throw new Error(`${refusal} Nothing was sent; send it again in a new chat once that turn has ended.`);
|
|
1178
|
+
}
|
|
1158
1179
|
if (controls?.state.setup && !controls.state.setup.ready) {
|
|
1159
1180
|
stream.markdown('Sign in before sending a message. Use **New Chat: Choose Agent** to get started.');
|
|
1160
1181
|
return {};
|
|
@@ -1676,19 +1697,30 @@ function registerSessions(context, frontends, participant, granted, handler, his
|
|
|
1676
1697
|
context.subscriptions.push(controller, frontends.onDidChange(refreshItems));
|
|
1677
1698
|
if (controls) {
|
|
1678
1699
|
const states = new Set();
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
const
|
|
1700
|
+
const draftPermissions = new WeakMap();
|
|
1701
|
+
const applyOptions = (groups, options) => groups.map(group => {
|
|
1702
|
+
const value = options.find(option => option.optionId === group.id)?.value;
|
|
1703
|
+
const selected = group.items.find(item => item.id === value);
|
|
1704
|
+
return selected ? { ...group, selected } : group;
|
|
1705
|
+
});
|
|
1706
|
+
controller.getChatSessionInputState = (resource, inputContext) => {
|
|
1707
|
+
const groups = controls.options(resource);
|
|
1708
|
+
// Native request options belong to this draft. previousInputState may instead
|
|
1709
|
+
// be the type-wide last chat, so the paired host exposes them separately.
|
|
1710
|
+
// Stock hosts without that context keep their existing previous-state contract.
|
|
1711
|
+
const initial = inputContext.initialSessionOptions ?? inputContext.previousInputState?.groups.flatMap(group => group.selected ? [{ optionId: group.id, value: group.selected.id }] : []) ?? [];
|
|
1712
|
+
const state = controller.createChatSessionInputState(resource ? groups : applyOptions(groups, initial));
|
|
1713
|
+
const initialPermission = initial.find(option => option.optionId === 'permissions')?.value;
|
|
1714
|
+
if (!resource && initialPermission)
|
|
1715
|
+
draftPermissions.set(state, initialPermission);
|
|
1687
1716
|
states.add(state);
|
|
1688
1717
|
const changed = state.onDidChange(() => {
|
|
1689
1718
|
const target = state.sessionResource ?? resource;
|
|
1719
|
+
const value = state.groups.find(group => group.id === 'permissions')?.selected?.id;
|
|
1690
1720
|
if (target)
|
|
1691
|
-
controls.setPermission(target,
|
|
1721
|
+
controls.setPermission(target, value);
|
|
1722
|
+
else if (value)
|
|
1723
|
+
draftPermissions.set(state, value);
|
|
1692
1724
|
});
|
|
1693
1725
|
const disposed = state.onDidDispose(() => { states.delete(state); changed.dispose(); disposed.dispose(); });
|
|
1694
1726
|
return state;
|
|
@@ -1696,13 +1728,15 @@ function registerSessions(context, frontends, participant, granted, handler, his
|
|
|
1696
1728
|
controller.newChatSessionItemHandler = async ({ inputState }, token) => {
|
|
1697
1729
|
if (token.isCancellationRequested)
|
|
1698
1730
|
throw new vscode.CancellationError();
|
|
1699
|
-
|
|
1731
|
+
const connection = await controls.create(inputState);
|
|
1732
|
+
return connection ? connectedItem(connection) : undefined;
|
|
1700
1733
|
};
|
|
1701
1734
|
context.subscriptions.push(controls.changed(() => {
|
|
1702
1735
|
refreshItems();
|
|
1703
1736
|
for (const state of states) {
|
|
1704
|
-
|
|
1705
|
-
|
|
1737
|
+
const groups = controls.options(state.sessionResource);
|
|
1738
|
+
const value = draftPermissions.get(state);
|
|
1739
|
+
state.groups = state.sessionResource || !value ? groups : applyOptions(groups, [{ optionId: 'permissions', value }]);
|
|
1706
1740
|
}
|
|
1707
1741
|
}));
|
|
1708
1742
|
}
|
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.
|
|
4
|
+
"version": "0.1.43",
|
|
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",
|