@volter/supercode-frontend-vscode 0.1.64 → 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 CHANGED
@@ -180,9 +180,10 @@ default is never stored as a choice (a pick equal to the default follows the def
180
180
  changes), and an Auto approve is never stored for a conversation that cannot answer. Conversations opened before 0.1.36 saved their picker's
181
181
  **Ask** when opened and keep it. Auto approve approves the requests the harness would ask you about, including requests to run
182
182
  outside its sandbox (Codex's approval requests are its sandbox escalations); the harness's deny
183
- rules still apply. A request it answers adds no line of its own: the call's row is the record (Code-OSS also says
184
- "Auto approved" on that row, which the extension API cannot); a card already shown gets its answer as a line beneath
185
- it, and an approval it could not answer has its card drawn again for the person to answer. A turn
183
+ rules still apply. A request it answers adds no line of its own: the call's row is the record, as in Code-OSS's own
184
+ agent host, whose Auto approve answers a harness's call with no reason on its row; a card already shown loses its
185
+ buttons where the host can retire it (`chatConfirmationResolution`), else gets its answer as a line beneath it, and an
186
+ approval it could not answer has its card drawn again for the person to answer. A turn
186
187
  re-joined after a page reload (the conversation's active response) is answered the same way. Unsupported modes are refused. Unanswerable
187
188
  harnesses show **Harness permissions**. Previously answered requests cannot affect another
188
189
  connection. Native command-button fallback limitations are documented below.
@@ -312,7 +313,9 @@ the frontend does not coerce them into JavaScript's object label.
312
313
  record this history path. The original Windows approval-row report remains
313
314
  unattributed, and native approval behavior is separate.
314
315
 
315
- The extension asks for five proposals. A host grants them through
316
+ The extension asks for eight proposals. Three of them (`chatSessionResponses`, `chatSystemNotification`,
317
+ `chatConfirmationResolution`) exist only in Volter's Code-OSS fork (volter-ai/code-oss, from `cb0c38b5`); a host
318
+ without one drops its name as a proposal that does not exist. A host grants them through
316
319
  `product.json#extensionEnabledApiProposals` (a Code-OSS derivative that bundles this extension) or
317
320
  `--enable-proposed-api volter-ai-dev.supercode-frontend-vscode`. **Stock VS Code grants none, and
318
321
  everything below still works there** — the granted set is read back from the extension host at
@@ -326,13 +329,18 @@ request), and each part declares its own fallback.
326
329
  | `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 |
327
330
  | `chatParticipantPrivate` | dynamic participant identity with the harness and provider icon; an interrupted turn's error at warning level | static participant identity; the interruption is an ordinary error |
328
331
  | `chatProvider` | native fixed-model identity and provider icon | host new-chat configuration |
332
+ | `chatSessionResponses` | the `ChatResult` a re-joined response resolves with and a saved turn's `result.errorDetails` as the response's error; the question carousel on a re-joined response; a conversation that could not be opened anchored to the system-initiated request "Couldn't open session" (`ChatRequestTurn.systemInitiatedLabel`) | a re-joined or saved response's error as a warning part, `Answer…` buttons on a re-joined response, that conversation's error under the marker request |
333
+ | `chatSystemNotification` | `ChatResponseSystemNotificationPart` for a harness notice, with its icon, live and saved | the notice as a plain line |
334
+ | `chatConfirmationResolution` | `ChatResponseConfirmationPart.isUsed`: an approval card answered elsewhere loses its buttons | one plain line beneath the card says the answer |
329
335
 
330
336
  ### Parity with Code-OSS's own chat
331
337
 
332
- The measure is the event, not the API part: **does this event look as the pinned Code-OSS (`4a7cd45`, the fork
333
- this product ships) draws its own equivalent event?** Using a native part for something VS Code never draws is not
338
+ The measure is the event, not the API part: **does this event look as Code-OSS draws its own equivalent
339
+ event?** The citations are read from Volter's Code-OSS fork at the editor's `FORK.json` pin; the workbench archives
340
+ this product ships are cut from earlier fork commits until they are re-cut at it: `4a7cd45` on macOS arm64, `4782d9a2`
341
+ on Linux x64 and Windows x64. Using a native part for something VS Code never draws is not
334
342
  parity. Each row names what Code-OSS itself draws, with its source, and what this frontend draws on the product's
335
- host (all five proposals granted). **Native**: drawn as Code-OSS draws it. **Fallback**: drawn, but plainer than
343
+ host (all eight proposals granted). **Native**: drawn as Code-OSS draws it. **Fallback**: drawn, but plainer than
336
344
  Code-OSS's own (why given). **Gap (API)**: Code-OSS's own drawing is not reachable through the extension API at the
337
345
  pin; the row names the fork change that would reach it. **Gap**: reachable, not built. This count is the measure of
338
346
  done; a change that moves a row says so here. Paths are under `src/vs/` unless they start with `src/vscode-dts`.
@@ -350,21 +358,24 @@ done; a change that moves a row says so here. Paths are under `src/vs/` unless t
350
358
  | A subagent and its calls | the subagent row with its calls grouped under it (`chatSubagentContentPart.ts`) | subagent tool data, `subAgentInvocationId` | native |
351
359
  | A hook that blocked or warned | the hook row (`chatHookContentPart.ts`) | `ChatResponseHookPart` | native; **gap (API)** inside a subagent: the part carries no subagent id |
352
360
  | An approval | the confirmation card, answered in the running turn (`chatConfirmationContentPart.ts`; the fork's `volter.chat.inTurnApprovalCapability`, `volterHarness/volterChat.ts`) | `ChatResponseConfirmationPart` where the host advertises in-turn cards (this product's host does); command buttons on a stock host, whose confirmation answers with a new request | native; fallback on a stock host |
353
- | A harness's questions | the question carousel (`chatQuestionCarouselPart.ts`) | the carousel on a turn this window runs; `Answer…` buttons on a re-joined response | native; **gap (API)** on a re-joined response: its stream has a resolver map no answer reaches (`extHostChatSessions.ts`). Fork: route `$handleQuestionCarouselAnswer` to the session's active-response stream |
361
+ | A harness's questions | the question carousel (`chatQuestionCarouselPart.ts`), in its turn's response | the carousel, on a turn this window runs and on a re-joined response (`chatSessionResponses`: `mainThreadChatSessions.ts` forwards the answer to the session's stream by its `resolveId`); `Answer…` buttons on a re-joined response on a host without it | native; fallback on a re-joined response without `chatSessionResponses` |
354
362
  | A request answered through its own card in this window | the card or carousel shows what was chosen (`chatConfirmationContentPart.ts`, `chatQuestionCarouselPart.ts`); the call's row carries on or ends denied | the same; a stock host's buttons stay, inert, and the status bar says what was sent | native |
355
- | A request answered any other way (another window, Auto approve turned on after its card showed, a typed reply) | the card retires itself with the answer | its card stays live; one plain line beneath it says the answer | **gap (API)**: an extension cannot retire a confirmation or carousel it pushed. Fork: let a pushed `ChatResponseConfirmationPart` or question carousel be resolved by the extension (marked used with the chosen answer) |
356
- | An approval answered automatically (Auto approve) | the call's row says "Auto approved …" (`chatToolPartUtilities.ts` `getApprovalMessageFromReason`, lines 58-80) | the call's row alone; an answer that fails or does not settle draws the approval's card again, as the one still waiting | **gap (API)**: `ChatToolInvocationPart` carries no confirmation reason. Fork: a proposed approval message (or reason) on `ChatToolInvocationPart`, passed through `extHostTypeConverters.ts` to the invocation's confirmed reason |
363
+ | A request answered any other way (another window, Auto approve turned on after its card showed, a typed reply) | the request's card retires itself: the call's row carries on or ends denied (`agentHostSessionHandler.ts` `IChatToolInvocation.confirmWith`); a carousel answered elsewhere is marked used with its answers (`answeredExternally`) | an approval card loses its buttons (`chatConfirmationResolution`: the same `ChatResponseConfirmationPart` pushed with `isUsed`, which `chatModel.ts` matches by its `data`); the call's row carries the outcome. A question carousel stays live, with one plain line beneath it saying the answer | native for an approval card; **gap (API)** for a question carousel: an extension cannot mark a carousel it pushed answered. Fork: let the extension resolve a pushed carousel with the answers given elsewhere (`answeredExternally`) |
364
+ | An approval answered automatically (Auto approve) | the call's row alone: Code-OSS's own agent host answers a harness's call under its Auto approve with `confirmed: NotNeeded` and no reason (`platform/agentHost/node/sessionPermissions.ts` `createToolReadyAction`, after `agentSideEffects.ts` strips the confirmation), so `getApprovalMessageFromReason` (`chatToolPartUtilities.ts`) has nothing to say. Only a client tool the workbench runs itself is "Auto approved by `autoApprove`" (`agentHostSessionHandler.ts` `getClientToolPreApproval`) | the call's row alone; an answer that fails or does not settle draws the approval's card again, as the one still waiting | native |
357
365
  | A request this window cannot answer (another client holds it) | no read-only confirmation: another client's call is its row, "Running {0} on another client…", with a cancel action (`stateToProgressAdapter.ts` `toolCallStateToInvocation`) | its title and summary as words, no controls; its answer as a line beneath it | fallback: the extension API cannot draw another client's call |
358
- | A turn that failed | the response's error (`agentHostSessionHandler.ts` `_getTurnErrorDetails`, `errorDetails`) | the request handler's `ChatResult.errorDetails` | native on a turn this window runs; **gap (API)** on a re-joined response and in history, which show VS Code's warning part with the error's words: the ext host ignores what `activeResponseCallback` returns (`extHostChatSessions.ts`) and `convertResponseTurn` drops `result.errorDetails`. Fork: let `activeResponseCallback` resolve a `ChatResult` whose `errorDetails` the main thread sets on the response (`response.setResult`, as `agentHostSessionHandler.ts` does), and pass `turn.result.errorDetails` through `convertResponseTurn` |
359
- | A turn interrupted from elsewhere | the response's warning-level, expected error (`_getTurnErrorDetails`, `executionInterrupted`) | `errorDetails` with `level: Warning`, `isExpectedError` | native on a turn this window runs; **gap (API)** elsewhere, as above |
366
+ | A turn that failed | the response's error (`agentHostSessionHandler.ts` `_getTurnErrorDetails`, `errorDetails`) | the request handler's `ChatResult.errorDetails`; on a re-joined response the `ChatResult` its `activeResponseCallback` resolves with, and in history the saved turn's `result.errorDetails` (`chatSessionResponses`: `extHostChatSessions.ts` passes both on, and `chatServiceImpl.ts` sets the result on the response as it completes); on a host without it, VS Code's warning part with the error's words there | native; fallback on a re-joined response and in history without `chatSessionResponses` |
367
+ | A turn interrupted from elsewhere | the response's warning-level, expected error (`_getTurnErrorDetails`, `executionInterrupted`) | `errorDetails` with `level: Warning`, `isExpectedError`, on the same paths | native; fallback without `chatSessionResponses`, as above |
360
368
  | A turn the person stopped | VS Code marks the response stopped | nothing more | native |
361
369
  | A request Chat cannot run (no sign-in, no runtime, a refused send) | the request's error | a thrown error, which the workbench writes as the response's error | native |
362
- | A transport or stream error while a turn may still run (`stream_error`, `transport_error`, `watch_error`, `runtime_disconnected`; a re-joined stream that fails; a conversation that cannot be recovered) | the response's error | VS Code's warning part with the error's words | fallback: the turn may still be running, so it is not ended with an error; a saved response cannot carry one (above) |
370
+ | A transport or stream error while a turn may still run (`stream_error`, `transport_error`, `watch_error`, `runtime_disconnected`) | the response's error | VS Code's warning part with the error's words | fallback: the turn may still be running, so it is not ended with an error |
371
+ | A re-joined stream that fails; a conversation that cannot be recovered | the response's error; a session that cannot be loaded is the system-initiated request "Couldn't open session" with that error (`agentHostSessionHandler.ts` `sessionLoadFailed`, `sessionLoadFailedLabel`) | the re-joined response's result carries the error; the conversation's marker request is the system-initiated "Couldn't open session" and its saved response's result carries the error (`chatSessionResponses`); VS Code's warning part under the marker request on a host without it | native; fallback without `chatSessionResponses` |
363
372
  | A harness warning (`cache_warning`, `warning`, a usage limit) | the warning part (`chatErrorContentPart.ts`, warning level) | `ChatResponseWarningPart` | native |
364
- | A harness record that is not the person's words (a background task's notification, live or saved; an interruption record) | a system notification row (`stateToProgressAdapter.ts` `systemNotificationToChatPart`; `claudeReplayMapper.ts` keeps `compact_boundary` and `notification`) | the notice's own words as a plain line, once per task (Claude Code reports a finished task both as `system/task_notification` and as the user-role record that wakes the agent; the saved transcript keeps only the latter, a `queued_command` attachment); not a progress part, which Code-OSS hides once any other part follows it (`chatProgressContentPart.ts`) | **gap (API)**: the extension API has no system-notification part. Fork: a proposed `ChatResponseSystemNotificationPart` converted to `{kind: 'systemNotification'}` in `extHostTypeConverters.ts` |
373
+ | A harness record that is not the person's words (a background task's notification, live or saved; an interruption record) | a system notification row, its icon picked per notification kind with the completion check as default (`stateToProgressAdapter.ts` `systemNotificationToChatPart`; `claudeReplayMapper.ts` keeps `compact_boundary` and `notification`) | `ChatResponseSystemNotificationPart` with the notice's own words (`chatSystemNotification`, converted to `{kind: 'systemNotification'}` in `extHostTypeConverters.ts`), live and saved, once per task (Claude Code reports a finished task both as `system/task_notification` and as the user-role record that wakes the agent; the saved transcript keeps only the latter, a `queued_command` attachment). "Turn interrupted." and a background task that failed or was stopped carry `warning`, by analogy with the icon upstream gives an automatic-approval review that was interrupted: upstream draws neither as a notification of its own. A plain line on a host without it, never a progress part, which Code-OSS hides once any other part follows it (`chatProgressContentPart.ts`) | native; fallback without `chatSystemNotification` |
365
374
  | A status line (`status`) | the progress message, hidden once another part follows (`chatProgressContentPart.ts`) | `ChatResponseProgressPart` | native |
366
- | Input arriving from elsewhere while a turn runs | a request turn of its own (`agentHostSessionHandler.ts`, server-initiated turns) | **Additional input** and the words, inside the running response | **gap (API)**: an extension cannot add a request turn to an open session. Fork: let a session's provider append a request turn (end the active response and open the next with that prompt) |
367
- | A prompt the runtime's scheduler starts | a request turn of its own | a plain line, "Scheduled prompt: …" | **gap (API)**, as above |
375
+ | Input arriving from elsewhere while a turn runs | the running turn's pending Steering message until the turn takes it in, then nothing: the turn keeps its response and no request turn opens (`agentHostSessionHandler.ts` `_applyRemotePendingMessages` → `syncPendingRequestsFromRemote`; `removePendingRequest` once taken in) | **Additional input** and the words, inside the running response, once the turn takes it in | **gap (runtime)**: the runtime announces no input waiting to be taken in (no frontend event carries it), and the runtime must echo a client-chosen input id on `user_message` and `turn_started`, and return it from `submit` and `steer` (`sdk/frontend/contract.json` carries no turn or input id, so which input started which turn is only a guess by text and order). Then a proposed API with which a session's provider sets and removes its pending Steering messages, mirroring those two calls |
376
+ | A message sent from this window while a turn it follows runs (a re-joined response) | a pending Steering message, which the workbench dispatches to a streamed session's provider at once (`chatServiceImpl.ts`, mid-turn steering); the turn keeps its response | the request handler's own turn, which the runtime refuses while a turn runs | **gap (runtime)**: steering it into the running turn without drawing its runtime echo a second time needs to know which `user_message` is that echo: the runtime must echo a client-chosen input id on `user_message` and `turn_started`, and return it from `submit` and `steer` (`sdk/frontend/contract.json` carries no turn or input id, so which input started which turn is only a guess by text and order) |
377
+ | A turn the runtime starts on its own (input that starts a turn while none runs, a turn after the previous one ended) | a request turn of its own (`agentHostSessionHandler.ts`, a changed active turn id: `startServerRequest`) | drawn inside the response of this window that follows the conversation, which follows to idle (a re-joined response), or only when the conversation reopens | **gap (runtime)**: opening it as a request turn of its own needs to know which input started it: the runtime must echo a client-chosen input id on `user_message` and `turn_started`, and return it from `submit` and `steer` (`sdk/frontend/contract.json` carries no turn or input id, so which input started which turn is only a guess by text and order) |
378
+ | A prompt the runtime's scheduler starts | a system-initiated request turn: a notification row with its label (`chatListRenderer.ts` `renderSystemInitiatedRequest`) | a notice, "Scheduled prompt: …", inside the response this window follows, or nothing until the conversation reopens; the same in saved history. Nothing in this repository produces `scheduled_prompt_started` (it is declared by `agent-runtime-host.mjs` `displayKinds` and read by `frontend-transcript.mjs` and the browser client), so its payload is not known; the prompt is read from `prompt`, `name` or `message` | **gap (runtime)**: a producer, and the turn attribution above, so that its turn opens as its own system-initiated request without also drawing the runtime's `user_message` for it |
368
379
  | A Codex diff | file edits with Keep/Undo and the multi-file diff (`chatTextEditContentPart.ts`, `chatMultiDiffContentPart.ts`) | `codeblockUri` with a fenced diff, plus one reference per file | **gap (known)**: the harness has already written the files and no event carries the before/after the editor needs (see *Why `diff_updated` is not an edit part*) |
369
380
  | An MCP call | the MCP input/output row | the simple input/output row | fallback: its result arrives as text |
370
381
  | Token usage | the context-window indicator (`stream.usage`) | not shown | **gap** |
@@ -373,11 +384,16 @@ done; a change that moves a row says so here. Paths are under `src/vs/` unless t
373
384
  | History past its bound | left out without a word; a turn without its request gets "Message content could not be retrieved" (`claudeReplayMapper.ts` `missingPromptPlaceholder`) | the same | native |
374
385
  | A call waiting on approval in re-joined history | its row, with its card on the active response | the same | native |
375
386
 
376
- Count, by row (33): 22 native on this product's host, of which three (the questions, a failed turn, an interrupted
377
- turn) are native only on a turn this window runs and an API gap on a re-joined response, and one (hooks) is an API gap
378
- inside a subagent; 4 fallback; 5 API gaps (a request answered elsewhere, an automatic approval, harness records,
379
- input from elsewhere, a scheduled prompt); 1 known gap (native edits); 1 gap not built (`stream.usage`). The fork
380
- changes named above would close every API gap; none is made yet.
387
+ Count, by row (36), on this product's host (Volter's Code-OSS fork at the editor's `FORK.json` pin, with all eight
388
+ proposals granted; a stock host draws the fallbacks above): 26 native, of which hooks are an API gap inside a
389
+ subagent and a request answered elsewhere an API gap for a question carousel; 4 fallback; 4 gaps that wait on the
390
+ runtime's turn attribution (input from elsewhere while a turn runs, a message this window sends into a turn it
391
+ follows, a turn the runtime starts on its own, a scheduled prompt, which also has no producer yet); 1 known gap
392
+ (native edits); 1 gap not built (`stream.usage`). The rows marked native for a harness's questions on a re-joined
393
+ response, harness records, a failed or interrupted turn on a re-joined or saved response, a conversation that cannot
394
+ be recovered, and a card answered elsewhere rest on the fork's proposals `chatSessionResponses`,
395
+ `chatSystemNotification` and `chatConfirmationResolution`; an automatic approval is native because Code-OSS's own
396
+ agent host draws that call's row alone.
381
397
 
382
398
  Parts the extension API offers that no harness event this frontend receives carries, so not used:
383
399
  `ChatResponseAnchorPart`, `ChatResponseFileTreePart`, `ChatResponseMarkdownWithVulnerabilitiesPart`,
@@ -417,21 +433,21 @@ it needs and the fallback it degrades to. Each arm names the producer it reads.
417
433
  | Event kind | Producer | Rendered as |
418
434
  |---|---|---|
419
435
  | `text_delta` | `crates/runtime/src/event.rs` `AgentEvent::TextDelta` | `response.markdown` (streamed) |
420
- | `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` as a plain line, or nothing |
436
+ | `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 before `turn_started` (VS Code already shows it); after it, **Additional input** inside the response; a harness record shows its `notice` as a system notification row (`chatSystemNotification`) → a plain line, or nothing |
421
437
  | `turn_started` / `turn_completed` / `turn_succeeded` | `submit_claimed`; `AgentEvent::TurnCompleted` | lifecycle only — the handler resolves, nothing renders |
