@esso0428/pi-subagents 0.17.16 → 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 +6 -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 +1 -1
  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
@@ -6,11 +6,10 @@
6
6
  */
7
7
 
8
8
  import { Editor, isKeyRelease, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
9
- import { renderAgentName } from "../agent-color.js";
10
9
  import type { AgentManager } from "../agent-manager.js";
11
10
  import { getConfig } from "../agent-types.js";
12
11
  import type { AgentInvocation, AgentRecord, SubagentType, WidgetMode } from "../types.js";
13
- import { getLifetimeCost, getLifetimeTotal, getSessionContextPercent, type LifetimeUsage, type SessionLike } from "../usage.js";
12
+ import { getLifetimeTotal, getSessionContextPercent, type LifetimeUsage, type SessionLike } from "../usage.js";
14
13
 
15
14
  // ---- Constants ----
16
15
 
@@ -55,11 +54,10 @@ export type Theme = {
55
54
 
56
55
  export type AgentWidgetOpenMode = "live" | "history";
57
56
  export type AgentWidgetOpenCallback = (record: AgentRecord, mode: AgentWidgetOpenMode) => void | Promise<void>;
58
- export interface AgentWidgetOptions {
59
- canOpenHistory?: (record: AgentRecord) => boolean;
60
- onOpen?: AgentWidgetOpenCallback;
61
- showCost?: () => boolean;
62
- }
57
+ export type AgentWidgetOptions = {
58
+ canOpenHistory: (record: AgentRecord) => boolean;
59
+ onOpen: AgentWidgetOpenCallback;
60
+ };
63
61
  /** @deprecated Use AgentWidgetOpenMode. */
64
62
  export type AgentOpenMode = AgentWidgetOpenMode;
65
63
  /** @deprecated Use AgentWidgetOpenCallback. */
@@ -74,8 +72,8 @@ export type UICtx = {
74
72
  content: undefined | ((tui: any, theme: Theme) => { render(): string[]; invalidate(): void }),
75
73
  options?: { placement?: "aboveEditor" | "belowEditor" },
76
74
  ): void;
77
- onTerminalInput?(handler: (data: string) => { consume?: boolean; data?: string } | undefined): () => void;
78
- getEditorText?(): string;
75
+ onTerminalInput(handler: (data: string) => { consume?: boolean; data?: string } | undefined): () => void;
76
+ getEditorText(): string;
79
77
  };
80
78
 
81
79
  /** Per-agent live activity state. */
@@ -113,8 +111,6 @@ export interface AgentDetails {
113
111
  turnCount?: number;
114
112
  /** Effective max turns (undefined = unlimited). */
115
113
  maxTurns?: number;
116
- /** Estimated cost in USD; 0 when the model has no pricing data. */
117
- cost?: number;
118
114
  agentId?: string;
119
115
  error?: string;
120
116
  }
@@ -180,16 +176,6 @@ export function formatDuration(startedAt: number, completedAt?: number): string
180
176
  return `${formatMs(Date.now() - startedAt)} (running)`;
181
177
  }
182
178
 
183
- /** Format an estimated model cost without implying billing precision. */
184
- export function formatCost(cost: number): string {
185
- if (!(cost > 0)) return "";
186
- if (cost < 0.0001) return "<$0.0001";
187
- if (cost >= 1) return `~$${cost.toFixed(2)}`;
188
- const rounded = Number(cost.toFixed(4));
189
- const decimals = (String(rounded).split(".")[1] ?? "").length;
190
- return `~$${rounded.toFixed(Math.max(2, decimals))}`;
191
- }
192
-
193
179
  /** Get display name for any agent type (built-in or custom). */
