elanous 0.2.17 → 0.2.19

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 (491) 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/J5H7BvqgSI8DUjtRRcA_S/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1157-cba0e8553d35a980.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/{726.83532d69452ee24b.js → 1470.98a6ab61ccb4131c.js} +1 -1
  7. package/apps/pwa/out/_next/static/chunks/171-23a877ed11720de9.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2336-e830c777eb6bce04.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/{9247-269e4464cc4b491c.js → 2375-e061bb24cd2b5cde.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/{211-9c9090c59e2c5907.js → 2977-e38442e86289f1d3.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/{f6b7c9ba.64a895b8b2fedbdc.js → 2ea96958.e8c7e112de50b067.js} +1 -1
  15. package/apps/pwa/out/_next/static/chunks/3094.67339318c7f72a3a.js +2 -0
  16. package/apps/pwa/out/_next/static/chunks/338-ceb956612dcfaa86.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/3395-835e4ceaa6335ba4.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +5 -0
  19. package/apps/pwa/out/_next/static/chunks/3441-be82e3595aef3a20.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/3689-89905039bf3e7257.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/3822-6f268bc26c6a84ac.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/{1268.0840ff91e083fcbd.js → 4323.c5dfb6f503304775.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/4457-d7df81c90f9d0268.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/4551-77dadae76f6cbace.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/{6110.1297d4093fdfbb0d.js → 4847.56f5bc454fbcc827.js} +2 -2
  28. package/apps/pwa/out/_next/static/chunks/4978.6fd93b974441e87f.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/5012-f746f6549b869f91.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/{7327-2d69fb321ba73389.js → 6062-73e68343a9814f6a.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/62-5bfed1e23790b745.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/6320-9bc8561fde416e07.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/649-ca96d1c5d09e02b8.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/6726-85a7db59b288c53f.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/6735-33267c4630d0d963.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/7633-141682015873beb6.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/{935.4ddaa99fc1d1126c.js → 7647.2fcd30d894d5ee30.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/{f6231284-45726550aba08fca.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/8067-143791231c99bd02.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/8430-a10cfba8ef282d4e.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/8870-9614a213b990b10b.js +25 -0
  49. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/9454-12c2f30dea62291c.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/976-4a9b4ebdca7c739a.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/9989-15cdf568bafa9fc8.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/ae787e4c-e5b1ef00b2dbceca.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/approvals/page-deef5ee191830f14.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-b193da485f9a14b2.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/board/page-b6e1a79df0e214f4.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/botlab/page-e84df4d907f703c7.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/bots/page-a5fb9a5db89ae4ea.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/ceo/page-1499f481d7fc52d6.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/chat/page-c7cf753aeb648790.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/consult/page-4118469e514e10fd.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/control/page-e968f7630654332a.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/decisions/page-505915052f947022.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/design-check/page-c2f962fb4a3b06c6.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/editor/page-7b5ed0bd8a589ec4.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/exec/page-cf8193250dd42dd4.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/field/page-cf3f5dfa35dea8e9.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/inside/page-5b29d9b06ceadade.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/intake/page-a15e7fcc283e9ca7.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/layout-d2915e7b70ff3d0e.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/live/page-b70cd005d9169e25.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/loops/page-2595f0eba29f57e1.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/market/page-c776322454156e36.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-77bbd17f4435aa10.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/morning/page-049689e611ecc936.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/not-found-85954a4fb09c44eb.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/observatory/page-e773e552e1068ee6.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5633c4bfa7684b4a.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d829b92bd01b4994.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-061c317fc771becf.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a45ece8865ed4b6.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/page-0be990618c0d56bd.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/reflection/page-bfea4865dc244b73.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-0d6ba8e337741484.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/sessions/page-76a88b55d816482e.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-bba1542df59f61ce.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/app/settings/page-4185c95689bb372e.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b699e1a8d5d34b8b.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/setup/page-c225e338c31bf274.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/app/share/page-449353f70507ca1f.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/app/showroom/page-90b889714102476d.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/app/tasks/page-f1ee7fbeb7f96ddc.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/app/term/page-d59a95e1434dfaa0.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/app/today/page-71491a23414d1a47.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/app/trace/page-de7112bdb65fa4fe.js +1 -0
  99. package/apps/pwa/out/_next/static/chunks/app/vault/page-feb4d0d88079660d.js +1 -0
  100. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f17d887b40dd6b1d.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/app/workflows/page-8da77be2192bcd5d.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/app/workspace/page-72c3b9a5e9582b29.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3afe4c8d0c48c39.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
  106. package/apps/pwa/out/_next/static/chunks/main-app-b9308f2d8a931531.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/webpack-6f35f9d892fa630b.js +1 -0
  110. package/apps/pwa/out/_next/static/css/{e99e3cff94ced78a.css → 599369d853c61df7.css} +1 -1
  111. package/apps/pwa/out/_next/static/css/85f53c6f35d11382.css +1 -0
  112. package/apps/pwa/out/_next/static/css/d3427996da11e480.css +1 -0
  113. package/apps/pwa/out/approvals/index.html +1 -1
  114. package/apps/pwa/out/approvals/index.txt +23 -23
  115. package/apps/pwa/out/autopilot/index.html +1 -1
  116. package/apps/pwa/out/autopilot/index.txt +23 -23
  117. package/apps/pwa/out/board/index.html +1 -1
  118. package/apps/pwa/out/board/index.txt +23 -23
  119. package/apps/pwa/out/botlab/index.html +1 -1
  120. package/apps/pwa/out/botlab/index.txt +22 -22
  121. package/apps/pwa/out/bots/index.html +1 -1
  122. package/apps/pwa/out/bots/index.txt +22 -22
  123. package/apps/pwa/out/ceo/index.html +1 -1
  124. package/apps/pwa/out/ceo/index.txt +23 -23
  125. package/apps/pwa/out/chat/index.html +1 -1
  126. package/apps/pwa/out/chat/index.txt +22 -22
  127. package/apps/pwa/out/consult/index.html +1 -1
  128. package/apps/pwa/out/consult/index.txt +23 -23
  129. package/apps/pwa/out/control/index.html +1 -1
  130. package/apps/pwa/out/control/index.txt +23 -23
  131. package/apps/pwa/out/decisions/index.html +1 -0
  132. package/apps/pwa/out/decisions/index.txt +30 -0
  133. package/apps/pwa/out/design-check/index.html +1 -1
  134. package/apps/pwa/out/design-check/index.txt +23 -23
  135. package/apps/pwa/out/editor/index.html +1 -1
  136. package/apps/pwa/out/editor/index.txt +25 -24
  137. package/apps/pwa/out/exec/index.html +1 -1
  138. package/apps/pwa/out/exec/index.txt +23 -23
  139. package/apps/pwa/out/field/index.html +1 -1
  140. package/apps/pwa/out/field/index.txt +23 -23
  141. package/apps/pwa/out/healthz +1 -1
  142. package/apps/pwa/out/index.html +1 -1
  143. package/apps/pwa/out/index.txt +22 -22
  144. package/apps/pwa/out/inside/index.html +1 -1
  145. package/apps/pwa/out/inside/index.txt +24 -23
  146. package/apps/pwa/out/intake/index.html +1 -1
  147. package/apps/pwa/out/intake/index.txt +23 -23
  148. package/apps/pwa/out/live/index.html +1 -1
  149. package/apps/pwa/out/live/index.txt +23 -23
  150. package/apps/pwa/out/loops/index.html +1 -1
  151. package/apps/pwa/out/loops/index.txt +28 -27
  152. package/apps/pwa/out/market/index.html +1 -1
  153. package/apps/pwa/out/market/index.txt +22 -22
  154. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  155. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  156. package/apps/pwa/out/morning/index.html +1 -1
  157. package/apps/pwa/out/morning/index.txt +23 -23
  158. package/apps/pwa/out/observatory/index.html +1 -1
  159. package/apps/pwa/out/observatory/index.txt +23 -23
  160. package/apps/pwa/out/ops/checklist/index.html +1 -1
  161. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  162. package/apps/pwa/out/ops/release/index.html +1 -1
  163. package/apps/pwa/out/ops/release/index.txt +22 -22
  164. package/apps/pwa/out/ops/seats/index.html +1 -1
  165. package/apps/pwa/out/ops/seats/index.txt +22 -22
  166. package/apps/pwa/out/outputs/index.html +1 -1
  167. package/apps/pwa/out/outputs/index.txt +22 -22
  168. package/apps/pwa/out/reflection/index.html +1 -1
  169. package/apps/pwa/out/reflection/index.txt +23 -23
  170. package/apps/pwa/out/scheduler/index.html +1 -1
  171. package/apps/pwa/out/scheduler/index.txt +23 -23
  172. package/apps/pwa/out/sessions/index.html +1 -1
  173. package/apps/pwa/out/sessions/index.txt +22 -22
  174. package/apps/pwa/out/settings/devices/index.html +1 -1
  175. package/apps/pwa/out/settings/devices/index.txt +23 -23
  176. package/apps/pwa/out/settings/index.html +1 -1
  177. package/apps/pwa/out/settings/index.txt +23 -23
  178. package/apps/pwa/out/setup/done/index.html +1 -1
  179. package/apps/pwa/out/setup/done/index.txt +23 -23
  180. package/apps/pwa/out/setup/index.html +1 -1
  181. package/apps/pwa/out/setup/index.txt +23 -23
  182. package/apps/pwa/out/share/index.html +1 -1
  183. package/apps/pwa/out/share/index.txt +23 -23
  184. package/apps/pwa/out/showroom/index.html +1 -1
  185. package/apps/pwa/out/showroom/index.txt +22 -22
  186. package/apps/pwa/out/tasks/index.html +1 -1
  187. package/apps/pwa/out/tasks/index.txt +23 -23
  188. package/apps/pwa/out/term/index.html +1 -1
  189. package/apps/pwa/out/term/index.txt +23 -23
  190. package/apps/pwa/out/today/index.html +1 -1
  191. package/apps/pwa/out/today/index.txt +23 -23
  192. package/apps/pwa/out/trace/index.html +1 -1
  193. package/apps/pwa/out/trace/index.txt +23 -23
  194. package/apps/pwa/out/vault/index.html +1 -1
  195. package/apps/pwa/out/vault/index.txt +23 -23
  196. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  197. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  198. package/apps/pwa/out/workflows/index.html +1 -1
  199. package/apps/pwa/out/workflows/index.txt +23 -23
  200. package/apps/pwa/out/workspace/index.html +1 -1
  201. package/apps/pwa/out/workspace/index.txt +23 -23
  202. package/apps/pwa/out/worktrees/index.html +1 -1
  203. package/apps/pwa/out/worktrees/index.txt +23 -23
  204. package/graphs/ops/guardian.yaml +26 -0
  205. package/graphs/ops/recipes.yaml +3 -0
  206. package/package.json +2 -1
  207. package/samples/workflows/templates/approval-gated-deploy.yaml +30 -0
  208. package/samples/workflows/templates/chat-driven-research.yaml +35 -0
  209. package/samples/workflows/templates/discord-listen-and-respond.yaml +29 -0
  210. package/samples/workflows/templates/http-poll-then-alert.yaml +30 -0
  211. package/samples/workflows/templates/manual-quickstart.yaml +21 -0
  212. package/samples/workflows/templates/scheduled-daily-digest.yaml +30 -0
  213. package/samples/workflows/templates/webhook-deploy-hook.yaml +27 -0
  214. package/scripts/ci-public-export-import-gate.ts +7 -2
  215. package/scripts/ci-typecheck-changed.ts +21 -6
  216. package/scripts/coord-channel-watch.sh +2 -2
  217. package/scripts/discord-seat-probe.ts +6 -3
  218. package/scripts/docs/generate-cli-anatomy.ts +162 -0
  219. package/scripts/goals/authoring-metrics.ts +73 -0
  220. package/scripts/landing-heal/must-fix-match.ts +7 -7
  221. package/scripts/landing-heal/run-mission.ts +3 -2
  222. package/scripts/lib/pch-regress-map.ts +23 -0
  223. package/scripts/morning-report.ts +18 -4
  224. package/scripts/pch-regress.ts +88 -0
  225. package/scripts/public-export-test-run.ts +37 -2
  226. package/scripts/release-loop/checklist-gate-node.ts +13 -11
  227. package/scripts/release-loop/cut-branch.ts +2 -1
  228. package/scripts/release-loop/cutoff.ts +4 -2
  229. package/scripts/release-loop/docs-land-node.ts +8 -1
  230. package/scripts/release-loop/docs-node.ts +1 -1
  231. package/scripts/release-loop/gate-diff.ts +18 -0
  232. package/scripts/release-loop/gate-node.ts +436 -59
  233. package/scripts/release-loop/gate-partial.ts +106 -0
  234. package/scripts/release-loop/gate-timing.ts +120 -0
  235. package/scripts/release-loop/node-verdict.ts +2 -1
  236. package/scripts/release-loop/npm-publish-node.ts +11 -7
  237. package/scripts/release-loop/ops-upgrade-node.ts +8 -1
  238. package/scripts/release-loop/preflight.ts +246 -0
  239. package/scripts/release-loop/publish-node.ts +8 -1
  240. package/scripts/release-loop/release-readiness.ts +11 -6
  241. package/scripts/release-loop/release-version.ts +48 -0
  242. package/scripts/release-loop/resume-release.ts +174 -0
  243. package/scripts/release-loop/shard-plan.ts +65 -0
  244. package/scripts/release-loop/unattended-release.ts +45 -7
  245. package/scripts/release-loop/version-node.ts +145 -40
  246. package/scripts/rhythm/daily.ts +25 -4
  247. package/scripts/skills/public-check.ts +62 -12
  248. package/scripts/test-deterministic.ts +51 -33
  249. package/scripts/test-diet/effectiveness.ts +62 -1
  250. package/scripts/test-diet/lib.ts +1 -1
  251. package/scripts/test-diet/node.ts +8 -1
  252. package/scripts/video/scene-table.ts +159 -0
  253. package/src/acp/core-turn-bridge.ts +39 -2
  254. package/src/agent-mission/review-loop.ts +4 -3
  255. package/src/agent-substrate/pr-comment-meta.ts +20 -0
  256. package/src/autopilot/build/llm-conflict-merge.ts +28 -8
  257. package/src/away/presence.ts +40 -0
  258. package/src/away/release-watch.ts +255 -0
  259. package/src/briefing/brief-items.ts +86 -17
  260. package/src/chat/index.ts +2 -1
  261. package/src/chat/short-question.ts +3 -40
  262. package/src/cli/autopilot-cli.ts +30 -11
  263. package/src/cli/away-cli.ts +68 -0
  264. package/src/cli/brief-cli.ts +36 -2
  265. package/src/cli/freeze-cli.ts +3 -2
  266. package/src/cli/guardian-cli.ts +49 -0
  267. package/src/cli/intake-cli.ts +85 -7
  268. package/src/cli/log-event-names.ts +1 -0
  269. package/src/cli/notify-cli.ts +75 -0
  270. package/src/cli/pod-cli.ts +5 -1
  271. package/src/cli/provider-cli.ts +12 -2
  272. package/src/cli/pty-drive-cli.ts +14 -3
  273. package/src/cli/release-cli.ts +128 -19
  274. package/src/cli/setup-cli.ts +31 -1
  275. package/src/cli/tasks-cli.ts +403 -4
  276. package/src/context-bus/condense.ts +19 -3
  277. package/src/context-bus/context-day.ts +77 -24
  278. package/src/context-bus/context-first.ts +33 -0
  279. package/src/context-bus/context-now-public.ts +1 -1
  280. package/src/context-bus/context-now-surfaces.ts +16 -6
  281. package/src/context-bus/context-now.ts +97 -3
  282. package/src/core-turn/short-question.ts +100 -0
  283. package/src/dashboard/dashboard-status-lines.ts +24 -4
  284. package/src/dashboard/index.ts +50 -5
  285. package/src/dashboard/input/slash-executor.ts +6 -2
  286. package/src/dashboard/slash-runtime/dashboard-handlers.ts +63 -36
  287. package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
  288. package/src/dashboard/slash-runtime/help-from-registry.ts +21 -8
  289. package/src/dashboard/slash-runtime/help-groups.ts +44 -0
  290. package/src/dashboard/slash-runtime/model-choices.ts +125 -0
  291. package/src/discord-self-message.ts +20 -2
  292. package/src/discord-slash-wire.ts +1 -0
  293. package/src/domains/guardian-tick.ts +138 -0
  294. package/src/domains/outbound-alert.ts +244 -40
  295. package/src/domains/outbound-log-probe.ts +16 -1
  296. package/src/domains/release-tool.ts +2 -2
  297. package/src/domains/schedule-registry.ts +23 -0
  298. package/src/domains/telegram-kind-route.ts +39 -0
  299. package/src/domains/watch-default-scheduler.ts +95 -0
  300. package/src/esc-abort-gate.ts +44 -0
  301. package/src/execution-loop/gates.ts +11 -2
  302. package/src/flow-loop/split.ts +8 -4
  303. package/src/git-fs/worktree.ts +23 -3
  304. package/src/graph-runner/runner.ts +25 -5
  305. package/src/harness/harness-cli-command.ts +12 -2
  306. package/src/harness/harness-queue.ts +388 -33
  307. package/src/harness/harness-salvage-cli.ts +78 -0
  308. package/src/harness/harness-worktree-add.ts +68 -0
  309. package/src/harness/helper-scan.ts +10 -3
  310. package/src/harness/ledger-write-guard.ts +6 -16
  311. package/src/hq/hq.ts +6 -1
  312. package/src/hq/seats.ts +369 -0
  313. package/src/index.ts +185 -18
  314. package/src/instance/test-write-guard-spy-first.fixture.ts +13 -0
  315. package/src/instance/test-write-guard.ts +200 -0
  316. package/src/intake-plane/adopt-gate.ts +49 -0
  317. package/src/intake-plane/check.ts +3 -0
  318. package/src/intake-plane/collect-github.ts +57 -25
  319. package/src/intake-plane/digest.ts +39 -15
  320. package/src/intake-plane/discord-seat-work.ts +8 -2
  321. package/src/intake-plane/harness-target.ts +4 -3
  322. package/src/intake-plane/intake-sources.ts +1 -0
  323. package/src/intake-plane/items.ts +1 -0
  324. package/src/intake-plane/requirement-funnel.ts +316 -0
  325. package/src/knowledge/know-find.ts +10 -1
  326. package/src/loops/budget.ts +36 -0
  327. package/src/loops/orchestrator/degradation.ts +57 -0
  328. package/src/loops/orchestrator/finish-rate.ts +106 -4
  329. package/src/loops/orchestrator/grid-place.ts +11 -0
  330. package/src/loops/orchestrator/tick.ts +450 -54
  331. package/src/maturity/feature-maturity.ts +8 -5
  332. package/src/maturity/tui-slash-maturity.ts +1 -0
  333. package/src/mss/logging/log-store.ts +3 -0
  334. package/src/nexus/api/decisions-route.ts +11 -1
  335. package/src/nexus/api/http-server.ts +16 -5
  336. package/src/nexus/api/loop-edges.ts +352 -23
  337. package/src/nexus/api/ops-api.ts +96 -28
  338. package/src/nexus/api/ops-seats-sources.ts +2 -2
  339. package/src/nexus/api/workflow-templates.ts +3 -0
  340. package/src/nexus/index.ts +7 -0
  341. package/src/oauth/codex.ts +38 -0
  342. package/src/onboarding/first-chat-gate.ts +29 -0
  343. package/src/release-loop/checklist.ts +106 -6
  344. package/src/release-loop/feature-store.ts +64 -16
  345. package/src/release-loop/landing-freeze.ts +6 -4
  346. package/src/release-loop/light-rc.ts +173 -0
  347. package/src/release-loop/placement.ts +64 -20
  348. package/src/release-loop/rubric.ts +136 -0
  349. package/src/seat-dispatch/seat-ask.ts +79 -16
  350. package/src/seat-loop/seat-loop.ts +12 -6
  351. package/src/self-dev/ask-launch-flow.ts +24 -3
  352. package/src/self-dev/ask-path-candidates.ts +68 -0
  353. package/src/self-dev/dev-cli.ts +36 -0
  354. package/src/self-dev/dev-pipeline.ts +4 -0
  355. package/src/self-dev/draft-triage-rules.ts +1 -1
  356. package/src/self-dev/orchestrate-cli.ts +22 -0
  357. package/src/self-dev/orchestrate.ts +1 -0
  358. package/src/self-dev/pr-preview.ts +179 -0
  359. package/src/self-dev/round-pr-supersede.ts +244 -0
  360. package/src/self-dev/run-supervisor.ts +14 -0
  361. package/src/self-dev/shard-run.ts +23 -7
  362. package/src/self-implement/follow-up-goals.ts +2 -2
  363. package/src/self-implement/gate-cli.ts +29 -8
  364. package/src/self-implement/gate-remote.ts +427 -0
  365. package/src/self-implement/goal-author-clarification.ts +88 -13
  366. package/src/self-implement/goal-author.ts +8 -3
  367. package/src/self-implement/goal-clarification-escalation.ts +2 -0
  368. package/src/self-implement/l8-merge-queue.ts +29 -5
  369. package/src/self-implement/orchestrator.ts +61 -13
  370. package/src/self-implement/prior-draft-findings.ts +4 -3
  371. package/src/self-implement/salvage-branches.ts +254 -0
  372. package/src/skills/skill-feedback.ts +76 -0
  373. package/src/task-agent/actions.ts +101 -7
  374. package/src/task-agent/board.ts +132 -0
  375. package/src/task-agent/card-evidence.ts +203 -0
  376. package/src/task-agent/completion-evidence.ts +126 -0
  377. package/src/task-agent/judge.ts +49 -5
  378. package/src/task-agent/live-moves.ts +176 -0
  379. package/src/task-agent/mission.ts +449 -0
  380. package/src/task-agent/shadow.ts +258 -0
  381. package/src/task-agent/task-hand.ts +337 -0
  382. package/src/task-orchestrator/surfaces/pod-command-job.ts +64 -4
  383. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +10 -0
  384. package/src/task-orchestrator/surfaces/pod-lease.ts +142 -13
  385. package/src/task-orchestrator/surfaces/pod-pool.ts +13 -5
  386. package/src/task-orchestrator/surfaces/self-implement-pod.ts +88 -20
  387. package/src/telegram-away-command.ts +38 -0
  388. package/src/telegram-commands.ts +11 -0
  389. package/src/telegram-loops-command.ts +1 -1
  390. package/src/telegram-report-log-probe.ts +21 -0
  391. package/src/telegram-report.ts +27 -3
  392. package/src/telegram.ts +5 -20
  393. package/src/tui-client/dashboard-session.ts +1 -0
  394. package/src/user-config.ts +196 -4
  395. package/src/version/packed-revision.json +1 -1
  396. package/apps/pwa/out/_next/static/chunks/1307-aac7332576c4d6fc.js +0 -1
  397. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +0 -1
  398. package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
  399. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
  400. package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
  401. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +0 -1
  402. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
  403. package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +0 -1
  404. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
  405. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +0 -1
  406. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +0 -1
  407. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +0 -1
  408. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +0 -1
  409. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
  411. package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/4614-21403aac730d4586.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +0 -1
  416. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
  419. package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +0 -2
  420. package/apps/pwa/out/_next/static/chunks/6877-fe94e76a5841ee9b.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +0 -1
  425. package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/8401-4ce1c42c8d89903f.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
  429. package/apps/pwa/out/_next/static/chunks/8949-ecb4128a0858b911.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/899-f41be85e8a680651.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/app/approvals/page-3143b07a817d1da9.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-17ba086ef82d339b.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/app/board/page-7eb661ff23af7da2.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/app/botlab/page-6b8444914da74ca6.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/app/bots/page-6e5c3a867557298b.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/app/ceo/page-7110f732f45bf944.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/app/chat/page-9a41d65355fc25b9.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/app/consult/page-5e77cd1802567c89.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/app/control/page-41afec0b255ed2bb.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/app/design-check/page-4187666132fc98c4.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/app/editor/page-09328d499162bae4.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/app/exec/page-29ff3a8f84f03b6a.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/app/field/page-80ec7401bc83f511.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/app/inside/page-ab93b240f687d69a.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/app/intake/page-fc7d86467291ae38.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/app/layout-de43a186e5ba5683.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/app/live/page-8b08cafad27759c9.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/app/loops/page-0e2d6985fb42cd2f.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/app/market/page-13e4108999f58d13.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e8296ee284aa1293.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/app/morning/page-043a9111e74f6b24.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/app/not-found-23a4601ceb7f452e.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/app/observatory/page-1ed1324b1dc36754.js +0 -1
  459. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-10d958605a77b3bd.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-166745e51ac0c7b0.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a04260b8ccc968d4.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/app/outputs/page-7a1d85e121065cc8.js +0 -1
  463. package/apps/pwa/out/_next/static/chunks/app/page-1f50e3d4e13435f4.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/app/reflection/page-12916918988ac97e.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d8a3347a9d0c3f4a.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/app/sessions/page-a9117d2cd3093d36.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-5a64cb5208c6a8fd.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/app/settings/page-1407ec83ea64c1f9.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-1d824510df025d18.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/app/setup/page-6e878ebbe1737d00.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/app/share/page-7f49fe645d5132f7.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/app/showroom/page-81a4e7f8dbfe74cb.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/app/tasks/page-27d71f131acd9320.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/app/term/page-3ab59d19913b200e.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/app/today/page-bf85b2f5bf077925.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/app/trace/page-424659bacfb8cdd7.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/app/vault/page-ab32a8f522a60371.js +0 -1
  479. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-fc2df261f8a2c7df.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/app/workflows/page-09461366af82ef13.js +0 -1
  481. package/apps/pwa/out/_next/static/chunks/app/workspace/page-5d4fef9ccd7d406b.js +0 -1
  482. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ea90a8ac73ce2749.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +0 -1
  485. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +0 -1
  487. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +0 -1
  489. package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +0 -1
  490. package/apps/pwa/out/_next/static/yRA5ur6DcPl0YHSRheH2O/_buildManifest.js +0 -1
  491. /package/apps/pwa/out/_next/static/{yRA5ur6DcPl0YHSRheH2O → J5H7BvqgSI8DUjtRRcA_S}/_ssgManifest.js +0 -0
@@ -46,7 +46,9 @@ export const FEATURE_MATURITY = {
46
46
  '/ops/seats': { pwa: 'ops' },
47
47
  '/ops/checklist': { pwa: 'ops' },
48
48
  '/ceo': { pwa: 'ops' },
49
+ '/decisions': { pwa: 'ops' },
49
50
  '/dashboard': { pwa: 'ops' },
51
+ '/pitch': { pwa: 'ops' }, // PITCH-ROOM — 투자자 피치 방(운영자 전용 · 공개 내보내기 제외)
50
52
  '/bots': { pwa: 'ops' },
51
53
  '/botlab': { pwa: 'ops' },
52
54
  '/morning': { pwa: 'broken' },
@@ -56,7 +58,7 @@ export const FEATURE_MATURITY = {
56
58
  },
57
59
  // Canonical CLI command names only; aliases inherit their command's grade.
58
60
  cliRoot: {
59
- pty: 'beta', pr: 'beta', repo: 'beta', role: 'beta', machine: 'beta', 'agent-env': 'beta',
61
+ preview: 'beta', pty: 'beta', pr: 'beta', repo: 'beta', role: 'beta', machine: 'beta', 'agent-env': 'beta',
60
62
  where: 'stable', shadow: 'beta', llm: 'beta', storage: 'beta', leader: 'beta',
61
63
  pod: 'beta', browser: 'beta', questions: 'beta', usage: 'stable', live: 'beta',
62
64
  research: 'beta', release: 'beta', know: 'beta', freeze: 'beta', flow: 'beta', seat: 'beta', hq: 'beta', steward: 'beta', coord: 'beta', context: 'beta', 'model-watch': 'beta', doctor: 'stable',
@@ -72,10 +74,10 @@ export const FEATURE_MATURITY = {
72
74
  loops: 'beta', buzz: 'beta', harness: 'stable', self: 'beta', factcheck: 'beta',
73
75
  provider: 'stable', 'provider:set': 'beta', 'provider:restore': 'beta',
74
76
  'provider:rotate': 'beta', 'provider:use': 'beta', 'status-bar': 'beta',
75
- schedule: 'beta', intake: 'beta', logs: 'stable', ad: 'beta', docs: 'beta',
77
+ schedule: 'beta', guardian: 'beta', intake: 'beta', logs: 'stable', ad: 'beta', docs: 'beta',
76
78
  decisions: 'beta', directives: 'beta', claims: 'beta', lesson: 'beta', fleet: 'beta', ops: 'stable', outputs: 'beta', publish: 'beta',
77
79
  env: 'beta', 'agent-mission': 'beta', dev: 'beta', keys: 'beta', finance: 'beta',
78
- config: 'stable', tasks: 'stable', brief: 'beta', labels: 'beta', msg: 'beta', ask: 'stable',
80
+ config: 'stable', tasks: 'stable', brief: 'beta', notify: 'beta', away: 'beta', labels: 'beta', msg: 'beta', ask: 'stable',
79
81
  onboarding: 'beta', login: 'stable', session: 'beta', chat: 'stable', repl: 'stable',
80
82
  agent: 'stable', registry: 'beta', tier: 'stable', local: 'beta', scheduler: 'beta',
81
83
  attach: 'beta', wf: 'beta', nexus: 'stable', phone: 'beta', token: 'beta',
@@ -98,6 +100,7 @@ export const FEATURE_MATURITY = {
98
100
  'run-skill': 'stable',
99
101
  ad: 'beta',
100
102
  design: 'beta',
103
+ decide: 'beta',
101
104
  provider: 'stable',
102
105
  reasoning: 'stable',
103
106
  local: 'tool',
@@ -157,7 +160,7 @@ export const FEATURE_MATURITY = {
157
160
  ping: 'stable', provider: 'stable', sessions: 'stable', fork: 'stable', resume: 'stable',
158
161
  decisions: 'stable', work: 'stable', cancel: 'stable',
159
162
  skills: 'beta', skill: 'beta', digest: 'beta', intake: 'beta', ad: 'beta',
160
- taste: 'beta', missions: 'beta', attach: 'beta', detach: 'beta', now: 'beta', loops: 'beta', project: 'beta', wish: 'beta',
163
+ taste: 'beta', missions: 'beta', attach: 'beta', detach: 'beta', now: 'beta', loops: 'beta', release: 'beta', away: 'beta', project: 'beta', wish: 'beta',
161
164
  brain: 'tool', cc: 'tool', cdx: 'tool', gem: 'tool', local: 'tool',
162
165
  harness: 'ops',
163
166
  // Existing commands outside the CXO four-grade menu list are owner-only.
@@ -166,7 +169,7 @@ export const FEATURE_MATURITY = {
166
169
  },
167
170
  discordCommand: {
168
171
  status: 'stable', sessions: 'stable', new: 'stable', fork: 'stable',
169
- persona: 'beta', poll: 'beta', attach: 'beta', now: 'beta',
172
+ persona: 'beta', poll: 'beta', attach: 'beta', now: 'beta', loops: 'beta',
170
173
  brain: 'tool', cc: 'tool', cdx: 'tool', gem: 'tool',
171
174
  relay: 'ops', showroom: 'ops', bots: 'ops', bot: 'ops', screen: 'ops', chart: 'ops',
172
175
  routines: 'ops', botsay: 'ops', 'voice-join': 'ops', 'voice-leave': 'ops', 'voice-status': 'ops',
@@ -12,6 +12,7 @@ export function readTuiSlashAudience(raw: unknown = getUserConfig().raw?.tui): {
12
12
  }
13
13
 
14
14
  export function slashMaturity(name: string): Maturity | undefined {
15
+ // Aliases inherit the canonical grade (including /dec from /decide).
15
16
  const command = SLASH_COMMANDS.find((entry) => entry.name === name || entry.aliases?.includes(name));
16
17
  if (!command) return undefined;
17
18
  return FEATURE_MATURITY.tuiSlash[command.name as keyof typeof FEATURE_MATURITY.tuiSlash];
@@ -26,6 +26,7 @@ import type { LogSink } from './sink.js';
26
26
  import { setInstanceName, resolveInstanceName } from '../../instance-identity.js';
27
27
  import * as nestDepth from '../../agent/nest-depth.js';
28
28
  import { debug } from '../../debug/log.js';
29
+ import { assertNotTestWritingOps } from '../../instance/test-write-guard.js';
29
30
 
30
31
  /** 스토어 경로 — `ELANOUS_STATE_DIR` 존중(격리 테스트/`nexus run --test` 의
31
32
  * 기존 knob 그대로), 기본 `~/.elanous/logs/logs.db`. lazy 함수 — env 를
@@ -209,6 +210,8 @@ export class LogStore {
209
210
  this.db.run(`PRAGMA busy_timeout = ${LOG_STORE_READONLY_BUSY_TIMEOUT_MS}`);
210
211
  return;
211
212
  }
213
+ // TEST-PROD-LEAK: a test process never opens the ops logs.db for write, preload or not.
214
+ assertNotTestWritingOps(path, 'open logs.db for write');
212
215
  if (path !== ':memory:') mkdirSync(dirname(path), { recursive: true });
213
216
  const openedAt = Date.now();
214
217
  for (let attempt = 0; ; attempt++) {
@@ -1,5 +1,6 @@
1
1
  import { debug } from '../../debug/log.js';
2
2
  import { DecisionLedger, type DecisionEntry } from '../../decisions/decision-ledger.js';
3
+ import { isIrreversible } from '../../decisions/decision-cards.js';
3
4
  import { jsonResponse } from './json-response.js';
4
5
 
5
6
  export const DECISIONS_PATH = '/v1/decisions';
@@ -27,10 +28,19 @@ export async function handleDecisions(req: Request, deps: DecisionsRouteDeps = {
27
28
  id: entry.id,
28
29
  title: entry.title,
29
30
  situation: entry.scqa.s.slice(0, 200),
30
- options: entry.options.map(option => ({ id: option.key, label: option.label })),
31
+ options: entry.options.map(option => ({ id: option.key, label: option.label, consequence: option.consequence })),
31
32
  recommendation: entry.recommendation,
32
33
  raisedAt: entry.raisedAt,
33
34
  dueAt: entry.dueAt,
35
+ scqa: entry.scqa,
36
+ category: entry.category,
37
+ irreversible: isIrreversible(entry),
38
+ raisedBy: { agent: entry.raisedBy.agent, ...(entry.raisedBy.track ? { track: entry.raisedBy.track } : {}) },
39
+ alternative: entry.alternative,
40
+ dissent: entry.dissent,
41
+ crossCheck: entry.crossCheck,
42
+ crossCheckSkipped: entry.crossCheckSkipped,
43
+ pendingQuestion: entry.pendingQuestion,
34
44
  }));
35
45
  debug.log('decisions.api', 'listed', { count: decisions.length });
36
46
  return jsonResponse({ decisions });
@@ -413,6 +413,8 @@ import { handleSelfEvent } from './self-event.js';
413
413
  /** 매매 «실행»은 애드온이다(대표 09-20 결정 ③ · 별도 상용 저장소 · release/trading-export.yaml) — 공개 코어엔 없다.
414
414
  * 있으면 그 경로를 켜고, 없으면 501. 경로를 변수로 둬 공개본 타입 검사가 «모듈 없음»으로 막히지 않게 한다. */
415
415
  const TRADE_PROPOSAL_MODULE: string = './trade-proposal.js';
416
+ /** PITCH-ROOM — 투자자 피치 방(덱·티저)은 비공개다(release/public-export.yaml 이 pitch-api 를 뺀다). 공개본엔 모듈이 없어 404. */
417
+ const PITCH_MODULE: string = './pitch-api.js';
416
418
  import {
417
419
  handleShowroomLayoutDelete,
418
420
  handleShowroomLayoutGet,
@@ -926,6 +928,16 @@ export async function routeRequest(
926
928
  if (pathname === '/v1/ops' || pathname.startsWith('/v1/ops/')) {
927
929
  return handleOpsApi(req, opts.metaApi);
928
930
  }
931
+ // PITCH-ROOM — same operatorSignal gate, inside the (private-only) pitch module.
932
+ if (pathname.startsWith('/v1/pitch/')) {
933
+ let pitch: { handlePitchApi(req: Request, metaApi: typeof opts.metaApi): Response };
934
+ try {
935
+ pitch = await import(PITCH_MODULE) as typeof pitch;
936
+ } catch {
937
+ return jsonResponse({ error: 'not-found' }, 404);
938
+ }
939
+ return pitch.handlePitchApi(req, opts.metaApi);
940
+ }
929
941
 
930
942
  // Default-deny for every `/v1/` path that is not on PUBLIC_ROUTES.
931
943
  // Reuses checkAuth (same-origin exemption + constant-time bearer).
@@ -1299,12 +1311,11 @@ export async function routeRequest(
1299
1311
  return handleDevices(req, opts.devices);
1300
1312
  }
1301
1313
 
1314
+ // Same owner auth as the neighbouring GETs (short-lived owner tokens · same-origin PWA); #23932 compared the admin
1315
+ // bearer only, so a PWA on a short-lived token got 401 and the loop map stayed empty.
1302
1316
  if (pathname === '/v1/loops/edges' && method === 'GET') {
1303
- if (!opts.metaApi?.bearerToken) return jsonResponse({ error: 'unauthorized' }, 401);
1304
- const offered = req.headers.get('authorization');
1305
- if (!offered?.startsWith('Bearer ') || !compareTokenConstTime(offered.slice('Bearer '.length).trim(), opts.metaApi.bearerToken)) {
1306
- return jsonResponse({ error: 'unauthorized' }, 401);
1307
- }
1317
+ if (!opts.metaApi) return jsonResponse({ error: 'meta-api-runtime-not-wired' }, 503);
1318
+ if (!checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
1308
1319
  return handleLoopEdgesGet(req);
1309
1320
  }
1310
1321
 
@@ -1,5 +1,7 @@
1
- import { existsSync, readdirSync, statSync } from 'node:fs';
1
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
+ import { effectiveInstanceRoot } from '../../instance/resolve.js';
4
+ import { listSeatRequests, type SeatRequestRow } from '../../seat-dispatch/seat-request-ledger.js';
3
5
  import { listCoordEvents } from '../../context-bus/coord-events.js';
4
6
  import { CardStore, cardIndexPath, type TaskCard } from '../../task-cards/card-store.js';
5
7
  import { loadRunLedger, resolveFederatedRunLedgerDirectories, type RunLedgerEntry } from '../../self-implement/run-ledger.js';
@@ -7,23 +9,78 @@ import { listLoops } from '../../loops/registry.js';
7
9
  import { listSubSeatCharters } from './sub-seat-charters.js';
8
10
  import { SEATS } from './ops-seats.js';
9
11
  import { jsonResponse } from './json-response.js';
12
+ import { readTaskAgentState, type TaskCard as TaskAgentCard } from '../../task-agent/task-hand.js';
13
+ import { LogStore, logsDbPath } from '../../mss/logging/log-store.js';
14
+
15
+ /** `move` 칸에 실을 수 있는 값(allowlist) — 판단부 수 종류만, 문면은 싣지 않는다. */
16
+ export const LOOP_EDGE_MOVES = ['green-proposal'] as const;
10
17
 
11
18
  export interface LoopEdge {
12
19
  at: string;
13
- kind: 'request' | 'decision' | 'report' | 'card' | 'run';
20
+ kind: 'request' | 'decision' | 'report' | 'card' | 'run' | 'hand' | 'launch' | 'move';
14
21
  from: string;
15
22
  to: string;
16
23
  ref: string;
24
+ /** 체크리스트 칸 id(ID 정규식) — hand 간선만. */
25
+ cell?: string;
26
+ /** LOOP_EDGE_MOVES 중 하나 — move 간선만. */
27
+ move?: typeof LOOP_EDGE_MOVES[number];
28
+ /** hand 간선만 — 원장 행 mode, 없으면 key 접두(`shadow:orch:` = shadow · `orch:` = live). 못 정하면 생략. */
29
+ mode?: 'shadow' | 'live';
30
+ /** hand 간선만 — 그 행을 쓴 오케스트레이터 틱 runId(ID 정규식). 없으면 생략. */
31
+ tick?: string;
32
+ /** live claimed 뒤 handed·released 없이 N분 지남 = 발사 실패(끊긴 넘김). ref = 오케스트레이터 카드 id. */
33
+ broken?: true;
34
+ }
35
+
36
+ /** 노드 접두 — 조율 루프는 레지스트리 행 `loop:orchestrator` 그대로 쓴다. `queue:` 는 대기열 대체 간선 자리(조각 A 밖). */
37
+ export const TASK_AGENT_NODE = 'agent:task-agent';
38
+ export const ORCHESTRATOR_NODE = 'loop:orchestrator';
39
+
40
+ /** 한 런 원장에서 투영한 사실 — 문면은 feature(연결 비교 전용 · 응답에 싣지 않는다)뿐. */
41
+ export interface RunFact {
42
+ runId: string;
43
+ startAt?: string;
44
+ seat?: string;
45
+ feature?: string;
46
+ goalId?: string;
47
+ mergedAt?: string;
48
+ prs: Array<{ at: string; number: number }>;
49
+ children: string[];
50
+ }
51
+
52
+ /** handed.jsonl 행 투영 — status 가 없으면 handed 로 읽는다(조각 A 주입 호환). claimed·released 는 끊긴 간선 판정에만 쓴다. */
53
+ export interface HandedRow {
54
+ at: string;
55
+ cardId?: string;
56
+ cell?: string;
57
+ status?: 'claimed' | 'handed' | 'released';
58
+ key?: string;
59
+ mode?: 'shadow' | 'live';
60
+ runId?: string;
17
61
  }
62
+ export interface PodFinish { at: string; childRunId: string; prNumber: number }
18
63
 
19
64
  export interface LoopEdgesDeps {
20
65
  listCoord?: typeof listCoordEvents;
21
66
  cardRoot?: string;
22
67
  ledgerDir?: string;
23
68
  listCards?: () => TaskCard[];
24
- listRunStarts?: (since: string) => Array<{ runId: string; at: string; seat: string }>;
69
+ listRunStarts?: (since: string) => Array<{ runId: string; at: string; seat: string; mergedAt?: string }>;
70
+ listSeatRequests?: () => SeatRequestRow[];
71
+ listOrchestratorLaunches?: () => Array<{ key: string; runId: string }>;
25
72
  listLoopOwners?: () => Array<{ id: string; owner?: string | null }>;
26
73
  seatIds?: () => string[];
74
+ /** 기본 = `<root>/loop/orchestrator/handed.jsonl` · root = cardRoot ?? effectiveInstanceRoot(). */
75
+ listHanded?: () => HandedRow[];
76
+ /** live claimed 가 handed·released 없이 이만큼 지나면 끊긴 hand 간선(기본 10분). */
77
+ handBrokenAfterMs?: number;
78
+ /** 기본 = `<root>/task-agent-actions.json` 의 tasks(readTaskAgentState). */
79
+ listTaskCards?: () => TaskAgentCard[];
80
+ /** 기본 = ledgerDir(또는 연합 원장)에서 투영. listRunStarts 를 주입하고 이것을 안 주면 [] 다. */
81
+ listRunFacts?: () => RunFact[];
82
+ /** 대체 원천 — logs `self-implement.pod` `job-finished` 의 childRunId·prUrl(PR 번호만). */
83
+ listPodFinishes?: (since: string) => PodFinish[];
27
84
  }
28
85
 
29
86
  const ID = /^[a-zA-Z0-9_-]{1,128}$/;
@@ -47,7 +104,9 @@ function inWindow(at: string, since: number, now: number): boolean {
47
104
  return Number.isFinite(ms) && ms >= since && ms <= now;
48
105
  }
49
106
 
50
- function cardEdges(card: TaskCard, since: number, now: number, seats: ReadonlySet<string>): LoopEdge[] {
107
+ type PlacedCell = { cardId: string; cellId: string; owner: string };
108
+
109
+ function cardEdges(card: TaskCard, since: number, now: number, seats: ReadonlySet<string>, placedCells: PlacedCell[]): LoopEdge[] {
51
110
  if (!card.goalId.startsWith('wish:') || !ID.test(card.id)) return [];
52
111
  const node = `card:${card.id}`;
53
112
  const ref = card.id;
@@ -75,6 +134,7 @@ function cardEdges(card: TaskCard, since: number, now: number, seats: ReadonlySe
75
134
  if (!owner || !placement || placement.id !== cellId || !ID.test(cellId)
76
135
  || typeof placement.version !== 'string' || !/^\d+\.\d+\.\d+$/.test(placement.version)) continue;
77
136
  placedOwners.add(owner);
137
+ placedCells.push({ cardId: card.id, cellId, owner });
78
138
  if (inWindow(section.createdAt, since, now)) {
79
139
  const cellNode = `loop:${cellId}`;
80
140
  edges.push({ at: section.createdAt, kind: 'card', from: node, to: cellNode, ref });
@@ -93,9 +153,9 @@ function cardEdges(card: TaskCard, since: number, now: number, seats: ReadonlySe
93
153
  return edges;
94
154
  }
95
155
 
96
- function runStarts(since: string, dir: string, owners: ReadonlyMap<string, string>, seats: ReadonlySet<string>): Array<{ runId: string; at: string; seat: string }> {
156
+ function runFacts(dir: string, owners: ReadonlyMap<string, string>, seats: ReadonlySet<string>): RunFact[] {
97
157
  if (!existsSync(dir)) return [];
98
- const result: Array<{ runId: string; at: string; seat: string }> = [];
158
+ const result: RunFact[] = [];
99
159
  let names: string[];
100
160
  try { names = readdirSync(dir); } catch { return []; }
101
161
  for (const name of names) {
@@ -106,28 +166,269 @@ function runStarts(since: string, dir: string, owners: ReadonlyMap<string, strin
106
166
  const runId = name.slice(0, -6);
107
167
  let entries: RunLedgerEntry[] | null;
108
168
  try { entries = loadRunLedger(runId, dir); } catch { continue; }
109
- const start = entries?.find(entry => entry.event === 'start' && entry.runId === runId);
169
+ if (!entries) continue;
170
+ const start = entries.find(entry => entry.event === 'start' && entry.runId === runId);
110
171
  const feature = typeof start?.data.feature === 'string' ? start.data.feature : '';
111
172
  const launchSeat = start?.data.launchSeat ?? start?.data.seat ?? owners.get(feature)
112
173
  ?? start?.data.originAgent ?? start?.data.controller;
113
- if (start?.timestamp && start.timestamp >= since && seat(launchSeat, seats)) {
114
- result.push({ runId, at: start.timestamp, seat: launchSeat });
115
- }
174
+ const merged = entries.find(entry => entry.event === 'merged' && entry.data.merged === true && typeof entry.timestamp === 'string');
175
+ const goalId = [start, ...entries].map(entry => entry?.goalId ?? (typeof entry?.data.goalId === 'string' ? entry.data.goalId : undefined))
176
+ .find((value): value is string => typeof value === 'string' && /^[a-f0-9]{16}$/.test(value));
177
+ // pr-opened data = {url, number, branch, …} (TC 실측 10-07) — 번호는 data.number(양의 정수)만 읽는다. 못 읽으면 간선 0.
178
+ const prs = entries.filter(entry => entry.event === 'pr-opened' && typeof entry.timestamp === 'string'
179
+ && Number.isSafeInteger(entry.data.number) && (entry.data.number as number) > 0)
180
+ .map(entry => ({ at: entry.timestamp!, number: entry.data.number as number }));
181
+ // `pod-ledger-incomplete` 첫 줄 원장은 시작이 없어도 버리지 않는다 — pr 근거로만 쓰고 run 단계는 만들지 않는다.
182
+ const children = [...new Set(entries.filter(entry => entry.event === 'pod-child-run' && typeof entry.data.childRunId === 'string'
183
+ && ID.test(entry.data.childRunId) && entry.data.childRunId !== runId).map(entry => entry.data.childRunId as string))];
184
+ result.push({
185
+ runId, prs, children,
186
+ ...(start?.timestamp ? { startAt: start.timestamp } : {}),
187
+ ...(start?.timestamp && seat(launchSeat, seats) ? { seat: launchSeat } : {}),
188
+ ...(start?.timestamp && feature ? { feature } : {}),
189
+ ...(goalId ? { goalId } : {}),
190
+ ...(merged?.timestamp ? { mergedAt: merged.timestamp } : {}),
191
+ });
116
192
  }
117
193
  return result;
118
194
  }
119
195
 
120
- /** GET only; project an allowlist of fields from the three ledgers. Never serialize ledger records. */
196
+ function runStartsFrom(facts: readonly RunFact[], since: string): Array<{ runId: string; at: string; seat: string; mergedAt?: string }> {
197
+ return facts.filter(fact => fact.startAt !== undefined && fact.seat !== undefined
198
+ && (fact.startAt >= since || (fact.mergedAt !== undefined && fact.mergedAt >= since)))
199
+ .map(fact => ({ runId: fact.runId, at: fact.startAt!, seat: fact.seat!, ...(fact.mergedAt ? { mergedAt: fact.mergedAt } : {}) }));
200
+ }
201
+
202
+ /** handed.jsonl(`tick.ts` ORCH-TA-HAND) — 행의 at·status·key·cardId·cell·mode·runId 만. 넘김은 handed 뿐이고 claimed·released 는 끊긴 간선 판정용이다. */
203
+ function handedRows(root: string): HandedRow[] {
204
+ const path = join(root, 'loop', 'orchestrator', 'handed.jsonl');
205
+ try { if (!existsSync(path) || statSync(path).size > 5 * 1024 * 1024) return []; } catch { return []; }
206
+ let raw: string;
207
+ try { raw = readFileSync(path, 'utf8'); } catch { return []; }
208
+ const rows: HandedRow[] = [];
209
+ for (const line of raw.split('\n')) {
210
+ const row = line.trim() ? parse(line) : null;
211
+ const status = row?.status;
212
+ if ((status !== 'handed' && status !== 'claimed' && status !== 'released') || typeof row!.at !== 'string') continue;
213
+ if (status === 'handed' && typeof row!.cardId !== 'string') continue;
214
+ rows.push({
215
+ at: row!.at as string, status,
216
+ ...(typeof row!.cardId === 'string' ? { cardId: row!.cardId } : {}),
217
+ ...(typeof row!.cell === 'string' ? { cell: row!.cell } : {}),
218
+ ...(typeof row!.key === 'string' ? { key: row!.key } : {}),
219
+ ...(row!.mode === 'shadow' || row!.mode === 'live' ? { mode: row!.mode } : {}),
220
+ ...(typeof row!.runId === 'string' ? { runId: row!.runId } : {}),
221
+ });
222
+ }
223
+ return rows;
224
+ }
225
+
226
+ function taskCards(root: string): TaskAgentCard[] {
227
+ const tasks = object(readTaskAgentState<{ tasks?: unknown }>(join(root, 'task-agent-actions.json')).tasks);
228
+ return tasks ? Object.values(tasks).filter((card): card is TaskAgentCard => object(card) !== null) : [];
229
+ }
230
+
231
+ /** 대체 원천: Pod 런은 도는 동안 원장이 Pod 안에만 있다 — 호스트 logs 의 job-finished 에서 childRunId·PR 번호만 읽는다. */
232
+ function podFinishes(since: string): PodFinish[] {
233
+ const path = logsDbPath();
234
+ if (!existsSync(path)) return [];
235
+ const store = LogStore.openReadOnly(path);
236
+ try {
237
+ const result: PodFinish[] = [];
238
+ for (const row of store.queryAll({ exactCategories: ['self-implement.pod'], events: ['job-finished'], sinceMs: Date.parse(since) })) {
239
+ const data = row.data ? parse(row.data) : null;
240
+ const pr = typeof data?.prUrl === 'string' ? /^https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/pull\/(\d+)$/.exec(data.prUrl) : null;
241
+ if (typeof data?.childRunId === 'string' && pr) result.push({ at: row.ts, childRunId: data.childRunId, prNumber: Number(pr[1]) });
242
+ }
243
+ return result;
244
+ } finally { store.close(); }
245
+ }
246
+
247
+ /** Project only the key and runId of orchestrator launches, never their card snapshots or prose. */
248
+ function orchestratorLaunches(root: string): Array<{ key: string; runId: string }> {
249
+ const dir = join(root, 'loop', 'orchestrator');
250
+ if (!existsSync(dir)) return [];
251
+ const launches: Array<{ key: string; runId: string }> = [];
252
+ let names: string[];
253
+ try { names = readdirSync(dir); } catch { return []; }
254
+ for (const name of names) {
255
+ if (!/^[a-zA-Z0-9._-]+\.json$/.test(name)) continue;
256
+ const path = join(dir, name);
257
+ try {
258
+ if (!statSync(path).isFile() || statSync(path).size > 5 * 1024 * 1024) continue;
259
+ const state = parse(readFileSync(path, 'utf8'));
260
+ if (state?.mode !== 'live' || !Array.isArray(state.launched)) continue;
261
+ for (const item of state.launched) {
262
+ const launch = object(item);
263
+ if (typeof launch?.key === 'string' && typeof launch.runId === 'string') {
264
+ launches.push({ key: launch.key, runId: launch.runId });
265
+ }
266
+ }
267
+ } catch { /* An unreadable tick cannot establish a launch. */ }
268
+ }
269
+ return launches;
270
+ }
271
+
272
+ type JourneyLink = { from: string; to: string; both: boolean };
273
+ type Journey = { links: JourneyLink[]; pieces: Map<string, string[]> };
274
+
275
+ /** 런 원장 start.feature 는 `boundReadableText(feature, 120)` 로 잘린다(orchestrator.ts RUN_START_FEATURE_MAX_CHARS) —
276
+ * 꼬리 표식 ` [truncated; originalChars=N]`·`[truncated]`·`…`(⊕ 손으로 쓴 `...`)을 떼고 공백을 정규화한다. */
277
+ const FEATURE_TRUNCATION_TAIL = /(?:\s*\[truncated(?:; originalChars=\d+)?\]|…|\.\.\.)$/;
278
+ const FEATURE_PREFIX_MIN = 40;
279
+ function normalizeText(text: string): string { return text.replace(/\s+/g, ' ').trim(); }
280
+ /** 잘린 feature 의 비교 접두 — 표식이 없으면 undefined(완전 일치만). 끝의 짝 잃은 서로게이트도 뗀다(UTF-16 절단). */
281
+ function featurePrefix(feature: string): string | undefined {
282
+ if (!FEATURE_TRUNCATION_TAIL.test(feature)) return undefined;
283
+ const prefix = normalizeText(feature.replace(FEATURE_TRUNCATION_TAIL, '').replace(/[\ud800-\udbff]$/, ''));
284
+ return prefix.length >= FEATURE_PREFIX_MIN ? prefix : undefined;
285
+ }
286
+ /** 카드 text ↔ 런 feature — 완전 일치, 또는 잘린 feature(≥40자)가 카드 text(공백 정규화)의 접두. */
287
+ function featureMatches(feature: string | undefined, text: string): boolean {
288
+ if (!feature || !text) return false;
289
+ if (feature === text) return true;
290
+ const prefix = featurePrefix(feature);
291
+ return prefix !== undefined && normalizeText(text).startsWith(prefix);
292
+ }
293
+
294
+ /** 카드 → 런 연결: 카드 runId(있으면 최우선) → goalId 일치 → feature 일치(완전 또는 잘린 접두) ∧ start ≥ launch.at — 뒤 둘은 «유일할 때만»
295
+ * (같은 feature 에 닿는 발사 카드가 둘이면 잇지 않는다). 자식 런(pod-child-run)은 부모로 닿는다. */
296
+ function linkedRun(card: TaskAgentCard, launchAt: string | undefined, launchedTexts: readonly string[], facts: readonly RunFact[], childIds: ReadonlySet<string>): string | undefined {
297
+ const explicitRun = (card as { runId?: unknown }).runId;
298
+ if (typeof explicitRun === 'string' && ID.test(explicitRun)) return explicitRun;
299
+ const pick = (candidates: RunFact[]): string | undefined => {
300
+ const top = candidates.filter(fact => !childIds.has(fact.runId));
301
+ return top.length === 1 ? top[0]!.runId : candidates.length === 1 ? candidates[0]!.runId : undefined;
302
+ };
303
+ if (typeof card.goalId === 'string' && card.goalId) {
304
+ const byGoal = pick(facts.filter(fact => fact.goalId === card.goalId));
305
+ if (byGoal) return byGoal;
306
+ }
307
+ const launchMs = launchAt === undefined ? NaN : Date.parse(launchAt);
308
+ if (!Number.isFinite(launchMs) || typeof card.text !== 'string' || !card.text) return undefined;
309
+ const runId = pick(facts.filter(fact => featureMatches(fact.feature, card.text) && fact.startAt !== undefined && Date.parse(fact.startAt) >= launchMs));
310
+ if (!runId) return undefined;
311
+ const feature = facts.find(fact => fact.runId === runId)!.feature;
312
+ return launchedTexts.filter(text => featureMatches(feature, text)).length === 1 ? runId : undefined;
313
+ }
314
+
315
+ /** hand 행의 mode — 행의 mode 필드가 우선, 없으면 key 접두(`shadow:orch:` = shadow · `orch:` = live). */
316
+ function handMode(row: HandedRow): 'shadow' | 'live' | undefined {
317
+ if (row.mode === 'shadow' || row.mode === 'live') return row.mode;
318
+ if (typeof row.key !== 'string') return undefined;
319
+ if (row.key.startsWith('shadow:orch:')) return 'shadow';
320
+ if (row.key.startsWith('orch:')) return 'live';
321
+ return undefined;
322
+ }
323
+
324
+ /** 넘김(hand)·발사(launch)·런→PR·green 제안(move) 간선을 더하고, ?ref= 여정에 쓸 id 연결을 돌려준다. 문면은 싣지 않는다. */
325
+ function taskAgentEdges(deps: LoopEdgesDeps, facts: readonly RunFact[], seats: ReadonlySet<string>, since: number, now: number, sinceIso: string, edges: LoopEdge[]): Journey {
326
+ // 주입된 카드 목록(시험)에 뿌리가 없으면 운영 뿌리를 읽지 않는다 — 공개본엔 bunfig preload 격리가 없다.
327
+ const root = deps.cardRoot ?? (deps.listCards ? undefined : effectiveInstanceRoot());
328
+ const journey: Journey = { links: [], pieces: new Map() };
329
+ let handed: HandedRow[] = [];
330
+ try { handed = (deps.listHanded ?? (() => root === undefined ? [] : handedRows(root)))(); } catch { /* An unreadable hand ledger hands nothing. */ }
331
+ const settled = new Set<string>();
332
+ for (const row of handed) if (row.key !== undefined && (row.status ?? 'handed') !== 'claimed') settled.add(row.key);
333
+ const brokenAfter = deps.handBrokenAfterMs ?? 10 * 60 * 1000;
334
+ const brokenKeys = new Set<string>();
335
+ for (const row of handed) {
336
+ const status = row.status ?? 'handed';
337
+ const mode = handMode(row);
338
+ const tick = typeof row.runId === 'string' && ID.test(row.runId) ? { tick: row.runId } : {};
339
+ if (status === 'handed') {
340
+ if (typeof row.cardId !== 'string' || !ID.test(row.cardId) || !inWindow(row.at, since, now)) continue;
341
+ edges.push({ at: row.at, kind: 'hand', from: ORCHESTRATOR_NODE, to: TASK_AGENT_NODE, ref: row.cardId,
342
+ ...(row.cell && ID.test(row.cell) ? { cell: row.cell } : {}), ...(mode ? { mode } : {}), ...tick });
343
+ // 넘김 행의 cardId 는 TASK-AGENT 카드다 — 소원(wish) 카드는 key(`orch:<wish>:<칸>`)에만 있다. 둘을 잇지 않으면
344
+ // `?ref=<wish>` 여정이 넘김에서 끊겨 데모 띠가 1단계 뒤로 «남음»에 멈춘다(10-07 리허설 2 실측 · JOURNEY-HAND-LINK).
345
+ const wish = row.key === undefined ? undefined : /^orch:([^:]+):([^:]+)$/.exec(row.key)?.[1];
346
+ if (wish !== undefined && ID.test(wish) && wish !== row.cardId) journey.links.push({ from: wish, to: row.cardId, both: true });
347
+ continue;
348
+ }
349
+ // live claimed 만(handed·released 없음 · N분 지남) = 발사 실패 — 끊긴 간선 하나. shadow claimed→released 는 간선 0.
350
+ if (status !== 'claimed' || mode !== 'live' || row.key === undefined || settled.has(row.key) || brokenKeys.has(row.key)) continue;
351
+ const claimedMs = Date.parse(row.at);
352
+ if (!Number.isFinite(claimedMs) || now - claimedMs < brokenAfter || !inWindow(row.at, since, now)) continue;
353
+ const parts = /^orch:([^:]+):([^:]+)$/.exec(row.key);
354
+ if (!parts || !ID.test(parts[1]!) || !ID.test(parts[2]!)) continue;
355
+ brokenKeys.add(row.key);
356
+ edges.push({ at: row.at, kind: 'hand', from: ORCHESTRATOR_NODE, to: TASK_AGENT_NODE, ref: parts[1]!, cell: parts[2]!, mode, ...tick, broken: true });
357
+ }
358
+ let cards: TaskAgentCard[] = [];
359
+ try { cards = (deps.listTaskCards ?? (() => root === undefined ? [] : taskCards(root)))().filter(card => typeof card?.id === 'string' && ID.test(card.id)); }
360
+ catch { /* An unreadable task-agent state launches nothing. */ }
361
+ const launchesOf = (card: TaskAgentCard) => (Array.isArray(card.history) ? card.history : [])
362
+ .filter(event => event?.event === 'launch' && typeof event.at === 'string');
363
+ const launchedTexts: string[] = [];
364
+ for (const card of cards) {
365
+ if (launchesOf(card).length && typeof card.text === 'string') launchedTexts.push(card.text);
366
+ }
367
+ const childIds = new Set(facts.flatMap(fact => fact.children));
368
+ for (const card of cards) {
369
+ if (typeof card.mission === 'string' && ID.test(card.mission)) journey.links.push({ from: card.id, to: card.mission, both: false });
370
+ if (Array.isArray(card.pieces)) journey.pieces.set(card.id, card.pieces.filter(piece => typeof piece === 'string' && ID.test(piece)));
371
+ const launches = launchesOf(card);
372
+ for (const launch of launches) {
373
+ if (seat(card.seat, seats) && inWindow(launch.at, since, now)) edges.push({ at: launch.at, kind: 'launch', from: TASK_AGENT_NODE, to: card.seat, ref: card.id });
374
+ }
375
+ const runId = linkedRun(card, launches[0]?.at, launchedTexts, facts, childIds);
376
+ if (runId) journey.links.push({ from: card.id, to: runId, both: true });
377
+ const green = object(card.greenProposal);
378
+ if (green && typeof green.at === 'string' && typeof green.checklistId === 'string' && ID.test(green.checklistId) && inWindow(green.at, since, now)) {
379
+ edges.push({ at: green.at, kind: 'move', from: TASK_AGENT_NODE, to: `release:${green.checklistId}`, ref: card.id, move: 'green-proposal' });
380
+ }
381
+ }
382
+ const prSeen = new Set<string>();
383
+ const prEdge = (runId: string, at: string, number: number) => {
384
+ const key = `${runId}:${number}`;
385
+ if (!ID.test(runId) || !Number.isSafeInteger(number) || number < 1 || prSeen.has(key) || !inWindow(at, since, now)) return;
386
+ prSeen.add(key);
387
+ edges.push({ at, kind: 'run', from: `loop:${runId}`, to: `pr:${number}`, ref: runId });
388
+ };
389
+ for (const fact of facts) {
390
+ for (const child of fact.children) journey.links.push({ from: fact.runId, to: child, both: true });
391
+ for (const pr of fact.prs) prEdge(fact.runId, pr.at, pr.number);
392
+ }
393
+ let pods: PodFinish[] = [];
394
+ // 대체 원천은 연합 원장(운영 기본)과 짝이다 — 원장을 주입했으면(시험·한 디렉터리) logs 를 읽지 않는다.
395
+ const injectedLedger = deps.ledgerDir !== undefined || deps.listRunStarts !== undefined || deps.listRunFacts !== undefined;
396
+ try { pods = (deps.listPodFinishes ?? (injectedLedger ? () => [] : podFinishes))(sinceIso); } catch { /* logs outage — the run ledger stays the primary source. */ }
397
+ for (const pod of pods) prEdge(pod.childRunId, pod.at, pod.prNumber);
398
+ return journey;
399
+ }
400
+
401
+ /** ?ref= 의 여정 id 집합 — 카드↔런·런↔자식 런은 양방향, 조각→미션은 위로만(형제 조각은 미션을 ref 로 줄 때만). */
402
+ function journeyIds(ref: string, journey: Journey): Set<string> {
403
+ const ids = new Set([ref, ...(journey.pieces.get(ref) ?? [])]);
404
+ const queue = [...ids];
405
+ while (queue.length) {
406
+ const id = queue.shift()!;
407
+ for (const link of journey.links) {
408
+ const next = link.from === id ? link.to : link.both && link.to === id ? link.from : undefined;
409
+ if (next !== undefined && !ids.has(next)) { ids.add(next); queue.push(next); }
410
+ }
411
+ }
412
+ return ids;
413
+ }
414
+
415
+ /** GET only; project an allowlist of fields from the ledgers. Never serialize ledger records. */
121
416
  export function handleLoopEdgesGet(req: Request, deps: LoopEdgesDeps = {}): Response {
122
417
  const params = new URL(req.url).searchParams;
123
418
  const rawSince = params.get('since');
124
- const since = rawSince === null ? Date.now() - 60 * 60 * 1000 : Date.parse(rawSince);
419
+ const journeyRef = params.get('ref');
420
+ // ?mode=live|shadow — hand 간선만 그 mode 로 거른다(다른 종류 간선은 그대로).
421
+ const handModeFilter = params.get('mode');
422
+ // ?ref= 는 한 카드·런의 여정 — 60분 창과 limit 에 잘리지 않게 since 를 안 주면 처음부터 본다.
423
+ const since = rawSince === null ? (journeyRef === null ? Date.now() - 60 * 60 * 1000 : 0) : Date.parse(rawSince);
125
424
  const rawLimit = params.get('limit');
126
425
  const limit = rawLimit === null ? 100 : Number(rawLimit);
127
426
  const now = Date.now();
128
427
  if (rawSince !== null && !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:\d\d)$/.test(rawSince)
129
- || !Number.isFinite(since) || since > now || !Number.isSafeInteger(limit) || limit < 1 || limit > 500) {
130
- return jsonResponse({ error: 'usage: GET /v1/loops/edges?since=<ISO>&limit=<1..500>' }, 400);
428
+ || !Number.isFinite(since) || since > now || !Number.isSafeInteger(limit) || limit < 1 || limit > 500
429
+ || (journeyRef !== null && !ID.test(journeyRef))
430
+ || (handModeFilter !== null && handModeFilter !== 'live' && handModeFilter !== 'shadow')) {
431
+ return jsonResponse({ error: 'usage: GET /v1/loops/edges?since=<ISO>&limit=<1..500>&ref=<card|run|mission id>' }, 400);
131
432
  }
132
433
  const sinceIso = new Date(since).toISOString();
133
434
  const seats = new Set((deps.seatIds ?? (() => [...SEATS.map(s => s.seat), ...listSubSeatCharters().map(s => s.id)]))());
@@ -143,17 +444,18 @@ export function handleLoopEdgesGet(req: Request, deps: LoopEdgesDeps = {}): Resp
143
444
  if (seat(recipient, seats)) edges.push({ at: event.at, kind, from: event.refs.seat, to: recipient, ref });
144
445
  }
145
446
  }
447
+ const placedCells: PlacedCell[] = [];
146
448
  if (deps.listCards) {
147
- for (const card of deps.listCards()) edges.push(...cardEdges(card, since, now, seats));
449
+ for (const card of deps.listCards()) edges.push(...cardEdges(card, since, now, seats, placedCells));
148
450
  } else {
149
451
  const root = deps.cardRoot;
150
452
  if (existsSync(cardIndexPath(root))) {
151
453
  const store = new CardStore(root, true);
152
- try { for (const card of store.listCards()) edges.push(...cardEdges(card, since, now, seats)); }
454
+ try { for (const card of store.listCards()) edges.push(...cardEdges(card, since, now, seats, placedCells)); }
153
455
  finally { store.close(); }
154
456
  }
155
457
  }
156
- const readStarts = deps.listRunStarts ?? ((from: string) => {
458
+ const readFacts = deps.listRunFacts ?? (deps.listRunStarts ? () => [] : () => {
157
459
  const owners = new Map<string, string>();
158
460
  try {
159
461
  for (const entry of (deps.listLoopOwners ?? (() => listLoops()))()) {
@@ -161,13 +463,40 @@ export function handleLoopEdgesGet(req: Request, deps: LoopEdgesDeps = {}): Resp
161
463
  }
162
464
  } catch { /* A registry outage does not erase explicit launch-seat evidence. */ }
163
465
  return (deps.ledgerDir ? [deps.ledgerDir] : resolveFederatedRunLedgerDirectories({ includeTest: false }))
164
- .flatMap(dir => runStarts(from, dir, owners, seats));
466
+ .flatMap(dir => runFacts(dir, owners, seats));
165
467
  });
166
- for (const run of readStarts(sinceIso)) {
167
- if (seat(run.seat, seats) && ID.test(run.runId) && inWindow(run.at, since, now)) {
168
- edges.push({ at: run.at, kind: 'run', from: run.seat, to: `loop:${run.runId}`, ref: run.runId });
468
+ let facts: RunFact[] = [];
469
+ try { facts = readFacts().filter(fact => ID.test(fact.runId)); } catch { /* An unreadable ledger set links nothing. */ }
470
+ const readStarts = deps.listRunStarts ?? ((from: string) => runStartsFrom(facts, from));
471
+ const runs = readStarts(sinceIso).filter(run => seat(run.seat, seats) && ID.test(run.runId)
472
+ && (inWindow(run.at, since, now) || (run.mergedAt !== undefined && inWindow(run.mergedAt, since, now))));
473
+ for (const run of runs) {
474
+ if (inWindow(run.at, since, now)) edges.push({ at: run.at, kind: 'run', from: run.seat, to: `loop:${run.runId}`, ref: run.runId });
475
+ }
476
+ if (placedCells.length && runs.length) {
477
+ const root = deps.cardRoot ?? effectiveInstanceRoot();
478
+ let requests: SeatRequestRow[] = [];
479
+ let launches: Array<{ key: string; runId: string }> = [];
480
+ try { requests = (deps.listSeatRequests ?? (() => listSeatRequests(root)))(); } catch { /* Missing request evidence is not a match. */ }
481
+ try { launches = (deps.listOrchestratorLaunches ?? (() => orchestratorLaunches(root)))(); } catch { /* Missing launch evidence is not a match. */ }
482
+ const confirmed = new Set(launches.filter(item => ID.test(item.runId) && runs.some(run => run.runId === item.runId))
483
+ .map(item => JSON.stringify([item.key, item.runId])));
484
+ for (const cell of placedCells) {
485
+ const key = `orch:${cell.cardId}:${cell.cellId}`;
486
+ if (!requests.some(row => row.key === key && row.cell === cell.cellId && row.source === 'orchestrator' && row.seat === cell.owner)) continue;
487
+ for (const run of runs) {
488
+ if (run.seat !== cell.owner || !confirmed.has(JSON.stringify([key, run.runId]))) continue;
489
+ if (inWindow(run.at, since, now)) edges.push({ at: run.at, kind: 'card', from: cell.owner, to: `loop:${run.runId}`, ref: cell.cardId });
490
+ if (run.mergedAt && inWindow(run.mergedAt, since, now)) {
491
+ edges.push({ at: run.mergedAt, kind: 'card', from: `loop:${run.runId}`, to: `landed:${cell.cellId}`, ref: cell.cardId });
492
+ }
493
+ }
169
494
  }
170
495
  }
171
- edges.sort((a, b) => b.at.localeCompare(a.at) || a.kind.localeCompare(b.kind) || a.from.localeCompare(b.from) || a.to.localeCompare(b.to));
172
- return jsonResponse({ edges: edges.slice(0, limit) });
496
+ const links = taskAgentEdges(deps, facts, seats, since, now, sinceIso, edges);
497
+ const journey = journeyRef === null ? null : journeyIds(journeyRef, links);
498
+ const out = edges.filter(edge => (!journey || journey.has(edge.ref))
499
+ && (handModeFilter === null || edge.kind !== 'hand' || edge.mode === handModeFilter));
500
+ out.sort((a, b) => b.at.localeCompare(a.at) || a.kind.localeCompare(b.kind) || a.from.localeCompare(b.from) || a.to.localeCompare(b.to));
501
+ return jsonResponse({ edges: out.slice(0, limit) });
173
502
  }