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
@@ -0,0 +1,34 @@
1
+ export type LoopCronVerdict = 'alive' | 'late' | 'failed' | 'off';
2
+
3
+ /** Shared display inputs for a graph loop or cron job. Fixed intervals use
4
+ * milliseconds; calendar cron schedules supply the second actual due instant. */
5
+ export interface LoopCronVerdictInput {
6
+ enabled: boolean;
7
+ lastRunAt: string | null;
8
+ lastStatus: string | null;
9
+ intervalMs: number | null;
10
+ /** Second scheduled execution after the last run, for calendar-based cron schedules. */
11
+ secondDueAt?: string | null;
12
+ /** Cron-aware daemon state (src/domains/schedule-state.ts). Callers pass it for cron schedules only —
13
+ * a fixed interval keeps its own «2× interval» rule (review must-fix: stale must not override an interval). */
14
+ scheduleState?: 'live' | 'firing' | 'stale' | 'off';
15
+ }
16
+
17
+ /** Pure health classification; a failed last execution takes precedence over age.
18
+ * Without a valid last run and a cadence/due instant, lateness needs the daemon stale signal. */
19
+ export function loopCronVerdict(entry: LoopCronVerdictInput, nowMs: number): LoopCronVerdict {
20
+ if (!entry.enabled || entry.scheduleState === 'off') return 'off';
21
+ if (entry.lastStatus === 'failed' || entry.lastStatus === 'error' || entry.lastStatus === 'failure' ||
22
+ entry.lastStatus === 'abandoned' || entry.lastStatus === 'expired') return 'failed';
23
+ if (entry.scheduleState === 'stale') return 'late';
24
+ const lastRunMs = entry.lastRunAt === null ? NaN : Date.parse(entry.lastRunAt);
25
+ if (entry.secondDueAt !== undefined) {
26
+ const secondDueMs = entry.secondDueAt === null ? NaN : Date.parse(entry.secondDueAt);
27
+ return Number.isFinite(lastRunMs) && Number.isFinite(nowMs) &&
28
+ Number.isFinite(secondDueMs) && secondDueMs > lastRunMs && nowMs > secondDueMs ? 'late' : 'alive';
29
+ }
30
+ if (Number.isFinite(nowMs) && Number.isFinite(lastRunMs) &&
31
+ entry.intervalMs !== null && Number.isFinite(entry.intervalMs) && entry.intervalMs > 0 &&
32
+ nowMs - lastRunMs > 2 * entry.intervalMs) return 'late';
33
+ return 'alive';
34
+ }
@@ -0,0 +1,66 @@
1
+ import type { SlashCommand } from '../chat/index.js';
2
+ import type { Maturity } from './feature-maturity.js';
3
+ import type { BotSurface } from './bot-command-maturity.js';
4
+
5
+ export interface BotSlashCatalogEntry {
6
+ name: string;
7
+ description: string;
8
+ supported: boolean;
9
+ }
10
+
11
+ export interface BotSlashCatalog {
12
+ commands: BotSlashCatalogEntry[];
13
+ /** Null means this name is already handled, or is not in the catalog. */
14
+ unsupportedReply(name: string): string | null;
15
+ }
16
+
17
+ /** The menu is a projection of registered core commands, not a second list of invented names.
18
+ * A bot's actual handlers take precedence over maturity metadata: a grade describes
19
+ * visibility, never whether an existing command can be dispatched. */
20
+ export function buildBotSlashCatalog(options: {
21
+ surface: BotSurface;
22
+ coreCommands: readonly Pick<SlashCommand, 'name' | 'description'>[];
23
+ maturity: {
24
+ tuiSlash: Readonly<Record<string, Maturity>>;
25
+ telegramCommand: Readonly<Record<string, Maturity>>;
26
+ discordCommand: Readonly<Record<string, Maturity>>;
27
+ };
28
+ handledCommands: readonly { name: string; description: string }[];
29
+ }): BotSlashCatalog {
30
+ const { surface, coreCommands, maturity, handledCommands } = options;
31
+ const grades = surface === 'telegram' ? maturity.telegramCommand : maturity.discordCommand;
32
+ const validName = surface === 'telegram' ? /^[a-z0-9_]{1,32}$/ : /^[a-z0-9_-]{1,32}$/;
33
+ const commands: BotSlashCatalogEntry[] = [];
34
+ const seen = new Set<string>();
35
+ for (const command of handledCommands) {
36
+ if (seen.has(command.name)) continue;
37
+ commands.push({ name: command.name, description: command.description, supported: true });
38
+ seen.add(command.name);
39
+ }
40
+ const limit = surface === 'telegram' ? 100 : 25;
41
+ const candidateNames = new Set<string>();
42
+ const candidates = coreCommands.filter((command) => {
43
+ const name = command.name;
44
+ const grade = Object.hasOwn(grades, name) ? grades[name] : undefined;
45
+ if (seen.has(name) || candidateNames.has(name) || !validName.test(name) || grade === undefined || grade === 'broken' || grade === 'system') return false;
46
+ candidateNames.add(name);
47
+ return true;
48
+ });
49
+ if (commands.length + candidates.length > limit) {
50
+ throw new Error(`${surface} command catalog exceeds ${limit} slots`);
51
+ }
52
+ for (const command of candidates) {
53
+ const name = command.name;
54
+ const description = command.description.replace(/\s+/g, ' ').trim();
55
+ const suffix = ` (${surface === 'telegram' ? '텔레그램' : '디스코드'} 미지원)`;
56
+ commands.push({ name, description: `${description.slice(0, 100 - suffix.length)}${suffix}`, supported: false });
57
+ seen.add(name);
58
+ }
59
+ const unsupported = new Set(commands.filter((command) => !command.supported).map((command) => command.name));
60
+ return {
61
+ commands,
62
+ unsupportedReply(name) {
63
+ return unsupported.has(name) ? `/${name}은(는) ${surface === 'telegram' ? '텔레그램' : '디스코드'}에서 아직 지원되지 않습니다. TUI에서 /${name}을(를) 사용하세요.` : null;
64
+ },
65
+ };
66
+ }
@@ -41,6 +41,7 @@ export const FEATURE_MATURITY = {
41
41
  '/design-check': { pwa: 'tool' },
42
42
  '/control': { pwa: 'tool' },
43
43
  '/scheduler': { pwa: 'ops' },
44
+ '/loops': { pwa: 'beta' },
44
45
  '/ops/release': { pwa: 'ops' },
45
46
  '/ops/seats': { pwa: 'ops' },
46
47
  '/ops/checklist': { pwa: 'ops' },
@@ -57,11 +58,11 @@ export const FEATURE_MATURITY = {
57
58
  pty: 'beta', pr: 'beta', repo: 'beta', role: 'beta', machine: 'beta',
58
59
  where: 'stable', shadow: 'beta', llm: 'beta', storage: 'beta', leader: 'beta',
59
60
  pod: 'beta', browser: 'beta', questions: 'beta', usage: 'stable', live: 'beta',
60
- research: 'beta', release: 'beta', seat: 'beta', coord: 'beta', context: 'beta', 'model-watch': 'beta', doctor: 'stable',
61
+ research: 'beta', release: 'beta', freeze: 'beta', flow: 'beta', seat: 'beta', hq: 'beta', steward: 'beta', coord: 'beta', context: 'beta', 'model-watch': 'beta', doctor: 'stable',
61
62
  control: 'beta', resources: 'beta', hooks: 'beta', setup: 'stable', start: 'stable',
62
63
  grounding: 'beta', graph: 'beta', loop: 'stable', card: 'stable', 'launch-head': 'beta',
63
64
  a2a: 'beta', plugin: 'stable', make: 'beta', skills: 'beta', connect: 'beta', import: 'beta',
64
- persona: 'beta', 'team-set': 'beta', market: 'beta', directive: 'stable', connector: 'beta', storyboard: 'beta',
65
+ persona: 'beta', team: 'beta', 'team-set': 'beta', market: 'beta', directive: 'stable', connector: 'beta', storyboard: 'beta',
65
66
  python: 'beta', 'self-update': 'stable', 'measure-fabric-arc-ab': 'beta', mcp: 'beta',
66
67
  relay: 'beta', 'telegram-test': 'beta', telegram: 'beta', 'discord-test': 'beta',
67
68
  sync: 'beta', status: 'beta', git: 'beta', gh: 'beta', repro: 'system',
@@ -71,7 +72,7 @@ export const FEATURE_MATURITY = {
71
72
  provider: 'stable', 'provider:set': 'beta', 'provider:restore': 'beta',
72
73
  'provider:rotate': 'beta', 'provider:use': 'beta', 'status-bar': 'beta',
73
74
  schedule: 'beta', intake: 'beta', logs: 'stable', ad: 'beta', docs: 'beta',
74
- decisions: 'beta', directives: 'beta', fleet: 'beta', ops: 'stable', publish: 'beta',
75
+ decisions: 'beta', directives: 'beta', claims: 'beta', lesson: 'beta', fleet: 'beta', ops: 'stable', outputs: 'beta', publish: 'beta',
75
76
  env: 'beta', 'agent-mission': 'beta', dev: 'beta', keys: 'beta', finance: 'beta',
76
77
  config: 'stable', tasks: 'stable', labels: 'beta', msg: 'beta', ask: 'stable',
77
78
  onboarding: 'beta', login: 'stable', session: 'beta', chat: 'stable', repl: 'stable',
@@ -110,6 +111,8 @@ export const FEATURE_MATURITY = {
110
111
  plugin: 'tool',
111
112
  widget: 'tool',
112
113
  log: 'tool',
114
+ loops: 'beta',
115
+ persona: 'beta', // G3 b — 대화별 페르소나(GOAL-BENCH B7 #23836)
113
116
  memory: 'beta',
114
117
  export: 'stable',
115
118
  delta: 'tool',
@@ -153,7 +156,7 @@ export const FEATURE_MATURITY = {
153
156
  ping: 'stable', provider: 'stable', sessions: 'stable', fork: 'stable', resume: 'stable',
154
157
  decisions: 'stable', work: 'stable', cancel: 'stable',
155
158
  skills: 'beta', skill: 'beta', digest: 'beta', intake: 'beta', ad: 'beta',
156
- taste: 'beta', missions: 'beta', attach: 'beta', detach: 'beta', now: 'beta', project: 'beta', wish: 'beta',
159
+ taste: 'beta', missions: 'beta', attach: 'beta', detach: 'beta', now: 'beta', loops: 'beta', project: 'beta', wish: 'beta',
157
160
  brain: 'tool', cc: 'tool', cdx: 'tool', gem: 'tool', local: 'tool',
158
161
  harness: 'ops',
159
162
  // Existing commands outside the CXO four-grade menu list are owner-only.
@@ -162,7 +165,7 @@ export const FEATURE_MATURITY = {
162
165
  },
163
166
  discordCommand: {
164
167
  status: 'stable', sessions: 'stable', new: 'stable', fork: 'stable',
165
- persona: 'beta', poll: 'beta', attach: 'beta',
168
+ persona: 'beta', poll: 'beta', attach: 'beta', now: 'beta',
166
169
  brain: 'tool', cc: 'tool', cdx: 'tool', gem: 'tool',
167
170
  relay: 'ops', showroom: 'ops', bots: 'ops', bot: 'ops', screen: 'ops', chart: 'ops',
168
171
  routines: 'ops', botsay: 'ops', 'voice-join': 'ops', 'voice-leave': 'ops', 'voice-status': 'ops',
package/src/memory.ts CHANGED
@@ -63,6 +63,16 @@ export function writeFileDurable(path: string, data: string, fsync: (fd: number)
63
63
 
64
64
  export type MemoryType = 'user' | 'feedback' | 'project' | 'reference';
65
65
 
66
+ /** Context-condense metadata travels with the existing curated memory file, not a parallel store. */
67
+ export interface ContextMemoryCard {
68
+ project: string;
69
+ seat: string;
70
+ topic: string;
71
+ source: string;
72
+ updatedAt: string;
73
+ status: 'active' | 'retired';
74
+ }
75
+
66
76
  export interface MemoryEntry {
67
77
  id: string; // uuid v4
68
78
  filename: string; // "<uuid>-<slug>.md"
@@ -89,6 +99,7 @@ export interface MemoryEntry {
89
99
  pinned: boolean;
90
100
  /** Optional pinned-memory freshness window; absent memories retain their original injection text. */
91
101
  staleAfterMinutes?: number;
102
+ contextCard?: ContextMemoryCard;
92
103
  }
93
104
 
94
105
  // ── Paths ────────────────────────────────────────────────────────────
@@ -192,6 +203,7 @@ export interface SaveMemoryOpts {
192
203
  staleAfterMinutes?: number;
193
204
  /** Override the generated id (tests). */
194
205
  id?: string;
206
+ contextCard?: ContextMemoryCard;
195
207
  }
196
208
 
197
209
  export function saveMemory(opts: SaveMemoryOpts, root: string = memoryRoot()): MemoryEntry {
@@ -207,6 +219,7 @@ export function saveMemory(opts: SaveMemoryOpts, root: string = memoryRoot()): M
207
219
  let existingPriority: number | undefined;
208
220
  let existingPinned: boolean | undefined;
209
221
  let existingStaleAfterMinutes: number | undefined;
222
+ let existingContextCard: ContextMemoryCard | undefined;
210
223
  if (existsSync(path)) {
211
224
  try {
212
225
  const parsed = parseFrontmatter(readFileSync(path, 'utf-8'));
@@ -214,6 +227,7 @@ export function saveMemory(opts: SaveMemoryOpts, root: string = memoryRoot()): M
214
227
  if (parsed.fm.priority) existingPriority = parseInt(parsed.fm.priority, 10) || 0;
215
228
  if (parsed.fm.pinned) existingPinned = parsed.fm.pinned === 'true';
216
229
  existingStaleAfterMinutes = parseStaleAfterMinutes(parsed.fm['stale-after-minutes']);
230
+ existingContextCard = parseContextCard(parsed.fm['context-card']);
217
231
  } catch { /* keep new createdAt */ }
218
232
  }
219
233
  const priority = opts.priority ?? existingPriority ?? 0;
@@ -229,6 +243,8 @@ export function saveMemory(opts: SaveMemoryOpts, root: string = memoryRoot()): M
229
243
  if (priority > 0) frontmatter.priority = String(Math.floor(priority));
230
244
  if (pinned) frontmatter.pinned = 'true';
231
245
  if (staleAfterMinutes !== undefined) frontmatter['stale-after-minutes'] = String(staleAfterMinutes);
246
+ const contextCard = opts.contextCard ?? existingContextCard;
247
+ if (contextCard) frontmatter['context-card'] = JSON.stringify(contextCard);
232
248
  const raw = renderFrontmatter(frontmatter, opts.body);
233
249
  writeFileDurable(path, raw);
234
250
 
@@ -248,9 +264,23 @@ export function saveMemory(opts: SaveMemoryOpts, root: string = memoryRoot()): M
248
264
  priority,
249
265
  pinned,
250
266
  staleAfterMinutes,
267
+ ...(contextCard ? { contextCard } : {}),
251
268
  };
252
269
  }
253
270
 
271
+ function parseContextCard(raw: string | undefined): ContextMemoryCard | undefined {
272
+ if (!raw) return undefined;
273
+ try {
274
+ const card: unknown = JSON.parse(raw);
275
+ if (card && typeof card === 'object' && !Array.isArray(card)) {
276
+ const c = card as Record<string, unknown>;
277
+ if (['project', 'seat', 'topic', 'source', 'updatedAt'].every(key => typeof c[key] === 'string')
278
+ && (c.status === 'active' || c.status === 'retired')) return c as unknown as ContextMemoryCard;
279
+ }
280
+ } catch { /* malformed metadata is not a context memory */ }
281
+ return undefined;
282
+ }
283
+
254
284
  /** Parse one memory file. Returns null on read/parse error. */
255
285
  function parseMemoryFile(path: string): MemoryEntry | null {
256
286
  if (!existsSync(path)) return null;
@@ -261,6 +291,7 @@ function parseMemoryFile(path: string): MemoryEntry | null {
261
291
  const basename = path.split(/[\\/]/).pop() ?? '';
262
292
  const match = basename.match(/^([0-9a-f-]{36})-(.+)\.md$/i);
263
293
  const id = match ? match[1] : createHash('sha1').update(basename).digest('hex').slice(0, 36);
294
+ const contextCard = parseContextCard(fm['context-card']);
264
295
  return {
265
296
  id,
266
297
  filename: basename,
@@ -274,6 +305,7 @@ function parseMemoryFile(path: string): MemoryEntry | null {
274
305
  priority: fm.priority ? (parseInt(fm.priority, 10) || 0) : 0,
275
306
  pinned: fm.pinned === 'true',
276
307
  staleAfterMinutes: parseStaleAfterMinutes(fm['stale-after-minutes']),
308
+ ...(contextCard ? { contextCard } : {}),
277
309
  };
278
310
  }
279
311
 
@@ -364,6 +396,7 @@ export function searchMemories(
364
396
  const entries = listMemories({ type: opts.type }, root);
365
397
  const hits: SearchHit[] = [];
366
398
  for (const entry of entries) {
399
+ if (entry.contextCard?.status === 'retired') continue;
367
400
  // 정확-토큰 매칭(2026-07-19) — 이전 substring `includes(kw)` 는 "ref"가 "reference"·
368
401
  // "prefer" 안에까지 걸려 오차용(false-recall)을 냈다(openclaw tool-search.ts 이식:
369
402
  // tokenize→Set.has 전체토큰 동등). name/description 은 토큰집합 정확매칭, body 는
@@ -415,7 +448,7 @@ export function rebuildIndex(root: string = memoryRoot()): void {
415
448
  const byType: Record<MemoryType, MemoryEntry[]> = {
416
449
  user: [], feedback: [], project: [], reference: [],
417
450
  };
418
- for (const e of entries) byType[e.type].push(e);
451
+ for (const e of entries) if (e.contextCard?.status !== 'retired') byType[e.type].push(e);
419
452
 
420
453
  const lines: string[] = [];
421
454
  lines.push('# elanous memory index');
@@ -515,7 +548,7 @@ export function buildMemoryInjection(
515
548
 
516
549
  // 📌 pinned 기억 — 키워드 무관 항상 주입(relevance gate bypass · Letta pinned block /
517
550
  // claude-code MEMORY.md tier 동형). priority 높은 순. core/safety/ref-location 사실용.
518
- const pinnedMems = listMemories({}, root).filter(m => m.pinned).sort((a, b) => b.priority - a.priority);
551
+ const pinnedMems = listMemories({}, root).filter(m => m.pinned && m.contextCard?.status !== 'retired').sort((a, b) => b.priority - a.priority);
519
552
  const pinnedParts: string[] = [];
520
553
  for (const e of pinnedMems) {
521
554
  const chunk = pinnedChunk(e);
@@ -678,7 +711,7 @@ export async function buildMemoryInjectionLLM(
678
711
  }
679
712
 
680
713
  // pinned 항상주입(키워드/판정 무관).
681
- const pinnedMems = listMemories({}, root).filter((m) => m.pinned).sort((a, b) => b.priority - a.priority);
714
+ const pinnedMems = listMemories({}, root).filter((m) => m.pinned && m.contextCard?.status !== 'retired').sort((a, b) => b.priority - a.priority);
682
715
  const pinnedParts: string[] = [];
683
716
  for (const e of pinnedMems) {
684
717
  const chunk = pinnedChunk(e);
@@ -693,7 +726,7 @@ export async function buildMemoryInjectionLLM(
693
726
  let judged: MemoryEntry[] = [];
694
727
  if (!lowContext) {
695
728
  const candidates = listMemories({}, root)
696
- .filter((m) => !m.pinned && !injected.includes(m.id))
729
+ .filter((m) => !m.pinned && m.contextCard?.status !== 'retired' && !injected.includes(m.id))
697
730
  .slice(0, opts.maxCandidates ?? 60);
698
731
  if (candidates.length > 0) {
699
732
  try {
@@ -0,0 +1,118 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { chmodSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
3
+ import { isAbsolute, join, relative, sep } from 'node:path';
4
+ import { defaultAttachmentBaseDir, resolveAttachmentPath } from '../../boot/attachment-store.js';
5
+ import { detectCard, NotACardError, runCardFollowup } from '../../card-followup/core.js';
6
+ import { debug } from '../../debug/log.js';
7
+ import { effectiveInstanceRoot } from '../../instance/resolve.js';
8
+ import { localOcrText } from '../../telegram-card-followup.js';
9
+ import { jsonResponse } from './json-response.js';
10
+
11
+ export const CARD_FOLLOWUP_PATH = '/v1/card-followup';
12
+
13
+ type CardJob = { id: string; status: 'judging' | 'running' | 'done' | 'not-card' | 'failed'; updatedAt: number; replies?: [string, string, string]; error?: string };
14
+ type CardDeps = {
15
+ rootDir?: string;
16
+ resolveAttachment?: typeof resolveAttachmentPath;
17
+ ocr?: typeof localOcrText;
18
+ detect?: typeof detectCard;
19
+ run?: typeof runCardFollowup;
20
+ now?: () => number;
21
+ };
22
+
23
+ function withinAttachmentStore(path: string): boolean {
24
+ try {
25
+ const relativePath = relative(realpathSync(defaultAttachmentBaseDir()), realpathSync(path));
26
+ return relativePath !== '' && relativePath !== '..' && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath);
27
+ } catch { return false; }
28
+ }
29
+
30
+ /** The in-memory set identifies only work launched by this daemon; disk is the pollable ledger. */
31
+ export function createCardFollowupJobs({
32
+ rootDir = effectiveInstanceRoot(),
33
+ resolveAttachment = resolveAttachmentPath,
34
+ ocr = localOcrText,
35
+ detect = detectCard,
36
+ run = runCardFollowup,
37
+ now = Date.now,
38
+ }: CardDeps = {}) {
39
+ const base = join(rootDir, 'graph-runs', 'card-followup');
40
+ const active = new Set<string>();
41
+ const unavailable = new Set<string>();
42
+ const file = (id: string) => join(base, `${id}.pwa.json`);
43
+ const save = (job: CardJob) => {
44
+ mkdirSync(base, { recursive: true, mode: 0o700 });
45
+ chmodSync(base, 0o700);
46
+ writeFileSync(file(job.id), JSON.stringify(job), { mode: 0o600 });
47
+ chmodSync(file(job.id), 0o600);
48
+ };
49
+
50
+ return {
51
+ async post(req: Request): Promise<Response> {
52
+ let body: unknown;
53
+ try { body = await req.json(); } catch { body = null; }
54
+ if (!body || typeof body !== 'object' || Array.isArray(body)) return jsonResponse({ error: 'bad_request' }, 400);
55
+ const { attachmentId, context } = body as Record<string, unknown>;
56
+ if (context !== undefined && typeof context !== 'string') return jsonResponse({ error: 'bad_request' }, 400);
57
+ const imagePath = typeof attachmentId === 'string' ? resolveAttachment(attachmentId) : null;
58
+ if (!imagePath || (resolveAttachment === resolveAttachmentPath && !withinAttachmentStore(imagePath))) {
59
+ return jsonResponse({ error: 'attachment-not-found' }, 400);
60
+ }
61
+
62
+ const id = randomUUID();
63
+ const update = (status: CardJob['status'], fields: Partial<Pick<CardJob, 'replies' | 'error'>> = {}) =>
64
+ save({ id, status, updatedAt: now(), ...fields });
65
+ update('judging');
66
+ active.add(id);
67
+ debug.log('card-followup.pwa', 'started', { id });
68
+ // Start OCR on the next event-loop turn so the acceptance response precedes the work.
69
+ setTimeout(() => {
70
+ void (async () => {
71
+ try {
72
+ const ocrText = await ocr(imagePath);
73
+ if (detect({ ocrText }).decision !== 'card') {
74
+ update('not-card');
75
+ debug.log('card-followup.pwa', 'not-card', { id });
76
+ return;
77
+ }
78
+ update('running');
79
+ const result = await run({ imagePath, ocrText, rootDir, runId: id, ...(context === undefined ? {} : { context }) });
80
+ update('done', { replies: result.replies });
81
+ debug.log('card-followup.pwa', 'done', { id });
82
+ } catch (cause) {
83
+ const notCard = cause instanceof NotACardError;
84
+ // OCR·그래프 예외 문구엔 명함 원문이 섞일 수 있다 — 원장·응답엔 고정 코드만 싣는다(리뷰 must-fix).
85
+ try {
86
+ update(notCard ? 'not-card' : 'failed', notCard ? {} : { error: 'card-followup-failed' });
87
+ debug.log('card-followup.pwa', notCard ? 'not-card' : 'failed', { id });
88
+ } catch {
89
+ unavailable.add(id);
90
+ debug.log('card-followup.pwa', 'failed', { id });
91
+ }
92
+ } finally {
93
+ active.delete(id);
94
+ }
95
+ })().catch(() => {
96
+ unavailable.add(id);
97
+ active.delete(id);
98
+ });
99
+ });
100
+ return jsonResponse({ id }, 202);
101
+ },
102
+ get(id: string): Response {
103
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)) return jsonResponse({ error: 'not-found' }, 404);
104
+ if (unavailable.has(id)) return jsonResponse({ id, status: 'failed', error: 'card-followup-failed' });
105
+ let job: CardJob;
106
+ try { job = JSON.parse(readFileSync(file(id), 'utf8')) as CardJob; }
107
+ catch { return jsonResponse({ error: 'not-found' }, 404); }
108
+ if (job.id !== id) return jsonResponse({ error: 'not-found' }, 404);
109
+ if ((job.status === 'judging' || job.status === 'running') && !active.has(id)) {
110
+ return jsonResponse({ id, status: 'failed', error: 'daemon-restarted' });
111
+ }
112
+ const { status, replies, error } = job;
113
+ return jsonResponse({ id, status, ...(replies ? { replies } : {}), ...(error ? { error } : {}) });
114
+ },
115
+ };
116
+ }
117
+
118
+ export type CardFollowupJobs = ReturnType<typeof createCardFollowupJobs>;
@@ -4,6 +4,7 @@
4
4
  // GET /v1/dashboard/timeline — 신호 타임라인 (?hours=48&floor=6)
5
5
  // GET /v1/dashboard/heatmap — 자산×국가×섹터 매력도 + US 섹터 + 뉴스 분포
6
6
  // GET /v1/dashboard/digs — 디깅 리포트 피드 (?limit=20)
7
+ // GET /v1/dashboard/loop-activity — 루프 활동 그래프 (?since=24h)
7
8
  //
8
9
  // 전부 read-only · 캐시/로컬 SQLite 만 (dashboard-data.ts) — 라이브 fetch 없음.
9
10
  // PWA 폴링(60s) 대상. reflection.ts 패턴(CORS·checkAuth seam) 상속.
@@ -16,12 +17,15 @@ import {
16
17
  } from '../../domains/dashboard-data.js';
17
18
  import { readLiveSnapshot } from '../../domains/market-live.js';
18
19
  import { marketSessions } from '../../domains/finance.js';
20
+ import { loopActivity } from '../../loops/activity.js';
19
21
 
20
22
  export interface DashboardRouteOpts {
21
23
  /** Auth check seam. */
22
24
  checkAuth?: (req: Request) => boolean;
23
25
  /** Test seam — 실제 detached 수집 spawn 대체. */
24
26
  spawnCollector?: () => void;
27
+ /** Read-only activity provider for isolated route tests. */
28
+ loopActivity?: typeof loopActivity;
25
29
  }
26
30
 
27
31
  const CORS_HEADERS: Record<string, string> = {
@@ -43,7 +47,7 @@ export function parseDashboardPath(pathname: string): string | null {
43
47
  const m = /^\/v1\/dashboard\/([^/]+)$/.exec(pathname);
44
48
  if (!m) return null;
45
49
  const seg = decodeURIComponent(m[1]!);
46
- return ['summary', 'timeline', 'heatmap', 'digs', 'schedules', 'ontology', 'backtest', 'loops', 'ops'].includes(seg) ? seg : null;
50
+ return ['summary', 'timeline', 'heatmap', 'digs', 'schedules', 'ontology', 'backtest', 'loops', 'loop-activity', 'ops'].includes(seg) ? seg : null;
47
51
  }
48
52
 
49
53
  /** POST /v1/dashboard/refresh-live — 온디맨드 라이브 재수집 트리거 (refresh 버튼 설계 ·
@@ -99,6 +103,7 @@ export function handleDashboard(req: Request, section: string, opts: DashboardRo
99
103
  case 'ontology': return jsonResponse({ ok: true, ontology: dashboardOntology() }, 200);
100
104
  case 'backtest': return jsonResponse({ ok: true, backtest: dashboardBacktest() }, 200);
101
105
  case 'loops': return jsonResponse({ ok: true, loops: dashboardLoops() }, 200);
106
+ case 'loop-activity': return jsonResponse({ ok: true, activity: (opts.loopActivity ?? loopActivity)({ since: q.get('since') ?? '24h' }) }, 200);
102
107
  case 'ops': {
103
108
  const mid = q.get('mission');
104
109
  if (mid) return jsonResponse({ ok: true, missionDetail: dashboardOpsMission(mid) }, 200);
@@ -0,0 +1,68 @@
1
+ import { debug } from '../../debug/log.js';
2
+ import { DecisionLedger, type DecisionEntry } from '../../decisions/decision-ledger.js';
3
+ import { jsonResponse } from './json-response.js';
4
+
5
+ export const DECISIONS_PATH = '/v1/decisions';
6
+
7
+ export interface DecisionsRouteDeps {
8
+ authorize?: (request: Request) => boolean;
9
+ ledger?: Pick<DecisionLedger, 'list' | 'decide'>;
10
+ }
11
+
12
+ export async function handleDecisions(req: Request, deps: DecisionsRouteDeps = {}): Promise<Response> {
13
+ const pathname = new URL(req.url).pathname;
14
+ if (pathname !== DECISIONS_PATH && !/^\/v1\/decisions\/[^/]+\/decide$/.test(pathname)) return jsonResponse({ error: 'not-found' }, 404);
15
+ if (!deps.authorize?.(req)) {
16
+ debug.log('decisions.api', 'rejected', { reason: 'unauthorized' });
17
+ return jsonResponse({ error: 'unauthorized' }, 401);
18
+ }
19
+ const ledger = deps.ledger ?? new DecisionLedger();
20
+ if (pathname === DECISIONS_PATH) {
21
+ if (req.method !== 'GET') return jsonResponse({ error: 'method-not-allowed' }, 405);
22
+ if (new URL(req.url).searchParams.get('status') !== 'open') {
23
+ debug.log('decisions.api', 'rejected', { reason: 'invalid-status' });
24
+ return jsonResponse({ error: 'bad_request' }, 400);
25
+ }
26
+ const decisions = ledger.list({ status: 'open' }).map(entry => ({
27
+ id: entry.id,
28
+ title: entry.title,
29
+ situation: entry.scqa.s.slice(0, 200),
30
+ options: entry.options.map(option => ({ id: option.key, label: option.label })),
31
+ recommendation: entry.recommendation,
32
+ raisedAt: entry.raisedAt,
33
+ dueAt: entry.dueAt,
34
+ }));
35
+ debug.log('decisions.api', 'listed', { count: decisions.length });
36
+ return jsonResponse({ decisions });
37
+ }
38
+ if (req.method !== 'POST') return jsonResponse({ error: 'method-not-allowed' }, 405);
39
+ let id: string;
40
+ try { id = decodeURIComponent(pathname.slice(DECISIONS_PATH.length + 1, -'/decide'.length)); }
41
+ catch {
42
+ debug.log('decisions.api', 'rejected', { reason: 'invalid-id' });
43
+ return jsonResponse({ error: 'not-found' }, 404);
44
+ }
45
+ let body: unknown;
46
+ try { body = await req.json(); } catch { body = null; }
47
+ if (!body || typeof body !== 'object' || Array.isArray(body)
48
+ || !('choice' in body) || typeof body.choice !== 'string' || !body.choice
49
+ || ('note' in body && body.note !== undefined && typeof body.note !== 'string')) {
50
+ debug.log('decisions.api', 'rejected', { id, reason: 'invalid-body' });
51
+ return jsonResponse({ error: 'bad_request' }, 400);
52
+ }
53
+ const { choice, note } = body as { choice: string; note?: string };
54
+ try {
55
+ const entry: DecisionEntry = ledger.decide(id, choice, { kind: 'human' }, note);
56
+ debug.log('decisions.api', 'decided', { id });
57
+ return jsonResponse(entry);
58
+ } catch (error) {
59
+ const message = error instanceof Error ? error.message : '';
60
+ const status = message.startsWith('decision not found:') ? 404
61
+ : message.startsWith('decision already closed:') ? 409
62
+ : message.startsWith('option not found:') || message.startsWith('note ') ? 400 : 0;
63
+ if (!status) throw error;
64
+ const reason = status === 404 ? 'not-found' : status === 409 ? 'already-decided' : 'bad_request';
65
+ debug.log('decisions.api', 'rejected', { id, reason });
66
+ return jsonResponse({ error: reason }, status);
67
+ }
68
+ }
@@ -22,6 +22,9 @@ import { spawnSync } from 'node:child_process';
22
22
  import { isAbsolute } from 'node:path';
23
23
  import { createProject, listProjects } from '../../project/project-store.js';
24
24
  import { handleOutputsGet, type OutputsDeps } from './outputs-route.js';
25
+ import { DECISIONS_PATH, handleDecisions, type DecisionsRouteDeps } from './decisions-route.js';
26
+ import { CARD_FOLLOWUP_PATH, createCardFollowupJobs, type CardFollowupJobs } from './card-followup-route.js';
27
+ import { SKILL_EXEC_PATH, createSkillExecJobs, type SkillExecJobs } from './skill-exec-route.js';
25
28
  import { compareTokenConstTime } from '../../acp/transport/auth.js';
26
29
  import { ensureAuthToken } from '../../auth/acp-token.js';
27
30
  import type { NexusState } from '../state/state.js';
@@ -181,7 +184,9 @@ import { handleChannelBotsGet, handleChannelBotSet } from './setup-channel-bot.j
181
184
  import { handleLlmRoutePredict } from './llm-route-predict.js';
182
185
  import type { MissionRouter } from '../../llm/mission-router.js';
183
186
  import { handleContextFetchUrl } from './context-url.js';
184
- import { contextNow } from '../../context-bus/context-now.js';
187
+ import { contextNow, type ContextNowDeps } from '../../context-bus/context-now.js';
188
+ import type { ContextNowAudience } from '../../context-bus/context-now-surfaces.js';
189
+ import { renderVoiceNow } from '../../context-bus/context-now-surfaces.js';
185
190
  import type { IntentPredictionService } from '../../intent-prediction/index.js';
186
191
  import type { NotificationActionLoopback } from '../../web-push/notification-action-loopback.js';
187
192
  import { handlePlatforms } from './platforms.js';
@@ -472,6 +477,9 @@ export interface NexusHttpServerOpts {
472
477
  /** Optional installer state root for isolated API consumers and tests. */
473
478
  pluginStateRoot?: string;
474
479
  outputs?: OutputsDeps;
480
+ decisions?: Pick<DecisionsRouteDeps, 'ledger'>;
481
+ cardFollowup?: CardFollowupJobs;
482
+ skillExec?: SkillExecJobs;
475
483
  execRequests?: ExecRequestRunner;
476
484
  seatRequests?: SeatRequestsDeps;
477
485
  consultRequests?: ConsultRequestsDeps;
@@ -480,6 +488,8 @@ export interface NexusHttpServerOpts {
480
488
  eventBus: NexusEventBus;
481
489
  /** Test seam for the read-only usage collector; production uses the cached handler. */
482
490
  usageHandler?: () => Promise<Response>;
491
+ /** Test seam for the context-now ledgers; production reads the live ledgers. */
492
+ contextNowDeps?: ContextNowDeps;
483
493
  /** Supervisor handle — required for the write API (PR ι). When omitted
484
494
  * (skipSupervisor mode), mutation routes return 503. */
485
495
  supervisor?: Supervisor;
@@ -852,6 +862,16 @@ function execRequestRunner(): ExecRequestRunner {
852
862
  return execRequestRunnerInstance;
853
863
  }
854
864
 
865
+ let cardFollowupJobs: CardFollowupJobs | undefined;
866
+ function defaultCardFollowupJobs(): CardFollowupJobs {
867
+ return cardFollowupJobs ??= createCardFollowupJobs();
868
+ }
869
+
870
+ let skillExecJobs: SkillExecJobs | undefined;
871
+ function defaultSkillExecJobs(): SkillExecJobs {
872
+ return skillExecJobs ??= createSkillExecJobs();
873
+ }
874
+
855
875
  export async function routeRequest(
856
876
  req: Request,
857
877
  opts: NexusHttpServerOpts,
@@ -1338,6 +1358,23 @@ export async function routeRequest(
1338
1358
  return method === 'GET' ? handleProjectsGet() : handleProjectsPost(req);
1339
1359
  }
1340
1360
 
1361
+ // Polling must remain outside the non-GET dispatch below.
1362
+ if (method === 'GET' && pathname.startsWith(`${SKILL_EXEC_PATH}/`) && !pathname.slice(SKILL_EXEC_PATH.length + 1).includes('/')) {
1363
+ if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
1364
+ return (opts.skillExec ?? defaultSkillExecJobs()).get(pathname.slice(SKILL_EXEC_PATH.length + 1));
1365
+ }
1366
+ if (method === 'GET' && pathname.startsWith(`${CARD_FOLLOWUP_PATH}/`) && !pathname.slice(CARD_FOLLOWUP_PATH.length + 1).includes('/')) {
1367
+ if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
1368
+ return (opts.cardFollowup ?? defaultCardFollowupJobs()).get(pathname.slice(CARD_FOLLOWUP_PATH.length + 1));
1369
+ }
1370
+
1371
+ if (method === 'GET' && pathname === DECISIONS_PATH) {
1372
+ return handleDecisions(req, {
1373
+ authorize: request => !!opts.metaApi && checkAuth(request, opts.metaApi),
1374
+ ...opts.decisions,
1375
+ });
1376
+ }
1377
+
1341
1378
  if (method === 'GET' && pathname === '/v1/outputs') {
1342
1379
  if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
1343
1380
  return handleOutputsGet(req, opts.outputs);
@@ -1371,6 +1408,20 @@ export async function routeRequest(
1371
1408
  // Templates POST handled here too (PR κ).
1372
1409
  // Config / secrets PUT/POST/DELETE here too (PR μ).
1373
1410
  if (method !== 'GET') {
1411
+ if (method === 'POST' && pathname === SKILL_EXEC_PATH) {
1412
+ if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
1413
+ return (opts.skillExec ?? defaultSkillExecJobs()).post(req);
1414
+ }
1415
+ if (method === 'POST' && pathname === CARD_FOLLOWUP_PATH) {
1416
+ if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
1417
+ return (opts.cardFollowup ?? defaultCardFollowupJobs()).post(req);
1418
+ }
1419
+ if (method === 'POST' && /^\/v1\/decisions\/[^/]+\/decide$/.test(pathname)) {
1420
+ return handleDecisions(req, {
1421
+ authorize: request => !!opts.metaApi && checkAuth(request, opts.metaApi),
1422
+ ...opts.decisions,
1423
+ });
1424
+ }
1374
1425
  if (method === 'POST' && pathname === '/v1/plugins/install') {
1375
1426
  if (!opts.metaApi) return jsonResponse({ error: 'unauthorized' }, 401);
1376
1427
  return handlePluginsInstall(req, opts.metaApi, opts.pluginStateRoot);
@@ -2832,7 +2883,19 @@ export async function routeRequest(
2832
2883
  }
2833
2884
  if (pathname === '/v1/context/now') {
2834
2885
  if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
2835
- return jsonResponse(contextNow({ ...(url.searchParams.has('topic') ? { topic: url.searchParams.get('topic') ?? '' } : {}) }));
2886
+ const audiences = url.searchParams.getAll('audience');
2887
+ if (audiences.length > 1) return jsonResponse({ error: 'invalid_audience' }, 400);
2888
+ const requested = audiences[0];
2889
+ if (requested !== undefined && requested !== 'operator' && requested !== 'user' && requested !== 'public-demo') {
2890
+ return jsonResponse({ error: 'invalid_audience' }, 400);
2891
+ }
2892
+ const forced = getUserConfig().nexus?.demoMode === true;
2893
+ const audience: ContextNowAudience = forced ? 'public-demo' : requested ?? 'operator';
2894
+ const answer = contextNow({ audience, ...(url.searchParams.has('topic') ? { topic: url.searchParams.get('topic') ?? '' } : {}) }, opts.contextNowDeps);
2895
+ debug.log('context.now', 'served', { audience, hidden: answer.hiddenCount ?? 0, forced });
2896
+ return jsonResponse(url.searchParams.get('format') === 'voice'
2897
+ ? { text: renderVoiceNow(answer, audience), audience, hiddenCount: answer.hiddenCount ?? 0 }
2898
+ : { ...answer, audience, hiddenCount: answer.hiddenCount ?? 0 });
2836
2899
  }
2837
2900
  // §6.3 (2026-05-09) — context URL fetch (showroom).
2838
2901
  // POST /v1/context/fetch-url