@esso0428/pi-subagents 0.17.15 → 0.17.17

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 (359) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/ROADMAP.md +87 -0
  3. package/docs/post-0.17.6-agents-roster-viewer-spec.md +42 -0
  4. package/docs/post-0.17.6-durable-history-spec.md +41 -0
  5. package/docs/post-0.17.6-feature-specs.md +50 -0
  6. package/docs/post-0.17.6-nested-agents-spec.md +38 -0
  7. package/docs/post-0.17.6-recovery-shutdown-spec.md +46 -0
  8. package/docs/post-0.17.6-ui-latency-baseline-spec.md +46 -0
  9. package/docs/post-0.17.6-workflow-rpc-lifecycle-spec.md +54 -0
  10. package/package.json +2 -3
  11. package/src/agent-history.ts +2 -54
  12. package/src/agent-manager.ts +406 -1288
  13. package/src/agent-runner.ts +27 -251
  14. package/src/agent-types.ts +32 -188
  15. package/src/cross-extension-rpc.ts +20 -96
  16. package/src/custom-agents.ts +13 -170
  17. package/src/index.ts +480 -1920
  18. package/src/invocation-config.ts +3 -118
  19. package/src/model-resolver.ts +0 -18
  20. package/src/output-file.ts +6 -61
  21. package/src/prompts.ts +2 -45
  22. package/src/schedule.ts +14 -35
  23. package/src/settings.ts +2 -301
  24. package/src/status-note.ts +1 -66
  25. package/src/types.ts +10 -177
  26. package/src/ui/agent-widget.ts +57 -278
  27. package/src/ui/conversation-blocks.ts +0 -6
  28. package/src/ui/conversation-timeline.ts +25 -139
  29. package/src/ui/conversation-viewer.ts +48 -212
  30. package/src/ui/schedule-menu.ts +8 -9
  31. package/src/usage.ts +2 -109
  32. package/src/worktree.ts +55 -69
  33. package/dist/abortable.d.ts +0 -13
  34. package/dist/abortable.d.ts.map +0 -1
  35. package/dist/abortable.js +0 -43
  36. package/dist/abortable.js.map +0 -1
  37. package/dist/agent-color.d.ts +0 -36
  38. package/dist/agent-color.d.ts.map +0 -1
  39. package/dist/agent-color.js +0 -124
  40. package/dist/agent-color.js.map +0 -1
  41. package/dist/agent-file-toggle.d.ts +0 -126
  42. package/dist/agent-file-toggle.d.ts.map +0 -1
  43. package/dist/agent-file-toggle.js +0 -259
  44. package/dist/agent-file-toggle.js.map +0 -1
  45. package/dist/agent-history-list.d.ts +0 -19
  46. package/dist/agent-history-list.d.ts.map +0 -1
  47. package/dist/agent-history-list.js +0 -69
  48. package/dist/agent-history-list.js.map +0 -1
  49. package/dist/agent-history.d.ts +0 -35
  50. package/dist/agent-history.d.ts.map +0 -1
  51. package/dist/agent-history.js +0 -188
  52. package/dist/agent-history.js.map +0 -1
  53. package/dist/agent-manager.d.ts +0 -503
  54. package/dist/agent-manager.d.ts.map +0 -1
  55. package/dist/agent-manager.js +0 -1572
  56. package/dist/agent-manager.js.map +0 -1
  57. package/dist/agent-recovery.d.ts +0 -37
  58. package/dist/agent-recovery.d.ts.map +0 -1
  59. package/dist/agent-recovery.js +0 -171
  60. package/dist/agent-recovery.js.map +0 -1
  61. package/dist/agent-runner.d.ts +0 -303
  62. package/dist/agent-runner.d.ts.map +0 -1
  63. package/dist/agent-runner.js +0 -1003
  64. package/dist/agent-runner.js.map +0 -1
  65. package/dist/agent-types.d.ts +0 -120
  66. package/dist/agent-types.d.ts.map +0 -1
  67. package/dist/agent-types.js +0 -301
  68. package/dist/agent-types.js.map +0 -1
  69. package/dist/child-context.d.ts +0 -3
  70. package/dist/child-context.d.ts.map +0 -1
  71. package/dist/child-context.js +0 -13
  72. package/dist/child-context.js.map +0 -1
  73. package/dist/context.d.ts +0 -13
  74. package/dist/context.d.ts.map +0 -1
  75. package/dist/context.js +0 -57
  76. package/dist/context.js.map +0 -1
  77. package/dist/cross-extension-rpc.d.ts +0 -67
  78. package/dist/cross-extension-rpc.d.ts.map +0 -1
  79. package/dist/cross-extension-rpc.js +0 -139
  80. package/dist/cross-extension-rpc.js.map +0 -1
  81. package/dist/custom-agents.d.ts +0 -55
  82. package/dist/custom-agents.d.ts.map +0 -1
  83. package/dist/custom-agents.js +0 -309
  84. package/dist/custom-agents.js.map +0 -1
  85. package/dist/default-agents.d.ts +0 -8
  86. package/dist/default-agents.d.ts.map +0 -1
  87. package/dist/default-agents.js +0 -123
  88. package/dist/default-agents.js.map +0 -1
  89. package/dist/enabled-models.d.ts +0 -50
  90. package/dist/enabled-models.d.ts.map +0 -1
  91. package/dist/enabled-models.js +0 -146
  92. package/dist/enabled-models.js.map +0 -1
  93. package/dist/env.d.ts +0 -7
  94. package/dist/env.d.ts.map +0 -1
  95. package/dist/env.js +0 -29
  96. package/dist/env.js.map +0 -1
  97. package/dist/group-join.d.ts +0 -33
  98. package/dist/group-join.d.ts.map +0 -1
  99. package/dist/group-join.js +0 -117
  100. package/dist/group-join.js.map +0 -1
  101. package/dist/index.d.ts +0 -51
  102. package/dist/index.d.ts.map +0 -1
  103. package/dist/index.js +0 -3687
  104. package/dist/index.js.map +0 -1
  105. package/dist/invocation-config.d.ts +0 -108
  106. package/dist/invocation-config.d.ts.map +0 -1
  107. package/dist/invocation-config.js +0 -84
  108. package/dist/invocation-config.js.map +0 -1
  109. package/dist/memory.d.ts +0 -54
  110. package/dist/memory.d.ts.map +0 -1
  111. package/dist/memory.js +0 -166
  112. package/dist/memory.js.map +0 -1
  113. package/dist/mention-clone.d.ts +0 -88
  114. package/dist/mention-clone.d.ts.map +0 -1
  115. package/dist/mention-clone.js +0 -154
  116. package/dist/mention-clone.js.map +0 -1
  117. package/dist/mention.d.ts +0 -82
  118. package/dist/mention.d.ts.map +0 -1
  119. package/dist/mention.js +0 -132
  120. package/dist/mention.js.map +0 -1
  121. package/dist/model-resolver.d.ts +0 -37
  122. package/dist/model-resolver.d.ts.map +0 -1
  123. package/dist/model-resolver.js +0 -96
  124. package/dist/model-resolver.js.map +0 -1
  125. package/dist/model-scope.d.ts +0 -50
  126. package/dist/model-scope.d.ts.map +0 -1
  127. package/dist/model-scope.js +0 -49
  128. package/dist/model-scope.js.map +0 -1
  129. package/dist/nested-tools.d.ts +0 -57
  130. package/dist/nested-tools.d.ts.map +0 -1
  131. package/dist/nested-tools.js +0 -301
  132. package/dist/nested-tools.js.map +0 -1
  133. package/dist/nico-overrides.d.ts +0 -54
  134. package/dist/nico-overrides.d.ts.map +0 -1
  135. package/dist/nico-overrides.js +0 -170
  136. package/dist/nico-overrides.js.map +0 -1
  137. package/dist/output-file.d.ts +0 -44
  138. package/dist/output-file.d.ts.map +0 -1
  139. package/dist/output-file.js +0 -156
  140. package/dist/output-file.js.map +0 -1
  141. package/dist/prompts.d.ts +0 -56
  142. package/dist/prompts.d.ts.map +0 -1
  143. package/dist/prompts.js +0 -92
  144. package/dist/prompts.js.map +0 -1
  145. package/dist/schedule-store.d.ts +0 -39
  146. package/dist/schedule-store.d.ts.map +0 -1
  147. package/dist/schedule-store.js +0 -156
  148. package/dist/schedule-store.js.map +0 -1
  149. package/dist/schedule.d.ts +0 -110
  150. package/dist/schedule.d.ts.map +0 -1
  151. package/dist/schedule.js +0 -360
  152. package/dist/schedule.js.map +0 -1
  153. package/dist/settings.d.ts +0 -354
  154. package/dist/settings.d.ts.map +0 -1
  155. package/dist/settings.js +0 -247
  156. package/dist/settings.js.map +0 -1
  157. package/dist/skill-loader.d.ts +0 -25
  158. package/dist/skill-loader.d.ts.map +0 -1
  159. package/dist/skill-loader.js +0 -94
  160. package/dist/skill-loader.js.map +0 -1
  161. package/dist/status-note.d.ts +0 -62
  162. package/dist/status-note.d.ts.map +0 -1
  163. package/dist/status-note.js +0 -86
  164. package/dist/status-note.js.map +0 -1
  165. package/dist/structured-output.d.ts +0 -62
  166. package/dist/structured-output.d.ts.map +0 -1
  167. package/dist/structured-output.js +0 -113
  168. package/dist/structured-output.js.map +0 -1
  169. package/dist/types.d.ts +0 -372
  170. package/dist/types.d.ts.map +0 -1
  171. package/dist/types.js +0 -6
  172. package/dist/types.js.map +0 -1
  173. package/dist/ui/agent-mention.d.ts +0 -83
  174. package/dist/ui/agent-mention.d.ts.map +0 -1
  175. package/dist/ui/agent-mention.js +0 -188
  176. package/dist/ui/agent-mention.js.map +0 -1
  177. package/dist/ui/agent-widget.d.ts +0 -241
  178. package/dist/ui/agent-widget.d.ts.map +0 -1
  179. package/dist/ui/agent-widget.js +0 -992
  180. package/dist/ui/agent-widget.js.map +0 -1
  181. package/dist/ui/ccstyle/diff/ansi-utils.d.ts +0 -11
  182. package/dist/ui/ccstyle/diff/ansi-utils.d.ts.map +0 -1
  183. package/dist/ui/ccstyle/diff/ansi-utils.js +0 -145
  184. package/dist/ui/ccstyle/diff/ansi-utils.js.map +0 -1
  185. package/dist/ui/ccstyle/diff/diff-presentation.d.ts +0 -12
  186. package/dist/ui/ccstyle/diff/diff-presentation.d.ts.map +0 -1
  187. package/dist/ui/ccstyle/diff/diff-presentation.js +0 -48
  188. package/dist/ui/ccstyle/diff/diff-presentation.js.map +0 -1
  189. package/dist/ui/ccstyle/diff/diff-renderer.d.ts +0 -46
  190. package/dist/ui/ccstyle/diff/diff-renderer.d.ts.map +0 -1
  191. package/dist/ui/ccstyle/diff/diff-renderer.js +0 -2049
  192. package/dist/ui/ccstyle/diff/diff-renderer.js.map +0 -1
  193. package/dist/ui/ccstyle/diff/line-width-safety.d.ts +0 -12
  194. package/dist/ui/ccstyle/diff/line-width-safety.d.ts.map +0 -1
  195. package/dist/ui/ccstyle/diff/line-width-safety.js +0 -58
  196. package/dist/ui/ccstyle/diff/line-width-safety.js.map +0 -1
  197. package/dist/ui/ccstyle/diff/render-utils.d.ts +0 -6
  198. package/dist/ui/ccstyle/diff/render-utils.d.ts.map +0 -1
  199. package/dist/ui/ccstyle/diff/render-utils.js +0 -25
  200. package/dist/ui/ccstyle/diff/render-utils.js.map +0 -1
  201. package/dist/ui/ccstyle/diff/shiki-highlight.d.ts +0 -19
  202. package/dist/ui/ccstyle/diff/shiki-highlight.d.ts.map +0 -1
  203. package/dist/ui/ccstyle/diff/shiki-highlight.js +0 -85
  204. package/dist/ui/ccstyle/diff/shiki-highlight.js.map +0 -1
  205. package/dist/ui/ccstyle/diff/types.d.ts +0 -22
  206. package/dist/ui/ccstyle/diff/types.d.ts.map +0 -1
  207. package/dist/ui/ccstyle/diff/types.js +0 -10
  208. package/dist/ui/ccstyle/diff/types.js.map +0 -1
  209. package/dist/ui/ccstyle/diff/write-display-utils.d.ts +0 -2
  210. package/dist/ui/ccstyle/diff/write-display-utils.d.ts.map +0 -1
  211. package/dist/ui/ccstyle/diff/write-display-utils.js +0 -12
  212. package/dist/ui/ccstyle/diff/write-display-utils.js.map +0 -1
  213. package/dist/ui/ccstyle/tool-renderer.d.ts +0 -17
  214. package/dist/ui/ccstyle/tool-renderer.d.ts.map +0 -1
  215. package/dist/ui/ccstyle/tool-renderer.js +0 -74
  216. package/dist/ui/ccstyle/tool-renderer.js.map +0 -1
  217. package/dist/ui/ccstyle/tool-result.d.ts +0 -44
  218. package/dist/ui/ccstyle/tool-result.d.ts.map +0 -1
  219. package/dist/ui/ccstyle/tool-result.js +0 -423
  220. package/dist/ui/ccstyle/tool-result.js.map +0 -1
  221. package/dist/ui/conversation-blocks.d.ts +0 -46
  222. package/dist/ui/conversation-blocks.d.ts.map +0 -1
  223. package/dist/ui/conversation-blocks.js +0 -313
  224. package/dist/ui/conversation-blocks.js.map +0 -1
  225. package/dist/ui/conversation-nvim.d.ts +0 -8
  226. package/dist/ui/conversation-nvim.d.ts.map +0 -1
  227. package/dist/ui/conversation-nvim.js +0 -117
  228. package/dist/ui/conversation-nvim.js.map +0 -1
  229. package/dist/ui/conversation-role.d.ts +0 -13
  230. package/dist/ui/conversation-role.d.ts.map +0 -1
  231. package/dist/ui/conversation-role.js +0 -39
  232. package/dist/ui/conversation-role.js.map +0 -1
  233. package/dist/ui/conversation-search.d.ts +0 -39
  234. package/dist/ui/conversation-search.d.ts.map +0 -1
  235. package/dist/ui/conversation-search.js +0 -124
  236. package/dist/ui/conversation-search.js.map +0 -1
  237. package/dist/ui/conversation-timeline.d.ts +0 -102
  238. package/dist/ui/conversation-timeline.d.ts.map +0 -1
  239. package/dist/ui/conversation-timeline.js +0 -555
  240. package/dist/ui/conversation-timeline.js.map +0 -1
  241. package/dist/ui/conversation-viewer.d.ts +0 -138
  242. package/dist/ui/conversation-viewer.d.ts.map +0 -1
  243. package/dist/ui/conversation-viewer.js +0 -1175
  244. package/dist/ui/conversation-viewer.js.map +0 -1
  245. package/dist/ui/schedule-menu.d.ts +0 -17
  246. package/dist/ui/schedule-menu.d.ts.map +0 -1
  247. package/dist/ui/schedule-menu.js +0 -95
  248. package/dist/ui/schedule-menu.js.map +0 -1
  249. package/dist/ui/select-item.d.ts +0 -28
  250. package/dist/ui/select-item.d.ts.map +0 -1
  251. package/dist/ui/select-item.js +0 -35
  252. package/dist/ui/select-item.js.map +0 -1
  253. package/dist/ui/viewer-keys.d.ts +0 -21
  254. package/dist/ui/viewer-keys.d.ts.map +0 -1
  255. package/dist/ui/viewer-keys.js +0 -18
  256. package/dist/ui/viewer-keys.js.map +0 -1
  257. package/dist/ui/workflow-card.d.ts +0 -176
  258. package/dist/ui/workflow-card.d.ts.map +0 -1
  259. package/dist/ui/workflow-card.js +0 -333
  260. package/dist/ui/workflow-card.js.map +0 -1
  261. package/dist/ui/workflow-dialog.d.ts +0 -306
  262. package/dist/ui/workflow-dialog.d.ts.map +0 -1
  263. package/dist/ui/workflow-dialog.js +0 -844
  264. package/dist/ui/workflow-dialog.js.map +0 -1
  265. package/dist/ui/workflow-menu.d.ts +0 -42
  266. package/dist/ui/workflow-menu.d.ts.map +0 -1
  267. package/dist/ui/workflow-menu.js +0 -127
  268. package/dist/ui/workflow-menu.js.map +0 -1
  269. package/dist/usage.d.ts +0 -136
  270. package/dist/usage.d.ts.map +0 -1
  271. package/dist/usage.js +0 -121
  272. package/dist/usage.js.map +0 -1
  273. package/dist/workflow/collisions.d.ts +0 -96
  274. package/dist/workflow/collisions.d.ts.map +0 -1
  275. package/dist/workflow/collisions.js +0 -89
  276. package/dist/workflow/collisions.js.map +0 -1
  277. package/dist/workflow/entry.d.ts +0 -33
  278. package/dist/workflow/entry.d.ts.map +0 -1
  279. package/dist/workflow/entry.js +0 -30
  280. package/dist/workflow/entry.js.map +0 -1
  281. package/dist/workflow/host.d.ts +0 -63
  282. package/dist/workflow/host.d.ts.map +0 -1
  283. package/dist/workflow/host.js +0 -363
  284. package/dist/workflow/host.js.map +0 -1
  285. package/dist/workflow/journal.d.ts +0 -98
  286. package/dist/workflow/journal.d.ts.map +0 -1
  287. package/dist/workflow/journal.js +0 -121
  288. package/dist/workflow/journal.js.map +0 -1
  289. package/dist/workflow/json-schema.d.ts +0 -52
  290. package/dist/workflow/json-schema.d.ts.map +0 -1
  291. package/dist/workflow/json-schema.js +0 -112
  292. package/dist/workflow/json-schema.js.map +0 -1
  293. package/dist/workflow/meta.d.ts +0 -68
  294. package/dist/workflow/meta.d.ts.map +0 -1
  295. package/dist/workflow/meta.js +0 -318
  296. package/dist/workflow/meta.js.map +0 -1
  297. package/dist/workflow/progress.d.ts +0 -225
  298. package/dist/workflow/progress.d.ts.map +0 -1
  299. package/dist/workflow/progress.js +0 -362
  300. package/dist/workflow/progress.js.map +0 -1
  301. package/dist/workflow/runtime.d.ts +0 -335
  302. package/dist/workflow/runtime.d.ts.map +0 -1
  303. package/dist/workflow/runtime.js +0 -831
  304. package/dist/workflow/runtime.js.map +0 -1
  305. package/dist/workflow/saved.d.ts +0 -91
  306. package/dist/workflow/saved.d.ts.map +0 -1
  307. package/dist/workflow/saved.js +0 -204
  308. package/dist/workflow/saved.js.map +0 -1
  309. package/dist/workflow/task.d.ts +0 -137
  310. package/dist/workflow/task.d.ts.map +0 -1
  311. package/dist/workflow/task.js +0 -208
  312. package/dist/workflow/task.js.map +0 -1
  313. package/dist/workflow/tool-description.d.ts +0 -39
  314. package/dist/workflow/tool-description.d.ts.map +0 -1
  315. package/dist/workflow/tool-description.js +0 -200
  316. package/dist/workflow/tool-description.js.map +0 -1
  317. package/dist/workflow/worker-source.d.ts +0 -48
  318. package/dist/workflow/worker-source.d.ts.map +0 -1
  319. package/dist/workflow/worker-source.js +0 -779
  320. package/dist/workflow/worker-source.js.map +0 -1
  321. package/dist/worktree.d.ts +0 -53
  322. package/dist/worktree.d.ts.map +0 -1
  323. package/dist/worktree.js +0 -165
  324. package/dist/worktree.js.map +0 -1
  325. package/dist/write-execution.d.ts +0 -42
  326. package/dist/write-execution.d.ts.map +0 -1
  327. package/dist/write-execution.js +0 -138
  328. package/dist/write-execution.js.map +0 -1
  329. package/dist/xml.d.ts +0 -11
  330. package/dist/xml.d.ts.map +0 -1
  331. package/dist/xml.js +0 -13
  332. package/dist/xml.js.map +0 -1
  333. package/src/abortable.ts +0 -43
  334. package/src/agent-color.ts +0 -161
  335. package/src/agent-file-toggle.ts +0 -269
  336. package/src/child-context.ts +0 -15
  337. package/src/mention-clone.ts +0 -196
  338. package/src/mention.ts +0 -141
  339. package/src/model-scope.ts +0 -70
  340. package/src/nested-tools.ts +0 -424
  341. package/src/structured-output.ts +0 -130
  342. package/src/ui/agent-mention.ts +0 -216
  343. package/src/ui/select-item.ts +0 -45
  344. package/src/ui/workflow-card.ts +0 -470
  345. package/src/ui/workflow-dialog.ts +0 -1115
  346. package/src/ui/workflow-menu.ts +0 -166
  347. package/src/workflow/collisions.ts +0 -123
  348. package/src/workflow/entry.ts +0 -47
  349. package/src/workflow/host.ts +0 -403
  350. package/src/workflow/journal.ts +0 -164
  351. package/src/workflow/json-schema.ts +0 -128
  352. package/src/workflow/meta.ts +0 -325
  353. package/src/workflow/progress.ts +0 -550
  354. package/src/workflow/runtime.ts +0 -1219
  355. package/src/workflow/saved.ts +0 -217
  356. package/src/workflow/task.ts +0 -302
  357. package/src/workflow/tool-description.ts +0 -200
  358. package/src/workflow/worker-source.ts +0 -781
  359. package/src/xml.ts +0 -13
