@aragon-agent/cli 0.6.47 → 0.6.48

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 (775) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +27 -6
  3. package/dist/agent/coalesce.d.ts +1 -1
  4. package/dist/agent/controller.d.ts +21 -21
  5. package/dist/agent/controller.js +13 -13
  6. package/dist/agent/conversation-reset.d.ts +1 -1
  7. package/dist/agent/governance-wiring.d.ts +6 -6
  8. package/dist/agent/governance-wiring.js +4 -4
  9. package/dist/agent/headless.d.ts +5 -5
  10. package/dist/agent/headless.js +4 -4
  11. package/dist/agent/job-notices.d.ts +1 -1
  12. package/dist/agent/job-notices.js +1 -1
  13. package/dist/agent/kit-apply.d.ts +3 -3
  14. package/dist/agent/kit-apply.js +3 -3
  15. package/dist/agent/model-profile-settings.d.ts +2 -2
  16. package/dist/agent/model-profile-settings.js +6 -6
  17. package/dist/agent/prompt-options.d.ts +1 -1
  18. package/dist/agent/system-prompt.d.ts +1 -1
  19. package/dist/{ui → app}/App.d.ts +9 -9
  20. package/dist/{ui → app}/App.js +79 -79
  21. package/dist/{ui → app}/use-startup-notices.d.ts +1 -1
  22. package/dist/{ui → app}/use-startup-notices.js +2 -2
  23. package/dist/cli.js +29 -29
  24. package/dist/commands/agents-command.js +1 -1
  25. package/dist/commands/builtins.js +9 -9
  26. package/dist/commands/governance-command.js +2 -2
  27. package/dist/commands/governance-runs-table.js +1 -1
  28. package/dist/commands/kit-command.js +1 -1
  29. package/dist/commands/perf.d.ts +3 -3
  30. package/dist/commands/perf.js +1 -1
  31. package/dist/commands/registry.d.ts +8 -8
  32. package/dist/commands/skills.js +2 -2
  33. package/dist/commands/team-command.js +1 -1
  34. package/dist/compaction/archive.d.ts +1 -1
  35. package/dist/compaction/archive.js +1 -1
  36. package/dist/compaction/child.d.ts +3 -3
  37. package/dist/compaction/child.js +4 -4
  38. package/dist/compaction/command.js +5 -5
  39. package/dist/compaction/compactor.d.ts +6 -6
  40. package/dist/compaction/compactor.js +6 -6
  41. package/dist/compaction/context-command.js +1 -1
  42. package/dist/compaction/digest.js +1 -1
  43. package/dist/compaction/memory-input.js +1 -1
  44. package/dist/compaction/memory-validation.js +1 -1
  45. package/dist/compaction/memory.js +1 -1
  46. package/dist/compaction/summarize-call.js +1 -1
  47. package/dist/compaction/summary-prompt.js +1 -1
  48. package/dist/compaction/tail-budget.js +2 -2
  49. package/dist/compaction/wiring.d.ts +5 -5
  50. package/dist/compaction/wiring.js +2 -2
  51. package/dist/config/cli-commands.d.ts +1 -1
  52. package/dist/config/cli-commands.js +3 -3
  53. package/dist/config/env.d.ts +1 -1
  54. package/dist/config/env.js +4 -4
  55. package/dist/config/load.d.ts +2 -2
  56. package/dist/config/load.js +6 -6
  57. package/dist/config/migrate-state-out-of-config.js +1 -1
  58. package/dist/config/model-profile-store.d.ts +4 -54
  59. package/dist/config/model-profile-store.js +3 -3
  60. package/dist/config/prompt-history.js +1 -1
  61. package/dist/config/store.d.ts +2 -2
  62. package/dist/config/store.js +3 -3
  63. package/dist/diagnostics/doctor.js +4 -4
  64. package/dist/exec/bridge/decisions.d.ts +1 -1
  65. package/dist/exec/bridge/human-answers.d.ts +1 -1
  66. package/dist/exec/bridge/human-answers.js +1 -1
  67. package/dist/exec/bridge/protocol.d.ts +1 -1
  68. package/dist/exec/bridge/server.js +1 -1
  69. package/dist/exec/events.d.ts +2 -2
  70. package/dist/exec/index.d.ts +1 -1
  71. package/dist/exec/index.js +1 -1
  72. package/dist/exec/permission.js +1 -1
  73. package/dist/exec/runner.d.ts +3 -3
  74. package/dist/exec/runner.js +2 -2
  75. package/dist/exec/threads/runtime.js +1 -1
  76. package/dist/fast/digest.d.ts +1 -1
  77. package/dist/fast/digest.js +1 -1
  78. package/dist/fast/frames.d.ts +1 -1
  79. package/dist/fast/frames.js +1 -1
  80. package/dist/fast/prompt.d.ts +1 -1
  81. package/dist/fast/prompt.js +1 -1
  82. package/dist/fast/review-call.js +1 -1
  83. package/dist/fast/reviewer.d.ts +3 -3
  84. package/dist/fast/reviewer.js +1 -1
  85. package/dist/fast/summary-call.d.ts +3 -3
  86. package/dist/fast/summary-call.js +2 -2
  87. package/dist/fast/summary-prompt.d.ts +1 -1
  88. package/dist/fast/summary-prompt.js +1 -1
  89. package/dist/fast/wiring.d.ts +4 -4
  90. package/dist/fast/wiring.js +1 -1
  91. package/dist/governance/card-normalize.d.ts +1 -1
  92. package/dist/governance/copy.d.ts +1 -1
  93. package/dist/governance/copy.js +2 -2
  94. package/dist/governance/engine/artifact-persist.d.ts +1 -1
  95. package/dist/governance/engine/budget.d.ts +1 -1
  96. package/dist/governance/engine/budget.js +1 -1
  97. package/dist/governance/engine/cards.d.ts +1 -1
  98. package/dist/governance/engine/digest.js +1 -1
  99. package/dist/governance/engine/engine-core.d.ts +5 -5
  100. package/dist/governance/engine/engine-core.js +1 -1
  101. package/dist/governance/engine/engine.d.ts +1 -1
  102. package/dist/governance/engine/engine.js +1 -1
  103. package/dist/governance/engine/estimate.d.ts +1 -1
  104. package/dist/governance/engine/estimate.js +1 -1
  105. package/dist/governance/engine/inspector.js +2 -2
  106. package/dist/governance/engine/integration.d.ts +1 -1
  107. package/dist/governance/engine/lifecycle.d.ts +1 -1
  108. package/dist/governance/engine/lifecycle.js +1 -1
  109. package/dist/governance/engine/phase-members.d.ts +1 -1
  110. package/dist/governance/engine/phase-planner.d.ts +1 -1
  111. package/dist/governance/engine/phase-planner.js +1 -1
  112. package/dist/governance/engine/rebuild.d.ts +1 -1
  113. package/dist/governance/engine/report.d.ts +1 -1
  114. package/dist/governance/engine/report.js +1 -1
  115. package/dist/governance/engine/run-digest.d.ts +1 -1
  116. package/dist/governance/engine/spec-builder.d.ts +2 -2
  117. package/dist/governance/engine/spec-builder.js +2 -2
  118. package/dist/governance/engine/stage-runner.js +2 -2
  119. package/dist/governance/engine/start-input.d.ts +1 -1
  120. package/dist/governance/engine/start-input.js +1 -1
  121. package/dist/governance/engine/state-machine.d.ts +1 -1
  122. package/dist/governance/engine/state-machine.js +3 -3
  123. package/dist/governance/engine/submission-queries.d.ts +1 -1
  124. package/dist/governance/engine/submission-render.d.ts +1 -1
  125. package/dist/governance/engine/submissions.d.ts +1 -1
  126. package/dist/governance/engine/submissions.js +1 -1
  127. package/dist/governance/engine/visibility.d.ts +1 -1
  128. package/dist/governance/engine/visibility.js +1 -1
  129. package/dist/governance/posture-transition.d.ts +2 -2
  130. package/dist/governance/posture-transition.js +1 -1
  131. package/dist/governance/profiles.d.ts +1 -1
  132. package/dist/governance/prompts/orchestrator-block.d.ts +2 -2
  133. package/dist/governance/prompts/orchestrator-block.js +2 -2
  134. package/dist/governance/prompts/plan-addendum.d.ts +1 -1
  135. package/dist/governance/prompts/plan-addendum.js +1 -1
  136. package/dist/governance/prompts/role-blocks.d.ts +2 -2
  137. package/dist/governance/prompts/role-blocks.js +1 -1
  138. package/dist/governance/report-reader.js +1 -1
  139. package/dist/governance/roles.d.ts +1 -1
  140. package/dist/governance/rules/claims.d.ts +1 -1
  141. package/dist/governance/rules/claims.js +1 -1
  142. package/dist/governance/rules/elimination.d.ts +1 -1
  143. package/dist/governance/rules/elimination.js +1 -1
  144. package/dist/governance/rules/failure-analysis.d.ts +2 -2
  145. package/dist/governance/rules/failure-analysis.js +1 -1
  146. package/dist/governance/rules/ledger-replay.d.ts +1 -1
  147. package/dist/governance/rules/ledger-state.d.ts +1 -1
  148. package/dist/governance/rules/ledger-state.js +1 -1
  149. package/dist/governance/rules/ledger.d.ts +2 -2
  150. package/dist/governance/rules/ledger.js +2 -2
  151. package/dist/governance/rules/rca.d.ts +1 -1
  152. package/dist/governance/rules/rca.js +1 -1
  153. package/dist/governance/rules/settlement.d.ts +2 -2
  154. package/dist/governance/rules/settlement.js +2 -2
  155. package/dist/governance/rules/suspicion.d.ts +1 -1
  156. package/dist/governance/store/artifacts.d.ts +1 -1
  157. package/dist/governance/store/artifacts.js +1 -1
  158. package/dist/governance/store/events-log.d.ts +1 -1
  159. package/dist/governance/store/run-store.d.ts +1 -1
  160. package/dist/governance/store/run-store.js +1 -1
  161. package/dist/governance/store/scoreboard-export.d.ts +1 -1
  162. package/dist/governance/tools/child-tools.d.ts +1 -1
  163. package/dist/governance/tools/guard.d.ts +1 -1
  164. package/dist/governance/tools/guard.js +1 -1
  165. package/dist/governance/tools/inspector-tools.d.ts +2 -2
  166. package/dist/governance/tools/inspector-tools.js +2 -2
  167. package/dist/governance/tools/lead-tools.d.ts +1 -1
  168. package/dist/governance/tools/lead-tools.js +1 -1
  169. package/dist/governance/tools/normalize-util.js +1 -1
  170. package/dist/governance/tools/scan-incomplete.js +1 -1
  171. package/dist/governance/tools/shared-tools.d.ts +1 -1
  172. package/dist/governance/tools/shared-tools.js +1 -1
  173. package/dist/governance/tools/submit-normalizers.d.ts +1 -1
  174. package/dist/governance/tools/submit-normalizers.js +1 -1
  175. package/dist/governance/tools/submit-tools.d.ts +1 -1
  176. package/dist/logging/cli-commands.js +1 -1
  177. package/dist/logging/install.d.ts +1 -1
  178. package/dist/logging/install.js +3 -3
  179. package/dist/logging/logger.d.ts +2 -2
  180. package/dist/logging/logger.js +31 -2
  181. package/dist/proc/kill-tree.js +1 -1
  182. package/dist/proc/log-mirror.js +1 -1
  183. package/dist/proc/log-ring.js +2 -2
  184. package/dist/proc/notices.d.ts +1 -1
  185. package/dist/proc/notices.js +2 -2
  186. package/dist/proc/prompt.d.ts +1 -1
  187. package/dist/proc/prompt.js +1 -1
  188. package/dist/proc/readiness.js +1 -1
  189. package/dist/proc/supervisor.d.ts +1 -1
  190. package/dist/proc/supervisor.js +3 -3
  191. package/dist/proc/wait.d.ts +1 -1
  192. package/dist/proc/wait.js +1 -1
  193. package/dist/prompts/command.d.ts +1 -1
  194. package/dist/prompts/command.js +1 -1
  195. package/dist/prompts/normalize.d.ts +1 -1
  196. package/dist/prompts/normalize.js +1 -1
  197. package/dist/prompts/resolve.d.ts +1 -1
  198. package/dist/prompts/store.d.ts +2 -12
  199. package/dist/prompts/store.js +6 -60
  200. package/dist/session/checkpoint.d.ts +2 -2
  201. package/dist/session/clear-archive.d.ts +2 -2
  202. package/dist/session/persist.d.ts +2 -2
  203. package/dist/session/persist.js +1 -1
  204. package/dist/session/startup-session.d.ts +2 -2
  205. package/dist/session/validate-session.js +3 -3
  206. package/dist/skills/service.d.ts +2 -2
  207. package/dist/team/bus.d.ts +1 -1
  208. package/dist/team/bus.js +1 -1
  209. package/dist/team/child-reviewer.d.ts +2 -2
  210. package/dist/team/child-reviewer.js +1 -1
  211. package/dist/team/comm-tools.js +1 -1
  212. package/dist/team/dispatch-gate.js +1 -1
  213. package/dist/team/forced-dispatch.js +1 -1
  214. package/dist/team/forced-mode.d.ts +1 -1
  215. package/dist/team/forced-prompt.d.ts +1 -1
  216. package/dist/team/forced-prompt.js +1 -1
  217. package/dist/team/normalize.d.ts +1 -1
  218. package/dist/team/normalize.js +1 -1
  219. package/dist/team/overseer.d.ts +3 -3
  220. package/dist/team/overseer.js +3 -3
  221. package/dist/team/prompt.d.ts +1 -1
  222. package/dist/team/prompt.js +1 -1
  223. package/dist/team/report.d.ts +2 -2
  224. package/dist/team/report.js +2 -2
  225. package/dist/team/retry.d.ts +1 -1
  226. package/dist/team/retry.js +1 -1
  227. package/dist/team/runtime.d.ts +4 -4
  228. package/dist/team/runtime.js +2 -2
  229. package/dist/team/settled.d.ts +2 -2
  230. package/dist/team/subagent.d.ts +4 -4
  231. package/dist/team/subagent.js +5 -5
  232. package/dist/team/task-tool.d.ts +1 -1
  233. package/dist/team/task-tool.js +1 -1
  234. package/dist/todo/normalize.d.ts +1 -1
  235. package/dist/todo/normalize.js +1 -1
  236. package/dist/todo/prompt.d.ts +1 -1
  237. package/dist/todo/prompt.js +1 -1
  238. package/dist/todo/store.d.ts +1 -1
  239. package/dist/todo/store.js +2 -2
  240. package/dist/todo/todo-tool.d.ts +1 -1
  241. package/dist/todo/todo-tool.js +2 -2
  242. package/dist/tools/bash-tool.d.ts +1 -1
  243. package/dist/tools/bash-tool.js +2 -2
  244. package/dist/tools/diff.d.ts +1 -1
  245. package/dist/tools/diff.js +1 -1
  246. package/dist/tools/file-change-store.d.ts +1 -1
  247. package/dist/tools/fs-tools.d.ts +2 -2
  248. package/dist/tools/fs-tools.js +1 -1
  249. package/dist/tools/index.d.ts +3 -3
  250. package/dist/tools/index.js +1 -1
  251. package/dist/tools/job-tools.d.ts +2 -2
  252. package/dist/tools/job-tools.js +4 -4
  253. package/dist/tools/plan-tools.d.ts +1 -1
  254. package/dist/tools/plan-tools.js +1 -1
  255. package/dist/tools/proc-tools.d.ts +1 -1
  256. package/dist/tools/proc-tools.js +2 -2
  257. package/dist/update/classify-failure.d.ts +1 -1
  258. package/dist/update/install-lock.js +1 -1
  259. package/dist/update/install-source.d.ts +1 -1
  260. package/dist/update/installer.js +2 -2
  261. package/dist/update/npm-view.d.ts +1 -1
  262. package/dist/update/npm-view.js +1 -1
  263. package/dist/update/registry.d.ts +1 -1
  264. package/dist/update/registry.js +1 -1
  265. package/dist/update/service.d.ts +2 -2
  266. package/dist/update/service.js +2 -2
  267. package/package.json +7 -7
  268. package/dist/agent/agent-mode.d.ts +0 -108
  269. package/dist/agent/agent-mode.js +0 -121
  270. package/dist/agent/entry-limits.d.ts +0 -65
  271. package/dist/agent/entry-limits.js +0 -133
  272. package/dist/agent/governance-view.d.ts +0 -40
  273. package/dist/agent/governance-view.js +0 -40
  274. package/dist/agent/invocation-card.d.ts +0 -77
  275. package/dist/agent/invocation-card.js +0 -169
  276. package/dist/agent/model-probe-limits.d.ts +0 -39
  277. package/dist/agent/model-probe-limits.js +0 -25
  278. package/dist/agent/model-probe.d.ts +0 -104
  279. package/dist/agent/model-probe.js +0 -391
  280. package/dist/agent/queued-messages.d.ts +0 -23
  281. package/dist/agent/queued-messages.js +0 -49
  282. package/dist/agent/reducer.d.ts +0 -857
  283. package/dist/agent/reducer.js +0 -1646
  284. package/dist/agent/retry-view.d.ts +0 -68
  285. package/dist/agent/retry-view.js +0 -95
  286. package/dist/agent/usage.d.ts +0 -47
  287. package/dist/agent/usage.js +0 -84
  288. package/dist/compaction/limits.d.ts +0 -61
  289. package/dist/compaction/limits.js +0 -64
  290. package/dist/compaction/meter.d.ts +0 -276
  291. package/dist/compaction/meter.js +0 -512
  292. package/dist/compaction/pressure.d.ts +0 -163
  293. package/dist/compaction/pressure.js +0 -199
  294. package/dist/compaction/reason-text.d.ts +0 -36
  295. package/dist/compaction/reason-text.js +0 -78
  296. package/dist/compaction/types.d.ts +0 -360
  297. package/dist/compaction/types.js +0 -22
  298. package/dist/config/governance-config.d.ts +0 -125
  299. package/dist/config/governance-config.js +0 -164
  300. package/dist/config/kit-resolution.d.ts +0 -93
  301. package/dist/config/kit-resolution.js +0 -226
  302. package/dist/config/model-kits.d.ts +0 -225
  303. package/dist/config/model-kits.js +0 -302
  304. package/dist/config/model-profile-resolution.d.ts +0 -28
  305. package/dist/config/model-profile-resolution.js +0 -88
  306. package/dist/config/model-profiles.d.ts +0 -103
  307. package/dist/config/model-profiles.js +0 -188
  308. package/dist/config/schema.d.ts +0 -1645
  309. package/dist/config/schema.js +0 -1383
  310. package/dist/fast/limits.d.ts +0 -152
  311. package/dist/fast/limits.js +0 -152
  312. package/dist/fast/resolve.d.ts +0 -44
  313. package/dist/fast/resolve.js +0 -107
  314. package/dist/fast/task-summary-types.d.ts +0 -66
  315. package/dist/fast/task-summary-types.js +0 -17
  316. package/dist/fast/types.d.ts +0 -141
  317. package/dist/fast/types.js +0 -14
  318. package/dist/governance/governed-dispatch.d.ts +0 -19
  319. package/dist/governance/governed-dispatch.js +0 -17
  320. package/dist/governance/limits.d.ts +0 -107
  321. package/dist/governance/limits.js +0 -131
  322. package/dist/governance/posture.d.ts +0 -35
  323. package/dist/governance/posture.js +0 -59
  324. package/dist/governance/rules/events-table.d.ts +0 -46
  325. package/dist/governance/rules/events-table.js +0 -94
  326. package/dist/governance/types.d.ts +0 -661
  327. package/dist/governance/types.js +0 -27
  328. package/dist/input/control-sequences.d.ts +0 -71
  329. package/dist/input/control-sequences.js +0 -222
  330. package/dist/input/csiu-keys.d.ts +0 -66
  331. package/dist/input/csiu-keys.js +0 -181
  332. package/dist/input/cursor-report.d.ts +0 -61
  333. package/dist/input/cursor-report.js +0 -97
  334. package/dist/input/enter-sequences.d.ts +0 -64
  335. package/dist/input/enter-sequences.js +0 -153
  336. package/dist/input/interrupt-gesture.d.ts +0 -18
  337. package/dist/input/interrupt-gesture.js +0 -19
  338. package/dist/input/keyboard-enhancement.d.ts +0 -42
  339. package/dist/input/keyboard-enhancement.js +0 -65
  340. package/dist/input/keymap.d.ts +0 -20
  341. package/dist/input/keymap.js +0 -99
  342. package/dist/input/limits.d.ts +0 -118
  343. package/dist/input/limits.js +0 -109
  344. package/dist/input/mouse-events.d.ts +0 -87
  345. package/dist/input/mouse-events.js +0 -212
  346. package/dist/input/paste-parse.d.ts +0 -83
  347. package/dist/input/paste-parse.js +0 -107
  348. package/dist/input/pointer-router.d.ts +0 -11
  349. package/dist/input/pointer-router.js +0 -14
  350. package/dist/input/stdin-filter.d.ts +0 -112
  351. package/dist/input/stdin-filter.js +0 -657
  352. package/dist/input/win32-input-mode.d.ts +0 -61
  353. package/dist/input/win32-input-mode.js +0 -269
  354. package/dist/logging/levels.d.ts +0 -40
  355. package/dist/logging/levels.js +0 -59
  356. package/dist/logging/secret-registry.d.ts +0 -32
  357. package/dist/logging/secret-registry.js +0 -73
  358. package/dist/proc/limits.d.ts +0 -214
  359. package/dist/proc/limits.js +0 -221
  360. package/dist/proc/progress.d.ts +0 -47
  361. package/dist/proc/progress.js +0 -163
  362. package/dist/proc/types.d.ts +0 -269
  363. package/dist/proc/types.js +0 -15
  364. package/dist/proc/wait-view.d.ts +0 -65
  365. package/dist/proc/wait-view.js +0 -66
  366. package/dist/prompts/copy.d.ts +0 -89
  367. package/dist/prompts/copy.js +0 -131
  368. package/dist/prompts/fill.d.ts +0 -51
  369. package/dist/prompts/fill.js +0 -46
  370. package/dist/prompts/limits.d.ts +0 -53
  371. package/dist/prompts/limits.js +0 -53
  372. package/dist/prompts/search.d.ts +0 -58
  373. package/dist/prompts/search.js +0 -146
  374. package/dist/prompts/types.d.ts +0 -204
  375. package/dist/prompts/types.js +0 -13
  376. package/dist/team/activity.d.ts +0 -67
  377. package/dist/team/activity.js +0 -130
  378. package/dist/team/limits.d.ts +0 -308
  379. package/dist/team/limits.js +0 -330
  380. package/dist/team/model.d.ts +0 -104
  381. package/dist/team/model.js +0 -150
  382. package/dist/team/panel-rows.d.ts +0 -48
  383. package/dist/team/panel-rows.js +0 -75
  384. package/dist/team/timeline.d.ts +0 -74
  385. package/dist/team/timeline.js +0 -101
  386. package/dist/team/types.d.ts +0 -354
  387. package/dist/team/types.js +0 -13
  388. package/dist/todo/acceptance.d.ts +0 -120
  389. package/dist/todo/acceptance.js +0 -399
  390. package/dist/todo/follow-through.d.ts +0 -138
  391. package/dist/todo/follow-through.js +0 -248
  392. package/dist/todo/limits.d.ts +0 -152
  393. package/dist/todo/limits.js +0 -152
  394. package/dist/todo/panel-rows.d.ts +0 -45
  395. package/dist/todo/panel-rows.js +0 -70
  396. package/dist/todo/plan-sections.d.ts +0 -60
  397. package/dist/todo/plan-sections.js +0 -108
  398. package/dist/todo/row-window.d.ts +0 -51
  399. package/dist/todo/row-window.js +0 -107
  400. package/dist/todo/types.d.ts +0 -124
  401. package/dist/todo/types.js +0 -12
  402. package/dist/tools/human-input.d.ts +0 -212
  403. package/dist/tools/human-input.js +0 -265
  404. package/dist/tools/patch.d.ts +0 -115
  405. package/dist/tools/patch.js +0 -453
  406. package/dist/tools/terminal-output.d.ts +0 -115
  407. package/dist/tools/terminal-output.js +0 -201
  408. package/dist/tools/tool-output-store.d.ts +0 -68
  409. package/dist/tools/tool-output-store.js +0 -102
  410. package/dist/ui/ActivityLine.d.ts +0 -26
  411. package/dist/ui/ActivityLine.js +0 -51
  412. package/dist/ui/AutocompletePopup.d.ts +0 -23
  413. package/dist/ui/AutocompletePopup.js +0 -17
  414. package/dist/ui/BottomStatusRow.d.ts +0 -10
  415. package/dist/ui/BottomStatusRow.js +0 -9
  416. package/dist/ui/Composer.d.ts +0 -115
  417. package/dist/ui/Composer.js +0 -64
  418. package/dist/ui/GovernancePanel.d.ts +0 -50
  419. package/dist/ui/GovernancePanel.js +0 -77
  420. package/dist/ui/GovernanceRun.d.ts +0 -18
  421. package/dist/ui/GovernanceRun.js +0 -11
  422. package/dist/ui/Header.d.ts +0 -48
  423. package/dist/ui/Header.js +0 -27
  424. package/dist/ui/Logo.d.ts +0 -32
  425. package/dist/ui/Logo.js +0 -65
  426. package/dist/ui/Markdown.d.ts +0 -29
  427. package/dist/ui/Markdown.js +0 -116
  428. package/dist/ui/PromptCaret.d.ts +0 -11
  429. package/dist/ui/PromptCaret.js +0 -31
  430. package/dist/ui/PromptInput.d.ts +0 -192
  431. package/dist/ui/PromptInput.js +0 -689
  432. package/dist/ui/QueuePanel.d.ts +0 -8
  433. package/dist/ui/QueuePanel.js +0 -9
  434. package/dist/ui/QueueStatusRow.d.ts +0 -24
  435. package/dist/ui/QueueStatusRow.js +0 -51
  436. package/dist/ui/ScrollbackShell.d.ts +0 -132
  437. package/dist/ui/ScrollbackShell.js +0 -26
  438. package/dist/ui/SessionOpener.d.ts +0 -30
  439. package/dist/ui/SessionOpener.js +0 -12
  440. package/dist/ui/StatusBar.d.ts +0 -130
  441. package/dist/ui/StatusBar.js +0 -61
  442. package/dist/ui/TeamPanel.d.ts +0 -76
  443. package/dist/ui/TeamPanel.js +0 -103
  444. package/dist/ui/ToastStack.d.ts +0 -8
  445. package/dist/ui/ToastStack.js +0 -31
  446. package/dist/ui/TodoPanel.d.ts +0 -52
  447. package/dist/ui/TodoPanel.js +0 -214
  448. package/dist/ui/Transcript.d.ts +0 -105
  449. package/dist/ui/Transcript.js +0 -258
  450. package/dist/ui/UpdateLine.d.ts +0 -44
  451. package/dist/ui/UpdateLine.js +0 -129
  452. package/dist/ui/activity-phrases.d.ts +0 -33
  453. package/dist/ui/activity-phrases.js +0 -50
  454. package/dist/ui/capabilities.d.ts +0 -27
  455. package/dist/ui/capabilities.js +0 -65
  456. package/dist/ui/clipboard-task.d.ts +0 -29
  457. package/dist/ui/clipboard-task.js +0 -66
  458. package/dist/ui/clipboard.d.ts +0 -31
  459. package/dist/ui/clipboard.js +0 -157
  460. package/dist/ui/color-runtime.d.ts +0 -5
  461. package/dist/ui/color-runtime.js +0 -29
  462. package/dist/ui/composer-input.d.ts +0 -38
  463. package/dist/ui/composer-input.js +0 -79
  464. package/dist/ui/composer-limits.d.ts +0 -30
  465. package/dist/ui/composer-limits.js +0 -45
  466. package/dist/ui/composer-rows.d.ts +0 -73
  467. package/dist/ui/composer-rows.js +0 -129
  468. package/dist/ui/console-bridge.d.ts +0 -19
  469. package/dist/ui/console-bridge.js +0 -52
  470. package/dist/ui/density.d.ts +0 -26
  471. package/dist/ui/density.js +0 -34
  472. package/dist/ui/display-tab.d.ts +0 -51
  473. package/dist/ui/display-tab.js +0 -82
  474. package/dist/ui/editor-navigation.d.ts +0 -55
  475. package/dist/ui/editor-navigation.js +0 -96
  476. package/dist/ui/editor-reducer.d.ts +0 -108
  477. package/dist/ui/editor-reducer.js +0 -187
  478. package/dist/ui/enter-frames.d.ts +0 -47
  479. package/dist/ui/enter-frames.js +0 -117
  480. package/dist/ui/entries/AssistantEntry.d.ts +0 -37
  481. package/dist/ui/entries/AssistantEntry.js +0 -43
  482. package/dist/ui/entries/CompactionCard.d.ts +0 -88
  483. package/dist/ui/entries/CompactionCard.js +0 -222
  484. package/dist/ui/entries/DiffView.d.ts +0 -61
  485. package/dist/ui/entries/DiffView.js +0 -103
  486. package/dist/ui/entries/EntryFrame.d.ts +0 -23
  487. package/dist/ui/entries/EntryFrame.js +0 -6
  488. package/dist/ui/entries/FastCard.d.ts +0 -53
  489. package/dist/ui/entries/FastCard.js +0 -105
  490. package/dist/ui/entries/GovernanceCard.d.ts +0 -30
  491. package/dist/ui/entries/GovernanceCard.js +0 -32
  492. package/dist/ui/entries/InvocationCard.d.ts +0 -20
  493. package/dist/ui/entries/InvocationCard.js +0 -32
  494. package/dist/ui/entries/QueuedEntry.d.ts +0 -27
  495. package/dist/ui/entries/QueuedEntry.js +0 -33
  496. package/dist/ui/entries/RetryCard.d.ts +0 -56
  497. package/dist/ui/entries/RetryCard.js +0 -60
  498. package/dist/ui/entries/ServiceCard.d.ts +0 -82
  499. package/dist/ui/entries/ServiceCard.js +0 -151
  500. package/dist/ui/entries/TeamCard.d.ts +0 -55
  501. package/dist/ui/entries/TeamCard.js +0 -94
  502. package/dist/ui/entries/TodoCard.d.ts +0 -54
  503. package/dist/ui/entries/TodoCard.js +0 -98
  504. package/dist/ui/entries/ToolCard.d.ts +0 -81
  505. package/dist/ui/entries/ToolCard.js +0 -169
  506. package/dist/ui/entries/ToolPreview.d.ts +0 -59
  507. package/dist/ui/entries/ToolPreview.js +0 -208
  508. package/dist/ui/entries/UserEntry.d.ts +0 -36
  509. package/dist/ui/entries/UserEntry.js +0 -57
  510. package/dist/ui/entries/WaitCard.d.ts +0 -37
  511. package/dist/ui/entries/WaitCard.js +0 -92
  512. package/dist/ui/exit-snapshot.d.ts +0 -43
  513. package/dist/ui/exit-snapshot.js +0 -26
  514. package/dist/ui/frame-differ.d.ts +0 -166
  515. package/dist/ui/frame-differ.js +0 -354
  516. package/dist/ui/frame-observer.d.ts +0 -17
  517. package/dist/ui/frame-observer.js +0 -94
  518. package/dist/ui/frame-parser.d.ts +0 -7
  519. package/dist/ui/frame-parser.js +0 -18
  520. package/dist/ui/gauge.d.ts +0 -39
  521. package/dist/ui/gauge.js +0 -55
  522. package/dist/ui/glyphs.d.ts +0 -178
  523. package/dist/ui/glyphs.js +0 -165
  524. package/dist/ui/governance-copy.d.ts +0 -69
  525. package/dist/ui/governance-copy.js +0 -104
  526. package/dist/ui/governance-status.d.ts +0 -145
  527. package/dist/ui/governance-status.js +0 -326
  528. package/dist/ui/gradient.d.ts +0 -11
  529. package/dist/ui/gradient.js +0 -50
  530. package/dist/ui/ink-runtime.d.ts +0 -4
  531. package/dist/ui/ink-runtime.js +0 -51
  532. package/dist/ui/interaction-copy.d.ts +0 -84
  533. package/dist/ui/interaction-copy.js +0 -41
  534. package/dist/ui/interaction-hints.d.ts +0 -44
  535. package/dist/ui/interaction-hints.js +0 -97
  536. package/dist/ui/layout/AppShell.d.ts +0 -17
  537. package/dist/ui/layout/AppShell.js +0 -6
  538. package/dist/ui/layout/Gutter.d.ts +0 -67
  539. package/dist/ui/layout/Gutter.js +0 -42
  540. package/dist/ui/layout/MeasuredEntry.d.ts +0 -21
  541. package/dist/ui/layout/MeasuredEntry.js +0 -30
  542. package/dist/ui/layout/OverlayFrame.d.ts +0 -73
  543. package/dist/ui/layout/OverlayFrame.js +0 -110
  544. package/dist/ui/layout/ScrollIndicator.d.ts +0 -32
  545. package/dist/ui/layout/ScrollIndicator.js +0 -22
  546. package/dist/ui/layout/ScrollViewport.d.ts +0 -40
  547. package/dist/ui/layout/ScrollViewport.js +0 -222
  548. package/dist/ui/layout/agents-board.d.ts +0 -102
  549. package/dist/ui/layout/agents-board.js +0 -389
  550. package/dist/ui/layout/autocomplete.d.ts +0 -18
  551. package/dist/ui/layout/autocomplete.js +0 -26
  552. package/dist/ui/layout/budget.d.ts +0 -43
  553. package/dist/ui/layout/budget.js +0 -52
  554. package/dist/ui/layout/cells.d.ts +0 -61
  555. package/dist/ui/layout/cells.js +0 -156
  556. package/dist/ui/layout/document-layout.d.ts +0 -13
  557. package/dist/ui/layout/document-layout.js +0 -10
  558. package/dist/ui/layout/follow-state.d.ts +0 -138
  559. package/dist/ui/layout/follow-state.js +0 -148
  560. package/dist/ui/layout/frame.d.ts +0 -36
  561. package/dist/ui/layout/frame.js +0 -39
  562. package/dist/ui/layout/governance-board.d.ts +0 -121
  563. package/dist/ui/layout/governance-board.js +0 -382
  564. package/dist/ui/layout/governance-card.d.ts +0 -53
  565. package/dist/ui/layout/governance-card.js +0 -134
  566. package/dist/ui/layout/governance-chip.d.ts +0 -38
  567. package/dist/ui/layout/governance-chip.js +0 -64
  568. package/dist/ui/layout/governance-panel.d.ts +0 -85
  569. package/dist/ui/layout/governance-panel.js +0 -358
  570. package/dist/ui/layout/governance-roster.d.ts +0 -65
  571. package/dist/ui/layout/governance-roster.js +0 -275
  572. package/dist/ui/layout/header-hints.d.ts +0 -90
  573. package/dist/ui/layout/header-hints.js +0 -110
  574. package/dist/ui/layout/header-left.d.ts +0 -115
  575. package/dist/ui/layout/header-left.js +0 -206
  576. package/dist/ui/layout/invocation-card.d.ts +0 -41
  577. package/dist/ui/layout/invocation-card.js +0 -91
  578. package/dist/ui/layout/kit-chip.d.ts +0 -50
  579. package/dist/ui/layout/kit-chip.js +0 -81
  580. package/dist/ui/layout/overlay-window.d.ts +0 -43
  581. package/dist/ui/layout/overlay-window.js +0 -51
  582. package/dist/ui/layout/queue-layout.d.ts +0 -19
  583. package/dist/ui/layout/queue-layout.js +0 -26
  584. package/dist/ui/layout/rail.d.ts +0 -7
  585. package/dist/ui/layout/rail.js +0 -16
  586. package/dist/ui/layout/scroll-indicator.d.ts +0 -27
  587. package/dist/ui/layout/scroll-indicator.js +0 -36
  588. package/dist/ui/layout/scroll.d.ts +0 -37
  589. package/dist/ui/layout/scroll.js +0 -99
  590. package/dist/ui/layout/scrollbar-geometry.d.ts +0 -17
  591. package/dist/ui/layout/scrollbar-geometry.js +0 -13
  592. package/dist/ui/layout/session-chip.d.ts +0 -52
  593. package/dist/ui/layout/session-chip.js +0 -64
  594. package/dist/ui/layout/shortcut-panel-layout.d.ts +0 -191
  595. package/dist/ui/layout/shortcut-panel-layout.js +0 -321
  596. package/dist/ui/layout/status-detail-layout.d.ts +0 -9
  597. package/dist/ui/layout/status-detail-layout.js +0 -79
  598. package/dist/ui/layout/status-layout.d.ts +0 -140
  599. package/dist/ui/layout/status-layout.js +0 -262
  600. package/dist/ui/layout/task-summary-cell.d.ts +0 -61
  601. package/dist/ui/layout/task-summary-cell.js +0 -65
  602. package/dist/ui/layout/team-panel.d.ts +0 -18
  603. package/dist/ui/layout/team-panel.js +0 -25
  604. package/dist/ui/layout/team-row.d.ts +0 -139
  605. package/dist/ui/layout/team-row.js +0 -246
  606. package/dist/ui/layout/todo-layout.d.ts +0 -62
  607. package/dist/ui/layout/todo-layout.js +0 -102
  608. package/dist/ui/layout/useTerminalSize.d.ts +0 -13
  609. package/dist/ui/layout/useTerminalSize.js +0 -50
  610. package/dist/ui/layout/viewport-geometry.d.ts +0 -29
  611. package/dist/ui/layout/viewport-geometry.js +0 -28
  612. package/dist/ui/layout/virtual-window.d.ts +0 -112
  613. package/dist/ui/layout/virtual-window.js +0 -494
  614. package/dist/ui/layout/wait-bar.d.ts +0 -34
  615. package/dist/ui/layout/wait-bar.js +0 -48
  616. package/dist/ui/layout/wrap-rows.d.ts +0 -26
  617. package/dist/ui/layout/wrap-rows.js +0 -88
  618. package/dist/ui/live-clamp.d.ts +0 -14
  619. package/dist/ui/live-clamp.js +0 -46
  620. package/dist/ui/markdown-blocks.d.ts +0 -107
  621. package/dist/ui/markdown-blocks.js +0 -240
  622. package/dist/ui/overlays/AgentsBoardOverlay.d.ts +0 -57
  623. package/dist/ui/overlays/AgentsBoardOverlay.js +0 -183
  624. package/dist/ui/overlays/ConfirmDialog.d.ts +0 -31
  625. package/dist/ui/overlays/ConfirmDialog.js +0 -44
  626. package/dist/ui/overlays/GovernanceBoardOverlay.d.ts +0 -41
  627. package/dist/ui/overlays/GovernanceBoardOverlay.js +0 -76
  628. package/dist/ui/overlays/GovernanceSettingsPage.d.ts +0 -48
  629. package/dist/ui/overlays/GovernanceSettingsPage.js +0 -93
  630. package/dist/ui/overlays/HelpOverlay.d.ts +0 -29
  631. package/dist/ui/overlays/HelpOverlay.js +0 -232
  632. package/dist/ui/overlays/KitEditor.d.ts +0 -23
  633. package/dist/ui/overlays/KitEditor.js +0 -101
  634. package/dist/ui/overlays/KitManager.d.ts +0 -23
  635. package/dist/ui/overlays/KitManager.js +0 -104
  636. package/dist/ui/overlays/KitPicker.d.ts +0 -49
  637. package/dist/ui/overlays/KitPicker.js +0 -58
  638. package/dist/ui/overlays/ModelDiscoveryList.d.ts +0 -23
  639. package/dist/ui/overlays/ModelDiscoveryList.js +0 -161
  640. package/dist/ui/overlays/ModelPicker.d.ts +0 -26
  641. package/dist/ui/overlays/ModelPicker.js +0 -63
  642. package/dist/ui/overlays/ModelProfileEditor.d.ts +0 -29
  643. package/dist/ui/overlays/ModelProfileEditor.js +0 -168
  644. package/dist/ui/overlays/ModelProfilePicker.d.ts +0 -48
  645. package/dist/ui/overlays/ModelProfilePicker.js +0 -231
  646. package/dist/ui/overlays/PlanReviewOverlay.d.ts +0 -51
  647. package/dist/ui/overlays/PlanReviewOverlay.js +0 -132
  648. package/dist/ui/overlays/PromptEditorPage.d.ts +0 -48
  649. package/dist/ui/overlays/PromptEditorPage.js +0 -227
  650. package/dist/ui/overlays/PromptLibraryOverlay.d.ts +0 -37
  651. package/dist/ui/overlays/PromptLibraryOverlay.js +0 -191
  652. package/dist/ui/overlays/PromptPicker.d.ts +0 -36
  653. package/dist/ui/overlays/PromptPicker.js +0 -129
  654. package/dist/ui/overlays/PromptTextArea.d.ts +0 -86
  655. package/dist/ui/overlays/PromptTextArea.js +0 -141
  656. package/dist/ui/overlays/QuestionOverlay.d.ts +0 -39
  657. package/dist/ui/overlays/QuestionOverlay.js +0 -258
  658. package/dist/ui/overlays/QueueOverlay.d.ts +0 -22
  659. package/dist/ui/overlays/QueueOverlay.js +0 -79
  660. package/dist/ui/overlays/SettingsScreen.d.ts +0 -228
  661. package/dist/ui/overlays/SettingsScreen.js +0 -1037
  662. package/dist/ui/overlays/ShortcutPanel.d.ts +0 -39
  663. package/dist/ui/overlays/ShortcutPanel.js +0 -106
  664. package/dist/ui/overlays/TeamFanoutPage.d.ts +0 -51
  665. package/dist/ui/overlays/TeamFanoutPage.js +0 -90
  666. package/dist/ui/overlays/governance-settings.d.ts +0 -79
  667. package/dist/ui/overlays/governance-settings.js +0 -140
  668. package/dist/ui/overlays/kit-picker-state.d.ts +0 -87
  669. package/dist/ui/overlays/kit-picker-state.js +0 -136
  670. package/dist/ui/overlays/kit-settings-state.d.ts +0 -129
  671. package/dist/ui/overlays/kit-settings-state.js +0 -268
  672. package/dist/ui/overlays/model-probe-view.d.ts +0 -128
  673. package/dist/ui/overlays/model-probe-view.js +0 -225
  674. package/dist/ui/overlays/model-profile-state.d.ts +0 -177
  675. package/dist/ui/overlays/model-profile-state.js +0 -301
  676. package/dist/ui/overlays/prompt-library-state.d.ts +0 -162
  677. package/dist/ui/overlays/prompt-library-state.js +0 -335
  678. package/dist/ui/overlays/prompt-picker-state.d.ts +0 -71
  679. package/dist/ui/overlays/prompt-picker-state.js +0 -98
  680. package/dist/ui/overlays/team-fanout-state.d.ts +0 -106
  681. package/dist/ui/overlays/team-fanout-state.js +0 -155
  682. package/dist/ui/overlays/use-model-probe.d.ts +0 -30
  683. package/dist/ui/overlays/use-model-probe.js +0 -109
  684. package/dist/ui/palettes.d.ts +0 -112
  685. package/dist/ui/palettes.js +0 -130
  686. package/dist/ui/paste-frames.d.ts +0 -91
  687. package/dist/ui/paste-frames.js +0 -117
  688. package/dist/ui/paste-tokens.d.ts +0 -90
  689. package/dist/ui/paste-tokens.js +0 -147
  690. package/dist/ui/render-cache.d.ts +0 -81
  691. package/dist/ui/render-cache.js +0 -196
  692. package/dist/ui/render-governor.d.ts +0 -54
  693. package/dist/ui/render-governor.js +0 -78
  694. package/dist/ui/run-metrics.d.ts +0 -23
  695. package/dist/ui/run-metrics.js +0 -27
  696. package/dist/ui/run-status-row.d.ts +0 -63
  697. package/dist/ui/run-status-row.js +0 -54
  698. package/dist/ui/screen.d.ts +0 -107
  699. package/dist/ui/screen.js +0 -234
  700. package/dist/ui/scrollback/LiveTail.d.ts +0 -115
  701. package/dist/ui/scrollback/LiveTail.js +0 -48
  702. package/dist/ui/scrollback/band.d.ts +0 -114
  703. package/dist/ui/scrollback/band.js +0 -143
  704. package/dist/ui/scrollback/budget.d.ts +0 -90
  705. package/dist/ui/scrollback/budget.js +0 -100
  706. package/dist/ui/scrollback/canvas.d.ts +0 -100
  707. package/dist/ui/scrollback/canvas.js +0 -114
  708. package/dist/ui/scrollback/cursor-probe.d.ts +0 -71
  709. package/dist/ui/scrollback/cursor-probe.js +0 -133
  710. package/dist/ui/scrollback/drain.d.ts +0 -32
  711. package/dist/ui/scrollback/drain.js +0 -64
  712. package/dist/ui/scrollback/exit-summary.d.ts +0 -23
  713. package/dist/ui/scrollback/exit-summary.js +0 -25
  714. package/dist/ui/scrollback/limits.d.ts +0 -98
  715. package/dist/ui/scrollback/limits.js +0 -98
  716. package/dist/ui/scrollback/live-preview.d.ts +0 -34
  717. package/dist/ui/scrollback/live-preview.js +0 -130
  718. package/dist/ui/scrollback/overflow-print.d.ts +0 -108
  719. package/dist/ui/scrollback/overflow-print.js +0 -120
  720. package/dist/ui/scrollback/renderer.d.ts +0 -48
  721. package/dist/ui/scrollback/renderer.js +0 -186
  722. package/dist/ui/scrollback/scrollback-writer.d.ts +0 -174
  723. package/dist/ui/scrollback/scrollback-writer.js +0 -648
  724. package/dist/ui/scrollback/settled-boundary.d.ts +0 -42
  725. package/dist/ui/scrollback/settled-boundary.js +0 -109
  726. package/dist/ui/scrollback/tail-anchor.d.ts +0 -71
  727. package/dist/ui/scrollback/tail-anchor.js +0 -79
  728. package/dist/ui/scrollback/tail-window.d.ts +0 -73
  729. package/dist/ui/scrollback/tail-window.js +0 -89
  730. package/dist/ui/scrollback/use-scrollback-view.d.ts +0 -139
  731. package/dist/ui/scrollback/use-scrollback-view.js +0 -601
  732. package/dist/ui/scrollback/writer-shapes.d.ts +0 -127
  733. package/dist/ui/scrollback/writer-shapes.js +0 -159
  734. package/dist/ui/scrollbar-controller.d.ts +0 -30
  735. package/dist/ui/scrollbar-controller.js +0 -124
  736. package/dist/ui/selection/highlight.d.ts +0 -39
  737. package/dist/ui/selection/highlight.js +0 -64
  738. package/dist/ui/selection/screen-mirror.d.ts +0 -24
  739. package/dist/ui/selection/screen-mirror.js +0 -35
  740. package/dist/ui/selection/selection-controller.d.ts +0 -104
  741. package/dist/ui/selection/selection-controller.js +0 -372
  742. package/dist/ui/selection/selection.d.ts +0 -90
  743. package/dist/ui/selection/selection.js +0 -223
  744. package/dist/ui/shortcuts.d.ts +0 -81
  745. package/dist/ui/shortcuts.js +0 -221
  746. package/dist/ui/status-feedback.d.ts +0 -35
  747. package/dist/ui/status-feedback.js +0 -38
  748. package/dist/ui/stdout-frame-writer.d.ts +0 -74
  749. package/dist/ui/stdout-frame-writer.js +0 -155
  750. package/dist/ui/team-status.d.ts +0 -166
  751. package/dist/ui/team-status.js +0 -252
  752. package/dist/ui/terminal-plan.d.ts +0 -52
  753. package/dist/ui/terminal-plan.js +0 -49
  754. package/dist/ui/theme.d.ts +0 -96
  755. package/dist/ui/theme.js +0 -130
  756. package/dist/ui/transcript-text.d.ts +0 -41
  757. package/dist/ui/transcript-text.js +0 -282
  758. package/dist/ui/use-height-store.d.ts +0 -31
  759. package/dist/ui/use-height-store.js +0 -76
  760. package/dist/ui/use-render-governor.d.ts +0 -39
  761. package/dist/ui/use-render-governor.js +0 -57
  762. package/dist/ui/use-wheel-routing.d.ts +0 -57
  763. package/dist/ui/use-wheel-routing.js +0 -152
  764. package/dist/ui/win-vt-force.d.ts +0 -136
  765. package/dist/ui/win-vt-force.js +0 -237
  766. package/dist/ui/win-vt-input.d.ts +0 -57
  767. package/dist/ui/win-vt-input.js +0 -62
  768. package/dist/update/limits.d.ts +0 -111
  769. package/dist/update/limits.js +0 -111
  770. package/dist/update/semver.d.ts +0 -89
  771. package/dist/update/semver.js +0 -295
  772. package/dist/update/types.d.ts +0 -169
  773. package/dist/update/types.js +0 -68
  774. /package/dist/{ui → app}/session-bridge.d.ts +0 -0
  775. /package/dist/{ui → app}/session-bridge.js +0 -0
