@snoglobe/helios 0.3.5 → 0.4.0

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 (331) hide show
  1. package/dist/__tests__/db-helper.d.ts +8 -0
  2. package/dist/__tests__/db-helper.d.ts.map +1 -0
  3. package/dist/__tests__/db-helper.js +15 -0
  4. package/dist/__tests__/db-helper.js.map +1 -0
  5. package/dist/config/project.test.d.ts +2 -0
  6. package/dist/config/project.test.d.ts.map +1 -0
  7. package/dist/config/project.test.js +219 -0
  8. package/dist/config/project.test.js.map +1 -0
  9. package/dist/core/orchestrator-integration.test.d.ts +2 -0
  10. package/dist/core/orchestrator-integration.test.d.ts.map +1 -0
  11. package/dist/core/orchestrator-integration.test.js +674 -0
  12. package/dist/core/orchestrator-integration.test.js.map +1 -0
  13. package/dist/core/orchestrator.d.ts +3 -1
  14. package/dist/core/orchestrator.d.ts.map +1 -1
  15. package/dist/core/orchestrator.js +98 -53
  16. package/dist/core/orchestrator.js.map +1 -1
  17. package/dist/core/orchestrator.test.d.ts +2 -0
  18. package/dist/core/orchestrator.test.d.ts.map +1 -0
  19. package/dist/core/orchestrator.test.js +982 -0
  20. package/dist/core/orchestrator.test.js.map +1 -0
  21. package/dist/core/state-machine.d.ts.map +1 -1
  22. package/dist/core/state-machine.js +3 -0
  23. package/dist/core/state-machine.js.map +1 -1
  24. package/dist/core/state-machine.test.d.ts +2 -0
  25. package/dist/core/state-machine.test.d.ts.map +1 -0
  26. package/dist/core/state-machine.test.js +95 -0
  27. package/dist/core/state-machine.test.js.map +1 -0
  28. package/dist/core/stickies.d.ts.map +1 -1
  29. package/dist/core/stickies.js +3 -0
  30. package/dist/core/stickies.js.map +1 -1
  31. package/dist/core/stickies.test.d.ts +2 -0
  32. package/dist/core/stickies.test.d.ts.map +1 -0
  33. package/dist/core/stickies.test.js +63 -0
  34. package/dist/core/stickies.test.js.map +1 -0
  35. package/dist/core/task-poller.d.ts.map +1 -1
  36. package/dist/core/task-poller.js +2 -1
  37. package/dist/core/task-poller.js.map +1 -1
  38. package/dist/init.d.ts +2 -0
  39. package/dist/init.d.ts.map +1 -1
  40. package/dist/init.js +19 -0
  41. package/dist/init.js.map +1 -1
  42. package/dist/memory/context-gate.test.d.ts +2 -0
  43. package/dist/memory/context-gate.test.d.ts.map +1 -0
  44. package/dist/memory/context-gate.test.js +318 -0
  45. package/dist/memory/context-gate.test.js.map +1 -0
  46. package/dist/memory/experiment-tracker.test.d.ts +2 -0
  47. package/dist/memory/experiment-tracker.test.d.ts.map +1 -0
  48. package/dist/memory/experiment-tracker.test.js +325 -0
  49. package/dist/memory/experiment-tracker.test.js.map +1 -0
  50. package/dist/memory/memory-store.d.ts +2 -0
  51. package/dist/memory/memory-store.d.ts.map +1 -1
  52. package/dist/memory/memory-store.js +35 -28
  53. package/dist/memory/memory-store.js.map +1 -1
  54. package/dist/memory/memory-store.test.d.ts +2 -0
  55. package/dist/memory/memory-store.test.d.ts.map +1 -0
  56. package/dist/memory/memory-store.test.js +144 -0
  57. package/dist/memory/memory-store.test.js.map +1 -0
  58. package/dist/memory/token-estimator.test.d.ts +2 -0
  59. package/dist/memory/token-estimator.test.d.ts.map +1 -0
  60. package/dist/memory/token-estimator.test.js +42 -0
  61. package/dist/memory/token-estimator.test.js.map +1 -0
  62. package/dist/metrics/analyzer.test.d.ts +2 -0
  63. package/dist/metrics/analyzer.test.d.ts.map +1 -0
  64. package/dist/metrics/analyzer.test.js +65 -0
  65. package/dist/metrics/analyzer.test.js.map +1 -0
  66. package/dist/metrics/parser.test.d.ts +2 -0
  67. package/dist/metrics/parser.test.d.ts.map +1 -0
  68. package/dist/metrics/parser.test.js +106 -0
  69. package/dist/metrics/parser.test.js.map +1 -0
  70. package/dist/metrics/store.d.ts +2 -0
  71. package/dist/metrics/store.d.ts.map +1 -1
  72. package/dist/metrics/store.js +26 -50
  73. package/dist/metrics/store.js.map +1 -1
  74. package/dist/metrics/store.test.d.ts +2 -0
  75. package/dist/metrics/store.test.d.ts.map +1 -0
  76. package/dist/metrics/store.test.js +148 -0
  77. package/dist/metrics/store.test.js.map +1 -0
  78. package/dist/paths.js +5 -5
  79. package/dist/paths.js.map +1 -1
  80. package/dist/providers/auth/auth-manager.d.ts +1 -0
  81. package/dist/providers/auth/auth-manager.d.ts.map +1 -1
  82. package/dist/providers/auth/auth-manager.js +8 -1
  83. package/dist/providers/auth/auth-manager.js.map +1 -1
  84. package/dist/providers/auth/auth-manager.test.d.ts +2 -0
  85. package/dist/providers/auth/auth-manager.test.d.ts.map +1 -0
  86. package/dist/providers/auth/auth-manager.test.js +364 -0
  87. package/dist/providers/auth/auth-manager.test.js.map +1 -0
  88. package/dist/providers/auth/token-store.js +2 -2
  89. package/dist/providers/auth/token-store.js.map +1 -1
  90. package/dist/providers/claude/history.test.d.ts +2 -0
  91. package/dist/providers/claude/history.test.d.ts.map +1 -0
  92. package/dist/providers/claude/history.test.js +755 -0
  93. package/dist/providers/claude/history.test.js.map +1 -0
  94. package/dist/providers/claude/provider.d.ts +4 -2
  95. package/dist/providers/claude/provider.d.ts.map +1 -1
  96. package/dist/providers/claude/provider.js +44 -22
  97. package/dist/providers/claude/provider.js.map +1 -1
  98. package/dist/providers/claude/provider.test.d.ts +2 -0
  99. package/dist/providers/claude/provider.test.d.ts.map +1 -0
  100. package/dist/providers/claude/provider.test.js +1167 -0
  101. package/dist/providers/claude/provider.test.js.map +1 -0
  102. package/dist/providers/openai/history.test.d.ts +2 -0
  103. package/dist/providers/openai/history.test.d.ts.map +1 -0
  104. package/dist/providers/openai/history.test.js +655 -0
  105. package/dist/providers/openai/history.test.js.map +1 -0
  106. package/dist/providers/openai/provider.d.ts +3 -2
  107. package/dist/providers/openai/provider.d.ts.map +1 -1
  108. package/dist/providers/openai/provider.js +18 -11
  109. package/dist/providers/openai/provider.js.map +1 -1
  110. package/dist/providers/openai/provider.test.d.ts +2 -0
  111. package/dist/providers/openai/provider.test.d.ts.map +1 -0
  112. package/dist/providers/openai/provider.test.js +1089 -0
  113. package/dist/providers/openai/provider.test.js.map +1 -0
  114. package/dist/providers/retry.test.d.ts +2 -0
  115. package/dist/providers/retry.test.d.ts.map +1 -0
  116. package/dist/providers/retry.test.js +194 -0
  117. package/dist/providers/retry.test.js.map +1 -0
  118. package/dist/providers/sse.d.ts +1 -0
  119. package/dist/providers/sse.d.ts.map +1 -1
  120. package/dist/providers/sse.js +29 -20
  121. package/dist/providers/sse.js.map +1 -1
  122. package/dist/providers/sse.test.d.ts +2 -0
  123. package/dist/providers/sse.test.d.ts.map +1 -0
  124. package/dist/providers/sse.test.js +79 -0
  125. package/dist/providers/sse.test.js.map +1 -0
  126. package/dist/providers/types.d.ts +1 -1
  127. package/dist/providers/types.d.ts.map +1 -1
  128. package/dist/providers/types.test.d.ts +2 -0
  129. package/dist/providers/types.test.d.ts.map +1 -0
  130. package/dist/providers/types.test.js +112 -0
  131. package/dist/providers/types.test.js.map +1 -0
  132. package/dist/remote/connection-pool.d.ts.map +1 -1
  133. package/dist/remote/connection-pool.js +24 -3
  134. package/dist/remote/connection-pool.js.map +1 -1
  135. package/dist/remote/file-sync.d.ts.map +1 -1
  136. package/dist/remote/file-sync.js +4 -3
  137. package/dist/remote/file-sync.js.map +1 -1
  138. package/dist/scheduler/sleep-manager.d.ts.map +1 -1
  139. package/dist/scheduler/sleep-manager.js +8 -1
  140. package/dist/scheduler/sleep-manager.js.map +1 -1
  141. package/dist/scheduler/sleep-manager.test.d.ts +2 -0
  142. package/dist/scheduler/sleep-manager.test.d.ts.map +1 -0
  143. package/dist/scheduler/sleep-manager.test.js +491 -0
  144. package/dist/scheduler/sleep-manager.test.js.map +1 -0
  145. package/dist/scheduler/ssh-batcher.d.ts.map +1 -1
  146. package/dist/scheduler/ssh-batcher.js +6 -4
  147. package/dist/scheduler/ssh-batcher.js.map +1 -1
  148. package/dist/scheduler/ssh-batcher.test.d.ts +2 -0
  149. package/dist/scheduler/ssh-batcher.test.d.ts.map +1 -0
  150. package/dist/scheduler/ssh-batcher.test.js +76 -0
  151. package/dist/scheduler/ssh-batcher.test.js.map +1 -0
  152. package/dist/scheduler/state-store.d.ts.map +1 -1
  153. package/dist/scheduler/state-store.js +1 -2
  154. package/dist/scheduler/state-store.js.map +1 -1
  155. package/dist/scheduler/trigger-scheduler.d.ts.map +1 -1
  156. package/dist/scheduler/trigger-scheduler.js +59 -36
  157. package/dist/scheduler/trigger-scheduler.js.map +1 -1
  158. package/dist/scheduler/trigger-scheduler.test.d.ts +2 -0
  159. package/dist/scheduler/trigger-scheduler.test.d.ts.map +1 -0
  160. package/dist/scheduler/trigger-scheduler.test.js +483 -0
  161. package/dist/scheduler/trigger-scheduler.test.js.map +1 -0
  162. package/dist/scheduler/triggers/file.d.ts.map +1 -1
  163. package/dist/scheduler/triggers/file.js +12 -3
  164. package/dist/scheduler/triggers/file.js.map +1 -1
  165. package/dist/scheduler/triggers/file.test.d.ts +2 -0
  166. package/dist/scheduler/triggers/file.test.d.ts.map +1 -0
  167. package/dist/scheduler/triggers/file.test.js +294 -0
  168. package/dist/scheduler/triggers/file.test.js.map +1 -0
  169. package/dist/scheduler/triggers/metric.d.ts +3 -1
  170. package/dist/scheduler/triggers/metric.d.ts.map +1 -1
  171. package/dist/scheduler/triggers/metric.js +12 -8
  172. package/dist/scheduler/triggers/metric.js.map +1 -1
  173. package/dist/scheduler/triggers/metric.test.d.ts +2 -0
  174. package/dist/scheduler/triggers/metric.test.d.ts.map +1 -0
  175. package/dist/scheduler/triggers/metric.test.js +533 -0
  176. package/dist/scheduler/triggers/metric.test.js.map +1 -0
  177. package/dist/scheduler/triggers/process-exit.d.ts.map +1 -1
  178. package/dist/scheduler/triggers/process-exit.js +2 -1
  179. package/dist/scheduler/triggers/process-exit.js.map +1 -1
  180. package/dist/scheduler/triggers/process-exit.test.d.ts +2 -0
  181. package/dist/scheduler/triggers/process-exit.test.d.ts.map +1 -0
  182. package/dist/scheduler/triggers/process-exit.test.js +118 -0
  183. package/dist/scheduler/triggers/process-exit.test.js.map +1 -0
  184. package/dist/scheduler/triggers/resource.d.ts.map +1 -1
  185. package/dist/scheduler/triggers/resource.js +2 -10
  186. package/dist/scheduler/triggers/resource.js.map +1 -1
  187. package/dist/scheduler/triggers/resource.test.d.ts +2 -0
  188. package/dist/scheduler/triggers/resource.test.d.ts.map +1 -0
  189. package/dist/scheduler/triggers/resource.test.js +225 -0
  190. package/dist/scheduler/triggers/resource.test.js.map +1 -0
  191. package/dist/scheduler/triggers/timer.test.d.ts +2 -0
  192. package/dist/scheduler/triggers/timer.test.d.ts.map +1 -0
  193. package/dist/scheduler/triggers/timer.test.js +56 -0
  194. package/dist/scheduler/triggers/timer.test.js.map +1 -0
  195. package/dist/scheduler/triggers/types.d.ts +4 -2
  196. package/dist/scheduler/triggers/types.d.ts.map +1 -1
  197. package/dist/scheduler/triggers/types.js +0 -1
  198. package/dist/scheduler/triggers/types.js.map +1 -1
  199. package/dist/skills/executor.d.ts.map +1 -1
  200. package/dist/skills/executor.js +13 -15
  201. package/dist/skills/executor.js.map +1 -1
  202. package/dist/store/database.d.ts +5 -0
  203. package/dist/store/database.d.ts.map +1 -1
  204. package/dist/store/database.js +17 -1
  205. package/dist/store/database.js.map +1 -1
  206. package/dist/store/migrations.test.d.ts +2 -0
  207. package/dist/store/migrations.test.d.ts.map +1 -0
  208. package/dist/store/migrations.test.js +278 -0
  209. package/dist/store/migrations.test.js.map +1 -0
  210. package/dist/store/session-store-edge.test.d.ts +2 -0
  211. package/dist/store/session-store-edge.test.d.ts.map +1 -0
  212. package/dist/store/session-store-edge.test.js +522 -0
  213. package/dist/store/session-store-edge.test.js.map +1 -0
  214. package/dist/store/session-store.d.ts +7 -0
  215. package/dist/store/session-store.d.ts.map +1 -1
  216. package/dist/store/session-store.js +27 -22
  217. package/dist/store/session-store.js.map +1 -1
  218. package/dist/store/session-store.test.d.ts +2 -0
  219. package/dist/store/session-store.test.d.ts.map +1 -0
  220. package/dist/store/session-store.test.js +125 -0
  221. package/dist/store/session-store.test.js.map +1 -0
  222. package/dist/subagent/executor.d.ts +21 -0
  223. package/dist/subagent/executor.d.ts.map +1 -0
  224. package/dist/subagent/executor.js +136 -0
  225. package/dist/subagent/executor.js.map +1 -0
  226. package/dist/subagent/manager.d.ts +20 -0
  227. package/dist/subagent/manager.d.ts.map +1 -0
  228. package/dist/subagent/manager.js +98 -0
  229. package/dist/subagent/manager.js.map +1 -0
  230. package/dist/subagent/scoped-memory.d.ts +28 -0
  231. package/dist/subagent/scoped-memory.d.ts.map +1 -0
  232. package/dist/subagent/scoped-memory.js +122 -0
  233. package/dist/subagent/scoped-memory.js.map +1 -0
  234. package/dist/subagent/types.d.ts +27 -0
  235. package/dist/subagent/types.d.ts.map +1 -0
  236. package/dist/subagent/types.js +2 -0
  237. package/dist/subagent/types.js.map +1 -0
  238. package/dist/tools/file-ops.d.ts.map +1 -1
  239. package/dist/tools/file-ops.js +23 -13
  240. package/dist/tools/file-ops.js.map +1 -1
  241. package/dist/tools/file-ops.test.d.ts +2 -0
  242. package/dist/tools/file-ops.test.d.ts.map +1 -0
  243. package/dist/tools/file-ops.test.js +656 -0
  244. package/dist/tools/file-ops.test.js.map +1 -0
  245. package/dist/tools/memory-tools.test.d.ts +2 -0
  246. package/dist/tools/memory-tools.test.d.ts.map +1 -0
  247. package/dist/tools/memory-tools.test.js +273 -0
  248. package/dist/tools/memory-tools.test.js.map +1 -0
  249. package/dist/tools/subagent.d.ts +10 -0
  250. package/dist/tools/subagent.d.ts.map +1 -0
  251. package/dist/tools/subagent.js +164 -0
  252. package/dist/tools/subagent.js.map +1 -0
  253. package/dist/tools/task-output.d.ts.map +1 -1
  254. package/dist/tools/task-output.js +13 -2
  255. package/dist/tools/task-output.js.map +1 -1
  256. package/dist/ui/components/input-bar.d.ts +1 -1
  257. package/dist/ui/components/input-bar.d.ts.map +1 -1
  258. package/dist/ui/components/input-bar.js +3 -3
  259. package/dist/ui/components/input-bar.js.map +1 -1
  260. package/dist/ui/components/input-bar.test.d.ts +2 -0
  261. package/dist/ui/components/input-bar.test.d.ts.map +1 -0
  262. package/dist/ui/components/input-bar.test.js +380 -0
  263. package/dist/ui/components/input-bar.test.js.map +1 -0
  264. package/dist/ui/components/key-hint-rule.d.ts +2 -1
  265. package/dist/ui/components/key-hint-rule.d.ts.map +1 -1
  266. package/dist/ui/components/key-hint-rule.js +3 -3
  267. package/dist/ui/components/key-hint-rule.js.map +1 -1
  268. package/dist/ui/components/status-bar.d.ts +1 -1
  269. package/dist/ui/components/status-bar.d.ts.map +1 -1
  270. package/dist/ui/components/status-bar.js +11 -10
  271. package/dist/ui/components/status-bar.js.map +1 -1
  272. package/dist/ui/components/status-bar.test.d.ts +2 -0
  273. package/dist/ui/components/status-bar.test.d.ts.map +1 -0
  274. package/dist/ui/components/status-bar.test.js +206 -0
  275. package/dist/ui/components/status-bar.test.js.map +1 -0
  276. package/dist/ui/format.d.ts +9 -0
  277. package/dist/ui/format.d.ts.map +1 -1
  278. package/dist/ui/format.js +21 -0
  279. package/dist/ui/format.js.map +1 -1
  280. package/dist/ui/format.test.d.ts +2 -0
  281. package/dist/ui/format.test.d.ts.map +1 -0
  282. package/dist/ui/format.test.js +122 -0
  283. package/dist/ui/format.test.js.map +1 -0
  284. package/dist/ui/layout.d.ts.map +1 -1
  285. package/dist/ui/layout.js +74 -12
  286. package/dist/ui/layout.js.map +1 -1
  287. package/dist/ui/markdown.test.d.ts +2 -0
  288. package/dist/ui/markdown.test.d.ts.map +1 -0
  289. package/dist/ui/markdown.test.js +133 -0
  290. package/dist/ui/markdown.test.js.map +1 -0
  291. package/dist/ui/mouse-filter.test.d.ts +2 -0
  292. package/dist/ui/mouse-filter.test.d.ts.map +1 -0
  293. package/dist/ui/mouse-filter.test.js +231 -0
  294. package/dist/ui/mouse-filter.test.js.map +1 -0
  295. package/dist/ui/overlays/metrics-overlay.test.d.ts +2 -0
  296. package/dist/ui/overlays/metrics-overlay.test.d.ts.map +1 -0
  297. package/dist/ui/overlays/metrics-overlay.test.js +248 -0
  298. package/dist/ui/overlays/metrics-overlay.test.js.map +1 -0
  299. package/dist/ui/overlays/task-overlay.test.d.ts +2 -0
  300. package/dist/ui/overlays/task-overlay.test.d.ts.map +1 -0
  301. package/dist/ui/overlays/task-overlay.test.js +238 -0
  302. package/dist/ui/overlays/task-overlay.test.js.map +1 -0
  303. package/dist/ui/panels/conversation.d.ts.map +1 -1
  304. package/dist/ui/panels/conversation.js +60 -5
  305. package/dist/ui/panels/conversation.js.map +1 -1
  306. package/dist/ui/panels/conversation.test.d.ts +2 -0
  307. package/dist/ui/panels/conversation.test.d.ts.map +1 -0
  308. package/dist/ui/panels/conversation.test.js +381 -0
  309. package/dist/ui/panels/conversation.test.js.map +1 -0
  310. package/dist/ui/panels/metrics-dashboard.test.d.ts +2 -0
  311. package/dist/ui/panels/metrics-dashboard.test.d.ts.map +1 -0
  312. package/dist/ui/panels/metrics-dashboard.test.js +191 -0
  313. package/dist/ui/panels/metrics-dashboard.test.js.map +1 -0
  314. package/dist/ui/panels/sleep-panel.test.d.ts +2 -0
  315. package/dist/ui/panels/sleep-panel.test.d.ts.map +1 -0
  316. package/dist/ui/panels/sleep-panel.test.js +376 -0
  317. package/dist/ui/panels/sleep-panel.test.js.map +1 -0
  318. package/dist/ui/panels/task-list.d.ts.map +1 -1
  319. package/dist/ui/panels/task-list.js +3 -2
  320. package/dist/ui/panels/task-list.js.map +1 -1
  321. package/dist/ui/panels/task-list.test.d.ts +2 -0
  322. package/dist/ui/panels/task-list.test.d.ts.map +1 -0
  323. package/dist/ui/panels/task-list.test.js +210 -0
  324. package/dist/ui/panels/task-list.test.js.map +1 -0
  325. package/dist/ui/theme.test.d.ts +2 -0
  326. package/dist/ui/theme.test.d.ts.map +1 -0
  327. package/dist/ui/theme.test.js +159 -0
  328. package/dist/ui/theme.test.js.map +1 -0
  329. package/dist/ui/types.d.ts +3 -0
  330. package/dist/ui/types.d.ts.map +1 -1
  331. package/package.json +6 -2