@@ -8,11 +8,10 @@
8
8
  import type { AgentSession, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
9
9
  import { copyToClipboard } from "@earendil-works/pi-coding-agent";
10
10
  import { type Component, Input, Key, matchesKey, type TUI, type TuiMouseEvent, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
11
- import { renderAgentName } from "../agent-color.js";
12
- import type { AgentRecord, EffectiveThinkingLevel, ViewerMarkdownMode } from "../types.js";
13
- import { getLifetimeCost, getLifetimeTotal, getSessionContextPercent } from "../usage.js";
11
+ import type { AgentRecord } from "../types.js";
12
+ import { getLifetimeTotal, getSessionContextPercent } from "../usage.js";
14
13
  import type { Theme } from "./agent-widget.js";
15
- import { type AgentActivity, buildInvocationTags, describeActivity, fgPreservingNestedStyles, formatCost, formatDuration, formatSessionTokens, getPromptModeLabel } from "./agent-widget.js";
14
+ import { type AgentActivity, buildInvocationTags, describeActivity, fgPreservingNestedStyles, formatDuration, formatSessionTokens, getDisplayName, getPromptModeLabel } from "./agent-widget.js";
16
15
  import { createViewerCcstyleResult } from "./ccstyle/tool-result.js";
17
16
  import { type ConversationBlock, formatConversationMessages } from "./conversation-blocks.js";
18
17
  import { editLiveAssistantBlockInNvim, viewConversationBlockInNvim } from "./conversation-nvim.js";
@@ -26,16 +25,9 @@ const CHROME_LINES_BASE = 6;
26
25
  const MIN_VIEWPORT = 3;
27
26
  export const VIEWPORT_HEIGHT_PCT = 70;
28
27
  const MAX_COPY_CHARS = 500_000;
29
- export const RESULT_MAX_CHARS = 16_000;
30
28
  const MATCH_BG = "\x1b[48;5;238m";
31
29
  const MATCH_BG_CURRENT = "\x1b[48;5;220m\x1b[30m";
32
30
  const BG_RESET = "\x1b[49m\x1b[39m";
33
- const MARKDOWN_MODES: readonly ViewerMarkdownMode[] = ["off", "assistant", "all"];
34
- const MARKDOWN_MODE_LABELS: Record<ViewerMarkdownMode, string> = {
35
- off: "raw",
36
- assistant: "md",
37
- all: "md+",
38
- };
39
31
 
40
32
  function renderScrollbarCell(
41
33
  theme: Theme,
@@ -86,42 +78,6 @@ export function createStaticConversationSource(
86
78
 
87
79
  type RenderLine = TimelineRenderLine;
88
80
 
89
- function makeRenderLine(fields: TimelineRenderLine): RenderLine {
90
- return fields;
91
- }
92
-
93
- class PublicRenderLines extends Array<string> {
94
- static get [Symbol.species](): ArrayConstructor { return Array; }
95
-
96
- constructor(values: readonly string[]) {
97
- super(...values.map(value => Object.assign(new String(value), { text: value }) as unknown as string));
98
- }
99
-
100
- override includes(searchElement: string, fromIndex?: number): boolean {
101
- return this.some((value, index) => {
102
- if (index < (fromIndex ?? 0)) return false;
103
- const text = String(value);
104
- return text === searchElement || stripAnsi(text) === searchElement;
105
- });
106
- }
107
-
108
- find(predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: unknown): string | undefined {
109
- for (let index = 0; index < this.length; index++) {
110
- const value = this[index]!;
111
- if (predicate.call(thisArg, value, index, this)) return String(value);
112
- }
113
- return undefined;
114
- }
115
- }
116
-
117
- function publicRenderLines(lines: readonly RenderLine[]): PublicRenderLines {
118
- return new PublicRenderLines(lines.map(line => {
119
- const plain = stripAnsi(line.text);
120
- const truncationStart = plain.indexOf("... (truncated,");
121
- return truncationStart >= 0 ? plain.slice(truncationStart).trimEnd() : line.text;
122
- }));
123
- }
124
-
125
81
  const FULL_TOOL_PREVIEW_MAX_CHARS = 500_000;
126
82
 
127
83
  class FullToolPreview implements Component {
@@ -256,7 +212,6 @@ export class ConversationViewer implements Component {
256
212
  private cacheVersion = 0;
257
213
  private cachedVersion = -1;
258
214
  private sessionRefreshQueued = false;
259
- private lastSessionKey = "";
260
215
  private selectedBlockId: string | undefined;
261
216
  private scrollOffset = 0;
262
217
  private autoScroll = true;
@@ -279,11 +234,6 @@ export class ConversationViewer implements Component {
279
234
  private steerHistoryDraft = "";
280
235
  private timeline: ConversationTimeline;
281
236
  private toolPreview: FullToolPreview | undefined;
282
- private readonly operations: ConversationViewerOperations | undefined;
283
- private readonly showCost: boolean;
284
- private readonly viewerMarkdown: (() => ViewerMarkdownMode) | undefined;
285
- private readonly onMarkdownMode: ((mode: ViewerMarkdownMode) => void) | undefined;
286
- private markdownModeOverride: ViewerMarkdownMode | undefined;
287
237
 
288
238
  constructor(
289
239
  private tui: TUI,
@@ -298,22 +248,14 @@ export class ConversationViewer implements Component {
298
248
  keybindings?: ViewerKeybindings,
299
249
  /** Send a steering message to the agent. */
300
250
  private onSteer?: (message: string) => void,
301
- /** Cost flag in upstream's constructor; fork callers may pass operations here. */
302
- showCostOrOperations?: boolean | ConversationViewerOperations,
303
- viewerMarkdown?: () => ViewerMarkdownMode,
304
- onMarkdownMode?: (mode: ViewerMarkdownMode) => void,
251
+ /** Optional clipboard/nvim operation context, appended for compatibility. */
252
+ private operations?: ConversationViewerOperations,
305
253
  ) {
306
- this.operations = typeof showCostOrOperations === "object" ? showCostOrOperations : undefined;
307
- this.showCost = typeof showCostOrOperations === "boolean" ? showCostOrOperations : false;
308
- this.viewerMarkdown = viewerMarkdown;
309
- this.onMarkdownMode = onMarkdownMode;
310
254
  this.keys = createViewerKeys(keybindings);
311
255
  this.blocks = formatConversationMessages(session.messages);
312
- this.lastSessionKey = this.sessionKey();
313
256
  this.timeline = new ConversationTimeline(tui, theme, {
314
- cwd: this.operations?.ctx.cwd,
257
+ cwd: operations?.ctx.cwd,
315
258
  record,
316
- markdownMode: () => this.currentMarkdownMode(),
317
259
  onChange: (change) => this.handleTimelineChange(change),
318
260
  });
319
261
  this.timeline.setBlocks(this.blocks);
@@ -343,11 +285,6 @@ export class ConversationViewer implements Component {
343
285
  this.handleSearchInput(data);
344
286
  return;
345
287
  }
346
- if (matchesKey(data, Key.ctrl("c"))) {
347
- this.closed = true;
348
- this.done(undefined);
349
- return;
350
- }
351
288
  if (matchesKey(data, "escape") || matchesKey(data, "q")) {
352
289
  this.closed = true;
353
290
  this.done(undefined);
@@ -359,11 +296,6 @@ export class ConversationViewer implements Component {
359
296
  this.timeline.toggleFocusedTool();
360
297
  return;
361
298
  }
362
- if (this.canSteer()) {
363
- this.stopArmed = false;
364
- this.openComposer();
365
- return;
366
- }
367
299
  }
368
300
  if (data === "e" && this.canSteer()) {
369
301
  this.stopArmed = false;
@@ -420,8 +352,6 @@ export class ConversationViewer implements Component {
420
352
  this.gotoMatch(-1);
421
353
  } else if (data === "M") {
422
354
  void this.copyCurrentMessage();
423
- } else if (data === "m") {
424
- this.cycleMarkdownMode();
425
355
  } else if (data === "o") {
426
356
  void this.viewCurrentInNvim();
427
357
  } else if (data === "O" && this.isLive()) {
@@ -464,13 +394,12 @@ export class ConversationViewer implements Component {
464
394
  return previewChanged || closeChanged ? { handled: true, render: true } : undefined;
465
395
  }
466
396
  if (event.x === railX) return previewChanged || closeChanged ? { handled: true, render: true } : undefined;
467
- this.buildContentLines(contentWidth);
468
- const contentLines = this.cachedLines;
397
+ const contentLines = this.buildContentLines(contentWidth);
469
398
  const viewportHeight = this.viewportHeight();
470
399
  const maxScroll = Math.max(0, contentLines.length - viewportHeight);
471
400
  if (this.autoScroll) this.scrollOffset = maxScroll;
472
401
  const visibleStart = Math.min(this.scrollOffset, maxScroll);
473
- const contentOrigin = 4;
402
+ const contentOrigin = 3 + (this.invocationLine() ? 1 : 0);
474
403
  const contentY = event.y - contentOrigin + visibleStart;
475
404
  const timelineResult = this.timeline.handleMouse({ ...event, y: contentY });
476
405
  return timelineResult ?? (previewChanged || closeChanged ? { handled: true, render: true } : undefined);
@@ -505,7 +434,7 @@ export class ConversationViewer implements Component {
505
434
  const viewportHeight = this.viewportHeight();
506
435
  const maxScroll = Math.max(0, contentLines.length - viewportHeight);
507
436
  const visibleStart = Math.min(this.scrollOffset, maxScroll);
508
- const contentOrigin = 4;
437
+ const contentOrigin = 3 + (this.invocationLine() ? 1 : 0);
509
438
  if (event.type === "wheel") {
510
439
  if (event.y < contentOrigin || event.y >= contentOrigin + viewportHeight) return undefined;
511
440
  this.scrollBy(Math.trunc(event.wheelDelta ?? 0));
@@ -562,12 +491,8 @@ export class ConversationViewer implements Component {
562
491
  const pad = (s: string, len: number) => s + " ".repeat(Math.max(0, len - visibleWidth(s)));
563
492
  const row = (content: string) => th.fg("border", "│") + " " + truncateToWidth(pad(content, innerW), innerW, "...", true) + " " + th.fg("border", "│");
564
493
  const contentRow = (content: string, lineIndex: number, totalLines: number, viewportHeight: number, offset: number) => {
565
- const isTruncationNote = stripAnsi(content).includes("... (truncated,");
566
- const text = isTruncationNote
567
- ? truncateToWidth(content, contentWidth, "...", true)
568
- : truncateToWidth(pad(content, contentWidth), contentWidth, "...", true);
494
+ const text = truncateToWidth(pad(content, contentWidth), contentWidth, "...", true);
569
495
  const rail = renderScrollbarCell(th, totalLines, viewportHeight, offset, lineIndex, false);
570
- if (isTruncationNote) return th.fg("border", "│") + " " + text + th.fg("border", "│");
571
496
  return th.fg("border", "│") + " " + text + rail + " " + th.fg("border", "│");
572
497
  };
573
498
  const hrTop = th.fg("border", `╭${"─".repeat(Math.max(0, width - 2))}╮`);
@@ -575,6 +500,7 @@ export class ConversationViewer implements Component {
575
500
  const hrMid = row(th.fg("dim", "─".repeat(innerW)));
576
501
 
577
502
  lines.push(hrTop);
503
+ const name = getDisplayName(this.record.type);
578
504
  const modeLabel = getPromptModeLabel(this.record.type);
579
505
  const modeTag = modeLabel ? ` ${th.fg("dim", `(${modeLabel})`)}` : "";
580
506
  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", "○");
@@ -582,16 +508,12 @@ export class ConversationViewer implements Component {
582
508
  const headerParts: string[] = [duration];
583
509
  const toolUses = this.activity?.toolUses ?? this.record.toolUses;
584
510
  if (toolUses > 0) headerParts.unshift(`${toolUses} tool${toolUses === 1 ? "" : "s"}`);
585
- const usage = this.activity?.lifetimeUsage ?? this.record.lifetimeUsage;
586
- const tokens = getLifetimeTotal(usage);
511
+ const tokens = getLifetimeTotal(this.activity?.lifetimeUsage);
587
512
  if (tokens > 0) {
588
- const percent = getSessionContextPercent(this.activity?.session ?? this.record.session);
589
- const tokenText = formatSessionTokens(tokens, percent, th, this.record.compactionCount);
590
- const costText = this.showCost ? formatCost(getLifetimeCost(usage)) : "";
591
- headerParts.push(costText ? `${tokenText} ${costText}` : tokenText);
513
+ const percent = getSessionContextPercent(this.activity?.session);
514
+ headerParts.push(formatSessionTokens(tokens, percent, th, this.record.compactionCount));
592
515
  }
593
- const styledName = renderAgentName(this.record.type, th, { bold: true, fallbackColor: "accent" });
594
- const headerText = `${statusIcon} ${styledName}${modeTag} ${th.fg("muted", this.record.description)} ${th.fg("dim", "·")} ${fgPreservingNestedStyles(th, "dim", headerParts.join(" · "))}`;
516
+ const headerText = `${statusIcon} ${th.bold(name)}${modeTag} ${th.fg("muted", this.record.description)} ${th.fg("dim", "·")} ${fgPreservingNestedStyles(th, "dim", headerParts.join(" · "))}`;
595
517
  const previewLabel = this.hoveredPreview ? th.fg("text", th.bold("[preview]")) : th.fg("dim", "[preview]");
596
518
  const closeLabel = this.hoveredClose ? th.fg("text", th.bold("[Esc]")) : th.fg("dim", "[Esc]");
597
519
  const actionsWidth = visibleWidth(previewLabel) + 1 + visibleWidth(closeLabel);
@@ -599,11 +521,10 @@ export class ConversationViewer implements Component {
599
521
  const headerGap = Math.max(1, innerW - visibleWidth(headerLeft) - actionsWidth);
600
522
  lines.push(row(headerLeft + " ".repeat(headerGap) + previewLabel + " " + closeLabel));
601
523
  const invocationLine = this.invocationLine();
602
- lines.push(row(invocationLine ?? ""));
524
+ if (invocationLine) lines.push(row(invocationLine));
603
525
  lines.push(hrMid);
604
526
 
605
- this.buildContentLines(contentWidth);
606
- const contentLines = this.cachedLines;
527
+ const contentLines = this.buildContentLines(contentWidth);
607
528
  const viewportHeight = this.viewportHeight();
608
529
  const maxScroll = Math.max(0, contentLines.length - viewportHeight);
609
530
  if (this.autoScroll) this.scrollOffset = maxScroll;
@@ -617,7 +538,7 @@ export class ConversationViewer implements Component {
617
538
  const block = this.cachedMessageBlocks[this.currentMessageIndex()];
618
539
  if (block) {
619
540
  const header = renderConversationRoleHeader(block, this.theme);
620
- displayed[0] = makeRenderLine({ text: ` ${header}`, plain: header, blockIndex: currentBlockIdx, railable: true });
541
+ displayed[0] = { text: ` ${header}`, plain: header, blockIndex: currentBlockIdx, railable: true };
621
542
  }
622
543
  }
623
544
  for (let i = 0; i < displayed.length; i++) {
@@ -633,10 +554,7 @@ export class ConversationViewer implements Component {
633
554
  const railGlyph = currentBlock?.kind === "tool" ? "▌" : "▎";
634
555
  text = text.startsWith(" ") ? th.fg(railColor, railGlyph) + text.slice(1) : th.fg(railColor, railGlyph) + text;
635
556
  }
636
- const plainText = stripAnsi(text);
637
- lines.push(plainText.includes("... (truncated,")
638
- ? th.fg("border", "│") + " " + text + th.fg("border", "│")
639
- : contentRow(text, i, contentLines.length, viewportHeight, visibleStart));
557
+ lines.push(contentRow(text, i, contentLines.length, viewportHeight, visibleStart));
640
558
  }
641
559
  for (let i = displayed.length; i < viewportHeight; i++) {
642
560
  lines.push(contentRow("", i, contentLines.length, viewportHeight, visibleStart));
@@ -657,21 +575,14 @@ export class ConversationViewer implements Component {
657
575
  lines.push(row(truncateToWidth(th.fg("accent", query) + th.fg("dim", matchCount) + hints, innerW, "...", true)));
658
576
  } else {
659
577
  const actions: string[] = [];
660
- if (this.canSteer()) actions.push(th.fg("dim", innerW < 100 ? "Enter steer (e steer)" : "e steer"));
578
+ if (this.canSteer()) actions.push(th.fg("dim", "e steer"));
661
579
  if (this.isStoppable()) actions.push(this.stopArmed ? th.fg("error", "x again to STOP") : th.fg("dim", "x stop"));
662
580
  const editHint = this.isLive() ? " O" : "";
663
- const markdownHint = innerW < 100 ? `m ${MARKDOWN_MODE_LABELS[this.currentMarkdownMode()]}` : "";
664
- const shortcutHints = innerW < 100
665
- ? [
666
- `j/k scroll · J/K messages · [/] tools · w preview · g/G t ${markdownHint} M o${editHint} /n N q`,
667
- `[/] tools · w preview · ${markdownHint} · M o${editHint} /n N q`,
668
- `${markdownHint} · M o${editHint} /n N · Esc close`,
669
- ]
670
- : [
671
- `j/k scroll · J/K messages · [/] tools · w preview · g/G t M o${editHint} /n N q`,
672
- `[/] tools · w preview · M o${editHint} /n N q`,
673
- `[/] tools · w · M o${editHint} /n N q`,
674
- ];
581
+ const shortcutHints = [
582
+ `j/k scroll · J/K messages · [/] tools · w preview · g/G t M o${editHint} /n N q`,
583
+ `[/] tools · w preview · M o${editHint} /n N q`,
584
+ `[/] tools · w · M o${editHint} /n N q`,
585
+ ];
675
586
  const scrollPct = contentLines.length <= viewportHeight ? "100%" : `${Math.round(((visibleStart + viewportHeight) / contentLines.length) * 100)}%`;
676
587
  const count = th.fg("dim", `${contentLines.length} lines · ${scrollPct}`);
677
588
  const countWidth = visibleWidth(count);
@@ -712,7 +623,6 @@ export class ConversationViewer implements Component {
712
623
  if (this.closed) return;
713
624
  if (this.hasFocusedBlock) this.selectedBlockId = this.currentBlock()?.id;
714
625
  this.blocks = formatConversationMessages(this.session.messages);
715
- this.lastSessionKey = this.sessionKey();
716
626
  this.timeline.setBlocks(this.blocks);
717
627
  this.cacheVersion++;
718
628
  this.invalidate();
@@ -731,7 +641,7 @@ export class ConversationViewer implements Component {
731
641
  const end = Math.min(this.cachedLines.length, start + Math.max(0, range.height));
732
642
  for (let index = start; index < end; index++) {
733
643
  const line = snapshotLines[index];
734
- if (line) this.cachedLines[index] = makeRenderLine({ ...line });
644
+ if (line) this.cachedLines[index] = { ...line };
735
645
  }
736
646
  }
737
647
  }
@@ -755,119 +665,45 @@ export class ConversationViewer implements Component {
755
665
  }
756
666
 
757
667
  private chromeLines(): number {
758
- return CHROME_LINES_BASE + 1 + (this.composer ? 1 : 0);
668
+ return CHROME_LINES_BASE + (this.invocationLine() ? 1 : 0) + (this.composer ? 1 : 0);
759
669
  }
760
670
 
761
671
  private invocationLine(): string | undefined {
762
672
  const liveModel = this.isLive() ? this.record.session?.model : undefined;
763
- const captured = this.record.invocation as (AgentRecord["invocation"] & {
764
- effectiveModelName?: string;
765
- effectiveModelId?: string;
766
- effectiveThinking?: EffectiveThinkingLevel;
767
- }) | undefined;
768
- const invocation = captured
673
+ const invocation = this.record.invocation
769
674
  ? {
770
- ...captured,
771
- ...(captured.modelName === undefined && captured.effectiveModelName !== undefined && { modelName: captured.effectiveModelName }),
772
- ...(captured.modelId === undefined && captured.effectiveModelId !== undefined && { modelId: captured.effectiveModelId }),
773
- ...(captured.thinking === undefined && captured.effectiveThinking !== undefined && { thinking: captured.effectiveThinking }),
774
- ...(liveModel && { modelName: liveModel.name ?? liveModel.id, modelId: `${liveModel.provider}/${liveModel.id}` }),
775
- ...(this.isLive() && { thinking: this.record.session?.thinkingLevel }),
675
+ ...this.record.invocation,
676
+ ...(liveModel && { effectiveModelName: liveModel.name ?? liveModel.id }),
677
+ ...(this.isLive() && { effectiveThinking: this.record.session?.thinkingLevel }),
776
678
  }
777
- : liveModel
778
- ? { modelName: liveModel.name ?? liveModel.id, modelId: `${liveModel.provider}/${liveModel.id}`, thinking: this.record.session?.thinkingLevel }
679
+ : liveModel || (this.isLive() && this.record.session?.thinkingLevel)
680
+ ? {
681
+ effectiveModelName: liveModel?.name ?? liveModel?.id,
682
+ effectiveThinking: this.record.session?.thinkingLevel,
683
+ }
779
684
  : undefined;
780
- if (!invocation) return undefined;
781
- const { modelName, modelId, tags } = buildInvocationTags(invocation);
782
- const effectiveModelName = modelId ?? modelName;
783
- if (!effectiveModelName && tags.length === 0) return undefined;
784
- const displayModel = captured?.effectiveModelName !== undefined
785
- ? `model: ${captured.effectiveModelName}`
786
- : effectiveModelName;
787
- return this.theme.fg("dim", ` ↳ ${[displayModel, ...tags].filter(Boolean).join(" · ")}`);
685
+ if (!invocation) return this.theme.fg("dim", " ↳ model: unknown · thinking: unknown");
686
+ const { modelName, tags } = buildInvocationTags(invocation);
687
+ const effectiveModelName = invocation.effectiveModelName ?? modelName;
688
+ const parts = effectiveModelName ? [`model: ${effectiveModelName}`, ...tags] : ["model: inherit", ...tags];
689
+ return this.theme.fg("dim", ` ↳ ${parts.join(" · ")}`);
788
690
  }
789
691
 
790
- private currentMarkdownMode(): ViewerMarkdownMode {
791
- return this.markdownModeOverride ?? this.viewerMarkdown?.() ?? "assistant";
792
- }
793
-
794
- private cycleMarkdownMode(): void {
795
- const current = this.currentMarkdownMode();
796
- const next = MARKDOWN_MODES[(MARKDOWN_MODES.indexOf(current) + 1) % MARKDOWN_MODES.length]!;
797
- this.markdownModeOverride = next;
798
- this.onMarkdownMode?.(next);
799
- this.timeline.setMarkdownMode(() => this.currentMarkdownMode());
800
- this.invalidate();
801
- this.tui.requestRender();
802
- }
803
-
804
- private sessionKey(): string {
805
- return this.session.messages.map(message => {
806
- const value = message as unknown as { role?: unknown; content?: unknown; output?: unknown; command?: unknown };
807
- const content = value.content;
808
- const summarize = (text: string): string => `${text.length}:${text.slice(0, 24)}:${text.slice(-24)}`;
809
- const contentSummary = typeof content === "string"
810
- ? summarize(content)
811
- : Array.isArray(content)
812
- ? content.map(part => typeof part === "object" && part && "text" in part && typeof part.text === "string" ? summarize(part.text) : "").join("|")
813
- : "";
814
- return `${String(value.role)}:${contentSummary}:${typeof value.output === "string" ? summarize(value.output) : ""}:${typeof value.command === "string" ? summarize(value.command) : ""}`;
815
- }).join("\u0001");
816
- }
817
-
818
- private blockSignature(block: ConversationBlock): string {
819
- let resultLength = 0;
820
- const content = block.toolResult?.content;
821
- if (typeof content === "string") resultLength = content.length;
822
- else if (Array.isArray(content)) resultLength = content.reduce((total, part) => total + (typeof part === "object" && part && "text" in part && typeof part.text === "string" ? part.text.length : 0), 0);
823
- return `${block.id}\u0000${block.markdown}\u0000${block.fullText}\u0000${block.toolLine ?? ""}\u0000${resultLength}`;
824
- }
825
-
826
- private syncBlocksFromSession(): void {
827
- const sessionKey = this.sessionKey();
828
- if (sessionKey === this.lastSessionKey) return;
829
- this.lastSessionKey = sessionKey;
830
- const refreshed = formatConversationMessages(this.session.messages);
831
- const sameShape = refreshed.length === this.blocks.length && refreshed.every((block, index) => block.id === this.blocks[index]?.id);
832
- if (!sameShape) {
833
- this.blocks = refreshed;
834
- this.timeline.setBlocks(this.blocks);
835
- this.cacheVersion++;
836
- this.cachedVersion = -1;
837
- return;
838
- }
839
- let changed = false;
840
- for (let index = 0; index < refreshed.length; index++) {
841
- const current = this.blocks[index]!;
842
- const next = refreshed[index]!;
843
- if (this.blockSignature(current) !== this.blockSignature(next)) changed = true;
844
- Object.assign(current, next);
845
- }
846
- if (changed) {
847
- this.cacheVersion++;
848
- this.cachedVersion = -1;
849
- }
850
- }
851
-
852
- private buildContentLines(width: number): PublicRenderLines {
853
- if (width <= 0) return new PublicRenderLines([]);
854
- this.syncBlocksFromSession();
855
- if (this.cachedVersion === this.cacheVersion && this.cachedWidth === width) {
856
- this.timeline.render(width);
857
- return publicRenderLines(this.cachedLines);
858
- }
692
+ private buildContentLines(width: number): RenderLine[] {
693
+ if (width <= 0) return [];
694
+ if (this.cachedVersion === this.cacheVersion && this.cachedWidth === width) return this.cachedLines;
859
695
  this.timeline.setShowTools(this.showTools);
860
696
  this.timeline.render(width);
861
697
  const timelineSnapshot = this.timeline.getSnapshot();
862
698
  const lines: RenderLine[] = timelineSnapshot.lines.map((line) => {
863
699
  const rawText = typeof line?.text === "string" ? line.text : "";
864
700
  const text = truncateToWidth(rawText, width, "", true);
865
- return makeRenderLine({
701
+ return {
866
702
  text,
867
703
  plain: stripAnsi(text),
868
704
  blockIndex: typeof line?.blockIndex === "number" ? line.blockIndex : -1,
869
705
  ...(line?.railable ? { railable: true } : {}),
870
- });
706
+ };
871
707
  });
872
708
  this.cachedMessageStarts = [...timelineSnapshot.messageStarts];
873
709
  this.cachedMessageBlocks = [...timelineSnapshot.messageBlocks];
@@ -887,13 +723,13 @@ export class ConversationViewer implements Component {
887
723
  if (this.record.status === "running" && this.activity) {
888
724
  const act = describeActivity(this.activity.activeTools, this.activity.responseText);
889
725
  const text = `▍ ${act}`;
890
- lines.push(makeRenderLine({ text: truncateToWidth(this.theme.fg("accent", "▍ ") + this.theme.fg("dim", act), width), plain: text, blockIndex: -1 }));
726
+ lines.push({ text: truncateToWidth(this.theme.fg("accent", "▍ ") + this.theme.fg("dim", act), width), plain: text, blockIndex: -1 });
891
727
  }
892
728
  this.cachedWidth = width;
893
729
  this.cachedVersion = this.cacheVersion;
894
730
  this.cachedLines = lines;
895
731
  if (this.searchMode || this.searchQuery) this.recomputeMatches();
896
- return publicRenderLines(lines);
732
+ return lines;
897
733
  }
898
734
 
899
735
  private currentMessageIndex(): number {
@@ -11,7 +11,6 @@
11
11
  import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
12
12
  import type { SubagentScheduler } from "../schedule.js";
13
13
  import type { ScheduledSubagent } from "../types.js";
14
- import { selectItem } from "./select-item.js";
15
14
 
16
15
  /** Format an ISO timestamp as relative time ("in 4h", "2d ago", "—"). */
17
16
  function relTime(iso: string | undefined, now = Date.now()): string {
@@ -86,16 +85,16 @@ export async function showSchedulesMenu(
86
85
  return;
87
86
  }
88
87
 
89
- // Numbered + item-paired: two jobs whose names agree in the first 18
90
- // characters format identically, and matching the returned string back
91
- // against a parallel label array cancelled whichever came first.
92
- const job = await selectItem(
93
- ctx.ui,
88
+ const labels = jobs.map(j => formatJob(j, scheduler));
89
+ const choice = await ctx.ui.select(
94
90
  `Scheduled jobs (${jobs.length}) — select to cancel`,
95
- jobs,
96
- j => formatJob(j, scheduler),
91
+ labels,
97
92
  );
98
- if (!job) return;
93
+ if (!choice) return;
94
+
95
+ const idx = labels.indexOf(choice);
96
+ if (idx < 0) return;
97
+ const job = jobs[idx];
99
98
 
100
99
  const ok = await ctx.ui.confirm(`Cancel "${job.name}"?`, formatDetails(job, scheduler));
101
100
  if (!ok) return;
package/src/usage.ts CHANGED
@@ -6,126 +6,19 @@
6
6
  * stats-derived sum). cacheRead is excluded because each turn's cacheRead is
7
7
  * the cumulative cached prefix re-read on that one call — summing across
8
8
  * turns counts the prefix N times. See issue #38.
9
- *
10
- * That exclusion is about this *display* total, not about what was billed: the
11
- * prefix really is re-read and re-charged on every call. So `cacheRead` is
12
- * accumulated here anyway, kept out of `getLifetimeTotal` and used only where
13
- * billing is the question — reporting to the parent session, whose own messages
14
- * pi counts the same way (`addUsageToTotals`). Reporting 0 there would make a
15
- * subagent's rows count differently from every other row in one total.
16
- *
17
- * `cost` is a plain sum for the same reason: it is what pi charged for that one
18
- * message (`usage.cost.total`, priced from the model's rates), not a cumulative
19
- * figure. Both are optional because a model with no pricing data reports no
20
- * cost, and because every accumulator predates them; absent reads as 0.
21
9
  */
22
- export type LifetimeUsage = { input: number; output: number; cacheWrite: number; cacheRead?: number; cost?: number };
10
+ export type LifetimeUsage = { input: number; output: number; cacheWrite: number };
23
11
 
24
- /**
25
- * Sum of lifetime *token* components for DISPLAY, or 0 if undefined.
26
- * Deliberately excludes `cacheRead` (see above) and `cost` — that is money, not
27
- * tokens, and lives on the same object only because it accumulates on the same
28
- * events.
29
- */
12
+ /** Sum of lifetime usage components, or 0 if undefined. */
30
13
  export function getLifetimeTotal(u?: LifetimeUsage): number {
31
14
  return u ? u.input + u.output + u.cacheWrite : 0;
32
15
  }
33
16
 
34
- /** Accumulated cost in USD, or 0 when unpriced/undefined. */
35
- export function getLifetimeCost(u?: LifetimeUsage): number {
36
- return u?.cost ?? 0;
37
- }
38
-
39
17
  /** Add a usage delta into a target accumulator (mutates target). */
40
18
  export function addUsage(into: LifetimeUsage, delta: LifetimeUsage): void {
41
19
  into.input += delta.input;
42
20
  into.output += delta.output;
43
21
  into.cacheWrite += delta.cacheWrite;
44
- if (delta.cacheRead) into.cacheRead = (into.cacheRead ?? 0) + delta.cacheRead;
45
- if (delta.cost) into.cost = (into.cost ?? 0) + delta.cost;
46
- }
47
-
48
- /**
49
- * A pi `Usage`. Rebuilt here rather than imported so this module stays
50
- * dependency-free for tests; the fields are pi's, and every one of them must be
51
- * present: pi's `addUsageToTotals` dereferences `usage.cost.total` with no
52
- * guard, so a partial object throws inside pi rather than at the call site.
53
- *
54
- * This is pi's convention for spend in anything handed to a consumer — every
55
- * extension-facing payload that carries it takes the whole object
56
- * (`ToolResultEvent`, `ToolResultEventResult`, `AssistantMessage`, …), never a
57
- * flattened cost. Pi flattens only in computed read APIs it expects you to
58
- * render, like `SessionStats`. So both places we hand usage to someone else —
59
- * `AgentToolResult.usage` and the `subagents:completed` / `subagents:failed`
60
- * events — carry this, and gain whatever pi adds to `Usage` for free.
61
- */
62
- export type ReportedUsage = {
63
- input: number;
64
- output: number;
65
- cacheRead: number;
66
- cacheWrite: number;
67
- totalTokens: number;
68
- cost: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number };
69
- };
70
-
71
- /**
72
- * Render an accumulator as a pi `Usage`, or undefined when nothing was spent —
73
- * callers attach nothing rather than a zero, so a consumer can tell "spent
74
- * nothing" from "never ran".
75
- *
76
- * `cacheRead` IS included, unlike in `getLifetimeTotal`: pi sums it across a
77
- * session's own assistant messages, and the prefix genuinely is re-read and
78
- * re-billed on every call. Only `total` is populated on the cost breakdown; pi
79
- * reads nothing else from it, and the per-kind split is not tracked.
80
- */
81
- export function toReportedUsage(u: LifetimeUsage): ReportedUsage | undefined {
82
- const { input, output, cacheWrite, cacheRead = 0, cost = 0 } = u;
83
- if (input === 0 && output === 0 && cacheWrite === 0 && cacheRead === 0 && cost === 0) return undefined;
84
- return {
85
- input,
86
- output,
87
- cacheRead,
88
- cacheWrite,
89
- totalTokens: input + output + cacheRead + cacheWrite,
90
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: cost },
91
- };
92
- }
93
-
94
- /**
95
- * Subagent spend that the parent session has not been told about yet.
96
- *
97
- * Subagents run in their own pi sessions, so none of what they spend appears in
98
- * the parent's `getSessionStats()`. Pi does aggregate `toolResult.usage` into
99
- * those stats, though — so the way back into the parent's footer and `/cost` is
100
- * to hang the spend on a tool result. Background and scheduled agents finish
101
- * between tool calls with nothing to hang it on, hence a pool: every assistant
102
- * message lands here as it happens, and the next tool result we return carries
103
- * whatever has accumulated.
104
- *
105
- * Drain empties it, so each message is reported exactly once no matter how many
106
- * results are returned or how many agents were running.
107
- */
108
- export class PendingUsagePool {
109
- private pending: LifetimeUsage = { input: 0, output: 0, cacheWrite: 0, cacheRead: 0, cost: 0 };
110
- private dirty = false;
111
-
112
- add(delta: LifetimeUsage): void {
113
- addUsage(this.pending, delta);
114
- this.dirty = true;
115
- }
116
-
117
- /**
118
- * Take everything accumulated so far as a pi `Usage`, resetting the pool.
119
- * Returns undefined when nothing is pending, so callers can leave the tool
120
- * result untouched rather than attaching a zero.
121
- */
122
- drain(): ReportedUsage | undefined {
123
- if (!this.dirty) return undefined;
124
- const drained = toReportedUsage(this.pending);
125
- this.pending = { input: 0, output: 0, cacheWrite: 0, cacheRead: 0, cost: 0 };
126
- this.dirty = false;
127
- return drained;
128
- }
129
22
  }
130
23
 
131
24
  /** Minimal shape we read from upstream `getSessionStats()`. */