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