@@ -0,0 +1,248 @@
1
+ import { describe, it, expect } from "vitest";
2
+ /**
3
+ * Pure-logic tests for MetricsOverlay.
4
+ *
5
+ * We replicate the selection clamping, navigation bounds,
6
+ * empty-state handling, rendering helpers, and chart downsampling.
7
+ */
8
+ // ─── Selection Logic (from useInput + useEffect) ────────
9
+ function selectionUp(current, entriesLength) {
10
+ if (entriesLength === 0)
11
+ return current;
12
+ return Math.max(0, current - 1);
13
+ }
14
+ function selectionDown(current, entriesLength) {
15
+ if (entriesLength === 0)
16
+ return current;
17
+ return Math.min(entriesLength - 1, current + 1);
18
+ }
19
+ function clampSelection(selectedIndex, entriesLength) {
20
+ if (entriesLength === 0)
21
+ return selectedIndex;
22
+ if (selectedIndex >= entriesLength)
23
+ return entriesLength - 1;
24
+ return selectedIndex;
25
+ }
26
+ // ─── Body Height Calculation ────────────────────────────
27
+ function bodyHeight(height) {
28
+ return height - 1;
29
+ }
30
+ // ─── Chart Width Calculation ────────────────────────────
31
+ function chartWidth(width) {
32
+ return Math.max(20, width - 16);
33
+ }
34
+ // ─── Spark Width Calculation ────────────────────────────
35
+ function sparkWidth(width, nameLength) {
36
+ return Math.max(10, width - nameLength - 4);
37
+ }
38
+ // ─── Chart Downsampling Logic ───────────────────────────
39
+ function downsample(values, width) {
40
+ if (values.length <= width)
41
+ return values;
42
+ const step = values.length / width;
43
+ const sampled = [];
44
+ for (let i = 0; i < width; i++) {
45
+ sampled.push(values[Math.floor(i * step)]);
46
+ }
47
+ return sampled;
48
+ }
49
+ // ─── Trend Icons ────────────────────────────────────────
50
+ const TREND_ICONS = {
51
+ decreasing: "↓",
52
+ increasing: "↑",
53
+ plateau: "→",
54
+ unstable: "~",
55
+ insufficient_data: "?",
56
+ };
57
+ function trendIcon(trend) {
58
+ return TREND_ICONS[trend] ?? "?";
59
+ }
60
+ // ─── Metric Stats Logic ─────────────────────────────────
61
+ function metricStats(values) {
62
+ if (values.length === 0)
63
+ return { latest: null, min: null, max: null };
64
+ return {
65
+ latest: values[values.length - 1],
66
+ min: Math.min(...values),
67
+ max: Math.max(...values),
68
+ };
69
+ }
70
+ // ─── Tests ──────────────────────────────────────────────
71
+ describe("MetricsOverlay — selection navigation", () => {
72
+ it("up from 0 stays at 0", () => {
73
+ expect(selectionUp(0, 5)).toBe(0);
74
+ });
75
+ it("up decrements by 1", () => {
76
+ expect(selectionUp(3, 5)).toBe(2);
77
+ });
78
+ it("down increments by 1", () => {
79
+ expect(selectionDown(0, 5)).toBe(1);
80
+ });
81
+ it("down from last entry stays at last", () => {
82
+ expect(selectionDown(4, 5)).toBe(4);
83
+ });
84
+ it("up with empty entries returns current", () => {
85
+ expect(selectionUp(0, 0)).toBe(0);
86
+ });
87
+ it("down with empty entries returns current", () => {
88
+ expect(selectionDown(0, 0)).toBe(0);
89
+ });
90
+ it("up from 1 goes to 0", () => {
91
+ expect(selectionUp(1, 3)).toBe(0);
92
+ });
93
+ it("down with single entry stays at 0", () => {
94
+ expect(selectionDown(0, 1)).toBe(0);
95
+ });
96
+ });
97
+ describe("MetricsOverlay — selection clamping", () => {
98
+ it("clamps index when entries shrink", () => {
99
+ expect(clampSelection(5, 3)).toBe(2);
100
+ });
101
+ it("does not clamp valid index", () => {
102
+ expect(clampSelection(1, 3)).toBe(1);
103
+ });
104
+ it("clamps to 0 for single entry", () => {
105
+ expect(clampSelection(5, 1)).toBe(0);
106
+ });
107
+ it("returns current for empty entries (no clamping)", () => {
108
+ // The component only clamps when entries.length > 0
109
+ expect(clampSelection(3, 0)).toBe(3);
110
+ });
111
+ it("exact boundary: index == length clamps to length - 1", () => {
112
+ expect(clampSelection(3, 3)).toBe(2);
113
+ });
114
+ it("index 0 with entries stays at 0", () => {
115
+ expect(clampSelection(0, 5)).toBe(0);
116
+ });
117
+ });
118
+ describe("MetricsOverlay — empty metrics map", () => {
119
+ it("entries array from empty map is empty", () => {
120
+ const map = new Map();
121
+ const entries = Array.from(map.entries());
122
+ expect(entries).toEqual([]);
123
+ expect(entries.length).toBe(0);
124
+ });
125
+ it("selection navigation is no-op with empty entries", () => {
126
+ expect(selectionUp(0, 0)).toBe(0);
127
+ expect(selectionDown(0, 0)).toBe(0);
128
+ });
129
+ });
130
+ describe("MetricsOverlay — body and chart dimensions", () => {
131
+ it("body height = height - 1 (for header)", () => {
132
+ expect(bodyHeight(40)).toBe(39);
133
+ expect(bodyHeight(1)).toBe(0);
134
+ expect(bodyHeight(0)).toBe(-1);
135
+ });
136
+ it("chart width has minimum of 20", () => {
137
+ expect(chartWidth(30)).toBe(20);
138
+ expect(chartWidth(36)).toBe(20);
139
+ expect(chartWidth(37)).toBe(21);
140
+ });
141
+ it("chart width subtracts 16 for labels and padding", () => {
142
+ expect(chartWidth(100)).toBe(84);
143
+ expect(chartWidth(80)).toBe(64);
144
+ });
145
+ it("spark width has minimum of 10", () => {
146
+ expect(sparkWidth(20, 15)).toBe(10);
147
+ expect(sparkWidth(10, 5)).toBe(10);
148
+ });
149
+ it("spark width adjusts for metric name length", () => {
150
+ expect(sparkWidth(80, 5)).toBe(71);
151
+ expect(sparkWidth(80, 20)).toBe(56);
152
+ });
153
+ });
154
+ describe("MetricsOverlay — chart downsampling", () => {
155
+ it("returns values unchanged when fewer than width", () => {
156
+ const values = [1, 2, 3, 4, 5];
157
+ expect(downsample(values, 10)).toEqual(values);
158
+ });
159
+ it("returns values unchanged when exactly width", () => {
160
+ const values = [1, 2, 3, 4, 5];
161
+ expect(downsample(values, 5)).toEqual(values);
162
+ });
163
+ it("downsamples to target width", () => {
164
+ const values = Array.from({ length: 100 }, (_, i) => i);
165
+ const sampled = downsample(values, 10);
166
+ expect(sampled.length).toBe(10);
167
+ // First sample should be index 0
168
+ expect(sampled[0]).toBe(0);
169
+ // Second sample: floor(1 * 10) = 10
170
+ expect(sampled[1]).toBe(10);
171
+ });
172
+ it("preserves first value", () => {
173
+ const values = [42, 1, 2, 3, 4, 5, 6, 7, 8, 9];
174
+ const sampled = downsample(values, 3);
175
+ expect(sampled[0]).toBe(42);
176
+ });
177
+ it("handles single-value arrays", () => {
178
+ expect(downsample([5], 1)).toEqual([5]);
179
+ });
180
+ it("handles empty arrays", () => {
181
+ expect(downsample([], 10)).toEqual([]);
182
+ });
183
+ });
184
+ describe("MetricsOverlay — trend icons", () => {
185
+ it("maps known trends to icons", () => {
186
+ expect(trendIcon("decreasing")).toBe("↓");
187
+ expect(trendIcon("increasing")).toBe("↑");
188
+ expect(trendIcon("plateau")).toBe("→");
189
+ expect(trendIcon("unstable")).toBe("~");
190
+ expect(trendIcon("insufficient_data")).toBe("?");
191
+ });
192
+ it("returns ? for unknown trends", () => {
193
+ expect(trendIcon("unknown_trend")).toBe("?");
194
+ expect(trendIcon("")).toBe("?");
195
+ });
196
+ });
197
+ describe("MetricsOverlay — metric stats computation", () => {
198
+ it("computes latest, min, max for normal values", () => {
199
+ const stats = metricStats([3, 1, 4, 1, 5, 9, 2, 6]);
200
+ expect(stats.latest).toBe(6);
201
+ expect(stats.min).toBe(1);
202
+ expect(stats.max).toBe(9);
203
+ });
204
+ it("returns null for empty array", () => {
205
+ const stats = metricStats([]);
206
+ expect(stats.latest).toBeNull();
207
+ expect(stats.min).toBeNull();
208
+ expect(stats.max).toBeNull();
209
+ });
210
+ it("handles single value", () => {
211
+ const stats = metricStats([42]);
212
+ expect(stats.latest).toBe(42);
213
+ expect(stats.min).toBe(42);
214
+ expect(stats.max).toBe(42);
215
+ });
216
+ it("handles negative values", () => {
217
+ const stats = metricStats([-5, -2, -8, -1]);
218
+ expect(stats.latest).toBe(-1);
219
+ expect(stats.min).toBe(-8);
220
+ expect(stats.max).toBe(-1);
221
+ });
222
+ it("handles identical values", () => {
223
+ const stats = metricStats([7, 7, 7]);
224
+ expect(stats.min).toBe(7);
225
+ expect(stats.max).toBe(7);
226
+ });
227
+ });
228
+ describe("MetricsOverlay — entries ordering from Map", () => {
229
+ it("preserves insertion order", () => {
230
+ const map = new Map();
231
+ map.set("loss", [0.5, 0.4]);
232
+ map.set("accuracy", [0.8, 0.9]);
233
+ map.set("lr", [0.001]);
234
+ const entries = Array.from(map.entries());
235
+ expect(entries.map(([name]) => name)).toEqual(["loss", "accuracy", "lr"]);
236
+ });
237
+ it("selected entry determined by index into entries array", () => {
238
+ const map = new Map();
239
+ map.set("loss", [0.5]);
240
+ map.set("accuracy", [0.9]);
241
+ const entries = Array.from(map.entries());
242
+ const selectedIndex = 1;
243
+ const [name, values] = entries[selectedIndex];
244
+ expect(name).toBe("accuracy");
245
+ expect(values).toEqual([0.9]);
246
+ });
247
+ });
248
+ //# sourceMappingURL=metrics-overlay.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics-overlay.test.js","sourceRoot":"","sources":["../../../src/ui/overlays/metrics-overlay.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=task-overlay.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-overlay.test.d.ts","sourceRoot":"","sources":["../../../src/ui/overlays/task-overlay.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,238 @@
1
+ import { describe, it, expect } from "vitest";
2
+ // ─── Selection Logic ────────────────────────────────────
3
+ function selectionUp(current, tasksLength) {
4
+ if (tasksLength === 0)
5
+ return current;
6
+ return Math.max(0, current - 1);
7
+ }
8
+ function selectionDown(current, tasksLength) {
9
+ if (tasksLength === 0)
10
+ return current;
11
+ return Math.min(tasksLength - 1, current + 1);
12
+ }
13
+ function clampSelection(selectedIndex, tasksLength) {
14
+ if (tasksLength === 0)
15
+ return selectedIndex;
16
+ if (selectedIndex >= tasksLength)
17
+ return tasksLength - 1;
18
+ return selectedIndex;
19
+ }
20
+ function selectedTask(tasks, selectedIndex) {
21
+ return tasks[selectedIndex] ?? null;
22
+ }
23
+ // ─── Layout Calculations ────────────────────────────────
24
+ function listWidth(width) {
25
+ return Math.min(35, Math.floor(width * 0.3));
26
+ }
27
+ function outputWidth(width) {
28
+ const lw = listWidth(width);
29
+ return width - lw - 1; // 1 for separator
30
+ }
31
+ function bodyHeight(height) {
32
+ return height - 2; // header + hint
33
+ }
34
+ // ─── Task Status Logic ──────────────────────────────────
35
+ function taskIcon(status) {
36
+ // G.dot for running, G.dotDim for completed, G.active for failed
37
+ if (status === "running")
38
+ return "◆";
39
+ if (status === "completed")
40
+ return "◇";
41
+ return "▸";
42
+ }
43
+ function taskColor(status) {
44
+ if (status === "running")
45
+ return "yellow"; // C.primary
46
+ if (status === "completed")
47
+ return "green"; // C.success
48
+ return "red"; // C.error
49
+ }
50
+ // ─── Output Fetching Error Pattern ──────────────────────
51
+ function formatError(err) {
52
+ return err instanceof Error ? err.message : String(err);
53
+ }
54
+ function getOutputState(selected, hasExecutor, hasLogPath) {
55
+ if (!selected || !hasExecutor)
56
+ return "empty";
57
+ if (!hasLogPath)
58
+ return "no_log";
59
+ return "fetch";
60
+ }
61
+ // ─── Tests ──────────────────────────────────────────────
62
+ describe("TaskOverlay — selection navigation", () => {
63
+ it("up from 0 stays at 0", () => {
64
+ expect(selectionUp(0, 5)).toBe(0);
65
+ });
66
+ it("up decrements by 1", () => {
67
+ expect(selectionUp(3, 5)).toBe(2);
68
+ });
69
+ it("down increments by 1", () => {
70
+ expect(selectionDown(0, 5)).toBe(1);
71
+ });
72
+ it("down from last entry stays at last", () => {
73
+ expect(selectionDown(4, 5)).toBe(4);
74
+ });
75
+ it("up with empty tasks returns current", () => {
76
+ expect(selectionUp(0, 0)).toBe(0);
77
+ });
78
+ it("down with empty tasks returns current", () => {
79
+ expect(selectionDown(0, 0)).toBe(0);
80
+ });
81
+ it("up from index 1 goes to 0", () => {
82
+ expect(selectionUp(1, 10)).toBe(0);
83
+ });
84
+ it("down with single task stays at 0", () => {
85
+ expect(selectionDown(0, 1)).toBe(0);
86
+ });
87
+ it("sequential down traversal", () => {
88
+ let idx = 0;
89
+ idx = selectionDown(idx, 3); // 1
90
+ idx = selectionDown(idx, 3); // 2
91
+ idx = selectionDown(idx, 3); // 2 (clamped)
92
+ expect(idx).toBe(2);
93
+ });
94
+ it("sequential up traversal", () => {
95
+ let idx = 4;
96
+ idx = selectionUp(idx, 5); // 3
97
+ idx = selectionUp(idx, 5); // 2
98
+ idx = selectionUp(idx, 5); // 1
99
+ idx = selectionUp(idx, 5); // 0
100
+ idx = selectionUp(idx, 5); // 0 (clamped)
101
+ expect(idx).toBe(0);
102
+ });
103
+ });
104
+ describe("TaskOverlay — selection clamping", () => {
105
+ it("clamps index when tasks shrink", () => {
106
+ expect(clampSelection(5, 3)).toBe(2);
107
+ });
108
+ it("does not clamp valid index", () => {
109
+ expect(clampSelection(1, 3)).toBe(1);
110
+ });
111
+ it("clamps to 0 for single task", () => {
112
+ expect(clampSelection(5, 1)).toBe(0);
113
+ });
114
+ it("returns current for empty tasks (no clamping)", () => {
115
+ expect(clampSelection(3, 0)).toBe(3);
116
+ });
117
+ it("exact boundary: index == length clamps to length - 1", () => {
118
+ expect(clampSelection(3, 3)).toBe(2);
119
+ });
120
+ it("index 0 with tasks stays at 0", () => {
121
+ expect(clampSelection(0, 5)).toBe(0);
122
+ });
123
+ it("index 0 with single task stays at 0", () => {
124
+ expect(clampSelection(0, 1)).toBe(0);
125
+ });
126
+ });
127
+ describe("TaskOverlay — selected task lookup", () => {
128
+ const tasks = [
129
+ { id: "t1", name: "train", status: "running", machineId: "gpu-0", pid: 1234, startedAt: 1000 },
130
+ { id: "t2", name: "eval", status: "completed", machineId: "gpu-1", pid: 5678, startedAt: 2000 },
131
+ { id: "t3", name: "preprocess", status: "failed", machineId: "cpu-0", startedAt: 3000 },
132
+ ];
133
+ it("returns task at valid index", () => {
134
+ expect(selectedTask(tasks, 0)?.id).toBe("t1");
135
+ expect(selectedTask(tasks, 1)?.id).toBe("t2");
136
+ expect(selectedTask(tasks, 2)?.id).toBe("t3");
137
+ });
138
+ it("returns null for out-of-bounds index", () => {
139
+ expect(selectedTask(tasks, 5)).toBeNull();
140
+ expect(selectedTask(tasks, -1)).toBeNull();
141
+ });
142
+ it("returns null for empty task list", () => {
143
+ expect(selectedTask([], 0)).toBeNull();
144
+ });
145
+ });
146
+ describe("TaskOverlay — empty task list handling", () => {
147
+ it("empty tasks array has length 0", () => {
148
+ const tasks = [];
149
+ expect(tasks.length).toBe(0);
150
+ });
151
+ it("selected is null with empty tasks", () => {
152
+ expect(selectedTask([], 0)).toBeNull();
153
+ });
154
+ it("navigation is no-op with empty tasks", () => {
155
+ expect(selectionUp(0, 0)).toBe(0);
156
+ expect(selectionDown(0, 0)).toBe(0);
157
+ });
158
+ });
159
+ describe("TaskOverlay — layout calculations", () => {
160
+ it("list width is 30% of total, max 35", () => {
161
+ expect(listWidth(100)).toBe(30);
162
+ expect(listWidth(120)).toBe(35); // min(35, 36) = 35
163
+ expect(listWidth(200)).toBe(35); // capped at 35
164
+ });
165
+ it("list width for narrow terminals", () => {
166
+ expect(listWidth(50)).toBe(15);
167
+ expect(listWidth(30)).toBe(9);
168
+ });
169
+ it("output width fills remaining space minus separator", () => {
170
+ // width=100: listWidth=30, output=100-30-1=69
171
+ expect(outputWidth(100)).toBe(69);
172
+ });
173
+ it("output width with wide terminal", () => {
174
+ // width=200: listWidth=35, output=200-35-1=164
175
+ expect(outputWidth(200)).toBe(164);
176
+ });
177
+ it("body height subtracts 2 for header and hint", () => {
178
+ expect(bodyHeight(40)).toBe(38);
179
+ expect(bodyHeight(10)).toBe(8);
180
+ expect(bodyHeight(2)).toBe(0);
181
+ });
182
+ it("body height can go negative for very small heights", () => {
183
+ expect(bodyHeight(1)).toBe(-1);
184
+ expect(bodyHeight(0)).toBe(-2);
185
+ });
186
+ });
187
+ describe("TaskOverlay — task status icons and colors", () => {
188
+ it("running task gets filled dot and primary color", () => {
189
+ expect(taskIcon("running")).toBe("◆");
190
+ expect(taskColor("running")).toBe("yellow");
191
+ });
192
+ it("completed task gets hollow dot and success color", () => {
193
+ expect(taskIcon("completed")).toBe("◇");
194
+ expect(taskColor("completed")).toBe("green");
195
+ });
196
+ it("failed task gets arrow and error color", () => {
197
+ expect(taskIcon("failed")).toBe("▸");
198
+ expect(taskColor("failed")).toBe("red");
199
+ });
200
+ });
201
+ describe("TaskOverlay — output fetching error handling", () => {
202
+ it("returns empty when no selected task", () => {
203
+ expect(getOutputState(null, true, true)).toBe("empty");
204
+ });
205
+ it("returns empty when no executor", () => {
206
+ const task = { id: "t1", name: "x", status: "running", machineId: "m1", startedAt: 0 };
207
+ expect(getOutputState(task, false, true)).toBe("empty");
208
+ });
209
+ it("returns no_log when process has no log path", () => {
210
+ const task = { id: "t1", name: "x", status: "running", machineId: "m1", startedAt: 0 };
211
+ expect(getOutputState(task, true, false)).toBe("no_log");
212
+ });
213
+ it("returns fetch when all conditions met", () => {
214
+ const task = { id: "t1", name: "x", status: "running", machineId: "m1", startedAt: 0 };
215
+ expect(getOutputState(task, true, true)).toBe("fetch");
216
+ });
217
+ it("formatError extracts Error message", () => {
218
+ expect(formatError(new Error("ssh timeout"))).toBe("ssh timeout");
219
+ });
220
+ it("formatError converts non-Error to string", () => {
221
+ expect(formatError("connection refused")).toBe("connection refused");
222
+ expect(formatError(42)).toBe("42");
223
+ expect(formatError(null)).toBe("null");
224
+ });
225
+ });
226
+ describe("TaskOverlay — separator height", () => {
227
+ it("separator string has bodyHeight lines", () => {
228
+ const h = bodyHeight(10); // 8
229
+ const separatorLines = Array.from({ length: h }, () => "│").join("\n");
230
+ expect(separatorLines.split("\n").length).toBe(8);
231
+ });
232
+ it("empty separator for 0 body height", () => {
233
+ const h = bodyHeight(2); // 0
234
+ const lines = Array.from({ length: h }, () => "│");
235
+ expect(lines.length).toBe(0);
236
+ });
237
+ });
238
+ //# sourceMappingURL=task-overlay.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-overlay.test.js","sourceRoot":"","sources":["../../../src/ui/overlays/task-overlay.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../../src/ui/panels/conversation.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAY,MAAM,aAAa,CAAC;AAKrD,eAAO,MAAM,WAAW;aAAqD,OAAO;EAyClF,CAAC;AAaH,wBAAgB,gBAAgB,4CAS/B"}
1
+ {"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../../src/ui/panels/conversation.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAY,MAAM,aAAa,CAAC;AAKrD,eAAO,MAAM,WAAW;aAAqD,OAAO;EAyClF,CAAC;AA2CH,wBAAgB,gBAAgB,4CAS/B"}
@@ -1,5 +1,5 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import { useState, useEffect, memo, useMemo } from "react";
2
+ import { useState, useEffect, useRef, memo, useMemo } from "react";
3
3
  import { Box, Text } from "ink";