194
180
  export function getDisplayName(type: SubagentType): string {
195
181
  return getConfig(type).displayName;
@@ -204,23 +190,17 @@ export function getPromptModeLabel(type: SubagentType): string | undefined {
204
190
  /** Mode label is not included — callers add it where they want it. */
205
191
  export function buildInvocationTags(
206
192
  invocation: AgentInvocation | undefined,
207
- ): { modelName?: string; modelId?: string; tags: string[] } {
193
+ ): { modelName?: string; tags: string[] } {
208
194
  const tags: string[] = [];
209
195
  if (!invocation) return { tags };
210
- const asked = (value: string | undefined, requested: string | undefined): string | undefined =>
211
- value && requested && requested !== value ? `${value} (asked ${requested})` : value;
212
- const thinking = asked(invocation.thinking, invocation.requestedThinking);
196
+ const thinking = invocation.effectiveThinking ?? invocation.thinking;
213
197
  if (thinking) tags.push(`thinking: ${thinking}`);
214
198
  if (invocation.isolated) tags.push("isolated");
215
199
  if (invocation.isolation === "worktree") tags.push("worktree");
216
200
  if (invocation.inheritContext) tags.push("inherit context");
217
201
  if (invocation.runInBackground) tags.push("background");
218
202
  if (invocation.maxTurns != null) tags.push(`max turns: ${invocation.maxTurns}`);
219
- return {
220
- modelName: asked(invocation.modelName, invocation.requestedModel),
221
- modelId: asked(invocation.modelId, invocation.requestedModel),
222
- tags,
223
- };
203
+ return { modelName: invocation.modelName, tags };
224
204
  }
225
205
 
226
206
  /** Normalize and truncate text so it can never add physical widget rows. */
@@ -274,14 +254,6 @@ export class AgentWidget {
274
254
  /** First logical row currently represented by the bounded viewport. */
275
255
  private viewportStart = 0;
276
256
 
277
- /** Cached records and roster. Rebuilt by update(), never by an arrow press. */
278
- private cachedAgents: AgentRecord[] = [];
279
- private cachedRoster: AgentRecord[] = [];
280
- private rosterCacheKey: string | undefined;
281
- private rosterCacheInitialized = false;
282
- /** Terminal history capability is a potentially filesystem-backed lookup. */
283
- private historyOpenabilityCache = new Map<string, { key: string; value: boolean }>();
284
-
285
257
  /** Whether the widget callback is currently registered with the TUI. */
286
258
  private widgetRegistered = false;
287
259
  /** Cached TUI reference from widget factory callback, used for requestRender(). */
@@ -290,27 +262,17 @@ export class AgentWidget {
290
262
  private lastStatusText: string | undefined;
291
263
  /** Snapshot of the state used for the last widget registration/render request. */
292
264
  private lastRenderKey: string | undefined;
293
- private readonly openOptions: AgentWidgetOptions;
294
- private readonly showCost: () => boolean;
295
- private readonly showModel: () => boolean;
296
265
 
297
266
  constructor(
298
267
  private manager: AgentManager,
299
268
  private agentActivity: Map<string, AgentActivity>,
300
269
  /** Read live at render time. Selects which agents the widget shows. */
301
270
  private mode: () => WidgetMode = () => "all",
302
- showCostOrOptions: (() => boolean) | AgentWidgetOptions = () => false,
303
- showModel: () => boolean = () => false,
304
- ) {
305
- if (typeof showCostOrOptions === "function") {
306
- this.showCost = showCostOrOptions;
307
- this.openOptions = {};
308
- } else {
309
- this.showCost = showCostOrOptions.showCost ?? (() => false);
310
- this.openOptions = showCostOrOptions;
311
- }
312
- this.showModel = showModel;
313
- }
271
+ private options: AgentWidgetOptions = {
272
+ canOpenHistory: (record) => record.session !== undefined || record.completedAt !== undefined,
273
+ onOpen: () => {},
274
+ },
275
+ ) {}
314
276
 
315
277
  /**
316
278
  * Agents eligible for the widget, per the current `WidgetMode`:
@@ -323,7 +285,7 @@ export class AgentWidget {
323
285
  * only proven-foreground runs drop out — nothing else silently vanishes.
324
286
  * - `all`: every agent.
325
287
  */
326
- private widgetAgentsFromManager(): AgentRecord[] {
288
+ private widgetAgents() {
327
289
  const all = this.manager.listAgents();
328
290
  switch (this.mode()) {
329
291
  case "off": return [];
@@ -332,80 +294,6 @@ export class AgentWidget {
332
294
  }
333
295
  }
334
296
 
335
- /** Records currently visible to the widget; safe for render and input paths. */
336
- private widgetAgents(): AgentRecord[] {
337
- return this.rosterCacheInitialized ? this.cachedAgents : this.widgetAgentsFromManager();
338
- }
339
-
340
- private canOpenHistory(record: AgentRecord): boolean {
341
- return this.openOptions.canOpenHistory?.(record) ?? true;
342
- }
343
-
344
- private historyOpenabilityKey(record: AgentRecord): string {
345
- return JSON.stringify([
346
- record.status,
347
- record.completedAt ?? null,
348
- record.transcriptPath ?? null,
349
- record.session !== undefined,
350
- ]);
351
- }
352
-
353
- /**
354
- * Cache terminal history capability by the record state that can affect it.
355
- * The supplied predicate may call `existsSync`, so it must not run from a
356
- * timer refresh, render-key calculation, or arrow-navigation path more than
357
- * once for the same terminal record state.
358
- */
359
- private cachedCanOpenHistory(record: AgentRecord): boolean {
360
- if (record.status === "running" || record.status === "queued") return true;
361
-
362
- const key = this.historyOpenabilityKey(record);
363
- const cached = this.historyOpenabilityCache.get(record.id);
364
- if (cached?.key === key) return cached.value;
365
-
366
- const value = this.canOpenHistory(record);
367
- this.historyOpenabilityCache.set(record.id, { key, value });
368
- return value;
369
- }
370
-
371
- /** Refresh the manager snapshot and openability cache at lifecycle/update time. */
372
- private refreshRoster(): AgentRecord[] {
373
- const agents = this.widgetAgentsFromManager();
374
- const key = JSON.stringify({
375
- mode: this.mode(),
376
- agents: agents.map(record => [
377
- record.id,
378
- record.type,
379
- record.description,
380
- record.status,
381
- record.completedAt ?? null,
382
- record.startedAt,
383
- record.toolUses,
384
- record.error ?? null,
385
- record.transcriptPath ?? null,
386
- record.session !== undefined,
387
- record.isBackground,
388
- record.status !== "running" && record.status !== "queued"
389
- ? this.cachedCanOpenHistory(record)
390
- : undefined,
391
- ]),
392
- });
393
- if (this.rosterCacheInitialized && key === this.rosterCacheKey) return this.cachedRoster;
394
-
395
- const finished = agents.filter(record =>
396
- record.status !== "running" && record.status !== "queued"
397
- && record.completedAt !== undefined
398
- && this.cachedCanOpenHistory(record),
399
- );
400
- const running = agents.filter(record => record.status === "running");
401
- const queued = agents.filter(record => record.status === "queued");
402
- this.cachedAgents = agents;
403
- this.cachedRoster = [...running, ...queued, ...finished];
404
- this.rosterCacheKey = key;
405
- this.rosterCacheInitialized = true;
406
- return this.cachedRoster;
407
- }
408
-
409
297
  /** Set the UI context (grabbed from first tool execution). */
410
298
  setUICtx(ctx: UICtx): boolean {
411
299
  if (ctx === this.uiCtx) return false;
@@ -423,11 +311,6 @@ export class AgentWidget {
423
311
  this.selectedAgentId = undefined;
424
312
  this.selectedRosterIndex = 0;
425
313
  this.viewportStart = 0;
426
- this.cachedAgents = [];
427
- this.cachedRoster = [];
428
- this.rosterCacheKey = undefined;
429
- this.rosterCacheInitialized = false;
430
- this.historyOpenabilityCache.clear();
431
314
  // Print/RPC tests and lightweight embedders may provide only the widget
432
315
  // surface; real interactive contexts always implement this hook.
433
316
  if (typeof ctx.onTerminalInput === "function") {
@@ -437,8 +320,8 @@ export class AgentWidget {
437
320
  }
438
321
 
439
322
  /** Request a render on the currently registered TUI without touching input. */
440
- requestUiRefresh(force = false): boolean {
441
- if (!this.tui || typeof this.tui.requestRender !== "function") return false;
323
+ requestUiRefresh(force = true): boolean {
324
+ if (!this.tui) return false;
442
325
  this.tui.requestRender(force);
443
326
  return true;
444
327
  }
@@ -450,9 +333,7 @@ export class AgentWidget {
450
333
 
451
334
  /** Keep the spinner/elapsed-time timer alive only while a visible agent runs. */
452
335
  ensureTimer() {
453
- if (!this.uiCtx) return;
454
- const roster = this.rosterCacheInitialized ? this.cachedRoster : this.refreshRoster();
455
- if (!roster.some(a => a.status === "running")) return;
336
+ if (!this.uiCtx || !this.widgetAgents().some(a => a.status === "running")) return;
456
337
  if (!this.widgetInterval) {
457
338
  this.widgetInterval = setInterval(() => this.update(true), 250);
458
339
  }
@@ -473,9 +354,6 @@ export class AgentWidget {
473
354
  */
474
355
  markFinished(_agentId: string) {}
475
356
 
476
- /** Retained for lifecycle call sites; terminal rows are record-driven now. */
477
- markRunning(_agentId: string) {}
478
-
479
357
  /**
480
358
  * Records represented by selectable rows in the above-editor widget.
481
359
  *
@@ -484,7 +362,15 @@ export class AgentWidget {
484
362
  * panel exposes currently useful work before terminal history.
485
363
  */
486
364
  private roster(): AgentRecord[] {
487
- return this.rosterCacheInitialized ? this.cachedRoster : this.refreshRoster();
365
+ const agents = this.widgetAgents();
366
+ const finished = agents.filter(record =>
367
+ record.status !== "running" && record.status !== "queued"
368
+ && record.completedAt !== undefined
369
+ && this.options.canOpenHistory(record),
370
+ );
371
+ const running = agents.filter(record => record.status === "running");
372
+ const queued = agents.filter(record => record.status === "queued");
373
+ return [...running, ...queued, ...finished];
488
374
  }
489
375
 
490
376
  /** True when pi's prompt editor owns the keyboard. */
@@ -504,7 +390,7 @@ export class AgentWidget {
504
390
  // roster) clear this state explicitly instead of treating every exit as a
505
391
  // reset.
506
392
  this.navigationActive = false;
507
- this.requestUiRefresh();
393
+ this.update();
508
394
  }
509
395
 
510
396
  /** Resume navigation from the retained row, or select the first row. */
@@ -522,7 +408,7 @@ export class AgentWidget {
522
408
  this.selectedRosterIndex = fallbackIndex;
523
409
  this.viewportStart = Math.min(this.viewportStart, Math.max(0, records.length - 1));
524
410
  }
525
- this.requestUiRefresh();
411
+ this.update();
526
412
  }
527
413
 
528
414
  /** Move the selected row, activating only from an empty focused editor. */
@@ -545,7 +431,7 @@ export class AgentWidget {
545
431
  const nextIndex = Math.max(0, Math.min(records.length - 1, currentIndex + direction));
546
432
  this.selectedAgentId = records[nextIndex].id;
547
433
  this.selectedRosterIndex = nextIndex;
548
- this.requestUiRefresh();
434
+ this.update();
549
435
  return true;
550
436
  }
551
437
 
@@ -554,7 +440,7 @@ export class AgentWidget {
554
440
  this.deactivate();
555
441
  if (!record) return;
556
442
  const mode: AgentWidgetOpenMode = record.status === "running" || record.status === "queued" ? "live" : "history";
557
- void this.openOptions.onOpen?.(record, mode);
443
+ void this.options.onOpen(record, mode);
558
444
  }
559
445
 
560
446
  /** Handle terminal input before it reaches the focused prompt editor. */
@@ -592,7 +478,8 @@ export class AgentWidget {
592
478
  }
593
479
 
594
480
  /** Render a finished agent line. */
595
- private renderFinishedLine(a: { id: string; type: SubagentType; status: string; description: string; toolUses: number; startedAt: number; completedAt?: number; error?: string; lifetimeUsage?: LifetimeUsage }, theme: Theme): string {
481
+ private renderFinishedLine(a: { id: string; type: SubagentType; status: string; description: string; toolUses: number; startedAt: number; completedAt?: number; error?: string }, theme: Theme): string {
482
+ const name = getDisplayName(a.type);
596
483
  const modeLabel = getPromptModeLabel(a.type);
597
484
  const duration = formatMs((a.completedAt ?? Date.now()) - a.startedAt);
598
485
 
@@ -621,13 +508,10 @@ export class AgentWidget {
621
508
  const activity = this.agentActivity.get(a.id);
622
509
  if (activity) parts.push(formatTurns(activity.turnCount, activity.maxTurns));
623
510
  if (a.toolUses > 0) parts.push(`${a.toolUses} tool use${a.toolUses === 1 ? "" : "s"}`);
624
- const costText = this.showCost() && a.lifetimeUsage ? formatCost(getLifetimeCost(a.lifetimeUsage)) : "";
625
- if (costText) parts.push(costText);
626
511
  parts.push(duration);
627
512
 
628
513
  const modeTag = modeLabel ? ` ${theme.fg("dim", `(${modeLabel})`)}` : "";
629
- const description = truncateLine(a.description);
630
- return `${icon} ${renderAgentName(a.type, theme, { fallbackColor: "dim" })}${modeTag} ${theme.fg("dim", description)} ${theme.fg("dim", "·")} ${theme.fg("dim", parts.join(" · "))}${statusText}`;
514
+ return `${icon} ${theme.fg("dim", name)}${modeTag} ${theme.fg("dim", truncateLine(a.description))} ${theme.fg("dim", "·")} ${theme.fg("dim", parts.join(" · "))}${statusText}`;
631
515
  }
632
516
 
633
517
  /**
@@ -635,10 +519,13 @@ export class AgentWidget {
635
519
  * reading live state each time instead of capturing it in a closure.
636
520
  */
637
521
  private renderWidget(tui: any, theme: Theme): string[] {
638
- const roster = this.roster();
639
- const running = roster.filter(a => a.status === "running");
640
- const queued = roster.filter(a => a.status === "queued");
641
- const finished = roster.filter(a => a.status !== "running" && a.status !== "queued");
522
+ const allAgents = this.widgetAgents();
523
+ const running = allAgents.filter(a => a.status === "running");
524
+ const queued = allAgents.filter(a => a.status === "queued");
525
+ const finished = allAgents.filter(a =>
526
+ a.status !== "running" && a.status !== "queued" && a.completedAt !== undefined
527
+ && this.options.canOpenHistory(a),
528
+ );
642
529
 
643
530
  const selectedId = this.navigationActive ? this.selectedAgentId : undefined;
644
531
  const hasActive = running.length > 0 || queued.length > 0;
@@ -658,9 +545,7 @@ export class AgentWidget {
658
545
  // Build sections separately for overflow-aware assembly.
659
546
  // Each running agent = 2 lines (header + activity), finished = 1 line, queued = 1 line.
660
547
 
661
- type WidgetRow = { record: AgentRecord; lines: string[]; recordCount?: number };
662
-
663
- const finishedLines: WidgetRow[] = [];
548
+ const finishedLines: { record: AgentRecord; lines: string[] }[] = [];
664
549
  for (const a of finished) {
665
550
  const marker = a.id === selectedId ? theme.fg("accent", "●") : theme.fg("dim", "○");
666
551
  finishedLines.push({
@@ -669,32 +554,23 @@ export class AgentWidget {
669
554
  });
670
555
  }
671
556
 
672
- const runningLines: WidgetRow[] = []; // each entry is [header, activity]
557
+ const runningLines: { record: AgentRecord; lines: string[] }[] = []; // each entry is [header, activity]
673
558
  for (const a of running) {
559
+ const name = getDisplayName(a.type);
674
560
  const modeLabel = getPromptModeLabel(a.type);
675
561
  const modeTag = modeLabel ? ` ${theme.fg("dim", `(${modeLabel})`)}` : "";
676
562
  const elapsed = formatMs(Date.now() - a.startedAt);
677
563
 
678
564
  const bg = this.agentActivity.get(a.id);
679
565
  const toolUses = bg?.toolUses ?? a.toolUses;
680
- // Spend comes from the record, never from the activity tracker: the record
681
- // survives the agent finishing and includes nested-child usage.
682
- const tokens = getLifetimeTotal(a.lifetimeUsage);
566
+ const tokens = getLifetimeTotal(bg?.lifetimeUsage);
683
567
  const contextPercent = getSessionContextPercent(bg?.session);
684
568
  const tokenText = tokens > 0 ? formatSessionTokens(tokens, contextPercent, theme, a.compactionCount) : "";
685
- const costText = this.showCost() ? formatCost(getLifetimeCost(a.lifetimeUsage)) : "";
686
569
 
687
570
  const parts: string[] = [];
688
- if (this.showModel()) {
689
- const { modelName, tags } = buildInvocationTags(a.invocation);
690
- if (modelName) parts.push(modelName);
691
- const thinkingTag = tags.find(tag => tag.startsWith("thinking: "));
692
- if (thinkingTag) parts.push(thinkingTag);
693
- }
694
571
  if (bg) parts.push(formatTurns(bg.turnCount, bg.maxTurns));
695
572
  if (toolUses > 0) parts.push(`${toolUses} tool use${toolUses === 1 ? "" : "s"}`);
696
573
  if (tokenText) parts.push(tokenText);
697
- if (costText) parts.push(costText);
698
574
  parts.push(elapsed);
699
575
  const statsText = parts.join(" · ");
700
576
 
@@ -704,13 +580,13 @@ export class AgentWidget {
704
580
  runningLines.push({
705
581
  record: a,
706
582
  lines: [
707
- truncate(theme.fg("dim", "├─") + ` ${marker} ${theme.fg("accent", frame)} ${renderAgentName(a.type, theme, { bold: true })}${modeTag} ${theme.fg("muted", truncateLine(a.description))} ${theme.fg("dim", "·")} ${fgPreservingNestedStyles(theme, "dim", statsText)}`),
708
- truncate(theme.fg("dim", "│ ") + ` ${theme.fg("dim", `⎿ ${truncateLine(activity)}`)}`),
583
+ truncate(theme.fg("dim", "├─") + ` ${marker} ${theme.fg("accent", frame)} ${theme.bold(name)}${modeTag} ${theme.fg("muted", truncateLine(a.description))} ${theme.fg("dim", "·")} ${fgPreservingNestedStyles(theme, "dim", statsText)}`),
584
+ truncate(theme.fg("dim", "│ ") + ` ${theme.fg("dim", `⎿ ${activity}`)}`),
709
585
  ],
710
586
  });
711
587
  }
712
588
 
713
- const queuedLines: WidgetRow[] = queued.map(a => {
589
+ const queuedLines: { record: AgentRecord; lines: string[] }[] = queued.map(a => {
714
590
  const marker = a.id === selectedId ? theme.fg("accent", "●") : theme.fg("dim", "○");
715
591
  return {
716
592
  record: a,
@@ -718,25 +594,10 @@ export class AgentWidget {
718
594
  };
719
595
  });
720
596
 
721
- // Keep the idle widget compact: a queue is one logical status line until
722
- // navigation starts. While navigating, expand it back to the same per-agent
723
- // roster used by key handling so every selected queued record can render.
724
- const queuedSummary: WidgetRow[] = queued.length > 0 && !this.navigationActive
725
- ? [{
726
- record: queued[0],
727
- recordCount: queued.length,
728
- lines: [truncate(theme.fg("dim", "├─") + ` ${theme.fg("dim", "○")} ${theme.fg("muted", "◦")} ${theme.fg("dim", `${queued.length} queued`)}`)],
729
- }]
730
- : [];
731
-
732
597
  // Assemble with a responsive cap (heading + overflow indicator = 2
733
598
  // reserved lines when content exceeds the available body budget).
734
599
  const maxBody = maxLines - 1; // heading takes 1 line
735
- const rows: WidgetRow[] = [
736
- ...runningLines,
737
- ...(this.navigationActive ? queuedLines : queuedSummary),
738
- ...finishedLines,
739
- ];
600
+ const rows = [...runningLines, ...queuedLines, ...finishedLines];
740
601
  const totalBody = rows.reduce((total, row) => total + row.lines.length, 0);
741
602
 
742
603
  const heading = "Agents ↑↓ select · enter view · esc back";
@@ -753,79 +614,11 @@ export class AgentWidget {
753
614
  if (rows.length > 0) {
754
615
  const lastRow = rows[rows.length - 1];
755
616
  const lastStart = lines.length - lastRow.lines.length;
756
- // Keep the selected row's bullet adjacent to the normal branch marker.
757
- // This makes the active row unambiguous even when it is the last row.
758
- const lastRowSelected = this.navigationActive && lastRow.record.id === selectedId;
759
- if (!lastRowSelected) lines[lastStart] = lines[lastStart].replace("├─", "└─");
760
- if (lastRow.lines.length === 2) {
761
- lines[lastStart + 1] = lines[lastStart + 1].replace("│ ", " ");
762
- }
763
- }
764
- } else if (!this.navigationActive && queuedSummary.length > 0) {
765
- // In the idle view the queue is deliberately one summary row. Preserve
766
- // that row before spending space on terminal history, and account for
767
- // the real number of queued records if the summary itself cannot fit.
768
- // Navigation expands the queue into per-record rows and uses the full
769
- // contiguous viewport below.
770
- const visibleRows: WidgetRow[] = [];
771
- const hiddenRows: WidgetRow[] = [];
772
- let bodyBudget = Math.max(0, maxBody - 1); // reserve the footer
773
- let runningBudget = Math.max(0, bodyBudget - 1); // reserve queued summary
774
-
775
- for (const row of runningLines) {
776
- const height = row.lines.length;
777
- if (height <= runningBudget) {
778
- visibleRows.push(row);
779
- runningBudget -= height;
780
- bodyBudget -= height;
781
- } else {
782
- hiddenRows.push(row);
783
- }
784
- }
785
-
786
- const summaryRow = queuedSummary[0];
787
- if (bodyBudget > 0) {
788
- visibleRows.push(summaryRow);
789
- bodyBudget -= summaryRow.lines.length;
790
- } else {
791
- hiddenRows.push(summaryRow);
792
- }
793
-
794
- for (const row of finishedLines) {
795
- const height = row.lines.length;
796
- if (height <= bodyBudget) {
797
- visibleRows.push(row);
798
- bodyBudget -= height;
799
- } else {
800
- hiddenRows.push(row);
801
- }
802
- }
803
-
804
- for (const row of visibleRows) lines.push(...row.lines);
805
- if (visibleRows.length > 0) {
806
- const lastRow = visibleRows[visibleRows.length - 1];
807
- const lastStart = lines.length - lastRow.lines.length;
808
617
  lines[lastStart] = lines[lastStart].replace("├─", "└─");
809
618
  if (lastRow.lines.length === 2) {
810
619
  lines[lastStart + 1] = lines[lastStart + 1].replace("│ ", " ");
811
620
  }
812
621
  }
813
-
814
- const hiddenCount = (rows: readonly WidgetRow[]): number =>
815
- rows.reduce((total, row) => total + (row.recordCount ?? 1), 0);
816
- const categoryCount = (status: string): number => hiddenRows
817
- .filter(row => row.record.status === status)
818
- .reduce((total, row) => total + (row.recordCount ?? 1), 0);
819
- const finishedCount = hiddenRows
820
- .filter(row => row.record.status !== "running" && row.record.status !== "queued")
821
- .reduce((total, row) => total + (row.recordCount ?? 1), 0);
822
- const categoryCounts: string[] = [
823
- ["running", categoryCount("running")] as [string, number],
824
- ["queued", categoryCount("queued")] as [string, number],
825
- ["finished", finishedCount] as [string, number],
826
- ].filter(([, count]) => count > 0).map(([label, count]) => `${count} ${label}`);
827
- const hidden = hiddenCount(hiddenRows);
828
- lines.push(truncate(theme.fg("dim", "└─") + ` ${theme.fg("dim", `+${hidden} more (${categoryCounts.join(", ")})`)}`));
829
622
  } else {
830
623
  // Reserve one line for a directional overflow summary. The viewport is
831
624
  // a contiguous slice in roster order, so the same slice is navigable and
@@ -877,21 +670,14 @@ export class AgentWidget {
877
670
  end = start + 1;
878
671
  }
879
672
 
880
- const countRows = (selectedRows: readonly WidgetRow[]): number =>
881
- selectedRows.reduce((total, row) => total + (row.recordCount ?? 1), 0);
882
- const hiddenBefore = countRows(rows.slice(0, start));
883
- const hiddenAfter = countRows(rows.slice(end));
673
+ const hiddenBefore = start;
674
+ const hiddenAfter = Math.max(0, rows.length - end);
884
675
  const hidden = hiddenBefore + hiddenAfter;
885
676
  const hiddenRows = rows.filter((_row, index) => index < start || index >= end);
886
- const categoryCount = (status: string): number => hiddenRows
887
- .filter(row => row.record.status === status)
888
- .reduce((total, row) => total + (row.recordCount ?? 1), 0);
889
677
  const categoryCounts: string[] = [
890
- ["running", categoryCount("running")] as [string, number],
891
- ["queued", categoryCount("queued")] as [string, number],
892
- ["finished", hiddenRows
893
- .filter(row => row.record.status !== "running" && row.record.status !== "queued")
894
- .reduce((total, row) => total + (row.recordCount ?? 1), 0)] as [string, number],
678
+ ["running", hiddenRows.filter(row => row.record.status === "running").length] as [string, number],
679
+ ["queued", hiddenRows.filter(row => row.record.status === "queued").length] as [string, number],
680
+ ["finished", hiddenRows.filter(row => row.record.status !== "running" && row.record.status !== "queued").length] as [string, number],
895
681
  ].filter(([, count]) => count > 0).map(([label, count]) => `${count} ${label}`);
896
682
  const direction = [
897
683
  ...(hiddenBefore > 0 ? [`↑ ${hiddenBefore} more`] : []),
@@ -972,7 +758,7 @@ export class AgentWidget {
972
758
  isBackground: a.isBackground,
973
759
  hasSession: a.session !== undefined,
974
760
  transcriptPath: a.transcriptPath,
975
- openableHistory: this.cachedCanOpenHistory(a),
761
+ openableHistory: this.options.canOpenHistory(a),
976
762
  })),
977
763
  activities,
978
764
  navigationActive: this.navigationActive,
@@ -984,10 +770,8 @@ export class AgentWidget {
984
770
  /** Force an immediate widget update. `advanceSpinner` is reserved for the timer. */
985
771
  update(advanceSpinner = false) {
986
772
  if (!this.uiCtx) return;
987
- // Refresh the manager snapshot once per lifecycle update. Arrow handling
988
- // only changes selection and requests a render; it never scans the manager.
989
- const roster = this.refreshRoster();
990
773
  const allAgents = this.widgetAgents();
774
+ const roster = this.roster();
991
775
 
992
776
  // Lightweight existence checks — full categorization happens in renderWidget()
993
777
  let runningCount = 0;
@@ -1101,11 +885,6 @@ export class AgentWidget {
1101
885
  this.selectedAgentId = undefined;
1102
886
  this.selectedRosterIndex = 0;
1103
887
  this.viewportStart = 0;
1104
- this.cachedAgents = [];
1105
- this.cachedRoster = [];
1106
- this.rosterCacheKey = undefined;
1107
- this.rosterCacheInitialized = false;
1108
- this.historyOpenabilityCache.clear();
1109
888
  this.uiCtx = undefined;
1110
889
  }
1111
890
  }
@@ -251,10 +251,6 @@ function bashBlock(
251
251
  const body = command ? `$ ${command}${output ? `\n${output}` : ""}` : output;
252
252
  const toolLine = `· bash → ${command || "(no command)"}`;
253
253
  const fullText = bound(body, maxChars);
254
- const result: ConversationToolResultSnapshot = {
255
- toolName: "bash",
256
- content: [{ type: "text", text: body }],
257
- };
258
254
  return {
259
255
  id,
260
256
  kind: "tool",
@@ -265,8 +261,6 @@ function bashBlock(
265
261
  fullText,
266
262
  toolLine,
267
263
  toolName: "bash",
268
- toolResult: result,
269
- result,
270
264
  };
271
265
  }
272
266