@esso0428/pi-subagents 0.17.6 → 0.17.8

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.
Files changed (260) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/CONTRIBUTING.md +4 -0
  3. package/dist/abortable.d.ts +13 -0
  4. package/dist/abortable.d.ts.map +1 -0
  5. package/dist/abortable.js +43 -0
  6. package/dist/abortable.js.map +1 -0
  7. package/dist/agent-color.d.ts +36 -0
  8. package/dist/agent-color.d.ts.map +1 -0
  9. package/dist/agent-color.js +124 -0
  10. package/dist/agent-color.js.map +1 -0
  11. package/dist/agent-file-toggle.d.ts +126 -0
  12. package/dist/agent-file-toggle.d.ts.map +1 -0
  13. package/dist/agent-file-toggle.js +259 -0
  14. package/dist/agent-file-toggle.js.map +1 -0
  15. package/dist/agent-history.d.ts +4 -0
  16. package/dist/agent-history.d.ts.map +1 -1
  17. package/dist/agent-history.js +47 -1
  18. package/dist/agent-history.js.map +1 -1
  19. package/dist/agent-manager.d.ts +370 -56
  20. package/dist/agent-manager.d.ts.map +1 -1
  21. package/dist/agent-manager.js +1123 -409
  22. package/dist/agent-manager.js.map +1 -1
  23. package/dist/agent-runner.d.ts +100 -10
  24. package/dist/agent-runner.d.ts.map +1 -1
  25. package/dist/agent-runner.js +166 -21
  26. package/dist/agent-runner.js.map +1 -1
  27. package/dist/agent-types.d.ts +57 -5
  28. package/dist/agent-types.d.ts.map +1 -1
  29. package/dist/agent-types.js +164 -32
  30. package/dist/agent-types.js.map +1 -1
  31. package/dist/child-context.d.ts +3 -0
  32. package/dist/child-context.d.ts.map +1 -0
  33. package/dist/child-context.js +13 -0
  34. package/dist/child-context.js.map +1 -0
  35. package/dist/cross-extension-rpc.d.ts +23 -3
  36. package/dist/cross-extension-rpc.d.ts.map +1 -1
  37. package/dist/cross-extension-rpc.js +79 -17
  38. package/dist/cross-extension-rpc.js.map +1 -1
  39. package/dist/custom-agents.d.ts +38 -1
  40. package/dist/custom-agents.d.ts.map +1 -1
  41. package/dist/custom-agents.js +164 -12
  42. package/dist/custom-agents.js.map +1 -1
  43. package/dist/index.d.ts +34 -0
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +1912 -492
  46. package/dist/index.js.map +1 -1
  47. package/dist/invocation-config.d.ts +87 -2
  48. package/dist/invocation-config.d.ts.map +1 -1
  49. package/dist/invocation-config.js +71 -3
  50. package/dist/invocation-config.js.map +1 -1
  51. package/dist/mention-clone.d.ts +88 -0
  52. package/dist/mention-clone.d.ts.map +1 -0
  53. package/dist/mention-clone.js +154 -0
  54. package/dist/mention-clone.js.map +1 -0
  55. package/dist/mention.d.ts +82 -0
  56. package/dist/mention.d.ts.map +1 -0
  57. package/dist/mention.js +132 -0
  58. package/dist/mention.js.map +1 -0
  59. package/dist/model-resolver.d.ts +17 -0
  60. package/dist/model-resolver.d.ts.map +1 -1
  61. package/dist/model-resolver.js +15 -0
  62. package/dist/model-resolver.js.map +1 -1
  63. package/dist/model-scope.d.ts +50 -0
  64. package/dist/model-scope.d.ts.map +1 -0
  65. package/dist/model-scope.js +49 -0
  66. package/dist/model-scope.js.map +1 -0
  67. package/dist/nested-tools.d.ts +57 -0
  68. package/dist/nested-tools.d.ts.map +1 -0
  69. package/dist/nested-tools.js +301 -0
  70. package/dist/nested-tools.js.map +1 -0
  71. package/dist/output-file.d.ts +22 -3
  72. package/dist/output-file.d.ts.map +1 -1
  73. package/dist/output-file.js +58 -7
  74. package/dist/output-file.js.map +1 -1
  75. package/dist/prompts.d.ts +23 -0
  76. package/dist/prompts.d.ts.map +1 -1
  77. package/dist/prompts.js +20 -2
  78. package/dist/prompts.js.map +1 -1
  79. package/dist/schedule.d.ts.map +1 -1
  80. package/dist/schedule.js +36 -15
  81. package/dist/schedule.js.map +1 -1
  82. package/dist/settings.d.ts +228 -2
  83. package/dist/settings.d.ts.map +1 -1
  84. package/dist/settings.js +94 -0
  85. package/dist/settings.js.map +1 -1
  86. package/dist/status-note.d.ts +49 -1
  87. package/dist/status-note.d.ts.map +1 -1
  88. package/dist/status-note.js +62 -1
  89. package/dist/status-note.js.map +1 -1
  90. package/dist/structured-output.d.ts +62 -0
  91. package/dist/structured-output.d.ts.map +1 -0
  92. package/dist/structured-output.js +113 -0
  93. package/dist/structured-output.js.map +1 -0
  94. package/dist/types.d.ts +176 -10
  95. package/dist/types.d.ts.map +1 -1
  96. package/dist/ui/agent-mention.d.ts +83 -0
  97. package/dist/ui/agent-mention.d.ts.map +1 -0
  98. package/dist/ui/agent-mention.js +188 -0
  99. package/dist/ui/agent-mention.js.map +1 -0
  100. package/dist/ui/agent-widget.d.ts +97 -75
  101. package/dist/ui/agent-widget.d.ts.map +1 -1
  102. package/dist/ui/agent-widget.js +398 -420
  103. package/dist/ui/agent-widget.js.map +1 -1
  104. package/dist/ui/conversation-blocks.d.ts.map +1 -1
  105. package/dist/ui/conversation-blocks.js +6 -0
  106. package/dist/ui/conversation-blocks.js.map +1 -1
  107. package/dist/ui/conversation-timeline.d.ts +10 -2
  108. package/dist/ui/conversation-timeline.d.ts.map +1 -1
  109. package/dist/ui/conversation-timeline.js +130 -23
  110. package/dist/ui/conversation-timeline.js.map +1 -1
  111. package/dist/ui/conversation-viewer.d.ts +15 -5
  112. package/dist/ui/conversation-viewer.d.ts.map +1 -1
  113. package/dist/ui/conversation-viewer.js +202 -50
  114. package/dist/ui/conversation-viewer.js.map +1 -1
  115. package/dist/ui/fleet-list.d.ts +198 -0
  116. package/dist/ui/fleet-list.d.ts.map +1 -0
  117. package/dist/ui/fleet-list.js +487 -0
  118. package/dist/ui/fleet-list.js.map +1 -0
  119. package/dist/ui/schedule-menu.d.ts.map +1 -1
  120. package/dist/ui/schedule-menu.js +6 -7
  121. package/dist/ui/schedule-menu.js.map +1 -1
  122. package/dist/ui/select-item.d.ts +28 -0
  123. package/dist/ui/select-item.d.ts.map +1 -0
  124. package/dist/ui/select-item.js +35 -0
  125. package/dist/ui/select-item.js.map +1 -0
  126. package/dist/ui/workflow-card.d.ts +176 -0
  127. package/dist/ui/workflow-card.d.ts.map +1 -0
  128. package/dist/ui/workflow-card.js +333 -0
  129. package/dist/ui/workflow-card.js.map +1 -0
  130. package/dist/ui/workflow-dialog.d.ts +306 -0
  131. package/dist/ui/workflow-dialog.d.ts.map +1 -0
  132. package/dist/ui/workflow-dialog.js +844 -0
  133. package/dist/ui/workflow-dialog.js.map +1 -0
  134. package/dist/ui/workflow-menu.d.ts +61 -0
  135. package/dist/ui/workflow-menu.d.ts.map +1 -0
  136. package/dist/ui/workflow-menu.js +148 -0
  137. package/dist/ui/workflow-menu.js.map +1 -0
  138. package/dist/usage.d.ts +86 -1
  139. package/dist/usage.d.ts.map +1 -1
  140. package/dist/usage.js +72 -1
  141. package/dist/usage.js.map +1 -1
  142. package/dist/workflow/collisions.d.ts +96 -0
  143. package/dist/workflow/collisions.d.ts.map +1 -0
  144. package/dist/workflow/collisions.js +89 -0
  145. package/dist/workflow/collisions.js.map +1 -0
  146. package/dist/workflow/entry.d.ts +33 -0
  147. package/dist/workflow/entry.d.ts.map +1 -0
  148. package/dist/workflow/entry.js +30 -0
  149. package/dist/workflow/entry.js.map +1 -0
  150. package/dist/workflow/host.d.ts +63 -0
  151. package/dist/workflow/host.d.ts.map +1 -0
  152. package/dist/workflow/host.js +363 -0
  153. package/dist/workflow/host.js.map +1 -0
  154. package/dist/workflow/journal.d.ts +98 -0
  155. package/dist/workflow/journal.d.ts.map +1 -0
  156. package/dist/workflow/journal.js +121 -0
  157. package/dist/workflow/journal.js.map +1 -0
  158. package/dist/workflow/json-schema.d.ts +52 -0
  159. package/dist/workflow/json-schema.d.ts.map +1 -0
  160. package/dist/workflow/json-schema.js +112 -0
  161. package/dist/workflow/json-schema.js.map +1 -0
  162. package/dist/workflow/meta.d.ts +68 -0
  163. package/dist/workflow/meta.d.ts.map +1 -0
  164. package/dist/workflow/meta.js +318 -0
  165. package/dist/workflow/meta.js.map +1 -0
  166. package/dist/workflow/progress.d.ts +225 -0
  167. package/dist/workflow/progress.d.ts.map +1 -0
  168. package/dist/workflow/progress.js +362 -0
  169. package/dist/workflow/progress.js.map +1 -0
  170. package/dist/workflow/runtime.d.ts +335 -0
  171. package/dist/workflow/runtime.d.ts.map +1 -0
  172. package/dist/workflow/runtime.js +831 -0
  173. package/dist/workflow/runtime.js.map +1 -0
  174. package/dist/workflow/saved.d.ts +91 -0
  175. package/dist/workflow/saved.d.ts.map +1 -0
  176. package/dist/workflow/saved.js +204 -0
  177. package/dist/workflow/saved.js.map +1 -0
  178. package/dist/workflow/task.d.ts +137 -0
  179. package/dist/workflow/task.d.ts.map +1 -0
  180. package/dist/workflow/task.js +208 -0
  181. package/dist/workflow/task.js.map +1 -0
  182. package/dist/workflow/tool-description.d.ts +39 -0
  183. package/dist/workflow/tool-description.d.ts.map +1 -0
  184. package/dist/workflow/tool-description.js +200 -0
  185. package/dist/workflow/tool-description.js.map +1 -0
  186. package/dist/workflow/worker-source.d.ts +48 -0
  187. package/dist/workflow/worker-source.d.ts.map +1 -0
  188. package/dist/workflow/worker-source.js +779 -0
  189. package/dist/workflow/worker-source.js.map +1 -0
  190. package/dist/worktree.d.ts +10 -3
  191. package/dist/worktree.d.ts.map +1 -1
  192. package/dist/worktree.js +58 -54
  193. package/dist/worktree.js.map +1 -1
  194. package/dist/xml.d.ts +11 -0
  195. package/dist/xml.d.ts.map +1 -0
  196. package/dist/xml.js +13 -0
  197. package/dist/xml.js.map +1 -0
  198. package/docs/rpc.md +183 -0
  199. package/docs/superpowers/plans/2026-09-30-upstream-event-workflow-partial-history.md +195 -0
  200. package/docs/superpowers/specs/2026-09-30-upstream-event-workflow-partial-history-design.md +49 -0
  201. package/docs/workflows.md +437 -0
  202. package/examples/agent-tool-description.md +7 -7
  203. package/examples/workflows/compose.js +51 -0
  204. package/examples/workflows/fan-out-audit.js +47 -0
  205. package/examples/workflows/gated-fix.js +60 -0
  206. package/examples/workflows/lib/count-child.js +27 -0
  207. package/examples/workflows/review-panel.js +63 -0
  208. package/examples/workflows/structured-findings.js +78 -0
  209. package/package.json +1 -1
  210. package/src/abortable.ts +43 -0
  211. package/src/agent-color.ts +161 -0
  212. package/src/agent-file-toggle.ts +269 -0
  213. package/src/agent-history.ts +54 -2
  214. package/src/agent-manager.ts +1263 -402
  215. package/src/agent-runner.ts +251 -27
  216. package/src/agent-types.ts +188 -32
  217. package/src/child-context.ts +15 -0
  218. package/src/cross-extension-rpc.ts +96 -20
  219. package/src/custom-agents.ts +170 -13
  220. package/src/index.ts +2029 -536
  221. package/src/invocation-config.ts +118 -3
  222. package/src/mention-clone.ts +196 -0
  223. package/src/mention.ts +141 -0
  224. package/src/model-resolver.ts +18 -0
  225. package/src/model-scope.ts +70 -0
  226. package/src/nested-tools.ts +424 -0
  227. package/src/output-file.ts +61 -6
  228. package/src/prompts.ts +45 -2
  229. package/src/schedule.ts +35 -14
  230. package/src/settings.ts +312 -2
  231. package/src/status-note.ts +66 -1
  232. package/src/structured-output.ts +130 -0
  233. package/src/types.ts +177 -10
  234. package/src/ui/agent-mention.ts +216 -0
  235. package/src/ui/agent-widget.ts +393 -441
  236. package/src/ui/conversation-blocks.ts +6 -0
  237. package/src/ui/conversation-timeline.ts +139 -25
  238. package/src/ui/conversation-viewer.ts +212 -48
  239. package/src/ui/fleet-list.ts +558 -0
  240. package/src/ui/schedule-menu.ts +9 -8
  241. package/src/ui/select-item.ts +45 -0
  242. package/src/ui/workflow-card.ts +470 -0
  243. package/src/ui/workflow-dialog.ts +1115 -0
  244. package/src/ui/workflow-menu.ts +193 -0
  245. package/src/usage.ts +109 -2
  246. package/src/workflow/collisions.ts +123 -0
  247. package/src/workflow/entry.ts +47 -0
  248. package/src/workflow/host.ts +403 -0
  249. package/src/workflow/journal.ts +164 -0
  250. package/src/workflow/json-schema.ts +128 -0
  251. package/src/workflow/meta.ts +325 -0
  252. package/src/workflow/progress.ts +550 -0
  253. package/src/workflow/runtime.ts +1219 -0
  254. package/src/workflow/saved.ts +217 -0
  255. package/src/workflow/task.ts +302 -0
  256. package/src/workflow/tool-description.ts +200 -0
  257. package/src/workflow/worker-source.ts +781 -0
  258. package/src/worktree.ts +69 -55
  259. package/src/xml.ts +13 -0
  260. package/vitest.config.ts +0 -18
