@volter/supercode-frontend-vscode 0.1.61 → 0.1.63
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 +91 -6
- package/dist/extension.js +132 -23
- package/dist/view.d.ts +93 -9
- package/dist/view.js +352 -47
- package/package.json +2 -1
- package/vendor/supercode-interchange/user-provenance.d.mts +19 -0
- package/vendor/supercode-interchange/user-provenance.mjs +230 -0
package/README.md
CHANGED
|
@@ -320,11 +320,56 @@ request), and each part declares its own fallback.
|
|
|
320
320
|
| Proposal | What it buys | Without it |
|
|
321
321
|
|---|---|---|
|
|
322
322
|
| `defaultChatParticipant` | `isDefault` — Volter Harness answers the Chat view with no `@` mention | the ordinary `@supercode` participant; a mention is required |
|
|
323
|
-
| `chatParticipantAdditions` | `ChatToolInvocationPart
|
|
323
|
+
| `chatParticipantAdditions` | `ChatToolInvocationPart` (with its terminal, todo-list and subagent data), `ChatResponseConfirmationPart`, `ChatResponseWarningPart`, `codeblockUri`, `ChatResponseThinkingProgressPart`, `ChatResponseHookPart`, `questionCarousel` | progress lines, labeled Markdown results, typed replies, plain fenced diffs, blockquoted reasoning, quoted hook lines, answer buttons |
|
|
324
324
|
| `chatSessionsProvider` | the runtime's sessions in the Chat view's session picker, each reopening with its history | the participant still attaches; there is no session list |
|
|
325
325
|
| `chatParticipantPrivate` | dynamic participant identity with the harness and provider icon | static participant identity |
|
|
326
326
|
| `chatProvider` | native fixed-model identity and provider icon | host new-chat configuration |
|
|
327
327
|
|
|
328
|
+
### Parity with Code-OSS's own chat parts
|
|
329
|
+
|
|
330
|
+
Every chat response part and tool-row kind the pinned Code-OSS (`4a7cd45`, `src/vscode-dts`) offers an extension,
|
|
331
|
+
and what this frontend does with it. **Native**: the harness's event is drawn as VS Code draws its own.
|
|
332
|
+
**Fallback**: drawn, but with a plainer part than VS Code's own (why given). **Not used**: no harness event this
|
|
333
|
+
frontend receives carries what the part shows. **Gap**: the event exists and the native part is not built yet.
|
|
334
|
+
This count is the measure of done; a change that moves a row says so here.
|
|
335
|
+
|
|
336
|
+
| Part / tool data | Status | What draws it, or why not |
|
|
337
|
+
|---|---|---|
|
|
338
|
+
| `ChatResponseMarkdownPart` | native | the agent's text |
|
|
339
|
+
| `ChatResponseProgressPart` | native | status lines; a finished subagent task as one line |
|
|
340
|
+
| `ChatResponseWarningPart` | native | warnings, failed turns, usage limits |
|
|
341
|
+
| `ChatResponseThinkingProgressPart` | native | the agent's reasoning (a subagent's is not shown, as VS Code shows none) |
|
|
342
|
+
| `ChatResponseReferencePart` | native | each file a Codex diff names |
|
|
343
|
+
| `ChatResponseCommandButtonPart` | native | approval answers; Stop for a background task |
|
|
344
|
+
| `ChatToolInvocationPart` | native | every tool call, updated in place with its result |
|
|
345
|
+
| ↳ terminal data | native | shell calls: command and output |
|
|
346
|
+
| ↳ todo-list data | native | Claude Code's TodoWrite and its task tools (TaskCreate, TaskUpdate, TaskList), and supercode's own `update_plan`, shown in VS Code's todo list. Codex app-server's plan (`turn/plan/updated`, not a tool call) is still a status line: fallback |
|
|
347
|
+
| ↳ subagent data, `subAgentInvocationId` | native | Claude Code's Agent/Task call is the subagent row; its own calls are grouped under it (`parent_tool_use_id`) |
|
|
348
|
+
| ↳ simple input/output data | native | any other finished call |
|
|
349
|
+
| ↳ MCP data | fallback | an MCP call's result arrives as text, so it is the simple input/output |
|
|
350
|
+
| ↳ resources data | not used | no tool event lists resources as such |
|
|
351
|
+
| `ChatResponseHookPart` | native | a blocked prompt, a blocked tool call, a hook that failed (Claude Code). The extension API cannot place a hook inside a subagent row |
|
|
352
|
+
| `ChatResponseQuestionCarouselPart` | native | an elicitation of questions (`ask_user`, Claude Code's AskUserQuestion) on a turn this window runs; command buttons on a re-joined response, where the pinned host never delivers the carousel's answer |
|
|
353
|
+
| `ChatResponseConfirmationPart` | native / fallback | approvals, where the host advertises in-turn approval cards; command buttons otherwise (a stock confirmation answers with a new request) |
|
|
354
|
+
| `ChatResponseCodeblockUriPart` | fallback | a Codex diff: `codeblockUri` with a fenced diff, not an edit (see *Why `diff_updated` is not an edit part*) |
|
|
355
|
+
| `ChatResponseTextEditPart`, `ChatResponseNotebookEditPart`, `ChatResponseWorkspaceEditPart`, `ChatResponseMultiDiffPart`, `ChatResponseExternalEditPart` | **gap (known)** | native file edits with Keep/Undo and the multi-file diff: the harness has already written the files, and no event carries the before/after the editor needs |
|
|
356
|
+
| `ChatResponseAnchorPart` | not used | no event names a location inline in text |
|
|
357
|
+
| `ChatResponseFileTreePart` | not used | no event lists a file tree |
|
|
358
|
+
| `ChatResponseMarkdownWithVulnerabilitiesPart` | not used | no harness reports vulnerabilities |
|
|
359
|
+
| `ChatResponseCodeCitationPart` | not used | no harness reports a licensed citation |
|
|
360
|
+
| `ChatResponseMovePart` | not used | no event moves a range |
|
|
361
|
+
| `ChatResponseExtensionsPart` | not used | no harness recommends extensions |
|
|
362
|
+
| `ChatResponsePullRequestPart` | not used | no structured event announces a pull request |
|
|
363
|
+
| `ChatResponseInfoPart` | not used | informational records are warnings or are dropped as telemetry |
|
|
364
|
+
| `ChatResponseAutoModeResolutionPart` | not used | the harness's model is the one chosen; there is no automatic routing to report |
|
|
365
|
+
| `ChatResponseProgressPart2`, `ChatResponseReferencePart2` | not used | no event carries a resolvable task or a reference status |
|
|
366
|
+
| `ChatResponseVoiceProgressPart` (`chatParticipantPrivate`) | not used | voice is not a harness event |
|
|
367
|
+
| `stream.usage` | gap | token counts are dropped from the transcript; VS Code's own context indicator could show them and does not yet |
|
|
368
|
+
|
|
369
|
+
Count, by row: 14 native (the confirmation card only where the host advertises it), 2 fallback (and Codex's plan
|
|
370
|
+
inside the todo-list row), 12 not used, 2 gaps — native edits (the known gap, five parts in one row) and
|
|
371
|
+
`stream.usage`.
|
|
372
|
+
|
|
328
373
|
The session participant uses the exact session type id (`supercode`). With
|
|
329
374
|
`chatParticipantPrivate`, the extension registers its dynamic identity directly and keeps
|
|
330
375
|
`canDelegate` disabled so the workbench does not pre-register a generic agent that masks the
|
|
@@ -357,7 +402,7 @@ it needs and the fallback it degrades to. Each arm names the producer it reads.
|
|
|
357
402
|
| Event kind | Producer | Rendered as |
|
|
358
403
|
|---|---|---|
|
|
359
404
|
| `text_delta` | `crates/runtime/src/event.rs` `AgentEvent::TextDelta` | `response.markdown` (streamed) |
|
|
360
|
-
| `user_message` | `sdk/interchange/agent-runtime-host.mjs` accepted-turn projection | the request turn in history; dropped live (VS Code already shows it) |
|
|
405
|
+
| `user_message` | `sdk/interchange/agent-runtime-host.mjs` accepted-turn projection; `runtime-native-projection.mjs` `claudeBlocks` for Claude's own user-role records (`source: harness`) | the request turn in history; dropped live (VS Code already shows it); a harness record shows its `notice` or nothing |
|
|
361
406
|
| `turn_started` / `turn_completed` / `turn_succeeded` | `submit_claimed`; `AgentEvent::TurnCompleted` | lifecycle only — the handler resolves, nothing renders |
|
|
362
407
|
| `turn_interrupted` | `submit_claimed` | an italic notice |
|
|
363
408
|
| `turn_failed` | `submit_claimed` | `ChatResponseWarningPart` → blockquote |
|
|
@@ -365,6 +410,9 @@ it needs and the fallback it degrades to. Each arm names the producer it reads.
|
|
|
365
410
|
| `diff_updated` | `crates/codex-frontend/src/codex_app_server_v0_144.rs` (stock Codex `turn/diff/updated`) | `codeblockUri` + a fenced `diff` block, plus one stable `reference` per file the diff names → the fenced block alone |
|
|
366
411
|
| `request` | `sdk/interchange/frontend-request-bridge.mjs` request publication | a titled markdown block plus one **command button per answer** — stable API, no proposal |
|
|
367
412
|
| `cache_warning` | `AgentEvent::CacheWarning` | `ChatResponseWarningPart` → blockquote |
|
|
413
|
+
| `hook` | `runtime-native-projection.mjs` from Claude Code's failed `hook_response`, its blocked-prompt `informational` record, and a tool result in its own `PreToolUse:<tool> hook error:` words | `ChatResponseHookPart` ("Blocked by" / "Warning from" the hook) → blockquote |
|
|
414
|
+
| `background_tasks` | `runtime-native-projection.mjs` from Claude Code's `background_tasks_changed` | nothing in the transcript: the turn lists them with a Stop each |
|
|
415
|
+
| any event with `parent_id` | `runtime-native-projection.mjs` from Claude Code's `parent_tool_use_id` | a subagent's calls are grouped under its call; its own text, reasoning and input are not shown |
|
|
368
416
|
| `usage` | `AgentEvent::Usage` | a progress line |
|
|
369
417
|
| `background_output` | `AgentEvent::BackgroundOutput` | a fenced block, saying so when the runtime stopped retaining |
|
|
370
418
|
| `scheduled_prompt_started` / `_deferred` / `_completed` | declared by `frontend_descriptor`'s `display.event_kinds` | a progress line |
|
|
@@ -402,6 +450,36 @@ resolution sentence lands directly beneath them. An elicitation that carries a `
|
|
|
402
450
|
`Answer…` button that asks for the value first; a request still pending while the session is idle
|
|
403
451
|
can also be answered by typing.
|
|
404
452
|
|
|
453
|
+
**A harness's questions are VS Code's question carousel.** Why the carousel once swallowed its answer, read
|
|
454
|
+
from the pinned Code-OSS (`4a7cd45`): `questionCarousel` resolves through the resolver map of the stream it
|
|
455
|
+
was called on, and `$handleQuestionCarouselAnswer` reads only the participant's map
|
|
456
|
+
(`extHostChatAgents2.ts`). A session's *re-joined* response (`activeResponseCallback`) gets a stream with a
|
|
457
|
+
map of its own (`extHostChatSessions.ts`) that no answer ever reaches. So the carousel is used only on a
|
|
458
|
+
turn this window's request handler is running; a re-joined response keeps the buttons above. The design:
|
|
459
|
+
|
|
460
|
+
- *The question reaches Chat as a request.* Supercode's own `ask_user` already asks through an `elicitation`
|
|
461
|
+
whose `requested_schema.properties.qN` carry `title`, `description`, `x-options` (`[{label, description}]`)
|
|
462
|
+
and `x-multi-select` (`sdk/interchange/question-tool.mjs`). Claude Code's AskUserQuestion takes the same
|
|
463
|
+
input (`questions[{question, header, options[{label, description}], multiSelect}]`, at most four of each)
|
|
464
|
+
and, with `--permission-prompt-tool stdio`, asks for it as a `can_use_tool` control request (Claude Code
|
|
465
|
+
enables the tool only when a permission prompt tool answers it — measured on 2.1.312). The runtime's
|
|
466
|
+
projection (`runtime-native-projection.mjs`) raises that request as an `elicitation` built by the same
|
|
467
|
+
`questionElicitation` the `ask_user` tool uses, not as an approval, so Auto approve never answers it and
|
|
468
|
+
every frontend that answers an elicitation answers this one.
|
|
469
|
+
- *Chat shows the carousel.* An elicitation whose every property is such a question (a `title`, the question
|
|
470
|
+
alone as `x-question`, and `x-options`) is one `ChatQuestion` per property: single or multi select with
|
|
471
|
+
free text allowed, or text when it has no options; Skip is allowed. Any other form (an MCP server's own
|
|
472
|
+
schema, with its `enum`, `format` or `required`) keeps the `Answer…` door, which honours them. Without the
|
|
473
|
+
carousel (no `chatParticipantAdditions`, or a re-joined response) it is the `Answer…` button.
|
|
474
|
+
- *The answer reaches the harness in its own format.* The carousel's answers become the elicitation's
|
|
475
|
+
`{action:'accept', content:{qN: label | [labels] | text}}`, or `decline` when skipped or submitted with
|
|
476
|
+
nothing chosen; a stopped turn sends nothing (Stop cancels its requests). A reply typed into Chat instead
|
|
477
|
+
answers every question with its words. For Claude Code the
|
|
478
|
+
hosted runtime (`runtime-hosted.mjs`) turns that into its permission reply
|
|
479
|
+
`{behavior:'allow', updatedInput:{questions, answers:{<question>: <answer>}}}`, multiple answers joined
|
|
480
|
+
with `, ` as AskUserQuestion's own output schema states; a decline is `{behavior:'deny', message}`.
|
|
481
|
+
`ask_user` already reads the same `content`.
|
|
482
|
+
|
|
405
483
|
**A hosted harness runtime.** When the runtime is one Volter Harness HOSTS — a Claude Code, Codex, pi or
|
|
406
484
|
opencode session it started through `harness.v1.runtimes.start` — an approval it raises is
|
|
407
485
|
**answerable from this view** for every harness whose reply this runtime can translate
|
|
@@ -438,10 +516,17 @@ needs it; a reader does not, and a collapsed block is still a reader's problem.
|
|
|
438
516
|
one to a person, so `role: 'system'` is dropped outright and the transcript starts where the
|
|
439
517
|
conversation does.
|
|
440
518
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
519
|
+
Whose words a user-role record carries is one rule,
|
|
520
|
+
`@volter/supercode-interchange/user-provenance`, vendored here unchanged. Live
|
|
521
|
+
and replayed `user_message` events carry its answer as `source`
|
|
522
|
+
(`person` | `harness`) with a `notice` where one is shown; saved history is
|
|
523
|
+
read through the same rule. Records the harness wrote (`isMeta`,
|
|
524
|
+
`isSynthetic` — the stream's image-coordinate note — `isCompactSummary`,
|
|
525
|
+
`promptSource: system`, sidechain and tool-result records, file attachments)
|
|
526
|
+
create no person-message bubble, live or restored; ordinary quoted text
|
|
527
|
+
without those flags stays a person message. A task notification becomes a
|
|
528
|
+
short background-task notice, and Claude's unmarked, exact interruption
|
|
529
|
+
records (`[Request interrupted by user]`, `… for tool use]`) a short turn
|
|
445
530
|
interruption notice. The native transcript remains authoritative and unchanged.
|
|
446
531
|
[Source evidence and remaining qualification](../../docs/evidence/chat-native-notices-20261013.md).
|
|
447
532
|
|
package/dist/extension.js
CHANGED
|
@@ -11,7 +11,7 @@ import { readCredentialFile } from './credential.js';
|
|
|
11
11
|
import { grantedProposals, PROPOSAL_EFFECTS, REQUESTED_PROPOSALS } from './proposals.js';
|
|
12
12
|
import { observeActiveResponse, sessionStatus } from './native-session.js';
|
|
13
13
|
import { RequestNotPendingError, VscodeFrontendSession } from './session.js';
|
|
14
|
-
import { acceptedDecisions, opaqueText, projectEvent, projectPendingRequests, projectHistory, projectReplayTurns, HISTORY_MARKER_PROMPT, resolveParts, resolutionSentence, typedAnswer, } from './view.js';
|
|
14
|
+
import { acceptedDecisions, opaqueText, projectEvent, projectPendingRequests, projectHistory, projectReplayTurns, HISTORY_MARKER_PROMPT, PlanTracker, resolveParts, resolutionSentence, typedAnswer, } from './view.js';
|
|
15
15
|
const SESSION_SCHEME = 'supercode';
|
|
16
16
|
/** The harnesses whose model and reasoning effort a new chat can set (the host passes them to the CLI). */
|
|
17
17
|
const CONFIGURABLE_HARNESSES = ['claude-code', 'codex'];
|
|
@@ -204,6 +204,88 @@ class SupercodeFrontends {
|
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
let nativeInTurnApprovalCards = false;
|
|
207
|
+
/**
|
|
208
|
+
* A harness's questions as VS Code's own carousel, inside the running turn; the person's answers are the
|
|
209
|
+
* elicitation's `{action:'accept', content:{<key>: label | [labels] | text}}`, and Skip is a decline. The answer
|
|
210
|
+
* takes the same door as a button's (`supercode.frontend.respond`).
|
|
211
|
+
*/
|
|
212
|
+
function askQuestions(stream, part, pending, cancelled) {
|
|
213
|
+
const questions = part.questions.map(field => new vscode.ChatQuestion(field.key, field.options.length === 0 ? vscode.ChatQuestionType.Text
|
|
214
|
+
: field.multiSelect ? vscode.ChatQuestionType.MultiSelect : vscode.ChatQuestionType.SingleSelect, field.title, {
|
|
215
|
+
...(field.message ? { message: field.message } : {}),
|
|
216
|
+
...(field.options.length ? {
|
|
217
|
+
options: field.options.map((option, index) => ({ id: `${field.key}-${index}`, label: option.description ? `${option.label} — ${option.description}` : option.label, value: option.label })),
|
|
218
|
+
allowFreeformInput: true,
|
|
219
|
+
} : {}),
|
|
220
|
+
}));
|
|
221
|
+
const respond = (response) => vscode.commands.executeCommand('supercode.frontend.respond', { connectionId: part.connectionId, requestId: part.requestId, response, label: 'Answer' });
|
|
222
|
+
void Promise.resolve(stream.questionCarousel(questions, true)).then(answers => {
|
|
223
|
+
// (the editor also resolves an unanswered carousel when its response ends: a request answered by another door
|
|
224
|
+
// by then is left alone)
|
|
225
|
+
// (and a stopped turn: Stop already cancels its requests)
|
|
226
|
+
if ((pending && !pending(part.requestId)) || cancelled?.())
|
|
227
|
+
return;
|
|
228
|
+
const content = {};
|
|
229
|
+
for (const field of part.questions) {
|
|
230
|
+
const answer = carouselAnswer(answers?.[field.key]);
|
|
231
|
+
if (answer !== undefined)
|
|
232
|
+
content[field.key] = answer;
|
|
233
|
+
}
|
|
234
|
+
// Skip, and a carousel submitted with nothing chosen, decline: a question is never answered with nothing.
|
|
235
|
+
if (Object.keys(content).length === 0)
|
|
236
|
+
return respond({ kind: 'elicitation', request_id: part.requestId, action: 'decline' });
|
|
237
|
+
return respond({ kind: 'elicitation', request_id: part.requestId, action: 'accept', content });
|
|
238
|
+
}, () => undefined);
|
|
239
|
+
}
|
|
240
|
+
/** One carousel answer as the elicitation's value: the text, the selected label (or the person's own words), or the
|
|
241
|
+
* selected labels with any words of their own after them. */
|
|
242
|
+
function carouselAnswer(value) {
|
|
243
|
+
if (typeof value === 'string')
|
|
244
|
+
return value.trim() || undefined;
|
|
245
|
+
if (!value || typeof value !== 'object')
|
|
246
|
+
return undefined;
|
|
247
|
+
const answer = value;
|
|
248
|
+
const own = typeof answer.freeformValue === 'string' && answer.freeformValue.trim() ? answer.freeformValue.trim() : undefined;
|
|
249
|
+
if (Array.isArray(answer.selectedValues)) {
|
|
250
|
+
const picked = [...answer.selectedValues.filter((item) => typeof item === 'string'), ...(own ? [own] : [])];
|
|
251
|
+
return picked.length ? picked : undefined;
|
|
252
|
+
}
|
|
253
|
+
return typeof answer.selectedValue === 'string' && answer.selectedValue ? answer.selectedValue : own;
|
|
254
|
+
}
|
|
255
|
+
/** VS Code's own hook row: which hook, and what it said — a stop reason where it blocked, a warning otherwise. */
|
|
256
|
+
function hookPart(part) {
|
|
257
|
+
return part.blocked
|
|
258
|
+
? new vscode.ChatResponseHookPart(part.hookType, part.reason || 'The hook gave no reason.')
|
|
259
|
+
: new vscode.ChatResponseHookPart(part.hookType, undefined, part.reason || 'The hook gave no reason.');
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* What a tool row shows inside it, the way VS Code's own rows do, live and in history alike: a plan call is the todo
|
|
263
|
+
* list as the call left it; a subagent call is the subagent row, with its answer once it has one (none for a subagent
|
|
264
|
+
* started in the background, whose call returns only a launch notice); a shell call is the terminal block; any other
|
|
265
|
+
* finished call its input and output. A subagent's own calls are grouped under the call they were made inside.
|
|
266
|
+
*/
|
|
267
|
+
function setToolDetails(invocation, row) {
|
|
268
|
+
if (row.parentToolCallId)
|
|
269
|
+
invocation.subAgentInvocationId = row.parentToolCallId;
|
|
270
|
+
if (row.plan) {
|
|
271
|
+
invocation.toolSpecificData = { todoList: row.plan.map((item, index) => ({
|
|
272
|
+
id: index + 1,
|
|
273
|
+
title: item.title,
|
|
274
|
+
status: item.status === 'completed' ? vscode.ChatTodoStatus.Completed
|
|
275
|
+
: item.status === 'in-progress' ? vscode.ChatTodoStatus.InProgress : vscode.ChatTodoStatus.NotStarted,
|
|
276
|
+
})) };
|
|
277
|
+
}
|
|
278
|
+
else if (row.subagent) {
|
|
279
|
+
invocation.toolSpecificData = new vscode.ChatSubagentToolInvocationData(row.subagent.description, row.subagent.agentName, row.subagent.prompt, row.isComplete && !row.background ? row.resultText ?? '' : undefined);
|
|
280
|
+
}
|
|
281
|
+
else if (row.commandLine) {
|
|
282
|
+
invocation.toolSpecificData = { commandLine: { original: row.commandLine }, language: 'sh',
|
|
283
|
+
...(row.isComplete ? { output: { text: row.resultText ?? '' } } : {}) };
|
|
284
|
+
}
|
|
285
|
+
else if (row.isComplete) {
|
|
286
|
+
invocation.toolSpecificData = { input: row.inputText ?? '', output: row.resultText ?? '' };
|
|
287
|
+
}
|
|
288
|
+
}
|
|
207
289
|
function writeProjected(stream, projected, granted, state) {
|
|
208
290
|
for (const candidate of projected) {
|
|
209
291
|
const parts = resolveParts([candidate], granted);
|
|
@@ -244,6 +326,15 @@ function writePart(stream, part, state) {
|
|
|
244
326
|
case 'thinking':
|
|
245
327
|
stream.push(new vscode.ChatResponseThinkingProgressPart(part.value));
|
|
246
328
|
return;
|
|
329
|
+
case 'hook':
|
|
330
|
+
stream.push(hookPart(part));
|
|
331
|
+
return;
|
|
332
|
+
case 'questions':
|
|
333
|
+
if (state.carousel)
|
|
334
|
+
askQuestions(stream, part, state.pending, state.cancelled);
|
|
335
|
+
else
|
|
336
|
+
writePart(stream, part.fallback, state);
|
|
337
|
+
return;
|
|
247
338
|
case 'reference': {
|
|
248
339
|
// Only a path the runtime's own payload named, resolved against the
|
|
249
340
|
// workspace this window already has. Nothing is created or fabricated.
|
|
@@ -299,17 +390,22 @@ function writePart(stream, part, state) {
|
|
|
299
390
|
invocation.isError = part.isError;
|
|
300
391
|
if (part.inputText !== undefined)
|
|
301
392
|
state.toolInputs.set(part.toolCallId, part.inputText);
|
|
302
|
-
if (
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
...
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
};
|
|
393
|
+
if (part.plan || part.subagent || part.parentToolCallId || part.background) {
|
|
394
|
+
const kept = state.toolKinds.get(part.toolCallId) ?? {};
|
|
395
|
+
state.toolKinds.set(part.toolCallId, {
|
|
396
|
+
...kept,
|
|
397
|
+
...(part.plan ? { plan: part.plan } : {}),
|
|
398
|
+
...(part.subagent ? { subagent: part.subagent } : {}),
|
|
399
|
+
...(part.parentToolCallId ? { parentToolCallId: part.parentToolCallId } : {}),
|
|
400
|
+
...(part.background ? { background: true } : {}),
|
|
401
|
+
});
|
|
312
402
|
}
|
|
403
|
+
setToolDetails(invocation, {
|
|
404
|
+
...part,
|
|
405
|
+
...state.toolKinds.get(part.toolCallId),
|
|
406
|
+
commandLine,
|
|
407
|
+
inputText: state.toolInputs.get(part.toolCallId),
|
|
408
|
+
});
|
|
313
409
|
stream.push(invocation);
|
|
314
410
|
return;
|
|
315
411
|
}
|
|
@@ -1496,7 +1592,7 @@ export async function activate(context) {
|
|
|
1496
1592
|
writeProjected(stream, projectPendingRequests([pending], {
|
|
1497
1593
|
connectionId: connection.id,
|
|
1498
1594
|
canRespond: connection.session.descriptor?.actions.respond !== false,
|
|
1499
|
-
}), granted, { toolInputs: new Map(), toolNames: new Map(), toolTitles: new Map(), toolCommands: new Map() });
|
|
1595
|
+
}), granted, { toolInputs: new Map(), toolNames: new Map(), toolTitles: new Map(), toolCommands: new Map(), toolKinds: new Map() });
|
|
1500
1596
|
}
|
|
1501
1597
|
catch {
|
|
1502
1598
|
void vscode.window.showWarningMessage(`${connection.label}: ${label} is waiting for approval. Open its chat to answer it.`);
|
|
@@ -1538,11 +1634,14 @@ export async function activate(context) {
|
|
|
1538
1634
|
if (approval() === 'autoApprove' && !canAnswer(connection)) {
|
|
1539
1635
|
throw new Error('This harness does not support automatic approval through Chat. Select Harness permissions.');
|
|
1540
1636
|
}
|
|
1541
|
-
const state = { toolInputs: new Map(), toolNames: new Map(), toolTitles: new Map(), toolCommands: new Map()
|
|
1637
|
+
const state = { toolInputs: new Map(), toolNames: new Map(), toolTitles: new Map(), toolCommands: new Map(), toolKinds: new Map(),
|
|
1638
|
+
carousel: true, pending: (requestId) => connection.session.pendingRequests.has(requestId),
|
|
1639
|
+
cancelled: () => token.isCancellationRequested };
|
|
1542
1640
|
const projection = {
|
|
1543
1641
|
connectionId: connection.id,
|
|
1544
1642
|
pendingRequest: (requestId) => connection.session.pendingRequests.get(requestId) ?? connection.session.resolvedRequests.get(requestId),
|
|
1545
1643
|
canRespond: connection.session.descriptor?.actions.respond !== false,
|
|
1644
|
+
plan: connection.plan ??= new PlanTracker(),
|
|
1546
1645
|
};
|
|
1547
1646
|
const cancellation = token.onCancellationRequested(() => {
|
|
1548
1647
|
void stopConnection(connection).catch(() => {
|
|
@@ -2143,15 +2242,15 @@ function historyParts(parts) {
|
|
|
2143
2242
|
invocation.isComplete = part.isComplete;
|
|
2144
2243
|
if (part.isError !== undefined)
|
|
2145
2244
|
invocation.isError = part.isError;
|
|
2146
|
-
|
|
2147
|
-
invocation.toolSpecificData = { commandLine: { original: part.commandLine }, language: 'sh', output: { text: part.resultText ?? '' } };
|
|
2148
|
-
}
|
|
2149
|
-
else if (part.isComplete) {
|
|
2150
|
-
invocation.toolSpecificData = { input: part.inputText ?? '', output: part.resultText ?? '' };
|
|
2151
|
-
}
|
|
2245
|
+
setToolDetails(invocation, part);
|
|
2152
2246
|
out.push(invocation);
|
|
2153
2247
|
continue;
|
|
2154
2248
|
}
|
|
2249
|
+
if (part.part === 'hook') {
|
|
2250
|
+
flush();
|
|
2251
|
+
out.push(hookPart(part));
|
|
2252
|
+
continue;
|
|
2253
|
+
}
|
|
2155
2254
|
const value = historyText(part);
|
|
2156
2255
|
if (value)
|
|
2157
2256
|
text.push(value);
|
|
@@ -2172,6 +2271,9 @@ function historyText(part) {
|
|
|
2172
2271
|
return `> ⚠ ${part.value}`;
|
|
2173
2272
|
case 'thinking':
|
|
2174
2273
|
return `> ${part.value}`;
|
|
2274
|
+
case 'hook':
|
|
2275
|
+
// Drawn as VS Code's own hook row by `historyParts`.
|
|
2276
|
+
return '';
|
|
2175
2277
|
case 'codeblock':
|
|
2176
2278
|
return part.value;
|
|
2177
2279
|
case 'reference':
|
|
@@ -2181,6 +2283,8 @@ function historyText(part) {
|
|
|
2181
2283
|
return '';
|
|
2182
2284
|
case 'answers':
|
|
2183
2285
|
return `**${part.title}**\n\n${part.message}`;
|
|
2286
|
+
case 'questions':
|
|
2287
|
+
return `**${part.fallback.title}**\n\n${part.fallback.message}`;
|
|
2184
2288
|
}
|
|
2185
2289
|
}
|
|
2186
2290
|
function registerSessions(context, frontends, participant, granted, handler, historyParticipant, controls) {
|
|
@@ -2366,6 +2470,8 @@ function registerSessions(context, frontends, participant, granted, handler, his
|
|
|
2366
2470
|
pendingToolNames.add(name);
|
|
2367
2471
|
}
|
|
2368
2472
|
}
|
|
2473
|
+
// The task list is replayed with the history it was built in, and live turns carry on from it.
|
|
2474
|
+
const plan = new PlanTracker();
|
|
2369
2475
|
const projected = projectReplayTurns(replay.filter(event => {
|
|
2370
2476
|
if (event.kind !== 'request')
|
|
2371
2477
|
return true;
|
|
@@ -2375,7 +2481,9 @@ function registerSessions(context, frontends, participant, granted, handler, his
|
|
|
2375
2481
|
connectionId: connection.id,
|
|
2376
2482
|
pendingRequest: (id) => connection.session.pendingRequests.get(id) ?? connection.session.resolvedRequests.get(id),
|
|
2377
2483
|
canRespond: Boolean(connection.session.descriptor?.actions.respond && connection.session.permissions.has('approve')),
|
|
2378
|
-
|
|
2484
|
+
plan,
|
|
2485
|
+
}, projectHistory(canonicalHistory, granted, pendingToolNames, plan));
|
|
2486
|
+
connection.plan = plan;
|
|
2379
2487
|
const live = connection.session.descriptor?.turn_state === 'busy' || connection.session.pendingRequests.size > 0;
|
|
2380
2488
|
controls?.rememberIdentity(connection);
|
|
2381
2489
|
if (!live)
|
|
@@ -2401,8 +2509,8 @@ function registerSessions(context, frontends, participant, granted, handler, his
|
|
|
2401
2509
|
// responses can be stale; their command buttons are disabled.
|
|
2402
2510
|
const last = projected.at(-1);
|
|
2403
2511
|
if (last?.role === 'assistant') {
|
|
2404
|
-
activeParts.push(...(projected.pop()?.parts ?? []).map(part => part.part === 'answers'
|
|
2405
|
-
? { part: 'markdown', value: `**${part.title}**\n\n${part.message}` }
|
|
2512
|
+
activeParts.push(...(projected.pop()?.parts ?? []).map(part => part.part === 'answers' || part.part === 'questions'
|
|
2513
|
+
? { part: 'markdown', value: `**${(part.part === 'questions' ? part.fallback : part).title}**\n\n${(part.part === 'questions' ? part.fallback : part).message}` }
|
|
2406
2514
|
: part));
|
|
2407
2515
|
}
|
|
2408
2516
|
}
|
|
@@ -2438,7 +2546,7 @@ function registerSessions(context, frontends, participant, granted, handler, his
|
|
|
2438
2546
|
// already waiting and later ones; Ask leaves them for the person, as before.
|
|
2439
2547
|
const watch = controls && !controls.attached(connection) ? controls.watchApprovals(connection, stream, token) : undefined;
|
|
2440
2548
|
try {
|
|
2441
|
-
const state = { toolInputs: new Map(), toolNames: new Map(), toolTitles: new Map(), toolCommands: new Map(), quiet: watch?.answers };
|
|
2549
|
+
const state = { toolInputs: new Map(), toolNames: new Map(), toolTitles: new Map(), toolCommands: new Map(), toolKinds: new Map(), quiet: watch?.answers };
|
|
2442
2550
|
for (const part of oneRowPerToolCall(activeParts))
|
|
2443
2551
|
writePart(stream, part, state);
|
|
2444
2552
|
writeProjected(stream, projectPendingRequests(pendingAtCursor.filter(request => connection.session.pendingRequests.has(request.id)), {
|
|
@@ -2451,6 +2559,7 @@ function registerSessions(context, frontends, participant, granted, handler, his
|
|
|
2451
2559
|
writeProjected(stream, projectEvent(event, {
|
|
2452
2560
|
connectionId: connection.id,
|
|
2453
2561
|
liveUserMessages: true,
|
|
2562
|
+
plan,
|
|
2454
2563
|
pendingRequest: (id) => connection.session.pendingRequests.get(id) ?? connection.session.resolvedRequests.get(id),
|
|
2455
2564
|
canRespond: Boolean(connection.session.descriptor?.actions.respond && connection.session.permissions.has('approve')),
|
|
2456
2565
|
}), granted, state);
|
package/dist/view.d.ts
CHANGED
|
@@ -50,6 +50,13 @@ export type ChatPart = {
|
|
|
50
50
|
uri: string;
|
|
51
51
|
value: string;
|
|
52
52
|
language: string;
|
|
53
|
+
}
|
|
54
|
+
/** A harness hook that blocked or warned: which event it ran on, and what it said. */
|
|
55
|
+
| {
|
|
56
|
+
part: 'hook';
|
|
57
|
+
hookType: HookType;
|
|
58
|
+
blocked: boolean;
|
|
59
|
+
reason: string;
|
|
53
60
|
} | {
|
|
54
61
|
part: 'tool';
|
|
55
62
|
toolName: string;
|
|
@@ -62,13 +69,45 @@ export type ChatPart = {
|
|
|
62
69
|
resultText?: string;
|
|
63
70
|
/** A shell call's command line: the row is then VS Code's own terminal block, command and output. */
|
|
64
71
|
commandLine?: string;
|
|
72
|
+
/** The plan as this call left it (`planItems`, `PlanTracker`): the row is then VS Code's own todo list. */
|
|
73
|
+
plan?: PlanItem[];
|
|
74
|
+
/** A subagent call's own fields (`subagentCall`): the row is then VS Code's own subagent row. */
|
|
75
|
+
subagent?: SubagentCall;
|
|
76
|
+
/** The subagent call this call was made inside (Claude Code's `parent_tool_use_id`): VS Code groups it there. */
|
|
77
|
+
parentToolCallId?: string;
|
|
78
|
+
/** The call started its subagent in the background: its result is a launch notice, not the subagent's answer. */
|
|
79
|
+
background?: boolean;
|
|
65
80
|
} | {
|
|
66
81
|
part: 'answers';
|
|
67
82
|
approval: boolean;
|
|
68
83
|
title: string;
|
|
69
84
|
message: string;
|
|
70
85
|
answers: ChatAnswer[];
|
|
86
|
+
}
|
|
87
|
+
/** A harness's questions (`requestQuestions`): VS Code's question carousel on a turn this window runs, its
|
|
88
|
+
* `answers` part anywhere else. */
|
|
89
|
+
| {
|
|
90
|
+
part: 'questions';
|
|
91
|
+
connectionId: string;
|
|
92
|
+
requestId: number;
|
|
93
|
+
questions: QuestionField[];
|
|
94
|
+
fallback: Extract<ChatPart, {
|
|
95
|
+
part: 'answers';
|
|
96
|
+
}>;
|
|
71
97
|
};
|
|
98
|
+
/** One question of an elicitation that asks questions (`requestQuestions`), as VS Code's carousel asks it. */
|
|
99
|
+
export interface QuestionField {
|
|
100
|
+
/** The elicitation's property, which the answer's `content` is keyed by. */
|
|
101
|
+
key: string;
|
|
102
|
+
title: string;
|
|
103
|
+
message?: string;
|
|
104
|
+
/** Each option's label (also its answer) and what it means. None: a free-text question. */
|
|
105
|
+
options: {
|
|
106
|
+
label: string;
|
|
107
|
+
description?: string;
|
|
108
|
+
}[];
|
|
109
|
+
multiSelect: boolean;
|
|
110
|
+
}
|
|
72
111
|
/**
|
|
73
112
|
* One rendered part plus the API proposal it needs and what this view renders
|
|
74
113
|
* instead when the host did not grant it. README.md's proposal table is this
|
|
@@ -105,6 +144,49 @@ export declare function toolTitles(toolName: string, argumentsText: string): {
|
|
|
105
144
|
invocation: string;
|
|
106
145
|
pastTense: string;
|
|
107
146
|
};
|
|
147
|
+
/** A hook's row as words, as VS Code titles its own (`chatHookContentPart.ts`): "Blocked by …" or "Warning from …". */
|
|
148
|
+
export declare function hookSentence(hookEvent: string, blocked: boolean, reason: string): string;
|
|
149
|
+
/** The hook events VS Code's own Chat names (`ChatHookType`), which are Claude Code's. */
|
|
150
|
+
export declare const HOOK_TYPES: readonly ["SessionStart", "SessionEnd", "UserPromptSubmit", "PreToolUse", "PostToolUse", "PreCompact", "SubagentStart", "SubagentStop", "Stop", "ErrorOccurred"];
|
|
151
|
+
export type HookType = (typeof HOOK_TYPES)[number];
|
|
152
|
+
/** One item of an agent's plan, as VS Code's own todo list holds it. */
|
|
153
|
+
export interface PlanItem {
|
|
154
|
+
title: string;
|
|
155
|
+
status: 'not-started' | 'in-progress' | 'completed';
|
|
156
|
+
}
|
|
157
|
+
/** A plan call's items (Claude Code's TodoWrite `todos` of `content`; the `update_plan` tool's `plan` of `step`, as
|
|
158
|
+
* supercode's own and Codex's take it), each its text and status, read from its own arguments; undefined for any
|
|
159
|
+
* other tool, or arguments holding none. Both carry the whole list, so they need no state. */
|
|
160
|
+
export declare function planItems(toolName: string, argumentsText: string): PlanItem[] | undefined;
|
|
161
|
+
/**
|
|
162
|
+
* The task list Claude Code keeps with its task tools, which it offers in place of TodoWrite (TaskCreate, TaskUpdate,
|
|
163
|
+
* TaskList; Claude Code 2.1.312 enables them by default and TodoWrite only when its tasks are turned off). Each call
|
|
164
|
+
* changes ONE task, while VS Code's own todo list shows the WHOLE list, so this keeps the list for one conversation as
|
|
165
|
+
* the calls go by and gives each call's row the list as that call left it.
|
|
166
|
+
*
|
|
167
|
+
* Read from the calls' own arguments, and from Claude Code's own result text where only the result has it (measured
|
|
168
|
+
* on 2.1.312, its `mapToolResultToToolResultBlockParam`): TaskCreate's `Task #<id> created successfully: <subject>`,
|
|
169
|
+
* TaskUpdate's `Updated task #<id> <fields>`, and TaskList's lines `#<id> [<status>] <subject>…`. A result in other
|
|
170
|
+
* words changes nothing here; the row is then an ordinary one.
|
|
171
|
+
*/
|
|
172
|
+
export declare class PlanTracker {
|
|
173
|
+
#private;
|
|
174
|
+
/** A call that started: the list as it leaves it, or undefined when it is not a task call (or changes nothing). */
|
|
175
|
+
started(toolName: string, argumentsText: string, toolCallId: string): PlanItem[] | undefined;
|
|
176
|
+
/** A call that completed: the list as it now stands (a failed call's change undone; TaskList's result is the
|
|
177
|
+
* list), so a finished row never shows an older list than the calls before it left. */
|
|
178
|
+
completed(toolCallId: string, output: string, isError: boolean): PlanItem[] | undefined;
|
|
179
|
+
private items;
|
|
180
|
+
}
|
|
181
|
+
/** What a subagent call says of its subagent, as VS Code's own subagent row shows it. */
|
|
182
|
+
export interface SubagentCall {
|
|
183
|
+
description?: string;
|
|
184
|
+
agentName?: string;
|
|
185
|
+
prompt?: string;
|
|
186
|
+
}
|
|
187
|
+
/** A subagent call's description, kind and prompt (Claude Code's Task and Agent tools: `description`,
|
|
188
|
+
* `subagent_type`, `prompt`), read from its own arguments; undefined for any other tool. */
|
|
189
|
+
export declare function subagentCall(toolName: string, argumentsText: string): SubagentCall | undefined;
|
|
108
190
|
/** The command line of a shell tool call, read from its own arguments; undefined for any other tool. */
|
|
109
191
|
export declare function shellCommand(toolName: string, argumentsText: string): string | undefined;
|
|
110
192
|
/** The information this view shows for a request payload. */
|
|
@@ -118,6 +200,14 @@ export declare function requestAnswers(request: FrontendRequest, connectionId: s
|
|
|
118
200
|
export declare function typedAnswer(request: FrontendRequest, reply: string): FrontendResponse;
|
|
119
201
|
/** The decisions a runtime said its harness accepts, when it said so. */
|
|
120
202
|
export declare function acceptedDecisions(request: FrontendRequest): string[] | undefined;
|
|
203
|
+
/**
|
|
204
|
+
* An elicitation that asks questions, the shape supercode's `ask_user` asks with and a hosted Claude Code's
|
|
205
|
+
* AskUserQuestion is raised as (`question-tool.mjs` `questionElicitation`): every property of its
|
|
206
|
+
* `requested_schema` is one question, with a `title`, the question alone (`x-question`) and its `x-options`
|
|
207
|
+
* (`[{label, description}]`, with `x-multi-select`; none for free text). Any other form (an MCP server's schema with
|
|
208
|
+
* its own `enum`, `format` or `required`) is undefined here and keeps the `Answer…` door, which honours them.
|
|
209
|
+
*/
|
|
210
|
+
export declare function requestQuestions(request: FrontendRequest): QuestionField[] | undefined;
|
|
121
211
|
/** True when this request asks for content a button click cannot carry. */
|
|
122
212
|
export declare function requestNeedsTypedReply(request: FrontendRequest): boolean;
|
|
123
213
|
/**
|
|
@@ -147,6 +237,8 @@ export interface ProjectionContext {
|
|
|
147
237
|
* buttons are absent rather than dead.
|
|
148
238
|
*/
|
|
149
239
|
canRespond?: boolean;
|
|
240
|
+
/** The conversation's task list as its task tools change it; without one, those calls are ordinary rows. */
|
|
241
|
+
plan?: PlanTracker;
|
|
150
242
|
}
|
|
151
243
|
/** Pending requests belong to the live runtime, even when saved transcript
|
|
152
244
|
* history predates them. Rehydrate their controls before waiting for new events. */
|
|
@@ -172,14 +264,6 @@ export interface ProjectedTurn {
|
|
|
172
264
|
/** Rendered parts for an assistant turn. */
|
|
173
265
|
parts?: ChatPart[];
|
|
174
266
|
}
|
|
175
|
-
/**
|
|
176
|
-
* A "user" message no person typed: Claude Code's `<task-notification>` (a background task reporting back) or the
|
|
177
|
-
* editor's automatic tripwire nudge or Claude's unmarked interruption record. None is shown as the person's words.
|
|
178
|
-
* A task notification and interruption are short notices; a nudge is not shown. Undefined for a person's message.
|
|
179
|
-
*/
|
|
180
|
-
export declare function automaticPrompt(body: string): {
|
|
181
|
-
notice?: string;
|
|
182
|
-
} | undefined;
|
|
183
267
|
export declare function projectReplayTurns(events: readonly FrontendEvent[], granted: ReadonlySet<string>, context: ProjectionContext, history?: readonly ProjectedTurn[]): ProjectedTurn[];
|
|
184
268
|
/**
|
|
185
269
|
* The attach snapshot's canonical history as chat turns. Bounded: when older
|
|
@@ -187,4 +271,4 @@ export declare function projectReplayTurns(events: readonly FrontendEvent[], gra
|
|
|
187
271
|
*/
|
|
188
272
|
export declare function projectHistory(history: readonly (ChatMessage & {
|
|
189
273
|
metadata?: Record<string, string>;
|
|
190
|
-
})[], granted: ReadonlySet<string>, pendingToolNames?: ReadonlySet<string
|
|
274
|
+
})[], granted: ReadonlySet<string>, pendingToolNames?: ReadonlySet<string>, plan?: PlanTracker): ProjectedTurn[];
|