422
- | `turn_interrupted` | `submit_claimed` | the request handler's result: `errorDetails` at warning level; elsewhere `ChatResponseWarningPart` → its words |
423
- | `turn_failed` | `submit_claimed` | the request handler's result: `errorDetails`; elsewhere `ChatResponseWarningPart` → blockquote |
438
+ | `turn_interrupted` | `submit_claimed` | the response's result: `errorDetails` at warning level (a re-joined or saved response needs `chatSessionResponses`); else `ChatResponseWarningPart` → its words |
439
+ | `turn_failed` | `submit_claimed` | the response's result: `errorDetails` (a re-joined or saved response needs `chatSessionResponses`); else `ChatResponseWarningPart` → blockquote |
424
440
  | `tool_call_started` / `tool_call_completed` | `AgentEvent::ToolCallStarted` / `ToolCallCompleted` | one `ChatToolInvocationPart` per call, updated in place with its result (`enablePartialUpdate`) → a progress line plus a labeled Markdown result |
425
441
  | `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 |
426
442
  | `request` | `sdk/interchange/frontend-request-bridge.mjs` request publication | `ChatResponseConfirmationPart` where the host advertises in-turn cards, the question carousel for questions; else a titled markdown block plus one **command button per answer**. Read-only (title and summary) for a request this window cannot answer |
427
- | `request_resolved` | `FrontendRequestBroker::respond` | nothing for a card answered through itself in this window; else one plain line beneath the card naming the answer |
443
+ | `request_resolved` | `FrontendRequestBroker::respond` | nothing for a card answered through itself in this window; a native approval card answered elsewhere loses its buttons (`chatConfirmationResolution`); else one plain line beneath the card naming the answer |
428
444
  | `cache_warning` | `AgentEvent::CacheWarning` | `ChatResponseWarningPart` → blockquote |
429
445
  | `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 |
