@volter/supercode-frontend-vscode 0.1.47 → 0.1.49
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/dist/extension.js +172 -36
- package/dist/host-controls.d.ts +16 -1
- package/dist/host-controls.js +9 -4
- package/dist/session.d.ts +16 -0
- package/dist/session.js +41 -2
- package/dist/view.d.ts +22 -0
- package/dist/view.js +156 -17
- package/package.json +2 -2
- package/vendor/supercode-frontend/contract.json +3 -0
- package/vendor/supercode-frontend/generated-client.d.mts +2 -1
- package/vendor/supercode-frontend/generated-client.mjs +1 -0
- package/vendor/supercode-frontend/generated.d.mts +8 -2
- package/vendor/supercode-frontend/generated.mjs +1 -0
- package/vendor/supercode-frontend/package.json +1 -1
package/dist/extension.js
CHANGED
|
@@ -267,17 +267,30 @@ function writePart(stream, part, state) {
|
|
|
267
267
|
const toolName = part.toolName || state.toolNames.get(part.toolCallId) || 'tool';
|
|
268
268
|
if (part.toolName)
|
|
269
269
|
state.toolNames.set(part.toolCallId, part.toolName);
|
|
270
|
+
// The titles a call started with stay its titles: a completion (which may not name its tool) never renames it.
|
|
271
|
+
const started = state.toolTitles.get(part.toolCallId);
|
|
272
|
+
const titles = started ?? { invocation: part.invocationMessage || `Running \`${toolName}\``, pastTense: part.pastTenseMessage || part.invocationMessage || `Ran \`${toolName}\`` };
|
|
273
|
+
if (!started)
|
|
274
|
+
state.toolTitles.set(part.toolCallId, titles);
|
|
275
|
+
if (part.commandLine)
|
|
276
|
+
state.toolCommands.set(part.toolCallId, part.commandLine);
|
|
277
|
+
const commandLine = state.toolCommands.get(part.toolCallId);
|
|
270
278
|
const invocation = new vscode.ChatToolInvocationPart(toolName, part.toolCallId);
|
|
271
279
|
invocation.enablePartialUpdate = true;
|
|
272
|
-
invocation.invocationMessage =
|
|
280
|
+
invocation.invocationMessage = new vscode.MarkdownString(titles.invocation);
|
|
273
281
|
invocation.isConfirmed = true;
|
|
274
282
|
invocation.isComplete = part.isComplete;
|
|
275
|
-
invocation.pastTenseMessage =
|
|
283
|
+
invocation.pastTenseMessage = new vscode.MarkdownString(titles.pastTense);
|
|
276
284
|
if (part.isError !== undefined)
|
|
277
285
|
invocation.isError = part.isError;
|
|
278
286
|
if (part.inputText !== undefined)
|
|
279
287
|
state.toolInputs.set(part.toolCallId, part.inputText);
|
|
280
|
-
if (
|
|
288
|
+
if (commandLine) {
|
|
289
|
+
// VS Code's own terminal block: the command, and its output once it has run.
|
|
290
|
+
invocation.toolSpecificData = { commandLine: { original: commandLine }, language: 'sh',
|
|
291
|
+
...(part.isComplete ? { output: { text: part.resultText ?? '' } } : {}) };
|
|
292
|
+
}
|
|
293
|
+
else if (part.isComplete) {
|
|
281
294
|
invocation.toolSpecificData = {
|
|
282
295
|
input: state.toolInputs.get(part.toolCallId) ?? '',
|
|
283
296
|
output: part.resultText ?? '',
|
|
@@ -494,14 +507,28 @@ export async function activate(context) {
|
|
|
494
507
|
controlsChanged.fire();
|
|
495
508
|
}
|
|
496
509
|
}));
|
|
510
|
+
// What a new chat of `selection` runs with: its own picks over what the harness's configuration names (host state).
|
|
511
|
+
const draftEffective = (selection) => {
|
|
512
|
+
const configured = controls?.state.effectiveByHarness?.[selection.harness];
|
|
513
|
+
if (!configured)
|
|
514
|
+
return null;
|
|
515
|
+
const picked = selection.model ? controls?.state.modelsByHarness?.[selection.harness]?.find(model => model.id === selection.model) : undefined;
|
|
516
|
+
return { model: selection.model || configured.model, modelName: selection.model ? picked?.name ?? selection.model : configured.modelName, effort: selection.effort || configured.effort };
|
|
517
|
+
};
|
|
518
|
+
const effectiveNow = () => {
|
|
519
|
+
if (focusedAttached)
|
|
520
|
+
return null;
|
|
521
|
+
const selection = draftSelection();
|
|
522
|
+
return selection ? draftEffective(selection) : controls?.state.effective ?? null;
|
|
523
|
+
};
|
|
497
524
|
const identity = () => {
|
|
498
525
|
if (focusedAttached)
|
|
499
526
|
return runtimeIdentity(focusedAttached.session.descriptor?.source_harness, focusedAttached.session.descriptor?.model);
|
|
500
527
|
const descriptor = managed()?.session.descriptor;
|
|
501
528
|
const selection = draftSelection();
|
|
502
529
|
if (selection)
|
|
503
|
-
return runtimeIdentity(selection.harness, selection.model);
|
|
504
|
-
return runtimeIdentity(controls?.state.selection.harness ?? descriptor?.source_harness, controls?.state.selection.model || controls?.state.actualModel || descriptor?.model);
|
|
530
|
+
return runtimeIdentity(selection.harness, selection.model, draftEffective(selection));
|
|
531
|
+
return runtimeIdentity(controls?.state.selection.harness ?? descriptor?.source_harness, controls?.state.selection.model || controls?.state.actualModel || descriptor?.model, controls?.state.effective);
|
|
505
532
|
};
|
|
506
533
|
const icon = () => {
|
|
507
534
|
const provider = identity().provider;
|
|
@@ -524,16 +551,18 @@ export async function activate(context) {
|
|
|
524
551
|
if (!choices.some(model => model.id === selection.model))
|
|
525
552
|
choices.push({ id: selection.model, name: selection.model });
|
|
526
553
|
modelSelections.clear();
|
|
554
|
+
// Named by what the conversation actually runs with, never "default model" (host `effective`).
|
|
555
|
+
const effective = draft ? draftEffective(selection) : controls.state.effective;
|
|
527
556
|
return choices.map(model => {
|
|
528
557
|
const id = `${selection.harness}/${encodeURIComponent(model.id || '__default__')}`;
|
|
529
558
|
modelSelections.set(id, { ...selection, model: model.id });
|
|
530
|
-
const provider = runtimeIdentity(selection.harness, selection.model).provider;
|
|
531
|
-
const name =
|
|
559
|
+
const provider = runtimeIdentity(selection.harness, selection.model, effective).provider;
|
|
560
|
+
const name = runtimeIdentity(selection.harness, model.id, effective).name;
|
|
532
561
|
return { id, name, family: selection.harness, version: '1',
|
|
533
562
|
statusIcon: new vscode.ThemeIcon(provider === 'anthropic' ? 'claude' : provider === 'openai' ? 'openai' : provider === 'google' ? 'google-gemini' : 'sparkle'),
|
|
534
563
|
maxInputTokens: 0, maxOutputTokens: 0, capabilities: {},
|
|
535
564
|
targetChatSessionType: SESSION_SCHEME, isUserSelectable: true, isDefault: model.id === selection.model,
|
|
536
|
-
tooltip: `${name}
|
|
565
|
+
tooltip: `${name}. Model is fixed for this conversation. Use Model settings to choose model and reasoning for a new conversation.`,
|
|
537
566
|
};
|
|
538
567
|
});
|
|
539
568
|
},
|
|
@@ -571,7 +600,7 @@ export async function activate(context) {
|
|
|
571
600
|
await frontends.disconnect(connection.id);
|
|
572
601
|
const connection = await frontends.connect({ connectionId: id, baseUrl: env.SUPERCODE_FRONTEND_URL,
|
|
573
602
|
token: await readCredentialFile(env.SUPERCODE_FRONTEND_CREDENTIAL_FILE), clientId: env.SUPERCODE_FRONTEND_CLIENT_ID,
|
|
574
|
-
permissions: normalizePermissions(env.SUPERCODE_FRONTEND_PERMISSIONS, ['observe']), label: runtimeIdentity(state.selection.harness, state.selection.model || state.actualModel).name }, log);
|
|
603
|
+
permissions: normalizePermissions(env.SUPERCODE_FRONTEND_PERMISSIONS, ['observe']), label: runtimeIdentity(state.selection.harness, state.selection.model || state.actualModel, state.effective).name }, log);
|
|
575
604
|
if (!connection)
|
|
576
605
|
throw new Error('Could not connect to the requested conversation.');
|
|
577
606
|
return connection;
|
|
@@ -842,7 +871,10 @@ export async function activate(context) {
|
|
|
842
871
|
const pickModelAndEffort = async (harness, current) => {
|
|
843
872
|
const choices = controls?.state.modelsByHarness?.[harness] ?? [{ id: '', name: 'Harness default' }];
|
|
844
873
|
const name = harnessDisplayName(harness);
|
|
845
|
-
|
|
874
|
+
// The harness's own choice is named by what it resolves to (host `effectiveByHarness`).
|
|
875
|
+
const configured = controls?.state.effectiveByHarness?.[harness];
|
|
876
|
+
const items = [...choices.map(model => ({ label: model.id === '' && configured?.modelName ? `${model.name}: ${configured.modelName}` : model.name,
|
|
877
|
+
description: [model.id, model.description].filter(Boolean).join(' · ') || undefined, id: model.id, efforts: model.efforts })),
|
|
846
878
|
{ label: 'Enter an exact model ID…', description: undefined, id: '__custom__', efforts: choices.find(model => model.id === '')?.efforts }];
|
|
847
879
|
const picked = await pickWithCurrent(items, `New chat · ${name} model`, 'Model availability depends on your harness account.', current?.model ?? '');
|
|
848
880
|
if (!picked)
|
|
@@ -855,8 +887,8 @@ export async function activate(context) {
|
|
|
855
887
|
model = typed.trim();
|
|
856
888
|
}
|
|
857
889
|
const levels = picked.efforts ?? ['low', 'medium', 'high'];
|
|
858
|
-
const efforts = [{ label: 'Harness default', id: '' }, ...levels.map(id => ({ label: id, id }))];
|
|
859
|
-
const effort = levels.length === 0 ? efforts[0] : await pickWithCurrent(efforts, `New chat · ${name} reasoning effort`,
|
|
890
|
+
const efforts = [{ label: configured?.effort && !model ? `Harness default: ${configured.effort}` : 'Harness default', id: '' }, ...levels.map(id => ({ label: id, id }))];
|
|
891
|
+
const effort = levels.length === 0 ? efforts[0] : await pickWithCurrent(efforts, `New chat · ${name} reasoning effort`, `For ${model || configured?.modelName || `${name}'s configured model`}`, current?.effort ?? '');
|
|
860
892
|
if (!effort)
|
|
861
893
|
return;
|
|
862
894
|
return { model, effort: effort.id };
|
|
@@ -958,7 +990,9 @@ export async function activate(context) {
|
|
|
958
990
|
// whose built-in model picker deliberately omits Manage Models. Do not advertise
|
|
959
991
|
// hot swapping: the command configures a separate native blank chat.
|
|
960
992
|
// A locked selected item hides the entire native dropdown, including commands.
|
|
961
|
-
|
|
993
|
+
// The model picker names the model and effort and the permissions picker the approval mode; this item is only
|
|
994
|
+
// the way to choose them for a new chat.
|
|
995
|
+
const item = { id: 'settings', name: 'Change model…', description: 'Choose model and reasoning for a new chat.' };
|
|
962
996
|
groups.push({ id: 'modelSettings', name: 'Model', items: [item], selected: item,
|
|
963
997
|
commands: [{ command: 'supercode.frontend.configureModel', title: 'Choose model and reasoning for a new chat…', arguments: [selected.id] }] });
|
|
964
998
|
}
|
|
@@ -990,10 +1024,27 @@ export async function activate(context) {
|
|
|
990
1024
|
resolve: (resource, token) => connectionFor(resource, false, token),
|
|
991
1025
|
attached: (connection) => attachedIds.has(connection.id),
|
|
992
1026
|
catalogue: () => controls?.state.sessions ?? [],
|
|
1027
|
+
effective: (entry) => controls && entry.id === controls.state.activeSession ? controls.state.effective ?? null : draftEffective(entry.selection),
|
|
993
1028
|
bindings: boundId,
|
|
994
1029
|
watchApprovals: (connection, stream, token) => watchApprovals(connection, stream, token, () => approvalFor(connection.id)),
|
|
995
1030
|
};
|
|
996
1031
|
context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.activateSession', async (resource) => { await connectionFor(vscode.Uri.parse(resource)); }));
|
|
1032
|
+
// A background task the chat named (sayTasks in runTurnInto), stopped through the harness's own control.
|
|
1033
|
+
context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.stopBackgroundTask', async (connectionId, taskId) => {
|
|
1034
|
+
const connection = frontends.get(connectionId);
|
|
1035
|
+
const task = connection?.session.backgroundTasks.find(candidate => candidate.id === taskId);
|
|
1036
|
+
if (!connection || !task) {
|
|
1037
|
+
void vscode.window.showInformationMessage('That background task is no longer running.');
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
try {
|
|
1041
|
+
const stopped = await connection.session.stopTask(taskId);
|
|
1042
|
+
void vscode.window.showInformationMessage(stopped ? `Stopped ${task.description || taskId}.` : `The harness did not stop ${task.description || taskId}.`);
|
|
1043
|
+
}
|
|
1044
|
+
catch (error) {
|
|
1045
|
+
void vscode.window.showErrorMessage(`Could not stop ${task.description || taskId}: ${String(error?.message ?? error)}`);
|
|
1046
|
+
}
|
|
1047
|
+
}));
|
|
997
1048
|
// The host's native focus event is authoritative even when a cached model
|
|
998
1049
|
// skips content resolution. Untitled/non-harness focus never starts a runtime.
|
|
999
1050
|
context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.focusSession', (resource) => {
|
|
@@ -1105,6 +1156,59 @@ export async function activate(context) {
|
|
|
1105
1156
|
});
|
|
1106
1157
|
}));
|
|
1107
1158
|
context.subscriptions.push({ dispose: () => frontends.dispose() });
|
|
1159
|
+
// Auto approve holds for the conversation, not only while a response of this window is open. A harness also runs
|
|
1160
|
+
// turns of its own (a background task reporting back, a Monitor's event): an approval those raise had no response to
|
|
1161
|
+
// answer it or show its card, and the turn waited forever. With no turn of this window running, such an approval is
|
|
1162
|
+
// answered here under the conversation's mode; under Ask it keeps waiting for the person, as before.
|
|
1163
|
+
const answerOutsideTurns = () => {
|
|
1164
|
+
for (const connection of frontends.all) {
|
|
1165
|
+
if (attachedIds.has(connection.id) || runningTurns.get(connection.id)?.size || !canAnswer(connection))
|
|
1166
|
+
continue;
|
|
1167
|
+
if (approvalFor(connection.id) !== 'autoApprove')
|
|
1168
|
+
continue;
|
|
1169
|
+
const answered = answeredRequests.get(connection.id) ?? new Set();
|
|
1170
|
+
answeredRequests.set(connection.id, answered);
|
|
1171
|
+
for (const pending of connection.session.pendingRequests.values()) {
|
|
1172
|
+
if (pending.kind !== 'approval' || answered.has(pending.id))
|
|
1173
|
+
continue;
|
|
1174
|
+
const decisions = acceptedDecisions(pending);
|
|
1175
|
+
if (decisions && !decisions.includes('allow'))
|
|
1176
|
+
continue;
|
|
1177
|
+
answered.add(pending.id);
|
|
1178
|
+
void connection.session.respond({ kind: 'approval', request_id: pending.id, decision: 'allow' }).catch(error => {
|
|
1179
|
+
answered.delete(pending.id);
|
|
1180
|
+
log.warn(`Auto approve could not answer request #${pending.id} outside a turn: ${String(error?.message ?? error)}`);
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
context.subscriptions.push(frontends.onDidChange(answerOutsideTurns));
|
|
1186
|
+
// STOP ANSWERS WHAT THE AGENT IS WAITING ON FIRST. An agent blocked on an approval (or a question) does not hear an
|
|
1187
|
+
// interrupt until that request is answered: Claude Code's permission prompt kept the turn alive, the request
|
|
1188
|
+
// vanished from view on reload, and every later prompt queued behind it (2026-10-07). So a Stop denies each waiting
|
|
1189
|
+
// approval and cancels each question, then interrupts.
|
|
1190
|
+
const stopConnection = async (connection) => {
|
|
1191
|
+
if (canAnswer(connection)) {
|
|
1192
|
+
for (const pending of [...connection.session.pendingRequests.values()]) {
|
|
1193
|
+
const answer = pending.kind === 'approval'
|
|
1194
|
+
? { kind: 'approval', request_id: pending.id, decision: 'deny' }
|
|
1195
|
+
: { kind: pending.kind === 'elicitation' ? 'elicitation' : 'other', request_id: pending.id, action: 'cancel' };
|
|
1196
|
+
await connection.session.respond(answer).catch(error => log.warn(`Stop could not answer request #${pending.id}: ${String(error?.message ?? error)}`));
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
await connection.session.interrupt();
|
|
1200
|
+
};
|
|
1201
|
+
// The same Stop for a turn no response of this window holds — one the harness started itself, after a background
|
|
1202
|
+
// task or a Monitor's event — which the Chat's own Stop (it cancels a response) cannot reach.
|
|
1203
|
+
context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.stopTurn', async (connectionId) => {
|
|
1204
|
+
const connection = connectionId ? frontends.get(connectionId) : managed();
|
|
1205
|
+
if (!connection)
|
|
1206
|
+
return { stopped: false, reason: 'no conversation' };
|
|
1207
|
+
await stopConnection(connection).catch(error => log.warn(`Stop could not interrupt: ${String(error?.message ?? error)}`));
|
|
1208
|
+
return { stopped: true, connectionId: connection.id };
|
|
1209
|
+
}));
|
|
1210
|
+
const outsideTurns = setInterval(answerOutsideTurns, 2_000);
|
|
1211
|
+
context.subscriptions.push({ dispose: () => clearInterval(outsideTurns) });
|
|
1108
1212
|
// Auto-answers a conversation's approvals while a response is written into `stream`: read at every event
|
|
1109
1213
|
// (`sweep`) and on every picker change for that conversation, so switching to Auto approve mid-turn answers
|
|
1110
1214
|
// the approvals already waiting and the ones after them. Used by a turn this window runs and by a turn it
|
|
@@ -1159,7 +1263,7 @@ export async function activate(context) {
|
|
|
1159
1263
|
writeProjected(stream, projectPendingRequests([pending], {
|
|
1160
1264
|
connectionId: connection.id,
|
|
1161
1265
|
canRespond: connection.session.descriptor?.actions.respond !== false,
|
|
1162
|
-
}), granted, { toolInputs: new Map(), toolNames: new Map() });
|
|
1266
|
+
}), granted, { toolInputs: new Map(), toolNames: new Map(), toolTitles: new Map(), toolCommands: new Map() });
|
|
1163
1267
|
}
|
|
1164
1268
|
catch {
|
|
1165
1269
|
void vscode.window.showWarningMessage(`${connection.label}: ${label} is waiting for approval. Open its chat to answer it.`);
|
|
@@ -1196,29 +1300,54 @@ export async function activate(context) {
|
|
|
1196
1300
|
if (approval() === 'autoApprove' && !canAnswer(connection)) {
|
|
1197
1301
|
throw new Error('This harness does not support automatic approval through Chat. Select Harness permissions.');
|
|
1198
1302
|
}
|
|
1199
|
-
const state = { toolInputs: new Map(), toolNames: new Map() };
|
|
1303
|
+
const state = { toolInputs: new Map(), toolNames: new Map(), toolTitles: new Map(), toolCommands: new Map() };
|
|
1200
1304
|
const projection = {
|
|
1201
1305
|
connectionId: connection.id,
|
|
1202
1306
|
pendingRequest: (requestId) => connection.session.pendingRequests.get(requestId) ?? connection.session.resolvedRequests.get(requestId),
|
|
1203
1307
|
canRespond: connection.session.descriptor?.actions.respond !== false,
|
|
1204
1308
|
};
|
|
1205
1309
|
const cancellation = token.onCancellationRequested(() => {
|
|
1206
|
-
void connection
|
|
1310
|
+
void stopConnection(connection).catch(() => {
|
|
1207
1311
|
// The runtime may already be idle; interrupting twice is not an error
|
|
1208
1312
|
// this view reports.
|
|
1209
1313
|
});
|
|
1210
1314
|
});
|
|
1211
1315
|
const watch = watchApprovals(connection, stream, token, approval);
|
|
1212
1316
|
state.quiet = watch.answers;
|
|
1317
|
+
// The harness's own background tasks (a Monitor, a background shell): each event one reports is a turn of its own,
|
|
1318
|
+
// which this message waits behind, and nothing here would show it. Said once per change of the set, each with a
|
|
1319
|
+
// way to stop it.
|
|
1320
|
+
let saidTasks = '';
|
|
1321
|
+
const sayTasks = () => {
|
|
1322
|
+
const tasks = connection.session.backgroundTasks;
|
|
1323
|
+
const key = tasks.map(task => task.id).join(',');
|
|
1324
|
+
if (key === saidTasks)
|
|
1325
|
+
return;
|
|
1326
|
+
saidTasks = key;
|
|
1327
|
+
if (!tasks.length) {
|
|
1328
|
+
stream.markdown('\n\n_No background task is running any more._\n\n');
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
stream.markdown(`\n\n_${tasks.length === 1 ? 'A background task is' : `${tasks.length} background tasks are`} still running in this session. The harness answers each of its events as a turn of its own, so this message can wait behind them:_\n\n`);
|
|
1332
|
+
for (const task of tasks) {
|
|
1333
|
+
stream.button({ command: 'supercode.frontend.stopBackgroundTask', title: `Stop ${opaqueText(task.description || task.id, 80)}`, arguments: [connection.id, task.id] });
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
sayTasks();
|
|
1337
|
+
const unwatchTasks = connection.session.onChange(sayTasks);
|
|
1213
1338
|
let completed = false;
|
|
1214
1339
|
try {
|
|
1215
1340
|
await connection.session.runTurn(prompt, (event) => {
|
|
1341
|
+
// A turn the person stopped is marked stopped by VS Code itself; only an interruption from elsewhere is said.
|
|
1342
|
+
if (event.kind === 'turn_interrupted' && token.isCancellationRequested)
|
|
1343
|
+
return;
|
|
1216
1344
|
writeProjected(stream, projectEvent(event, projection), granted, state);
|
|
1217
1345
|
watch.sweep();
|
|
1218
1346
|
});
|
|
1219
1347
|
completed = true;
|
|
1220
1348
|
}
|
|
1221
1349
|
finally {
|
|
1350
|
+
unwatchTasks();
|
|
1222
1351
|
watch.dispose();
|
|
1223
1352
|
cancellation.dispose();
|
|
1224
1353
|
if (controls && !attachedIds.has(connection.id)) {
|
|
@@ -1227,13 +1356,14 @@ export async function activate(context) {
|
|
|
1227
1356
|
await controls.refresh().catch(error => log.warn(`Could not refresh chat identity: ${String(error)}`));
|
|
1228
1357
|
controlsChanged.fire();
|
|
1229
1358
|
}
|
|
1230
|
-
// The model the turn ran on: what the harness's own replies name (the host watches them), else what the
|
|
1231
|
-
// runtime reports; and the effort it was given.
|
|
1232
|
-
const ran = (controls?.state.activeSession === connection.id ? controls.state.actualModel : undefined) || connection.session.descriptor?.model;
|
|
1233
|
-
const effort = controls?.state.sessions.find(session => session.id === connection.id)?.selection.effort;
|
|
1234
|
-
if (completed && ran && !token.isCancellationRequested)
|
|
1235
|
-
stream.markdown(`\n\n_Ran on ${opaqueText(ran, 120)}${effort ? ` · ${opaqueText(effort, 24)} effort` : ''}_`);
|
|
1236
1359
|
}
|
|
1360
|
+
// The model the turn ran on: what the harness's own replies name (the host watches them), else what the
|
|
1361
|
+
// runtime reports; and the effort it ran at (host `effective`).
|
|
1362
|
+
const active = controls?.state.activeSession === connection.id;
|
|
1363
|
+
const effective = active ? controls?.state.effective : undefined;
|
|
1364
|
+
const ran = effective?.modelName || (active ? controls?.state.actualModel : undefined) || connection.session.descriptor?.model;
|
|
1365
|
+
const effort = effective?.effort || controls?.state.sessions.find(session => session.id === connection.id)?.selection.effort;
|
|
1366
|
+
return completed && ran && !token.isCancellationRequested ? `${opaqueText(ran, 120)}${effort ? ` · ${opaqueText(effort, 24)}` : ''}` : undefined;
|
|
1237
1367
|
};
|
|
1238
1368
|
const handler = async (request, chatContext, stream, token) => {
|
|
1239
1369
|
await applying;
|
|
@@ -1268,21 +1398,23 @@ export async function activate(context) {
|
|
|
1268
1398
|
// path for the answers no button can carry (`allow for session`, `cancel`,
|
|
1269
1399
|
// and an elicitation's free-text content).
|
|
1270
1400
|
const pending = pendingRequestOf(connection);
|
|
1401
|
+
// A message is never taken as an approval: one sent while an approval waits would otherwise allow the tool and
|
|
1402
|
+
// never reach the agent. It is refused, with what to do, like a send during any running turn.
|
|
1403
|
+
if (pending?.kind === 'approval') {
|
|
1404
|
+
throw new Error(`${busyRefusal(connection, 'sending another message') ?? 'The current chat is waiting for your answer.'} Nothing was sent.`);
|
|
1405
|
+
}
|
|
1271
1406
|
if (pending) {
|
|
1407
|
+
// A question that needs typed content: the message is its answer, and its resolution line says so.
|
|
1272
1408
|
try {
|
|
1273
|
-
|
|
1274
|
-
await connection.session.respond(answer);
|
|
1275
|
-
stream.markdown(`_answered request #${pending.id}: ${opaqueText(answer, 256)}_`);
|
|
1409
|
+
await connection.session.respond(typedAnswer(pending, request.prompt));
|
|
1276
1410
|
}
|
|
1277
1411
|
catch (error) {
|
|
1278
|
-
|
|
1412
|
+
throw new Error(`Could not answer the question: ${opaqueText(String(error.message), 512)}`);
|
|
1279
1413
|
}
|
|
1280
1414
|
return {};
|
|
1281
1415
|
}
|
|
1282
|
-
if (request.prompt.trim().length === 0)
|
|
1283
|
-
stream.markdown('_nothing to submit_');
|
|
1416
|
+
if (request.prompt.trim().length === 0)
|
|
1284
1417
|
return {};
|
|
1285
|
-
}
|
|
1286
1418
|
const native = controls ? undefined : turnPermission(request.permissionLevel);
|
|
1287
1419
|
const approval = () => native ?? approvalFor(connection.id);
|
|
1288
1420
|
const attached = [];
|
|
@@ -1296,8 +1428,9 @@ export async function activate(context) {
|
|
|
1296
1428
|
attached.push({ name: uri.toString(), text: document.getText(location?.range) });
|
|
1297
1429
|
}
|
|
1298
1430
|
const prompt = withTextContext(request.prompt, attached);
|
|
1299
|
-
await runTurnInto(connection, prompt, stream, token, approval);
|
|
1300
|
-
|
|
1431
|
+
const details = await runTurnInto(connection, prompt, stream, token, approval);
|
|
1432
|
+
// The model the turn ran on, in VS Code's own response footer rather than as a line of the conversation.
|
|
1433
|
+
return details ? { details } : {};
|
|
1301
1434
|
};
|
|
1302
1435
|
const participants = [];
|
|
1303
1436
|
for (const id of granted.has('defaultChatParticipant') ? [PARTICIPANT_ID, DEFAULT_PARTICIPANT_ID] : [PARTICIPANT_ID]) {
|
|
@@ -1728,13 +1861,16 @@ function historyParts(parts) {
|
|
|
1728
1861
|
flush();
|
|
1729
1862
|
const toolName = part.toolName || 'tool';
|
|
1730
1863
|
const invocation = new vscode.ChatToolInvocationPart(toolName, part.toolCallId);
|
|
1731
|
-
invocation.invocationMessage = part.invocationMessage || toolName;
|
|
1732
|
-
invocation.pastTenseMessage = part.pastTenseMessage || part.invocationMessage || toolName;
|
|
1864
|
+
invocation.invocationMessage = new vscode.MarkdownString(part.invocationMessage || `Ran \`${toolName}\``);
|
|
1865
|
+
invocation.pastTenseMessage = new vscode.MarkdownString(part.pastTenseMessage || part.invocationMessage || `Ran \`${toolName}\``);
|
|
1733
1866
|
invocation.isConfirmed = true;
|
|
1734
1867
|
invocation.isComplete = part.isComplete;
|
|
1735
1868
|
if (part.isError !== undefined)
|
|
1736
1869
|
invocation.isError = part.isError;
|
|
1737
|
-
if (part.
|
|
1870
|
+
if (part.commandLine) {
|
|
1871
|
+
invocation.toolSpecificData = { commandLine: { original: part.commandLine }, language: 'sh', output: { text: part.resultText ?? '' } };
|
|
1872
|
+
}
|
|
1873
|
+
else if (part.isComplete) {
|
|
1738
1874
|
invocation.toolSpecificData = { input: part.inputText ?? '', output: part.resultText ?? '' };
|
|
1739
1875
|
}
|
|
1740
1876
|
out.push(invocation);
|
|
@@ -1794,7 +1930,7 @@ function registerSessions(context, frontends, participant, granted, handler, his
|
|
|
1794
1930
|
const items = () => {
|
|
1795
1931
|
return (controls?.catalogue() ?? []).filter(entry => !frontends.get(entry.id)).map(entry => {
|
|
1796
1932
|
const item = controller.createChatSessionItem(vscode.Uri.from({ scheme: SESSION_SCHEME, authority: entry.id, path: '/conversation' }), entry.title);
|
|
1797
|
-
item.description = runtimeIdentity(entry.selection.harness, entry.selection.model).name;
|
|
1933
|
+
item.description = runtimeIdentity(entry.selection.harness, entry.selection.model, controls?.effective(entry)).name;
|
|
1798
1934
|
item.status = vscode.ChatSessionStatus.Completed;
|
|
1799
1935
|
item.timing = { created: entry.created };
|
|
1800
1936
|
return item;
|
|
@@ -1936,7 +2072,7 @@ function registerSessions(context, frontends, participant, granted, handler, his
|
|
|
1936
2072
|
// already waiting and later ones; Ask leaves them for the person, as before.
|
|
1937
2073
|
const watch = controls && !controls.attached(connection) ? controls.watchApprovals(connection, stream, token) : undefined;
|
|
1938
2074
|
try {
|
|
1939
|
-
const state = { toolInputs: new Map(), toolNames: new Map(), quiet: watch?.answers };
|
|
2075
|
+
const state = { toolInputs: new Map(), toolNames: new Map(), toolTitles: new Map(), toolCommands: new Map(), quiet: watch?.answers };
|
|
1940
2076
|
writeProjected(stream, projectPendingRequests(connection.session.pendingRequests.values(), {
|
|
1941
2077
|
connectionId: connection.id,
|
|
1942
2078
|
canRespond: connection.session.descriptor?.actions.respond !== false,
|
package/dist/host-controls.d.ts
CHANGED
|
@@ -5,6 +5,12 @@ export interface ChatSelection {
|
|
|
5
5
|
model: string;
|
|
6
6
|
effort: string;
|
|
7
7
|
}
|
|
8
|
+
/** `modelName`: how to name `model` ("Claude Opus 5.5"); `effort` is '' when the model takes none. */
|
|
9
|
+
export interface EffectiveChat {
|
|
10
|
+
model: string;
|
|
11
|
+
modelName: string;
|
|
12
|
+
effort: string;
|
|
13
|
+
}
|
|
8
14
|
/** `efforts`: the reasoning efforts a model takes, as the host read them from the harness (absent from older hosts). */
|
|
9
15
|
export interface ChatChoice {
|
|
10
16
|
id: string;
|
|
@@ -51,6 +57,11 @@ export interface HostState {
|
|
|
51
57
|
modelsByHarness?: Record<string, ChatChoice[]>;
|
|
52
58
|
busy: boolean;
|
|
53
59
|
actualModel?: string | null;
|
|
60
|
+
/** The model and effort this conversation actually runs with, read from the harness's own configuration when the
|
|
61
|
+
* selection names none (and from the runtime once a turn has run). Absent from older hosts. */
|
|
62
|
+
effective?: EffectiveChat | null;
|
|
63
|
+
/** The same for a new chat of each startable harness. */
|
|
64
|
+
effectiveByHarness?: Record<string, EffectiveChat | null>;
|
|
54
65
|
configurable: boolean;
|
|
55
66
|
setup?: ChatSetupState;
|
|
56
67
|
/** The mode a new chat's picker shows. A conversation's own default is its `ManagedChatSession.defaultPermission`;
|
|
@@ -85,9 +96,13 @@ export declare class ChatHostControls {
|
|
|
85
96
|
/** A picked approval mode, never inferred from connection privileges (those only keep a host default at ask). */
|
|
86
97
|
export declare function turnPermission(value: string | undefined): 'ask' | 'autoApprove';
|
|
87
98
|
export declare function harnessDisplayName(harness: string | null | undefined): string;
|
|
88
|
-
|
|
99
|
+
/** The Chat's name for a runtime: the model and effort it actually runs with (`effective`, from the host) and its
|
|
100
|
+
* harness. It never says "default model": a harness whose model nobody reports is said to choose its own. */
|
|
101
|
+
export declare function runtimeIdentity(harness: string | null | undefined, model: string | null | undefined, effective?: EffectiveChat | null): {
|
|
89
102
|
name: string;
|
|
90
103
|
provider: string | undefined;
|
|
91
104
|
};
|
|
105
|
+
/** The approval mode as the Chat names it. */
|
|
106
|
+
export declare function permissionName(value: 'ask' | 'autoApprove'): string;
|
|
92
107
|
/** Native pickers need a nonempty id; the host uses an empty model for its default. */
|
|
93
108
|
export declare function modelSelection(value: string): string;
|
package/dist/host-controls.js
CHANGED
|
@@ -64,14 +64,19 @@ export function turnPermission(value) {
|
|
|
64
64
|
export function harnessDisplayName(harness) {
|
|
65
65
|
return { 'claude-code': 'Claude Code', codex: 'Codex', gemini: 'Gemini CLI', opencode: 'OpenCode', pi: 'Pi', goose: 'Goose' }[harness || ''] || harness || 'Assistant';
|
|
66
66
|
}
|
|
67
|
-
|
|
67
|
+
/** The Chat's name for a runtime: the model and effort it actually runs with (`effective`, from the host) and its
|
|
68
|
+
* harness. It never says "default model": a harness whose model nobody reports is said to choose its own. */
|
|
69
|
+
export function runtimeIdentity(harness, model, effective) {
|
|
68
70
|
const harnessName = harnessDisplayName(harness);
|
|
69
|
-
const knownModel = model && model !== harness ? model : '';
|
|
70
|
-
const modelName = { sonnet: 'Claude Sonnet', opus: 'Claude Opus', haiku: 'Claude Haiku' }[knownModel] || knownModel;
|
|
71
|
+
const knownModel = effective?.model || (model && model !== harness ? model : '');
|
|
72
|
+
const modelName = effective?.modelName || { sonnet: 'Claude Sonnet', opus: 'Claude Opus', haiku: 'Claude Haiku' }[knownModel] || knownModel;
|
|
71
73
|
const provider = /claude|sonnet|opus|haiku/i.test(modelName) || (!modelName && harness === 'claude-code') ? 'anthropic'
|
|
72
74
|
: /^(gpt|o[134]|chatgpt)|\/gpt/i.test(modelName) || (!modelName && harness === 'codex') ? 'openai'
|
|
73
75
|
: /gemini/i.test(modelName) || (!modelName && harness === 'gemini') ? 'google' : undefined;
|
|
74
|
-
|
|
76
|
+
const effort = effective?.effort ? ` · ${effective.effort}` : '';
|
|
77
|
+
return { name: modelName ? `${modelName}${effort}` : `Model chosen by ${harnessName}`, provider };
|
|
75
78
|
}
|
|
79
|
+
/** The approval mode as the Chat names it. */
|
|
80
|
+
export function permissionName(value) { return value === 'autoApprove' ? 'Auto approve' : 'Ask for approval'; }
|
|
76
81
|
/** Native pickers need a nonempty id; the host uses an empty model for its default. */
|
|
77
82
|
export function modelSelection(value) { return value === "__default__" ? "" : value; }
|
package/dist/session.d.ts
CHANGED
|
@@ -15,6 +15,9 @@ export interface VscodeRuntimeClient {
|
|
|
15
15
|
steer(prompt: string): Promise<{
|
|
16
16
|
queued: boolean;
|
|
17
17
|
}>;
|
|
18
|
+
stopTask(taskId: string): Promise<{
|
|
19
|
+
stopped: boolean;
|
|
20
|
+
}>;
|
|
18
21
|
interrupt(): Promise<{
|
|
19
22
|
interrupted: boolean;
|
|
20
23
|
}>;
|
|
@@ -62,6 +65,12 @@ export declare const TURN_TERMINAL_KINDS: readonly ["turn_succeeded", "turn_fail
|
|
|
62
65
|
* the view reports.
|
|
63
66
|
*/
|
|
64
67
|
export declare const LEASE_ERROR_NAMES: readonly string[];
|
|
68
|
+
/** One of the harness's own background tasks (a Monitor, a background shell), as it last listed them. */
|
|
69
|
+
export interface BackgroundTask {
|
|
70
|
+
id: string;
|
|
71
|
+
kind: string;
|
|
72
|
+
description: string;
|
|
73
|
+
}
|
|
65
74
|
/**
|
|
66
75
|
* One attachment to one already-running Volter Harness SDK runtime: cursor-safe
|
|
67
76
|
* event projection, permission narrowing, and the semantic actions the runtime
|
|
@@ -86,6 +95,11 @@ export declare class VscodeFrontendSession {
|
|
|
86
95
|
*/
|
|
87
96
|
replay: FrontendEvent[];
|
|
88
97
|
pendingRequests: Map<number, FrontendRequest>;
|
|
98
|
+
/**
|
|
99
|
+
* The harness's own background tasks as it last listed them (`background_tasks`). Each event one reports starts a
|
|
100
|
+
* turn of its own, so while any runs the session can be busy with turns no one here submitted.
|
|
101
|
+
*/
|
|
102
|
+
backgroundTasks: BackgroundTask[];
|
|
89
103
|
/**
|
|
90
104
|
* Requests this attachment has seen resolved. `request_resolved` deletes the
|
|
91
105
|
* pending entry while it is being projected, so without this the resolution
|
|
@@ -136,6 +150,8 @@ export declare class VscodeFrontendSession {
|
|
|
136
150
|
submit(prompt: string): Promise<void>;
|
|
137
151
|
steer(prompt: string): Promise<void>;
|
|
138
152
|
interrupt(): Promise<void>;
|
|
153
|
+
/** Stop one of the harness's own background tasks, by the id it listed it with. */
|
|
154
|
+
stopTask(taskId: string): Promise<boolean>;
|
|
139
155
|
respond(response: FrontendResponse): Promise<void>;
|
|
140
156
|
detach(): Promise<void>;
|
|
141
157
|
close(): Promise<void>;
|
package/dist/session.js
CHANGED
|
@@ -56,6 +56,11 @@ export class VscodeFrontendSession {
|
|
|
56
56
|
*/
|
|
57
57
|
replay = [];
|
|
58
58
|
pendingRequests = new Map();
|
|
59
|
+
/**
|
|
60
|
+
* The harness's own background tasks as it last listed them (`background_tasks`). Each event one reports starts a
|
|
61
|
+
* turn of its own, so while any runs the session can be busy with turns no one here submitted.
|
|
62
|
+
*/
|
|
63
|
+
backgroundTasks = [];
|
|
59
64
|
/**
|
|
60
65
|
* Requests this attachment has seen resolved. `request_resolved` deletes the
|
|
61
66
|
* pending entry while it is being projected, so without this the resolution
|
|
@@ -105,6 +110,7 @@ export class VscodeFrontendSession {
|
|
|
105
110
|
const staged = {
|
|
106
111
|
descriptor: structuredClone(snapshot.descriptor),
|
|
107
112
|
pendingRequests: new Map([...this.pendingRequests].map(([id, request]) => [id, structuredClone(request)])),
|
|
113
|
+
backgroundTasks: structuredClone(this.backgroundTasks),
|
|
108
114
|
cursor: Math.max(this.cursor, snapshot.history_cursor),
|
|
109
115
|
};
|
|
110
116
|
// Validate and project the whole attachment before any observable field
|
|
@@ -122,13 +128,36 @@ export class VscodeFrontendSession {
|
|
|
122
128
|
// none no matter what its replay still carries.
|
|
123
129
|
// Hosted Codex also receives standalone MCP elicitations. Its host retains
|
|
124
130
|
// request/resolved pairs without the native broker's turn compaction.
|
|
125
|
-
|
|
126
|
-
|
|
131
|
+
// BUT A HARNESS ALSO RUNS TURNS OF ITS OWN (a background task reporting back, a Monitor's event): the runtime then
|
|
132
|
+
// reads idle while the agent loop is blocked on an approval it raised. Clearing that one left the agent waiting
|
|
133
|
+
// forever with nothing to answer it (2026-10-07). Compaction happens only at a turn boundary, so a request raised
|
|
134
|
+
// AFTER the replay's last boundary and not resolved since is still open, idle or not; only older ones are dropped.
|
|
135
|
+
if (staged.descriptor?.turn_state !== 'busy' && staged.descriptor?.source_harness !== 'codex') {
|
|
136
|
+
const BOUNDARIES = new Set(['turn_succeeded', 'turn_completed', 'turn_failed', 'turn_interrupted']);
|
|
137
|
+
let lastBoundary = -1;
|
|
138
|
+
const raisedAt = new Map();
|
|
139
|
+
for (const event of snapshot.replay) {
|
|
140
|
+
if (BOUNDARIES.has(event.kind))
|
|
141
|
+
lastBoundary = Math.max(lastBoundary, event.sequence);
|
|
142
|
+
const payload = (event.payload ?? {});
|
|
143
|
+
const request = event.kind === 'request' ? payload.request : undefined;
|
|
144
|
+
if (request && typeof request.id === 'number')
|
|
145
|
+
raisedAt.set(request.id, event.sequence);
|
|
146
|
+
}
|
|
147
|
+
for (const id of [...staged.pendingRequests.keys()]) {
|
|
148
|
+
const at = raisedAt.get(id);
|
|
149
|
+
// A request known from before this attachment, with no boundary since, is still the open one it was.
|
|
150
|
+
const open = at !== undefined ? at > lastBoundary : lastBoundary < 0 && this.pendingRequests.has(id);
|
|
151
|
+
if (!open)
|
|
152
|
+
staged.pendingRequests.delete(id);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
127
155
|
this.descriptor = staged.descriptor;
|
|
128
156
|
this.history = structuredClone(snapshot.history);
|
|
129
157
|
this.historyCursor = snapshot.history_cursor;
|
|
130
158
|
this.appendTranscript(structuredClone(snapshot.replay));
|
|
131
159
|
this.pendingRequests = staged.pendingRequests;
|
|
160
|
+
this.backgroundTasks = staged.backgroundTasks;
|
|
132
161
|
this.cursor = staged.cursor;
|
|
133
162
|
this.connection = 'connected';
|
|
134
163
|
this.attachedAtMs ??= Date.now();
|
|
@@ -263,6 +292,11 @@ export class VscodeFrontendSession {
|
|
|
263
292
|
if (request && typeof request.id === 'number')
|
|
264
293
|
state.pendingRequests.set(request.id, structuredClone(request));
|
|
265
294
|
}
|
|
295
|
+
if (event.kind === 'background_tasks' && Array.isArray(payload.tasks)) {
|
|
296
|
+
state.backgroundTasks = payload.tasks
|
|
297
|
+
.filter(task => task && typeof task.id === 'string')
|
|
298
|
+
.map(task => ({ id: task.id, kind: String(task.kind ?? 'task'), description: String(task.description ?? '') }));
|
|
299
|
+
}
|
|
266
300
|
if (event.kind === 'request_resolved' && typeof payload.request_id === 'number') {
|
|
267
301
|
const resolved = state.pendingRequests.get(payload.request_id);
|
|
268
302
|
if (resolved) {
|
|
@@ -337,6 +371,11 @@ export class VscodeFrontendSession {
|
|
|
337
371
|
this.require('interrupt');
|
|
338
372
|
await this.withControl(() => this.client.interrupt());
|
|
339
373
|
}
|
|
374
|
+
/** Stop one of the harness's own background tasks, by the id it listed it with. */
|
|
375
|
+
async stopTask(taskId) {
|
|
376
|
+
const result = await this.withControl(() => this.client.stopTask(taskId));
|
|
377
|
+
return result?.stopped === true;
|
|
378
|
+
}
|
|
340
379
|
async respond(response) {
|
|
341
380
|
this.require('respond');
|
|
342
381
|
if (!this.pendingRequests.has(response.request_id))
|
package/dist/view.d.ts
CHANGED
|
@@ -60,6 +60,8 @@ export type ChatPart = {
|
|
|
60
60
|
isError?: boolean;
|
|
61
61
|
isComplete: boolean;
|
|
62
62
|
resultText?: string;
|
|
63
|
+
/** A shell call's command line: the row is then VS Code's own terminal block, command and output. */
|
|
64
|
+
commandLine?: string;
|
|
63
65
|
} | {
|
|
64
66
|
part: 'answers';
|
|
65
67
|
approval: boolean;
|
|
@@ -93,6 +95,18 @@ export declare function diffPaths(diff: string): string[];
|
|
|
93
95
|
* wasted row; `write_file · hello.txt` says what it did.
|
|
94
96
|
*/
|
|
95
97
|
export declare function toolSubject(argumentsText: string): string | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* A tool row's titles the way VS Code's own agent writes them: what the call does, in the present while it runs
|
|
100
|
+
* ("Reading `view.ts`") and the past once done ("Read `view.ts`"), read from the call's own arguments. Shell calls
|
|
101
|
+
* use the description the harness gave them (Claude Code's `Bash.description`) when there is one. A tool this does
|
|
102
|
+
* not know is "Ran `name`", with its subject when the arguments name one.
|
|
103
|
+
*/
|
|
104
|
+
export declare function toolTitles(toolName: string, argumentsText: string): {
|
|
105
|
+
invocation: string;
|
|
106
|
+
pastTense: string;
|
|
107
|
+
};
|
|
108
|
+
/** The command line of a shell tool call, read from its own arguments; undefined for any other tool. */
|
|
109
|
+
export declare function shellCommand(toolName: string, argumentsText: string): string | undefined;
|
|
96
110
|
/** The summary line this view shows for a request payload. */
|
|
97
111
|
export declare function requestSummary(request: FrontendRequest): string;
|
|
98
112
|
/** The answers this view offers for a request, one button each. */
|
|
@@ -164,6 +178,14 @@ export interface ProjectedTurn {
|
|
|
164
178
|
* supercode's own agent loop, and a hosted harness has none. So a resumed
|
|
165
179
|
* Claude Code session's turns are here or nowhere.
|
|
166
180
|
*/
|
|
181
|
+
/**
|
|
182
|
+
* A "user" message no person typed: Claude Code's `<task-notification>` (a background task reporting back) or the
|
|
183
|
+
* editor's automatic tripwire nudge. Neither is shown as the person's words. A task notification is said as one
|
|
184
|
+
* short line ("Background task completed: <its summary>"); a nudge is not shown. Undefined for a person's message.
|
|
185
|
+
*/
|
|
186
|
+
export declare function automaticPrompt(body: string): {
|
|
187
|
+
notice?: string;
|
|
188
|
+
} | undefined;
|
|
167
189
|
export declare function projectReplayTurns(events: readonly FrontendEvent[], granted: ReadonlySet<string>, context: ProjectionContext): ProjectedTurn[];
|
|
168
190
|
/**
|
|
169
191
|
* The attach snapshot's canonical history as chat turns. Bounded: when older
|
package/dist/view.js
CHANGED
|
@@ -98,6 +98,101 @@ export function toolSubject(argumentsText) {
|
|
|
98
98
|
}
|
|
99
99
|
return undefined;
|
|
100
100
|
}
|
|
101
|
+
const code = (value) => `\`${value.replace(/`/g, "'")}\``;
|
|
102
|
+
const basename = (path) => path.split(/[\\/]/).filter(Boolean).pop() ?? path;
|
|
103
|
+
/**
|
|
104
|
+
* A tool row's titles the way VS Code's own agent writes them: what the call does, in the present while it runs
|
|
105
|
+
* ("Reading `view.ts`") and the past once done ("Read `view.ts`"), read from the call's own arguments. Shell calls
|
|
106
|
+
* use the description the harness gave them (Claude Code's `Bash.description`) when there is one. A tool this does
|
|
107
|
+
* not know is "Ran `name`", with its subject when the arguments name one.
|
|
108
|
+
*/
|
|
109
|
+
export function toolTitles(toolName, argumentsText) {
|
|
110
|
+
let fields = {};
|
|
111
|
+
try {
|
|
112
|
+
const parsed = JSON.parse(argumentsText);
|
|
113
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
|
|
114
|
+
fields = parsed;
|
|
115
|
+
}
|
|
116
|
+
catch { /* not JSON: titled by name */ }
|
|
117
|
+
const field = (...names) => {
|
|
118
|
+
for (const name of names) {
|
|
119
|
+
const value = fields[name];
|
|
120
|
+
if (typeof value === 'string' && value.trim())
|
|
121
|
+
return clamp(stripControls(value.trim()), 120);
|
|
122
|
+
if (Array.isArray(value) && value.every(part => typeof part === 'string') && value.length)
|
|
123
|
+
return clamp(stripControls(value.join(' ')), 120);
|
|
124
|
+
}
|
|
125
|
+
return '';
|
|
126
|
+
};
|
|
127
|
+
const both = (present, past) => ({ invocation: clamp(present, VSCODE_CHAT_LIMITS.titleCharacters), pastTense: clamp(past, VSCODE_CHAT_LIMITS.titleCharacters) });
|
|
128
|
+
const file = field('file_path', 'path', 'file', 'notebook_path');
|
|
129
|
+
const name = toolName.toLowerCase();
|
|
130
|
+
if (['bash', 'shell', 'exec_command', 'local_shell', 'powershell'].includes(name)) {
|
|
131
|
+
const description = field('description');
|
|
132
|
+
if (description)
|
|
133
|
+
return both(description, description);
|
|
134
|
+
const command = field('command', 'cmd');
|
|
135
|
+
return command ? both(`Running ${code(command)}`, `Ran ${code(command)}`) : both(`Running a command`, `Ran a command`);
|
|
136
|
+
}
|
|
137
|
+
if (name === 'read' && file) {
|
|
138
|
+
const offset = typeof fields.offset === 'number' ? fields.offset : undefined;
|
|
139
|
+
const limit = typeof fields.limit === 'number' ? fields.limit : undefined;
|
|
140
|
+
const lines = offset !== undefined && limit !== undefined ? `, lines ${offset} to ${offset + limit - 1}` : '';
|
|
141
|
+
return both(`Reading ${code(basename(file))}${lines}`, `Read ${code(basename(file))}${lines}`);
|
|
142
|
+
}
|
|
143
|
+
if (name === 'write' && file)
|
|
144
|
+
return both(`Writing ${code(basename(file))}`, `Wrote ${code(basename(file))}`);
|
|
145
|
+
if (['edit', 'multiedit', 'notebookedit', 'apply_patch'].includes(name))
|
|
146
|
+
return file ? both(`Editing ${code(basename(file))}`, `Edited ${code(basename(file))}`) : both('Editing files', 'Edited files');
|
|
147
|
+
if (name === 'grep') {
|
|
148
|
+
const pattern = field('pattern');
|
|
149
|
+
if (pattern)
|
|
150
|
+
return both(`Searching for ${code(pattern)}`, `Searched for ${code(pattern)}`);
|
|
151
|
+
}
|
|
152
|
+
if (name === 'glob') {
|
|
153
|
+
const pattern = field('pattern');
|
|
154
|
+
if (pattern)
|
|
155
|
+
return both(`Finding files matching ${code(pattern)}`, `Found files matching ${code(pattern)}`);
|
|
156
|
+
}
|
|
157
|
+
if (name === 'webfetch') {
|
|
158
|
+
const url = field('url');
|
|
159
|
+
if (url)
|
|
160
|
+
return both(`Fetching ${url}`, `Fetched ${url}`);
|
|
161
|
+
}
|
|
162
|
+
if (name === 'websearch' || name === 'web_search') {
|
|
163
|
+
const query = field('query');
|
|
164
|
+
if (query)
|
|
165
|
+
return both(`Searching the web for ${code(query)}`, `Searched the web for ${code(query)}`);
|
|
166
|
+
}
|
|
167
|
+
if (['task', 'agent'].includes(name)) {
|
|
168
|
+
const description = field('description');
|
|
169
|
+
if (description)
|
|
170
|
+
return both(`Running a subagent: ${description}`, `Ran a subagent: ${description}`);
|
|
171
|
+
}
|
|
172
|
+
if (name === 'todowrite' || name === 'update_plan')
|
|
173
|
+
return both('Updating the plan', 'Updated the plan');
|
|
174
|
+
// Claude Code names an MCP tool `mcp__<server>__<tool>`; VS Code writes "Ran `tool` – server".
|
|
175
|
+
const mcp = /^mcp__(.+?)__(.+)$/.exec(toolName);
|
|
176
|
+
if (mcp)
|
|
177
|
+
return both(`Running ${code(mcp[2])} – ${mcp[1]}`, `Ran ${code(mcp[2])} – ${mcp[1]}`);
|
|
178
|
+
const subject = toolSubject(argumentsText);
|
|
179
|
+
return subject ? both(`Running ${code(toolName)} · ${subject}`, `Ran ${code(toolName)} · ${subject}`) : both(`Running ${code(toolName)}`, `Ran ${code(toolName)}`);
|
|
180
|
+
}
|
|
181
|
+
/** The command line of a shell tool call, read from its own arguments; undefined for any other tool. */
|
|
182
|
+
export function shellCommand(toolName, argumentsText) {
|
|
183
|
+
if (!['bash', 'shell', 'exec_command', 'local_shell', 'powershell'].includes(toolName.toLowerCase()))
|
|
184
|
+
return undefined;
|
|
185
|
+
try {
|
|
186
|
+
const fields = JSON.parse(argumentsText);
|
|
187
|
+
const command = fields?.command ?? fields?.cmd;
|
|
188
|
+
if (typeof command === 'string' && command.trim())
|
|
189
|
+
return stripControls(command);
|
|
190
|
+
if (Array.isArray(command) && command.every(part => typeof part === 'string') && command.length)
|
|
191
|
+
return stripControls(command.join(' '));
|
|
192
|
+
}
|
|
193
|
+
catch { /* not JSON */ }
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
101
196
|
/** The summary line this view shows for a request payload. */
|
|
102
197
|
export function requestSummary(request) {
|
|
103
198
|
const payload = request.payload;
|
|
@@ -302,21 +397,21 @@ export function projectEvent(event, context) {
|
|
|
302
397
|
const toolCallId = text(payload.id, `event-${event.sequence}`);
|
|
303
398
|
const rawArguments = text(payload.arguments);
|
|
304
399
|
const inputText = clamp(stripControls(parseArguments(rawArguments)), VSCODE_CHAT_LIMITS.partCharacters);
|
|
305
|
-
const
|
|
400
|
+
const titles = toolTitles(toolName, rawArguments);
|
|
401
|
+
const commandLine = shellCommand(toolName, rawArguments);
|
|
306
402
|
return [{
|
|
307
403
|
emit: {
|
|
308
404
|
part: 'tool',
|
|
309
405
|
toolName,
|
|
310
406
|
toolCallId,
|
|
311
|
-
invocationMessage:
|
|
407
|
+
invocationMessage: titles.invocation,
|
|
408
|
+
pastTenseMessage: titles.pastTense,
|
|
312
409
|
inputText,
|
|
313
410
|
isComplete: false,
|
|
411
|
+
...(commandLine ? { commandLine } : {}),
|
|
314
412
|
},
|
|
315
413
|
needs: 'chatParticipantAdditions',
|
|
316
|
-
fallback: [{
|
|
317
|
-
part: 'progress',
|
|
318
|
-
value: clamp(subject ? `${toolName} · ${subject}` : toolName, VSCODE_CHAT_LIMITS.titleCharacters),
|
|
319
|
-
}],
|
|
414
|
+
fallback: [{ part: 'progress', value: titles.invocation }],
|
|
320
415
|
}];
|
|
321
416
|
}
|
|
322
417
|
// crates/runtime/src/event.rs `AgentEvent::ToolCallCompleted`.
|
|
@@ -328,13 +423,16 @@ export function projectEvent(event, context) {
|
|
|
328
423
|
const toolCallId = text(payload.id, `event-${event.sequence}`);
|
|
329
424
|
const output = clamp(stripControls(text(payload.output)), VSCODE_CHAT_LIMITS.partCharacters);
|
|
330
425
|
const isError = payload.is_error === true;
|
|
426
|
+
// The titles stay the ones the call started with (the writer keeps them per call); a completion that names its
|
|
427
|
+
// tool without having started on this stream is titled by that name.
|
|
428
|
+
const titles = toolName ? toolTitles(toolName, text(payload.arguments)) : undefined;
|
|
331
429
|
return [{
|
|
332
430
|
emit: {
|
|
333
431
|
part: 'tool',
|
|
334
432
|
toolName,
|
|
335
433
|
toolCallId,
|
|
336
|
-
invocationMessage:
|
|
337
|
-
pastTenseMessage:
|
|
434
|
+
invocationMessage: titles?.invocation ?? '',
|
|
435
|
+
pastTenseMessage: titles?.pastTense ?? '',
|
|
338
436
|
isError,
|
|
339
437
|
isComplete: true,
|
|
340
438
|
resultText: output,
|
|
@@ -410,6 +508,10 @@ export function projectEvent(event, context) {
|
|
|
410
508
|
case 'request_resolved': {
|
|
411
509
|
const requestId = typeof payload.request_id === 'number' ? payload.request_id : Number.NaN;
|
|
412
510
|
const response = payload.response;
|
|
511
|
+
// An allowed approval says nothing more, as in VS Code's own chat: its tool row carries on and is the record.
|
|
512
|
+
// A denial, and an answer to any other request, is said once.
|
|
513
|
+
if (response?.kind === 'approval' && response.decision !== 'deny')
|
|
514
|
+
return [];
|
|
413
515
|
const sentence = resolutionSentence(response, context.pendingRequest?.(requestId), requestId);
|
|
414
516
|
return [stable({ part: 'markdown', value: `\n\n_${sentence}_\n\n` })];
|
|
415
517
|
}
|
|
@@ -423,11 +525,9 @@ export function projectEvent(event, context) {
|
|
|
423
525
|
}];
|
|
424
526
|
}
|
|
425
527
|
// crates/runtime/src/event.rs `AgentEvent::Usage`.
|
|
528
|
+
// Token counts are not conversation: VS Code's own chat keeps them out of the transcript.
|
|
426
529
|
case 'usage':
|
|
427
|
-
return [
|
|
428
|
-
part: 'progress',
|
|
429
|
-
value: `prompt ${text(payload.prompt_tokens, '?')} · completion ${text(payload.completion_tokens, '?')} · total ${text(payload.total_tokens, '?')} tokens`,
|
|
430
|
-
})];
|
|
530
|
+
return [];
|
|
431
531
|
// crates/runtime/src/event.rs `AgentEvent::BackgroundOutput`.
|
|
432
532
|
case 'background_output': {
|
|
433
533
|
const chunk = fence(clamp(stripControls(text(payload.chunk)), VSCODE_CHAT_LIMITS.partCharacters));
|
|
@@ -438,8 +538,9 @@ export function projectEvent(event, context) {
|
|
|
438
538
|
})];
|
|
439
539
|
}
|
|
440
540
|
// crates/harness/src/server.rs `submit_claimed`.
|
|
541
|
+
// The live writer drops it when the person stopped the turn (VS Code marks a stopped response itself).
|
|
441
542
|
case 'turn_interrupted':
|
|
442
|
-
return [stable({ part: '
|
|
543
|
+
return [stable({ part: 'warning', value: 'The turn was interrupted.' })];
|
|
443
544
|
// crates/harness/src/server.rs `submit_claimed` emits `turn_failed`. The
|
|
444
545
|
// other four are the error kinds the donor-neutral reader in
|
|
445
546
|
// crates/frontend-model/src/transcript.rs treats identically:
|
|
@@ -555,6 +656,23 @@ function messageText(message) {
|
|
|
555
656
|
* supercode's own agent loop, and a hosted harness has none. So a resumed
|
|
556
657
|
* Claude Code session's turns are here or nowhere.
|
|
557
658
|
*/
|
|
659
|
+
/**
|
|
660
|
+
* A "user" message no person typed: Claude Code's `<task-notification>` (a background task reporting back) or the
|
|
661
|
+
* editor's automatic tripwire nudge. Neither is shown as the person's words. A task notification is said as one
|
|
662
|
+
* short line ("Background task completed: <its summary>"); a nudge is not shown. Undefined for a person's message.
|
|
663
|
+
*/
|
|
664
|
+
export function automaticPrompt(body) {
|
|
665
|
+
const trimmed = body.trimStart();
|
|
666
|
+
if (trimmed.startsWith('<task-notification>')) {
|
|
667
|
+
const tag = (name) => clamp(stripControls(new RegExp(`<${name}>([\\s\\S]*?)</${name}>`).exec(trimmed)?.[1]?.trim() ?? ''), 200);
|
|
668
|
+
const summary = tag('summary');
|
|
669
|
+
const status = tag('status');
|
|
670
|
+
return { notice: `_Background task${status ? ` ${status}` : ''}${summary ? `: ${summary}` : ''}_` };
|
|
671
|
+
}
|
|
672
|
+
if (trimmed.startsWith('[Volter editor tripwire'))
|
|
673
|
+
return {};
|
|
674
|
+
return undefined;
|
|
675
|
+
}
|
|
558
676
|
export function projectReplayTurns(events, granted, context) {
|
|
559
677
|
const turns = [];
|
|
560
678
|
let streamedText;
|
|
@@ -562,6 +680,20 @@ export function projectReplayTurns(events, granted, context) {
|
|
|
562
680
|
if (event.kind === 'user_message') {
|
|
563
681
|
streamedText = undefined;
|
|
564
682
|
const payload = payloadOf(event);
|
|
683
|
+
const automatic = automaticPrompt(text(payload.text));
|
|
684
|
+
if (automatic) {
|
|
685
|
+
if (!automatic.notice)
|
|
686
|
+
continue;
|
|
687
|
+
if (turns.length === 0)
|
|
688
|
+
turns.push({ role: 'user', prompt: HISTORY_MARKER_PROMPT });
|
|
689
|
+
const last = turns[turns.length - 1];
|
|
690
|
+
const notice = { part: 'markdown', value: `\n\n${automatic.notice}\n\n` };
|
|
691
|
+
if (last.role === 'assistant' && last.parts)
|
|
692
|
+
last.parts.push(notice);
|
|
693
|
+
else
|
|
694
|
+
turns.push({ role: 'assistant', parts: [notice] });
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
565
697
|
turns.push({ role: 'user', prompt: clamp(stripControls(text(payload.text)), VSCODE_CHAT_LIMITS.partCharacters) });
|
|
566
698
|
continue;
|
|
567
699
|
}
|
|
@@ -652,6 +784,12 @@ export function projectHistory(history, granted, pendingToolNames = new Set()) {
|
|
|
652
784
|
pushAssistant([{ part: 'markdown', value: clamp(stripControls(text), VSCODE_CHAT_LIMITS.partCharacters) }]);
|
|
653
785
|
continue;
|
|
654
786
|
}
|
|
787
|
+
const automatic = automaticPrompt(body);
|
|
788
|
+
if (automatic) {
|
|
789
|
+
if (automatic.notice)
|
|
790
|
+
pushAssistant([{ part: 'markdown', value: `\n\n${automatic.notice}\n\n` }]);
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
655
793
|
toolRows.clear();
|
|
656
794
|
turns.push({ role: 'user', prompt: clamp(stripControls(body), VSCODE_CHAT_LIMITS.partCharacters) });
|
|
657
795
|
continue;
|
|
@@ -675,14 +813,15 @@ export function projectHistory(history, granted, pendingToolNames = new Set()) {
|
|
|
675
813
|
pushAssistant([{ part: 'markdown', value: clamp(stripControls(body), VSCODE_CHAT_LIMITS.partCharacters) }]);
|
|
676
814
|
}
|
|
677
815
|
for (const call of message.tool_calls ?? []) {
|
|
678
|
-
const
|
|
679
|
-
const
|
|
816
|
+
const titles = toolTitles(call.function.name, call.function.arguments);
|
|
817
|
+
const commandLine = shellCommand(call.function.name, call.function.arguments);
|
|
680
818
|
const row = {
|
|
681
819
|
part: 'tool',
|
|
682
820
|
toolName: call.function.name,
|
|
683
821
|
toolCallId: call.id,
|
|
684
|
-
invocationMessage:
|
|
685
|
-
pastTenseMessage:
|
|
822
|
+
invocationMessage: titles.invocation,
|
|
823
|
+
pastTenseMessage: titles.pastTense,
|
|
824
|
+
...(commandLine ? { commandLine } : {}),
|
|
686
825
|
inputText: clamp(stripControls(parseArguments(call.function.arguments)), VSCODE_CHAT_LIMITS.partCharacters),
|
|
687
826
|
isComplete: true,
|
|
688
827
|
};
|
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.49",
|
|
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",
|
|
@@ -190,7 +190,7 @@
|
|
|
190
190
|
},
|
|
191
191
|
"supercodeFrontendContract": {
|
|
192
192
|
"name": "@volter/supercode-frontend",
|
|
193
|
-
"version": "0.2.
|
|
193
|
+
"version": "0.2.9",
|
|
194
194
|
"mode": "generated-artifact"
|
|
195
195
|
},
|
|
196
196
|
"license": "MIT OR Apache-2.0"
|
|
@@ -251,6 +251,8 @@
|
|
|
251
251
|
"Reply": {"kind": "object", "fields": {"reply": {"type": "string"}}},
|
|
252
252
|
"Interrupted": {"kind": "object", "fields": {"interrupted": {"type": "boolean"}}},
|
|
253
253
|
"Queued": {"kind": "object", "fields": {"queued": {"type": "boolean"}}},
|
|
254
|
+
"TaskParams": {"kind": "object", "fields": {"task_id": {"type": "string"}}},
|
|
255
|
+
"Stopped": {"kind": "object", "fields": {"stopped": {"type": "boolean"}}},
|
|
254
256
|
"Closed": {"kind": "object", "fields": {"closed": {"type": "boolean"}}}
|
|
255
257
|
},
|
|
256
258
|
"methods": [
|
|
@@ -260,6 +262,7 @@
|
|
|
260
262
|
{"id": "submit", "wire": "frontend.v2.submit", "sdk_action": "input", "params": "MultimodalPromptParams", "result": "Reply", "compatibility": "completion_waiting_alias", "transports": ["local", "http", "websocket", "acp"]},
|
|
261
263
|
{"id": "interrupt", "wire": "frontend.v2.interrupt", "sdk_action": "interrupt", "params": "Empty", "result": "Interrupted", "transports": ["local", "http", "websocket", "acp"]},
|
|
262
264
|
{"id": "steer", "wire": "frontend.v2.steer", "sdk_action": "steer", "params": "PromptParams", "result": "Queued", "transports": ["local", "http", "websocket", "acp"]},
|
|
265
|
+
{"id": "stop_task", "wire": "frontend.v2.stop_task", "sdk_action": "stop_task", "params": "TaskParams", "result": "Stopped", "transports": ["local", "http", "websocket", "acp"]},
|
|
263
266
|
{"id": "respond", "wire": "frontend.v2.respond", "sdk_action": "respond", "params": "RespondParams", "result": "Accepted", "transports": ["local", "http", "websocket", "acp"]},
|
|
264
267
|
{"id": "invoke", "wire": "frontend.v2.invoke", "sdk_action": "input", "params": "InvokeParams", "result": "FrontendOperationResult", "transports": ["local", "http", "websocket", "acp"]},
|
|
265
268
|
{"id": "lease", "wire": "frontend.v2.lease", "sdk_action": "events", "params": "Empty", "result": "RuntimeLeaseSnapshot", "transports": ["local", "http", "websocket", "acp"]},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @generated by sdk/frontend/generate.mjs from contract.json. Do not edit.
|
|
2
|
-
import type { Accepted, Closed, FrontendAttachSnapshot, FrontendOperationInvocation, FrontendOperationResult, FrontendResponse, FrontendRuntimeDescriptor, FrontendWireMethod, Interrupted, Queued, Reply, RuntimeLeaseSnapshot } from './generated.mjs';
|
|
2
|
+
import type { Accepted, Closed, FrontendAttachSnapshot, FrontendOperationInvocation, FrontendOperationResult, FrontendResponse, FrontendRuntimeDescriptor, FrontendWireMethod, Interrupted, Queued, Reply, RuntimeLeaseSnapshot, Stopped } from './generated.mjs';
|
|
3
3
|
export abstract class GeneratedFrontendClient<Options = unknown> {
|
|
4
4
|
abstract rpc(method: FrontendWireMethod, params?: Record<string, unknown>, options?: Options): Promise<unknown>;
|
|
5
5
|
describe(options?: Options): Promise<FrontendRuntimeDescriptor>;
|
|
@@ -8,6 +8,7 @@ export abstract class GeneratedFrontendClient<Options = unknown> {
|
|
|
8
8
|
submit(prompt: string, options?: Options & {imageUrls?: string[]}): Promise<Reply>;
|
|
9
9
|
interrupt(options?: Options): Promise<Interrupted>;
|
|
10
10
|
steer(prompt: string, options?: Options): Promise<Queued>;
|
|
11
|
+
stopTask(taskId: string, options?: Options): Promise<Stopped>;
|
|
11
12
|
respond(response: FrontendResponse, options?: Options): Promise<Accepted>;
|
|
12
13
|
invoke(operation: FrontendOperationInvocation, options?: Options): Promise<FrontendOperationResult>;
|
|
13
14
|
lease(options?: Options): Promise<RuntimeLeaseSnapshot>;
|
|
@@ -9,6 +9,7 @@ export class GeneratedFrontendClient {
|
|
|
9
9
|
submit(prompt, { imageUrls, ...options } = {}) { return this.rpc(FRONTEND_METHODS.submit, { prompt, ...(imageUrls?.length ? {image_urls:imageUrls} : {}) }, options); }
|
|
10
10
|
interrupt(options) { return this.rpc(FRONTEND_METHODS.interrupt, {}, options); }
|
|
11
11
|
steer(prompt, options) { return this.rpc(FRONTEND_METHODS.steer, { prompt }, options); }
|
|
12
|
+
stopTask(taskId, options) { return this.rpc(FRONTEND_METHODS.stop_task, { task_id: taskId }, options); }
|
|
12
13
|
respond(response, options) { return this.rpc(FRONTEND_METHODS.respond, { response }, options); }
|
|
13
14
|
invoke(operation, options) { return this.rpc(FRONTEND_METHODS.invoke, { operation }, options); }
|
|
14
15
|
lease(options) { return this.rpc(FRONTEND_METHODS.lease, {}, options); }
|
|
@@ -139,8 +139,14 @@ export interface Interrupted {
|
|
|
139
139
|
export interface Queued {
|
|
140
140
|
queued: boolean;
|
|
141
141
|
}
|
|
142
|
+
export interface TaskParams {
|
|
143
|
+
task_id: string;
|
|
144
|
+
}
|
|
145
|
+
export interface Stopped {
|
|
146
|
+
stopped: boolean;
|
|
147
|
+
}
|
|
142
148
|
export interface Closed {
|
|
143
149
|
closed: boolean;
|
|
144
150
|
}
|
|
145
|
-
export type FrontendMethodId = 'describe' | 'attach' | 'send_input' | 'submit' | 'interrupt' | 'steer' | 'respond' | 'invoke' | 'lease' | 'acquire_control' | 'take_control' | 'heartbeat' | 'detach' | 'close';
|
|
146
|
-
export type FrontendWireMethod = 'frontend.v2.describe' | 'frontend.v2.attach' | 'frontend.v2.send_input' | 'frontend.v2.submit' | 'frontend.v2.interrupt' | 'frontend.v2.steer' | 'frontend.v2.respond' | 'frontend.v2.invoke' | 'frontend.v2.lease' | 'frontend.v2.acquire_control' | 'frontend.v2.take_control' | 'frontend.v2.heartbeat' | 'frontend.v2.detach' | 'frontend.v2.close';
|
|
151
|
+
export type FrontendMethodId = 'describe' | 'attach' | 'send_input' | 'submit' | 'interrupt' | 'steer' | 'stop_task' | 'respond' | 'invoke' | 'lease' | 'acquire_control' | 'take_control' | 'heartbeat' | 'detach' | 'close';
|
|
152
|
+
export type FrontendWireMethod = 'frontend.v2.describe' | 'frontend.v2.attach' | 'frontend.v2.send_input' | 'frontend.v2.submit' | 'frontend.v2.interrupt' | 'frontend.v2.steer' | 'frontend.v2.stop_task' | 'frontend.v2.respond' | 'frontend.v2.invoke' | 'frontend.v2.lease' | 'frontend.v2.acquire_control' | 'frontend.v2.take_control' | 'frontend.v2.heartbeat' | 'frontend.v2.detach' | 'frontend.v2.close';
|
|
@@ -7,6 +7,7 @@ export const FRONTEND_METHODS = Object.freeze({
|
|
|
7
7
|
"submit": "frontend.v2.submit",
|
|
8
8
|
"interrupt": "frontend.v2.interrupt",
|
|
9
9
|
"steer": "frontend.v2.steer",
|
|
10
|
+
"stop_task": "frontend.v2.stop_task",
|
|
10
11
|
"respond": "frontend.v2.respond",
|
|
11
12
|
"invoke": "frontend.v2.invoke",
|
|
12
13
|
"lease": "frontend.v2.lease",
|