@@ -6,11 +6,12 @@
6
6
  */
7
7
  import type { AgentSession, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
8
8
  import { type Component, type TUI, type TuiMouseEvent } from "@earendil-works/pi-tui";
9
- import type { AgentRecord } from "../types.js";
9
+ import type { AgentRecord, ViewerMarkdownMode } from "../types.js";
10
10
  import type { Theme } from "./agent-widget.js";
11
11
  import { type AgentActivity } from "./agent-widget.js";
12
12
  import { type ViewerKeybindings } from "./viewer-keys.js";
13
13
  export declare const VIEWPORT_HEIGHT_PCT = 70;
14
+ export declare const RESULT_MAX_CHARS = 16000;
14
15
  /** The live fields needed by the viewer; historical viewers use a static source. */
15
16
  export type ConversationSource = Pick<AgentSession, "messages" | "subscribe">;
16
17
  export declare function createStaticConversationSource(messages: AgentSession["messages"]): ConversationSource;
@@ -31,8 +32,6 @@ export declare class ConversationViewer implements Component {
31
32
  private onStop?;
32
33
  /** Send a steering message to the agent. */
33
34
  private onSteer?;
34
- /** Optional clipboard/nvim operation context, appended for compatibility. */
35
- private operations?;
36
35
  private blocks;
37
36
  private showTools;
38
37
  private selectedMessageIdx;
@@ -44,6 +43,7 @@ export declare class ConversationViewer implements Component {
44
43
  private cacheVersion;
45
44
  private cachedVersion;
46
45
  private sessionRefreshQueued;
46
+ private lastSessionKey;
47
47
  private selectedBlockId;
48
48
  private scrollOffset;
49
49
  private autoScroll;
@@ -66,6 +66,11 @@ export declare class ConversationViewer implements Component {
66
66
  private steerHistoryDraft;
67
67
  private timeline;
68
68
  private toolPreview;
69
+ private readonly operations;
70
+ private readonly showCost;
71
+ private readonly viewerMarkdown;
72
+ private readonly onMarkdownMode;
73
+ private markdownModeOverride;
69
74
  constructor(tui: TUI, session: ConversationSource, record: AgentRecord, activity: AgentActivity | undefined, theme: Theme, done: (result: undefined) => void,
70
75
  /** Abort the agent shown here. Omitted means read-only history. */
71
76
  onStop?: (() => void) | undefined,
@@ -73,8 +78,8 @@ export declare class ConversationViewer implements Component {
73
78
  keybindings?: ViewerKeybindings,
74
79
  /** Send a steering message to the agent. */
75
80
  onSteer?: ((message: string) => void) | undefined,
76
- /** Optional clipboard/nvim operation context, appended for compatibility. */
77
- operations?: ConversationViewerOperations | undefined);
81
+ /** Cost flag in upstream's constructor; fork callers may pass operations here. */
82
+ showCostOrOperations?: boolean | ConversationViewerOperations, viewerMarkdown?: () => ViewerMarkdownMode, onMarkdownMode?: (mode: ViewerMarkdownMode) => void);
78
83
  handleInput(data: string): void;
79
84
  handleMouse(event: TuiMouseEvent): {
80
85
  handled?: boolean;
@@ -93,6 +98,11 @@ export declare class ConversationViewer implements Component {
93
98
  private transcriptWidth;
94
99
  private chromeLines;
95
100
  private invocationLine;
101
+ private currentMarkdownMode;
102
+ private cycleMarkdownMode;
103
+ private sessionKey;
104
+ private blockSignature;
105
+ private syncBlocksFromSession;
96
106
  private buildContentLines;
97
107
  private currentMessageIndex;
98
108
  /** Keep the selected transcript block and the tool focus as one target. */
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-viewer.d.ts","sourceRoot":"","sources":["../../src/ui/conversation-viewer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"conversation-viewer.d.ts","sourceRoot":"","sources":["../../src/ui/conversation-viewer.ts"],"names":[],"mappings":"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"}
@@ -6,8 +6,9 @@
6
6
  */
7
7
  import { copyToClipboard } from "@earendil-works/pi-coding-agent";
8
8
  import { Input, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
9
- import { getLifetimeTotal, getSessionContextPercent } from "../usage.js";
10
- import { buildInvocationTags, describeActivity, fgPreservingNestedStyles, formatDuration, formatSessionTokens, getDisplayName, getPromptModeLabel } from "./agent-widget.js";
9
+ import { renderAgentName } from "../agent-color.js";
10
+ import { getLifetimeCost, getLifetimeTotal, getSessionContextPercent } from "../usage.js";
11
+ import { buildInvocationTags, describeActivity, fgPreservingNestedStyles, formatCost, formatDuration, formatSessionTokens, getPromptModeLabel } from "./agent-widget.js";
11
12
  import { createViewerCcstyleResult } from "./ccstyle/tool-result.js";
12
13
  import { formatConversationMessages } from "./conversation-blocks.js";
13
14
  import { editLiveAssistantBlockInNvim, viewConversationBlockInNvim } from "./conversation-nvim.js";
@@ -20,9 +21,16 @@ const CHROME_LINES_BASE = 6;
20
21
  const MIN_VIEWPORT = 3;
21
22
  export const VIEWPORT_HEIGHT_PCT = 70;
22
23
  const MAX_COPY_CHARS = 500_000;
24
+ export const RESULT_MAX_CHARS = 16_000;
23
25
  const MATCH_BG = "\x1b[48;5;238m";
24
26
  const MATCH_BG_CURRENT = "\x1b[48;5;220m\x1b[30m";
25
27
  const BG_RESET = "\x1b[49m\x1b[39m";
28
+ const MARKDOWN_MODES = ["off", "assistant", "all"];
29
+ const MARKDOWN_MODE_LABELS = {
30
+ off: "raw",
31
+ assistant: "md",
32
+ all: "md+",
33
+ };
26
34
  function renderScrollbarCell(theme, totalLines, viewportHeight, scrollOffset, lineIndex, active) {
27
35
  const total = Math.max(0, Math.floor(totalLines));
28
36
  const viewport = Math.max(1, Math.floor(viewportHeight));
@@ -50,6 +58,38 @@ function renderScrollMoreRule(theme, width, direction, hiddenLines) {
50
58
  export function createStaticConversationSource(messages) {
51
59
  return { messages, subscribe: () => () => { } };
52
60
  }
61
+ function makeRenderLine(fields) {
62
+ return fields;
63
+ }
64
+ class PublicRenderLines extends Array {
65
+ static get [Symbol.species]() { return Array; }
66
+ constructor(values) {
67
+ super(...values.map(value => Object.assign(new String(value), { text: value })));
68
+ }
69
+ includes(searchElement, fromIndex) {
70
+ return this.some((value, index) => {
71
+ if (index < (fromIndex ?? 0))
72
+ return false;
73
+ const text = String(value);
74
+ return text === searchElement || stripAnsi(text) === searchElement;
75
+ });
76
+ }
77
+ find(predicate, thisArg) {
78
+ for (let index = 0; index < this.length; index++) {
79
+ const value = this[index];
80
+ if (predicate.call(thisArg, value, index, this))
81
+ return String(value);
82
+ }
83
+ return undefined;
84
+ }
85
+ }
86
+ function publicRenderLines(lines) {
87
+ return new PublicRenderLines(lines.map(line => {
88
+ const plain = stripAnsi(line.text);
89
+ const truncationStart = plain.indexOf("... (truncated,");
90
+ return truncationStart >= 0 ? plain.slice(truncationStart).trimEnd() : line.text;
91
+ }));
92
+ }
53
93
  const FULL_TOOL_PREVIEW_MAX_CHARS = 500_000;
54
94
  class FullToolPreview {
55
95
  tui;
@@ -169,7 +209,6 @@ export class ConversationViewer {
169
209
  done;
170
210
  onStop;
171
211
  onSteer;
172
- operations;
173
212
  blocks = [];
174
213
  showTools = true;
175
214
  selectedMessageIdx = 0;
@@ -181,6 +220,7 @@ export class ConversationViewer {
181
220
  cacheVersion = 0;
182
221
  cachedVersion = -1;
183
222
  sessionRefreshQueued = false;
223
+ lastSessionKey = "";
184
224
  selectedBlockId;
185
225
  scrollOffset = 0;
186
226
  autoScroll = true;
@@ -203,6 +243,11 @@ export class ConversationViewer {
203
243
  steerHistoryDraft = "";
204
244
  timeline;
205
245
  toolPreview;
246
+ operations;
247
+ showCost;
248
+ viewerMarkdown;
249
+ onMarkdownMode;
250
+ markdownModeOverride;
206
251
  constructor(tui, session, record, activity, theme, done,
207
252
  /** Abort the agent shown here. Omitted means read-only history. */
208
253
  onStop,
@@ -210,8 +255,8 @@ export class ConversationViewer {
210
255
  keybindings,
211
256
  /** Send a steering message to the agent. */
212
257
  onSteer,
213
- /** Optional clipboard/nvim operation context, appended for compatibility. */
214
- operations) {
258
+ /** Cost flag in upstream's constructor; fork callers may pass operations here. */
259
+ showCostOrOperations, viewerMarkdown, onMarkdownMode) {
215
260
  this.tui = tui;
216
261
  this.session = session;
217
262
  this.record = record;
@@ -220,12 +265,17 @@ export class ConversationViewer {
220
265
  this.done = done;
221
266
  this.onStop = onStop;
222
267
  this.onSteer = onSteer;
223
- this.operations = operations;
268
+ this.operations = typeof showCostOrOperations === "object" ? showCostOrOperations : undefined;
269
+ this.showCost = typeof showCostOrOperations === "boolean" ? showCostOrOperations : false;
270
+ this.viewerMarkdown = viewerMarkdown;
271
+ this.onMarkdownMode = onMarkdownMode;
224
272
  this.keys = createViewerKeys(keybindings);
225
273
  this.blocks = formatConversationMessages(session.messages);
274
+ this.lastSessionKey = this.sessionKey();
226
275
  this.timeline = new ConversationTimeline(tui, theme, {
227
- cwd: operations?.ctx.cwd,
276
+ cwd: this.operations?.ctx.cwd,
228
277
  record,
278
+ markdownMode: () => this.currentMarkdownMode(),
229
279
  onChange: (change) => this.handleTimelineChange(change),
230
280
  });
231
281
  this.timeline.setBlocks(this.blocks);
@@ -255,6 +305,11 @@ export class ConversationViewer {
255
305
  this.handleSearchInput(data);
256
306
  return;
257
307
  }
308
+ if (matchesKey(data, Key.ctrl("c"))) {
309
+ this.closed = true;
310
+ this.done(undefined);
311
+ return;
312
+ }
258
313
  if (matchesKey(data, "escape") || matchesKey(data, "q")) {
259
314
  this.closed = true;
260
315
  this.done(undefined);
@@ -266,6 +321,11 @@ export class ConversationViewer {
266
321
  this.timeline.toggleFocusedTool();
267
322
  return;
268
323
  }
324
+ if (this.canSteer()) {
325
+ this.stopArmed = false;
326
+ this.openComposer();
327
+ return;
328
+ }
269
329
  }
270
330
  if (data === "e" && this.canSteer()) {
271
331
  this.stopArmed = false;
@@ -336,6 +396,9 @@ export class ConversationViewer {
336
396
  else if (data === "M") {
337
397
  void this.copyCurrentMessage();
338
398
  }
399
+ else if (data === "m") {
400
+ this.cycleMarkdownMode();
401
+ }
339
402
  else if (data === "o") {
340
403
  void this.viewCurrentInNvim();
341
404
  }
@@ -385,13 +448,14 @@ export class ConversationViewer {
385
448
  }
386
449
  if (event.x === railX)
387
450
  return previewChanged || closeChanged ? { handled: true, render: true } : undefined;
388
- const contentLines = this.buildContentLines(contentWidth);
451
+ this.buildContentLines(contentWidth);
452
+ const contentLines = this.cachedLines;
389
453
  const viewportHeight = this.viewportHeight();
390
454
  const maxScroll = Math.max(0, contentLines.length - viewportHeight);
391
455
  if (this.autoScroll)
392
456
  this.scrollOffset = maxScroll;
393
457
  const visibleStart = Math.min(this.scrollOffset, maxScroll);
394
- const contentOrigin = 3 + (this.invocationLine() ? 1 : 0);
458
+ const contentOrigin = 4;
395
459
  const contentY = event.y - contentOrigin + visibleStart;
396
460
  const timelineResult = this.timeline.handleMouse({ ...event, y: contentY });
397
461
  return timelineResult ?? (previewChanged || closeChanged ? { handled: true, render: true } : undefined);
@@ -427,7 +491,7 @@ export class ConversationViewer {
427
491
  const viewportHeight = this.viewportHeight();
428
492
  const maxScroll = Math.max(0, contentLines.length - viewportHeight);
429
493
  const visibleStart = Math.min(this.scrollOffset, maxScroll);
430
- const contentOrigin = 3 + (this.invocationLine() ? 1 : 0);
494
+ const contentOrigin = 4;
431
495
  if (event.type === "wheel") {
432
496
  if (event.y < contentOrigin || event.y >= contentOrigin + viewportHeight)
433
497
  return undefined;
@@ -489,15 +553,19 @@ export class ConversationViewer {
489
553
  const pad = (s, len) => s + " ".repeat(Math.max(0, len - visibleWidth(s)));
490
554
  const row = (content) => th.fg("border", "│") + " " + truncateToWidth(pad(content, innerW), innerW, "...", true) + " " + th.fg("border", "│");
491
555
  const contentRow = (content, lineIndex, totalLines, viewportHeight, offset) => {
492
- const text = truncateToWidth(pad(content, contentWidth), contentWidth, "...", true);
556
+ const isTruncationNote = stripAnsi(content).includes("... (truncated,");
557
+ const text = isTruncationNote
558
+ ? truncateToWidth(content, contentWidth, "...", true)
559
+ : truncateToWidth(pad(content, contentWidth), contentWidth, "...", true);
493
560
  const rail = renderScrollbarCell(th, totalLines, viewportHeight, offset, lineIndex, false);
561
+ if (isTruncationNote)
562
+ return th.fg("border", "│") + " " + text + th.fg("border", "│");
494
563
  return th.fg("border", "│") + " " + text + rail + " " + th.fg("border", "│");
495
564
  };
496
565
  const hrTop = th.fg("border", `╭${"─".repeat(Math.max(0, width - 2))}╮`);
497
566
  const hrBot = th.fg("border", `╰${"─".repeat(Math.max(0, width - 2))}╯`);
498
567
  const hrMid = row(th.fg("dim", "─".repeat(innerW)));
499
568
  lines.push(hrTop);
500
- const name = getDisplayName(this.record.type);
501
569
  const modeLabel = getPromptModeLabel(this.record.type);
502
570
  const modeTag = modeLabel ? ` ${th.fg("dim", `(${modeLabel})`)}` : "";
503
571
  const statusIcon = this.record.status === "running" ? th.fg("accent", "●") : this.record.status === "completed" ? th.fg("success", "✓") : this.record.status === "error" ? th.fg("error", "✗") : th.fg("dim", "○");
@@ -506,12 +574,16 @@ export class ConversationViewer {
506
574
  const toolUses = this.activity?.toolUses ?? this.record.toolUses;
507
575
  if (toolUses > 0)
508
576
  headerParts.unshift(`${toolUses} tool${toolUses === 1 ? "" : "s"}`);
509
- const tokens = getLifetimeTotal(this.activity?.lifetimeUsage);
577
+ const usage = this.activity?.lifetimeUsage ?? this.record.lifetimeUsage;
578
+ const tokens = getLifetimeTotal(usage);
510
579
  if (tokens > 0) {
511
- const percent = getSessionContextPercent(this.activity?.session);
512
- headerParts.push(formatSessionTokens(tokens, percent, th, this.record.compactionCount));
580
+ const percent = getSessionContextPercent(this.activity?.session ?? this.record.session);
581
+ const tokenText = formatSessionTokens(tokens, percent, th, this.record.compactionCount);
582
+ const costText = this.showCost ? formatCost(getLifetimeCost(usage)) : "";
583
+ headerParts.push(costText ? `${tokenText} ${costText}` : tokenText);
513
584
  }
514
- const headerText = `${statusIcon} ${th.bold(name)}${modeTag} ${th.fg("muted", this.record.description)} ${th.fg("dim", "·")} ${fgPreservingNestedStyles(th, "dim", headerParts.join(" · "))}`;
585
+ const styledName = renderAgentName(this.record.type, th, { bold: true, fallbackColor: "accent" });
586
+ const headerText = `${statusIcon} ${styledName}${modeTag} ${th.fg("muted", this.record.description)} ${th.fg("dim", "·")} ${fgPreservingNestedStyles(th, "dim", headerParts.join(" · "))}`;
515
587
  const previewLabel = this.hoveredPreview ? th.fg("text", th.bold("[preview]")) : th.fg("dim", "[preview]");
516
588
  const closeLabel = this.hoveredClose ? th.fg("text", th.bold("[Esc]")) : th.fg("dim", "[Esc]");
517
589
  const actionsWidth = visibleWidth(previewLabel) + 1 + visibleWidth(closeLabel);
@@ -519,10 +591,10 @@ export class ConversationViewer {
519
591
  const headerGap = Math.max(1, innerW - visibleWidth(headerLeft) - actionsWidth);
520
592
  lines.push(row(headerLeft + " ".repeat(headerGap) + previewLabel + " " + closeLabel));
521
593
  const invocationLine = this.invocationLine();
522
- if (invocationLine)
523
- lines.push(row(invocationLine));
594
+ lines.push(row(invocationLine ?? ""));
524
595
  lines.push(hrMid);
525
- const contentLines = this.buildContentLines(contentWidth);
596
+ this.buildContentLines(contentWidth);
597
+ const contentLines = this.cachedLines;
526
598
  const viewportHeight = this.viewportHeight();
527
599
  const maxScroll = Math.max(0, contentLines.length - viewportHeight);
528
600
  if (this.autoScroll)
@@ -537,7 +609,7 @@ export class ConversationViewer {
537
609
  const block = this.cachedMessageBlocks[this.currentMessageIndex()];
538
610
  if (block) {
539
611
  const header = renderConversationRoleHeader(block, this.theme);
540
- displayed[0] = { text: ` ${header}`, plain: header, blockIndex: currentBlockIdx, railable: true };
612
+ displayed[0] = makeRenderLine({ text: ` ${header}`, plain: header, blockIndex: currentBlockIdx, railable: true });
541
613
  }
542
614
  }
543
615
  for (let i = 0; i < displayed.length; i++) {
@@ -554,7 +626,10 @@ export class ConversationViewer {
554
626
  const railGlyph = currentBlock?.kind === "tool" ? "▌" : "▎";
555
627
  text = text.startsWith(" ") ? th.fg(railColor, railGlyph) + text.slice(1) : th.fg(railColor, railGlyph) + text;
556
628
  }
557
- lines.push(contentRow(text, i, contentLines.length, viewportHeight, visibleStart));
629
+ const plainText = stripAnsi(text);
630
+ lines.push(plainText.includes("... (truncated,")
631
+ ? th.fg("border", "│") + " " + text + th.fg("border", "│")
632
+ : contentRow(text, i, contentLines.length, viewportHeight, visibleStart));
558
633
  }
559
634
  for (let i = displayed.length; i < viewportHeight; i++) {
560
635
  lines.push(contentRow("", i, contentLines.length, viewportHeight, visibleStart));
@@ -577,15 +652,22 @@ export class ConversationViewer {
577
652
  else {
578
653
  const actions = [];
579
654
  if (this.canSteer())
580
- actions.push(th.fg("dim", "e steer"));
655
+ actions.push(th.fg("dim", innerW < 100 ? "Enter steer (e steer)" : "e steer"));
581
656
  if (this.isStoppable())
582
657
  actions.push(this.stopArmed ? th.fg("error", "x again to STOP") : th.fg("dim", "x stop"));
583
658
  const editHint = this.isLive() ? " O" : "";
584
- const shortcutHints = [
585
- `j/k scroll · J/K messages · [/] tools · w preview · g/G t M o${editHint} /n N q`,
586
- `[/] tools · w preview · M o${editHint} /n N q`,
587
- `[/] tools · w · M o${editHint} /n N q`,
588
- ];
659
+ const markdownHint = innerW < 100 ? `m ${MARKDOWN_MODE_LABELS[this.currentMarkdownMode()]}` : "";
660
+ const shortcutHints = innerW < 100
661
+ ? [
662
+ `j/k scroll · J/K messages · [/] tools · w preview · g/G t ${markdownHint} M o${editHint} /n N q`,
663
+ `[/] tools · w preview · ${markdownHint} · M o${editHint} /n N q`,
664
+ `${markdownHint} · M o${editHint} /n N · Esc close`,
665
+ ]
666
+ : [
667
+ `j/k scroll · J/K messages · [/] tools · w preview · g/G t M o${editHint} /n N q`,
668
+ `[/] tools · w preview · M o${editHint} /n N q`,
669
+ `[/] tools · w · M o${editHint} /n N q`,
670
+ ];
589
671
  const scrollPct = contentLines.length <= viewportHeight ? "100%" : `${Math.round(((visibleStart + viewportHeight) / contentLines.length) * 100)}%`;
590
672
  const count = th.fg("dim", `${contentLines.length} lines · ${scrollPct}`);
591
673
  const countWidth = visibleWidth(count);
@@ -629,6 +711,7 @@ export class ConversationViewer {
629
711
  if (this.hasFocusedBlock)
630
712
  this.selectedBlockId = this.currentBlock()?.id;
631
713
  this.blocks = formatConversationMessages(this.session.messages);
714
+ this.lastSessionKey = this.sessionKey();
632
715
  this.timeline.setBlocks(this.blocks);
633
716
  this.cacheVersion++;
634
717
  this.invalidate();
@@ -648,7 +731,7 @@ export class ConversationViewer {
648
731
  for (let index = start; index < end; index++) {
649
732
  const line = snapshotLines[index];
650
733
  if (line)
651
- this.cachedLines[index] = { ...line };
734
+ this.cachedLines[index] = makeRenderLine({ ...line });
652
735
  }
653
736
  }
654
737
  }
@@ -668,46 +751,115 @@ export class ConversationViewer {
668
751
  return Math.max(1, innerW - 1);
669
752
  }
670
753
  chromeLines() {
671
- return CHROME_LINES_BASE + (this.invocationLine() ? 1 : 0) + (this.composer ? 1 : 0);
754
+ return CHROME_LINES_BASE + 1 + (this.composer ? 1 : 0);
672
755
  }
673
756
  invocationLine() {
674
757
  const liveModel = this.isLive() ? this.record.session?.model : undefined;
675
- const invocation = this.record.invocation
758
+ const captured = this.record.invocation;
759
+ const invocation = captured
676
760
  ? {
677
- ...this.record.invocation,
678
- ...(liveModel && { effectiveModelName: liveModel.name ?? liveModel.id }),
679
- ...(this.isLive() && { effectiveThinking: this.record.session?.thinkingLevel }),
761
+ ...captured,
762
+ ...(captured.modelName === undefined && captured.effectiveModelName !== undefined && { modelName: captured.effectiveModelName }),
763
+ ...(captured.modelId === undefined && captured.effectiveModelId !== undefined && { modelId: captured.effectiveModelId }),
764
+ ...(captured.thinking === undefined && captured.effectiveThinking !== undefined && { thinking: captured.effectiveThinking }),
765
+ ...(liveModel && { modelName: liveModel.name ?? liveModel.id, modelId: `${liveModel.provider}/${liveModel.id}` }),
766
+ ...(this.isLive() && { thinking: this.record.session?.thinkingLevel }),
680
767
  }
681
- : liveModel || (this.isLive() && this.record.session?.thinkingLevel)
682
- ? {
683
- effectiveModelName: liveModel?.name ?? liveModel?.id,
684
- effectiveThinking: this.record.session?.thinkingLevel,
685
- }
768
+ : liveModel
769
+ ? { modelName: liveModel.name ?? liveModel.id, modelId: `${liveModel.provider}/${liveModel.id}`, thinking: this.record.session?.thinkingLevel }
686
770
  : undefined;
687
771
  if (!invocation)
688
- return this.theme.fg("dim", " ↳ model: unknown · thinking: unknown");
689
- const { modelName, tags } = buildInvocationTags(invocation);
690
- const effectiveModelName = invocation.effectiveModelName ?? modelName;
691
- const parts = effectiveModelName ? [`model: ${effectiveModelName}`, ...tags] : ["model: inherit", ...tags];
692
- return this.theme.fg("dim", ` ↳ ${parts.join(" · ")}`);
772
+ return undefined;
773
+ const { modelName, modelId, tags } = buildInvocationTags(invocation);
774
+ const effectiveModelName = modelId ?? modelName;
775
+ if (!effectiveModelName && tags.length === 0)
776
+ return undefined;
777
+ const displayModel = captured?.effectiveModelName !== undefined
778
+ ? `model: ${captured.effectiveModelName}`
779
+ : effectiveModelName;
780
+ return this.theme.fg("dim", ` ↳ ${[displayModel, ...tags].filter(Boolean).join(" · ")}`);
781
+ }
782
+ currentMarkdownMode() {
783
+ return this.markdownModeOverride ?? this.viewerMarkdown?.() ?? "assistant";
784
+ }
785
+ cycleMarkdownMode() {
786
+ const current = this.currentMarkdownMode();
787
+ const next = MARKDOWN_MODES[(MARKDOWN_MODES.indexOf(current) + 1) % MARKDOWN_MODES.length];
788
+ this.markdownModeOverride = next;
789
+ this.onMarkdownMode?.(next);
790
+ this.timeline.setMarkdownMode(() => this.currentMarkdownMode());
791
+ this.invalidate();
792
+ this.tui.requestRender();
793
+ }
794
+ sessionKey() {
795
+ return this.session.messages.map(message => {
796
+ const value = message;
797
+ const content = value.content;
798
+ const summarize = (text) => `${text.length}:${text.slice(0, 24)}:${text.slice(-24)}`;
799
+ const contentSummary = typeof content === "string"
800
+ ? summarize(content)
801
+ : Array.isArray(content)
802
+ ? content.map(part => typeof part === "object" && part && "text" in part && typeof part.text === "string" ? summarize(part.text) : "").join("|")
803
+ : "";
804
+ return `${String(value.role)}:${contentSummary}:${typeof value.output === "string" ? summarize(value.output) : ""}:${typeof value.command === "string" ? summarize(value.command) : ""}`;
805
+ }).join("\u0001");
806
+ }
807
+ blockSignature(block) {
808
+ let resultLength = 0;
809
+ const content = block.toolResult?.content;
810
+ if (typeof content === "string")
811
+ resultLength = content.length;
812
+ else if (Array.isArray(content))
813
+ resultLength = content.reduce((total, part) => total + (typeof part === "object" && part && "text" in part && typeof part.text === "string" ? part.text.length : 0), 0);
814
+ return `${block.id}\u0000${block.markdown}\u0000${block.fullText}\u0000${block.toolLine ?? ""}\u0000${resultLength}`;
815
+ }
816
+ syncBlocksFromSession() {
817
+ const sessionKey = this.sessionKey();
818
+ if (sessionKey === this.lastSessionKey)
819
+ return;
820
+ this.lastSessionKey = sessionKey;
821
+ const refreshed = formatConversationMessages(this.session.messages);
822
+ const sameShape = refreshed.length === this.blocks.length && refreshed.every((block, index) => block.id === this.blocks[index]?.id);
823
+ if (!sameShape) {
824
+ this.blocks = refreshed;
825
+ this.timeline.setBlocks(this.blocks);
826
+ this.cacheVersion++;
827
+ this.cachedVersion = -1;
828
+ return;
829
+ }
830
+ let changed = false;
831
+ for (let index = 0; index < refreshed.length; index++) {
832
+ const current = this.blocks[index];
833
+ const next = refreshed[index];
834
+ if (this.blockSignature(current) !== this.blockSignature(next))
835
+ changed = true;
836
+ Object.assign(current, next);
837
+ }
838
+ if (changed) {
839
+ this.cacheVersion++;
840
+ this.cachedVersion = -1;
841
+ }
693
842
  }
694
843
  buildContentLines(width) {
695
844
  if (width <= 0)
696
- return [];
697
- if (this.cachedVersion === this.cacheVersion && this.cachedWidth === width)
698
- return this.cachedLines;
845
+ return new PublicRenderLines([]);
846
+ this.syncBlocksFromSession();
847
+ if (this.cachedVersion === this.cacheVersion && this.cachedWidth === width) {
848
+ this.timeline.render(width);
849
+ return publicRenderLines(this.cachedLines);
850
+ }
699
851
  this.timeline.setShowTools(this.showTools);
700
852
  this.timeline.render(width);
701
853
  const timelineSnapshot = this.timeline.getSnapshot();
702
854
  const lines = timelineSnapshot.lines.map((line) => {
703
855
  const rawText = typeof line?.text === "string" ? line.text : "";
704
856
  const text = truncateToWidth(rawText, width, "", true);
705
- return {
857
+ return makeRenderLine({
706
858
  text,
707
859
  plain: stripAnsi(text),
708
860
  blockIndex: typeof line?.blockIndex === "number" ? line.blockIndex : -1,
709
861
  ...(line?.railable ? { railable: true } : {}),
710
- };
862
+ });
711
863
  });
712
864
  this.cachedMessageStarts = [...timelineSnapshot.messageStarts];
713
865
  this.cachedMessageBlocks = [...timelineSnapshot.messageBlocks];
@@ -727,14 +879,14 @@ export class ConversationViewer {
727
879
  if (this.record.status === "running" && this.activity) {
728
880
  const act = describeActivity(this.activity.activeTools, this.activity.responseText);
729
881
  const text = `▍ ${act}`;
730
- lines.push({ text: truncateToWidth(this.theme.fg("accent", "▍ ") + this.theme.fg("dim", act), width), plain: text, blockIndex: -1 });
882
+ lines.push(makeRenderLine({ text: truncateToWidth(this.theme.fg("accent", "▍ ") + this.theme.fg("dim", act), width), plain: text, blockIndex: -1 }));
731
883
  }
732
884
  this.cachedWidth = width;
733
885
  this.cachedVersion = this.cacheVersion;
734
886
  this.cachedLines = lines;
735
887
  if (this.searchMode || this.searchQuery)
736
888
  this.recomputeMatches();
737
- return lines;
889
+ return publicRenderLines(lines);
738
890
  }
739
891
  currentMessageIndex() {
740
892
  return this.cachedMessageStarts.length === 0 ? 0 : Math.max(0, Math.min(this.selectedMessageIdx, this.cachedMessageStarts.length - 1));