430
446
  | `background_tasks` | `runtime-native-projection.mjs` from Claude Code's `background_tasks_changed` | nothing: a background command is its call's row, as in Code-OSS's own Claude host |
431
447
  | 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 |
432
448
  | `usage` | `AgentEvent::Usage` | nothing (see `stream.usage` in the parity table) |
433
449
  | `background_output` | `sdk/interchange/agent-background.mjs` `status` | nothing: the status call's own row carries the output |
434
- | `scheduled_prompt_started` / `_deferred` / `_completed` | declared by `agent-runtime-host.mjs` `displayKinds`; no producer here | started: a plain line, "Scheduled prompt: …"; deferred and completed: lifecycle only |
450
+ | `scheduled_prompt_started` / `_deferred` / `_completed` | declared by `agent-runtime-host.mjs` `displayKinds`; no producer here | started: a notice, "Scheduled prompt: …"; deferred and completed: lifecycle only |
435
451
  | `scheduler_error` | same | `ChatResponseWarningPart` with its words → blockquote |
436
452
  | `reasoning` / `thinking` | no producer in this repository; read because `sdk/interchange/frontend-transcript.mjs` reads them | `ChatResponseThinkingProgressPart` → blockquote |
437
453
  | `stream_error` / `transport_error` / `watch_error` / `runtime_disconnected` | `crates/opencode-frontend/src/server.rs`; `crates/interchange/src/watch.rs`; synthesized by a frontend (`sdk/interchange/frontend-app.mjs`); `stream_error` has no producer here | `ChatResponseWarningPart` with its words → blockquote |
