@volter/supercode-frontend-vscode 0.1.38 → 0.1.40
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 +4 -2
- package/dist/extension.js +63 -13
- package/dist/host-controls.d.ts +5 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -152,8 +152,10 @@ owns no processes.
|
|
|
152
152
|
The permission group offers **Ask for approval** and, on an answerable interactive attachment,
|
|
153
153
|
**Auto approve**. Policies are scoped by conversation and read throughout the turn: switching to
|
|
154
154
|
**Auto approve** while a turn runs answers the approvals already waiting and every later one.
|
|
155
|
-
A lifecycle host may
|
|
156
|
-
|
|
155
|
+
A lifecycle host may start the conversations it creates in **Auto approve**: it records
|
|
156
|
+
`defaultPermission: "autoApprove"` on each such conversation and declares `newChatPermission` for the new
|
|
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. It applies to Claude Code and Codex where the runtime answers approvals
|
|
157
159
|
through Chat, every other harness keeps its own prompts, and a person's explicit pick always
|
|
158
160
|
wins. Only a pick that differs from the default (or replaces an earlier pick) is saved, so the
|
|
159
161
|
default is never stored as a choice (a pick equal to the default follows the default if it later
|
package/dist/extension.js
CHANGED
|
@@ -13,6 +13,8 @@ import { acceptedDecisions, opaqueText, projectEvent, projectPendingRequests, pr
|
|
|
13
13
|
const SESSION_SCHEME = 'supercode';
|
|
14
14
|
/** The harnesses whose model and reasoning effort a new chat can set (the host passes them to the CLI). */
|
|
15
15
|
const CONFIGURABLE_HARNESSES = ['claude-code', 'codex'];
|
|
16
|
+
/** How long an automatic approval may go unanswered before its card is shown for the person to answer. */
|
|
17
|
+
const AUTO_ANSWER_SETTLE_MS = 15_000;
|
|
16
18
|
const PARTICIPANT_ID = 'supercode.chat';
|
|
17
19
|
const DEFAULT_PARTICIPANT_ID = 'supercode.chat.default';
|
|
18
20
|
/** `package.json` gates the `isDefault` participant on this key. */
|
|
@@ -238,6 +240,8 @@ function writePart(stream, part, state) {
|
|
|
238
240
|
return;
|
|
239
241
|
}
|
|
240
242
|
case 'answers':
|
|
243
|
+
if (part.approval && part.answers.length > 0 && state.quiet?.(part.answers[0].requestId))
|
|
244
|
+
return;
|
|
241
245
|
if (part.approval && nativeInTurnApprovalCards && part.answers.length > 0) {
|
|
242
246
|
stream.confirmation(part.title, part.message, {
|
|
243
247
|
kind: 'supercode.inTurnApproval', version: 1, answers: part.answers,
|
|
@@ -370,18 +374,22 @@ export async function activate(context) {
|
|
|
370
374
|
// The approval sweeps of each conversation's running turns, so a picker change reaches a turn already running.
|
|
371
375
|
const runningTurns = new Map();
|
|
372
376
|
const answeredRequests = new Map();
|
|
373
|
-
// The
|
|
374
|
-
|
|
377
|
+
// The mode a NEW chat's picker shows (`HostState.newChatPermission`; Model Editor declares Auto approve). It is
|
|
378
|
+
// never applied to a conversation that exists: that reads its own recorded default below.
|
|
379
|
+
const newChatDefault = () => controls?.state.newChatPermission === 'autoApprove' ? 'autoApprove' : 'ask';
|
|
375
380
|
const canAnswer = (connection) => Boolean(connection.session.descriptor?.actions.respond && connection.session.permissions.has('approve'));
|
|
376
|
-
// The
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
//
|
|
381
|
+
// The default of one conversation: the one the host recorded for THAT conversation when it created it
|
|
382
|
+
// (`ManagedChatSession.defaultPermission`), so a conversation that existed before a host began starting chats
|
|
383
|
+
// in Auto approve keeps asking. Auto approve only for a conversation the host manages and only where this Chat
|
|
384
|
+
// can answer the runtime's approvals, read from its live connection, or, while it is not connected, from its
|
|
385
|
+
// harness (hosted Claude Code and Codex answer through Chat). A runtime attached by address and every other
|
|
386
|
+
// harness keep their own prompts.
|
|
380
387
|
const defaultFor = (id) => {
|
|
381
|
-
|
|
388
|
+
const session = controls?.state.sessions.find(entry => entry.id === id);
|
|
389
|
+
if (session?.defaultPermission !== 'autoApprove' || attachedIds.has(id))
|
|
382
390
|
return 'ask';
|
|
383
391
|
const connection = frontends.get(id);
|
|
384
|
-
const harness =
|
|
392
|
+
const harness = session.selection.harness;
|
|
385
393
|
// Only the harnesses whose approvals this default was decided for; a runtime that answers through another
|
|
386
394
|
// protocol (an ACP agent) keeps asking until a host names it.
|
|
387
395
|
return ['claude-code', 'codex'].includes(harness) && (!connection || canAnswer(connection)) ? 'autoApprove' : 'ask';
|
|
@@ -489,7 +497,7 @@ export async function activate(context) {
|
|
|
489
497
|
}
|
|
490
498
|
const currentOptions = (resource) => {
|
|
491
499
|
const id = resource ? boundId(resource) : controls?.state.activeSession;
|
|
492
|
-
return { permissions: id ? approvalFor(id) : permission ??
|
|
500
|
+
return { permissions: id ? approvalFor(id) : permission ?? newChatDefault() };
|
|
493
501
|
};
|
|
494
502
|
const savedConnections = new Map();
|
|
495
503
|
const saveManagedHistory = async (connection) => {
|
|
@@ -871,7 +879,7 @@ export async function activate(context) {
|
|
|
871
879
|
const connection = await pending;
|
|
872
880
|
// The picker shows the host's default until someone changes it; only a change is the person's choice.
|
|
873
881
|
const picked = turnPermission(inputState.groups.find(group => group.id === 'permissions')?.selected?.id);
|
|
874
|
-
choosePermission(connection.id, picked, permission !== undefined ? null :
|
|
882
|
+
choosePermission(connection.id, picked, permission !== undefined ? null : newChatDefault());
|
|
875
883
|
permission = undefined;
|
|
876
884
|
if (pendingDraft === draft) {
|
|
877
885
|
pendingDraft = undefined;
|
|
@@ -997,6 +1005,21 @@ export async function activate(context) {
|
|
|
997
1005
|
// never both answer the same request (the second answer would be refused and noted as a failure).
|
|
998
1006
|
const answered = answeredRequests.get(connection.id) ?? new Set();
|
|
999
1007
|
answeredRequests.set(connection.id, answered);
|
|
1008
|
+
// Requests whose card was not shown because this watch was going to answer them.
|
|
1009
|
+
const unshown = new Set();
|
|
1010
|
+
const answers = (requestId) => {
|
|
1011
|
+
if (approval() !== 'autoApprove' || token.isCancellationRequested || !canAnswer(connection))
|
|
1012
|
+
return false;
|
|
1013
|
+
const pending = connection.session.pendingRequests.get(requestId);
|
|
1014
|
+
// Hidden only when this watch's own sweep will answer it: one another watch claimed keeps its card here.
|
|
1015
|
+
if (!pending || pending.kind !== 'approval' || answered.has(requestId))
|
|
1016
|
+
return false;
|
|
1017
|
+
const decisions = acceptedDecisions(pending);
|
|
1018
|
+
if (decisions && !decisions.includes('allow'))
|
|
1019
|
+
return false;
|
|
1020
|
+
unshown.add(requestId);
|
|
1021
|
+
return true;
|
|
1022
|
+
};
|
|
1000
1023
|
const sweep = () => {
|
|
1001
1024
|
if (approval() !== 'autoApprove' || token.isCancellationRequested || !canAnswer(connection))
|
|
1002
1025
|
return;
|
|
@@ -1017,16 +1040,42 @@ export async function activate(context) {
|
|
|
1017
1040
|
catch {
|
|
1018
1041
|
log.info(text.trim());
|
|
1019
1042
|
} };
|
|
1043
|
+
const label = opaqueText(named ?? `request #${pending.id}`, 200);
|
|
1044
|
+
// A card this watch hid is shown when its automatic answer fails or does not settle: a typed reply cannot
|
|
1045
|
+
// reach a running turn, its buttons can. With the response closed, the person is told an approval waits.
|
|
1046
|
+
const showCard = () => {
|
|
1047
|
+
if (!unshown.delete(pending.id) || !connection.session.pendingRequests.has(pending.id))
|
|
1048
|
+
return;
|
|
1049
|
+
try {
|
|
1050
|
+
writeProjected(stream, projectPendingRequests([pending], {
|
|
1051
|
+
connectionId: connection.id,
|
|
1052
|
+
canRespond: connection.session.descriptor?.actions.respond !== false,
|
|
1053
|
+
}), granted, { toolInputs: new Map(), toolNames: new Map() });
|
|
1054
|
+
}
|
|
1055
|
+
catch {
|
|
1056
|
+
void vscode.window.showWarningMessage(`${connection.label}: ${label} is waiting for approval. Open its chat to answer it.`);
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
const unsettled = setTimeout(() => {
|
|
1060
|
+
note(`\nAuto approve has had no answer for ${label} for ${AUTO_ANSWER_SETTLE_MS / 1000} s. Use the approval buttons to answer.\n`);
|
|
1061
|
+
showCard();
|
|
1062
|
+
}, AUTO_ANSWER_SETTLE_MS);
|
|
1020
1063
|
void connection.session.respond({ kind: 'approval', request_id: pending.id, decision: 'allow' }).then(() => {
|
|
1021
|
-
|
|
1064
|
+
clearTimeout(unsettled);
|
|
1065
|
+
// A card that was never shown needs no note: its resolution line says it was allowed.
|
|
1066
|
+
if (unshown.has(pending.id))
|
|
1067
|
+
return;
|
|
1068
|
+
note(`\n_auto-approved: ${label}_\n`);
|
|
1022
1069
|
}, error => {
|
|
1070
|
+
clearTimeout(unsettled);
|
|
1023
1071
|
note(`\nApproval failed: ${String(error.message || error)}. Use the approval buttons to answer.\n`);
|
|
1072
|
+
showCard();
|
|
1024
1073
|
});
|
|
1025
1074
|
}
|
|
1026
1075
|
};
|
|
1027
1076
|
const sweeps = runningTurns.get(connection.id) ?? new Set();
|
|
1028
1077
|
runningTurns.set(connection.id, sweeps.add(sweep));
|
|
1029
|
-
return { sweep, dispose: () => {
|
|
1078
|
+
return { sweep, answers, dispose: () => {
|
|
1030
1079
|
sweeps.delete(sweep);
|
|
1031
1080
|
if (!sweeps.size && runningTurns.get(connection.id) === sweeps) {
|
|
1032
1081
|
runningTurns.delete(connection.id);
|
|
@@ -1051,6 +1100,7 @@ export async function activate(context) {
|
|
|
1051
1100
|
});
|
|
1052
1101
|
});
|
|
1053
1102
|
const watch = watchApprovals(connection, stream, token, approval);
|
|
1103
|
+
state.quiet = watch.answers;
|
|
1054
1104
|
let completed = false;
|
|
1055
1105
|
try {
|
|
1056
1106
|
await connection.session.runTurn(prompt, (event) => {
|
|
@@ -1706,7 +1756,7 @@ function registerSessions(context, frontends, participant, granted, handler, his
|
|
|
1706
1756
|
// already waiting and later ones; Ask leaves them for the person, as before.
|
|
1707
1757
|
const watch = controls && !controls.attached(connection) ? controls.watchApprovals(connection, stream, token) : undefined;
|
|
1708
1758
|
try {
|
|
1709
|
-
const state = { toolInputs: new Map(), toolNames: new Map() };
|
|
1759
|
+
const state = { toolInputs: new Map(), toolNames: new Map(), quiet: watch?.answers };
|
|
1710
1760
|
writeProjected(stream, projectPendingRequests(connection.session.pendingRequests.values(), {
|
|
1711
1761
|
connectionId: connection.id,
|
|
1712
1762
|
canRespond: connection.session.descriptor?.actions.respond !== false,
|
package/dist/host-controls.d.ts
CHANGED
|
@@ -13,12 +13,14 @@ export interface ChatChoice {
|
|
|
13
13
|
autoStart?: boolean;
|
|
14
14
|
efforts?: string[];
|
|
15
15
|
}
|
|
16
|
+
/** `defaultPermission`: the approval mode the host started this conversation in when no one picked one; absent, it asks. */
|
|
16
17
|
export interface ManagedChatSession {
|
|
17
18
|
id: string;
|
|
18
19
|
selection: ChatSelection;
|
|
19
20
|
identity: string | null;
|
|
20
21
|
title: string;
|
|
21
22
|
created: number;
|
|
23
|
+
defaultPermission?: 'ask' | 'autoApprove';
|
|
22
24
|
}
|
|
23
25
|
/** Host-owned operations. The host revalidates commands before native terminal execution. */
|
|
24
26
|
export interface ChatSetupAction {
|
|
@@ -51,9 +53,9 @@ export interface HostState {
|
|
|
51
53
|
actualModel?: string | null;
|
|
52
54
|
configurable: boolean;
|
|
53
55
|
setup?: ChatSetupState;
|
|
54
|
-
/** The
|
|
55
|
-
* where the runtime can answer approvals
|
|
56
|
-
|
|
56
|
+
/** The mode a new chat's picker shows. A conversation's own default is its `ManagedChatSession.defaultPermission`;
|
|
57
|
+
* this is never applied to one that exists. `autoApprove` takes effect only where the runtime can answer approvals. */
|
|
58
|
+
newChatPermission?: 'ask' | 'autoApprove';
|
|
57
59
|
}
|
|
58
60
|
export interface HostSelectionResult extends HostState {
|
|
59
61
|
connection: Record<string, 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.
|
|
4
|
+
"version": "0.1.40",
|
|
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",
|