@kushalkhemka/opensec-pi-subagents 0.20.3 → 0.20.4

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/CHANGELOG.md CHANGED
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.20.4] - 2026-10-16
11
+
12
+ The conversation viewer draws a subagent's transcript with Pi's own chat components, so it looks exactly like Pi's
13
+ main session.
14
+
15
+ > **Possibly breaking:** the `viewerMarkdown` setting (`/agents → Settings → Viewer markdown`) and the viewer's `m`
16
+ > key are removed: Pi's components decide how each message renders. A `viewerMarkdown` key left in `subagents.json`
17
+ > is ignored.
18
+
19
+ ### Changed
20
+
21
+ - **The conversation viewer (regular overlay and fullscreen observer) renders like Pi's main session.** It builds
22
+ `UserMessageComponent`, `AssistantMessageComponent` (thinking and text), `ToolExecutionComponent` (each tool's own
23
+ call and result renderers from the subagent's session: edit diffs, read previews, bash command, output, exit status
24
+ and duration, running/done/error state, arguments), `BashExecutionComponent`, `CompactionSummaryMessageComponent`,
25
+ `BranchSummaryMessageComponent`, `SkillInvocationMessageComponent` and `CustomMessageComponent` from the session's
26
+ messages as Pi's interactive mode does, with the same spacing and collapsed defaults. Streaming messages, tool
27
+ progress and results update live. It replaces the viewer's own `[User]` / `[Assistant]` / `[Tool: name]` /
28
+ `[Result]` rendering and its 16k-character result cap (Pi's renderers truncate collapsed output themselves).
29
+ - **`ctrl+o` expands and collapses tool output in the viewer**, as in Pi; it follows a rebinding of Pi's
30
+ `app.tools.expand`, and the footer shows it when there is room. Pi's `hideThinkingBlock`, `outputPad` and
31
+ `markdown.codeBlockIndent` settings apply.
32
+ - Each component is built once and its rendered lines are cached per width and expand state; a frame re-renders only
33
+ the streamed message and tools that changed, scrolling never re-renders, and only visible rows are assembled.
34
+ Without a JIT (Bun with `BUN_JSC_useJIT=0`, standing in for Pi-Bolt), at 500 messages: a frame 6 µs, a scroll step
35
+ 15 µs, a streaming delta 0.70 ms (was 0.92 ms), a tool call finishing 0.26 ms (was 0.31 ms), the first frame 121 ms
36
+ (was 160 ms). A new user message costs 0.27 ms (was 0.17 ms): Pi's user message is Markdown on a background.
37
+ - Where the viewer differs from Pi's main session: images in tool results show as Pi's text placeholder, since an
38
+ inline image cannot be clipped to a scrolled overlay; Mermaid blocks are not drawn as diagrams (Pi's Mermaid
39
+ transformer is not exported); OSC 133 prompt marks are not emitted inside the overlay; clicking a tool or thinking
40
+ block does not toggle it (use `ctrl+o`).
41
+
42
+ ### Removed
43
+
44
+ - The `viewerMarkdown` setting and the viewer's `m` key (see above).
45
+
10
46
  ## [0.20.3] - 2026-10-16
11
47
 
12
48
  Background agents survive a session switch, subagents run on the parent's model by default, and nested and workflow
package/README.md CHANGED
@@ -108,6 +108,28 @@ appears under one of them after 2 s. In the conversation viewer (FleetView, `/ag
108
108
  run moves from the foreground to the background concurrency pool, over the limit if the pool is full.
109
109
  - Nested agents, workflow agents, queued spawns and resumed runs cannot be handed off.
110
110
 
111
+ ### Conversation viewer
112
+
113
+ Opening an agent (FleetView, `/agents`, the workflow inspector's `c`) shows its conversation drawn by Pi's own chat
114
+ components, exactly as Pi draws the main session: user and assistant messages with thinking blocks, each tool call
115
+ with its arguments and its tool's own result rendering (edit diffs, read previews, bash command, output, exit status
116
+ and duration, running/done/error state), `!` bash runs, compaction and branch summaries, skill blocks and extension
117
+ messages. Tool renderers come from the subagent's own session, so an extension tool draws as it does in Pi. A message
118
+ that is still streaming and a tool that is still running update live.
119
+
120
+ - `ctrl+o` expands and collapses tool output, as in Pi (it follows a rebinding of Pi's `app.tools.expand`).
121
+ - Pi's `hideThinkingBlock`, `outputPad` and `markdown.codeBlockIndent` settings apply, read when the viewer opens.
122
+ - Agent output is sanitized before Pi's components see it: escape and control sequences in messages, tool arguments
123
+ and results are removed (Pi's own styling is kept).
124
+ - Not the same as the main session: images in tool results show as Pi's text placeholder (an inline image cannot be
125
+ clipped to a scrolled overlay), Mermaid blocks are not drawn as diagrams (Pi's Mermaid renderer is not available to
126
+ extensions), and clicking a tool block or thinking block does not toggle it (use `ctrl+o`).
127
+ - The `viewerMarkdown` setting and the viewer's `m` key were removed in 0.20.4; a `viewerMarkdown` key left in
128
+ `subagents.json` is ignored.
129
+ - Cost: each message's component is built once and its rendered lines are kept; a frame re-renders only the streamed
130
+ message and tools that changed, and scrolling never re-renders. Without a JIT (Pi-Bolt) a frame takes about 5 µs and
131
+ a scroll step about 15 µs at any transcript length.
132
+
111
133
  ### Fullscreen conversation observer
112
134
 
113
135
  In Pi's fullscreen mode (`/settings → TUI mode → fullscreen`, Pi's default), an agent opened from FleetView, `/agents`
@@ -118,14 +140,15 @@ or pauses an agent, and `Esc` returns to the previous view (while composing, the
118
140
  - A one-column scrollbar in Pi's style that follows Pi's `/settings → Fullscreen scrollbar`: `auto` appears while
119
141
  scrolling or hovering and hides after a second, `always` stays, `hidden` removes it and its mouse target. Drag the
120
142
  thumb, or click the track to page. The column is kept free in `auto` mode too, so the bar never re-wraps the text.
121
- - Clickable footer controls (steer, send to background, stop with two clicks, Markdown mode, close).
143
+ - Clickable footer controls (steer, send to background, stop with two clicks, expand tool output, close).
122
144
  - While scrolled away, the divider above the footer shows `[ ↓ N new messages · Ctrl+End ]` (conversation messages,
123
145
  not streaming updates); click it or press `Ctrl+End` to jump back and follow, without losing a steering draft.
124
146
  - Mouse events are consumed, so they never scroll, select or click the main view underneath. Text selection, copying
125
147
  and switching agents without leaving the observer are not implemented.
126
148
  - Cost: built on the viewer's incremental transcript, so scrolling, wheel and drags reuse cached lines, a frame checks
127
- only the transcript's last message and draws only the visible rows, and nothing ticks while it is idle. Without a JIT
128
- (Pi-Bolt), a frame or a scroll step costs well under a millisecond whatever the transcript's length.
149
+ only what changed and draws only the visible rows, and nothing ticks while it is idle (a running bash tool's elapsed
150
+ time redraws its block once a second, as in Pi). Without a JIT (Pi-Bolt), a frame or a scroll step costs well under a
151
+ millisecond whatever the transcript's length.
129
152
 
130
153
  ### Resuming agents
131
154
 
package/dist/index.js CHANGED
@@ -541,14 +541,6 @@ export default function (pi) {
541
541
  let showModel = false;
542
542
  function isShowModelEnabled() { return showModel; }
543
543
  function setShowModel(b) { showModel = b; widget.update(); }
544
- /**
545
- * How much of the conversation viewer renders as Markdown. Read through a
546
- * getter by the viewer rather than captured like `showCost`, because the
547
- * viewer's `m` key writes back here while the overlay is on screen.
548
- */
549
- let viewerMarkdown = "assistant";
550
- function getViewerMarkdown() { return viewerMarkdown; }
551
- function setViewerMarkdown(mode) { viewerMarkdown = mode; }
552
544
  /** The `backgroundKey` setting as written; undefined means the default. Persisted as written. */
553
545
  let backgroundKeySetting;
554
546
  function setBackgroundKey(key) { backgroundKeySetting = key; }
@@ -557,16 +549,6 @@ export default function (pi) {
557
549
  const key = backgroundKeySetting ?? DEFAULT_BACKGROUND_KEY;
558
550
  return key === "off" ? undefined : key;
559
551
  }
560
- /**
561
- * The viewer's `m` key, from either entry point: set the mode and persist it,
562
- * so the key and `/agents → Settings` stay one setting rather than one per
563
- * entry point. `ctx` carries only the warning a failed write notifies with,
564
- * and the fleet list may be acting without one.
565
- */
566
- function chooseViewerMarkdown(mode, ctx) {
567
- setViewerMarkdown(mode);
568
- persistSettings(ctx, `Viewer markdown set to ${mode}`);
569
- }
570
552
  const pendingUsage = new PendingUsagePool();
571
553
  /**
572
554
  * Set once this activation's session is shutting down (`/new`, `/resume`,
@@ -1694,10 +1676,28 @@ export default function (pi) {
1694
1676
  return "auto";
1695
1677
  }
1696
1678
  }
1679
+ /**
1680
+ * Pi's chat settings the conversation viewer draws with, read from the main
1681
+ * session's live settings and normalized as Pi's SettingsManager getters do,
1682
+ * so a subagent's transcript looks like the main one.
1683
+ */
1684
+ function getViewerRenderOptions() {
1685
+ try {
1686
+ const settings = pi.getSettings();
1687
+ return {
1688
+ hideThinkingBlock: settings.hideThinkingBlock ?? false,
1689
+ outputPad: settings.outputPad === 0 ? 0 : 1,
1690
+ codeBlockIndent: settings.markdown?.codeBlockIndent ?? " ",
1691
+ };
1692
+ }
1693
+ catch {
1694
+ return {};
1695
+ }
1696
+ }
1697
1697
  // Claude Code-style FleetView: navigable list of main + subagents below the editor.
1698
- // The last two arguments keep a conversation overlay opened here identical to
1699
- // one opened from `/agents`: same setting on the way in, same persist out.
1700
- const fleet = new FleetList(manager, agentActivity, isShowCostEnabled, getViewerMarkdown, (mode) => chooseViewerMarkdown(mode, currentCtx), refreshTicker, backgroundActionFor, getFullscreenScrollbar);
1698
+ // The render options keep a conversation overlay opened here identical to one
1699
+ // opened from `/agents`.
1700
+ const fleet = new FleetList(manager, agentActivity, isShowCostEnabled, getViewerRenderOptions, refreshTicker, backgroundActionFor, getFullscreenScrollbar);
1701
1701
  let fleetViewEnabled = true;
1702
1702
  function isFleetViewEnabled() { return fleetViewEnabled; }
1703
1703
  function setFleetViewEnabled(b) { fleetViewEnabled = b; fleet.setEnabled(b); }
@@ -2015,7 +2015,6 @@ export default function (pi) {
2015
2015
  setReportUsage,
2016
2016
  setShowCost,
2017
2017
  setShowModel,
2018
- setViewerMarkdown,
2019
2018
  setBackgroundKey,
2020
2019
  };
2021
2020
  applyAndEmitLoaded(settingsAppliers, (event, payload) => pi.events.emit(event, payload), process.cwd(), projectTrusted);
@@ -3730,7 +3729,7 @@ Terse command-style prompts produce shallow, generic work.
3730
3729
  if (manager.abort(record.id)) {
3731
3730
  ctx.ui.notify(`Stopped "${record.description}".`, "info");
3732
3731
  }
3733
- }, keybindings, (message) => manager.steer(record.id, message), showCost, getViewerMarkdown, (mode) => chooseViewerMarkdown(mode, ctx), backgroundActionFor(record, message => ctx.ui.notify(message, "info")), getFullscreenScrollbar());
3732
+ }, keybindings, (message) => manager.steer(record.id, message), showCost, backgroundActionFor(record, message => ctx.ui.notify(message, "info")), getFullscreenScrollbar(), getViewerRenderOptions());
3734
3733
  }, {
3735
3734
  overlay: true,
3736
3735
  overlayOptions: () => overlayOptions,
@@ -4122,7 +4121,6 @@ Write the file using the write tool. Only write the file, nothing else.`;
4122
4121
  reportUsage: isReportUsageEnabled(),
4123
4122
  showCost: isShowCostEnabled(),
4124
4123
  showModel: isShowModelEnabled(),
4125
- viewerMarkdown: getViewerMarkdown(),
4126
4124
  // As written: undefined (the default) stays out of the file.
4127
4125
  backgroundKey: backgroundKeySetting,
4128
4126
  };
@@ -4292,13 +4290,6 @@ Write the file using the write tool. Only write the file, nothing else.`;
4292
4290
  currentValue: isShowModelEnabled() ? "on" : "off",
4293
4291
  values: ["on", "off"],
4294
4292
  },
4295
- {
4296
- id: "viewerMarkdown",
4297
- label: "Viewer markdown",
4298
- description: "How much of the conversation viewer renders as Markdown. assistant = assistant text only (default); all = tool results too, for tools that emit Markdown — accepting that a Markdown pass over a diff or a log eats `#` comments, swallows a `---` line and re-fences indented output; off = everything verbatim. `m` in the viewer cycles the same setting (footer: raw / md / md+).",
4299
- currentValue: getViewerMarkdown(),
4300
- values: ["off", "assistant", "all"],
4301
- },
4302
4293
  {
4303
4294
  id: "fleetView",
4304
4295
  label: "Fleet view",
@@ -4485,10 +4476,6 @@ Write the file using the write tool. Only write the file, nothing else.`;
4485
4476
  setShowModel(enabled);
4486
4477
  notifyApplied(ctx, `Model display ${enabled ? "enabled" : "disabled"}`);
4487
4478
  }
4488
- else if (id === "viewerMarkdown") {
4489
- setViewerMarkdown(value);
4490
- notifyApplied(ctx, `Viewer markdown set to ${value}`);
4491
- }
4492
4479
  else if (id === "fleetView") {
4493
4480
  const enabled = value === "on";
4494
4481
  setFleetViewEnabled(enabled);
@@ -4590,23 +4577,6 @@ Write the file using the write tool. Only write the file, nothing else.`;
4590
4577
  // the right toast. Successful saves show info; persistence failures downgrade
4591
4578
  // to warning so users aren't silently reverted on restart. Event fires regardless
4592
4579
  // of outcome so listeners see the in-memory change.
4593
- /**
4594
- * Persist + broadcast the settings, silent on success — for a change whose
4595
- * feedback is the UI it just changed: the viewer's `m` key, where a
4596
- * notification per press would talk over the overlay it is describing.
4597
- *
4598
- * A *failed* write still speaks. Every other settings path warns when the
4599
- * value is session-only, and swallowing it here would leave a preference
4600
- * looking persisted when the next session will not have it.
4601
- */
4602
- function persistSettings(ctx, changeMsg) {
4603
- const { message, level } = saveAndEmitChanged(snapshotSettings(), changeMsg, (event, payload) => pi.events.emit(event, payload));
4604
- // `ctx` is absent only on the fleet path between sessions, where
4605
- // `currentCtx` has been cleared and there is no UI to carry the warning to.
4606
- // The write still happens.
4607
- if (level === "warning")
4608
- ctx?.ui.notify(message, level);
4609
- }
4610
4580
  function notifyApplied(ctx, successMsg) {
4611
4581
  const { message, level } = saveAndEmitChanged(snapshotSettings(), successMsg, (event, payload) => pi.events.emit(event, payload));
4612
4582
  ctx.ui.notify(message, level);
@@ -35,7 +35,7 @@
35
35
  */
36
36
  import { type ExtensionContext } from "@earendil-works/pi-coding-agent";
37
37
  /** The package version this code ships in. `test/session-handoff.test.ts` keeps it equal to package.json. */
38
- export declare const PACKAGE_VERSION = "0.20.3";
38
+ export declare const PACKAGE_VERSION = "0.20.4";
39
39
  /** Bumped when the shape of a parked entry changes within one version (never in a release). */
40
40
  export declare const HANDOFF_PROTOCOL = 1;
41
41
  /** Writes an entry into the session `owner` if the caller can (it is the active one there); false otherwise. */
@@ -36,7 +36,7 @@
36
36
  import { existsSync } from "node:fs";
37
37
  import { SessionManager } from "@earendil-works/pi-coding-agent";
38
38
  /** The package version this code ships in. `test/session-handoff.test.ts` keeps it equal to package.json. */
39
- export const PACKAGE_VERSION = "0.20.3";
39
+ export const PACKAGE_VERSION = "0.20.4";
40
40
  /** Bumped when the shape of a parked entry changes within one version (never in a release). */
41
41
  export const HANDOFF_PROTOCOL = 1;
42
42
  const HANDOFF_KEY = Symbol.for("pi-subagents:handoff:@kushalkhemka/opensec-pi-subagents");
@@ -1,4 +1,4 @@
1
- import type { AgentMentionMode, JoinMode, ViewerMarkdownMode, WidgetMode } from "./types.js";
1
+ import type { AgentMentionMode, JoinMode, WidgetMode } from "./types.js";
2
2
  export type ProjectTrustMode = "full" | "pi";
3
3
  export interface SubagentsSettings {
4
4
  maxConcurrent?: number;
@@ -310,19 +310,6 @@ export interface SubagentsSettings {
310
310
  * narrow terminal.
311
311
  */
312
312
  showModel?: boolean;
313
- /**
314
- * How much of the conversation viewer's transcript renders as Markdown.
315
- * Defaults to `assistant`. Applied live — the viewer's `m` key cycles this
316
- * same setting, so a choice made in the overlay persists like one made in
317
- * `/agents → Settings`.
318
- *
319
- * Scoped rather than all-or-nothing because the two kinds of content have
320
- * different contracts: assistant text is authored as Markdown, while a tool
321
- * result is whatever bytes the tool produced. Rendering the latter as
322
- * Markdown is lossy in ways that look like the tool misbehaved — see
323
- * `ViewerMarkdownMode` for the specific rewrites — so `all` is opt-in.
324
- */
325
- viewerMarkdown?: ViewerMarkdownMode;
326
313
  /**
327
314
  * The key that sends every running foreground `Agent` call to the background,
328
315
  * as Claude Code's ctrl+b does. A pi key id (`ctrl+b`, `alt+b`, `ctrl+shift+b`,
@@ -363,7 +350,6 @@ export interface SettingsAppliers {
363
350
  setReportUsage: (b: boolean) => void;
364
351
  setShowCost: (b: boolean) => void;
365
352
  setShowModel: (b: boolean) => void;
366
- setViewerMarkdown: (mode: ViewerMarkdownMode) => void;
367
353
  setBackgroundKey: (key: string) => void;
368
354
  }
369
355
  /** Emit callback — a subset of `pi.events.emit` to keep helpers testable. */
package/dist/settings.js CHANGED
@@ -9,7 +9,6 @@ import { createDiscoveryCache } from "./discovery-cache.js";
9
9
  const VALID_JOIN_MODES = new Set(["async", "group", "smart"]);
10
10
  const VALID_TOOL_DESCRIPTION_MODES = new Set(["full", "compact", "custom"]);
11
11
  const VALID_WIDGET_MODES = new Set(["all", "background", "off"]);
12
- const VALID_VIEWER_MARKDOWN_MODES = new Set(["off", "assistant", "all"]);
13
12
  const VALID_AGENT_MENTION_MODES = new Set(["model", "direct", "off"]);
14
13
  // Sanity ceilings — prevent hand-edited configs from asking for values that
15
14
  // make no operational sense (e.g. 1e6 concurrent subagents). Permissive enough
@@ -116,9 +115,7 @@ function sanitize(raw) {
116
115
  if (typeof r.showModel === "boolean") {
117
116
  out.showModel = r.showModel;
118
117
  }
119
- if (typeof r.viewerMarkdown === "string" && VALID_VIEWER_MARKDOWN_MODES.has(r.viewerMarkdown)) {
120
- out.viewerMarkdown = r.viewerMarkdown;
121
- }
118
+ // `viewerMarkdown` (until 0.20.3) is ignored: the viewer draws with Pi's own components now.
122
119
  if (typeof r.backgroundKey === "string" && r.backgroundKey.trim()) {
123
120
  out.backgroundKey = r.backgroundKey.trim().toLowerCase();
124
121
  }
@@ -241,8 +238,6 @@ export function applySettings(s, appliers) {
241
238
  appliers.setShowCost(s.showCost);
242
239
  if (typeof s.showModel === "boolean")
243
240
  appliers.setShowModel(s.showModel);
244
- if (s.viewerMarkdown)
245
- appliers.setViewerMarkdown(s.viewerMarkdown);
246
241
  if (s.backgroundKey)
247
242
  appliers.setBackgroundKey(s.backgroundKey);
248
243
  if (typeof s.workflowsEnabled === "boolean")
package/dist/types.d.ts CHANGED
@@ -100,20 +100,6 @@ export type JoinMode = 'async' | 'group' | 'smart';
100
100
  * - `off`: hide the widget entirely.
101
101
  */
102
102
  export type WidgetMode = 'all' | 'background' | 'off';
103
- /**
104
- * How much of the conversation viewer's transcript is rendered as Markdown.
105
- * - `off`: every line wraps as literal text, as it did before the mode existed.
106
- * - `assistant`: assistant text renders as Markdown; tool results stay verbatim
107
- * and dim. The default, because assistant text *is* Markdown by contract
108
- * while a tool result is arbitrary bytes — a Markdown pass over a log or a
109
- * diff eats `#` from shell comments, swallows a `---` line into a setext
110
- * heading, re-fences indented output and redraws `| a | b |` as a table.
111
- * (Ordered-list renumbering is the one such rewrite actively suppressed —
112
- * see `MARKDOWN_OPTIONS` — because it silently changes data, not layout.)
113
- * - `all`: tool results render as Markdown too, for tools that genuinely emit
114
- * it (#210's `ctx_execute`), accepting the rewrites above on ones that don't.
115
- */
116
- export type ViewerMarkdownMode = 'off' | 'assistant' | 'all';
117
103
  /**
118
104
  * How `@handle message` starts an agent that is not already running.
119
105
  * - `model`: inject Claude Code's `agent_mention` reminder and let the main
@@ -2,14 +2,19 @@
2
2
  * conversation-viewer.ts — Live conversation overlay for viewing agent sessions.
3
3
  *
4
4
  * Displays a scrollable, live-updating view of an agent's conversation.
5
- * Subscribes to session events for real-time streaming updates.
5
+ * Subscribes to session events for real-time streaming updates. The
6
+ * transcript is drawn with Pi's own chat components (`viewer-transcript.ts`),
7
+ * so it looks exactly like Pi's main session; the frame around it (header,
8
+ * footer, scrolling, steering) is the viewer's.
6
9
  */
7
10
  import { type AgentSession } from "@earendil-works/pi-coding-agent";
8
11
  import { type Component, type Focusable, type OverlayOptions, type ScrollViewScrollbar, type TUI, type TuiMouseEvent, type TuiMouseEventResult } from "@earendil-works/pi-tui";
9
- import type { AgentRecord, ViewerMarkdownMode } from "../types.js";
12
+ import type { AgentRecord } from "../types.js";
10
13
  import type { Theme } from "./agent-widget.js";
11
14
  import { type AgentActivity } from "./agent-widget.js";
12
15
  import { type ViewerKeybindings } from "./viewer-keys.js";
16
+ import { type ViewerRenderOptions } from "./viewer-transcript.js";
17
+ export type { ViewerRenderOptions } from "./viewer-transcript.js";
13
18
  /** Height ceiling shared by the overlay's `maxHeight` and the viewer's internal viewport cap. */
14
19
  export declare const VIEWPORT_HEIGHT_PCT = 70;
15
20
  /**
@@ -18,18 +23,6 @@ export declare const VIEWPORT_HEIGHT_PCT = 70;
18
23
  * setting that may need a restart to take effect.
19
24
  */
20
25
  export declare function getConversationOverlayOptions(tui: Pick<TUI, "mode">): OverlayOptions;
21
- /**
22
- * Cap on a single tool result or bash output before the viewer elides the rest.
23
- *
24
- * The cap is not cosmetic — it bounds render cost. A message's lines are
25
- * rebuilt whenever the width or Markdown mode changes, and the streaming tail
26
- * on every frame it changes, so an uncapped 200 KB result costs ~6 ms per
27
- * rebuild to parse as Markdown, against ~0.5 ms once capped (best of 5, width
28
- * 76). 16 KB is roughly a screenful at every terminal size and still ~30x the
29
- * 500 characters this replaces, which was small enough to cut most real
30
- * results mid-sentence.
31
- */
32
- export declare const RESULT_MAX_CHARS = 16000;
33
26
  /** The `b` affordance: whether this run can be handed to the background now, and the handoff. */
34
27
  export interface ViewerBackgroundAction {
35
28
  available(): boolean;
@@ -52,17 +45,6 @@ export declare class ConversationViewer implements Component, Focusable {
52
45
  * cannot change while it is on screen.
53
46
  */
54
47
  private showCost;
55
- /**
56
- * The current `viewerMarkdown` setting. Read live rather than captured,
57
- * unlike `showCost`: `m` changes it while the overlay is on screen.
58
- * Omitted → `assistant`.
59
- */
60
- private viewerMarkdown?;
61
- /**
62
- * Persist a mode chosen with `m`, so the key and `/agents → Settings` mean
63
- * the same thing. Omitted → `m` still cycles, viewer-locally.
64
- */
65
- private onMarkdownMode?;
66
48
  /**
67
49
  * Hand this run to the background. `available` is read live — it turns
68
50
  * false the instant the handoff lands or the run ends. Omitted → no `b`
@@ -107,39 +89,13 @@ export declare class ConversationViewer implements Component, Focusable {
107
89
  private keys;
108
90
  /** Steering composer — present while the user is typing a message to the agent. */
109
91
  private composer;
110
- /** Resolved once: pi's Markdown theme is fixed for the life of the process. */
111
- private readonly markdownTheme;
112
- /** Set by the `m` key. Wins over the setting so `m` works without a persist hook. */
113
- private markdownModeOverride;
114
- /**
115
- * One `Markdown` per message, so its own text/width cache does the work. A
116
- * fresh instance per render would re-parse the whole transcript on every
117
- * keystroke — the component caches, but only across calls to the same object.
118
- * Weak so a compacted-away message doesn't pin its render.
119
- */
120
- private readonly markdownCache;
121
- /**
122
- * The transcript, kept between frames. `blocks[i]` renders `messages[i]`;
123
- * `lines` holds every block's lines (and separators) in order, and
124
- * `blockStart[i]` is where block `i`'s part of it begins. All of it is valid
125
- * for `cacheWidth` and `cacheMode` only.
126
- */
127
- private blocks;
128
- private blockStart;
129
- private lines;
130
- /** `lineWidths[i]` is the visible width of `lines[i]`, measured once when the line is built. */
131
- private lineWidths;
132
92
  /**
133
- * The messages array the cache was last validated against. Pi only appends
134
- * to that array and replaces it wholesale otherwise, so while it is the same
135
- * object only the unsettled tail needs checking — what keeps a frame's cost
136
- * flat however long the transcript gets.
93
+ * The transcript drawn with Pi's components, its lines cached between
94
+ * frames. Undefined when Pi's theme is not initialized (see `plainLines`).
137
95
  */
138
- private cacheMessages;
139
- private cacheWidth;
140
- private cacheMode;
141
- /** Messages from this index on may still change in place; earlier ones are settled. */
142
- private unsettledFrom;
96
+ private readonly transcript;
97
+ /** The plain fallback's lines and what they were built for. */
98
+ private plain;
143
99
  /** Pending coalesced redraw for streaming events. */
144
100
  private streamRenderTimer;
145
101
  constructor(tui: TUI, session: AgentSession, record: AgentRecord, activity: AgentActivity | undefined, theme: Theme, done: (result: undefined) => void,
@@ -155,17 +111,6 @@ export declare class ConversationViewer implements Component, Focusable {
155
111
  * cannot change while it is on screen.
156
112
  */
157
113
  showCost?: boolean,
158
- /**
159
- * The current `viewerMarkdown` setting. Read live rather than captured,
160
- * unlike `showCost`: `m` changes it while the overlay is on screen.
161
- * Omitted → `assistant`.
162
- */
163
- viewerMarkdown?: (() => ViewerMarkdownMode) | undefined,
164
- /**
165
- * Persist a mode chosen with `m`, so the key and `/agents → Settings` mean
166
- * the same thing. Omitted → `m` still cycles, viewer-locally.
167
- */
168
- onMarkdownMode?: ((mode: ViewerMarkdownMode) => void) | undefined,
169
114
  /**
170
115
  * Hand this run to the background. `available` is read live — it turns
171
116
  * false the instant the handoff lands or the run ends. Omitted → no `b`
@@ -173,7 +118,9 @@ export declare class ConversationViewer implements Component, Focusable {
173
118
  */
174
119
  onBackground?: ViewerBackgroundAction | undefined,
175
120
  /** Pi's `fullscreenScrollbar` preference, captured when the viewer opens. */
176
- scrollbarMode?: ScrollViewScrollbar);
121
+ scrollbarMode?: ScrollViewScrollbar,
122
+ /** Pi's chat settings (thinking blocks, padding, code indent), read when the viewer opens. */
123
+ renderOptions?: ViewerRenderOptions);
177
124
  handleInput(data: string): void;
178
125
  /** Follow the conversation again and clear the new-message count (fullscreen `Ctrl+End`). */
179
126
  private jumpToLatest;
@@ -217,12 +164,10 @@ export declare class ConversationViewer implements Component, Focusable {
217
164
  * matches spawns nobody can hand off (the agent generator).
218
165
  */
219
166
  private canSendToBackground;
220
- /** The mode in force: an `m` press, else the setting, else the default. */
221
- private markdownMode;
222
- /** Wrap `text` literally — the pre-Markdown path, and the fallback from it. */
223
- private rawLines;
224
- /** Render `text` as Markdown, reusing this message's component instance. */
225
- private markdownLines;
167
+ /** Expand or collapse every tool, bash and summary block, as Pi's `app.tools.expand`. */
168
+ private toggleExpand;
169
+ /** The expand toggle's footer control: the key bound to it and what it does next. */
170
+ private expandHint;
226
171
  /** Steerable only when a steer handler exists and the agent is still active. */
227
172
  private canSteer;
228
173
  /** Open the inline steering composer and route subsequent input to it. */
@@ -236,23 +181,16 @@ export declare class ConversationViewer implements Component, Focusable {
236
181
  buildContentLines(width: number): string[];
237
182
  private contentLength;
238
183
  /**
239
- * The content area as cached transcript lines (`base`, owned by the viewer —
240
- * do not mutate) plus the live activity indicator (`tail`).
241
- *
242
- * Only messages that changed are rebuilt. A message is matched to its block
243
- * by identity; messages from `unsettledFrom` on (the last one when the
244
- * previous frame was drawn, and anything after it) are also compared by
245
- * content, because older pi streams into the last message in place and a
246
- * message can be edited and then pushed past before the next frame. Earlier
247
- * messages are settled: pi never rewrites a message once another follows it,
248
- * and compaction replaces the objects, which the identity check catches.
184
+ * The content area as cached transcript lines (`base`, owned by the
185
+ * transcript — do not mutate) with their widths, plus the live activity
186
+ * indicator (`tail`). Only blocks that changed are re-rendered, so a frame
187
+ * where nothing changed costs a few comparisons however long the transcript.
249
188
  */
250
189
  private contentView;
251
190
  /**
252
- * Everything a message's rendered lines depend on, besides width and mode.
253
- * Cheap next to rendering: string joins, no wrapping or parsing.
191
+ * The transcript as plain wrapped text, for a process whose Pi theme is not
192
+ * initialized (Pi's components cannot render there). Never the case in Pi's
193
+ * interactive mode; it keeps the viewer usable rather than throwing.
254
194
  */
255
- private messageSource;
256
- /** One message's lines without its separator, clamped to `width`; undefined when it renders nothing. */
257
- private messageLines;
195
+ private plainLines;
258
196
  }