4
4
  import { C, G, METRIC_COLORS, nameHash } from "../theme.js";
5
5
  import { renderMarkdown } from "../markdown.js";
@@ -23,8 +23,35 @@ export const MessageLine = memo(function MessageLine({ message }) {
23
23
  return (_jsx(Box, { paddingLeft: 2, children: _jsx(Text, { wrap: "wrap", children: content }) }));
24
24
  }
25
25
  });
26
+ const MD_THROTTLE_MS = 150;
26
27
  function AssistantMessage({ content }) {
27
- const rendered = useMemo(() => renderMarkdown(content), [content]);
28
+ const [rendered, setRendered] = useState(() => renderMarkdown(content));
29
+ const timerRef = useRef(null);
30
+ const lastRenderTimeRef = useRef(Date.now());
31
+ const lastContentRef = useRef(content);
32
+ useEffect(() => {
33
+ // Skip if content hasn't changed (avoids double-render on mount)
34
+ if (lastContentRef.current === content && rendered)
35
+ return;
36
+ lastContentRef.current = content;
37
+ if (timerRef.current)
38
+ clearTimeout(timerRef.current);
39
+ const elapsed = Date.now() - lastRenderTimeRef.current;
40
+ if (elapsed >= MD_THROTTLE_MS) {
41
+ lastRenderTimeRef.current = Date.now();
42
+ setRendered(renderMarkdown(content));
43
+ }
44
+ else {
45
+ timerRef.current = setTimeout(() => {
46
+ lastRenderTimeRef.current = Date.now();
47
+ setRendered(renderMarkdown(content));
48
+ }, MD_THROTTLE_MS - elapsed);
49
+ }
50
+ return () => {
51
+ if (timerRef.current)
52
+ clearTimeout(timerRef.current);
53
+ };
54
+ }, [content]);
28
55
  return (_jsx(Box, { paddingLeft: 2, children: _jsx(Text, { wrap: "wrap", children: rendered }) }));
29
56
  }
