elanous 0.2.13 → 0.2.15

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 (524) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/LZcstaO9_NXypPFmujolv/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/1307-33ffcc49ab45123b.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/{9656-c1540088f5859c80.js → 1844-9ec4b1d12f6e80eb.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/{390.c373e31b3b9d0c41.js → 1948.15f356b15a625597.js} +2 -2
  10. package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/{4685-c3c1c625b71accd1.js → 4614-21403aac730d4586.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/{8039-df92e08fede5de8f.js → 4795-f372490b178583fc.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/{1452.93700483b9d4fc61.js → 6110.1297d4093fdfbb0d.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +2 -0
  31. package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/726.83532d69452ee24b.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/{1829-b453a1b22d3999bb.js → 7327-2d69fb321ba73389.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +25 -0
  42. package/apps/pwa/out/_next/static/chunks/{8421-1c4acfb1395a0edf.js → 8949-ecb4128a0858b911.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/9103-7a3ae7f60af3b99f.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/{3869-3cd46526879f454c.js → 9247-29d889b809288e65.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/{79f8be9e-957d68913b590b08.js → a9f2b54d-7865aba0f4d2ee49.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/approvals/page-0e2c57fe6add4b2a.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e05a77df7e005b04.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/botlab/page-5491c115b1f9a923.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/bots/page-b8c49f6b4fa2df91.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/chat/page-976813c91d7394ea.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/consult/page-28b9448091776c06.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/control/page-1d4901eb7c2d8822.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/design-check/page-72d3e7ff35a8c954.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/editor/page-60c7a8e1a4fe62ce.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/exec/page-2a52b316cb143d9c.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/field/page-c3ce483c8d5db370.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/inside/page-13c7b53e0c2519a9.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/intake/page-96f074f6538e8a2c.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/layout-e97be5334d83a9f5.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/market/page-955ec2da3be4c560.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-9f88fbc15d9058d1.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/morning/page-c1343f9499d86f3f.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/not-found-8739a2e0ae793130.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/observatory/page-c9fc5706b8abaa11.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-d23b12bb92242558.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c7fea475488201a8.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e2ee2896811e98d0.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/reflection/page-5efbf81124c474ae.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f1a1fc0a82738c.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/sessions/page-da6c08aae0f92768.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-06ee266953d9c879.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/settings/page-153c2b3bcc89e8de.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-87a11e6a05f15199.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/setup/page-7d4182964407c09c.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/share/page-0d6d23b40263a7ea.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/term/{page-63d14a4d8fe1e1c1.js → page-f5597e894c02cd33.js} +1 -1
  91. package/apps/pwa/out/_next/static/chunks/app/today/page-0337fdf7fb5e37b6.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/trace/page-73735b64bfe1ae67.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/app/vault/page-8fcec4c9ea2e7f70.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f7e631e9b4daf66e.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/app/workspace/page-9817c7f9007466cc.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-97f9f43172a3a90e.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/{5cd7e3a9-404fd6b8d0ecd44c.js → f6231284-45726550aba08fca.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/{f48170cc-d05d28bf87a43bf4.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
  100. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +1 -0
  106. package/apps/pwa/out/_next/static/css/1f7c958c41ffa3b9.css +1 -0
  107. package/apps/pwa/out/approvals/index.html +1 -1
  108. package/apps/pwa/out/approvals/index.txt +23 -23
  109. package/apps/pwa/out/autopilot/index.html +1 -1
  110. package/apps/pwa/out/autopilot/index.txt +23 -23
  111. package/apps/pwa/out/board/index.html +1 -1
  112. package/apps/pwa/out/board/index.txt +23 -23
  113. package/apps/pwa/out/botlab/index.html +1 -1
  114. package/apps/pwa/out/botlab/index.txt +22 -22
  115. package/apps/pwa/out/bots/index.html +1 -1
  116. package/apps/pwa/out/bots/index.txt +22 -22
  117. package/apps/pwa/out/ceo/index.html +1 -0
  118. package/apps/pwa/out/ceo/index.txt +33 -0
  119. package/apps/pwa/out/chat/index.html +1 -1
  120. package/apps/pwa/out/chat/index.txt +22 -22
  121. package/apps/pwa/out/consult/index.html +1 -1
  122. package/apps/pwa/out/consult/index.txt +23 -23
  123. package/apps/pwa/out/control/index.html +1 -1
  124. package/apps/pwa/out/control/index.txt +23 -23
  125. package/apps/pwa/out/design-check/index.html +1 -1
  126. package/apps/pwa/out/design-check/index.txt +23 -23
  127. package/apps/pwa/out/editor/index.html +1 -1
  128. package/apps/pwa/out/editor/index.txt +23 -23
  129. package/apps/pwa/out/exec/index.html +1 -1
  130. package/apps/pwa/out/exec/index.txt +23 -23
  131. package/apps/pwa/out/field/index.html +1 -1
  132. package/apps/pwa/out/field/index.txt +23 -23
  133. package/apps/pwa/out/healthz +1 -1
  134. package/apps/pwa/out/index.html +1 -1
  135. package/apps/pwa/out/index.txt +22 -22
  136. package/apps/pwa/out/inside/index.html +1 -1
  137. package/apps/pwa/out/inside/index.txt +22 -22
  138. package/apps/pwa/out/intake/index.html +1 -1
  139. package/apps/pwa/out/intake/index.txt +23 -23
  140. package/apps/pwa/out/live/index.html +1 -1
  141. package/apps/pwa/out/live/index.txt +23 -23
  142. package/apps/pwa/out/loops/index.html +1 -0
  143. package/apps/pwa/out/loops/index.txt +30 -0
  144. package/apps/pwa/out/market/index.html +1 -1
  145. package/apps/pwa/out/market/index.txt +22 -22
  146. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  147. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  148. package/apps/pwa/out/morning/index.html +1 -1
  149. package/apps/pwa/out/morning/index.txt +23 -23
  150. package/apps/pwa/out/observatory/index.html +1 -1
  151. package/apps/pwa/out/observatory/index.txt +23 -23
  152. package/apps/pwa/out/ops/checklist/index.html +1 -1
  153. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  154. package/apps/pwa/out/ops/release/index.html +1 -1
  155. package/apps/pwa/out/ops/release/index.txt +22 -22
  156. package/apps/pwa/out/ops/seats/index.html +1 -1
  157. package/apps/pwa/out/ops/seats/index.txt +22 -22
  158. package/apps/pwa/out/outputs/index.html +1 -1
  159. package/apps/pwa/out/outputs/index.txt +22 -22
  160. package/apps/pwa/out/reflection/index.html +1 -1
  161. package/apps/pwa/out/reflection/index.txt +23 -23
  162. package/apps/pwa/out/scheduler/index.html +1 -1
  163. package/apps/pwa/out/scheduler/index.txt +23 -23
  164. package/apps/pwa/out/sessions/index.html +1 -1
  165. package/apps/pwa/out/sessions/index.txt +22 -22
  166. package/apps/pwa/out/settings/devices/index.html +1 -1
  167. package/apps/pwa/out/settings/devices/index.txt +23 -23
  168. package/apps/pwa/out/settings/index.html +1 -1
  169. package/apps/pwa/out/settings/index.txt +23 -23
  170. package/apps/pwa/out/setup/done/index.html +1 -1
  171. package/apps/pwa/out/setup/done/index.txt +23 -23
  172. package/apps/pwa/out/setup/index.html +1 -1
  173. package/apps/pwa/out/setup/index.txt +23 -23
  174. package/apps/pwa/out/share/index.html +1 -1
  175. package/apps/pwa/out/share/index.txt +23 -23
  176. package/apps/pwa/out/showroom/index.html +1 -1
  177. package/apps/pwa/out/showroom/index.txt +22 -22
  178. package/apps/pwa/out/tasks/index.html +1 -1
  179. package/apps/pwa/out/tasks/index.txt +23 -23
  180. package/apps/pwa/out/term/index.html +1 -1
  181. package/apps/pwa/out/term/index.txt +23 -23
  182. package/apps/pwa/out/today/index.html +1 -1
  183. package/apps/pwa/out/today/index.txt +23 -23
  184. package/apps/pwa/out/trace/index.html +1 -1
  185. package/apps/pwa/out/trace/index.txt +23 -23
  186. package/apps/pwa/out/vault/index.html +1 -1
  187. package/apps/pwa/out/vault/index.txt +23 -23
  188. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  189. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  190. package/apps/pwa/out/workflows/index.html +1 -1
  191. package/apps/pwa/out/workflows/index.txt +23 -23
  192. package/apps/pwa/out/workspace/index.html +1 -1
  193. package/apps/pwa/out/workspace/index.txt +23 -23
  194. package/apps/pwa/out/worktrees/index.html +1 -1
  195. package/apps/pwa/out/worktrees/index.txt +23 -23
  196. package/graphs/heal/heal-loop.yaml +1 -1
  197. package/graphs/implement-loop.yaml +1 -1
  198. package/graphs/orchestrator/orchestrator.yaml +59 -0
  199. package/graphs/orchestrator/recipes.yaml +18 -0
  200. package/graphs/release/recipes.yaml +6 -0
  201. package/graphs/release/release-loop.yaml +6 -1
  202. package/graphs/rhythm/daily.yaml +38 -0
  203. package/graphs/rhythm/recipes.yaml +18 -0
  204. package/graphs/rhythm/weekly.yaml +39 -0
  205. package/graphs/video/recipes.yaml +3 -0
  206. package/graphs/video/youtube-summary.yaml +17 -0
  207. package/package.json +1 -1
  208. package/scripts/audit-test-state-writes.ts +72 -17
  209. package/scripts/brand/check.ts +188 -0
  210. package/scripts/ci-typecheck-changed.ts +50 -18
  211. package/scripts/coord-post.sh +11 -1
  212. package/scripts/docs/generate-graph-anatomy.ts +315 -0
  213. package/scripts/docs-cli-check.ts +23 -6
  214. package/scripts/expose-rubric-check.ts +118 -0
  215. package/scripts/expose-rubric-suggest.ts +169 -0
  216. package/scripts/heal/run-role.ts +14 -0
  217. package/scripts/hq/standby-verify.ts +11 -7
  218. package/scripts/lecture-note/node.ts +31 -11
  219. package/scripts/lessons/handbook-page.ts +112 -0
  220. package/scripts/marketing/generate-ax-brief.ts +140 -0
  221. package/scripts/marketing/generate-feature-map.ts +118 -0
  222. package/scripts/release-loop/checklist-gate-node.ts +53 -14
  223. package/scripts/release-loop/gate-node.ts +89 -14
  224. package/scripts/release-loop/publish-node.ts +14 -1
  225. package/scripts/release-loop/unattended-release.ts +23 -1
  226. package/scripts/release-loop/vault-note-node.ts +428 -0
  227. package/scripts/release-story/draft.ts +220 -0
  228. package/scripts/rhythm/daily.ts +570 -0
  229. package/scripts/rhythm/weekly.ts +315 -0
  230. package/scripts/skills/public-check.ts +169 -0
  231. package/scripts/smart-merge/resync-shadow.ts +147 -0
  232. package/scripts/test-deterministic.ts +4 -8
  233. package/scripts/test-diet/effectiveness.ts +195 -0
  234. package/scripts/test-diet/lib.ts +16 -8
  235. package/scripts/test-diet/node.ts +77 -9
  236. package/scripts/ux/demo-rehearsal.ts +2 -2
  237. package/scripts/ux/lib/rehearsal-verdict.ts +1 -0
  238. package/scripts/video/youtube-summary.ts +133 -0
  239. package/skills/diagram-master/SKILL.md +42 -49
  240. package/skills/diagram-master/references/pyproject.toml +4 -1
  241. package/skills/diagram-master/references/routing-guide.md +4 -2
  242. package/skills/grill-me/SKILL.md +1 -0
  243. package/skills/youtube-master/SKILL.md +13 -13
  244. package/skills/youtube-master/package-lock.json +0 -545
  245. package/skills/youtube-master/package.json +2 -5
  246. package/skills/youtube-master/scripts/main.ts +2 -2
  247. package/skills/youtube-master/scripts/transcribe-local.ts +1 -1
  248. package/skills/youtube-master/src/util.ts +30 -7
  249. package/src/agent-env/profile.ts +161 -0
  250. package/src/agent-mission/driver.ts +3 -3
  251. package/src/agent-substrate/pr-reviewer.ts +2 -0
  252. package/src/ask-user-question/pending-questions.ts +5 -0
  253. package/src/auth/github-app-token.ts +112 -13
  254. package/src/autopilot/build/llm-conflict-merge.ts +73 -10
  255. package/src/autopilot/build/merge-intent.ts +53 -0
  256. package/src/briefing/brief-items.ts +121 -0
  257. package/src/chat/index.ts +2 -0
  258. package/src/claims/claims-ledger.ts +232 -0
  259. package/src/claims/claims-render.ts +166 -0
  260. package/src/cli/agent-env-cli.ts +21 -0
  261. package/src/cli/claims-cli.ts +94 -0
  262. package/src/cli/decisions-cli.ts +44 -3
  263. package/src/cli/docs-cli.ts +2 -0
  264. package/src/cli/doctor-cli.ts +23 -1
  265. package/src/cli/doctor-node-modules.ts +147 -0
  266. package/src/cli/flow-cli.ts +11 -0
  267. package/src/cli/freeze-cli.ts +45 -0
  268. package/src/cli/intake-cli.ts +46 -17
  269. package/src/cli/lesson-cli.ts +108 -0
  270. package/src/cli/logs-cli.ts +104 -2
  271. package/src/cli/logs-model-input.ts +131 -0
  272. package/src/cli/pod-cli.ts +2 -0
  273. package/src/cli/pr-cli.ts +29 -1
  274. package/src/cli/private-ledger-commands.ts +54 -4
  275. package/src/cli/pty-drive-verdict.ts +11 -2
  276. package/src/cli/release-cli.ts +190 -33
  277. package/src/cli/rfc-status.ts +91 -0
  278. package/src/cli/seat-requests-cli.ts +52 -0
  279. package/src/connectors/linear.ts +5 -3
  280. package/src/context-bus/condense.ts +15 -20
  281. package/src/context-bus/context-now-public.ts +96 -0
  282. package/src/context-bus/context-now-surfaces.ts +55 -2
  283. package/src/context-bus/context-now.ts +6 -3
  284. package/src/context-bus/coord-events.ts +6 -4
  285. package/src/context-bus/long-term-memory.ts +92 -11
  286. package/src/dashboard/index.ts +75 -22
  287. package/src/dashboard/slash-runtime/chat-persona.ts +66 -0
  288. package/src/dashboard/slash-runtime/dashboard-handlers.ts +99 -0
  289. package/src/dashboard/slash-runtime/loops-table.ts +77 -0
  290. package/src/decisions/decision-cards.ts +33 -7
  291. package/src/decisions/decision-ledger.ts +40 -1
  292. package/src/decisions/proact-meter.ts +286 -0
  293. package/src/decisions/proact1-lite.ts +86 -0
  294. package/src/decisions/web-push-decision-cards.ts +1 -1
  295. package/src/discord/sprint21-runtime.ts +16 -0
  296. package/src/discord-self-message.ts +8 -0
  297. package/src/discord-slash-wire.ts +31 -2
  298. package/src/domains/alert-audit.ts +271 -0
  299. package/src/domains/core-tools.ts +4 -3
  300. package/src/domains/ops-facts-tool.ts +31 -1
  301. package/src/domains/outbound-alert.ts +1 -1
  302. package/src/elanous-config-dir.ts +29 -4
  303. package/src/execution-loop/launch-gate.ts +29 -14
  304. package/src/flow/cards.ts +239 -0
  305. package/src/graph-runner/graph-tick.ts +4 -0
  306. package/src/graph-runner/runner.ts +30 -5
  307. package/src/harness/harness-cli-command.ts +259 -38
  308. package/src/harness/harness-incidents.ts +45 -12
  309. package/src/harness/harness-pod-dispatch.ts +102 -11
  310. package/src/harness/harness-queue-child.ts +3 -3
  311. package/src/harness/harness-queue.ts +583 -65
  312. package/src/harness/helper-repair.ts +87 -0
  313. package/src/harness/helper-scan.ts +151 -0
  314. package/src/harness/launch-stamp.ts +236 -0
  315. package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
  316. package/src/harness/ledger-write-guard.ts +35 -0
  317. package/src/harness/model-input-observation.ts +37 -0
  318. package/src/harness/pod-memory-policy.ts +15 -0
  319. package/src/harness/repository-name.ts +55 -0
  320. package/src/hq/fence-audit.ts +90 -0
  321. package/src/hq/hq.ts +63 -5
  322. package/src/hq/lease.ts +4 -4
  323. package/src/hq/move-plan.ts +126 -0
  324. package/src/hq/promote.ts +332 -0
  325. package/src/index.ts +159 -27
  326. package/src/intake-plane/check.ts +63 -0
  327. package/src/intake-plane/digest.ts +37 -15
  328. package/src/intake-plane/front-classifier.ts +6 -0
  329. package/src/intake-plane/items.ts +3 -2
  330. package/src/intake-plane/lens.ts +177 -0
  331. package/src/intake-plane/linear-wish.ts +39 -11
  332. package/src/intake-plane/route.ts +145 -1
  333. package/src/intake-plane/wish-benchmark-ledger.ts +129 -0
  334. package/src/intake-plane/wish-card.ts +53 -2
  335. package/src/intake-plane/wish-folder.ts +5 -2
  336. package/src/intake-plane/wish-reply.ts +72 -0
  337. package/src/launch-head/seat-goal-queue-lock.fixture.ts +15 -0
  338. package/src/launch-head/seat-goal-queue.ts +198 -0
  339. package/src/lessons/lesson-import.ts +106 -0
  340. package/src/lessons/lesson-ledger.ts +206 -0
  341. package/src/loops/activity.ts +203 -0
  342. package/src/loops/budget.ts +63 -0
  343. package/src/loops/checker.ts +5 -3
  344. package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
  345. package/src/loops/loop-cli.ts +36 -4
  346. package/src/loops/neighbors.ts +40 -0
  347. package/src/loops/observe.ts +10 -0
  348. package/src/loops/orchestrator/author-depth.ts +220 -0
  349. package/src/loops/orchestrator/author-input-check.ts +82 -0
  350. package/src/loops/orchestrator/author-ledger.ts +232 -0
  351. package/src/loops/orchestrator/finish-rate.ts +175 -0
  352. package/src/loops/orchestrator/overlap.ts +51 -12
  353. package/src/loops/orchestrator/resync-shadow.ts +227 -0
  354. package/src/loops/orchestrator/seat-share.ts +56 -0
  355. package/src/loops/orchestrator/tick.ts +309 -0
  356. package/src/loops/orchestrator/traffic.ts +134 -13
  357. package/src/loops/registry.ts +535 -9
  358. package/src/loops/status-rows.ts +118 -0
  359. package/src/loops/verdict.ts +17 -5
  360. package/src/maturity/bot-slash-catalog.ts +66 -0
  361. package/src/maturity/feature-maturity.ts +9 -5
  362. package/src/maturity/tui-slash-availability.ts +31 -0
  363. package/src/mss/logging/log-store.ts +35 -4
  364. package/src/nexus/api/dashboard.ts +6 -1
  365. package/src/nexus/api/http-server.ts +33 -3
  366. package/src/nexus/api/loop-edges.ts +173 -0
  367. package/src/nexus/api/merge-approvals-gate.ts +1 -1
  368. package/src/nexus/api/schedules-read.ts +18 -1
  369. package/src/nexus/api/seat-requests.ts +79 -56
  370. package/src/nexus/api/task-cards-api.ts +44 -5
  371. package/src/nexus/api/workflows.ts +2 -1
  372. package/src/prompt-enhance/enhance.ts +6 -1
  373. package/src/release-loop/auto-start.ts +51 -0
  374. package/src/release-loop/checklist.ts +22 -4
  375. package/src/release-loop/feature-store.ts +71 -32
  376. package/src/release-loop/landing-freeze.ts +121 -0
  377. package/src/release-loop/merged-pr-checklist.ts +125 -0
  378. package/src/release-loop/placement.ts +302 -0
  379. package/src/release-loop/release-schedule.ts +3 -1
  380. package/src/seat-dispatch/ceo-commands.ts +2 -2
  381. package/src/seat-dispatch/seat-ask.ts +16 -3
  382. package/src/seat-dispatch/seat-questions.ts +58 -0
  383. package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
  384. package/src/seat-dispatch/seat-request-ledger.ts +133 -0
  385. package/src/seat-loop/seat-loop.ts +531 -40
  386. package/src/self-dev/dev-pipeline.ts +14 -6
  387. package/src/self-dev/draft-sweep.ts +162 -6
  388. package/src/self-dev/draft-triage-rules.ts +25 -8
  389. package/src/self-dev/orchestrate.ts +15 -1
  390. package/src/self-dev/review-provider-fallback.ts +6 -1
  391. package/src/self-dev/run-store.ts +73 -1
  392. package/src/self-dev/run-supervisor.ts +57 -2
  393. package/src/self-dev/self-orchestrate-runtime.ts +5 -1
  394. package/src/self-implement/budget-gate.ts +29 -0
  395. package/src/self-implement/expose-gate.ts +47 -0
  396. package/src/self-implement/frozen-merges.ts +295 -0
  397. package/src/self-implement/gate-cli.ts +10 -4
  398. package/src/self-implement/gate-scope.ts +46 -9
  399. package/src/self-implement/goal-archive.ts +222 -0
  400. package/src/self-implement/goal-author.ts +43 -5
  401. package/src/self-implement/goal-clarification-escalation.ts +5 -0
  402. package/src/self-implement/goal-lookup.ts +147 -0
  403. package/src/self-implement/heal-intake.ts +197 -0
  404. package/src/self-implement/host-regate.ts +32 -2
  405. package/src/self-implement/l8-merge-queue.ts +132 -0
  406. package/src/self-implement/orchestrator.ts +299 -48
  407. package/src/self-implement/pipeline-shape.ts +1 -1
  408. package/src/self-implement/revert-guard.ts +97 -0
  409. package/src/self-implement/run-ledger.ts +3 -1
  410. package/src/self-implement/run-status-mapping.ts +2 -0
  411. package/src/self-implement/seams.ts +29 -9
  412. package/src/self-implement/self-implement-cli.ts +1 -0
  413. package/src/self-implement/speed-claims.ts +338 -0
  414. package/src/session/index.ts +2 -0
  415. package/src/steward/triage.ts +74 -3
  416. package/src/steward/working-backwards.ts +4 -1
  417. package/src/task-cards/card-cli.ts +10 -2
  418. package/src/task-cards/card-store.ts +10 -6
  419. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
  420. package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
  421. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
  422. package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
  423. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
  424. package/src/task-orchestrator/surfaces/pod-pool.ts +60 -8
  425. package/src/task-orchestrator/surfaces/self-implement-pod.ts +224 -49
  426. package/src/task-orchestrator/surfaces/self-implement.ts +5 -1
  427. package/src/telegram-commands.ts +27 -3
  428. package/src/telegram-loops-command.ts +44 -0
  429. package/src/telegram.ts +25 -4
  430. package/src/user-config.ts +146 -18
  431. package/src/version/packed-revision.json +1 -1
  432. package/src/workflow-runtime/nodes/bash.ts +9 -1
  433. package/src/workflow-runtime/storage.ts +31 -13
  434. package/apps/pwa/out/_next/static/D51l8jUBplJrIpWvENsDK/_buildManifest.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/1394-931c9827067b2276.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/3992-fe5c06d6b31f8b25.js +0 -25
  445. package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +0 -2
  449. package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
  459. package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +0 -1
  463. package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/8605-9da4678f8a310d18.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/9269.d2a4795bc6812717.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e49b9b76c565a0b1.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/app/botlab/page-f14987d5850a31e0.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/app/chat/page-4c1dbb21861beab2.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +0 -1
  479. package/apps/pwa/out/_next/static/chunks/app/control/page-f6bc66b919b2ea7d.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/app/design-check/page-60603c1e056c9b25.js +0 -1
  481. package/apps/pwa/out/_next/static/chunks/app/editor/page-a0ea8f5a2341ee6b.js +0 -1
  482. package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +0 -1
  485. package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/app/intake/page-31012264da8a341f.js +0 -1
  487. package/apps/pwa/out/_next/static/chunks/app/layout-50309aeb0bdd554a.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +0 -1
  489. package/apps/pwa/out/_next/static/chunks/app/market/page-0696ee3fac9fe56c.js +0 -1
  490. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66c27608f651b866.js +0 -1
  491. package/apps/pwa/out/_next/static/chunks/app/morning/page-6ddd2109b7fc3a80.js +0 -1
  492. package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +0 -1
  493. package/apps/pwa/out/_next/static/chunks/app/observatory/page-7a2bf830038a2264.js +0 -1
  494. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1f327f18694fabca.js +0 -1
  495. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e5cef2fe6e4028df.js +0 -1
  496. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cf596128d2b420ba.js +0 -1
  497. package/apps/pwa/out/_next/static/chunks/app/outputs/page-f672536f14f0969f.js +0 -1
  498. package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +0 -1
  499. package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-29a709616df9b050.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/app/sessions/page-fee842d7c2151129.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-78b40a344ed22f24.js +0 -1
  503. package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +0 -1
  504. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-a011dd5b0bff29c5.js +0 -1
  505. package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +0 -1
  506. package/apps/pwa/out/_next/static/chunks/app/setup/page-d92e32d51ab5157e.js +0 -1
  507. package/apps/pwa/out/_next/static/chunks/app/share/page-5f21aed6932ef375.js +0 -1
  508. package/apps/pwa/out/_next/static/chunks/app/showroom/page-1e1a53946804debf.js +0 -1
  509. package/apps/pwa/out/_next/static/chunks/app/tasks/page-a1833cd43d832601.js +0 -1
  510. package/apps/pwa/out/_next/static/chunks/app/today/page-2a511d320f762e06.js +0 -1
  511. package/apps/pwa/out/_next/static/chunks/app/trace/page-1c3e03f88dac1e21.js +0 -1
  512. package/apps/pwa/out/_next/static/chunks/app/vault/page-c4d5289c2ea97a89.js +0 -1
  513. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b91f8952deaeaf27.js +0 -1
  514. package/apps/pwa/out/_next/static/chunks/app/workflows/page-71b43a2588489987.js +0 -1
  515. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +0 -1
  516. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +0 -1
  517. package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +0 -1
  518. package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +0 -1
  519. package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +0 -1
  520. package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +0 -1
  521. package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +0 -1
  522. package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +0 -1
  523. package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +0 -1
  524. /package/apps/pwa/out/_next/static/{D51l8jUBplJrIpWvENsDK → LZcstaO9_NXypPFmujolv}/_ssgManifest.js +0 -0
@@ -0,0 +1,66 @@
1
+ import { awaitGlobalPersonaLoad, getGlobalPersonaRegistry } from '../../persona/global-registry.js';
2
+ import { resolvePersonaAddress } from '../../intake-plane/persona-answer.js';
3
+ import { loadSession, updateSessionMeta } from '../../session/index.js';
4
+ import { assemblePersonaPrompt } from '../../persona/prompt-assembler.js';
5
+ import type { LLMMessage } from '../../llm.js';
6
+
7
+ /** Unattached conversations use a dashboard-local id; attached sessions use persisted metadata. */
8
+ export class TuiChatPersonaSelection {
9
+ private readonly selected = new Map<string, string>();
10
+
11
+ get(sessionId: string): string | undefined {
12
+ return loadSession(sessionId)?.meta.chatPersonaId ?? this.selected.get(sessionId);
13
+ }
14
+
15
+ set(sessionId: string, personaId: string | undefined): void {
16
+ const meta = updateSessionMeta(sessionId, (m) => {
17
+ if (personaId) m.chatPersonaId = personaId;
18
+ else delete m.chatPersonaId;
19
+ });
20
+ if (!meta) {
21
+ if (personaId) this.selected.set(sessionId, personaId);
22
+ else this.selected.delete(sessionId);
23
+ } else {
24
+ this.selected.delete(sessionId);
25
+ }
26
+ }
27
+ }
28
+
29
+ export async function handleTuiChatPersona(
30
+ args: readonly string[], sessionId: string,
31
+ selection: TuiChatPersonaSelection,
32
+ ): Promise<string> {
33
+ if (args[0] === '-') {
34
+ if (args.length !== 1) return '쓰는 법: /persona [이름|-]';
35
+ selection.set(sessionId, undefined);
36
+ return '이 대화의 페르소나를 뺐습니다';
37
+ }
38
+ try {
39
+ await awaitGlobalPersonaLoad();
40
+ const registry = getGlobalPersonaRegistry();
41
+ if (!args.length) {
42
+ const selected = selection.get(sessionId);
43
+ const personas = registry.list().sort((a, b) => a.displayName.localeCompare(b.displayName));
44
+ const current = personas.find(p => p.personaId === selected);
45
+ return `이 대화의 페르소나: ${current ? `${current.displayName} (${current.personaId})` : '없음'}\n고를 수 있는 페르소나: ${personas.map(p => `${p.displayName} (${p.personaId})${p.personaId === selected ? ' (지금)' : ''}`).join(' · ') || '없음'}\n고르기: /persona <이름> · 빼기: /persona -`;
46
+ }
47
+ const name = args.join(' ');
48
+ const persona = resolvePersonaAddress(name, registry);
49
+ if (!persona) return `그런 페르소나가 없습니다: ${name}`;
50
+ selection.set(sessionId, persona.personaId);
51
+ return `${persona.displayName} 페르소나를 이 대화에 골랐습니다`;
52
+ } catch {
53
+ return '페르소나 목록을 읽지 못했습니다';
54
+ }
55
+ }
56
+
57
+ export function tuiChatPersonaMessages(
58
+ sessionId: string, selection: TuiChatPersonaSelection,
59
+ ): LLMMessage[] {
60
+ const id = selection.get(sessionId);
61
+ if (!id) return [];
62
+ const persona = getGlobalPersonaRegistry().get(id);
63
+ if (!persona) return [];
64
+ const prompt = assemblePersonaPrompt(persona, '').systemPrompt;
65
+ return prompt ? [{ role: 'system', content: prompt }] : [];
66
+ }
@@ -273,6 +273,7 @@
273
273
  // of circular dependency on dashboard/index.ts.
274
274
 
275
275
  import { tuiNowSlash } from '../../context-bus/context-now-surfaces.js';
276
+ import { handleTuiChatPersona, type TuiChatPersonaSelection } from './chat-persona.js';
276
277
  import { createWishCard } from '../../intake-plane/wish-card.js';
277
278
  import { CardStore } from '../../task-cards/card-store.js';
278
279
  import type { ContextNowDeps } from '../../context-bus/context-now.js';
@@ -390,6 +391,8 @@ import { resolveTerminalMoveDestination } from '../../terminal-matrix/mobility.j
390
391
  import { loadPersistedSessions } from '../../terminal/session-persistence.js';
391
392
  import { dispatchTerminalModalObserve } from '../../skills/tools/terminal-modal.js';
392
393
  import { SlashCommandRegistry } from './registry.js';
394
+ import { cronSecondDueAt, filterLoopRows, formatLoopsTable, localLoopRows, type LoopsTableRow } from './loops-table.js';
395
+ import { loopCronVerdict } from '../../loops/verdict.js';
393
396
  import { executeImmediateDashboardSlash } from '../input/slash-executor.js';
394
397
  import {
395
398
  buildContextSlashCommand,
@@ -1029,6 +1032,7 @@ export interface DashboardSlashContext {
1029
1032
  /** Currently-attached session id (TUI's pointer to a JSONL on disk). */
1030
1033
  getAttachedSessionId(): string | null;
1031
1034
  setAttachedSessionId(id: string | null): void;
1035
+ chatPersona?: { sessionId(): string; selection: TuiChatPersonaSelection; newConversation?(): void };
1032
1036
  /** Telegram chat id paired with the attached session, when handed off. */
1033
1037
  getAttachedChatId(): number | null;
1034
1038
  setAttachedChatId(id: number | null): void;
@@ -1038,6 +1042,9 @@ export interface DashboardSlashContext {
1038
1042
  send: (cfg: ReturnType<typeof getUserConfig>, text: string) => Promise<boolean>;
1039
1043
  };
1040
1044
 
1045
+ /** Test seam for the local read-only loop and cron registry. */
1046
+ loopsLocalRows?: (now: Date) => LoopsTableRow[];
1047
+
1041
1048
  sessionSlash: {
1042
1049
  /** opts.remote — when the dashboard is attached to a remote daemon. */
1043
1050
  remoteDaemon(): { url: string; token: string | undefined } | null;
@@ -1364,6 +1371,28 @@ function emitReplyToChat(ctx: Pick<DashboardSlashContext, 'pushChatLine'>, line:
1364
1371
  ctx.pushChatLine(line);
1365
1372
  }
1366
1373
 
1374
+ async function fetchLoopsDaemonJson<T>(url: string, headers: Record<string, string>): Promise<T> {
1375
+ const controller = new AbortController();
1376
+ let timeout: ReturnType<typeof setTimeout> | undefined;
1377
+ try {
1378
+ return await Promise.race([
1379
+ (async () => {
1380
+ const response = await fetch(url, { headers, signal: controller.signal });
1381
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
1382
+ return await response.json() as T;
1383
+ })(),
1384
+ new Promise<never>((_, reject) => {
1385
+ timeout = setTimeout(() => {
1386
+ controller.abort();
1387
+ reject(new Error('request timed out'));
1388
+ }, 1_000);
1389
+ }),
1390
+ ]);
1391
+ } finally {
1392
+ if (timeout) clearTimeout(timeout);
1393
+ }
1394
+ }
1395
+
1367
1396
  export function buildDashboardSlashRegistry(nowDeps?: ContextNowDeps): SlashCommandRegistry<DashboardSlashContext, DashboardSlashReturn> {
1368
1397
  const registry = new SlashCommandRegistry<DashboardSlashContext, DashboardSlashReturn>();
1369
1398
 
@@ -1371,6 +1400,61 @@ export function buildDashboardSlashRegistry(nowDeps?: ContextNowDeps): SlashComm
1371
1400
  for (const line of tuiNowSlash(args, nowDeps)) ctx.pushChatLine(line);
1372
1401
  });
1373
1402
 
1403
+ registry.register('loops', async (args, ctx) => {
1404
+ if (args.length > 1) {
1405
+ ctx.pushChatLine(ctx.warning(' usage: /loops [owner]'));
1406
+ return;
1407
+ }
1408
+ const now = new Date(Date.now());
1409
+ let rows: LoopsTableRow[] = [];
1410
+ const remote = ctx.sessionSlash.remoteDaemon();
1411
+ if (remote) {
1412
+ const base = deriveDaemonHttpBase(remote.url);
1413
+ if (base) {
1414
+ try {
1415
+ const headers: Record<string, string> = remote.token ? { authorization: `Bearer ${remote.token}` } : {};
1416
+ const body = await fetchLoopsDaemonJson<{ schedules?: Array<{ name: string; domain?: string | null; runVia?: string | null;
1417
+ source: string; cron?: string | null; state: string; lastRun?: { at: string; status: string | null; exit: number | null } | null;
1418
+ intervalMs?: number | null }> }>(`${base}/v1/schedules?includeOff=1`, headers);
1419
+ rows = (body.schedules ?? []).map(schedule => ({
1420
+ layer: 'daemon cron', name: schedule.name, owner: schedule.domain ?? '—', mode: schedule.runVia ?? schedule.source,
1421
+ lastRun: schedule.lastRun?.at ?? null,
1422
+ verdict: loopCronVerdict({ enabled: schedule.state !== 'off', lastRunAt: schedule.lastRun?.at ?? null,
1423
+ lastStatus: schedule.lastRun?.exit != null && schedule.lastRun.exit !== 0 ? 'failed' : schedule.lastRun?.status ?? null,
1424
+ intervalMs: schedule.intervalMs ?? null,
1425
+ secondDueAt: cronSecondDueAt(schedule.cron ?? null, schedule.lastRun?.at ?? null) }, now.getTime()),
1426
+ }));
1427
+ } catch (err) {
1428
+ ctx.pushChatLine(ctx.warning(` daemon cron unavailable: ${err instanceof Error ? err.message : String(err)}`));
1429
+ }
1430
+ try {
1431
+ const headers: Record<string, string> = remote.token ? { authorization: `Bearer ${remote.token}` } : {};
1432
+ const body = await fetchLoopsDaemonJson<{ loops?: { loops?: Array<{
1433
+ name: string; label: string; armed: boolean | null; category?: string;
1434
+ last: { at: string; status: string } | null;
1435
+ }> } | null }>(`${base}/v1/dashboard/loops`, headers);
1436
+ // dashboardLoops returns LoopStatus (no owner field). Its category is
1437
+ // exec/reflect, not an owner; keep ownership unknown rather than
1438
+ // attributing every daemon loop to a guessed domain.
1439
+ rows.push(...(body.loops?.loops ?? []).map(loop => ({
1440
+ layer: 'daemon loop', name: loop.label || loop.name, owner: '—', mode: loop.category ?? 'loop',
1441
+ lastRun: loop.last?.at ?? null,
1442
+ verdict: loopCronVerdict({ enabled: loop.armed !== false, lastRunAt: loop.last?.at ?? null,
1443
+ lastStatus: loop.last?.status ?? null, intervalMs: null }, now.getTime()),
1444
+ })));
1445
+ } catch (err) {
1446
+ ctx.pushChatLine(ctx.warning(` daemon loops unavailable: ${err instanceof Error ? err.message : String(err)}`));
1447
+ }
1448
+ }
1449
+ }
1450
+ try { rows.push(...(ctx.loopsLocalRows ?? localLoopRows)(now)); }
1451
+ catch (err) { ctx.pushChatLine(ctx.warning(` local loops unavailable: ${err instanceof Error ? err.message : String(err)}`)); }
1452
+ const filtered = filterLoopRows(rows, args[0]);
1453
+ for (const line of formatLoopsTable(filtered, text => ctx.error(text))) ctx.pushChatLine(` ${line}`);
1454
+ if (!filtered.length) ctx.pushChatLine(ctx.muted(' No loops or cron jobs found.'));
1455
+ ctx.setChatScrollOffset(-1);
1456
+ });
1457
+
1374
1458
  registry.register('wish', (args, ctx) => {
1375
1459
  const text = args.join(' ').trim();
1376
1460
  if (!text) {
@@ -1427,6 +1511,16 @@ export function buildDashboardSlashRegistry(nowDeps?: ContextNowDeps): SlashComm
1427
1511
  }
1428
1512
  });
1429
1513
 
1514
+ registry.register('persona', async (args, ctx) => {
1515
+ if (!ctx.chatPersona) {
1516
+ ctx.pushChatLine(ctx.warning(' 페르소나를 사용할 수 없습니다'));
1517
+ } else {
1518
+ const text = await handleTuiChatPersona(args, ctx.chatPersona.sessionId(), ctx.chatPersona.selection);
1519
+ for (const line of text.split('\n')) ctx.pushChatLine(ctx.muted(` ${line}`));
1520
+ }
1521
+ ctx.setChatScrollOffset(-1);
1522
+ });
1523
+
1430
1524
  registry.register('directive', async (args, ctx) => {
1431
1525
  try {
1432
1526
  const { addDirective } = await import('../../steward/directive.js');
@@ -4042,6 +4136,7 @@ export function buildDashboardSlashRegistry(nowDeps?: ContextNowDeps): SlashComm
4042
4136
  const prev = attached;
4043
4137
  ctx.setAttachedSessionId(null);
4044
4138
  ctx.setAttachedChatId(null);
4139
+ ctx.chatPersona?.newConversation?.();
4045
4140
  const chatHistory = ctx.compactSlash.chatHistory;
4046
4141
  const sysFromDash = chatHistory.find(m => m.role === 'system');
4047
4142
  chatHistory.length = 0;
@@ -4151,6 +4246,10 @@ export function buildDashboardSlashRegistry(nowDeps?: ContextNowDeps): SlashComm
4151
4246
  sessionAppendMessage(meta.id, { role: m.role, content, ts: nowTs });
4152
4247
  }
4153
4248
  sessionAttachTelegram(meta.id, chatTarget);
4249
+ if (ctx.chatPersona) {
4250
+ const selected = ctx.chatPersona.selection.get(ctx.chatPersona.sessionId());
4251
+ if (selected) ctx.chatPersona.selection.set(meta.id, selected);
4252
+ }
4154
4253
  ctx.setAttachedSessionId(meta.id);
4155
4254
  ctx.setAttachedChatId(chatTarget);
4156
4255
  ctx.chatLines.push(ctx.success(` ✓ attached session ${meta.id.slice(0, 8)} → chat ${chatTarget} (${chatHistory.length} turns mirrored)`));
@@ -0,0 +1,77 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { existsSync } from 'node:fs';
3
+ import { listLoops, type LoopEntry } from '../../loops/registry.js';
4
+ import { loopCronVerdict, type LoopCronVerdict } from '../../loops/verdict.js';
5
+ import { listSchedules, schedulesDbPath, type ScheduleRow } from '../../domains/schedule-registry.js';
6
+ import { nextRuns } from '../../domains/schedule-state.js';
7
+
8
+ export interface LoopsTableRow {
9
+ layer: string;
10
+ name: string;
11
+ owner: string;
12
+ mode: string;
13
+ lastRun: string | null;
14
+ verdict: LoopCronVerdict;
15
+ }
16
+
17
+ /** Read the already-inventoried local registry without opening/migrating a writable store. */
18
+ export function localLoopRows(now: Date): LoopsTableRow[] {
19
+ let schedules: ScheduleRow[] = [];
20
+ if (existsSync(schedulesDbPath())) {
21
+ const db = new Database(schedulesDbPath(), { readonly: true });
22
+ try { schedules = listSchedules(db); } finally { db.close(); }
23
+ }
24
+ const loops = listLoops({ schedules, now });
25
+ const rows: LoopsTableRow[] = loops.map(loop => graphLoopRow(loop, schedules, now));
26
+ const graphJobIds = new Set(loops.flatMap(loop => loop.jobs.map(job => job.id)));
27
+ for (const schedule of schedules) {
28
+ if (graphJobIds.has(schedule.id)) continue;
29
+ rows.push(scheduleRow(schedule, 'local cron', now));
30
+ }
31
+ return rows;
32
+ }
33
+
34
+ export function cronSecondDueAt(cron: string | null, lastRun: string | null): string | null {
35
+ if (!cron || !lastRun) return null;
36
+ const last = new Date(lastRun);
37
+ if (!Number.isFinite(last.getTime())) return null;
38
+ return nextRuns(cron, last, 2)[1] ?? null;
39
+ }
40
+
41
+ export function graphLoopRow(loop: LoopEntry, schedules: ScheduleRow[], now: Date): LoopsTableRow {
42
+ const active = loop.jobs.map(job => schedules.find(row => row.id === job.id)).filter((r): r is ScheduleRow => !!r);
43
+ const cron = loop.trigger.cron ?? active[0]?.cron ?? null;
44
+ const interval = active.find(r => r.interval_ms && r.interval_ms > 0)?.interval_ms ?? null;
45
+ const lastRun = loop.lastRun?.at ?? active.map(r => r.last_run).filter((at): at is string => !!at).sort().at(-1) ?? null;
46
+ const status = loop.lastRun?.status ?? active.find(r => r.last_run === lastRun)?.last_status ?? null;
47
+ return {
48
+ layer: 'local loop', name: loop.title, owner: loop.owner ?? '—', mode: loop.mode ?? (loop.trigger.cron ? 'cron' : 'event'), lastRun,
49
+ verdict: loopCronVerdict({ enabled: loop.enabled, lastRunAt: lastRun, lastStatus: status, intervalMs: interval,
50
+ secondDueAt: cronSecondDueAt(cron, lastRun) }, now.getTime()),
51
+ };
52
+ }
53
+
54
+ export function scheduleRow(row: ScheduleRow, layer: string, now: Date): LoopsTableRow {
55
+ return {
56
+ layer, name: row.name, owner: row.domain ?? '—', mode: row.run_via || row.source,
57
+ lastRun: row.last_run,
58
+ verdict: loopCronVerdict({ enabled: !!row.enabled && row.disabled_reason !== 'vanished', lastRunAt: row.last_run,
59
+ lastStatus: row.last_exit !== null && row.last_exit !== undefined && row.last_exit !== 0 ? 'failed' : row.last_status ?? null,
60
+ intervalMs: row.interval_ms, secondDueAt: cronSecondDueAt(row.cron, row.last_run) }, now.getTime()),
61
+ };
62
+ }
63
+
64
+ export function filterLoopRows(rows: LoopsTableRow[], owner?: string): LoopsTableRow[] {
65
+ return owner ? rows.filter(row => row.owner !== '—' && row.owner.toLowerCase() === owner.toLowerCase()) : rows;
66
+ }
67
+
68
+ export function formatLoopsTable(rows: LoopsTableRow[], verdictColor: (text: string) => string): string[] {
69
+ const columns = ['LAYER', 'NAME', 'OWNER', 'MODE', 'LAST RUN', 'VERDICT'];
70
+ const data = rows.map(r => [r.layer, r.name, r.owner, r.mode, r.lastRun ?? '—', r.verdict]);
71
+ const widths = columns.map((col, i) => Math.max(col.length, ...data.map(cells => cells[i]!.length)));
72
+ const format = (cells: string[]) => cells.map((cell, i) => cell.padEnd(widths[i]!)).join(' ');
73
+ return [format(columns), ...rows.map((row, i) => {
74
+ const cells = data[i]!;
75
+ return `${format(cells.slice(0, 5))} ${row.verdict === 'late' || row.verdict === 'failed' ? verdictColor(cells[5]!) : cells[5]}`;
76
+ })];
77
+ }
@@ -28,6 +28,7 @@ export interface CardTransport {
28
28
  const IRREVERSIBLE = new Set(['money', 'publish', 'security', 'irreversible', 'secret']);
29
29
  export const DECISION_DATA_PREFIX = 'dec:';
30
30
  const REMIND_BEFORE_MS = 2 * 3600_000;
31
+ const WEBPUSH_MAX_SEND_ATTEMPTS = 3;
31
32
 
32
33
  export function isIrreversible(entry: Pick<DecisionEntry, 'category'>): boolean {
33
34
  return IRREVERSIBLE.has(entry.category);
@@ -99,7 +100,8 @@ interface CardState {
99
100
  /** First start of this platform's service — decisions raised before it are not pushed (no burst of old items at
100
101
  * deploy); they stay reachable through `/decisions`. */
101
102
  since?: string;
102
- cards: Record<string, { refs: CardRef[]; closed?: boolean; remindedAt?: string; note?: string }>;
103
+ cards: Record<string, { refs: CardRef[]; closed?: boolean; remindedAt?: string; note?: string; attempts?: number; noSubscribers?: boolean }>;
104
+ testOriginQuestions?: Record<string, true>;
103
105
  }
104
106
 
105
107
  export interface DecisionCardServiceOptions {
@@ -165,16 +167,27 @@ export class DecisionCardService {
165
167
  for (const record of pending.questions) {
166
168
  for (const question of record.questions) {
167
169
  const data = { questionId: record.id, runId: record.runId };
170
+ const logSkip = (reason: string): void => debug.log('hitl.card-bridge', 'skipped', { ...data, reason });
171
+ // Path is not provenance: operational goals can live under the OS temporary directory too.
172
+ if (record.testOrigin === true) {
173
+ state.testOriginQuestions ??= {};
174
+ if (!state.testOriginQuestions[record.id]) {
175
+ state.testOriginQuestions[record.id] = true;
176
+ dirty = true;
177
+ debug.log('hitl.card-bridge', 'skipped', { ...data, reason: 'test-origin' });
178
+ }
179
+ continue;
180
+ }
168
181
  if (record.expiresAt && record.expiresAt <= this.now().toISOString()) {
169
- debug.log('hitl.card-bridge', 'skipped', { ...data, reason: 'expired' });
182
+ logSkip('expired');
170
183
  continue;
171
184
  }
172
185
  if (question.impact !== 'high' && question.impact !== 'critical') {
173
- debug.log('hitl.card-bridge', 'skipped', { ...data, reason: 'impact' });
186
+ logSkip('impact');
174
187
  continue;
175
188
  }
176
189
  if (record.questions.length !== 1 || question.options.length < 2 || question.options.length > 4) {
177
- debug.log('hitl.card-bridge', 'skipped', { ...data, reason: 'options' });
190
+ logSkip('options');
178
191
  continue;
179
192
  }
180
193
  if (seen.has(record.id)) {
@@ -200,7 +213,7 @@ export class DecisionCardService {
200
213
  all.unshift(entry);
201
214
  debug.log('hitl.card-bridge', 'raised', { ...data, decisionId: entry.id });
202
215
  } catch (error) {
203
- debug.log('hitl.card-bridge', 'skipped', { ...data, reason: error instanceof Error ? error.name : 'unknown' });
216
+ logSkip(error instanceof Error ? error.name : 'unknown');
204
217
  }
205
218
  }
206
219
  }
@@ -208,13 +221,26 @@ export class DecisionCardService {
208
221
  const chats = await this.opts.transport.ownerChats().catch(() => [] as string[]);
209
222
  for (const e of all) {
210
223
  const card = state.cards[e.id];
211
- if (e.status === 'open' && !card && e.options.length >= 2 && e.raisedAt !== undefined && e.raisedAt >= state.since) {
224
+ if (e.status === 'open' && (!card || (platform === 'webpush' && !card.refs.length && (card.attempts ?? 0) < WEBPUSH_MAX_SEND_ATTEMPTS))
225
+ && e.options.length >= 2 && e.raisedAt !== undefined && e.raisedAt >= state.since) {
226
+ if (platform === 'webpush' && !chats.length) {
227
+ if (!card?.noSubscribers) {
228
+ state.cards[e.id] = { ...(card ?? { refs: [] }), noSubscribers: true };
229
+ dirty = true;
230
+ debug.log('decisions.webpush', 'no-subscribers', { id: e.id });
231
+ }
232
+ continue;
233
+ }
212
234
  const refs: CardRef[] = [];
213
235
  for (const chat of chats) {
214
236
  const ref = await this.opts.transport.send(chat, renderCard(e)).catch(() => null);
215
237
  if (ref) refs.push(ref);
216
238
  }
217
- if (refs.length) { state.cards[e.id] = { refs }; sent++; }
239
+ if (platform === 'webpush') {
240
+ state.cards[e.id] = { ...(card ?? { refs: [] }), refs, attempts: (card?.attempts ?? 0) + 1 };
241
+ dirty = true;
242
+ } else if (refs.length) state.cards[e.id] = { refs };
243
+ if (refs.length) sent++;
218
244
  debug.log('decisions.telegram', 'card-sent', { platform, id: e.id, category: e.category, chats: chats.length, delivered: refs.length });
219
245
  continue;
220
246
  }
@@ -31,7 +31,15 @@ export interface DecisionEntry {
31
31
  history: Array<{ type: 'raised' | 'options-added' | 'decided' | 'withdrawn'; at?: string; by: string; version?: Versions; choice?: string; reason?: string }>;
32
32
  }
33
33
  export type RaiseInput = Pick<DecisionEntry, 'title' | 'category' | 'scqa' | 'options' | 'recommendation' | 'raisedBy' | 'refs' | 'dueAt' | 'resume' | 'pendingQuestion' | 'crossCheck' | 'crossCheckSkipped' | 'alternative' | 'dissent'> & { raisedAt?: string };
34
- type Event = { type: 'raised'; entry: DecisionEntry } | { type: 'options-added'; id: string; at: string; options: DecisionOption[]; by: string } | { type: 'decided'; id: string; at?: string; by: DecisionActor; choice?: string; version?: Versions; note?: string } | { type: 'withdrawn'; id: string; at: string; reason: string; version: Versions };
34
+ export type Seat = 'OP' | 'MK' | 'TC' | 'UX';
35
+ export interface SeatDecisionRecord {
36
+ id: string; title: string; decision: string; seat: Seat; delegation: string;
37
+ reporting: 'posthoc'; recordedAt: string;
38
+ /** Present only when the actual decision time is known. */
39
+ decidedAt?: string; versionAtDecision?: Versions; refs?: string[];
40
+ }
41
+ export type SeatDecisionInput = Pick<SeatDecisionRecord, 'title' | 'decision' | 'seat' | 'delegation' | 'refs'> & { decidedAt?: string };
42
+ type Event = { type: 'raised'; entry: DecisionEntry } | { type: 'seat-recorded'; entry: SeatDecisionRecord } | { type: 'options-added'; id: string; at: string; options: DecisionOption[]; by: string } | { type: 'decided'; id: string; at?: string; by: DecisionActor; choice?: string; version?: Versions; note?: string } | { type: 'withdrawn'; id: string; at: string; reason: string; version: Versions };
35
43
  export interface DecisionLedgerOptions extends VersionOptions { stateDir?: string; now?: () => Date; resolveVersion?: (at: string) => Versions; writeAnswer?: typeof writePendingQuestionAnswer }
36
44
 
37
45
  /** null = the decision resumes no run; otherwise whether the answer file was written (or already matched). */
@@ -196,6 +204,7 @@ export class DecisionLedger {
196
204
  if (filters.category && !CATEGORIES.includes(filters.category)) throw new Error('invalid category');
197
205
  const map = new Map<string, DecisionEntry>();
198
206
  for (const event of this.events()) {
207
+ if (event.type === 'seat-recorded') continue;
199
208
  if (event.type === 'raised') {
200
209
  if (map.has(event.entry.id)) throw new Error(`duplicate decision: ${event.entry.id}`);
201
210
  map.set(event.entry.id, event.entry);
@@ -221,6 +230,36 @@ export class DecisionLedger {
221
230
  && (!filters.decidedBy || e.decidedBy?.kind === filters.decidedBy)
222
231
  && (!filters.category || e.category === filters.category)).sort((a, b) => (b.raisedAt ?? b.importedAt ?? '').localeCompare(a.raisedAt ?? a.importedAt ?? '') || a.id.localeCompare(b.id));
223
232
  }
233
+ /** Delegated decisions are already made: they share the append-only ledger, never the owner's open-card state machine. */
234
+ recordSeatDecision(input: SeatDecisionInput): SeatDecisionRecord {
235
+ const seat = input.seat;
236
+ if (!['OP', 'MK', 'TC', 'UX'].includes(seat)) throw new Error('invalid seat');
237
+ const title = safe(single(input.title, 'title'));
238
+ const decision = safe(single(input.decision, 'decision'));
239
+ const delegation = safe(single(input.delegation, 'delegation'));
240
+ const refs = input.refs?.map(ref => safe(single(ref, 'ref')));
241
+ const decidedAt = input.decidedAt === undefined ? undefined : utc(input.decidedAt);
242
+ return this.locked(() => {
243
+ const existing = this.seatReport();
244
+ if (refs?.some(ref => existing.some(entry => entry.seat === seat && entry.title === title && entry.decision === decision && entry.refs?.includes(ref)))) throw new Error('seat decision source already recorded');
245
+ const recordedAt = utc(this.now().toISOString());
246
+ const prefix = `SD-${recordedAt.slice(0, 10).replaceAll('-', '')}-`;
247
+ const next = existing.reduce((n, entry) => entry.id.startsWith(prefix) ? Math.max(n, Number(entry.id.slice(prefix.length)) + 1) : n, 1);
248
+ const entry: SeatDecisionRecord = { id: `${prefix}${String(next).padStart(2, '0')}`, title, decision, seat, delegation,
249
+ reporting: 'posthoc', recordedAt, ...(decidedAt ? { decidedAt, versionAtDecision: this.version(decidedAt) } : {}), ...(refs?.length ? { refs } : {}) };
250
+ this.append({ type: 'seat-recorded', entry });
251
+ debug.log('decisions', 'seat-recorded', { id: entry.id, seat });
252
+ return entry;
253
+ });
254
+ }
255
+ seatReport(filters: { since?: string; seat?: Seat } = {}): SeatDecisionRecord[] {
256
+ const since = filters.since === undefined ? undefined : utc(filters.since);
257
+ if (filters.seat && !['OP', 'MK', 'TC', 'UX'].includes(filters.seat)) throw new Error('invalid seat');
258
+ return this.events().filter((event): event is Extract<Event, { type: 'seat-recorded' }> => event.type === 'seat-recorded')
259
+ .map(event => event.entry)
260
+ .filter(entry => (!since || (entry.recordedAt ?? entry.decidedAt ?? '') >= since) && (!filters.seat || entry.seat === filters.seat))
261
+ .sort((a, b) => (b.recordedAt ?? b.decidedAt ?? '').localeCompare(a.recordedAt ?? a.decidedAt ?? '') || a.id.localeCompare(b.id));
262
+ }
224
263
  show(id: string): DecisionEntry { const entry = this.list({ status: 'all' }).find(e => e.id === id); if (!entry) throw new Error(`decision not found: ${id}`); return entry; }
225
264
  raise(input: RaiseInput): DecisionEntry {
226
265
  const clean = validate(input);