@@ -1,1646 +0,0 @@
1
- /**
2
- * Pure event → view reducer (spec §3.4 / §6.2).
3
- *
4
- * `reduceEvent(event)` maps a raw core `AgentEvent` into zero or more view
5
- * actions; `viewReducer(state, action)` folds an action into the immutable
6
- * `ViewState`. Keeping this pure lets the whole UI be tested without a live LLM.
7
- *
8
- * Two load-bearing behaviors from R2 (silent-failure surfacing) live here:
9
- * - the `error` StreamEvent is rendered as an error notice, and
10
- * - `agent_end` after a turn that produced no `turn_end` and no error notice
11
- * synthesizes a generic error notice (a swallowed throw is never a no-op).
12
- */
13
- import process from 'node:process';
14
- import { reconcileSteeringReceipt } from './queued-messages.js';
15
- import { emptyContextUsage } from '../compaction/meter.js';
16
- import { addUsage, computeCost } from './usage.js';
17
- import { ENTRY_LIMITS, appendBounded, entryRetain, trimEntries } from './entry-limits.js';
18
- import { isGovernedDispatch } from '../governance/governed-dispatch.js';
19
- import { adoptPosture, foldGovernanceSnapshot } from './governance-view.js';
20
- /** Default auto-dismiss window for a toast. */
21
- export const DEFAULT_TOAST_TTL_MS = 2500;
22
- /**
23
- * The zero session total.
24
- *
25
- * ONE LITERAL FOR TWO SITES (`initialViewState` and `resetConversation`). They
26
- * used to spell the object out twice, which is how `cacheReadTokens` would have
27
- * been added to one of them and not the other.
28
- */
29
- export const EMPTY_USAGE_TOTAL = {
30
- inputTokens: 0,
31
- outputTokens: 0,
32
- cacheReadTokens: 0,
33
- cacheWriteTokens: 0,
34
- costUsd: 0,
35
- };
36
- /**
37
- * The initial view state, optionally seeded from config.
38
- *
39
- * THE DEFAULT IS `false`, NOT `true`, AND THAT IS THE WHOLE SAFETY ARGUMENT
40
- * (D-2). There is exactly one production call site — `App`'s `useReducer`, which
41
- * passes `cfg.showThinking`. Every other caller is a test. A future call site
42
- * that forgets the seed fails CLOSED, toward the product default, rather than
43
- * silently restoring the reasoning firehose for one code path with nothing
44
- * reporting it.
45
- *
46
- * The seed is a PARAMETER rather than a post-mount dispatch so that `/resume`
47
- * cannot flash a restored thinking block on frame 1.
48
- */
49
- export function initialViewState(seed = {}) {
50
- return {
51
- entries: [],
52
- pendingSteering: [],
53
- seenSteeringIds: new Set(),
54
- runPhase: 'idle',
55
- runOutcome: 'none',
56
- status: 'idle',
57
- usageTotal: EMPTY_USAGE_TOTAL,
58
- context: emptyContextUsage(),
59
- overlay: null,
60
- thinkingVisible: seed.thinkingVisible ?? false,
61
- toasts: [],
62
- expandedToolIds: {},
63
- // Seeded to the default posture; the App overrides it once on mount from
64
- // `controller.getAgentMode()` so `--plan` renders on the first frame.
65
- agentMode: 'build',
66
- pendingAgentMode: null,
67
- posture: 'build',
68
- pendingPosture: null,
69
- governance: null,
70
- team: null,
71
- todos: null,
72
- retry: null,
73
- fast: null,
74
- compaction: null,
75
- taskSummary: null,
76
- taskSummaryPending: false,
77
- seq: 0,
78
- teamEntryId: undefined,
79
- todoEntryId: undefined,
80
- retryEntryId: undefined,
81
- fastEntryId: undefined,
82
- compactionEntryId: undefined,
83
- streamingId: undefined,
84
- turnProduced: false,
85
- errorNoticed: false,
86
- aborted: false,
87
- droppedEntries: 0,
88
- };
89
- }
90
- // ---------------------------------------------------------------------------
91
- // Event → actions
92
- // ---------------------------------------------------------------------------
93
- // Stored preview caps (spec §3.7 / P1-3). Raised from 16 / 800 so a `Ctrl+O`
94
- // expansion can actually reveal lines 9…200 and the bash `[exit code N]` footer
95
- // survives ≥16-line output. The *collapsed* view still shows only 8 lines
96
- // (COLLAPSED_LINES in ToolCard).
97
- export const STORED_PREVIEW_CHARS = 8000;
98
- export const STORED_PREVIEW_LINES = 200;
99
- /**
100
- * Marker appended to a truncated tool preview. ASCII on purpose: this string is
101
- * produced outside `src/ui/**`, where no terminal capabilities are available, so
102
- * it must be safe on a legacy `cmd.exe` (spec §4.1 tier A). `ToolPreview` dims
103
- * this line and imports the constant rather than re-spelling it — the two used
104
- * to be a matched pair of `…` literals, and splitting the spelling would break
105
- * the dimming silently.
106
- */
107
- export const PREVIEW_TRUNCATION_MARK = '...';
108
- /**
109
- * Flatten a ToolResult into a stored preview string. Truncation appends
110
- * `PREVIEW_TRUNCATION_MARK` (line- or char-based) so a card footer never
111
- * over-promises how much more is expandable.
112
- */
113
- export function buildToolPreview(result) {
114
- const text = result.content
115
- .map((c) => (c.type === 'text' ? c.text : `[image: ${c.mediaType}]`))
116
- .join('\n');
117
- const allLines = text.split('\n');
118
- const lines = allLines.slice(0, STORED_PREVIEW_LINES);
119
- let preview = lines.join('\n');
120
- const lineTruncated = allLines.length > STORED_PREVIEW_LINES;
121
- if (preview.length > STORED_PREVIEW_CHARS) {
122
- preview = `${preview.slice(0, STORED_PREVIEW_CHARS)}${PREVIEW_TRUNCATION_MARK}`;
123
- }
124
- else if (lineTruncated) {
125
- preview = `${preview}\n${PREVIEW_TRUNCATION_MARK}`;
126
- }
127
- return preview;
128
- }
129
- /** The first line of a stored preview, without its trailing newline. */
130
- function firstLine(text) {
131
- const at = text.indexOf('\n');
132
- return at === -1 ? text : text.slice(0, at);
133
- }
134
- /**
135
- * Map one core `AgentEvent` to view actions. `cost` (the active model's cost
136
- * table) lets `turn_end` accumulate USD cost; omit it in tests that only assert
137
- * token totals. `patches` is the CLI-local diff side channel (§3.3.7); omitting
138
- * it — every existing test, and headless mode — produces byte-identical actions.
139
- */
140
- export function reduceEvent(event, cost, patches) {
141
- switch (event.type) {
142
- case 'steering_accepted':
143
- return [{ type: 'steeringAccepted', ids: event.ids }];
144
- case 'agent_start':
145
- return [{ type: 'runStart' }];
146
- case 'turn_start':
147
- return [{ type: 'turnStart' }];
148
- case 'turn_end':
149
- return [
150
- {
151
- type: 'turnEnd',
152
- usage: event.usage,
153
- costDelta: computeCost(event.usage, cost),
154
- },
155
- ];
156
- case 'agent_end':
157
- return [{ type: 'runEnd' }];
158
- case 'tool_execution_start':
159
- return [{ type: 'toolExecStart', toolCallId: event.toolCallId, toolName: event.toolName }];
160
- case 'tool_execution_end': {
161
- const patch = patches?.take(event.toolCallId);
162
- const preview = buildToolPreview(event.result);
163
- return [
164
- {
165
- type: 'toolExecEnd',
166
- toolCallId: event.toolCallId,
167
- isError: event.isError,
168
- duration: event.duration,
169
- // AN ENTRY CARRIES A PATCH OR A FULL PREVIEW, NEVER BOTH (D-19). This
170
- // is not a budget compromise, it is deleting a duplicate: `ToolCard`
171
- // renders `DiffView` INSTEAD of `ToolPreview` when a patch exists, so
172
- // the stored preview is never drawn on that card — and for `edit_file`
173
- // it is literally the same diff, as text. Keeping the first line
174
- // preserves the one thing outside `DiffView` that still reads it,
175
- // `entryRevision`'s `preview?.length` term.
176
- preview: patch ? firstLine(preview) : preview,
177
- ...(patch ? { patch } : {}),
178
- },
179
- ];
180
- }
181
- case 'message_update':
182
- return reduceStreamEvent(event.streamEvent);
183
- case 'code_execution_start':
184
- case 'code_execution_end':
185
- // CodeAct sandbox is off by default; no dedicated view surface in v1.
186
- return [];
187
- default:
188
- return [];
189
- }
190
- }
191
- /**
192
- * Tools that render their own transcript entry, so the generic tool card would
193
- * be a duplicate. `task` is the second member (F-8 A'3): every dispatch opens
194
- * a TeamCard immediately after, so the pair read as TWO dispatches - the first
195
- * failed, the second succeeded - when there was only ever one. `todo_write`'s
196
- * reasons (todo-plan-execution §3.8 / D-10) carry over unchanged.
197
- *
198
- * SUPPRESSING ONLY `tool_call_start` IS SUFFICIENT, AND THAT IS THE WHOLE REASON
199
- * THIS IS SAFE (D-10). Every later handler — `toolCallDelta` / `toolCallEnd` /
200
- * `toolExecStart` / `toolExecEnd` — resolves its target through
201
- * `findToolEntryId`, which returns `undefined` when no entry was created, and
202
- * every one of them already early-returns on that. There is no fifth handler.
203
- * Blast radius verified: headless and `exec` writers key on EVENTS, not tool
204
- * names, and `transcript-text` renders `kind:'team'` on its own.
205
- */
206
- const SELF_RENDERING_TOOLS = new Set(['todo_write', 'task']);
207
- function reduceStreamEvent(se) {
208
- switch (se.type) {
209
- case 'thinking_start':
210
- return [{ type: 'thinkingStart' }];
211
- case 'thinking_delta':
212
- return [{ type: 'thinkingDelta', delta: se.delta }];
213
- case 'text_delta':
214
- return [{ type: 'textDelta', delta: se.delta }];
215
- case 'tool_call_start':
216
- return [
217
- { type: 'toolCallStart', toolCallId: se.toolCallId, toolName: se.toolName },
218
- ];
219
- case 'tool_call_delta':
220
- return [
221
- { type: 'toolCallDelta', toolCallId: se.toolCallId, argsDelta: se.argsDelta },
222
- ];
223
- case 'tool_call_end':
224
- return [{ type: 'toolCallEnd', toolCallId: se.toolCallId, args: se.args }];
225
- case 'error':
226
- return [
227
- { type: 'runError' },
228
- {
229
- type: 'notice',
230
- level: 'error',
231
- text: formatStreamError(se.error),
232
- },
233
- ];
234
- case 'done':
235
- // Authoritative usage is redundant with `turn_end`; nothing to add.
236
- return [];
237
- // --- API retry (llm-api-retry-backoff §6.4) ---------------------------
238
- case 'retry_scheduled':
239
- return [
240
- {
241
- type: 'retryScheduled',
242
- attempt: se.attempt,
243
- maxRetries: se.maxRetries,
244
- delayMs: se.delayMs,
245
- resumeAt: se.resumeAt,
246
- errorType: se.errorType,
247
- message: se.message,
248
- },
249
- ];
250
- case 'retry_attempt':
251
- return [{ type: 'retryAttempt', attempt: se.attempt, maxRetries: se.maxRetries }];
252
- case 'stream_restart':
253
- return [{ type: 'streamRestart', discardedToolCallIds: se.discardedToolCallIds }];
254
- default:
255
- return [];
256
- }
257
- }
258
- /**
259
- * The opening words of the `context_overflow` banner.
260
- *
261
- * A SHARED CONSTANT RATHER THAN TWO LITERALS, because `compactionStart` matches
262
- * on it to rewrite the notice a successful reactive recovery would otherwise
263
- * leave behind (P1-4). Two spellings that must agree, with nothing checking that
264
- * they do, is how that rewrite silently stops matching — at which point the red
265
- * "start over" banner comes back and only on the recovery path, which is the one
266
- * place nobody re-tests.
267
- */
268
- export const CONTEXT_OVERFLOW_NOTICE_PREFIX = 'Context length exceeded';
269
- /** Turn a provider error into a friendly banner, mapping LLMError.errorType. */
270
- export function formatStreamError(error) {
271
- const errorType = error.errorType;
272
- const base = error.message || 'Unknown error';
273
- switch (errorType) {
274
- case 'auth_error':
275
- return `Authentication failed - check your API key. (${base})`;
276
- case 'rate_limit':
277
- return `Rate limited by the provider - please retry shortly. (${base})`;
278
- case 'overloaded':
279
- return `Provider overloaded - please retry shortly. (${base})`;
280
- case 'context_overflow':
281
- // `/compact` IS NAMED FIRST, and adding it is the second half of P1-4's
282
- // fix. This remedy list predates context compaction and was, until it
283
- // existed, complete: throwing the conversation away really was the only
284
- // exit. It is not any more, and a banner that offers only `/reset` now
285
- // tells the user to discard a session the very next line of code may be
286
- // about to save.
287
- return `${CONTEXT_OVERFLOW_NOTICE_PREFIX} - try /compact, or start over with /reset. (${base})`;
288
- case 'network_error':
289
- return `Network error reaching the provider. (${base})`;
290
- case 'timeout':
291
- return `The request timed out. (${base})`;
292
- default:
293
- return base;
294
- }
295
- }
296
- // ---------------------------------------------------------------------------
297
- // Reducer
298
- // ---------------------------------------------------------------------------
299
- const GENERIC_RUN_FAILURE = 'The run ended without producing a response - check your API key, model, and network.';
300
- function nextId(state) {
301
- const seq = state.seq + 1;
302
- return { id: `e${seq}`, seq };
303
- }
304
- function mapEntry(entries, id, fn) {
305
- return entries.map((e) => (e.id === id ? fn(e) : e));
306
- }
307
- /**
308
- * Seal `thinkingMs`, once, as a spreadable patch (§3.1.2).
309
- *
310
- * Returns an EMPTY object when there is nothing to seal — no reasoning happened,
311
- * or the clock has already stopped — so the three call sites can spread it
312
- * unconditionally and none of them can re-seal a settled duration.
313
- */
314
- function sealThinkingMs(entry) {
315
- if (entry.thinkingStartedAt === undefined || entry.thinkingMs !== undefined)
316
- return {};
317
- return { thinkingMs: Math.max(0, Date.now() - entry.thinkingStartedAt) };
318
- }
319
- /**
320
- * Append one entry and ring-trim the result (tui-render-performance L1).
321
- *
322
- * TRIMMING HAPPENS ONLY AT APPEND POINTS, and that is the whole reason this is a
323
- * helper rather than a line in `viewReducer`. A mid-turn trim triggered by an
324
- * UPDATE action could drop the entry `streamingId` / `teamEntryId` /
325
- * `todoEntryId` points at while it is still being written to; appending is the
326
- * only moment at which the tail is by definition the newest thing.
327
- *
328
- * I-L1-1 — the trim must never drop a live target. With `retain >= 200` and a
329
- * trim that only ever removes from the head this is structurally true, but a
330
- * future clamp change would break it SILENTLY (the `mapEntry` calls would simply
331
- * stop matching), so development builds assert it.
332
- */
333
- function appendEntry(state, entries, entry) {
334
- const trimmed = trimEntries([...entries, entry], entryRetain());
335
- if (trimmed.dropped > 0 && process.env.NODE_ENV !== 'production') {
336
- // `retryEntryId` IS PART OF THIS LIST. A fourth live card that is not in it is
337
- // a fourth card I-L1-1 does not cover — the trim would silently turn its
338
- // `mapEntry` into a no-op instead of failing loudly.
339
- const live = [
340
- state.streamingId,
341
- state.teamEntryId,
342
- state.todoEntryId,
343
- state.retryEntryId,
344
- // `fastEntryId` IS PART OF THIS LIST for the reason the note above gives:
345
- // a fifth live card left out of it is a card I-L1-1 does not cover.
346
- state.fastEntryId,
347
- // And `compactionEntryId` is the sixth. Same reason, and it is the one
348
- // whose absence would be hardest to notice: a compaction card is rare, so
349
- // a `mapEntry` that silently stopped matching would be found by a user
350
- // rather than by a test.
351
- state.compactionEntryId,
352
- ].filter((id) => typeof id === 'string');
353
- for (const id of live) {
354
- if (!trimmed.entries.some((e) => e.id === id)) {
355
- throw new Error(`trimEntries dropped a live entry (${id}) - see I-L1-1`);
356
- }
357
- }
358
- }
359
- return {
360
- entries: trimmed.entries,
361
- droppedEntries: state.droppedEntries + trimmed.dropped,
362
- };
363
- }
364
- /**
365
- * Settle the turn's retry card, if it has one (llm-api-retry-backoff §6.4).
366
- *
367
- * SETTLING IS DERIVED HERE, NOT SENT BY CORE. Core knows it scheduled a retry; it
368
- * does not know whether the turn eventually succeeded, was interrupted, or ran out
369
- * of budget — that is the consumer's own timeline.
370
- *
371
- * FOUR PATHS REACH THIS, and the fourth is what makes the set exhaustive rather
372
- * than enumerated: `turnEnd` (recovered), an error `notice` (exhausted),
373
- * `abortMark` (interrupted) and `runEnd` (interrupted). The rule is "every path
374
- * that reaches `status: 'idle'` settles the card", because a card left at
375
- * `phase: 'waiting'` pins `Transcript`'s MONOTONIC settled boundary and re-renders
376
- * the tail every frame for the rest of the session (R-10).
377
- *
378
- * Returning `retry: null` / `retryEntryId: undefined` unconditionally is
379
- * deliberate: both are already that when there is no card, so every caller can
380
- * spread the result without a branch of its own.
381
- */
382
- function settleRetryCard(state, entries, phase) {
383
- if (!state.retryEntryId)
384
- return { entries, retry: null, retryEntryId: undefined };
385
- const now = Date.now();
386
- return {
387
- entries: mapEntry(entries, state.retryEntryId, (e) => e.kind === 'retry'
388
- ? {
389
- ...e,
390
- phase,
391
- totalRetries: e.attempt,
392
- elapsedMs: Math.max(0, now - e.startedAt),
393
- resumeAt: undefined,
394
- }
395
- : e),
396
- retry: null,
397
- retryEntryId: undefined,
398
- };
399
- }
400
- function releaseLiveTails(entries) {
401
- let touched = false;
402
- const next = entries.map((e) => {
403
- // `!e.live && !e.wait`, NOT `!e.live` (extended-job-wait RV-2): a `job_wait`
404
- // never streams, so its `live` is always empty and the old guard would skip it,
405
- // leaving a wait card that says "running" in the transcript forever.
406
- if (e.kind !== 'tool' || (!e.live && !e.wait))
407
- return e;
408
- if (e.status !== 'running' && e.status !== 'pending')
409
- return e;
410
- touched = true;
411
- return {
412
- ...e,
413
- live: undefined,
414
- lastOutputAt: undefined,
415
- liveSeq: (e.liveSeq ?? 0) + 1,
416
- wait: undefined,
417
- waitSeq: (e.waitSeq ?? 0) + 1,
418
- };
419
- });
420
- return touched ? next : entries;
421
- }
422
- /**
423
- * Drop settled assistant entries that render nothing at all (defect 5).
424
- *
425
- * `turnStart` ALWAYS creates an entry, so a turn that only called a tool leaves a
426
- * bubble behind that `AssistantEntry` then refuses to draw. A rendered absence is
427
- * not the same thing as an absent entry: `virtual-window.ts:305` still estimates a
428
- * row for it (two when `aborted`, and nothing draws the second), and `/save`
429
- * exports a blank bubble. Settle is the only safe moment to prune — before it the
430
- * entry is still the target of every `textDelta` / `thinkingDelta`.
431
- *
432
- * I-L1-1 — `streamingId` COMES BACK WITH THE ARRAY. If the dropped entry is the
433
- * one that id pointed at, the id is cleared: leaving it set would point the whole
434
- * reducer at an entry no `mapEntry` can ever match again, silently, because a
435
- * `mapEntry` on a missing id is a no-op. `turnEnd` and `runEnd` clear the id
436
- * themselves; `abortMark` does not, which is why the caller writes this back.
437
- *
438
- * Returns the SAME array when nothing was dropped, so every settled entry keeps
439
- * its identity and `EntryView`'s memo boundary stays intact.
440
- */
441
- function pruneEmptyAssistant(entries, streamingId) {
442
- const isEmptySettledAssistant = (entry) => entry.kind === 'assistant' &&
443
- !entry.streaming &&
444
- entry.text.trim() === '' &&
445
- (entry.thinking ?? '').trim() === '';
446
- const kept = entries.filter((entry) => !isEmptySettledAssistant(entry));
447
- if (kept.length === entries.length)
448
- return { entries, streamingId };
449
- const liveGone = streamingId !== undefined && !kept.some((entry) => entry.id === streamingId);
450
- return { entries: kept, streamingId: liveGone ? undefined : streamingId };
451
- }
452
- function findToolEntryId(state, toolCallId) {
453
- for (let i = state.entries.length - 1; i >= 0; i -= 1) {
454
- const e = state.entries[i];
455
- if (e && e.kind === 'tool' && e.toolCallId === toolCallId)
456
- return e.id;
457
- }
458
- return undefined;
459
- }
460
- /** Runtime facts outlive transcript cards and are independent of command/clipboard notices. */
461
- function reduceRunFacts(state, action) {
462
- switch (action.type) {
463
- case 'runStarting': return { ...state, runPhase: 'starting', runOutcome: 'none' };
464
- case 'runStart': return { ...state, runPhase: 'waiting', runOutcome: 'none', activeTool: undefined };
465
- case 'turnStart': return { ...state, runPhase: 'waiting' };
466
- case 'thinkingStart':
467
- case 'thinkingDelta': return { ...state, runPhase: 'thinking' };
468
- case 'textDelta': return { ...state, runPhase: 'generating' };
469
- case 'toolCallStart':
470
- case 'toolCallDelta':
471
- case 'toolCallEnd': return { ...state, runPhase: 'preparing-tool' };
472
- case 'toolExecStart': {
473
- const card = state.entries.find((entry) => entry.kind === 'tool' && entry.toolCallId === action.toolCallId);
474
- return { ...state, runPhase: 'tool', activeTool: {
475
- toolCallId: action.toolCallId,
476
- name: action.toolName ?? (card?.kind === 'tool' ? card.name : 'tool'),
477
- } };
478
- }
479
- case 'toolExecEnd': return state.activeTool?.toolCallId === action.toolCallId
480
- ? { ...state, runPhase: 'waiting', activeTool: undefined } : state;
481
- case 'runError': return { ...state, runOutcome: 'failed' };
482
- case 'abortMark': return { ...state, runOutcome: 'interrupted' };
483
- case 'runEnd': return { ...state, runPhase: 'idle', activeTool: undefined,
484
- runOutcome: state.runOutcome !== 'none' ? state.runOutcome
485
- : !state.turnProduced && !state.errorNoticed && !state.aborted ? 'failed' : 'ended' };
486
- case 'resetConversation':
487
- case 'restoreEntries': return { ...state, runPhase: 'idle', activeTool: undefined,
488
- runOutcome: 'none', seenSteeringIds: new Set() };
489
- default: return state;
490
- }
491
- }
492
- export function viewReducer(state, action) {
493
- state = reduceRunFacts(state, action);
494
- switch (action.type) {
495
- case 'submit': {
496
- const { id, seq } = nextId(state);
497
- const entry = {
498
- id, kind: 'user', text: action.text,
499
- ...(action.invocation ? { invocation: action.invocation } : {}),
500
- };
501
- return {
502
- ...state,
503
- seq,
504
- ...appendEntry(state, state.entries, entry),
505
- streamingId: undefined,
506
- // A new turn gets its own card, in that turn's position in the
507
- // transcript. The PREVIOUS card stays exactly where it is — it is
508
- // history, and history is not retracted.
509
- todoEntryId: undefined,
510
- // Same rule for the retry card. `runStart` clears NONE of the three (it
511
- // never has), and `runEnd`'s settle rule below is what makes that safe
512
- // rather than merely lucky.
513
- retryEntryId: undefined,
514
- turnProduced: false,
515
- errorNoticed: false,
516
- aborted: false,
517
- };
518
- }
519
- case 'steerQueued': {
520
- if (state.seenSteeringIds.has(action.queueId))
521
- return state;
522
- const { id, seq } = nextId(state);
523
- const entry = {
524
- id, kind: 'queued', queueId: action.queueId, text: action.text,
525
- ...(action.invocation ? { invocation: action.invocation } : {}),
526
- };
527
- // NOTHING ELSE RESETS. This is not a new turn -- `turnProduced`,
528
- // `errorNoticed` and `aborted` describe the run that is still in
529
- // flight, and clearing them here would let the next `runEnd` failure
530
- // guard misfire on a turn that produced plenty.
531
- return {
532
- ...state, seq, ...appendEntry(state, state.entries, entry),
533
- seenSteeringIds: new Set([...state.seenSteeringIds, action.queueId]),
534
- pendingSteering: [...state.pendingSteering, {
535
- queueId: action.queueId, text: action.text,
536
- ...(action.invocation ? { invocation: action.invocation } : {}),
537
- }],
538
- };
539
- }
540
- case 'steeringAccepted': {
541
- const receipt = reconcileSteeringReceipt({
542
- entries: state.entries, pending: state.pendingSteering, ids: action.ids,
543
- });
544
- if (!receipt.changed)
545
- return state;
546
- const trimmed = trimEntries(receipt.entries, entryRetain());
547
- return { ...state, pendingSteering: receipt.pending, entries: trimmed.entries,
548
- droppedEntries: state.droppedEntries + trimmed.dropped };
549
- }
550
- case 'runStart':
551
- return {
552
- ...state,
553
- status: 'running',
554
- turnProduced: false,
555
- errorNoticed: false,
556
- aborted: false,
557
- };
558
- case 'turnStart': {
559
- const { id, seq } = nextId(state);
560
- const entry = {
561
- id,
562
- kind: 'assistant',
563
- text: '',
564
- thinking: undefined,
565
- thinkingOpen: false,
566
- streaming: true,
567
- };
568
- return { ...state, seq, ...appendEntry(state, state.entries, entry), streamingId: id };
569
- }
570
- case 'thinkingStart': {
571
- if (!state.streamingId)
572
- return state;
573
- return {
574
- ...state,
575
- entries: mapEntry(state.entries, state.streamingId, (e) => e.kind === 'assistant'
576
- ? {
577
- ...e,
578
- thinking: e.thinking ?? '',
579
- thinkingOpen: true,
580
- // `Date.now()` in the reducer is not a new impurity class: the
581
- // retry card's `startedAt` already reads the clock here.
582
- thinkingStartedAt: e.thinkingStartedAt ?? Date.now(),
583
- }
584
- : e),
585
- };
586
- }
587
- case 'thinkingDelta': {
588
- if (!state.streamingId)
589
- return state;
590
- return {
591
- ...state,
592
- entries: mapEntry(state.entries, state.streamingId, (e) => e.kind === 'assistant'
593
- ? { ...e, thinking: appendBounded(e.thinking ?? '', action.delta, ENTRY_LIMITS.thinking) }
594
- : e),
595
- };
596
- }
597
- case 'textDelta': {
598
- if (!state.streamingId)
599
- return state;
600
- return {
601
- ...state,
602
- entries: mapEntry(state.entries, state.streamingId, (e) => e.kind === 'assistant'
603
- ? {
604
- ...e,
605
- text: appendBounded(e.text, action.delta, ENTRY_LIMITS.text),
606
- // FIRST TEXT DELTA ONLY. Thinking is over the moment the answer
607
- // starts; re-sealing on every delta would make the number climb
608
- // with the answer's length.
609
- ...sealThinkingMs(e),
610
- }
611
- : e),
612
- };
613
- }
614
- case 'toolCallStart': {
615
- if (SELF_RENDERING_TOOLS.has(action.toolName))
616
- return state;
617
- const { id, seq } = nextId(state);
618
- const entry = {
619
- id,
620
- kind: 'tool',
621
- toolCallId: action.toolCallId,
622
- name: action.toolName,
623
- label: action.label ?? action.toolName,
624
- argsRaw: '',
625
- status: 'pending',
626
- };
627
- return { ...state, seq, ...appendEntry(state, state.entries, entry) };
628
- }
629
- case 'toolCallDelta': {
630
- const id = findToolEntryId(state, action.toolCallId);
631
- if (!id)
632
- return state;
633
- return {
634
- ...state,
635
- entries: mapEntry(state.entries, id, (e) => e.kind === 'tool'
636
- ? { ...e, argsRaw: appendBounded(e.argsRaw, action.argsDelta, ENTRY_LIMITS.argsRaw) }
637
- : e),
638
- };
639
- }
640
- case 'toolCallEnd': {
641
- const id = findToolEntryId(state, action.toolCallId);
642
- if (!id)
643
- return state;
644
- return {
645
- ...state,
646
- entries: mapEntry(state.entries, id, (e) => e.kind === 'tool'
647
- ? { ...e, args: action.args, argsRaw: safeStringify(action.args) }
648
- : e),
649
- };
650
- }
651
- case 'toolExecStart': {
652
- const id = findToolEntryId(state, action.toolCallId);
653
- if (!id)
654
- return state;
655
- return {
656
- ...state,
657
- entries: mapEntry(state.entries, id, (e) => e.kind === 'tool' ? { ...e, status: 'running' } : e),
658
- };
659
- }
660
- /**
661
- * THE STATUS GUARD IS WHAT KEEPS A LATE CHUNK FROM RESURRECTING A SETTLED
662
- * CARD. The child's last write and its `close` event race, so a chunk can
663
- * legitimately arrive after `tool_execution_end`; without the guard it would
664
- * reattach a tail to a card whose authoritative preview is already drawn.
665
- */
666
- case 'toolOutputDelta': {
667
- const id = findToolEntryId(state, action.toolCallId);
668
- if (!id)
669
- return state;
670
- return {
671
- ...state,
672
- entries: mapEntry(state.entries, id, (e) => e.kind === 'tool' && (e.status === 'running' || e.status === 'pending')
673
- ? {
674
- ...e,
675
- live: action.rows,
676
- liveSeq: (e.liveSeq ?? 0) + 1,
677
- lastOutputAt: action.at,
678
- }
679
- : e),
680
- };
681
- }
682
- /**
683
- * ONLY A RUNNING, MATCHING ENTRY. `begin` travels from the controller and can
684
- * beat `tool_execution_start` through the agent-event pipe, so a miss is
685
- * normal and is dropped on purpose: the next 1 Hz beat repairs it (RV-5).
686
- */
687
- case 'waitUpdate': {
688
- const id = findToolEntryId(state, action.toolCallId);
689
- if (!id)
690
- return state;
691
- const target = state.entries.find((e) => e.id === id);
692
- if (!target || target.kind !== 'tool' || target.status !== 'running')
693
- return state;
694
- return {
695
- ...state,
696
- entries: mapEntry(state.entries, id, (e) => e.kind === 'tool' ? { ...e, wait: action.view, waitSeq: (e.waitSeq ?? 0) + 1 } : e),
697
- };
698
- }
699
- case 'toolExecEnd': {
700
- const id = findToolEntryId(state, action.toolCallId);
701
- if (!id)
702
- return state;
703
- return {
704
- ...state,
705
- entries: mapEntry(state.entries, id, (e) => e.kind === 'tool'
706
- ? {
707
- ...e,
708
- status: action.isError ? 'error' : 'done',
709
- isError: action.isError,
710
- durationMs: action.duration,
711
- preview: action.preview,
712
- ...(action.patch ? { patch: action.patch } : {}),
713
- // THE TAIL IS NEVER MERGED INTO `preview` (D-23): the
714
- // authoritative result already contains everything it held, in
715
- // full, and concatenating them would double the output and break
716
- // `bashBadge`'s last-line parse (`ToolPreview.tsx:35-54`).
717
- live: undefined,
718
- lastOutputAt: undefined,
719
- wait: undefined,
720
- waitSeq: undefined,
721
- }
722
- : e),
723
- };
724
- }
725
- case 'turnEnd': {
726
- const entries = state.streamingId
727
- ? mapEntry(state.entries, state.streamingId, (e) => e.kind === 'assistant'
728
- ? {
729
- ...e,
730
- streaming: false,
731
- thinkingOpen: false,
732
- usage: action.usage,
733
- // The turn that thought and then called a tool without emitting
734
- // any text never saw a `textDelta`, so this is where its clock
735
- // is sealed.
736
- ...sealThinkingMs(e),
737
- }
738
- : e)
739
- : state.entries;
740
- return {
741
- ...state,
742
- streamingId: undefined,
743
- turnProduced: true,
744
- // `contextTokens` IS NOT WRITTEN HERE ANY MORE (I-1). The occupancy this
745
- // turn produced reaches the view through `ContextMeter`, which subscribes
746
- // to the same `turn_end` and publishes the measured pressure - window,
747
- // percentage and approximation markers included, none of which this
748
- // branch ever had. Two branches writing one number is what P0-1 was.
749
- usageTotal: addUsage(state.usageTotal, action.usage, action.costDelta),
750
- // The turn produced an answer, so whatever it had to retry, it recovered.
751
- // Defect 5: prune AFTER the mapEntry above has settled the entry, so an
752
- // assistant bubble this turn never filled in is gone before the retry card
753
- // is settled and before the height table ever sees it.
754
- ...settleRetryCard(state, pruneEmptyAssistant(entries, state.streamingId).entries, 'recovered'),
755
- };
756
- }
757
- case 'runEnd': {
758
- // Finalize any still-streaming entry.
759
- let entries = state.streamingId
760
- ? mapEntry(state.entries, state.streamingId, (e) => e.kind === 'assistant' ? { ...e, streaming: false, ...sealThinkingMs(e) } : e)
761
- : state.entries;
762
- /*
763
- * Defect 5, and BEFORE the silent-failure guard below so the guard and the
764
- * retry settle both see the pruned array. `streamingId: undefined` is set in
765
- * the returned state either way, so the returned id is not needed here.
766
- */
767
- entries = pruneEmptyAssistant(entries, state.streamingId).entries;
768
- let seq = state.seq;
769
- let droppedEntries = state.droppedEntries;
770
- // Silent-failure guard (R2): a swallowed throw shows nothing otherwise.
771
- // A user abort is an expected empty run, not a failure — skip the guard.
772
- if (!state.turnProduced && !state.errorNoticed && !state.aborted) {
773
- const next = nextId({ ...state, seq });
774
- seq = next.seq;
775
- const appended = appendEntry(state, entries, {
776
- id: next.id,
777
- kind: 'notice',
778
- level: 'error',
779
- text: GENERIC_RUN_FAILURE,
780
- });
781
- entries = appended.entries;
782
- droppedEntries = appended.droppedEntries;
783
- }
784
- // The card may now reach `<Static>` (C-5 / I-6): the turn that owns it is
785
- // over, so nothing will rewrite it.
786
- if (state.todoEntryId) {
787
- entries = mapEntry(entries, state.todoEntryId, (e) => e.kind === 'todo' ? { ...e, live: false } : e);
788
- }
789
- return {
790
- ...state,
791
- seq,
792
- droppedEntries,
793
- status: 'idle',
794
- streamingId: undefined,
795
- // THE TERMINAL BACKSTOP, and it is not redundant with `abortMark`
796
- // (llm-api-retry-backoff P1-8 / AC-25b). `abortMark` is dispatched only
797
- // from the Esc handler, but the ENGINE also aborts itself: the idle
798
- // watchdog calls `Agent.abort()` directly, the run unwinds, and the UI
799
- // sees `agent_end` -> `runEnd` with no `abortMark`, no `turn_end` and no
800
- // error notice. A card left at `phase: 'waiting'` then pins
801
- // `Transcript`'s monotonic boundary and re-renders the tail every frame
802
- // for the rest of the session. By the time this runs the three other
803
- // settle paths have already cleared `retryEntryId`, so this only fires on
804
- // a genuinely unsettled card.
805
- //
806
- // `releaseLiveTails` rides the same argument one entry kind over (D-35):
807
- // a tool card stranded at `running` by this very path would otherwise
808
- // keep a tail and a frozen stall clock for the rest of the session.
809
- ...settleRetryCard(state, releaseLiveTails(entries), 'interrupted'),
810
- };
811
- }
812
- case 'notice': {
813
- const { id, seq } = nextId(state);
814
- const entry = { id, kind: 'notice', level: action.level, text: action.text };
815
- const appended = appendEntry(state, state.entries, entry);
816
- // A terminal error notice is what an EXHAUSTED ladder looks like from here:
817
- // the retry wrapper forwards the provider's own error once it stops
818
- // retrying, and that error always precedes any `turn_end` because a failed
819
- // turn has none. A non-error notice leaves the card alone.
820
- const settled = action.level === 'error'
821
- ? settleRetryCard(state, appended.entries, 'exhausted')
822
- : { entries: appended.entries };
823
- return {
824
- ...state,
825
- seq,
826
- droppedEntries: appended.droppedEntries,
827
- errorNoticed: action.level === 'error' ? true : state.errorNoticed,
828
- ...settled,
829
- };
830
- }
831
- case 'abortMark': {
832
- // Mark the run aborted even when no assistant entry is streaming (e.g. an
833
- // abort during a tool call) so the runEnd failure guard stays suppressed.
834
- const marked = state.streamingId
835
- ? mapEntry(state.entries, state.streamingId, (e) => e.kind === 'assistant' ? { ...e, streaming: false, aborted: true } : e)
836
- : state.entries;
837
- // Defect 5 / I-L1-1: prune AFTER the entry is settled, and write the
838
- // returned id back below -- `abortMark` is the one settle path that does
839
- // NOT clear `streamingId`, so a prune without that write-back would leave
840
- // it naming an entry this branch has just removed.
841
- const pruned = pruneEmptyAssistant(marked, state.streamingId);
842
- return {
843
- ...state,
844
- aborted: true,
845
- streamingId: pruned.streamingId,
846
- // An abort during a tool call is the COMMON way a tail is stranded --
847
- // `Esc` while `npm test` runs -- so this is the clause manual row 9
848
- // exercises (D-35 / AC-40).
849
- ...settleRetryCard(state, releaseLiveTails(pruned.entries), 'interrupted'),
850
- };
851
- }
852
- // `teamEntryId` is dropped with the entries it pointed at. Keeping it would
853
- // leave `teamUpdate` mapping an id that no longer exists — harmless today,
854
- // and a dangling reference the next reader has to reason about.
855
- case 'clearTranscript':
856
- return {
857
- ...state,
858
- entries: [],
859
- // The count describes what the RETAIN RING removed from the visible
860
- // transcript. `/clear screen` empties it by intent, so carrying the number
861
- // over would make the exported transcript claim a loss that did not
862
- // happen.
863
- droppedEntries: 0,
864
- streamingId: undefined,
865
- expandedToolIds: {},
866
- teamEntryId: undefined,
867
- // The list goes with the screen it was on. `/clear screen` is an explicit
868
- // user instruction, which is I-2's standing exception (`TodoClearReason`
869
- // `'user'`, the same door `/todo clear` uses) - so the command clears the
870
- // STORE via `controller.clearTodos()` and this clears the MIRROR, exactly
871
- // the pairing `resetConversation` below has always had.
872
- //
873
- // The mirror write is same-valued with the store's own `todoCleared`
874
- // dispatch rather than redundant with it: it is what makes this action
875
- // stand on its own. `commands/clear-command.ts` is its only dispatcher
876
- // TODAY - `/clear` itself now goes through `resetConversation`, because
877
- // clearing the SCREEN and clearing the SESSION are different intentions
878
- // (clear-command-context-reset D-3) - and a hypothetical second one would
879
- // otherwise bring the bug back in identical form and in silence.
880
- //
881
- // KEPT RATHER THAN FOLDED INTO `resetConversation` for the same reason:
882
- // a user who wants the screen back must not have to destroy their context
883
- // to get it, and an action with no dispatcher is dead code by `CLAUDE.md`
884
- // section 4 - which is what `/clear screen` prevents.
885
- //
886
- // The entry id is dropped because the entry it pointed at is gone.
887
- todos: null,
888
- todoEntryId: undefined,
889
- // Same reasoning for the retry card: the entry is gone, so the id must go
890
- // with it or `retryScheduled` would `mapEntry` an id that no longer
891
- // exists. `retry` goes too — unlike `todos` it is not a belief the model
892
- // holds, it is a projection of an entry that has just been erased.
893
- retryEntryId: undefined,
894
- retry: null,
895
- // Same reasoning again for the compaction card: the entry is gone, so the
896
- // id must go with it or `compactionEnd` would `mapEntry` an id that no
897
- // longer exists. `compaction` STAYS — unlike `retry` it is not a
898
- // projection of an entry, it is the live state of a subsystem that is
899
- // still running. `/clear screen` clears the screen, not the session.
900
- compactionEntryId: undefined,
901
- };
902
- case 'resetUsage':
903
- // The one field, for the one reason: a new id bills from zero (D-10).
904
- return { ...state, usageTotal: EMPTY_USAGE_TOTAL };
905
- case 'resetConversation':
906
- return {
907
- ...state,
908
- pendingSteering: [],
909
- entries: [],
910
- droppedEntries: 0,
911
- streamingId: undefined,
912
- expandedToolIds: {},
913
- // `context` IS DELIBERATELY UNTOUCHED (I-1). `clear-command.ts` calls
914
- // `controller.resetConversation()` BEFORE dispatching this, and that marks
915
- // the meter dirty and schedules a publication; zeroing here would make the
916
- // final state `0` rather than the meter's real answer - which for an
917
- // emptied conversation is the system prompt's own couple of percent, not
918
- // nothing. `usageTotal` DOES reset: that is session spend, not occupancy.
919
- usageTotal: EMPTY_USAGE_TOTAL,
920
- teamEntryId: undefined,
921
- // `/clear` clears `messages`, so the belief is gone and the list goes
922
- // with it - the other half of I-2. `controller.clearMessages()` clears
923
- // the STORE; this clears the mirror.
924
- todos: null,
925
- todoEntryId: undefined,
926
- retryEntryId: undefined,
927
- retry: null,
928
- compactionEntryId: undefined,
929
- // THE SIX RUN FACTS THIS BRANCH USED TO KEEP (clear-command-context-reset
930
- // §3.6 / P0-3). `reduceRunFacts` above already zeroes `runPhase`,
931
- // `activeTool`, `runOutcome` and `seenSteeringIds`, so nothing here
932
- // repeats those four - these six are the actual gap, and each one
933
- // otherwise describes a run that no longer exists.
934
- //
935
- // `status` is the status bar: a late `runEnd` would correct it a moment
936
- // later, and until then the user is told a conversation that is over is
937
- // still running. `team` is a dispatch panel with nothing dispatching.
938
- // `fastEntryId` points at a `kind: 'fast'` card that `entries: []` just
939
- // deleted, the same dangling-id rule `retryEntryId` and
940
- // `compactionEntryId` above already follow.
941
- //
942
- // `turnProduced` / `errorNoticed` / `aborted` are ALSO cleared by
943
- // `submit`, which is not a reason to leave them out: clearing is an
944
- // independent entry point, so a user who clears and then only reads the
945
- // panel would carry the previous turn's three facts into a conversation
946
- // they never submitted into - and `runEnd`'s settle rule reads exactly
947
- // those three.
948
- status: 'idle',
949
- team: null,
950
- // The live run snapshot goes with the conversation it was shown in (the run itself
951
- // survives on disk and announces itself again on its next event).
952
- governance: null,
953
- fastEntryId: undefined,
954
- turnProduced: false,
955
- errorNoticed: false,
956
- aborted: false,
957
- // THE TASK LABEL IS PART OF THE TASK (D-13). `/clear`, `/reset` and a
958
- // session switch all land here, so the header loses its label without the
959
- // App needing a second cleanup path. `/clear screen` does NOT come here -
960
- // it keeps the conversation, so it keeps the label.
961
- taskSummary: null,
962
- taskSummaryPending: false,
963
- };
964
- case 'setOverlay':
965
- return { ...state, overlay: action.overlay };
966
- // --- Task summary (tui-shortcut-drawer-and-task-summary §7.1) ----------
967
- //
968
- // TWO FIELDS, ONE DIRECTION: `pending` is a latch the App raises before the
969
- // call and releases on the result, and `taskSummary` is only ever replaced
970
- // wholesale. Neither is persisted and neither is derived from `entries`, so a
971
- // resumed conversation shows nothing until the App rebuilds the label.
972
- case 'taskSummaryPending':
973
- return { ...state, taskSummaryPending: true };
974
- case 'taskSummaryReady':
975
- return { ...state, taskSummary: action.view, taskSummaryPending: false };
976
- // Pure mirror write. The App only ever dispatches what
977
- // `controller.setAgentMode()` reported it ADOPTED, so this reducer has no
978
- // deferral logic of its own to get out of step with (§3.1 / R-P7).
979
- case 'setAgentMode':
980
- return {
981
- ...state,
982
- agentMode: action.mode,
983
- pendingAgentMode: action.pending,
984
- ...adoptPosture(state.posture, action),
985
- };
986
- // --- Governance (governance-mode 14.4) -------------------------------------
987
- case 'govSnapshot': {
988
- const governance = foldGovernanceSnapshot(state.governance, action.snapshot);
989
- return governance === state.governance ? state : { ...state, governance };
990
- }
991
- case 'govEnd':
992
- return state.governance === null ? state : { ...state, governance: null };
993
- case 'governanceCard': {
994
- const { id, seq } = nextId(state);
995
- const entry = { id, kind: 'governance', at: action.at ?? Date.now(), card: action.card };
996
- return { ...state, seq, ...appendEntry(state, state.entries, entry) };
997
- }
998
- case 'toggleThinking':
999
- return { ...state, thinkingVisible: !state.thinkingVisible };
1000
- case 'pushToast': {
1001
- const { seq } = nextId(state);
1002
- const toast = {
1003
- id: `t${seq}`,
1004
- level: action.level,
1005
- text: action.text,
1006
- ttlMs: action.ttlMs ?? DEFAULT_TOAST_TTL_MS,
1007
- };
1008
- return { ...state, seq, toasts: [...state.toasts, toast] };
1009
- }
1010
- case 'dismissToast':
1011
- return { ...state, toasts: state.toasts.filter((t) => t.id !== action.id) };
1012
- case 'toggleExpand': {
1013
- const next = { ...state.expandedToolIds };
1014
- if (next[action.id])
1015
- delete next[action.id];
1016
- else
1017
- next[action.id] = true;
1018
- return { ...state, expandedToolIds: next };
1019
- }
1020
- // --- Team mode (team-subagents §5.3) ---------------------------------
1021
- case 'teamStart': {
1022
- const { id, seq } = nextId(state);
1023
- const runs = action.specs.map((spec) => ({
1024
- label: spec.label,
1025
- description: spec.description,
1026
- phase: 'queued',
1027
- turns: 0,
1028
- toolCalls: 0,
1029
- usage: { inputTokens: 0, outputTokens: 0 },
1030
- filesTouched: [],
1031
- messagesSent: 0,
1032
- }));
1033
- const entry = {
1034
- id,
1035
- kind: 'team',
1036
- dispatchId: action.dispatchId,
1037
- requested: action.requested,
1038
- runs,
1039
- aborted: false,
1040
- active: true,
1041
- };
1042
- const team = {
1043
- dispatchId: action.dispatchId,
1044
- active: true,
1045
- runs,
1046
- requested: action.requested,
1047
- startedAt: Date.now(),
1048
- messageCount: 0,
1049
- };
1050
- // A GOVERNED DISPATCH LEAVES NO TRANSCRIPT CARD (RV-3): a run is 10-20 dispatches,
1051
- // and a `TeamCard` for each would bury the conversation. The live snapshot is still
1052
- // written - the legion panel and `/agents` read it - and with no `teamEntryId`,
1053
- // `teamUpdate` / `teamEnd` already pass `entries` through untouched.
1054
- if (isGovernedDispatch(action.specs))
1055
- return { ...state, team };
1056
- return {
1057
- ...state,
1058
- seq,
1059
- ...appendEntry(state, state.entries, entry),
1060
- teamEntryId: id,
1061
- team,
1062
- };
1063
- }
1064
- case 'teamUpdate': {
1065
- // One `mapEntry` on the KNOWN id, never a scan: this action arrives at up
1066
- // to `maxConcurrent` x 8/s and a reverse search over a long transcript on
1067
- // every one of them is exactly the cost the coalescer exists to avoid.
1068
- const entries = state.teamEntryId
1069
- ? mapEntry(state.entries, state.teamEntryId, (e) => e.kind === 'team' ? { ...e, runs: action.snapshot.runs } : e)
1070
- : state.entries;
1071
- return { ...state, entries, team: action.snapshot };
1072
- }
1073
- case 'teamUsage':
1074
- // `usageTotal` AND NOTHING ELSE (§3.9 / AC-15). In particular NOT
1075
- // `context`: that gauge shows the LEAD's context occupancy against
1076
- // the model's window, and folding five children into it would read 180%
1077
- // on a perfectly healthy session. Child spend is real money and has to
1078
- // appear in the cost readout; it is not the lead's context.
1079
- return { ...state, usageTotal: addUsage(state.usageTotal, action.usage, action.costDelta) };
1080
- case 'teamEnd': {
1081
- const { outcome } = action;
1082
- const entries = state.teamEntryId
1083
- ? mapEntry(state.entries, state.teamEntryId, (e) => e.kind === 'team'
1084
- ? {
1085
- ...e,
1086
- runs: outcome.runs,
1087
- aborted: outcome.aborted,
1088
- durationMs: Math.max(0, outcome.endedAt - outcome.startedAt),
1089
- active: false,
1090
- }
1091
- : e)
1092
- : state.entries;
1093
- // `team: null` is what makes the panel disappear — the requirement's
1094
- // "(dang you de shi hou)": it renders only while a dispatch is running.
1095
- return { ...state, entries, team: null, teamEntryId: undefined };
1096
- }
1097
- // --- Todo planning (todo-plan-execution §5.2) -------------------------
1098
- case 'todoUpdate': {
1099
- // TYPED AS THE TODO MEMBER, NOT AS `Entry`. The rewrite below spreads this
1100
- // and overrides `live: boolean`; while the return type was the whole union
1101
- // that spread also produced a TOOL entry carrying `live: boolean`, which
1102
- // stopped type-checking the moment the tool member gained its own `live`
1103
- // (agent-activity-presentation-live). Narrowing here is what keeps the
1104
- // override honest rather than accidentally legal.
1105
- const entry = (id) => ({
1106
- id,
1107
- kind: 'todo',
1108
- items: action.snapshot.items,
1109
- doneCount: action.snapshot.doneCount,
1110
- total: action.snapshot.total,
1111
- acceptance: action.snapshot.acceptance,
1112
- live: true,
1113
- });
1114
- // Rewrite the turn's card when there is one; otherwise append and record
1115
- // its id. One `mapEntry` on the KNOWN id, never a scan.
1116
- if (state.todoEntryId) {
1117
- const known = state.todoEntryId;
1118
- return {
1119
- ...state,
1120
- todos: action.snapshot,
1121
- entries: mapEntry(state.entries, known, (e) => e.kind === 'todo' ? { ...entry(known), live: e.live } : e),
1122
- };
1123
- }
1124
- const { id, seq } = nextId(state);
1125
- return {
1126
- ...state,
1127
- seq,
1128
- todos: action.snapshot,
1129
- ...appendEntry(state, state.entries, entry(id)),
1130
- todoEntryId: id,
1131
- };
1132
- }
1133
- // THE ENTRY STAYS. It is history, and history is not retracted — only the
1134
- // live projection goes away, which is what unmounts the rail.
1135
- case 'todoCleared':
1136
- return { ...state, todos: null, todoEntryId: undefined };
1137
- // --- Fast model tier (fast-model-tier §5.4) ---------------------------
1138
- case 'fastStart': {
1139
- const { id, seq } = nextId(state);
1140
- const entry = {
1141
- id,
1142
- kind: 'fast',
1143
- reviewIndex: action.index,
1144
- model: action.model,
1145
- status: 'running',
1146
- turn: action.turn,
1147
- live: true,
1148
- };
1149
- return {
1150
- ...state,
1151
- seq,
1152
- ...appendEntry(state, state.entries, entry),
1153
- fastEntryId: id,
1154
- };
1155
- }
1156
- case 'fastEnd': {
1157
- const { review } = action;
1158
- const settle = (e) => e.kind === 'fast'
1159
- ? {
1160
- ...e,
1161
- status: review.kind,
1162
- ...(review.text !== undefined ? { text: review.text } : {}),
1163
- ...(review.detail !== undefined ? { detail: review.detail } : {}),
1164
- durationMs: review.durationMs,
1165
- // SETTLED. `Transcript`'s boundary is monotonic, so a card that
1166
- // never settles is re-rendered on every frame for the rest of the
1167
- // session (C-5).
1168
- live: false,
1169
- }
1170
- : e;
1171
- // Rewrite the card this review opened when there is one; otherwise append
1172
- // a settled card. THE SECOND BRANCH IS NOT DEFENSIVE PADDING: a review
1173
- // that is dropped as `stranded` is emitted from `agent_end` and never had
1174
- // a `review_start`, and a run restored from `/resume` has no live id at
1175
- // all — "it did nothing" must never be the observable outcome (R-8).
1176
- if (state.fastEntryId) {
1177
- return {
1178
- ...state,
1179
- entries: mapEntry(state.entries, state.fastEntryId, settle),
1180
- fastEntryId: undefined,
1181
- };
1182
- }
1183
- const { id, seq } = nextId(state);
1184
- const entry = {
1185
- id,
1186
- kind: 'fast',
1187
- reviewIndex: review.index,
1188
- model: review.model,
1189
- status: review.kind,
1190
- ...(review.text !== undefined ? { text: review.text } : {}),
1191
- ...(review.detail !== undefined ? { detail: review.detail } : {}),
1192
- turn: review.turn,
1193
- durationMs: review.durationMs,
1194
- live: false,
1195
- };
1196
- return { ...state, seq, ...appendEntry(state, state.entries, entry) };
1197
- }
1198
- case 'fastUsage':
1199
- // `usageTotal` AND NOTHING ELSE, exactly as `teamUsage` (§3.6 / AC-18). In
1200
- // particular NOT `context`: that gauge shows the LEAD's context
1201
- // occupancy against the lead model's window, and a review is a separate
1202
- // conversation with a different model entirely.
1203
- return { ...state, usageTotal: addUsage(state.usageTotal, action.usage, action.costDelta) };
1204
- case 'fastTier':
1205
- return { ...state, fast: action.snapshot };
1206
- // --- Context compaction (context-auto-compaction §5.5) ----------------
1207
- // THE TRANSCRIPT NOW CARRIES ONE CARD PER ATTEMPT (context-compaction-never-
1208
- // fires C). Before this change a declined `pressure` checkpoint dispatched
1209
- // NOTHING, which is what made "95 % and nothing happens" unreportable: the
1210
- // feature was trying, failing and saying nothing. The flood the old rule
1211
- // feared is bounded by `maxPerRun` / `stuckLimit` in `compaction/limits.ts`.
1212
- case 'compactionStart': {
1213
- const { id, seq } = nextId(state);
1214
- let entries = state.entries;
1215
- // RESPONSIBILITY 1 — SEAL ANY LIVE `streamingId` ENTRY, defensively.
1216
- // C-8's failure mode, and reachable here: `provider.ts` records that
1217
- // Anthropic can deliver an error IN-STREAM, after the connection is open
1218
- // and therefore after text deltas have already been emitted. An entry that
1219
- // never settles pins `Transcript`'s monotonic boundary and re-renders the
1220
- // tail forever.
1221
- if (state.streamingId) {
1222
- entries = mapEntry(entries, state.streamingId, (e) => e.kind === 'assistant' ? { ...e, streaming: false, ...sealThinkingMs(e) } : e);
1223
- }
1224
- // RESPONSIBILITY 2 — REWRITE A TRAILING `context_overflow` ERROR NOTICE
1225
- // (P1-4). `agent-loop.ts` emits `message_update` for EVERY stream event
1226
- // before the `error` branch throws, and `reduceStreamEvent`'s `case 'error'`
1227
- // turns that into a `level: 'error'` notice reading "Context length
1228
- // exceeded". So the HAPPY PATH of the reactive recovery would otherwise end
1229
- // with a red banner telling the user to throw away the session we are one
1230
- // frame from saving.
1231
- //
1232
- // IT REWRITES RATHER THAN REMOVES, because `<Static>` cannot un-print an
1233
- // entry it has already drawn — and it matches ONLY when that notice is the
1234
- // TAIL entry, so it can never touch an older, genuine error. If the
1235
- // recovery then fails, the ladder's own notice or the re-thrown 400 is what
1236
- // the user is left with, so neither direction lies.
1237
- if (action.trigger === 'overflow') {
1238
- const tail = entries[entries.length - 1];
1239
- if (tail &&
1240
- tail.kind === 'notice' &&
1241
- tail.level === 'error' &&
1242
- tail.text.startsWith(CONTEXT_OVERFLOW_NOTICE_PREFIX)) {
1243
- entries = mapEntry(entries, tail.id, (e) => e.kind === 'notice'
1244
- ? { ...e, level: 'info', text: 'Context window exceeded - compacting and retrying.' }
1245
- : e);
1246
- }
1247
- }
1248
- const entry = {
1249
- id,
1250
- kind: 'compaction',
1251
- index: action.index,
1252
- trigger: action.trigger,
1253
- mode: 'none',
1254
- applied: false,
1255
- messagesBefore: 0,
1256
- messagesAfter: 0,
1257
- tokensBefore: 0,
1258
- tokensAfter: 0,
1259
- model: action.model,
1260
- startedAt: Date.now(),
1261
- phase: 'planning',
1262
- live: true,
1263
- };
1264
- return {
1265
- ...state,
1266
- seq,
1267
- ...appendEntry(state, entries, entry),
1268
- compactionEntryId: id,
1269
- streamingId: undefined,
1270
- };
1271
- }
1272
- case 'compactionEnd': {
1273
- const { record } = action;
1274
- const settle = (e) => e.kind === 'compaction'
1275
- ? {
1276
- ...e,
1277
- index: record.index,
1278
- trigger: record.trigger,
1279
- mode: record.mode,
1280
- applied: record.applied,
1281
- ...(record.reason !== undefined ? { reason: record.reason } : {}),
1282
- messagesBefore: record.messagesBefore,
1283
- messagesAfter: record.messagesAfter,
1284
- tokensBefore: record.tokensBefore,
1285
- tokensAfter: record.tokensAfter,
1286
- ...(record.summary !== undefined ? { summary: record.summary } : {}),
1287
- ...(record.tailRelief ? { tailRelief: record.tailRelief } : {}),
1288
- ...(record.decision ? { decision: record.decision } : {}),
1289
- ...(record.memoryVersion ? { memoryVersion: record.memoryVersion } : {}),
1290
- ...(record.keepRelaxed ? { keepRelaxed: record.keepRelaxed } : {}),
1291
- ...(record.retainedTurns !== undefined
1292
- ? { retainedTurns: record.retainedTurns } : {}),
1293
- ...(record.retainedMessages !== undefined
1294
- ? { retainedMessages: record.retainedMessages } : {}),
1295
- ...(record.retainedMessages !== undefined
1296
- ? { retainedMessages: record.retainedMessages } : {}),
1297
- ...(record.keepRecentTurnsUsed !== undefined
1298
- ? { keepRecentTurnsUsed: record.keepRecentTurnsUsed } : {}),
1299
- ...(record.elidedMessages ? { elidedMessages: record.elidedMessages } : {}),
1300
- model: record.model,
1301
- durationMs: record.durationMs,
1302
- // SETTLED. `Transcript`'s boundary is monotonic, so a card that
1303
- // never settles is re-rendered on every frame for the rest of the
1304
- // session (C-8).
1305
- live: false,
1306
- }
1307
- : e;
1308
- // THE SECOND BRANCH IS NOT DEFENSIVE PADDING, exactly as `fastEnd`'s is
1309
- // not: the IDLE `/compact` path settles a record for a card this session
1310
- // may never have opened, and "it did nothing" must never be the observable
1311
- // outcome (R-8 / §6.4).
1312
- if (state.compactionEntryId) {
1313
- return {
1314
- ...state,
1315
- entries: mapEntry(state.entries, state.compactionEntryId, settle),
1316
- compactionEntryId: undefined,
1317
- };
1318
- }
1319
- const { id, seq } = nextId(state);
1320
- const entry = {
1321
- id,
1322
- kind: 'compaction',
1323
- index: record.index,
1324
- trigger: record.trigger,
1325
- mode: record.mode,
1326
- applied: record.applied,
1327
- ...(record.reason !== undefined ? { reason: record.reason } : {}),
1328
- messagesBefore: record.messagesBefore,
1329
- messagesAfter: record.messagesAfter,
1330
- tokensBefore: record.tokensBefore,
1331
- tokensAfter: record.tokensAfter,
1332
- ...(record.summary !== undefined ? { summary: record.summary } : {}),
1333
- ...(record.tailRelief ? { tailRelief: record.tailRelief } : {}),
1334
- ...(record.decision ? { decision: record.decision } : {}),
1335
- ...(record.memoryVersion ? { memoryVersion: record.memoryVersion } : {}),
1336
- ...(record.keepRelaxed ? { keepRelaxed: record.keepRelaxed } : {}),
1337
- ...(record.retainedTurns !== undefined ? { retainedTurns: record.retainedTurns } : {}),
1338
- ...(record.keepRecentTurnsUsed !== undefined
1339
- ? { keepRecentTurnsUsed: record.keepRecentTurnsUsed } : {}),
1340
- ...(record.elidedMessages ? { elidedMessages: record.elidedMessages } : {}),
1341
- model: record.model,
1342
- durationMs: record.durationMs,
1343
- live: false,
1344
- };
1345
- return { ...state, seq, ...appendEntry(state, state.entries, entry) };
1346
- }
1347
- case 'compactionPhase': {
1348
- // THE LIVE CARD'S STAGE, AND NOTHING ELSE. It must not open a card (a phase
1349
- // can arrive for an entry that a fast settle already closed) and must not
1350
- // resurrect a settled one: `live` is left untouched, so a late phase on a
1351
- // settled card changes nothing the renderer shows.
1352
- if (!state.compactionEntryId)
1353
- return state;
1354
- const phase = action.phase;
1355
- return {
1356
- ...state,
1357
- entries: mapEntry(state.entries, state.compactionEntryId, (e) => e.kind === 'compaction' && e.live ? { ...e, phase } : e),
1358
- };
1359
- }
1360
- case 'compactionUsage':
1361
- // `usageTotal` AND NOTHING ELSE, exactly as `teamUsage` and `fastUsage`. In
1362
- // particular NOT `context`: that gauge shows the LEAD's context
1363
- // occupancy against the lead model's window, and a summarization is a
1364
- // separate conversation with a possibly different model entirely.
1365
- return { ...state, usageTotal: addUsage(state.usageTotal, action.usage, action.costDelta) };
1366
- case 'compactionSnapshot':
1367
- return { ...state, compaction: action.snapshot };
1368
- /**
1369
- * THE ONE WRITER OF `state.context` (I-1).
1370
- *
1371
- * THE IDENTITY SHORT-CIRCUIT BELOW IS LOAD-BEARING, NOT A MICRO-OPTIMISATION
1372
- * (§5.2 / R-3). The meter publishes on a 400 ms tick throughout a turn and
1373
- * most of those ticks measure the same number - the history did not change
1374
- * between two tool calls of the same size, or the percentage rounded to the
1375
- * same integer. Returning the SAME state object lets React skip the whole
1376
- * subtree; returning a fresh one would make a permanently visible row into a
1377
- * 2.5 Hz re-render source and walk the render governor up its ladder for no
1378
- * visible change at all.
1379
- *
1380
- * `ContextUsageSnapshot` carries no timestamp for exactly this reason: a
1381
- * field that moves every tick would make this comparison never hold.
1382
- */
1383
- case 'contextUsage': {
1384
- const prev = state.context;
1385
- const next = action.snapshot;
1386
- if (prev.occupied === next.occupied &&
1387
- prev.window === next.window &&
1388
- prev.pct === next.pct &&
1389
- prev.source === next.source &&
1390
- prev.deltaTokens === next.deltaTokens &&
1391
- prev.windowKnown === next.windowKnown &&
1392
- prev.windowSource === next.windowSource &&
1393
- prev.windowOverridden === next.windowOverridden) {
1394
- return state;
1395
- }
1396
- return { ...state, context: next };
1397
- }
1398
- // --- API retry (llm-api-retry-backoff §6.4) ---------------------------
1399
- case 'retryScheduled': {
1400
- const snapshot = {
1401
- attempt: action.attempt,
1402
- maxRetries: action.maxRetries,
1403
- resumeAt: action.resumeAt,
1404
- phase: 'waiting',
1405
- errorType: action.errorType,
1406
- };
1407
- // ONE CARD PER TURN, REWRITTEN IN PLACE. One `mapEntry` on the KNOWN id,
1408
- // never a scan — the same discipline `todoUpdate` follows.
1409
- if (state.retryEntryId) {
1410
- const known = state.retryEntryId;
1411
- return {
1412
- ...state,
1413
- retry: snapshot,
1414
- entries: mapEntry(state.entries, known, (e) => e.kind === 'retry'
1415
- ? {
1416
- ...e,
1417
- attempt: action.attempt,
1418
- maxRetries: action.maxRetries,
1419
- errorType: action.errorType,
1420
- message: action.message,
1421
- delayMs: action.delayMs,
1422
- resumeAt: action.resumeAt,
1423
- phase: 'waiting',
1424
- }
1425
- : e),
1426
- };
1427
- }
1428
- const { id, seq } = nextId(state);
1429
- const entry = {
1430
- id,
1431
- kind: 'retry',
1432
- attempt: action.attempt,
1433
- maxRetries: action.maxRetries,
1434
- errorType: action.errorType,
1435
- message: action.message,
1436
- delayMs: action.delayMs,
1437
- resumeAt: action.resumeAt,
1438
- phase: 'waiting',
1439
- // The clock the settled card's `elapsedMs` is measured from: the FIRST
1440
- // retry of the turn, not the last.
1441
- startedAt: Date.now(),
1442
- };
1443
- return {
1444
- ...state,
1445
- seq,
1446
- retry: snapshot,
1447
- ...appendEntry(state, state.entries, entry),
1448
- retryEntryId: id,
1449
- };
1450
- }
1451
- case 'retryAttempt': {
1452
- if (!state.retryEntryId)
1453
- return state;
1454
- const known = state.retryEntryId;
1455
- return {
1456
- ...state,
1457
- // `resumeAt` is CLEARED: the wait is over, so a countdown would tick
1458
- // downward past zero while the request is already in flight.
1459
- retry: state.retry ? { ...state.retry, phase: 'retrying', resumeAt: undefined } : null,
1460
- entries: mapEntry(state.entries, known, (e) => e.kind === 'retry' ? { ...e, phase: 'retrying', resumeAt: undefined } : e),
1461
- };
1462
- }
1463
- case 'streamRestart': {
1464
- const discarded = new Set(action.discardedToolCallIds);
1465
- const entries = state.entries.filter((e) => !(e.kind === 'tool' && discarded.has(e.toolCallId)));
1466
- return {
1467
- ...state,
1468
- entries: state.streamingId
1469
- ? mapEntry(entries, state.streamingId, (e) => e.kind === 'assistant'
1470
- ? {
1471
- ...e,
1472
- text: '',
1473
- thinking: undefined,
1474
- thinkingOpen: false,
1475
- // A restarted stream re-thinks from scratch. Carrying the old
1476
- // clock forward would report a duration for reasoning that
1477
- // was discarded.
1478
- thinkingStartedAt: undefined,
1479
- thinkingMs: undefined,
1480
- }
1481
- : e)
1482
- : entries,
1483
- };
1484
- }
1485
- /**
1486
- * The 1 Hz countdown tick. NO VALUE CHANGES — the card is refreshed by
1487
- * IDENTITY so that React re-renders exactly one entry.
1488
- *
1489
- * A FLAT NONCE ON `ViewState` WOULD NOT WORK, and this is the trap worth
1490
- * naming: `EntryView` is `React.memo`'d with an explicit comparator whose
1491
- * first clause is `a.entry === b.entry`, so a state field the card does not
1492
- * receive as a prop changes nothing at all — the countdown would freeze at
1493
- * whatever second it was first drawn on, and the transcript would keep
1494
- * re-rendering around it for no benefit. Rewriting the entry is what breaks
1495
- * that boundary, and `EntryView` reads the clock for this kind only.
1496
- *
1497
- * `entryRevision` is deliberately NOT extended: the card is one row in every
1498
- * phase, so the cached height stays correct and no re-measure is wanted.
1499
- *
1500
- * Returning `state` unchanged when nothing is waiting is what keeps a stray
1501
- * tick from costing a render at all.
1502
- */
1503
- case 'retryTick': {
1504
- if (state.retry?.phase !== 'waiting' || !state.retryEntryId)
1505
- return state;
1506
- const known = state.retryEntryId;
1507
- return {
1508
- ...state,
1509
- entries: mapEntry(state.entries, known, (e) => (e.kind === 'retry' ? { ...e } : e)),
1510
- };
1511
- }
1512
- case 'restoreEntries': {
1513
- // Adopt a seq beyond any restored id to keep future ids unique.
1514
- const maxSeq = action.entries.reduce((acc, e) => {
1515
- const n = Number.parseInt(e.id.replace(/^e/, ''), 10);
1516
- return Number.isFinite(n) ? Math.max(acc, n) : acc;
1517
- }, state.seq);
1518
- // A session file can be longer than the retain ring; trimming it here is
1519
- // the one append-free place the bound still has to hold, and the count
1520
- // starts from this restore rather than inheriting the previous session's.
1521
- const restored = trimEntries([...action.entries], entryRetain());
1522
- return {
1523
- ...state,
1524
- entries: restored.entries,
1525
- pendingSteering: [],
1526
- droppedEntries: restored.dropped,
1527
- seq: maxSeq,
1528
- teamEntryId: undefined,
1529
- // As for `team`: the restored cards are history and no turn owns them.
1530
- // The LIST itself arrives separately, through `controller.restoreTodos`
1531
- // and the store's own event (§3.13).
1532
- todoEntryId: undefined,
1533
- // Same for the retry card. `normalizeLoadedEntries` has already forced any
1534
- // in-flight phase to `interrupted`, so nothing restored is live.
1535
- retryEntryId: undefined,
1536
- retry: null,
1537
- // Same again for the fast card: `normalizeLoadedEntries` has already
1538
- // forced `live: false`, so nothing restored is live. `state.fast` is
1539
- // NOT cleared — it is the session's own tier projection, which a
1540
- // `/resume` does not change.
1541
- fastEntryId: undefined,
1542
- // `/resume` ADOPTS ANOTHER CONVERSATION, so the previous conversation's
1543
- // label describes something that is no longer on screen. The reducer only
1544
- // CLEARS it; the App's `makeCtx().dispatch` wrapper rebuilds one from
1545
- // `action.entries` (D-13), because it is the only layer that sees the
1546
- // restored entries BEFORE this branch runs.
1547
- taskSummary: null,
1548
- taskSummaryPending: false,
1549
- // Same rule for a live governed run: `/resume` adopts another conversation, and
1550
- // a snapshot of the old one's run must not be drawn over it. Settlement cards
1551
- // arrive with the restored `entries`.
1552
- governance: null,
1553
- };
1554
- }
1555
- // --- Background services (§3.10 / D-11) -----------------------------
1556
- case 'serviceStart': {
1557
- const { id, seq } = nextId(state);
1558
- const entry = { id, ...serviceFields(action.service) };
1559
- const appended = appendEntry(state, state.entries, entry);
1560
- return {
1561
- ...state,
1562
- seq,
1563
- entries: appended.entries,
1564
- droppedEntries: appended.droppedEntries,
1565
- };
1566
- }
1567
- case 'serviceUpdate': {
1568
- const target = findServiceEntryId(state, action.service.id);
1569
- if (!target)
1570
- return state;
1571
- return {
1572
- ...state,
1573
- entries: mapEntry(state.entries, target, (e) => e.kind === 'service' ? { ...e, ...serviceFields(action.service) } : e),
1574
- };
1575
- }
1576
- case 'serviceEnd': {
1577
- const target = findServiceEntryId(state, action.service.id);
1578
- const settled = target
1579
- ? mapEntry(state.entries, target, (e) => e.kind === 'service' ? { ...e, ...serviceFields(action.service) } : e)
1580
- : state.entries;
1581
- const { id, seq } = nextId(state);
1582
- const appended = appendEntry(state, settled, {
1583
- id,
1584
- ...serviceFields(action.service),
1585
- terminal: true,
1586
- });
1587
- return {
1588
- ...state,
1589
- seq,
1590
- entries: appended.entries,
1591
- droppedEntries: appended.droppedEntries,
1592
- };
1593
- }
1594
- default:
1595
- return state;
1596
- }
1597
- }
1598
- /**
1599
- * Project a supervisor snapshot onto the entry's own fields.
1600
- *
1601
- * ONE PROJECTION FOR ALL THREE ACTIONS, so a field that reaches the card through
1602
- * `serviceStart` can never fail to reach it through `serviceUpdate` — the kind of
1603
- * asymmetry that produces a card frozen at its first values with nothing
1604
- * reporting it.
1605
- */
1606
- function serviceFields(service) {
1607
- return {
1608
- kind: 'service',
1609
- serviceId: service.id,
1610
- jobKind: service.kind,
1611
- command: service.command,
1612
- status: service.status,
1613
- ...(service.url !== undefined ? { url: service.url } : {}),
1614
- ...(service.port !== undefined ? { port: service.port } : {}),
1615
- exitCode: service.exitCode,
1616
- startedAt: service.startedAt,
1617
- ...(service.readyAt !== undefined ? { readyAt: service.readyAt } : {}),
1618
- ...(service.endedAt !== undefined ? { endedAt: service.endedAt } : {}),
1619
- rows: service.rows,
1620
- rowsSeen: service.rowsSeen,
1621
- ...(service.killIncomplete ? { killIncomplete: true } : {}),
1622
- // The four fields long-task-jobs adds, in the SAME projection for all three
1623
- // actions - which is the property this function exists to guarantee.
1624
- lastOutputAt: service.lastOutputAt,
1625
- ...(service.progress ? { progress: service.progress.text } : {}),
1626
- ...(service.stalled ? { stalled: true } : {}),
1627
- ...(service.logPath !== undefined ? { logPath: service.logPath } : {}),
1628
- };
1629
- }
1630
- /** The LIVE card for a service id, newest first; terminal records are skipped. */
1631
- function findServiceEntryId(state, serviceId) {
1632
- for (let i = state.entries.length - 1; i >= 0; i -= 1) {
1633
- const e = state.entries[i];
1634
- if (e && e.kind === 'service' && e.serviceId === serviceId && !e.terminal)
1635
- return e.id;
1636
- }
1637
- return undefined;
1638
- }
1639
- function safeStringify(value) {
1640
- try {
1641
- return JSON.stringify(value);
1642
- }
1643
- catch {
1644
- return String(value);
1645
- }
1646
- }