30
57
  const PULSE_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
@@ -50,6 +77,12 @@ function ToolCallBlock({ tool }) {
50
77
  return _jsx(FileSyncDisplay, { tool: tool });
51
78
  case "sleep":
52
79
  return _jsx(SleepDisplay, { tool: tool });
80
+ case "subagent":
81
+ return _jsx(SubagentDisplay, { tool: tool });
82
+ case "subagent_status":
83
+ return _jsx(SubagentStatusDisplay, { tool: tool });
84
+ case "subagent_result":
85
+ return _jsx(SubagentResultDisplay, { tool: tool });
53
86
  case "start_monitor":
54
87
  return _jsx(MonitorDisplay, { tool: tool });
55
88
  case "stop_monitor":
@@ -84,7 +117,7 @@ function parseResult(tool) {
84
117
  function ExecDisplay({ tool }) {
85
118
  const machine = tool.args.machine_id ?? "?";
86
119
  const command = tool.args.command ?? "";
87
- const result = parseResult(tool);
120
+ const result = useMemo(() => parseResult(tool), [tool.result]);
88
121
  const stdout = result?.stdout;
89
122
  const stderr = result?.stderr;
90
123
  const exitCode = result?.exit_code;
@@ -127,7 +160,7 @@ function SleepDisplay({ tool }) {
127
160
  function TaskOutputDisplay({ tool }) {
128
161
  const machine = tool.args.machine_id ?? "?";
129
162
  const pid = tool.args.pid;
130
- const result = parseResult(tool);
163
+ const result = useMemo(() => parseResult(tool), [tool.result]);
131
164
  const output = result?.output;
132
165
  const running = result?.running;
133
166
  return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [_jsx(ToolHeader, { icon: "\u22B3", label: `${machine}:${pid}`, detail: running !== undefined ? (running ? "running" : "stopped") : undefined }), output ? (_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: C.dim, dimColor: true, children: "┃ " }), _jsx(Text, { color: C.text, wrap: "wrap", children: trimOutput(output, 20) })] })) : !tool.result ? (_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: C.dim, dimColor: true, children: "┃ " }), _jsx(Text, { color: C.dim, children: "fetching\u2026" })] })) : result?.error ? (_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: C.dim, dimColor: true, children: "┃ " }), _jsx(Text, { color: C.error, children: String(result.error) })] })) : null] }));
