@volter/supercode-frontend-vscode 0.1.63 → 0.1.64
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 +85 -66
- package/dist/extension.js +127 -91
- package/dist/host-controls.d.ts +0 -1
- package/dist/proposals.js +2 -2
- package/dist/session.d.ts +2 -21
- package/dist/session.js +3 -35
- package/dist/view.d.ts +42 -5
- package/dist/view.js +179 -107
- package/package.json +1 -1
package/dist/view.d.ts
CHANGED
|
@@ -79,11 +79,28 @@ export type ChatPart = {
|
|
|
79
79
|
background?: boolean;
|
|
80
80
|
} | {
|
|
81
81
|
part: 'answers';
|
|
82
|
+
connectionId: string;
|
|
83
|
+
requestId: number;
|
|
82
84
|
approval: boolean;
|
|
83
85
|
title: string;
|
|
84
86
|
message: string;
|
|
87
|
+
/** None: a request this window cannot answer, drawn read-only. */
|
|
85
88
|
answers: ChatAnswer[];
|
|
86
89
|
}
|
|
90
|
+
/** How a request drawn in this stream was answered, said only when the answer did not come through that card in
|
|
91
|
+
* this window (`writePart`), so a card answered elsewhere is not left looking open. Markdown-safe. */
|
|
92
|
+
| {
|
|
93
|
+
part: 'resolution';
|
|
94
|
+
connectionId: string;
|
|
95
|
+
requestId: number;
|
|
96
|
+
value: string;
|
|
97
|
+
}
|
|
98
|
+
/** A record that is not conversation (an event kind this version does not know, a malformed request): written to
|
|
99
|
+
* the output channel, never to the transcript. */
|
|
100
|
+
| {
|
|
101
|
+
part: 'unshown';
|
|
102
|
+
value: string;
|
|
103
|
+
}
|
|
87
104
|
/** A harness's questions (`requestQuestions`): VS Code's question carousel on a turn this window runs, its
|
|
88
105
|
* `answers` part anywhere else. */
|
|
89
106
|
| {
|
|
@@ -187,7 +204,11 @@ export interface SubagentCall {
|
|
|
187
204
|
/** A subagent call's description, kind and prompt (Claude Code's Task and Agent tools: `description`,
|
|
188
205
|
* `subagent_type`, `prompt`), read from its own arguments; undefined for any other tool. */
|
|
189
206
|
export declare function subagentCall(toolName: string, argumentsText: string): SubagentCall | undefined;
|
|
190
|
-
/**
|
|
207
|
+
/**
|
|
208
|
+
* The command line of a shell tool call, read from its own arguments; undefined for any other tool. Claude Code's
|
|
209
|
+
* BashOutput and KillBash are terminal rows too, as Code-OSS's own Claude host draws them (`claudeToolDisplay.ts`
|
|
210
|
+
* `TOOL_ROWS`): their command line is their `command`, else their input (`getClaudeToolInputString`).
|
|
211
|
+
*/
|
|
191
212
|
export declare function shellCommand(toolName: string, argumentsText: string): string | undefined;
|
|
192
213
|
/** The information this view shows for a request payload. */
|
|
193
214
|
export declare function requestSummary(request: FrontendRequest): string;
|
|
@@ -218,6 +239,15 @@ export declare function requestNeedsTypedReply(request: FrontendRequest): boolea
|
|
|
218
239
|
export declare function resolutionSentence(response: FrontendResponse | undefined, request: FrontendRequest | undefined, requestId: number): string;
|
|
219
240
|
/** `write_file on hello.txt`, read from the request's own payload. */
|
|
220
241
|
export declare function requestSubject(request: FrontendRequest): string | undefined;
|
|
242
|
+
/**
|
|
243
|
+
* How a turn that did not succeed ends the response its request handler runs, as VS Code's own agent host ends one
|
|
244
|
+
* (`agentHostSessionHandler.ts` `_getTurnErrorDetails`): a failed turn is the response's error, an interrupted one its
|
|
245
|
+
* warning-level, expected error. Undefined for any other event.
|
|
246
|
+
*/
|
|
247
|
+
export declare function turnOutcome(event: FrontendEvent): {
|
|
248
|
+
message: string;
|
|
249
|
+
interrupted: boolean;
|
|
250
|
+
} | undefined;
|
|
221
251
|
export interface ProjectionContext {
|
|
222
252
|
/** Identifies which attached runtime a confirmation click must answer. */
|
|
223
253
|
connectionId: string;
|
|
@@ -225,8 +255,14 @@ export interface ProjectionContext {
|
|
|
225
255
|
echoOwnPrompts?: boolean;
|
|
226
256
|
/** Show input arriving in an observed turn, without inventing a native request turn. */
|
|
227
257
|
liveUserMessages?: boolean;
|
|
228
|
-
/** The request a `request_resolved` answers, so its
|
|
258
|
+
/** The request a `request_resolved` answers, so its line can name it. */
|
|
229
259
|
pendingRequest?: (requestId: number) => FrontendRequest | undefined;
|
|
260
|
+
/** The calls whose background task's notification this stream has drawn (`firstNotice`), so a task finishing is one
|
|
261
|
+
* line: Claude Code reports it both as `system/task_notification` (a `status`) and as the user-role record that
|
|
262
|
+
* wakes the agent (a `user_message` notice). */
|
|
263
|
+
notices?: Set<string>;
|
|
264
|
+
/** Where a record that is not conversation goes when there is no writer to log it (history). */
|
|
265
|
+
unshown?: (value: string) => void;
|
|
230
266
|
/**
|
|
231
267
|
* Whether the runtime's own descriptor advertises `respond`. A HOSTED
|
|
232
268
|
* harness runtime sets `actions.respond: false` — the portable contract
|
|
@@ -252,10 +288,11 @@ export declare function projectEvent(event: FrontendEvent, context: ProjectionCo
|
|
|
252
288
|
/** Replace every part whose proposal the host did not grant with its fallback. */
|
|
253
289
|
export declare function resolveParts(parts: readonly ProjectedPart[], granted: ReadonlySet<string>): ChatPart[];
|
|
254
290
|
/**
|
|
255
|
-
* The prompt of the marker turn that carries runtime history with no user turn
|
|
256
|
-
*
|
|
291
|
+
* The prompt of the marker turn that carries runtime history with no user turn of its own: VS Code's own words for a
|
|
292
|
+
* turn whose request is not in the transcript (`claudeReplayMapper.ts` `missingPromptPlaceholder`), so nothing is
|
|
293
|
+
* presented as a person's words.
|
|
257
294
|
*/
|
|
258
|
-
export declare const HISTORY_MARKER_PROMPT = "
|
|
295
|
+
export declare const HISTORY_MARKER_PROMPT = "Message content could not be retrieved";
|
|
259
296
|
export type TurnRole = 'user' | 'assistant';
|
|
260
297
|
export interface ProjectedTurn {
|
|
261
298
|
role: TurnRole;
|
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
|
+
// which the extension API does not carry: here it is its own words as a line that stays (`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 a re-joined response or history, which have
|
|
922
|
+
// no result to carry it, shows its warning part. The live writer drops it when the person stopped the turn
|
|
923
|
+
// (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,40 @@ 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`); a re-joined response and history carry no result, so there it is the warning part. The
|
|
939
|
+
// error's own words, as VS Code's agent host shows a turn's error, never the event kind's name.
|
|
900
940
|
case 'turn_failed':
|
|
901
941
|
case 'stream_error':
|
|
902
942
|
case 'transport_error':
|
|
903
943
|
case 'watch_error':
|
|
904
944
|
case 'runtime_disconnected': {
|
|
905
|
-
const
|
|
906
|
-
|
|
945
|
+
const value = turnOutcome(event)?.message ?? errorMessage(payload);
|
|
946
|
+
if (!value)
|
|
947
|
+
return [];
|
|
907
948
|
return [{
|
|
908
949
|
emit: { part: 'warning', value },
|
|
909
950
|
needs: 'chatParticipantAdditions',
|
|
910
951
|
fallback: [{ part: 'markdown', value: `> ⚠ ${value}` }],
|
|
911
952
|
}];
|
|
912
953
|
}
|
|
913
|
-
// Declared by the runtime descriptor's `display.event_kinds`
|
|
914
|
-
//
|
|
915
|
-
//
|
|
916
|
-
|
|
954
|
+
// Declared by the runtime descriptor's `display.event_kinds` (`agent-runtime-host.mjs` `displayKinds`); nothing
|
|
955
|
+
// in this repository produces them, so their payload is read for the words it states and never shown as JSON.
|
|
956
|
+
// A scheduled prompt is a turn the runtime starts itself: its start is a labelled line naming the prompt, its
|
|
957
|
+
// deferral and completion are lifecycle like `turn_started` / `turn_completed`, and an error is a warning.
|
|
958
|
+
case 'scheduled_prompt_started': {
|
|
959
|
+
const value = clamp(stripControls(text(payload.prompt ?? payload.name ?? payload.message)), VSCODE_CHAT_LIMITS.titleCharacters);
|
|
960
|
+
// (labelled, so the runtime's prompt is not read as the agent's words)
|
|
961
|
+
return value ? [stable(noticeLine(`Scheduled prompt: ${value}`))] : [];
|
|
962
|
+
}
|
|
917
963
|
case 'scheduled_prompt_deferred':
|
|
918
964
|
case 'scheduled_prompt_completed':
|
|
919
|
-
return [
|
|
920
|
-
part: 'progress',
|
|
921
|
-
value: `${event.kind.replaceAll('_', ' ')} · ${opaqueText(event.payload, 256)}`,
|
|
922
|
-
})];
|
|
965
|
+
return [];
|
|
923
966
|
case 'scheduler_error': {
|
|
924
|
-
const value =
|
|
967
|
+
const value = errorMessage(payload);
|
|
968
|
+
if (!value)
|
|
969
|
+
return [];
|
|
925
970
|
return [{
|
|
926
971
|
emit: { part: 'warning', value },
|
|
927
972
|
needs: 'chatParticipantAdditions',
|
|
@@ -933,18 +978,23 @@ export function projectEvent(event, context) {
|
|
|
933
978
|
case 'native_event':
|
|
934
979
|
return [];
|
|
935
980
|
case 'status':
|
|
936
|
-
// A
|
|
937
|
-
//
|
|
938
|
-
//
|
|
981
|
+
// A background task's notification (`call_id` names the call that started it): a harness record, drawn as the
|
|
982
|
+
// `user_message` notices are (`noticeLine`), since a progress part would be hidden by whatever follows. Its
|
|
983
|
+
// answer belongs to that call's row (or to the agent's reply, for a subagent started in the background), so the
|
|
984
|
+
// notice is its first line, and the task's output file where it names one.
|
|
939
985
|
if (text(payload.call_id)) {
|
|
986
|
+
if (!firstNotice(context, text(payload.call_id)))
|
|
987
|
+
return [];
|
|
940
988
|
const lines = stripControls(text(payload.message)).split('\n');
|
|
941
989
|
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(
|
|
990
|
+
const line = [clamp((lines[0] ?? '').trim(), VSCODE_CHAT_LIMITS.titleCharacters), output].filter(Boolean).join('\n\n');
|
|
991
|
+
return line ? [stable(noticeLine(clamp(line, VSCODE_CHAT_LIMITS.partCharacters)))] : [];
|
|
944
992
|
}
|
|
945
993
|
return [stable({ part: 'progress', value: clamp(stripControls(text(payload.message)), VSCODE_CHAT_LIMITS.partCharacters) })];
|
|
946
|
-
case 'warning':
|
|
947
|
-
|
|
994
|
+
case 'warning': {
|
|
995
|
+
const value = clamp(stripControls(text(payload.message)), VSCODE_CHAT_LIMITS.partCharacters);
|
|
996
|
+
return [{ emit: { part: 'warning', value }, needs: 'chatParticipantAdditions', fallback: [{ part: 'markdown', value: `> ⚠ ${value}` }] }];
|
|
997
|
+
}
|
|
948
998
|
// Not in this runtime's declared set; read the way
|
|
949
999
|
// sdk/interchange/frontend-transcript.mjs reads a donor that emits them.
|
|
950
1000
|
case 'reasoning':
|
|
@@ -959,17 +1009,11 @@ export function projectEvent(event, context) {
|
|
|
959
1009
|
fallback: [{ part: 'markdown', value: `\n\n> ${body.replaceAll('\n', '\n> ')}\n\n` }],
|
|
960
1010
|
}];
|
|
961
1011
|
}
|
|
962
|
-
//
|
|
963
|
-
//
|
|
1012
|
+
// A kind this version does not know is a protocol record, not conversation, like `native_event` above: VS Code's
|
|
1013
|
+
// own chat shows none (its agent host drops a record it does not map). It goes to the output channel; the
|
|
1014
|
+
// runtime's history keeps it.
|
|
964
1015
|
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
|
-
})];
|
|
1016
|
+
return [stable({ part: 'unshown', value: `event kind ${clamp(event.kind, VSCODE_CHAT_LIMITS.titleCharacters)} #${event.sequence} is not displayed: ${opaqueText(event.payload)}` })];
|
|
973
1017
|
}
|
|
974
1018
|
}
|
|
975
1019
|
/** Replace every part whose proposal the host did not grant with its fallback. */
|
|
@@ -984,10 +1028,11 @@ export function resolveParts(parts, granted) {
|
|
|
984
1028
|
return resolved;
|
|
985
1029
|
}
|
|
986
1030
|
/**
|
|
987
|
-
* The prompt of the marker turn that carries runtime history with no user turn
|
|
988
|
-
*
|
|
1031
|
+
* The prompt of the marker turn that carries runtime history with no user turn of its own: VS Code's own words for a
|
|
1032
|
+
* turn whose request is not in the transcript (`claudeReplayMapper.ts` `missingPromptPlaceholder`), so nothing is
|
|
1033
|
+
* presented as a person's words.
|
|
989
1034
|
*/
|
|
990
|
-
export const HISTORY_MARKER_PROMPT = '
|
|
1035
|
+
export const HISTORY_MARKER_PROMPT = 'Message content could not be retrieved';
|
|
991
1036
|
function messageText(message) {
|
|
992
1037
|
const content = message.content;
|
|
993
1038
|
if (typeof content === 'string')
|
|
@@ -1012,21 +1057,40 @@ function messageText(message) {
|
|
|
1012
1057
|
}
|
|
1013
1058
|
/**
|
|
1014
1059
|
* 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
|
|
1060
|
+
* short notice, plain text drawn as a status line. The harness's own records carry `source: 'harness'` (and a `notice` where one is shown) from the one
|
|
1016
1061
|
* provenance rule the runtime applies (`@volter/supercode-interchange/user-provenance`). The editor's automatic
|
|
1017
1062
|
* tripwire nudge travels through the person's input door, so the editor's frame is what names it.
|
|
1018
1063
|
*/
|
|
1019
1064
|
function notPersonNotice(payload) {
|
|
1020
1065
|
if (payload.source === 'harness')
|
|
1021
|
-
return
|
|
1066
|
+
return noticeText(text(payload.notice));
|
|
1022
1067
|
if (editorNudge(text(payload.text)))
|
|
1023
1068
|
return '';
|
|
1024
1069
|
return undefined;
|
|
1025
1070
|
}
|
|
1026
|
-
/**
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1071
|
+
/**
|
|
1072
|
+
* A notice as a line of the transcript: its own words, plain. Not a progress part: Code-OSS hides a progress message
|
|
1073
|
+
* as soon as any other part follows it (`chatProgressContentPart.ts`), so the notice would never be seen.
|
|
1074
|
+
*/
|
|
1075
|
+
function noticeLine(notice) {
|
|
1076
|
+
return { part: 'markdown', value: `\n\n${plain(notice)}\n\n` };
|
|
1077
|
+
}
|
|
1078
|
+
/** The call a background task's notification record names (`<tool-use-id>`), for `firstNotice`. */
|
|
1079
|
+
function notifiedCall(payload) {
|
|
1080
|
+
return /<tool-use-id>([^<]+)<\/tool-use-id>/.exec(text(payload.text))?.[1]?.trim() ?? '';
|
|
1081
|
+
}
|
|
1082
|
+
/** Whether this is the first notification of `call`'s task in this stream (always, for one that names no call). */
|
|
1083
|
+
function firstNotice(context, call) {
|
|
1084
|
+
if (!call || !context.notices)
|
|
1085
|
+
return true;
|
|
1086
|
+
if (context.notices.has(call))
|
|
1087
|
+
return false;
|
|
1088
|
+
context.notices.add(call);
|
|
1089
|
+
return true;
|
|
1090
|
+
}
|
|
1091
|
+
/** The rule's plain notice, bounded; '' for none. */
|
|
1092
|
+
function noticeText(notice) {
|
|
1093
|
+
return clamp(stripControls(notice ?? ''), VSCODE_CHAT_LIMITS.titleCharacters);
|
|
1030
1094
|
}
|
|
1031
1095
|
function editorNudge(body) {
|
|
1032
1096
|
return body.trimStart().startsWith('[Volter editor tripwire');
|
|
@@ -1037,6 +1101,18 @@ export function projectReplayTurns(events, granted, context, history = []) {
|
|
|
1037
1101
|
// Before the replay supplies a prompt, give its parts their own runtime
|
|
1038
1102
|
// history turn rather than attributing old approvals to the latest answer.
|
|
1039
1103
|
const turns = history.map(turn => ({ ...turn, parts: turn.parts?.slice() }));
|
|
1104
|
+
// A reopened chat names what each answer answered from the replay's own requests first: the session's maps hold
|
|
1105
|
+
// only what is pending now and the latest few resolved.
|
|
1106
|
+
const requests = new Map();
|
|
1107
|
+
for (const event of events) {
|
|
1108
|
+
if (event.kind !== 'request')
|
|
1109
|
+
continue;
|
|
1110
|
+
const request = (payloadOf(event).request ?? payloadOf(event));
|
|
1111
|
+
if (typeof request?.id === 'number')
|
|
1112
|
+
requests.set(request.id, request);
|
|
1113
|
+
}
|
|
1114
|
+
const known = context.pendingRequest;
|
|
1115
|
+
context = { ...context, notices: context.notices ?? new Set(), pendingRequest: (id) => requests.get(id) ?? known?.(id) };
|
|
1040
1116
|
// An approval the replay shows allowed is not drawn again: live, its card is hidden (Auto approve) or answered,
|
|
1041
1117
|
// and its allowed resolution says nothing more, so the tool row is the record (`request_resolved` above).
|
|
1042
1118
|
const allowed = new Set();
|
|
@@ -1056,20 +1132,23 @@ export function projectReplayTurns(events, granted, context, history = []) {
|
|
|
1056
1132
|
if (typeof request?.id === 'number' && allowed.has(request.id))
|
|
1057
1133
|
continue;
|
|
1058
1134
|
}
|
|
1135
|
+
// (nor is its answer, which would answer a card that is not there)
|
|
1136
|
+
if (event.kind === 'request_resolved' && allowed.has(payloadOf(event).request_id))
|
|
1137
|
+
continue;
|
|
1059
1138
|
// (a subagent's own input is no turn of the conversation; `projectEvent` draws nothing for it)
|
|
1060
1139
|
if (event.kind === 'user_message' && !text(payloadOf(event).parent_id)) {
|
|
1061
1140
|
streamedText = undefined;
|
|
1062
1141
|
const payload = payloadOf(event);
|
|
1063
1142
|
const shown = notPersonNotice(payload);
|
|
1064
1143
|
if (shown !== undefined) {
|
|
1065
|
-
if (!shown)
|
|
1144
|
+
if (!shown || !firstNotice(context, notifiedCall(payload)))
|
|
1066
1145
|
continue;
|
|
1067
1146
|
if (!replayTurn) {
|
|
1068
1147
|
turns.push({ role: 'user', prompt: HISTORY_MARKER_PROMPT });
|
|
1069
1148
|
replayTurn = true;
|
|
1070
1149
|
}
|
|
1071
1150
|
const last = turns[turns.length - 1];
|
|
1072
|
-
const notice =
|
|
1151
|
+
const notice = noticeLine(shown);
|
|
1073
1152
|
if (last.role === 'assistant' && last.parts)
|
|
1074
1153
|
last.parts.push(notice);
|
|
1075
1154
|
else
|
|
@@ -1080,7 +1159,13 @@ export function projectReplayTurns(events, granted, context, history = []) {
|
|
|
1080
1159
|
replayTurn = true;
|
|
1081
1160
|
continue;
|
|
1082
1161
|
}
|
|
1083
|
-
|
|
1162
|
+
// (a record that is not conversation is no part of a turn: it goes to `unshown`, wherever it falls)
|
|
1163
|
+
const parts = resolveParts(projectEvent(event, context), granted).filter(part => {
|
|
1164
|
+
if (part.part !== 'unshown')
|
|
1165
|
+
return true;
|
|
1166
|
+
context.unshown?.(part.value);
|
|
1167
|
+
return false;
|
|
1168
|
+
});
|
|
1084
1169
|
if (parts.length === 0)
|
|
1085
1170
|
continue;
|
|
1086
1171
|
if (event.kind === 'text_delta' && parts[0]?.part === 'markdown') {
|
|
@@ -1103,19 +1188,20 @@ export function projectReplayTurns(events, granted, context, history = []) {
|
|
|
1103
1188
|
else
|
|
1104
1189
|
turns.push({ role: 'assistant', parts });
|
|
1105
1190
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1191
|
+
return boundTurns(turns);
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* The latest `historyTurns` turns. The earlier ones are left out without a word, as VS Code's own agent host shows a
|
|
1195
|
+
* transcript its harness truncated; a response the cut leaves without its request gets the marker request, as that
|
|
1196
|
+
* host gives one its missing-prompt placeholder (`claudeReplayMapper.ts` `missingPromptPlaceholder`).
|
|
1197
|
+
*/
|
|
1198
|
+
function boundTurns(turns) {
|
|
1199
|
+
if (turns.length <= VSCODE_CHAT_LIMITS.historyTurns)
|
|
1200
|
+
return turns;
|
|
1201
|
+
const kept = turns.slice(-VSCODE_CHAT_LIMITS.historyTurns);
|
|
1202
|
+
if (kept[0]?.role === 'assistant')
|
|
1203
|
+
kept.unshift({ role: 'user', prompt: HISTORY_MARKER_PROMPT });
|
|
1204
|
+
return kept;
|
|
1119
1205
|
}
|
|
1120
1206
|
/**
|
|
1121
1207
|
* The attach snapshot's canonical history as chat turns. Bounded: when older
|
|
@@ -1174,9 +1260,9 @@ export function projectHistory(history, granted, pendingToolNames = new Set(), p
|
|
|
1174
1260
|
// interruption record are not the person's words.
|
|
1175
1261
|
const provenance = userProvenance(message.metadata, body);
|
|
1176
1262
|
if (provenance.source === 'harness' || editorNudge(body)) {
|
|
1177
|
-
const notice =
|
|
1263
|
+
const notice = noticeText(provenance.notice);
|
|
1178
1264
|
if (notice)
|
|
1179
|
-
pushAssistant([
|
|
1265
|
+
pushAssistant([noticeLine(notice)]);
|
|
1180
1266
|
continue;
|
|
1181
1267
|
}
|
|
1182
1268
|
toolRows.clear();
|
|
@@ -1236,13 +1322,10 @@ export function projectHistory(history, granted, pendingToolNames = new Set(), p
|
|
|
1236
1322
|
// interruption. Only the final matching tool is affected, never old history.
|
|
1237
1323
|
const pending = message === history.at(-1) && pendingToolNames.has(message.name ?? '')
|
|
1238
1324
|
&& 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.' }]);
|
|
1325
|
+
// Its call's row stays without a result, and the active response restores the approval it waits on, which is
|
|
1326
|
+
// where VS Code shows a call waiting for confirmation: on its own card, not in words in the transcript.
|
|
1327
|
+
if (pending)
|
|
1244
1328
|
continue;
|
|
1245
|
-
}
|
|
1246
1329
|
if (granted.has('chatParticipantAdditions')) {
|
|
1247
1330
|
const row = message.tool_call_id ? toolRows.get(message.tool_call_id) : undefined;
|
|
1248
1331
|
const output = clamp(stripControls(body), VSCODE_CHAT_LIMITS.partCharacters);
|
|
@@ -1278,16 +1361,5 @@ export function projectHistory(history, granted, pendingToolNames = new Set(), p
|
|
|
1278
1361
|
value: `**Tool result${message.name ? ` · ${message.name}` : ''}**\n\n${fence(clamp(stripControls(body), VSCODE_CHAT_LIMITS.partCharacters))}`,
|
|
1279
1362
|
}]);
|
|
1280
1363
|
}
|
|
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;
|
|
1364
|
+
return boundTurns(turns);
|
|
1293
1365
|
}
|
package/package.json
CHANGED