@@ -467,11 +483,13 @@ status bar says what was sent. An elicitation that carries a `requested_schema`
467
483
  can also be answered by typing.
468
484
 
469
485
  **A harness's questions are VS Code's question carousel.** Why the carousel once swallowed its answer, read
470
- from the pinned Code-OSS (`4a7cd45`): `questionCarousel` resolves through the resolver map of the stream it
486
+ from Code-OSS at `4a7cd45`, the fork commit the macOS arm64 archive is still cut from (Linux and Windows: `4782d9a2`): `questionCarousel` resolves through the resolver map of the stream it
471
487
  was called on, and `$handleQuestionCarouselAnswer` reads only the participant's map
472
488
  (`extHostChatAgents2.ts`). A session's *re-joined* response (`activeResponseCallback`) gets a stream with a
473
- map of its own (`extHostChatSessions.ts`) that no answer ever reaches. So the carousel is used only on a
474
- turn this window's request handler is running; a re-joined response keeps the buttons above. The design:
489
+ map of its own (`extHostChatSessions.ts`) that no answer reached at that pin. The fork's `chatSessionResponses`
490
+ routes a carousel answer to a session's stream by its `resolveId` (`mainThreadChatSessions.ts` →
491
+ `$handleQuestionCarouselAnswer`), so on the product's host the carousel is used on a re-joined response too;
492
+ a host without it keeps the buttons above on a re-joined response. The design:
475
493
 
476
494
  - *The question reaches Chat as a request.* Supercode's own `ask_user` already asks through an `elicitation`
477
495
  whose `requested_schema.properties.qN` carry `title`, `description`, `x-options` (`[{label, description}]`)
@@ -486,7 +504,8 @@ turn this window's request handler is running; a re-joined response keeps the bu
486
504
  alone as `x-question`, and `x-options`) is one `ChatQuestion` per property: single or multi select with
487
505
  free text allowed, or text when it has no options; Skip is allowed. Any other form (an MCP server's own
488
506
  schema, with its `enum`, `format` or `required`) keeps the `Answer…` door, which honours them. Without the
489
- carousel (no `chatParticipantAdditions`, or a re-joined response) it is the `Answer…` button.
507
+ carousel (no `chatParticipantAdditions`, or a re-joined response on a host without `chatSessionResponses`) it is
508
+ the `Answer…` button.
490
509
  - *The answer reaches the harness in its own format.* The carousel's answers become the elicitation's
491
510
  `{action:'accept', content:{qN: label | [labels] | text}}`, or `decline` when skipped or submitted with
492
511
  nothing chosen; a stopped turn sends nothing (Stop cancels its requests). A reply typed into Chat instead
package/dist/extension.js CHANGED
@@ -10,7 +10,7 @@ import { createFrontendClient } from './client.js';
10
10
  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