@@ -140,7 +173,7 @@ function ListMachinesDisplay({ tool }) {
140
173
  }
141
174
  // ── show_metrics ────────────────────────────────────────────────────
142
175
  function ShowMetricsDisplay({ tool }) {
143
- const result = parseResult(tool);
176
+ const result = useMemo(() => parseResult(tool), [tool.result]);
144
177
  const metrics = (result?.metrics ?? []);
145
178
  const trendIcon = (trend) => {
146
179
  switch (trend) {
@@ -200,6 +233,28 @@ function CompareRunsDisplay({ tool }) {
200
233
  return (_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: C.dim, dimColor: true, children: "┃ " }), _jsx(Text, { color: color, children: c.metric.padEnd(12) }), _jsx(Text, { color: C.dim, children: fmtVal(c.baseline?.latest ?? null) }), _jsx(Text, { color: C.dim, children: " → " }), _jsx(Text, { color: dirColor(c.direction), children: fmtVal(c.experiment?.latest ?? null) }), c.delta !== null ? (_jsxs(Text, { color: dirColor(c.direction), children: [" ", dirIcon(c.direction), " ", c.delta > 0 ? "+" : "", fmtVal(c.delta)] })) : null] }, c.metric));
201
234
  })) : !tool.result ? (_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: C.dim, dimColor: true, children: "┃ " }), _jsx(Text, { color: C.dim, children: "comparing\u2026" })] })) : result?.error ? (_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: C.dim, dimColor: true, children: "┃ " }), _jsx(Text, { color: C.error, children: String(result.error) })] })) : null] }));
