@volter/supercode-frontend-vscode 0.1.63 → 0.1.65
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 +110 -72
- package/dist/extension.js +245 -104
- package/dist/host-controls.d.ts +0 -1
- package/dist/proposals.d.ts +1 -1
- package/dist/proposals.js +18 -2
- package/dist/session.d.ts +2 -21
- package/dist/session.js +3 -35
- package/dist/view.d.ts +58 -5
- package/dist/view.js +234 -109
- package/package.json +5 -2
package/dist/view.js
CHANGED
|
@@ -137,6 +137,11 @@ export function toolTitles(toolName, argumentsText) {
|
|
|
137
137
|
const command = field('command', 'cmd');
|
|
138
138
|
return command ? both(`Running ${code(command)}`, `Ran ${code(command)}`) : both(`Running a command`, `Ran a command`);
|
|
139
139
|
}
|
|
140
|
+
// Code-OSS's own Claude host's words for the shell's output and stop rows (`claudeToolDisplay.ts`).
|
|
141
|
+
if (name === 'bashoutput')
|
|
142
|
+
return both('Reading shell output', 'Read shell output');
|
|
143
|
+
if (name === 'killbash')
|
|
144
|
+
return both('Kill shell command', 'Kill shell command');
|
|
140
145
|
if (name === 'read' && file) {
|
|
141
146
|
const offset = typeof fields.offset === 'number' ? fields.offset : undefined;
|
|
142
147
|
const limit = typeof fields.limit === 'number' ? fields.limit : undefined;
|
|
@@ -354,9 +359,18 @@ export function subagentCall(toolName, argumentsText) {
|
|
|
354
359
|
const prompt = field('prompt', VSCODE_CHAT_LIMITS.partCharacters);
|
|
355
360
|
return { ...(description ? { description } : {}), ...(agentName ? { agentName } : {}), ...(prompt ? { prompt } : {}) };
|
|
356
361
|
}
|
|
357
|
-
/**
|
|
362
|
+
/**
|
|
363
|
+
* The command line of a shell tool call, read from its own arguments; undefined for any other tool. Claude Code's
|
|
364
|
+
* BashOutput and KillBash are terminal rows too, as Code-OSS's own Claude host draws them (`claudeToolDisplay.ts`
|
|
365
|
+
* `TOOL_ROWS`): their command line is their `command`, else their input (`getClaudeToolInputString`).
|
|
366
|
+
*/
|
|
358
367
|
export function shellCommand(toolName, argumentsText) {
|
|
359
|
-
|
|
368
|
+
const name = toolName.toLowerCase();
|
|
369
|
+
if (name === 'bashoutput' || name === 'killbash') {
|
|
370
|
+
const command = argumentFields(argumentsText).command;
|
|
371
|
+
return stripControls(typeof command === 'string' && command.trim() ? command : parseArguments(argumentsText)) || undefined;
|
|
372
|
+
}
|
|
373
|
+
if (!['bash', 'shell', 'exec_command', 'local_shell', 'powershell'].includes(name))
|
|
360
374
|
return undefined;
|
|
361
375
|
try {
|
|
362
376
|
const fields = JSON.parse(argumentsText);
|
|
@@ -644,6 +658,24 @@ export function requestSubject(request) {
|
|
|
644
658
|
return clamp(stripControls(subject), 120);
|
|
645
659
|
return undefined;
|
|
646
660
|
}
|
|
661
|
+
/** The words an error event states (`message`, else `error` or `reason`), bounded; '' when it states none. */
|
|
662
|
+
function errorMessage(payload) {
|
|
663
|
+
const said = [payload.message, payload.error, payload.reason].find((value) => typeof value === 'string' && value.trim() !== '');
|
|
664
|
+
return said ? clamp(stripControls(said.trim()), VSCODE_CHAT_LIMITS.partCharacters) : '';
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* How a turn that did not succeed ends the response its request handler runs, as VS Code's own agent host ends one
|
|
668
|
+
* (`agentHostSessionHandler.ts` `_getTurnErrorDetails`): a failed turn is the response's error, an interrupted one its
|
|
669
|
+
* warning-level, expected error. Undefined for any other event.
|
|
670
|
+
*/
|
|
671
|
+
export function turnOutcome(event) {
|
|
672
|
+
const payload = payloadOf(event);
|
|
673
|
+
if (event.kind === 'turn_interrupted')
|
|
674
|
+
return { message: errorMessage(payload) || 'Turn interrupted.', interrupted: true };
|
|
675
|
+
if (event.kind === 'turn_failed')
|
|
676
|
+
return { message: errorMessage(payload) || 'Turn failed.', interrupted: false };
|
|
677
|
+
return undefined;
|
|
678
|
+
}
|
|
647
679
|
/** Pending requests belong to the live runtime, even when saved transcript
|
|
648
680
|
* history predates them. Rehydrate their controls before waiting for new events. */
|
|
649
681
|
export function projectPendingRequests(requests, context) {
|
|
@@ -669,9 +701,11 @@ export function projectEvent(event, context) {
|
|
|
669
701
|
// (a subagent's own input, `parent_id`: what it was asked is on its row)
|
|
670
702
|
if (text(payload.parent_id))
|
|
671
703
|
return [];
|
|
704
|
+
// A harness record (a background task's notification, an interruption) is Code-OSS's system notification row
|
|
705
|
+
// (`noticeLine`).
|
|
672
706
|
const notice = notPersonNotice(payload);
|
|
673
707
|
if (notice !== undefined)
|
|
674
|
-
return notice ? [stable(
|
|
708
|
+
return notice && firstNotice(context, notifiedCall(payload)) ? [stable(noticeLine(notice))] : [];
|
|
675
709
|
if (context.liveUserMessages) {
|
|
676
710
|
const body = text(payload.text);
|
|
677
711
|
const prefix = '\n\n**Additional input**\n\n';
|
|
@@ -793,11 +827,9 @@ export function projectEvent(event, context) {
|
|
|
793
827
|
// crates/harness/src/server.rs `FrontendRequestBroker::publish`.
|
|
794
828
|
case 'request': {
|
|
795
829
|
const request = (payload.request ?? payload);
|
|
830
|
+
// (a request with no id cannot be answered, so it is a malformed record, not conversation)
|
|
796
831
|
if (!request || typeof request.id !== 'number') {
|
|
797
|
-
return [stable({
|
|
798
|
-
part: 'markdown',
|
|
799
|
-
value: `**supercode** · malformed request event\n\n${fence(opaqueText(event.payload), 'json')}`,
|
|
800
|
-
})];
|
|
832
|
+
return [stable({ part: 'unshown', value: `malformed request event #${event.sequence}: ${opaqueText(event.payload)}` })];
|
|
801
833
|
}
|
|
802
834
|
const summary = requestSummary(request);
|
|
803
835
|
const answers = requestAnswers(request, context.connectionId);
|
|
@@ -810,19 +842,22 @@ export function projectEvent(event, context) {
|
|
|
810
842
|
// The host capability decides whether these original answers can use
|
|
811
843
|
// a native in-turn confirmation card. Stock confirmation clicks start a
|
|
812
844
|
// new chat request, so stable command buttons remain the fallback.
|
|
813
|
-
if (context.canRespond === false || (request.payload && typeof request.payload === 'object' && !Array.isArray(request.payload) && request.payload.observe_only === true)) {
|
|
814
|
-
return [stable({
|
|
815
|
-
part: 'markdown',
|
|
816
|
-
value: `\n\n**${title}**\n\n${summary}\n\n_This runtime answers its own requests; Volter Harness is observing._\n\n`,
|
|
817
|
-
})];
|
|
818
|
-
}
|
|
819
845
|
const buttons = {
|
|
820
846
|
part: 'answers',
|
|
847
|
+
connectionId: context.connectionId,
|
|
848
|
+
requestId: request.id,
|
|
821
849
|
approval: request.kind === 'approval',
|
|
822
850
|
title: clamp(title, VSCODE_CHAT_LIMITS.titleCharacters),
|
|
823
851
|
message: summary,
|
|
824
852
|
answers,
|
|
825
853
|
};
|
|
854
|
+
// A request this window cannot answer (another client, the runtime's owner, answers it) is still drawn: its
|
|
855
|
+
// title and summary, without controls. Code-OSS has no read-only confirmation; it draws another client's call
|
|
856
|
+
// as its row, "Running {0} on another client…", with a cancel action (`stateToProgressAdapter.ts`
|
|
857
|
+
// `toolCallStateToInvocation`), which the extension API cannot draw.
|
|
858
|
+
if (context.canRespond === false || (request.payload && typeof request.payload === 'object' && !Array.isArray(request.payload) && request.payload.observe_only === true)) {
|
|
859
|
+
return [stable({ ...buttons, answers: [] })];
|
|
860
|
+
}
|
|
826
861
|
// A harness's questions: VS Code's own carousel, answered inside the running turn.
|
|
827
862
|
if (asked) {
|
|
828
863
|
return [{
|
|
@@ -833,16 +868,15 @@ export function projectEvent(event, context) {
|
|
|
833
868
|
}
|
|
834
869
|
return [stable(buttons)];
|
|
835
870
|
}
|
|
836
|
-
// crates/harness/src/server.rs `FrontendRequestBroker::respond`.
|
|
871
|
+
// crates/harness/src/server.rs `FrontendRequestBroker::respond`. A card answered through itself shows what was
|
|
872
|
+
// chosen (`chatConfirmationContentPart`, `chatQuestionCarouselPart`). One answered any other way (another window,
|
|
873
|
+
// Auto approve, a typed reply) cannot be retired through the extension API, so its answer is said beneath it, once,
|
|
874
|
+
// as one plain line; the writer decides which (`resolution`).
|
|
837
875
|
case 'request_resolved': {
|
|
838
876
|
const requestId = typeof payload.request_id === 'number' ? payload.request_id : Number.NaN;
|
|
839
877
|
const response = payload.response;
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
if (response?.kind === 'approval' && response.decision !== 'deny')
|
|
843
|
-
return [];
|
|
844
|
-
const sentence = resolutionSentence(response, context.pendingRequest?.(requestId), requestId);
|
|
845
|
-
return [stable({ part: 'markdown', value: `\n\n_${sentence}_\n\n` })];
|
|
878
|
+
const value = plain(resolutionSentence(response, context.pendingRequest?.(requestId), requestId));
|
|
879
|
+
return [stable({ part: 'resolution', connectionId: context.connectionId, requestId, value })];
|
|
846
880
|
}
|
|
847
881
|
// crates/runtime/src/event.rs `AgentEvent::CacheWarning`.
|
|
848
882
|
case 'cache_warning': {
|
|
@@ -872,23 +906,25 @@ export function projectEvent(event, context) {
|
|
|
872
906
|
// Token counts are not conversation: VS Code's own chat keeps them out of the transcript.
|
|
873
907
|
case 'usage':
|
|
874
908
|
return [];
|
|
875
|
-
//
|
|
876
|
-
// its
|
|
909
|
+
// A harness's listing of its background tasks is not conversation: Code-OSS's own Claude host draws a background
|
|
910
|
+
// command as its call's terminal row, and BashOutput and KillBash as rows of their own (`claudeToolDisplay.ts`).
|
|
911
|
+
// Current Claude Code stops a task with TaskStop, an ordinary row there and here.
|
|
877
912
|
case 'background_tasks':
|
|
878
913
|
return [];
|
|
879
|
-
//
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
return [
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
914
|
+
// sdk/interchange/agent-background.mjs `status`: the output a `background_status` call drained, which that call's
|
|
915
|
+
// own result also carries (`output`, `output_truncated`). It is that row's output, as a VS Code background
|
|
916
|
+
// terminal's output is its own row's, and nothing more in the transcript.
|
|
917
|
+
case 'background_output':
|
|
918
|
+
return [];
|
|
919
|
+
// crates/harness/src/server.rs `submit_claimed`. VS Code's own agent host draws an interrupted turn as its
|
|
920
|
+
// response's warning-level error (`agentHostSessionHandler.ts` `_getTurnErrorDetails`, `executionInterrupted`):
|
|
921
|
+
// the handler's turn returns it as its result (`turnOutcome`), and so do a re-joined response and a saved one
|
|
922
|
+
// where the host keeps their result (`chatSessionResponses`); elsewhere they show its warning part. The live
|
|
923
|
+
// writer drops it when the person stopped the turn (VS Code marks a stopped response itself).
|
|
924
|
+
case 'turn_interrupted': {
|
|
925
|
+
const value = turnOutcome(event).message;
|
|
926
|
+
return [{ emit: { part: 'warning', value }, needs: 'chatParticipantAdditions', fallback: [{ part: 'markdown', value }] }];
|
|
887
927
|
}
|
|
888
|
-
// crates/harness/src/server.rs `submit_claimed`.
|
|
889
|
-
// The live writer drops it when the person stopped the turn (VS Code marks a stopped response itself).
|
|
890
|
-
case 'turn_interrupted':
|
|
891
|
-
return [stable({ part: 'warning', value: 'The turn was interrupted.' })];
|
|
892
928
|
// crates/harness/src/server.rs `submit_claimed` emits `turn_failed`. The
|
|
893
929
|
// other four are the error kinds the donor-neutral reader in
|
|
894
930
|
// sdk/interchange/frontend-transcript.mjs treats identically:
|
|
@@ -897,31 +933,41 @@ export function projectEvent(event, context) {
|
|
|
897
933
|
// `runtime_disconnected`, which a frontend synthesizes for itself
|
|
898
934
|
// (sdk/interchange/frontend-app.mjs). `stream_error` has no producer in this
|
|
899
935
|
// repository and is read only because that reader reads it.
|
|
936
|
+
//
|
|
937
|
+
// A failed turn is VS Code's own request error (`ChatResult.errorDetails`) on the turn the handler runs
|
|
938
|
+
// (`turnOutcome`), and on a re-joined or saved response where the host keeps their result (`chatSessionResponses`);
|
|
939
|
+
// elsewhere it is the warning part. The error's own words, as VS Code's agent host shows a turn's error, never
|
|
940
|
+
// the event kind's name.
|
|
900
941
|
case 'turn_failed':
|
|
901
942
|
case 'stream_error':
|
|
902
943
|
case 'transport_error':
|
|
903
944
|
case 'watch_error':
|
|
904
945
|
case 'runtime_disconnected': {
|
|
905
|
-
const
|
|
906
|
-
|
|
946
|
+
const value = turnOutcome(event)?.message ?? errorMessage(payload);
|
|
947
|
+
if (!value)
|
|
948
|
+
return [];
|
|
907
949
|
return [{
|
|
908
950
|
emit: { part: 'warning', value },
|
|
909
951
|
needs: 'chatParticipantAdditions',
|
|
910
952
|
fallback: [{ part: 'markdown', value: `> ⚠ ${value}` }],
|
|
911
953
|
}];
|
|
912
954
|
}
|
|
913
|
-
// Declared by the runtime descriptor's `display.event_kinds`
|
|
914
|
-
//
|
|
915
|
-
//
|
|
916
|
-
|
|
955
|
+
// Declared by the runtime descriptor's `display.event_kinds` (`agent-runtime-host.mjs` `displayKinds`); nothing
|
|
956
|
+
// in this repository produces them, so their payload is read for the words it states and never shown as JSON.
|
|
957
|
+
// A scheduled prompt is a turn the runtime starts itself: its start is a labelled line naming the prompt, its
|
|
958
|
+
// deferral and completion are lifecycle like `turn_started` / `turn_completed`, and an error is a warning.
|
|
959
|
+
case 'scheduled_prompt_started': {
|
|
960
|
+
const value = clamp(stripControls(text(payload.prompt ?? payload.name ?? payload.message)), VSCODE_CHAT_LIMITS.titleCharacters);
|
|
961
|
+
// (labelled, so the runtime's prompt is not read as the agent's words)
|
|
962
|
+
return value ? [stable(noticeLine(`Scheduled prompt: ${value}`))] : [];
|
|
963
|
+
}
|
|
917
964
|
case 'scheduled_prompt_deferred':
|
|
918
965
|
case 'scheduled_prompt_completed':
|
|
919
|
-
return [
|
|
920
|
-
part: 'progress',
|
|
921
|
-
value: `${event.kind.replaceAll('_', ' ')} · ${opaqueText(event.payload, 256)}`,
|
|
922
|
-
})];
|
|
966
|
+
return [];
|
|
923
967
|
case 'scheduler_error': {
|
|
924
|
-
const value =
|
|
968
|
+
const value = errorMessage(payload);
|
|
969
|
+
if (!value)
|
|
970
|
+
return [];
|
|
925
971
|
return [{
|
|
926
972
|
emit: { part: 'warning', value },
|
|
927
973
|
needs: 'chatParticipantAdditions',
|
|
@@ -933,18 +979,23 @@ export function projectEvent(event, context) {
|
|
|
933
979
|
case 'native_event':
|
|
934
980
|
return [];
|
|
935
981
|
case 'status':
|
|
936
|
-
// A
|
|
937
|
-
//
|
|
938
|
-
//
|
|
982
|
+
// A background task's notification (`call_id` names the call that started it): a harness record, drawn as the
|
|
983
|
+
// `user_message` notices are (`noticeLine`), since a progress part would be hidden by whatever follows. Its
|
|
984
|
+
// answer belongs to that call's row (or to the agent's reply, for a subagent started in the background), so the
|
|
985
|
+
// notice is its first line, and the task's output file where it names one.
|
|
939
986
|
if (text(payload.call_id)) {
|
|
987
|
+
if (!firstNotice(context, text(payload.call_id)))
|
|
988
|
+
return [];
|
|
940
989
|
const lines = stripControls(text(payload.message)).split('\n');
|
|
941
990
|
const output = lines.find(line => line.startsWith('Output: ') && line.length > 8);
|
|
942
|
-
const line = [clamp((lines[0] ?? '').trim(), VSCODE_CHAT_LIMITS.titleCharacters), output].filter(Boolean).join('\n');
|
|
943
|
-
return line ? [stable(
|
|
991
|
+
const line = [clamp((lines[0] ?? '').trim(), VSCODE_CHAT_LIMITS.titleCharacters), output].filter(Boolean).join('\n\n');
|
|
992
|
+
return line ? [stable(noticeLine(clamp(line, VSCODE_CHAT_LIMITS.partCharacters)))] : [];
|
|
944
993
|
}
|
|
945
994
|
return [stable({ part: 'progress', value: clamp(stripControls(text(payload.message)), VSCODE_CHAT_LIMITS.partCharacters) })];
|
|
946
|
-
case 'warning':
|
|
947
|
-
|
|
995
|
+
case 'warning': {
|
|
996
|
+
const value = clamp(stripControls(text(payload.message)), VSCODE_CHAT_LIMITS.partCharacters);
|
|
997
|
+
return [{ emit: { part: 'warning', value }, needs: 'chatParticipantAdditions', fallback: [{ part: 'markdown', value: `> ⚠ ${value}` }] }];
|
|
998
|
+
}
|
|
948
999
|
// Not in this runtime's declared set; read the way
|
|
949
1000
|
// sdk/interchange/frontend-transcript.mjs reads a donor that emits them.
|
|
950
1001
|
case 'reasoning':
|
|
@@ -959,17 +1010,11 @@ export function projectEvent(event, context) {
|
|
|
959
1010
|
fallback: [{ part: 'markdown', value: `\n\n> ${body.replaceAll('\n', '\n> ')}\n\n` }],
|
|
960
1011
|
}];
|
|
961
1012
|
}
|
|
962
|
-
//
|
|
963
|
-
//
|
|
1013
|
+
// A kind this version does not know is a protocol record, not conversation, like `native_event` above: VS Code's
|
|
1014
|
+
// own chat shows none (its agent host drops a record it does not map). It goes to the output channel; the
|
|
1015
|
+
// runtime's history keeps it.
|
|
964
1016
|
default:
|
|
965
|
-
|
|
966
|
-
// next streamed delta lands against on the same line never closes, and
|
|
967
|
-
// it swallows everything after it — observed doing exactly that to an
|
|
968
|
-
// approval request.
|
|
969
|
-
return [stable({
|
|
970
|
-
part: 'markdown',
|
|
971
|
-
value: `\n\n_supercode event \`${clamp(event.kind, VSCODE_CHAT_LIMITS.titleCharacters)}\` is not displayable by this frontend version_\n\n${fence(opaqueText(event.payload), 'json')}\n\n`,
|
|
972
|
-
})];
|
|
1017
|
+
return [stable({ part: 'unshown', value: `event kind ${clamp(event.kind, VSCODE_CHAT_LIMITS.titleCharacters)} #${event.sequence} is not displayed: ${opaqueText(event.payload)}` })];
|
|
973
1018
|
}
|
|
974
1019
|
}
|
|
975
1020
|
/** Replace every part whose proposal the host did not grant with its fallback. */
|
|
@@ -984,10 +1029,11 @@ export function resolveParts(parts, granted) {
|
|
|
984
1029
|
return resolved;
|
|
985
1030
|
}
|
|
986
1031
|
/**
|
|
987
|
-
* The prompt of the marker turn that carries runtime history with no user turn
|
|
988
|
-
*
|
|
1032
|
+
* The prompt of the marker turn that carries runtime history with no user turn of its own: VS Code's own words for a
|
|
1033
|
+
* turn whose request is not in the transcript (`claudeReplayMapper.ts` `missingPromptPlaceholder`), so nothing is
|
|
1034
|
+
* presented as a person's words.
|
|
989
1035
|
*/
|
|
990
|
-
export const HISTORY_MARKER_PROMPT = '
|
|
1036
|
+
export const HISTORY_MARKER_PROMPT = 'Message content could not be retrieved';
|
|
991
1037
|
function messageText(message) {
|
|
992
1038
|
const content = message.content;
|
|
993
1039
|
if (typeof content === 'string')
|
|
@@ -1012,21 +1058,55 @@ function messageText(message) {
|
|
|
1012
1058
|
}
|
|
1013
1059
|
/**
|
|
1014
1060
|
* What a `user_message` shows when no person typed it: undefined for the person's words, '' to show nothing, else a
|
|
1015
|
-
* short notice. The harness's own records carry `source: 'harness'` (and a `notice` where one is shown) from the one
|
|
1061
|
+
* short notice, plain text drawn as a notice row (`noticeLine`). The harness's own records carry `source: 'harness'` (and a `notice` where one is shown) from the one
|
|
1016
1062
|
* provenance rule the runtime applies (`@volter/supercode-interchange/user-provenance`). The editor's automatic
|
|
1017
1063
|
* tripwire nudge travels through the person's input door, so the editor's frame is what names it.
|
|
1018
1064
|
*/
|
|
1019
1065
|
function notPersonNotice(payload) {
|
|
1020
1066
|
if (payload.source === 'harness')
|
|
1021
|
-
return
|
|
1067
|
+
return noticeText(text(payload.notice));
|
|
1022
1068
|
if (editorNudge(text(payload.text)))
|
|
1023
1069
|
return '';
|
|
1024
1070
|
return undefined;
|
|
1025
1071
|
}
|
|
1026
|
-
/**
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1072
|
+
/**
|
|
1073
|
+
* A notice as a row of the transcript: its own words, plain, as Code-OSS's system notification row
|
|
1074
|
+
* (`stateToProgressAdapter.ts` `systemNotificationToChatPart`). Not a progress part: Code-OSS hides a progress message
|
|
1075
|
+
* as soon as any other part follows it (`chatProgressContentPart.ts`), so the notice would never be seen.
|
|
1076
|
+
*/
|
|
1077
|
+
function noticeLine(notice) {
|
|
1078
|
+
const icon = noticeIcon(notice);
|
|
1079
|
+
return { part: 'notice', value: plain(notice), ...(icon ? { icon } : {}) };
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* The icon of a notice that reports no completion. Code-OSS's agent host picks one per notification kind
|
|
1083
|
+
* (`stateToProgressAdapter.ts` `systemNotificationToChatPart`), with the completion check as its default; `warning` here
|
|
1084
|
+
* follows, by analogy, the one it gives an automatic-approval review that was interrupted. None for the others.
|
|
1085
|
+
*/
|
|
1086
|
+
function noticeIcon(notice) {
|
|
1087
|
+
// (by analogy: upstream draws no harness interruption or failed background task as a notification of its own)
|
|
1088
|
+
if (notice === 'Turn interrupted.')
|
|
1089
|
+
return 'warning';
|
|
1090
|
+
if (/^Background task (failed|killed|stopped|cancell?ed|error)\b/i.test(notice))
|
|
1091
|
+
return 'warning';
|
|
1092
|
+
return undefined;
|
|
1093
|
+
}
|
|
1094
|
+
/** The call a background task's notification record names (`<tool-use-id>`), for `firstNotice`. */
|
|
1095
|
+
function notifiedCall(payload) {
|
|
1096
|
+
return /<tool-use-id>([^<]+)<\/tool-use-id>/.exec(text(payload.text))?.[1]?.trim() ?? '';
|
|
1097
|
+
}
|
|
1098
|
+
/** Whether this is the first notification of `call`'s task in this stream (always, for one that names no call). */
|
|
1099
|
+
function firstNotice(context, call) {
|
|
1100
|
+
if (!call || !context.notices)
|
|
1101
|
+
return true;
|
|
1102
|
+
if (context.notices.has(call))
|
|
1103
|
+
return false;
|
|
1104
|
+
context.notices.add(call);
|
|
1105
|
+
return true;
|
|
1106
|
+
}
|
|
1107
|
+
/** The rule's plain notice, bounded; '' for none. */
|
|
1108
|
+
function noticeText(notice) {
|
|
1109
|
+
return clamp(stripControls(notice ?? ''), VSCODE_CHAT_LIMITS.titleCharacters);
|
|
1030
1110
|
}
|
|
1031
1111
|
function editorNudge(body) {
|
|
1032
1112
|
return body.trimStart().startsWith('[Volter editor tripwire');
|
|
@@ -1037,6 +1117,18 @@ export function projectReplayTurns(events, granted, context, history = []) {
|
|
|
1037
1117
|
// Before the replay supplies a prompt, give its parts their own runtime
|
|
1038
1118
|
// history turn rather than attributing old approvals to the latest answer.
|
|
1039
1119
|
const turns = history.map(turn => ({ ...turn, parts: turn.parts?.slice() }));
|
|
1120
|
+
// A reopened chat names what each answer answered from the replay's own requests first: the session's maps hold
|
|
1121
|
+
// only what is pending now and the latest few resolved.
|
|
1122
|
+
const requests = new Map();
|
|
1123
|
+
for (const event of events) {
|
|
1124
|
+
if (event.kind !== 'request')
|
|
1125
|
+
continue;
|
|
1126
|
+
const request = (payloadOf(event).request ?? payloadOf(event));
|
|
1127
|
+
if (typeof request?.id === 'number')
|
|
1128
|
+
requests.set(request.id, request);
|
|
1129
|
+
}
|
|
1130
|
+
const known = context.pendingRequest;
|
|
1131
|
+
context = { ...context, notices: context.notices ?? new Set(), pendingRequest: (id) => requests.get(id) ?? known?.(id) };
|
|
1040
1132
|
// An approval the replay shows allowed is not drawn again: live, its card is hidden (Auto approve) or answered,
|
|
1041
1133
|
// and its allowed resolution says nothing more, so the tool row is the record (`request_resolved` above).
|
|
1042
1134
|
const allowed = new Set();
|
|
@@ -1050,28 +1142,43 @@ export function projectReplayTurns(events, granted, context, history = []) {
|
|
|
1050
1142
|
}
|
|
1051
1143
|
let replayTurn = false;
|
|
1052
1144
|
let streamedText;
|
|
1145
|
+
// A saved failure is its response's error only while nothing follows it: the next turn's start, or anything drawn
|
|
1146
|
+
// after it in that response, turns it back into its warning part where it stood, so an error never shows after a
|
|
1147
|
+
// later turn's content and a second failure never replaces the first. A re-joined response follows the same rule.
|
|
1148
|
+
const append = (turn, ...parts) => {
|
|
1149
|
+
const failed = turn.errorEvent;
|
|
1150
|
+
if (failed) {
|
|
1151
|
+
turn.error = undefined;
|
|
1152
|
+
turn.errorEvent = undefined;
|
|
1153
|
+
turn.parts.push(...resolveParts(projectEvent(failed, context), granted));
|
|
1154
|
+
}
|
|
1155
|
+
turn.parts.push(...parts);
|
|
1156
|
+
};
|
|
1053
1157
|
for (const event of events) {
|
|
1054
1158
|
if (event.kind === 'request') {
|
|
1055
1159
|
const request = (payloadOf(event).request ?? payloadOf(event));
|
|
1056
1160
|
if (typeof request?.id === 'number' && allowed.has(request.id))
|
|
1057
1161
|
continue;
|
|
1058
1162
|
}
|
|
1163
|
+
// (nor is its answer, which would answer a card that is not there)
|
|
1164
|
+
if (event.kind === 'request_resolved' && allowed.has(payloadOf(event).request_id))
|
|
1165
|
+
continue;
|
|
1059
1166
|
// (a subagent's own input is no turn of the conversation; `projectEvent` draws nothing for it)
|
|
1060
1167
|
if (event.kind === 'user_message' && !text(payloadOf(event).parent_id)) {
|
|
1061
1168
|
streamedText = undefined;
|
|
1062
1169
|
const payload = payloadOf(event);
|
|
1063
1170
|
const shown = notPersonNotice(payload);
|
|
1064
1171
|
if (shown !== undefined) {
|
|
1065
|
-
if (!shown)
|
|
1172
|
+
if (!shown || !firstNotice(context, notifiedCall(payload)))
|
|
1066
1173
|
continue;
|
|
1067
1174
|
if (!replayTurn) {
|
|
1068
1175
|
turns.push({ role: 'user', prompt: HISTORY_MARKER_PROMPT });
|
|
1069
1176
|
replayTurn = true;
|
|
1070
1177
|
}
|
|
1071
1178
|
const last = turns[turns.length - 1];
|
|
1072
|
-
const notice =
|
|
1179
|
+
const notice = noticeLine(shown);
|
|
1073
1180
|
if (last.role === 'assistant' && last.parts)
|
|
1074
|
-
last
|
|
1181
|
+
append(last, notice);
|
|
1075
1182
|
else
|
|
1076
1183
|
turns.push({ role: 'assistant', parts: [notice] });
|
|
1077
1184
|
continue;
|
|
@@ -1080,7 +1187,38 @@ export function projectReplayTurns(events, granted, context, history = []) {
|
|
|
1080
1187
|
replayTurn = true;
|
|
1081
1188
|
continue;
|
|
1082
1189
|
}
|
|
1083
|
-
|
|
1190
|
+
// A turn that failed or was interrupted is its saved response's own error, where the host shows one.
|
|
1191
|
+
const outcome = granted.has('chatSessionResponses') ? turnOutcome(event) : undefined;
|
|
1192
|
+
if (outcome) {
|
|
1193
|
+
streamedText = undefined;
|
|
1194
|
+
if (!replayTurn) {
|
|
1195
|
+
turns.push({ role: 'user', prompt: HISTORY_MARKER_PROMPT });
|
|
1196
|
+
replayTurn = true;
|
|
1197
|
+
}
|
|
1198
|
+
const last = turns[turns.length - 1];
|
|
1199
|
+
if (last.role === 'assistant') {
|
|
1200
|
+
append(last);
|
|
1201
|
+
last.error = outcome;
|
|
1202
|
+
last.errorEvent = event;
|
|
1203
|
+
}
|
|
1204
|
+
else {
|
|
1205
|
+
turns.push({ role: 'assistant', parts: [], error: outcome, errorEvent: event });
|
|
1206
|
+
}
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1209
|
+
// (the next turn's start ends the last failure's hold, as on a re-joined response)
|
|
1210
|
+
if (event.kind === 'turn_started') {
|
|
1211
|
+
const last = turns[turns.length - 1];
|
|
1212
|
+
if (last?.role === 'assistant' && last.errorEvent)
|
|
1213
|
+
append(last);
|
|
1214
|
+
}
|
|
1215
|
+
// (a record that is not conversation is no part of a turn: it goes to `unshown`, wherever it falls)
|
|
1216
|
+
const parts = resolveParts(projectEvent(event, context), granted).filter(part => {
|
|
1217
|
+
if (part.part !== 'unshown')
|
|
1218
|
+
return true;
|
|
1219
|
+
context.unshown?.(part.value);
|
|
1220
|
+
return false;
|
|
1221
|
+
});
|
|
1084
1222
|
if (parts.length === 0)
|
|
1085
1223
|
continue;
|
|
1086
1224
|
if (event.kind === 'text_delta' && parts[0]?.part === 'markdown') {
|
|
@@ -1099,23 +1237,24 @@ export function projectReplayTurns(events, granted, context, history = []) {
|
|
|
1099
1237
|
}
|
|
1100
1238
|
const last = turns[turns.length - 1];
|
|
1101
1239
|
if (last.role === 'assistant' && last.parts)
|
|
1102
|
-
last
|
|
1240
|
+
append(last, ...parts);
|
|
1103
1241
|
else
|
|
1104
1242
|
turns.push({ role: 'assistant', parts });
|
|
1105
1243
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1244
|
+
return boundTurns(turns);
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* The latest `historyTurns` turns. The earlier ones are left out without a word, as VS Code's own agent host shows a
|
|
1248
|
+
* transcript its harness truncated; a response the cut leaves without its request gets the marker request, as that
|
|
1249
|
+
* host gives one its missing-prompt placeholder (`claudeReplayMapper.ts` `missingPromptPlaceholder`).
|
|
1250
|
+
*/
|
|
1251
|
+
function boundTurns(turns) {
|
|
1252
|
+
if (turns.length <= VSCODE_CHAT_LIMITS.historyTurns)
|
|
1253
|
+
return turns;
|
|
1254
|
+
const kept = turns.slice(-VSCODE_CHAT_LIMITS.historyTurns);
|
|
1255
|
+
if (kept[0]?.role === 'assistant')
|
|
1256
|
+
kept.unshift({ role: 'user', prompt: HISTORY_MARKER_PROMPT });
|
|
1257
|
+
return kept;
|
|
1119
1258
|
}
|
|
1120
1259
|
/**
|
|
1121
1260
|
* The attach snapshot's canonical history as chat turns. Bounded: when older
|
|
@@ -1174,9 +1313,9 @@ export function projectHistory(history, granted, pendingToolNames = new Set(), p
|
|
|
1174
1313
|
// interruption record are not the person's words.
|
|
1175
1314
|
const provenance = userProvenance(message.metadata, body);
|
|
1176
1315
|
if (provenance.source === 'harness' || editorNudge(body)) {
|
|
1177
|
-
const notice =
|
|
1316
|
+
const notice = noticeText(provenance.notice);
|
|
1178
1317
|
if (notice)
|
|
1179
|
-
pushAssistant([
|
|
1318
|
+
pushAssistant([noticeLine(notice)]);
|
|
1180
1319
|
continue;
|
|
1181
1320
|
}
|
|
1182
1321
|
toolRows.clear();
|
|
@@ -1236,13 +1375,10 @@ export function projectHistory(history, granted, pendingToolNames = new Set(), p
|
|
|
1236
1375
|
// interruption. Only the final matching tool is affected, never old history.
|
|
1237
1376
|
const pending = message === history.at(-1) && pendingToolNames.has(message.name ?? '')
|
|
1238
1377
|
&& body === '[no tool result recorded — turn interrupted]';
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
// results; the active response separately restores the approval controls.
|
|
1243
|
-
pushAssistant([{ part: 'markdown', value: '**Awaiting approval** — no tool result yet.' }]);
|
|
1378
|
+
// Its call's row stays without a result, and the active response restores the approval it waits on, which is
|
|
1379
|
+
// where VS Code shows a call waiting for confirmation: on its own card, not in words in the transcript.
|
|
1380
|
+
if (pending)
|
|
1244
1381
|
continue;
|
|
1245
|
-
}
|
|
1246
1382
|
if (granted.has('chatParticipantAdditions')) {
|
|
1247
1383
|
const row = message.tool_call_id ? toolRows.get(message.tool_call_id) : undefined;
|
|
1248
1384
|
const output = clamp(stripControls(body), VSCODE_CHAT_LIMITS.partCharacters);
|
|
@@ -1278,16 +1414,5 @@ export function projectHistory(history, granted, pendingToolNames = new Set(), p
|
|
|
1278
1414
|
value: `**Tool result${message.name ? ` · ${message.name}` : ''}**\n\n${fence(clamp(stripControls(body), VSCODE_CHAT_LIMITS.partCharacters))}`,
|
|
1279
1415
|
}]);
|
|
1280
1416
|
}
|
|
1281
|
-
|
|
1282
|
-
return turns;
|
|
1283
|
-
const omitted = turns.length - VSCODE_CHAT_LIMITS.historyTurns;
|
|
1284
|
-
const kept = turns.slice(-VSCODE_CHAT_LIMITS.historyTurns);
|
|
1285
|
-
kept.unshift({
|
|
1286
|
-
role: 'assistant',
|
|
1287
|
-
parts: [{
|
|
1288
|
-
part: 'markdown',
|
|
1289
|
-
value: `_${omitted} earlier turns are omitted by this view’s bound; the runtime’s own history is unchanged and authoritative._`,
|
|
1290
|
-
}],
|
|
1291
|
-
});
|
|
1292
|
-
return kept;
|
|
1417
|
+
return boundTurns(turns);
|
|
1293
1418
|
}
|
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.65",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "VS Code chat-view frontend for an existing Volter Harness SDK runtime",
|
|
7
7
|
"repository": {
|
|
@@ -53,7 +53,10 @@
|
|
|
53
53
|
"chatParticipantPrivate",
|
|
54
54
|
"chatParticipantAdditions",
|
|
55
55
|
"chatSessionsProvider",
|
|
56
|
-
"chatProvider"
|
|
56
|
+
"chatProvider",
|
|
57
|
+
"chatSessionResponses",
|
|
58
|
+
"chatSystemNotification",
|
|
59
|
+
"chatConfirmationResolution"
|
|
57
60
|
],
|
|
58
61
|
"contributes": {
|
|
59
62
|
"taskDefinitions": [
|