- import { RequestNotPendingError, VscodeFrontendSession } from './session.js';
13
+ import { RequestNotPendingError, TURN_TERMINAL_KINDS, VscodeFrontendSession } from './session.js';
14
14
  import { acceptedDecisions, opaqueText, projectEvent, projectPendingRequests, projectHistory, projectReplayTurns, HISTORY_MARKER_PROMPT, PlanTracker, resolveParts, resolutionSentence, turnOutcome, 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). */
@@ -204,6 +204,10 @@ class SupercodeFrontends {
204
204
  }
205
205
  }
206
206
  let nativeInTurnApprovalCards = false;
207
+ /** The host draws a harness notice as its system notification row (`chatSystemNotification`). */
208
+ let systemNotifications = false;
209
+ /** The host retires a confirmation the extension marks answered (`chatConfirmationResolution`). */
210
+ let confirmationResolution = false;
207
211
  /** The output channel, for records that are not conversation (`unshown` parts). */
208
212
  let outputLog;
209
213
  /** Each such record once, however often a history is rebuilt from the same events. */
@@ -225,6 +229,18 @@ const drew = (stream, requestId) => {
225
229
  const drawn = drawnRequests.get(stream) ?? new Set();
226
230
  drawnRequests.set(stream, drawn.add(requestId));
227
231
  };