202
235
  }
236
+ // ── subagent ─────────────────────────────────────────────────────────
237
+ function SubagentDisplay({ tool }) {
238
+ const task = tool.args.task ?? "";
239
+ const model = tool.args.model ?? "";
240
+ const result = parseResult(tool);
241
+ const id = result?.id;
242
+ return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [_jsx(ToolHeader, { icon: "\u2295", label: "subagent", detail: model ? `${model}` : undefined }), _jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: C.dim, dimColor: true, children: "┃ " }), _jsx(Text, { color: C.text, wrap: "wrap", children: truncate(task, 80) })] }), id && (_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: C.dim, dimColor: true, children: "┃ " }), _jsx(Text, { color: C.dim, children: "id " }), _jsx(Text, { color: C.text, children: id })] })), tool.isError && tool.result && (_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: C.dim, dimColor: true, children: "┃ " }), _jsx(Text, { color: C.error, children: truncate(tool.result, 80) })] }))] }));
243
+ }
244
+ function SubagentStatusDisplay({ tool }) {
245
+ const result = parseResult(tool);
246
+ const subagents = (result?.subagents ?? (result ? [result] : []));
247
+ return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [_jsx(ToolHeader, { icon: "\u2295", label: "subagent_status" }), subagents.length > 0 ? subagents.map((sa) => (_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: C.dim, dimColor: true, children: "┃ " }), _jsx(Text, { color: sa.status === "running" ? C.primary : sa.status === "completed" ? C.success : C.error, children: sa.id }), _jsxs(Text, { color: C.dim, children: [" ", sa.status, " ", sa.elapsed, " $", sa.cost_usd?.toFixed(4) ?? "0"] })] }, sa.id))) : (_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: C.dim, dimColor: true, children: "┃ " }), _jsx(Text, { color: C.dim, children: "no subagents" })] }))] }));
248
+ }
249
+ function SubagentResultDisplay({ tool }) {
250
+ const id = tool.args.id ?? "?";
251
+ const result = parseResult(tool);
252
+ const status = result?.status;
253
+ const text = result?.result;
254
+ const elapsed = result?.elapsed;
255
+ const costUsd = result?.cost_usd;
256
+ return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [_jsx(ToolHeader, { icon: "\u2295", label: `subagent_result ${id}`, detail: status === "running" ? "still running…" : elapsed ? `${elapsed}` : undefined }), text ? (_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: C.dim, dimColor: true, children: "┃ " }), _jsx(Text, { color: C.text, wrap: "wrap", children: trimOutput(text, 20) })] })) : !tool.result ? (_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: C.dim, dimColor: true, children: "┃ " }), _jsx(Text, { color: C.dim, children: "fetching\u2026" })] })) : null, costUsd !== undefined && (_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: C.dim, dimColor: true, children: "┃ " }), _jsxs(Text, { color: C.dim, children: ["$", costUsd.toFixed(4)] })] }))] }));
257
+ }
203
258
  // ── Generic fallback ─────────────────────────────────────────────────
204
259
  function GenericToolDisplay({ tool }) {
205
260
  const argStr = Object.entries(tool.args)