elanous 0.2.12 → 0.2.14

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 (534) 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/chunks/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/1307-6e7e0377c1db0bdc.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/{9656-c1540088f5859c80.js → 1844-9ec4b1d12f6e80eb.js} +1 -1
  7. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/{390.c373e31b3b9d0c41.js → 1948.15f356b15a625597.js} +2 -2
  9. package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.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-2df489622a52a33a.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/{9906-482c6619b3fdda20.js → 8060-cc008cc6fedfa818.js} +1 -1
  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/9070.067c106528ec1ae6.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9103-7a3ae7f60af3b99f.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/{3869-3cd46526879f454c.js → 9247-29d889b809288e65.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/{79f8be9e-957d68913b590b08.js → a9f2b54d-7865aba0f4d2ee49.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/approvals/page-74383f890f075511.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-7b2b4f378354869a.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/botlab/page-1abfe3aec3be6bdc.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/bots/page-1512d91c7491ed62.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/chat/page-a627db94ec6904a0.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/control/page-e6b84ab07d3567d5.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/editor/page-fed69f9759d248d0.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/exec/{page-a67e11ba962337b5.js → page-15374909d8f1aebd.js} +1 -1
  60. package/apps/pwa/out/_next/static/chunks/app/field/page-012a742f81218b01.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/intake/page-99effd9d893a43dc.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/layout-938de57ae0315e46.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/loops/page-160089123a40dd91.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/market/page-629e1ede482ec6eb.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-793fa536b3a6ef37.js → page-2ce5471926761fdd.js} +1 -1
  69. package/apps/pwa/out/_next/static/chunks/app/morning/page-27e99b4e54a849d4.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-32cde713be47cddb.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e7e10cd3386222e5.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/reflection/page-e64caff084f7ce21.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-db07eaffba0df7e3.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e1e47d8d940aba3d.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/settings/page-86f91034bd5cabe5.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/setup/page-b3759cc14da80908.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/share/page-ce4d8a1215ce53a3.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/showroom/page-58043db78ba519ae.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/tasks/page-357750dfb91bf960.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/term/{page-e297866051595d81.js → page-274703bc41b71333.js} +1 -1
  89. package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/app/vault/page-3b7e821dc492bdfd.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/app/workflows/page-f8da5dc6f3752276.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2f810b18d89ecba6.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/{5cd7e3a9-404fd6b8d0ecd44c.js → f6231284-45726550aba08fca.js} +1 -1
  97. package/apps/pwa/out/_next/static/chunks/{f48170cc-d05d28bf87a43bf4.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
  98. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
  99. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
  100. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +1 -0
  104. package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +1 -0
  105. package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +1 -0
  106. package/apps/pwa/out/approvals/index.html +1 -1
  107. package/apps/pwa/out/approvals/index.txt +23 -23
  108. package/apps/pwa/out/autopilot/index.html +1 -1
  109. package/apps/pwa/out/autopilot/index.txt +23 -23
  110. package/apps/pwa/out/board/index.html +1 -1
  111. package/apps/pwa/out/board/index.txt +23 -23
  112. package/apps/pwa/out/botlab/index.html +1 -1
  113. package/apps/pwa/out/botlab/index.txt +22 -22
  114. package/apps/pwa/out/bots/index.html +1 -1
  115. package/apps/pwa/out/bots/index.txt +22 -22
  116. package/apps/pwa/out/chat/index.html +1 -1
  117. package/apps/pwa/out/chat/index.txt +22 -22
  118. package/apps/pwa/out/consult/index.html +1 -1
  119. package/apps/pwa/out/consult/index.txt +23 -23
  120. package/apps/pwa/out/control/index.html +1 -1
  121. package/apps/pwa/out/control/index.txt +23 -23
  122. package/apps/pwa/out/design-check/index.html +1 -1
  123. package/apps/pwa/out/design-check/index.txt +23 -23
  124. package/apps/pwa/out/editor/index.html +1 -1
  125. package/apps/pwa/out/editor/index.txt +23 -23
  126. package/apps/pwa/out/exec/index.html +1 -1
  127. package/apps/pwa/out/exec/index.txt +23 -23
  128. package/apps/pwa/out/field/index.html +1 -1
  129. package/apps/pwa/out/field/index.txt +23 -23
  130. package/apps/pwa/out/healthz +1 -1
  131. package/apps/pwa/out/index.html +1 -1
  132. package/apps/pwa/out/index.txt +22 -22
  133. package/apps/pwa/out/inside/index.html +1 -1
  134. package/apps/pwa/out/inside/index.txt +22 -22
  135. package/apps/pwa/out/intake/index.html +1 -1
  136. package/apps/pwa/out/intake/index.txt +23 -23
  137. package/apps/pwa/out/live/index.html +1 -1
  138. package/apps/pwa/out/live/index.txt +23 -23
  139. package/apps/pwa/out/loops/index.html +1 -0
  140. package/apps/pwa/out/loops/index.txt +30 -0
  141. package/apps/pwa/out/market/index.html +1 -1
  142. package/apps/pwa/out/market/index.txt +22 -22
  143. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  144. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  145. package/apps/pwa/out/morning/index.html +1 -1
  146. package/apps/pwa/out/morning/index.txt +23 -23
  147. package/apps/pwa/out/observatory/index.html +1 -1
  148. package/apps/pwa/out/observatory/index.txt +23 -23
  149. package/apps/pwa/out/ops/checklist/index.html +1 -1
  150. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  151. package/apps/pwa/out/ops/release/index.html +1 -1
  152. package/apps/pwa/out/ops/release/index.txt +22 -22
  153. package/apps/pwa/out/ops/seats/index.html +1 -1
  154. package/apps/pwa/out/ops/seats/index.txt +22 -22
  155. package/apps/pwa/out/outputs/index.html +1 -1
  156. package/apps/pwa/out/outputs/index.txt +22 -22
  157. package/apps/pwa/out/reflection/index.html +1 -1
  158. package/apps/pwa/out/reflection/index.txt +23 -23
  159. package/apps/pwa/out/scheduler/index.html +1 -1
  160. package/apps/pwa/out/scheduler/index.txt +23 -23
  161. package/apps/pwa/out/sessions/index.html +1 -1
  162. package/apps/pwa/out/sessions/index.txt +22 -22
  163. package/apps/pwa/out/settings/devices/index.html +1 -1
  164. package/apps/pwa/out/settings/devices/index.txt +23 -23
  165. package/apps/pwa/out/settings/index.html +1 -1
  166. package/apps/pwa/out/settings/index.txt +23 -23
  167. package/apps/pwa/out/setup/done/index.html +1 -1
  168. package/apps/pwa/out/setup/done/index.txt +23 -23
  169. package/apps/pwa/out/setup/index.html +1 -1
  170. package/apps/pwa/out/setup/index.txt +23 -23
  171. package/apps/pwa/out/share/index.html +1 -1
  172. package/apps/pwa/out/share/index.txt +23 -23
  173. package/apps/pwa/out/showroom/index.html +1 -1
  174. package/apps/pwa/out/showroom/index.txt +22 -22
  175. package/apps/pwa/out/tasks/index.html +1 -1
  176. package/apps/pwa/out/tasks/index.txt +23 -23
  177. package/apps/pwa/out/term/index.html +1 -1
  178. package/apps/pwa/out/term/index.txt +23 -23
  179. package/apps/pwa/out/today/index.html +1 -1
  180. package/apps/pwa/out/today/index.txt +23 -23
  181. package/apps/pwa/out/trace/index.html +1 -1
  182. package/apps/pwa/out/trace/index.txt +23 -23
  183. package/apps/pwa/out/vault/index.html +1 -1
  184. package/apps/pwa/out/vault/index.txt +23 -23
  185. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  186. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  187. package/apps/pwa/out/workflows/index.html +1 -1
  188. package/apps/pwa/out/workflows/index.txt +23 -23
  189. package/apps/pwa/out/workspace/index.html +1 -1
  190. package/apps/pwa/out/workspace/index.txt +23 -23
  191. package/apps/pwa/out/worktrees/index.html +1 -1
  192. package/apps/pwa/out/worktrees/index.txt +23 -23
  193. package/examples/teams/personas/backend.yaml +4 -0
  194. package/examples/teams/personas/pm.yaml +4 -0
  195. package/examples/teams/personas/qa.yaml +4 -0
  196. package/examples/teams/small-product.yaml +20 -0
  197. package/graphs/lecture/lecture-note.yaml +28 -0
  198. package/graphs/lecture/recipes.yaml +17 -0
  199. package/graphs/orchestrator/orchestrator.yaml +59 -0
  200. package/graphs/orchestrator/recipes.yaml +18 -0
  201. package/graphs/release/recipes.yaml +3 -0
  202. package/graphs/release/release-loop.yaml +4 -1
  203. package/graphs/rhythm/daily.yaml +38 -0
  204. package/graphs/rhythm/recipes.yaml +18 -0
  205. package/graphs/rhythm/weekly.yaml +39 -0
  206. package/graphs/video/recipes.yaml +3 -0
  207. package/graphs/video/youtube-summary.yaml +17 -0
  208. package/package.json +2 -1
  209. package/scripts/audit-test-state-writes.ts +72 -17
  210. package/scripts/brand/check.ts +186 -0
  211. package/scripts/context-hooks/emit.ts +36 -14
  212. package/scripts/coord-channel-watch.sh +1 -0
  213. package/scripts/coord-post.sh +11 -1
  214. package/scripts/discord-seat-probe.ts +144 -0
  215. package/scripts/docs-cli-check.ts +23 -6
  216. package/scripts/expose-rubric-check.ts +118 -0
  217. package/scripts/expose-rubric-suggest.ts +163 -0
  218. package/scripts/heal/run-role.ts +14 -0
  219. package/scripts/hq/hq-standby.sh +65 -0
  220. package/scripts/hq/standby-promote.ts +173 -0
  221. package/scripts/hq/standby-role.ts +59 -0
  222. package/scripts/hq/standby-snapshot.ts +226 -0
  223. package/scripts/hq/standby-verify.ts +223 -0
  224. package/scripts/lecture-note/node.ts +256 -0
  225. package/scripts/lessons/handbook-page.ts +85 -0
  226. package/scripts/release-loop/gate-node.ts +62 -15
  227. package/scripts/release-loop/ops-upgrade-node.ts +17 -5
  228. package/scripts/release-loop/publish-node.ts +14 -1
  229. package/scripts/release-loop/unattended-release.ts +23 -1
  230. package/scripts/release-loop/vault-note-node.ts +196 -0
  231. package/scripts/release-story/draft.ts +172 -0
  232. package/scripts/rhythm/daily.ts +383 -0
  233. package/scripts/rhythm/weekly.ts +294 -0
  234. package/scripts/skills/public-check.ts +169 -0
  235. package/scripts/test-deterministic.ts +57 -11
  236. package/scripts/test-diet/effectiveness.ts +195 -0
  237. package/scripts/test-diet/lib.ts +16 -8
  238. package/scripts/test-diet/node.ts +77 -9
  239. package/scripts/ux/demo-rehearsal.ts +2 -2
  240. package/scripts/ux/lib/rehearsal-verdict.ts +1 -0
  241. package/scripts/video/youtube-summary.ts +133 -0
  242. package/skills/diagram-master/SKILL.md +42 -49
  243. package/skills/diagram-master/references/pyproject.toml +4 -1
  244. package/skills/diagram-master/references/routing-guide.md +4 -2
  245. package/skills/grill-me/SKILL.md +1 -0
  246. package/skills/omni-crawl/SKILL.md +16 -16
  247. package/skills/omni-crawl/package-lock.json +1 -544
  248. package/skills/omni-crawl/package.json +3 -4
  249. package/skills/omni-crawl/scripts/main.ts +13 -13
  250. package/skills/omni-crawl/scripts/monitor.ts +8 -8
  251. package/skills/omni-crawl/src/apify.ts +13 -2
  252. package/skills/omni-crawl/src/capture.ts +6 -0
  253. package/skills/omni-crawl/src/registry.ts +2 -2
  254. package/skills/omni-crawl/src/stdout-json.ts +4 -0
  255. package/skills/omni-digest/SKILL.md +10 -11
  256. package/skills/omni-digest/package-lock.json +0 -545
  257. package/skills/omni-digest/package.json +2 -5
  258. package/skills/omni-digest/scripts/main.ts +3 -3
  259. package/skills/omni-digest/src/delegate.ts +5 -5
  260. package/skills/omni-digest/src/fetch.ts +2 -2
  261. package/skills/omni-digest/src/media.ts +25 -11
  262. package/skills/omni-digest/src/visual.ts +3 -2
  263. package/skills/omni-market/scripts/main.ts +1 -1
  264. package/skills/omni-market/scripts/stdout-json.ts +4 -0
  265. package/skills/youtube-master/SKILL.md +13 -13
  266. package/skills/youtube-master/package-lock.json +0 -545
  267. package/skills/youtube-master/package.json +2 -5
  268. package/skills/youtube-master/scripts/main.ts +2 -2
  269. package/skills/youtube-master/scripts/transcribe-local.ts +1 -1
  270. package/skills/youtube-master/src/util.ts +30 -7
  271. package/src/agent/identity-env.ts +11 -0
  272. package/src/agent-mission/driver.ts +15 -10
  273. package/src/agent-mission/duo.ts +94 -0
  274. package/src/agent-mission/pty-decision.ts +1 -1
  275. package/src/autopilot/build/llm-conflict-merge.ts +45 -8
  276. package/src/autopilot/build/merge-intent.ts +53 -0
  277. package/src/autopilot/track-agent-shadow.ts +2 -1
  278. package/src/autopilot/track-agent.ts +4 -1
  279. package/src/boot/daemon-prompt-request.ts +3 -0
  280. package/src/boot/daemon-prompt-turn.ts +6 -2
  281. package/src/boot/daemon-runtime.ts +48 -16
  282. package/src/chat/index.ts +2 -0
  283. package/src/claims/claims-ledger.ts +232 -0
  284. package/src/claims/claims-render.ts +157 -0
  285. package/src/cli/claims-cli.ts +94 -0
  286. package/src/cli/decisions-cli.ts +84 -20
  287. package/src/cli/docs-cli.ts +2 -0
  288. package/src/cli/flow-cli.ts +11 -0
  289. package/src/cli/freeze-cli.ts +45 -0
  290. package/src/cli/lesson-cli.ts +108 -0
  291. package/src/cli/logs-cli.ts +104 -2
  292. package/src/cli/logs-model-input.ts +131 -0
  293. package/src/cli/outputs-cli.ts +92 -0
  294. package/src/cli/persona-cli.ts +29 -0
  295. package/src/cli/pod-cli.ts +10 -3
  296. package/src/cli/pr-cli.ts +44 -2
  297. package/src/cli/private-ledger-commands.ts +54 -4
  298. package/src/cli/pty-drive-verdict.ts +11 -2
  299. package/src/cli/release-cli.ts +248 -45
  300. package/src/cli/rfc-status.ts +91 -0
  301. package/src/connectors/linear.ts +5 -3
  302. package/src/context-bus/condense.ts +189 -0
  303. package/src/context-bus/context-day.ts +82 -0
  304. package/src/context-bus/context-now-public.ts +86 -0
  305. package/src/context-bus/context-now-surfaces.ts +70 -4
  306. package/src/context-bus/context-now.ts +4 -3
  307. package/src/context-bus/long-term-memory.ts +201 -24
  308. package/src/context-bus/nightly-condense.ts +1 -1
  309. package/src/dashboard/index.ts +75 -22
  310. package/src/dashboard/slash-runtime/chat-persona.ts +66 -0
  311. package/src/dashboard/slash-runtime/dashboard-handlers.ts +99 -0
  312. package/src/dashboard/slash-runtime/loops-table.ts +77 -0
  313. package/src/decisions/decision-cards.ts +13 -7
  314. package/src/decisions/decision-ledger.ts +83 -18
  315. package/src/discord/sprint21-runtime.ts +16 -0
  316. package/src/discord-self-message.ts +8 -0
  317. package/src/discord-slash-wire.ts +31 -2
  318. package/src/domains/outbound-alert.ts +1 -1
  319. package/src/elanous-config-dir.ts +29 -4
  320. package/src/execution-loop/launch-gate.ts +29 -14
  321. package/src/flow/cards.ts +239 -0
  322. package/src/graph-kinds/registry.ts +2 -2
  323. package/src/graph-runner/graph-approval-card.ts +2 -2
  324. package/src/graph-runner/graph-grow.ts +64 -11
  325. package/src/graph-runner/graph-tick.ts +13 -2
  326. package/src/graph-runner/runner.ts +111 -22
  327. package/src/harness/harness-cli-command.ts +178 -27
  328. package/src/harness/harness-incidents.ts +43 -12
  329. package/src/harness/harness-pod-dispatch.ts +2 -0
  330. package/src/harness/harness-queue-child.ts +37 -0
  331. package/src/harness/harness-queue.ts +494 -0
  332. package/src/harness/helper-scan.ts +139 -0
  333. package/src/harness/model-input-observation.ts +37 -0
  334. package/src/hooks/github-shadow.ts +72 -2
  335. package/src/hooks/receiver.ts +4 -2
  336. package/src/hq/hq.ts +318 -0
  337. package/src/hq/lease.ts +210 -0
  338. package/src/hq/ledger-write-target.ts +41 -0
  339. package/src/hq/promote.ts +301 -0
  340. package/src/index.ts +281 -27
  341. package/src/intake-plane/discord-seat-work.ts +13 -6
  342. package/src/intake-plane/linear-wish.ts +72 -0
  343. package/src/intake-plane/persona-answer.ts +5 -0
  344. package/src/intake-plane/telegram-seat-work.ts +13 -6
  345. package/src/intake-plane/wish-benchmark-ledger.ts +129 -0
  346. package/src/intake-plane/wish-card.ts +53 -2
  347. package/src/intake-plane/wish-folder.ts +5 -2
  348. package/src/intake-plane/wish-reply.ts +72 -0
  349. package/src/lessons/lesson-import.ts +106 -0
  350. package/src/lessons/lesson-ledger.ts +206 -0
  351. package/src/loops/activity.ts +203 -0
  352. package/src/loops/budget.ts +32 -0
  353. package/src/loops/checker.ts +180 -0
  354. package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
  355. package/src/loops/loop-cli.ts +72 -6
  356. package/src/loops/neighbors.ts +40 -0
  357. package/src/loops/observe.ts +59 -0
  358. package/src/loops/orchestrator/author-depth.ts +164 -0
  359. package/src/loops/orchestrator/author-input-check.ts +77 -0
  360. package/src/loops/orchestrator/author-ledger.ts +230 -0
  361. package/src/loops/orchestrator/finish-rate.ts +175 -0
  362. package/src/loops/orchestrator/overlap.ts +180 -0
  363. package/src/loops/orchestrator/resync-shadow.ts +227 -0
  364. package/src/loops/orchestrator/tick.ts +302 -0
  365. package/src/loops/orchestrator/traffic.ts +165 -0
  366. package/src/loops/package/verify.ts +125 -0
  367. package/src/loops/registry.ts +454 -9
  368. package/src/loops/status-rows.ts +118 -0
  369. package/src/loops/verdict.ts +34 -0
  370. package/src/maturity/bot-slash-catalog.ts +66 -0
  371. package/src/maturity/feature-maturity.ts +8 -5
  372. package/src/memory.ts +37 -4
  373. package/src/nexus/api/card-followup-route.ts +118 -0
  374. package/src/nexus/api/dashboard.ts +6 -1
  375. package/src/nexus/api/decisions-route.ts +68 -0
  376. package/src/nexus/api/http-server.ts +65 -2
  377. package/src/nexus/api/merge-approvals-gate.ts +1 -1
  378. package/src/nexus/api/personas.ts +2 -1
  379. package/src/nexus/api/schedules-read.ts +5 -1
  380. package/src/nexus/api/skill-exec-route.ts +97 -0
  381. package/src/nexus/api/task-cards-api.ts +44 -5
  382. package/src/nexus/api/workflows.ts +2 -1
  383. package/src/persona/loader.ts +18 -0
  384. package/src/persona/persona-todo.ts +14 -9
  385. package/src/persona/presets.ts +2 -1
  386. package/src/persona/team.ts +105 -0
  387. package/src/persona/types.ts +4 -0
  388. package/src/pod-lease/host-lease.ts +130 -20
  389. package/src/release-loop/auto-start.ts +48 -0
  390. package/src/release-loop/checklist.ts +13 -4
  391. package/src/release-loop/feature-store.ts +28 -16
  392. package/src/release-loop/landed-but-yellow.ts +12 -0
  393. package/src/release-loop/landing-freeze.ts +121 -0
  394. package/src/release-loop/placement.ts +242 -0
  395. package/src/release-loop/release-schedule.ts +3 -1
  396. package/src/seat-dispatch/ceo-commands.ts +2 -2
  397. package/src/seat-dispatch/seat-ask.ts +73 -15
  398. package/src/seat-dispatch/seat-questions.ts +58 -0
  399. package/src/seat-loop/seat-loop.ts +495 -46
  400. package/src/seat-loop/seat-pack.ts +127 -0
  401. package/src/self-dev/ask-launch-flow.ts +3 -0
  402. package/src/self-dev/dev-pipeline.ts +6 -2
  403. package/src/self-dev/draft-sweep.ts +76 -0
  404. package/src/self-dev/run-store.ts +73 -1
  405. package/src/self-dev/run-supervisor.ts +8 -0
  406. package/src/self-dev/self-orchestrate-runtime.ts +5 -1
  407. package/src/self-implement/budget-gate.ts +29 -0
  408. package/src/self-implement/expose-gate.ts +47 -0
  409. package/src/self-implement/frozen-merges.ts +294 -0
  410. package/src/self-implement/gate-cli.ts +10 -4
  411. package/src/self-implement/gate-scope.ts +46 -9
  412. package/src/self-implement/goal-archive.ts +222 -0
  413. package/src/self-implement/goal-lookup.ts +147 -0
  414. package/src/self-implement/heal-intake.ts +79 -0
  415. package/src/self-implement/host-regate.ts +25 -2
  416. package/src/self-implement/l8-merge-queue.ts +157 -1
  417. package/src/self-implement/orchestrator.ts +114 -10
  418. package/src/self-implement/revert-guard.ts +62 -0
  419. package/src/self-implement/run-ledger.ts +1 -1
  420. package/src/self-implement/seams.ts +28 -8
  421. package/src/session/index.ts +2 -0
  422. package/src/steward/launch.ts +5 -1
  423. package/src/steward/readiness.ts +125 -0
  424. package/src/steward/rescue.ts +75 -0
  425. package/src/steward/triage.ts +48 -9
  426. package/src/task-cards/card-cli.ts +86 -0
  427. package/src/task-cards/card-store.ts +10 -6
  428. package/src/task-orchestrator/surfaces/pod-lease.ts +23 -8
  429. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
  430. package/src/task-orchestrator/surfaces/pod-pool.ts +27 -10
  431. package/src/task-orchestrator/surfaces/self-implement-pod.ts +112 -18
  432. package/src/telegram-commands.ts +27 -3
  433. package/src/telegram-dispatch-forward.ts +1 -1
  434. package/src/telegram-fabric-plan.ts +90 -0
  435. package/src/telegram-loops-command.ts +44 -0
  436. package/src/telegram-project-command.ts +10 -4
  437. package/src/telegram.ts +5 -0
  438. package/src/tool-runtime/elanous-control-runtimes.ts +2 -1
  439. package/src/user-config.ts +204 -16
  440. package/src/version/packed-revision.json +1 -1
  441. package/src/workflow-runtime/executor.ts +4 -0
  442. package/src/workflow-runtime/node-catalog.ts +13 -0
  443. package/src/workflow-runtime/nodes/knowledge.ts +38 -0
  444. package/src/workflow-runtime/schema.ts +17 -1
  445. package/src/workflow-runtime/storage.ts +31 -13
  446. package/src/workflow-runtime/types.ts +13 -1
  447. package/apps/pwa/out/_next/static/-6XrAoD3eU7NyMHAE4xPS/_buildManifest.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/1147-466ec15afacc1a65.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/1394-ee3795b112907122.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/3992-2e522a8ac5f2afde.js +0 -25
  459. package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +0 -2
  463. package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/7277-0b2fc7c16ba2d64b.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/7758-0b018a9e4fc56200.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/8605-0259994816604c49.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +0 -1
  479. package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/9269.d2a4795bc6812717.js +0 -1
  481. package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +0 -1
  482. package/apps/pwa/out/_next/static/chunks/9920-5cfdc5e8fd209825.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/app/approvals/page-cb169620945d1952.js +0 -1
  485. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-cefe8169e36cf62a.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/app/board/page-d2e35228bc497119.js +0 -1
  487. package/apps/pwa/out/_next/static/chunks/app/botlab/page-5c05b0da4bc1392c.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/app/bots/page-e0ccdb576812b523.js +0 -1
  489. package/apps/pwa/out/_next/static/chunks/app/chat/page-813c4a52c9d05ca8.js +0 -1
  490. package/apps/pwa/out/_next/static/chunks/app/consult/page-4731f2c215358937.js +0 -1
  491. package/apps/pwa/out/_next/static/chunks/app/control/page-a022b257950f39e6.js +0 -1
  492. package/apps/pwa/out/_next/static/chunks/app/design-check/page-a2ffc717e04b8b0a.js +0 -1
  493. package/apps/pwa/out/_next/static/chunks/app/editor/page-b59ee4e70fc18fcf.js +0 -1
  494. package/apps/pwa/out/_next/static/chunks/app/field/page-c172dabd796eb7a3.js +0 -1
  495. package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +0 -1
  496. package/apps/pwa/out/_next/static/chunks/app/inside/page-1efd0cb9e0ed9d13.js +0 -1
  497. package/apps/pwa/out/_next/static/chunks/app/intake/page-d5dc7ff69a37b4cf.js +0 -1
  498. package/apps/pwa/out/_next/static/chunks/app/layout-e71c84189e8c683e.js +0 -1
  499. package/apps/pwa/out/_next/static/chunks/app/live/page-1d6a533ce5a6efcb.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/app/market/page-dfca9b7d2de0b135.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/app/morning/page-6dd3bfbbde2a5c2c.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/app/not-found-490653e8d4cc8852.js +0 -1
  503. package/apps/pwa/out/_next/static/chunks/app/observatory/page-77447356baad75d2.js +0 -1
  504. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-29e961191f6057df.js +0 -1
  505. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-c822f55b884d9104.js +0 -1
  506. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fa4ccfc432424e52.js +0 -1
  507. package/apps/pwa/out/_next/static/chunks/app/outputs/page-6d604227e1664b71.js +0 -1
  508. package/apps/pwa/out/_next/static/chunks/app/page-9c7e8f744a765daf.js +0 -1
  509. package/apps/pwa/out/_next/static/chunks/app/reflection/page-4048543b4b7ad5e0.js +0 -1
  510. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4c3a3126abed551a.js +0 -1
  511. package/apps/pwa/out/_next/static/chunks/app/sessions/page-64b0aecb1ca75e75.js +0 -1
  512. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4292d0149020e8a7.js +0 -1
  513. package/apps/pwa/out/_next/static/chunks/app/settings/page-db14d0be7f1c460c.js +0 -1
  514. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-e5d8072cd7cfef73.js +0 -1
  515. package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +0 -1
  516. package/apps/pwa/out/_next/static/chunks/app/setup/page-9547b5cec7cf76bf.js +0 -1
  517. package/apps/pwa/out/_next/static/chunks/app/share/page-e6dcb83265530636.js +0 -1
  518. package/apps/pwa/out/_next/static/chunks/app/showroom/page-45248f08d6aecae5.js +0 -1
  519. package/apps/pwa/out/_next/static/chunks/app/tasks/page-2cddeb9e2aeedc55.js +0 -1
  520. package/apps/pwa/out/_next/static/chunks/app/today/page-baba2e047872fb0f.js +0 -1
  521. package/apps/pwa/out/_next/static/chunks/app/trace/page-e895da4ec9cac345.js +0 -1
  522. package/apps/pwa/out/_next/static/chunks/app/vault/page-4344faf20216233b.js +0 -1
  523. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-698bb29f11864b89.js +0 -1
  524. package/apps/pwa/out/_next/static/chunks/app/workflows/page-bd6a6dc1ddc273f2.js +0 -1
  525. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2e9e2fb01c9da1c9.js +0 -1
  526. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-d62ceb13411ebba4.js +0 -1
  527. package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +0 -1
  528. package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +0 -1
  529. package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +0 -1
  530. package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +0 -1
  531. package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +0 -1
  532. package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +0 -1
  533. package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +0 -1
  534. /package/apps/pwa/out/_next/static/{-6XrAoD3eU7NyMHAE4xPS → x6mjI6vqOkVVu0jFuDkz1}/_ssgManifest.js +0 -0
@@ -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
+ }
@@ -35,21 +35,27 @@ export function isIrreversible(entry: Pick<DecisionEntry, 'category'>): boolean
35
35
 
36
36
  const kst = (at: string) => new Intl.DateTimeFormat('ko-KR', { timeZone: 'Asia/Seoul', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false }).format(new Date(at));
37
37
 
38
- /** The card body: title, SCQA in four short lines, options with consequences, recommendation, deadline. */
38
+ /** The card opens with a short pitch; the full question and choice buttons remain below it. */
39
39
  export function renderCardText(e: DecisionEntry, extra: { note?: string; confirm?: string } = {}): string {
40
+ const recommendedKey = 'option' in e.recommendation ? e.recommendation.option : undefined;
41
+ const recommended = e.options.find(o => o.key === recommendedKey);
42
+ const alternative = e.options.find(o => o.key === e.alternative);
40
43
  const lines = [
41
44
  `🗳 결정 요청 ${e.id}${isIrreversible(e) ? ' · ⚠️ 되돌릴 수 없음' : ''}`,
42
45
  `«${e.title}» · 올린 자리: ${e.raisedBy.agent}${e.raisedBy.track ? `(${e.raisedBy.track})` : ''}`,
43
46
  '',
44
- `상황: ${e.scqa.s}`,
45
- `문제: ${e.scqa.c}`,
47
+ `S: ${e.scqa.s.replace(/\s+/g, ' ')}`,
48
+ `C: ${e.scqa.c.replace(/\s+/g, ' ')}`,
49
+ `Q: ${(e.scqa.q ?? '(비움)').replace(/\s+/g, ' ')}`,
50
+ `A: ${(e.scqa.a ?? '(비움)').replace(/\s+/g, ' ')}`,
51
+ 'skipped' in e.recommendation ? `권고 없음: ${e.recommendation.reason}` : `권고: ${recommended?.label ?? e.recommendation.option} — ${e.recommendation.why}`,
52
+ ...(alternative ? [`대안: ${alternative.label} — ${alternative.consequence}`] : []),
53
+ e.crossCheck?.length ? `교차 확인: ${e.crossCheck.map(check => `${check.seat} ✓ ${check.note.replace(/\s+/g, ' ')}`).join(' · ')}` : `교차 확인 없음(${e.crossCheckSkipped ?? '미기재'})`,
54
+ ...(e.dissent ? [`이견: ${e.dissent}`] : []),
55
+ ...(e.dueAt ? [`기한: ${kst(e.dueAt)} KST`] : []),
46
56
  ...(e.pendingQuestion ? ['', `대기 질문 전문:\n${e.pendingQuestion}`] : []),
47
- ...(e.scqa.q ? [`질문: ${e.scqa.q}`] : []),
48
- ...(e.scqa.a ? [`제안: ${e.scqa.a}`] : []),
49
57
  '',
50
58
  ...e.options.map((o) => `${o.key.toUpperCase()}) ${o.label} — ${o.consequence}`),
51
- 'skipped' in e.recommendation ? `권고 없음: ${e.recommendation.reason}` : `권고: ${e.recommendation.option.toUpperCase()} — ${e.recommendation.why}`,
52
- ...(e.dueAt ? [`기한: ${kst(e.dueAt)}`] : []),
53
59
  ...(extra.note ? ['', `📝 메모: ${extra.note}`] : []),
54
60
  ];
55
61
  if (extra.confirm) {
@@ -22,12 +22,24 @@ export interface DecisionEntry {
22
22
  pendingQuestion?: string;
23
23
  /** DEC-TG — optional deadline (UTC). Cards remind the owner two hours before it. */
24
24
  dueAt?: string;
25
+ crossCheck?: Array<{ seat: string; at: string; note: string }>;
26
+ crossCheckSkipped?: string;
27
+ alternative?: string;
28
+ dissent?: string;
25
29
  decidedAt?: string; decidedBy?: DecisionActor; choice?: string; note?: string; versionAtDecision?: Versions;
26
30
  withdrawnAt?: string; withdrawReason?: string;
27
31
  history: Array<{ type: 'raised' | 'options-added' | 'decided' | 'withdrawn'; at?: string; by: string; version?: Versions; choice?: string; reason?: string }>;
28
32
  }
29
- export type RaiseInput = Pick<DecisionEntry, 'title' | 'category' | 'scqa' | 'options' | 'recommendation' | 'raisedBy' | 'refs' | 'dueAt' | 'resume' | 'pendingQuestion'> & { raisedAt?: string };
30
- 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 };
33
+ export type RaiseInput = Pick<DecisionEntry, 'title' | 'category' | 'scqa' | 'options' | 'recommendation' | 'raisedBy' | 'refs' | 'dueAt' | 'resume' | 'pendingQuestion' | 'crossCheck' | 'crossCheckSkipped' | 'alternative' | 'dissent'> & { raisedAt?: string };
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 };
31
43
  export interface DecisionLedgerOptions extends VersionOptions { stateDir?: string; now?: () => Date; resolveVersion?: (at: string) => Versions; writeAnswer?: typeof writePendingQuestionAnswer }
32
44
 
33
45
  /** null = the decision resumes no run; otherwise whether the answer file was written (or already matched). */
@@ -89,13 +101,27 @@ function validate(input: RaiseInput, historical = false): RaiseInput {
89
101
  ? { skipped: true as const, reason: safe(single(input.recommendation.reason, 'skip reason')) }
90
102
  : { option: input.recommendation.option, why: safe(single(input.recommendation.why, 'recommendation why')) };
91
103
  if ('option' in recommendation && !options.some(o => o.key === recommendation.option)) throw new Error('recommended option not found');
104
+ const alternative = input.alternative === undefined ? undefined : single(input.alternative, 'alternative');
105
+ if (alternative && !options.some(o => o.key === alternative)) throw new Error('alternative option not found');
106
+ if (alternative && 'option' in recommendation && alternative === recommendation.option) throw new Error('alternative must differ from recommendation');
107
+ if (input.crossCheck?.length && input.crossCheckSkipped !== undefined) throw new Error('cross-check and skipped reason cannot coexist');
108
+ if (input.crossCheck !== undefined && !Array.isArray(input.crossCheck)) throw new Error('cross-check must be a list');
109
+ const crossCheck = input.crossCheck?.map(check => {
110
+ const seat = single(check.seat, 'cross-check seat');
111
+ if (!['OP', 'MK', 'TC', 'UX'].includes(seat)) throw new Error('invalid cross-check seat');
112
+ return { seat, at: utc(check.at), note: safe(single(check.note, 'cross-check note')) };
113
+ });
114
+ const crossCheckSkipped = input.crossCheckSkipped === undefined ? undefined : safe(single(input.crossCheckSkipped, 'cross-check skipped reason'));
115
+ const dissent = input.dissent === undefined ? undefined : safe(single(input.dissent, 'dissent'));
92
116
  if (input.resume && !/^(?:auq:[0-9a-z]+:[0-9a-z]{5}|execution:[A-Za-z0-9_-]+:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?![\s\S])/.test(input.resume.questionId)) throw new Error('invalid resume questionId');
93
117
  const resume = input.resume ? { questionId: input.resume.questionId,
94
118
  ...(input.resume.runId === undefined ? {} : { runId: single(input.resume.runId, 'resume runId') }) } : undefined;
95
119
  if (input.pendingQuestion !== undefined) required(input.pendingQuestion, 'pending question');
96
120
  return { ...input, title, scqa: { s, c, ...(q ? { q } : {}), ...(a ? { a } : {}) }, options, recommendation,
97
121
  ...(input.pendingQuestion === undefined ? {} : { pendingQuestion: safe(input.pendingQuestion) }),
98
- ...(resume ? { resume } : {}),
122
+ ...(resume ? { resume } : {}), ...(crossCheck ? { crossCheck } : {}),
123
+ ...(crossCheckSkipped !== undefined ? { crossCheckSkipped } : {}),
124
+ ...(alternative !== undefined ? { alternative } : {}), ...(dissent !== undefined ? { dissent } : {}),
99
125
  raisedBy: { agent: safe(single(input.raisedBy.agent, 'agent')), ...(input.raisedBy.track ? { track: input.raisedBy.track } : {}), ...(input.raisedBy.session ? { session: safe(single(input.raisedBy.session, 'session')) } : {}) },
100
126
  ...(input.refs ? { refs: input.refs.map(r => safe(single(r, 'ref'))) } : {}),
101
127
  ...(input.dueAt ? { dueAt: utc(input.dueAt) } : {}) };
@@ -178,6 +204,7 @@ export class DecisionLedger {
178
204
  if (filters.category && !CATEGORIES.includes(filters.category)) throw new Error('invalid category');
179
205
  const map = new Map<string, DecisionEntry>();
180
206
  for (const event of this.events()) {
207
+ if (event.type === 'seat-recorded') continue;
181
208
  if (event.type === 'raised') {
182
209
  if (map.has(event.entry.id)) throw new Error(`duplicate decision: ${event.entry.id}`);
183
210
  map.set(event.entry.id, event.entry);
@@ -203,25 +230,63 @@ export class DecisionLedger {
203
230
  && (!filters.decidedBy || e.decidedBy?.kind === filters.decidedBy)
204
231
  && (!filters.category || e.category === filters.category)).sort((a, b) => (b.raisedAt ?? b.importedAt ?? '').localeCompare(a.raisedAt ?? a.importedAt ?? '') || a.id.localeCompare(b.id));
205
232
  }
206
- 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; }
207
- raise(input: RaiseInput): DecisionEntry {
208
- const clean = validate(input);
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);
209
242
  return this.locked(() => {
210
- const at = utc(clean.raisedAt ?? this.now().toISOString());
211
- const day = at.slice(0, 10).replaceAll('-', '');
212
- const prefix = `D-${day}-`;
213
- let next = 1;
214
- for (const entry of this.list({ status: 'all' })) {
215
- if (entry.id.startsWith(prefix)) next = Math.max(next, Number(entry.id.slice(prefix.length)) + 1);
216
- }
217
- const id = `${prefix}${String(next).padStart(2, '0')}`;
218
- const version = this.version(at);
219
- const entry: DecisionEntry = { ...clean, id, raisedAt: at, version, status: 'open', history: [{ type: 'raised', at, by: clean.raisedBy.agent, version }] };
220
- this.append({ type: 'raised', entry });
221
- debug.log('decisions', 'raised', { id, category: entry.category, by: entry.raisedBy.agent });
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 });
222
252
  return entry;
223
253
  });
224
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
+ }
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; }
264
+ raise(input: RaiseInput): DecisionEntry {
265
+ const clean = validate(input);
266
+ return this.locked(() => this.raiseLocked(clean));
267
+ }
268
+ /** Reuse a card with the same source reference, including across competing raisers. */
269
+ raiseOnce(input: RaiseInput, ref: string): DecisionEntry {
270
+ const clean = validate(input);
271
+ if (!clean.refs?.includes(ref)) throw new Error('decision source reference required');
272
+ return this.locked(() => this.list({ status: 'all' }).find(entry => entry.refs?.includes(ref)) ?? this.raiseLocked(clean));
273
+ }
274
+ private raiseLocked(clean: RaiseInput): DecisionEntry {
275
+ const at = utc(clean.raisedAt ?? this.now().toISOString());
276
+ const day = at.slice(0, 10).replaceAll('-', '');
277
+ const prefix = `D-${day}-`;
278
+ let next = 1;
279
+ for (const entry of this.list({ status: 'all' })) {
280
+ if (entry.id.startsWith(prefix)) next = Math.max(next, Number(entry.id.slice(prefix.length)) + 1);
281
+ }
282
+ const id = `${prefix}${String(next).padStart(2, '0')}`;
283
+ const version = this.version(at);
284
+ const entry: DecisionEntry = { ...clean, id, raisedAt: at, version, status: 'open', history: [{ type: 'raised', at, by: clean.raisedBy.agent, version }] };
285
+ this.append({ type: 'raised', entry });
286
+ debug.log('decisions', 'raised', { id, category: entry.category, by: entry.raisedBy.agent });
287
+ debug.log('decisions', 'raised-crosscheck', { id, seats: entry.crossCheck?.length ?? 0, skipped: entry.crossCheckSkipped ?? null });
288
+ return entry;
289
+ }
225
290
  /** Record the decision and say whether its answer reached the waiting run (null = the decision resumes nothing). */