232
+ /** The confirmation cards each response stream drew natively, so one answered elsewhere can be retired. */
233
+ const drawnCards = new WeakMap();
234
+ /** Retires a native card drawn in `stream`, as one answered through itself is; false where the host cannot. */
235
+ function retireCard(stream, requestId) {
236
+ const card = confirmationResolution ? drawnCards.get(stream)?.get(requestId) : undefined;
237
+ if (!card)
238
+ return false;
239
+ const retired = new vscode.ChatResponseConfirmationPart(card.title, card.message, card.data, card.buttons);
240
+ retired.isUsed = true;
241
+ stream.push(retired);
242
+ return true;
243
+ }
228
244
  /**
229
245
  * A harness's questions as VS Code's own carousel, inside the running turn; the person's answers are the
230
246
  * elicitation's `{action:'accept', content:{<key>: label | [labels] | text}}`, and Skip is a decline. The answer
@@ -337,6 +353,12 @@ function writePart(stream, part, state) {
337
353
  case 'progress':
338
354
  stream.progress(part.value);
339
355
  return;
356
+ case 'notice':
357
+ if (systemNotifications)
358
+ stream.push(new vscode.ChatResponseSystemNotificationPart(part.value, part.icon ? new vscode.ThemeIcon(part.icon) : undefined));
359
+ else
360
+ stream.markdown(`\n\n${part.value}\n\n`);
361
+ return;
340
362
  case 'warning':
341
363
  stream.warning(part.value);
342
364
  return;
@@ -370,6 +392,9 @@ function writePart(stream, part, state) {
370
392
  case 'resolution':
371
393
  if (!drawnRequests.get(stream)?.has(part.requestId) || answeredByCard.has(cardKey(part.connectionId, part.requestId)))
372
394
  return;
395
+ // A native card answered elsewhere retires itself, as Code-OSS's does; the call's row carries the outcome.
396
+ if (retireCard(stream, part.requestId))
397
+ return;
373
398
  stream.markdown(`\n\n${part.value}\n\n`);
374
399
  return;
375
400
  case 'unshown':
@@ -380,9 +405,10 @@ function writePart(stream, part, state) {
380
405
  return;
381
406
  drew(stream, part.requestId);
382
407
  if (part.approval && nativeInTurnApprovalCards && part.answers.length > 0) {
383
- stream.confirmation(part.title, part.message, {
384
- kind: 'supercode.inTurnApproval', version: 1, answers: part.answers,
385
- }, part.answers.map(answer => answer.label));
408
+ const card = { title: part.title, message: part.message, data: { kind: 'supercode.inTurnApproval', version: 1, answers: part.answers }, buttons: part.answers.map(answer => answer.label) };
409
+ stream.confirmation(card.title, card.message, card.data, card.buttons);
410
+ const cards = drawnCards.get(stream) ?? new Map();
411
+ drawnCards.set(stream, cards.set(part.requestId, card));
386
412
  return;
387
413
  }
388
414
  // Stable API on purpose: a command button runs INSIDE the live turn,
@@ -472,6 +498,8 @@ export async function activate(context) {
472
498
  // description, so this is the grant, not the request.
473
499
  const granted = grantedProposals(context.extension.packageJSON);
474
500
  log.info(`Volter Harness: proposals requested ${REQUESTED_PROPOSALS.join(', ')}; granted ${granted.size ? [...granted].join(', ') : 'none (stable chat parts only)'}`);
501
+ systemNotifications = granted.has('chatSystemNotification');
502
+ confirmationResolution = granted.has('chatConfirmationResolution');
475
503
  // Explicit workbench support is required: stock confirmation clicks send
476
504
  // a new prompt. A missing capability keeps the stable in-turn buttons.
477
505
  if (granted.has('chatParticipantAdditions')) {
@@ -1608,8 +1636,9 @@ export async function activate(context) {
1608
1636
  void vscode.window.showWarningMessage(`${connection.label}: ${label} is waiting for approval. Open its chat to answer it.`);
1609
1637
  }
1610
1638
  };
1611
- // An approval answered automatically adds no line of its own (Code-OSS says "Auto approved" on the call's row,
1612
- // which the extension API cannot), and a card it answered after the card was shown gets its resolution line.
1639
+ // An approval answered automatically adds no line of its own: Code-OSS's own agent host draws a call its Auto
1640
+ // approve answered as its plain row (`sessionPermissions.ts` `createToolReadyAction`, `confirmed: NotNeeded`).
1641
+ // A card it answered after the card was shown gets its resolution.
1613
1642
  const unsettled = setTimeout(() => {
1614
1643
  log.warn(`Auto approve has had no answer for ${label} for ${AUTO_ANSWER_SETTLE_MS / 1000} s.`);
1615
1644
  showCard();
@@ -2259,6 +2288,12 @@ function historyParts(parts, granted) {
2259
2288
  out.push(hookPart(part));
2260
2289
  continue;
2261
2290
  }
2291
+ // A harness notice is Code-OSS's system notification row in history too, where the host has it.
2292
+ if (part.part === 'notice' && granted.has('chatSystemNotification')) {
2293
+ flush();
2294
+ out.push(new vscode.ChatResponseSystemNotificationPart(part.value, part.icon ? new vscode.ThemeIcon(part.icon) : undefined));
2295
+ continue;
2296
+ }
2262
2297
  // A warning and the agent's reasoning are VS Code's own parts in history too, where the host granted them.
2263
2298
  if ((part.part === 'warning' || part.part === 'thinking') && granted.has('chatParticipantAdditions')) {
2264
2299
  flush();
@@ -2272,10 +2307,29 @@ function historyParts(parts, granted) {
2272
2307
  flush();
2273
2308
  return out.length ? out : [new vscode.ChatResponseMarkdownPart('')];
2274
2309
  }
2275
- /** A conversation that failed, as a history response shows it: VS Code's warning part where the host granted it (the
2276
- * extension API does not carry a history response's own error), else the words alone. */
2277
- function failurePart(message, granted) {
2278
- return granted.has('chatParticipantAdditions') ? new vscode.ChatResponseWarningPart(message) : new vscode.ChatResponseMarkdownPart(message);
2310
+ /** How a turn that did not succeed ends its response, as VS Code's agent host ends one (`_getTurnErrorDetails`). */
2311
+ function outcomeErrorDetails(outcome, granted) {
2312
+ // (an interruption is the expected, warning-level error: `isExpectedError` and `level` are
2313
+ // `chatParticipantPrivate`'s, which the ext host checks for `isExpectedError`, so a host without it gets the message)
2314
+ return outcome.interrupted && granted.has('chatParticipantPrivate')
2315
+ ? { message: outcome.message, isExpectedError: true, level: 1 /* ChatErrorLevel.Warning */ }
2316
+ : { message: outcome.message };
2317
+ }
2318
+ /** A conversation that failed, as a history response shows it: the response's own error where the host keeps a saved
2319
+ * response's result (`chatSessionResponses`), as Code-OSS shows a session that could not be loaded; else VS Code's
2320
+ * warning part where the host granted it, else the words alone. */
2321
+ function failureResponse(message, granted) {
2322
+ if (granted.has('chatSessionResponses'))
2323
+ return { parts: [], result: { errorDetails: { message } } };
2324
+ return { parts: [granted.has('chatParticipantAdditions') ? new vscode.ChatResponseWarningPart(message) : new vscode.ChatResponseMarkdownPart(message)], result: {} };
2325
+ }
2326
+ /** The request a failure's response answers: the marker turn, which Code-OSS anchors as the system-initiated request
2327
+ * "Couldn't open session" (`agentHostSessionHandler.ts` `sessionLoadFailedLabel`) where the host keeps that label. */
2328
+ function failureRequest(turns, participant, granted) {
2329
+ const request = new turns.ChatRequestTurn(HISTORY_MARKER_PROMPT, undefined, [], participant, []);
2330
+ if (granted.has('chatSessionResponses'))
2331
+ request.systemInitiatedLabel = "Couldn't open session";
2332
+ return request;
2279
2333
  }
2280
2334
  /**
2281
2335
  * One text part as the markdown a HISTORY turn carries (tool calls are drawn
@@ -2285,6 +2339,7 @@ function historyText(part) {
2285
2339
  switch (part.part) {
2286
2340
  case 'markdown':
2287
2341
  case 'progress':
2342
+ case 'notice':
2288
2343
  return part.value;
2289
2344
  case 'warning':
2290
2345
  return `> ⚠ ${part.value}`;
@@ -2424,10 +2479,11 @@ function registerSessions(context, frontends, participant, granted, handler, his
2424
2479
  failedResources.set(resource.authority, message);
2425
2480
  refreshItems();
2426
2481
  }
2482
+ const failure = failureResponse(message, granted);
2427
2483
  return {
2428
2484
  history: [
2429
- new turns.ChatRequestTurn(HISTORY_MARKER_PROMPT, undefined, [], 'supercode', []),
2430
- new turns.ChatResponseTurn([failurePart(message, granted)], {}, 'supercode'),
2485
+ failureRequest(turns, 'supercode', granted),
2486
+ new turns.ChatResponseTurn(failure.parts, failure.result, 'supercode'),
2431
2487
  ],
2432
2488
  requestHandler: boundHandler,
2433
2489
  };
@@ -2518,6 +2574,7 @@ function registerSessions(context, frontends, participant, granted, handler, his
2518
2574
  if (!live)
2519
2575
  void controls?.finishIdentitySave(connection);
2520
2576
  const activeParts = [];
2577
+ let heldFailure;
2521
2578
  if (live) {
2522
2579
  // Standalone requests have no user turn. Give their live response
2523
2580
  // an explicit runtime marker instead of reopening an old answer.
@@ -2538,6 +2595,8 @@ function registerSessions(context, frontends, participant, granted, handler, his
2538
2595
  // responses can be stale; their command buttons are disabled.
2539
2596
  const last = projected.at(-1);
2540
2597
  if (last?.role === 'assistant') {
2598
+ // (a failure that ended that turn stays held for the live response, as it was in history)
2599
+ heldFailure = last.errorEvent;
2541
2600
  activeParts.push(...(projected.pop()?.parts ?? []).map(part => part.part === 'answers' || part.part === 'questions'
2542
2601
  ? { part: 'markdown', value: `**${(part.part === 'questions' ? part.fallback : part).title}**\n\n${(part.part === 'questions' ? part.fallback : part).message}` }
2543
2602
  // (its card is words here, so its answer is said beneath it)
@@ -2552,12 +2611,14 @@ function registerSessions(context, frontends, participant, granted, handler, his
2552
2611
  history.push(new turns.ChatRequestTurn(turn.prompt ?? '', undefined, [], authorId, []));
2553
2612
  continue;
2554
2613
  }
2555
- history.push(new turns.ChatResponseTurn(historyParts(oneRowPerToolCall(turn.parts ?? []), granted), {}, authorId));
2614
+ // (a turn that failed keeps its error as the saved response's own, where the host shows one)
2615
+ history.push(new turns.ChatResponseTurn(historyParts(oneRowPerToolCall(turn.parts ?? []), granted), turn.error ? { errorDetails: outcomeErrorDetails(turn.error, granted) } : {}, authorId));
2556
2616
  }
2557
2617
  if (connection.session.failure) {
2558
2618
  // An explicit runtime marker pairs this notice without presenting
2559
2619
  // it as a person's prompt. The native busy state stays unchanged.
2560
- history.push(new turns.ChatRequestTurn(HISTORY_MARKER_PROMPT, undefined, [], authorId, []), new turns.ChatResponseTurn([failurePart(opaqueText(connection.session.failure.message, 2048), granted)], {}, authorId));
2620
+ const failure = failureResponse(opaqueText(connection.session.failure.message, 2048), granted);
2621
+ history.push(failureRequest(turns, authorId, granted), new turns.ChatResponseTurn(failure.parts, failure.result, authorId));
2561
2622
  }
2562
2623
  const renderedCursor = connection.session.cursor;
2563
2624
  const pendingAtCursor = [...connection.session.pendingRequests.values()];
@@ -2574,8 +2635,31 @@ function registerSessions(context, frontends, participant, granted, handler, his
2574
2635
  // answered here under the conversation's mode (t_39dac582): Auto approve answers the cards
2575
2636
  // already waiting and later ones; Ask leaves them for the person, as before.
2576
2637
  const watch = controls && !controls.attached(connection) ? controls.watchApprovals(connection, stream, token) : undefined;
2638
+ // Where the host keeps a re-joined response's result and answers its questions (`chatSessionResponses`),
2639
+ // how the turn it ends on did not succeed is that result, as on a turn this window runs, and its
2640
+ // questions are the carousel. It follows to idle, as it always did, so an earlier turn's failure it
2641
+ // passes is still drawn, as its warning part.
2642
+ const results = granted.has('chatSessionResponses');
2643
+ let failure = results ? heldFailure : undefined;
2644
+ let result;
2645
+ // Draws the held failure where it stood, once anything follows it, as history draws one.
2646
+ let release = () => { };
2577
2647
  try {
2578
- const state = { toolInputs: new Map(), toolNames: new Map(), toolTitles: new Map(), toolCommands: new Map(), toolKinds: new Map(), quiet: watch?.answers };
2648
+ const state = { toolInputs: new Map(), toolNames: new Map(), toolTitles: new Map(), toolCommands: new Map(), toolKinds: new Map(), quiet: watch?.answers,
2649
+ ...(results ? { carousel: true, pending: (requestId) => connection.session.pendingRequests.has(requestId), cancelled: () => token.isCancellationRequested } : {}) };
2650
+ const project = (event) => projectEvent(event, {
2651
+ connectionId: connection.id,
2652
+ liveUserMessages: true,
2653
+ plan,
2654
+ notices,
2655
+ pendingRequest: (id) => connection.session.pendingRequests.get(id) ?? connection.session.resolvedRequests.get(id),
2656
+ canRespond: Boolean(connection.session.descriptor?.actions.respond && connection.session.permissions.has('approve')),
2657
+ });
2658
+ release = () => {
2659
+ if (failure)
2660
+ writeProjected(stream, project(failure), granted, state);
2661
+ failure = undefined;
2662
+ };
2579
2663
  for (const part of oneRowPerToolCall(activeParts))
2580
2664
  writePart(stream, part, state);
2581
2665
  writeProjected(stream, projectPendingRequests(pendingAtCursor.filter(request => connection.session.pendingRequests.has(request.id)), {
@@ -2585,25 +2669,45 @@ function registerSessions(context, frontends, participant, granted, handler, his
2585
2669
  watch?.sweep();
2586
2670
  controls?.rememberIdentity(connection);
2587
2671
  await observeActiveResponse(connection.session, token, (event) => {
2588
- writeProjected(stream, projectEvent(event, {
2589
- connectionId: connection.id,
2590
- liveUserMessages: true,
2591
- plan,
2592
- notices,
2593
- pendingRequest: (id) => connection.session.pendingRequests.get(id) ?? connection.session.resolvedRequests.get(id),
2594
- canRespond: Boolean(connection.session.descriptor?.actions.respond && connection.session.permissions.has('approve')),
2595
- }), granted, state);
2672
+ // (a failure the next turn follows is that turn's past: drawn where it stood as soon as the next
2673
+ // turn starts or anything is drawn after it, so neither a later Stop nor a disconnect makes it
2674
+ // this response's error)
2675
+ const terminal = TURN_TERMINAL_KINDS.includes(event.kind);
2676
+ if (event.kind === 'turn_started' || terminal)
2677
+ release();
2678
+ if (results && terminal) {
2679
+ failure = turnOutcome(event) && !token.isCancellationRequested ? event : undefined;
2680
+ }
2681
+ else {
2682
+ const projected = project(event);
2683
+ if (resolveParts(projected, granted).some(part => part.part !== 'unshown'))
2684
+ release();
2685
+ writeProjected(stream, projected, granted, state);
2686
+ }
2596
2687
  watch?.sweep();
2597
2688
  controls?.rememberIdentity(connection);
2598
2689
  }, () => { connection.viewReady = true; }, renderedCursor);
2690
+ const outcome = failure && turnOutcome(failure);
2691
+ if (outcome)
2692
+ result = { errorDetails: outcomeErrorDetails(outcome, granted) };
2599
2693
  }
2600
2694
  catch (error) {
2601
2695
  if (token.isCancellationRequested || error instanceof vscode.CancellationError)
2602
2696
  throw error;
2603
- // The response's own error is what VS Code shows, and a re-joined response cannot set one (the
2604
- // ext host ignores what `activeResponseCallback` returns), so it is the warning part.
2697
+ // The response's own error is what VS Code shows; a host that does not keep a re-joined
2698
+ // response's result gets the warning part.
2605
2699
  const message = opaqueText(String(error?.message ?? error), 2048);
2606
- if (granted.has('chatParticipantAdditions'))
2700
+ // (a failure held when the connection drops is drawn; the drop is the result)
2701
+ if (results) {
2702
+ try {
2703
+ release();
2704
+ }
2705
+ catch {
2706
+ // (a stream that refuses a part has closed)
2707
+ }
2708
+ result = { errorDetails: { message } };
2709
+ }
2710
+ else if (granted.has('chatParticipantAdditions'))
2607
2711
  stream.warning(message);
2608
2712
  else
2609
2713
  stream.markdown(`\n\n${message}\n\n`);
@@ -2613,6 +2717,7 @@ function registerSessions(context, frontends, participant, granted, handler, his
2613
2717
  watch?.dispose();
2614
2718
  await controls?.finishIdentitySave(connection);
2615
2719
  }
2720
+ return result;
2616
2721
  }
2617
2722
  : undefined,
2618
2723
  };
@@ -13,7 +13,7 @@
13
13
  * unconditionally, so a `typeof vscode.ChatResponseConfirmationPart` probe
14
14
  * cannot answer this question — only the description can.
15
15
  */
16
- export declare const REQUESTED_PROPOSALS: readonly ["defaultChatParticipant", "chatParticipantPrivate", "chatParticipantAdditions", "chatSessionsProvider", "chatProvider"];
16
+ export declare const REQUESTED_PROPOSALS: readonly ["defaultChatParticipant", "chatParticipantPrivate", "chatParticipantAdditions", "chatSessionsProvider", "chatProvider", "chatSessionResponses", "chatSystemNotification", "chatConfirmationResolution"];
17
17
  export type ProposalName = (typeof REQUESTED_PROPOSALS)[number];
18
18
  /** What each requested proposal buys, and what this frontend does without it. */
19
19
  export declare const PROPOSAL_EFFECTS: Readonly<Record<ProposalName, {
package/dist/proposals.js CHANGED
@@ -19,6 +19,10 @@ export const REQUESTED_PROPOSALS = Object.freeze([
19
19
  'chatParticipantAdditions',
20
20
  'chatSessionsProvider',
21
21
  'chatProvider',
22
+ // Volter's Code-OSS fork (volter-ai/code-oss); a host without them drops the names ("DOES NOT EXIST").
23
+ 'chatSessionResponses',
24
+ 'chatSystemNotification',
25
+ 'chatConfirmationResolution',
22
26
  ]);
23
27
  /** What each requested proposal buys, and what this frontend does without it. */
24
28
  export const PROPOSAL_EFFECTS = Object.freeze({
@@ -39,6 +43,18 @@ export const PROPOSAL_EFFECTS = Object.freeze({
39
43
  grants: 'the runtime’s sessions in the Chat view’s session picker, each reopening with its history',
40
44
  without: 'the participant still attaches; there is no session list',
41
45
  },
46
+ chatSessionResponses: {
47
+ grants: 'a re-joined or saved response ending with its error; questions on a re-joined response as the carousel; a conversation that could not be opened anchored as Code-OSS anchors one',
48
+ without: 'a re-joined or saved response’s error as a warning part; questions there as buttons; that conversation’s error under the marker request',
49
+ },
50
+ chatSystemNotification: {
51
+ grants: 'a harness notice (a background task finishing, an interruption record) as the system notification row',
52
+ without: 'the notice as a plain line',
53
+ },
54
+ chatConfirmationResolution: {
55
+ grants: 'an approval card answered elsewhere losing its buttons',
56
+ without: 'the card stays and one plain line beneath it says the answer',
57
+ },
42
58
  });
43
59
  /** Read the proposals the HOST granted this extension, never the ones it asked for. */
44
60
  export function grantedProposals(packageJSON) {
package/dist/view.d.ts CHANGED
@@ -34,6 +34,14 @@ export type ChatPart = {
34
34
  } | {
35
35
  part: 'progress';
36
36
  value: string;
37
+ }
38
+ /** A record of the harness that is neither the agent's words nor the person's (a background task finishing, an
39
+ * interruption record, a scheduled prompt's start): Code-OSS's system notification row where the host has it
40
+ * (`chatSystemNotification`), with `icon` where it reports no completion, else a plain line. Markdown-safe text. */
41
+ | {
42
+ part: 'notice';
43
+ value: string;
44
+ icon?: string;
37
45
  } | {
38
46
  part: 'warning';
39
47
  value: string;
@@ -300,6 +308,14 @@ export interface ProjectedTurn {
300
308
  prompt?: string;
301
309
  /** Rendered parts for an assistant turn. */
302
310
  parts?: ChatPart[];
311
+ /** How an assistant turn ended when it did not succeed, kept as the saved response's own error where the host
312
+ * shows one (`chatSessionResponses`); otherwise it is a warning part among `parts`. */
313
+ error?: {
314
+ message: string;
315
+ interrupted: boolean;
316
+ };
317
+ /** The record `error` came from, drawn as its warning part where anything follows it. */
318
+ errorEvent?: FrontendEvent;
303
319
  }
304
320
  export declare function projectReplayTurns(events: readonly FrontendEvent[], granted: ReadonlySet<string>, context: ProjectionContext, history?: readonly ProjectedTurn[]): ProjectedTurn[];
305
321
  /**
package/dist/view.js CHANGED
@@ -701,8 +701,8 @@ export function projectEvent(event, context) {
701
701
  // (a subagent's own input, `parent_id`: what it was asked is on its row)
702
702
  if (text(payload.parent_id))
703
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`).
704
+ // A harness record (a background task's notification, an interruption) is Code-OSS's system notification row
705
+ // (`noticeLine`).
706
706
  const notice = notPersonNotice(payload);
707
707
  if (notice !== undefined)
708
708
  return notice && firstNotice(context, notifiedCall(payload)) ? [stable(noticeLine(notice))] : [];
@@ -918,9 +918,9 @@ export function projectEvent(event, context) {
918
918
  return [];
919
919
  // crates/harness/src/server.rs `submit_claimed`. VS Code's own agent host draws an interrupted turn as its
920
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).
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
924
  case 'turn_interrupted': {
925
925
  const value = turnOutcome(event).message;
926
926
  return [{ emit: { part: 'warning', value }, needs: 'chatParticipantAdditions', fallback: [{ part: 'markdown', value }] }];
@@ -935,8 +935,9 @@ export function projectEvent(event, context) {
935
935
  // repository and is read only because that reader reads it.
936
936
  //
937
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.
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.
940
941
  case 'turn_failed':
941
942
  case 'stream_error':
942
943
  case 'transport_error':
@@ -1057,7 +1058,7 @@ function messageText(message) {
1057
1058
  }
1058
1059
  /**
1059
1060
  * What a `user_message` shows when no person typed it: undefined for the person's words, '' to show nothing, else a
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
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
1061
1062
  * provenance rule the runtime applies (`@volter/supercode-interchange/user-provenance`). The editor's automatic
1062
1063
  * tripwire nudge travels through the person's input door, so the editor's frame is what names it.
1063
1064
  */
@@ -1069,11 +1070,26 @@ function notPersonNotice(payload) {
1069
1070
  return undefined;
1070
1071
  }
1071
1072
  /**
1072
- * A notice as a line of the transcript: its own words, plain. Not a progress part: Code-OSS hides a progress message
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
1073
1075
  * as soon as any other part follows it (`chatProgressContentPart.ts`), so the notice would never be seen.
1074
1076
  */
1075
1077
  function noticeLine(notice) {
1076
- return { part: 'markdown', value: `\n\n${plain(notice)}\n\n` };
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;
1077
1093
  }
1078
1094
  /** The call a background task's notification record names (`<tool-use-id>`), for `firstNotice`. */
1079
1095
  function notifiedCall(payload) {
@@ -1126,6 +1142,18 @@ export function projectReplayTurns(events, granted, context, history = []) {
1126
1142
  }
1127
1143
  let replayTurn = false;
1128
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
+ };
1129
1157
  for (const event of events) {
1130
1158
  if (event.kind === 'request') {
1131
1159
  const request = (payloadOf(event).request ?? payloadOf(event));
@@ -1150,7 +1178,7 @@ export function projectReplayTurns(events, granted, context, history = []) {
1150
1178
  const last = turns[turns.length - 1];
1151
1179
  const notice = noticeLine(shown);
1152
1180
  if (last.role === 'assistant' && last.parts)
1153
- last.parts.push(notice);
1181
+ append(last, notice);
1154
1182
  else
1155
1183
  turns.push({ role: 'assistant', parts: [notice] });
1156
1184
  continue;
@@ -1159,6 +1187,31 @@ export function projectReplayTurns(events, granted, context, history = []) {
1159
1187
  replayTurn = true;
1160
1188
  continue;
1161
1189
  }
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
+ }
1162
1215
  // (a record that is not conversation is no part of a turn: it goes to `unshown`, wherever it falls)
1163
1216
  const parts = resolveParts(projectEvent(event, context), granted).filter(part => {
1164
1217
  if (part.part !== 'unshown')
@@ -1184,7 +1237,7 @@ export function projectReplayTurns(events, granted, context, history = []) {
1184
1237
  }
1185
1238
  const last = turns[turns.length - 1];
1186
1239
  if (last.role === 'assistant' && last.parts)
1187
- last.parts.push(...parts);
1240
+ append(last, ...parts);
1188
1241
  else
1189
1242
  turns.push({ role: 'assistant', parts });
1190
1243
  }
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.64",
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": [