226
291
  decideWithDelivery(id: string, choice: string, by: DecisionActor, note?: string, decidedAt?: string): { entry: DecisionEntry; delivery: AnswerDelivery } {
227
292
  return this.decideLocked(id, choice, by, note, decidedAt);
@@ -22,6 +22,9 @@
22
22
 
23
23
  import { debug } from '../debug/log.js';
24
24
  import { botAudienceFor, gateDiscordSchemas } from '../maturity/bot-command-maturity.js';
25
+ import { buildBotSlashCatalog } from '../maturity/bot-slash-catalog.js';
26
+ import { FEATURE_MATURITY } from '../maturity/feature-maturity.js';
27
+ import { SLASH_COMMANDS } from '../chat/index.js';
25
28
  import type { DiscordBot } from '../discord.js';
26
29
  import {
27
30
  loadLayeredPersonaDirs,
@@ -195,6 +198,19 @@ export async function wireSprint21Runtime(opts: WireSprint21Opts): Promise<Sprin
195
198
  router.bind(statusCommand);
196
199
  router.bind(pollCommand);
197
200
  for (const command of botCommands) router.bind(command);
201
+ const catalog = buildBotSlashCatalog({
202
+ surface: 'discord', coreCommands: SLASH_COMMANDS, maturity: FEATURE_MATURITY,
203
+ handledCommands: router.schemas(),
204
+ });
205
+ for (const entry of catalog.commands) {
206
+ if (entry.supported) continue;
207
+ const { name, description } = entry;
208
+ router.bind({ schema: { name, description }, handler: async () => ({
209
+ type: RESPONSE_CHANNEL_MESSAGE_WITH_SOURCE,
210
+ content: catalog.unsupportedReply(entry.name)!,
211
+ ephemeral: true,
212
+ }) });
213
+ }
198
214
 
199
215
  // 5. Wire bot callbacks — INTERACTION_CREATE + reactions
200
216
  // (Bot must be created with onInteraction/onReaction in opts;
@@ -64,6 +64,8 @@ import { getUserConfig } from './user-config.js';
64
64
  import { detectCard, runCardFollowup, NotACardError } from './card-followup/core.js';
65
65
  import { localOcrText } from './telegram-card-followup.js';
66
66
  import type { runGraph } from './graph-runner/runner.js';
67
+ import { readSlashContextNow, renderTelegramNow } from './context-bus/context-now-surfaces.js';
68
+ import type { ContextNowDeps } from './context-bus/context-now.js';
67
69
 
68
70
  const SLASH_FOCUS_TURNS_DEFAULT = 8;
69
71
 
@@ -139,6 +141,7 @@ export interface DiscordSelfMessageDeps {
139
141
  questionChannelFor?: (channelId: string) => import('./hitl/question.js').QuestionChannel;
140
142
  log?: (msg: string) => void;
141
143
  cardFollowupDeps?: { ocrText?: (path: string) => Promise<string | null>; runGraph?: typeof runGraph; rootDir?: () => string };
144
+ nowDeps?: ContextNowDeps;
142
145
  }
143
146
 
144
147
  /** Compose the full discord self+interweave onMessage handler. */
@@ -356,6 +359,11 @@ export function buildDiscordSelfOnMessage(deps: DiscordSelfMessageDeps): DcMessa
356
359
  // Voice text commands pass the guild gate for the voice adapter's
357
360
  // sake — never route them into the LLM turn (legacy behavior).
358
361
  if (/^\/voice-(join|leave|status)\b/.test(ctx.text.trim())) return undefined;
362
+ const nowCommand = /^[/!]now(?:\s+([\s\S]*))?$/.exec(ctx.text.trim());
363
+ if (nowCommand) {
364
+ const args = nowCommand[1]?.trim().split(/\s+/).filter(Boolean) ?? [];
365
+ return renderTelegramNow(readSlashContextNow(args, deps.nowDeps));
366
+ }
359
367
  if (!ctx.text.trim() && ctx.attachments.length === 1 && ctx.attachments[0]!.contentType?.startsWith('image/')) {
360
368
  const photo = ctx.attachments[0]!;
361
369
  if (detectCard({ width: photo.width, height: photo.height, ocrText: null }).decision !== 'skip') {
@@ -16,12 +16,15 @@
16
16
  // 우회해 도착하므로 여기서 allowlist를 직접 강제한다.
17
17
 
18
18
  import type { UserConfig } from './user-config.js';
19
+ import { SLASH_COMMANDS } from './chat/index.js';
19
20
  import type { DiscordBot, DcIncoming, DcMessageStreamer } from './discord.js';
20
21
  import { makeCommandRest } from './discord/slash-registry.js';
21
22
  import { normalizeInteractionPayload } from './discord/slash-router.js';
22
23
  import type { SlashCommandSchema } from './discord/slash-types.js';
23
24
  import { debug } from './debug/log.js';
24
25
  import { gateDiscordSchemas, readBotAudience } from './maturity/bot-command-maturity.js';
26
+ import { buildBotSlashCatalog } from './maturity/bot-slash-catalog.js';
27
+ import { FEATURE_MATURITY } from './maturity/feature-maturity.js';
25
28
  import { resolveChannelBotToken } from './channel-bot-token.js';
26
29
 
27
30
  const REST_BASE = 'https://discord.com/api/v10';
@@ -42,6 +45,9 @@ export const ELANOUS_SLASH_COMMANDS: readonly SlashCommandSchema[] = [
42
45
  { name: 'file', description: '첨부 (이미지/문서 — 백엔드가 봄)', type: 11, required: false },
43
46
  ] },
44
47
  { name: 'brain', description: '브레인 모드로 복귀 (위임 해제)' },
48
+ { name: 'now', description: '지금 판·칸·결정·자리·최근 맥락 보기', options: [
49
+ { name: 'topic', description: '좁혀 볼 주제 (선택)', type: 3, required: false },
50
+ ] },
45
51
  { name: 'sessions', description: '바인딩된 세션 목록' },
46
52
  { name: 'new', description: '새 세션으로 시작 (이전 대화 보존)' },
47
53
  { name: 'attach', description: '이 채널을 다른 세션에 연결', options: [
@@ -58,6 +64,15 @@ export const ELANOUS_SLASH_COMMANDS: readonly SlashCommandSchema[] = [
58
64
  { name: 'voice-status', description: '보이스 연결 상태' },
59
65
  ] as const;
60
66
 
67
+ /** The native wire keeps its original schemas (including options); only new catalog
68
+ * entries use the core registry's description and the unsupported response. */
69
+ export function discordWireCatalog() {
70
+ return buildBotSlashCatalog({
71
+ surface: 'discord', coreCommands: SLASH_COMMANDS, maturity: FEATURE_MATURITY,
72
+ handledCommands: ELANOUS_SLASH_COMMANDS,
73
+ });
74
+ }
75
+
61
76
  /** SlashInteraction → 기존 텍스트 명령 문장 합성. */
62
77
  export function synthesizeCommandText(
63
78
  commandName: string,
@@ -79,6 +94,10 @@ export function synthesizeCommandText(
79
94
  case 'voice-leave': return '!voice-leave';
80
95
  case 'voice-status': return '!voice-status';
81
96
  case 'sessions': return '!sessions';
97
+ case 'now': {
98
+ const topic = options.get('topic');
99
+ return typeof topic === 'string' && topic.trim() ? `!now ${topic.trim()}` : '!now';
100
+ }
82
101
  case 'new': return '!new';
83
102
  case 'attach': return `!attach ${String(options.get('prefix') ?? '').trim()}`;
84
103
  case 'brain': return '!brain';
@@ -123,8 +142,16 @@ export function buildDiscordSlashWire(deps: DiscordSlashWireDeps): DiscordSlashW
123
142
  const guilds = await (await fetchImpl(`${REST_BASE}/users/@me/guilds`, auth)).json() as Array<{ id: string }>;
124
143
  const rest = makeCommandRest({ token, ...(deps.__fetchImpl ? { fetchImpl: deps.__fetchImpl } : {}) });
125
144
  const audience = readBotAudience((deps.userConfig.raw?.discord as { commandAudience?: unknown } | undefined)?.commandAudience);
126
- const { schemas, hidden } = gateDiscordSchemas(ELANOUS_SLASH_COMMANDS, audience);
127
- debug.log('discord.command', 'menu-filtered', { total: ELANOUS_SLASH_COMMANDS.length, shown: schemas.length - hidden, ...audience });
145
+ const catalog = discordWireCatalog();
146
+ const handled = new Map(ELANOUS_SLASH_COMMANDS.map((schema) => [schema.name, schema]));
147
+ const allSchemas: SlashCommandSchema[] = catalog.commands.map((entry) => {
148
+ const original = handled.get(entry.name);
149
+ if (original) return original;
150
+ const { name, description } = entry;
151
+ return { name, description };
152
+ });
153
+ const { schemas, hidden } = gateDiscordSchemas(allSchemas, audience);
154
+ debug.log('discord.command', 'menu-filtered', { total: allSchemas.length, shown: schemas.length - hidden, ...audience });
128
155
  for (const g of Array.isArray(guilds) ? guilds : []) {
129
156
  const registered = await rest.bulkOverwriteGuild(app.id, g.id, schemas);
130
157
  log(`[slash] registered ${registered.length} commands in guild ${g.id}`);
@@ -180,6 +207,8 @@ export function buildDiscordSlashWire(deps: DiscordSlashWireDeps): DiscordSlashW
180
207
  await ack(it.id, it.token, `이 봇은 <#${deps.channelScope}> 채널 전용입니다.`);
181
208
  return;
182
209
  }
210
+ const unsupported = discordWireCatalog().unsupportedReply(it.commandName);
211
+ if (unsupported) { await ack(it.id, it.token, unsupported); return; }
183
212
  const text = synthesizeCommandText(it.commandName, it.options);
184
213
  const attachments = resolvedAttachments(raw);
185
214
  await ack(it.id, it.token, `▶ \`${text}\`${attachments.length ? ` (+첨부 ${attachments.length})` : ''}`);
@@ -448,7 +448,7 @@ function reportUndeliverable(kind: string, daemonPath: DaemonPathClass | 'not-fo
448
448
  try { debug.log('outbound.send', 'undeliverable', { kind: safeObservationLabel(kind), daemonPath, universe, root }); } catch { /* fail-soft */ }
449
449
  const hint = universe === 'prod'
450
450
  ? '운영 데몬이 떠 있는지 확인: elanous nexus show'
451
- : '운영으로 보내려면 설치본 elanous 로 실행하거나 ELANOUS_STATE_DIR=~/.elanous ELANOUS_CONFIG_DIR=~/.elanous 를 준다';
451
+ : '운영으로 보내려면 설치본 elanous 로 실행하거나 ELANOUS_STATE_DIR=~/.elanous 와 --config-dir ~/.elanous 를 준다';
452
452
  try { console.error(`[outbound] ⛔ 못 보냄(${kind}) — 데몬 ${daemonPath} · 직접 발송도 실패(토큰 없음 또는 전송 실패) · 이 프로세스의 우주 ${universe} (${root}) · ${hint}`); } catch { /* fail-soft */ }
453
453
  }
454
454
 
@@ -15,18 +15,41 @@
15
15
  // Resolution order:
16
16
  // 1. Programmatic override set via `setElanousConfigDir(dir)` (incl.
17
17
  // from the `--config-dir` CLI flag).
18
- // 2. `~/.elanous` — default daily-driver root.
18
+ // 2. State-dir via `effectiveInstanceRoot()` (including isolated source trees).
19
+ // 3. `~/.elanous` for the installed daily-driver root.
19
20
 
20
21
  import { homedir } from 'node:os';
21
- import { join } from 'node:path';
22
+ import { resolve } from 'node:path';
22
23
 
23
24
  let override: string | undefined;
25
+ let checkedIgnoredEnv = false;
26
+
27
+ function normalizedDir(dir: string): string {
28
+ const trimmed = dir.trim();
29
+ const expanded = trimmed === '~' ? homedir() : trimmed.startsWith('~/') ? `${homedir()}${trimmed.slice(1)}` : trimmed;
30
+ return resolve(expanded);
31
+ }
32
+
33
+ function warnIfIgnoredEnv(resolved: string, source: 'flag' | 'state-dir' | 'default'): void {
34
+ if (checkedIgnoredEnv) return;
35
+ checkedIgnoredEnv = true;
36
+ const env = process.env.ELANOUS_CONFIG_DIR;
37
+ if (env === undefined || normalizedDir(env) === normalizedDir(resolved)) return;
38
+ console.error(`ELANOUS_CONFIG_DIR 는 읽지 않습니다 — 설정 폴더 = ${resolved} · 그 폴더를 쓰려면 --config-dir ${normalizedDir(env)}`);
39
+ try {
40
+ const { debug } = require('./debug/log.js') as typeof import('./debug/log.js');
41
+ debug.log('config.dir', 'env-ignored', { env, resolved, source });
42
+ } catch { /* logging must not change config resolution */ }
43
+ }
24
44
 
25
45
  /** Resolve the current elanous config / daemon root directory. Caller
26
46
  * decides what subpath to append (`workflows/`, `config.json`,
27
47
  * `acp-token`, ...). */
28
48
  export function getElanousConfigDir(): string {
29
- if (override !== undefined) return override;
49
+ if (override !== undefined) {
50
+ warnIfIgnoredEnv(override, 'flag');
51
+ return override;
52
+ }
30
53
  // ★ §4d (P3 · 2026-07-26) — **config-dir 은 state-dir 을 따라간다.**
31
54
  //
32
55
  // `ELANOUS_STATE_DIR` 은 env 라 전 자손에 자동 전파되는데 config-dir 은 argv 재부착 **7곳**
@@ -41,7 +64,9 @@ export function getElanousConfigDir(): string {
41
64
  // (`configDirFollowingStateDir` 은 §4d 계약을 문서화·테스트하는 순수 함수로 남는다 —
42
65
  // 실제 해석 경로는 이 리졸버 하나다.)
43
66
  const { effectiveInstanceRoot } = require('./instance/resolve.js') as typeof import('./instance/resolve.js');
44
- return effectiveInstanceRoot();
67
+ const resolved = effectiveInstanceRoot();
68
+ warnIfIgnoredEnv(resolved, process.env.ELANOUS_STATE_DIR?.trim() ? 'state-dir' : 'default');
69
+ return resolved;
45
70
  }
46
71
 
47
72
  /** Programmatic override. Trims, rejects empty/blank. Child processes