elanous 0.2.18 → 0.2.20

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 (350) 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/{1307-30dff22c9e1f3073.js → 1307-77fad80cf54477c6.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/224-e7be8f762fe74986.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/2489-ff55a35b202c0f94.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/4144-fad095b48a1862ee.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/5755-ea7ed88eb956f47b.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/5977-94ed6f78e9814b2b.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/7748-6e51bfe927019f5c.js +25 -0
  14. package/apps/pwa/out/_next/static/chunks/8401-35e94e9091f5eb8d.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/a9f2b54d-33c0689b70ed8cf3.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-d92f7ee3994e84c7.js → page-c855c24de7db74fe.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-4733c967b56a4277.js → page-85b316bb4235980b.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/board/{page-d22fd849cc22f104.js → page-9b2b26a33cb2cd42.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-bb742f995c6769e7.js → page-c9955fde67b1bcb5.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/bots/{page-02b073a2ac793b83.js → page-7a852956c74015a3.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/app/chat/{page-8a9b3594bc23289b.js → page-4ba4abe096b6c2a8.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/consult/page-575d4a8b4f7becb2.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/control/{page-308111abadf35670.js → page-f5d56ff5f45ad164.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/decisions/page-2260e9eef0357421.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-5d97b2901e0d1ea6.js → page-7c058069c5e7fa63.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/exec/{page-8c3d8e63ae2e1c98.js → page-466bf0ee16ad1432.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/field/{page-9b72a07d424701c6.js → page-a23bc71713bcc08f.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/inside/page-ed4a6f799f005d09.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/intake/{page-a572f58cd3ca5864.js → page-9cd565005b93aca6.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/live/{page-be5ea965a07fbc41.js → page-474cb5888ea0e8f3.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/loops/page-35bfec2e631d3eae.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/market/{page-fa244bd146a00ae8.js → page-9ca21f9ad3208a61.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-44d56d6391965a6a.js → page-f9ccb1b1adbea14d.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/app/morning/{page-83a700d4c754d802.js → page-ec102c9b8b691e37.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/app/{not-found-7b3170f374f77b9d.js → not-found-a9c2cfa8dda922a5.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/observatory/page-6da35144878feb6d.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-f6055495e9ed7ea8.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-25204694fa9984a5.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a60e035f151d84d3.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/outputs/page-189c587f36874538.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/page-c56634bde705a65d.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-1467047104141297.js → page-db03ae3589a81c9e.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4a6463b97f0ad94f.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-102310930791197a.js → page-75bffecc9aa1004e.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a8d2fe0c669e0de4.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/settings/{page-6ed0b73bae8351d8.js → page-31d5b875760bb62d.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c26fa10aac6c21c4.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/setup/{page-43336055451f6e37.js → page-68793c6e10331d66.js} +1 -1
  53. package/apps/pwa/out/_next/static/chunks/app/share/{page-3cc47dca0922ab9a.js → page-46d5201d2207a511.js} +1 -1
  54. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-a77ee355a2304ff0.js → page-d81c07a7b4f618ae.js} +1 -1
  55. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-b17cd46cf0e9b8cf.js → page-be1c4e8697f22360.js} +1 -1
  56. package/apps/pwa/out/_next/static/chunks/app/term/page-cf0a4b4abead71ea.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/today/page-a4717e2c4bf7a28f.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/trace/{page-7bf54c9bb9274af8.js → page-0fd895fab9b62c1b.js} +1 -1
  59. package/apps/pwa/out/_next/static/chunks/app/vault/page-8b6c1454a8e8f306.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-c6e4364d9de8f717.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/workflows/page-0acc8e198f7b0dfb.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-134da468538baf48.js → page-34882c84f84021c5.js} +1 -1
  63. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/{webpack-c2782b330447ffb6.js → webpack-c260571513bf43c2.js} +1 -1
  65. package/apps/pwa/out/_next/static/css/{e99e3cff94ced78a.css → 599369d853c61df7.css} +1 -1
  66. package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +1 -0
  67. package/apps/pwa/out/_next/static/css/d3427996da11e480.css +1 -0
  68. package/apps/pwa/out/approvals/index.html +1 -1
  69. package/apps/pwa/out/approvals/index.txt +14 -14
  70. package/apps/pwa/out/autopilot/index.html +1 -1
  71. package/apps/pwa/out/autopilot/index.txt +14 -14
  72. package/apps/pwa/out/board/index.html +1 -1
  73. package/apps/pwa/out/board/index.txt +14 -14
  74. package/apps/pwa/out/botlab/index.html +1 -1
  75. package/apps/pwa/out/botlab/index.txt +14 -14
  76. package/apps/pwa/out/bots/index.html +1 -1
  77. package/apps/pwa/out/bots/index.txt +14 -14
  78. package/apps/pwa/out/ceo/index.html +1 -1
  79. package/apps/pwa/out/ceo/index.txt +14 -14
  80. package/apps/pwa/out/chat/index.html +1 -1
  81. package/apps/pwa/out/chat/index.txt +14 -14
  82. package/apps/pwa/out/consult/index.html +1 -1
  83. package/apps/pwa/out/consult/index.txt +14 -14
  84. package/apps/pwa/out/control/index.html +1 -1
  85. package/apps/pwa/out/control/index.txt +14 -14
  86. package/apps/pwa/out/decisions/index.html +1 -1
  87. package/apps/pwa/out/decisions/index.txt +14 -14
  88. package/apps/pwa/out/design-check/index.html +1 -1
  89. package/apps/pwa/out/design-check/index.txt +14 -14
  90. package/apps/pwa/out/editor/index.html +1 -1
  91. package/apps/pwa/out/editor/index.txt +16 -15
  92. package/apps/pwa/out/exec/index.html +1 -1
  93. package/apps/pwa/out/exec/index.txt +14 -14
  94. package/apps/pwa/out/field/index.html +1 -1
  95. package/apps/pwa/out/field/index.txt +14 -14
  96. package/apps/pwa/out/healthz +1 -1
  97. package/apps/pwa/out/index.html +1 -1
  98. package/apps/pwa/out/index.txt +14 -14
  99. package/apps/pwa/out/inside/index.html +1 -1
  100. package/apps/pwa/out/inside/index.txt +16 -15
  101. package/apps/pwa/out/intake/index.html +1 -1
  102. package/apps/pwa/out/intake/index.txt +14 -14
  103. package/apps/pwa/out/live/index.html +1 -1
  104. package/apps/pwa/out/live/index.txt +14 -14
  105. package/apps/pwa/out/loops/index.html +1 -1
  106. package/apps/pwa/out/loops/index.txt +24 -23
  107. package/apps/pwa/out/market/index.html +1 -1
  108. package/apps/pwa/out/market/index.txt +14 -14
  109. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  110. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  111. package/apps/pwa/out/morning/index.html +1 -1
  112. package/apps/pwa/out/morning/index.txt +14 -14
  113. package/apps/pwa/out/observatory/index.html +1 -1
  114. package/apps/pwa/out/observatory/index.txt +14 -14
  115. package/apps/pwa/out/ops/checklist/index.html +1 -1
  116. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  117. package/apps/pwa/out/ops/release/index.html +1 -1
  118. package/apps/pwa/out/ops/release/index.txt +14 -14
  119. package/apps/pwa/out/ops/seats/index.html +1 -1
  120. package/apps/pwa/out/ops/seats/index.txt +14 -14
  121. package/apps/pwa/out/outputs/index.html +1 -1
  122. package/apps/pwa/out/outputs/index.txt +14 -14
  123. package/apps/pwa/out/reflection/index.html +1 -1
  124. package/apps/pwa/out/reflection/index.txt +15 -15
  125. package/apps/pwa/out/scheduler/index.html +1 -1
  126. package/apps/pwa/out/scheduler/index.txt +14 -14
  127. package/apps/pwa/out/sessions/index.html +1 -1
  128. package/apps/pwa/out/sessions/index.txt +14 -14
  129. package/apps/pwa/out/settings/devices/index.html +1 -1
  130. package/apps/pwa/out/settings/devices/index.txt +14 -14
  131. package/apps/pwa/out/settings/index.html +1 -1
  132. package/apps/pwa/out/settings/index.txt +14 -14
  133. package/apps/pwa/out/setup/done/index.html +1 -1
  134. package/apps/pwa/out/setup/done/index.txt +14 -14
  135. package/apps/pwa/out/setup/index.html +1 -1
  136. package/apps/pwa/out/setup/index.txt +14 -14
  137. package/apps/pwa/out/share/index.html +1 -1
  138. package/apps/pwa/out/share/index.txt +14 -14
  139. package/apps/pwa/out/showroom/index.html +1 -1
  140. package/apps/pwa/out/showroom/index.txt +14 -14
  141. package/apps/pwa/out/tasks/index.html +1 -1
  142. package/apps/pwa/out/tasks/index.txt +14 -14
  143. package/apps/pwa/out/term/index.html +1 -1
  144. package/apps/pwa/out/term/index.txt +14 -14
  145. package/apps/pwa/out/today/index.html +1 -1
  146. package/apps/pwa/out/today/index.txt +14 -14
  147. package/apps/pwa/out/trace/index.html +1 -1
  148. package/apps/pwa/out/trace/index.txt +14 -14
  149. package/apps/pwa/out/vault/index.html +1 -1
  150. package/apps/pwa/out/vault/index.txt +14 -14
  151. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  152. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  153. package/apps/pwa/out/workflows/index.html +1 -1
  154. package/apps/pwa/out/workflows/index.txt +14 -14
  155. package/apps/pwa/out/workspace/index.html +1 -1
  156. package/apps/pwa/out/workspace/index.txt +14 -14
  157. package/apps/pwa/out/worktrees/index.html +1 -1
  158. package/apps/pwa/out/worktrees/index.txt +14 -14
  159. package/graphs/demo/editor-recipes.yaml +22 -0
  160. package/graphs/ops/guardian.yaml +26 -0
  161. package/graphs/ops/recipes.yaml +3 -0
  162. package/graphs/retro/daily.yaml +25 -0
  163. package/graphs/retro/recipes.yaml +24 -0
  164. package/package.json +1 -1
  165. package/scripts/ask-marker-check.ts +3 -1
  166. package/scripts/coord-channel-watch.sh +2 -2
  167. package/scripts/discord-seat-probe.ts +3 -1
  168. package/scripts/land-global-checks.ts +89 -0
  169. package/scripts/lib/pch-regress-map.ts +24 -0
  170. package/scripts/morning-report.ts +18 -4
  171. package/scripts/pch-regress.ts +88 -0
  172. package/scripts/release-loop/gate-diff.ts +18 -0
  173. package/scripts/release-loop/gate-node.ts +628 -65
  174. package/scripts/release-loop/gate-partial.ts +106 -0
  175. package/scripts/release-loop/gate-timing.ts +229 -0
  176. package/scripts/release-loop/preflight.ts +6 -4
  177. package/scripts/release-loop/publish-window.ts +141 -0
  178. package/scripts/release-loop/release-docs.ts +1 -4
  179. package/scripts/release-loop/release-universe.ts +38 -0
  180. package/scripts/release-loop/resume-release.ts +67 -5
  181. package/scripts/release-loop/shard-plan.ts +65 -0
  182. package/scripts/release-loop/unattended-release.ts +12 -2
  183. package/scripts/release-story/draft.ts +5 -1
  184. package/scripts/retro/shadow-compare.ts +71 -0
  185. package/scripts/rhythm/daily.ts +6 -1
  186. package/scripts/video/scene-table.ts +159 -0
  187. package/src/acp/core-turn-bridge.ts +39 -2
  188. package/src/agent-mission/driver.ts +34 -6
  189. package/src/autopilot/build/llm-conflict-merge.ts +132 -7
  190. package/src/autopilot/build/merge-intent.ts +135 -7
  191. package/src/away/presence.ts +40 -0
  192. package/src/away/release-watch.ts +272 -0
  193. package/src/chat/index.ts +2 -1
  194. package/src/chat/short-question.ts +3 -40
  195. package/src/cli/away-cli.ts +68 -0
  196. package/src/cli/config-drift.ts +513 -0
  197. package/src/cli/guardian-cli.ts +49 -0
  198. package/src/cli/intake-cli.ts +13 -7
  199. package/src/cli/log-event-names.ts +5 -0
  200. package/src/cli/pod-cli.ts +5 -1
  201. package/src/cli/pr-cli.ts +20 -0
  202. package/src/cli/pty-drive-cli.ts +14 -3
  203. package/src/cli/release-cli.ts +52 -12
  204. package/src/cli/retro-cli.ts +78 -0
  205. package/src/cli/setup-cli.ts +31 -1
  206. package/src/cli/tasks-cli.ts +193 -14
  207. package/src/context-bus/context-now-public.ts +1 -1
  208. package/src/context-bus/context-now-surfaces.ts +16 -6
  209. package/src/context-bus/context-now.ts +97 -3
  210. package/src/core-turn/short-question.ts +100 -0
  211. package/src/dashboard/dashboard-status-lines.ts +24 -4
  212. package/src/dashboard/index.ts +50 -5
  213. package/src/dashboard/input/slash-executor.ts +6 -2
  214. package/src/dashboard/slash-runtime/dashboard-handlers.ts +68 -40
  215. package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
  216. package/src/dashboard/slash-runtime/help-from-registry.ts +62 -23
  217. package/src/dashboard/slash-runtime/help-groups.ts +44 -0
  218. package/src/dashboard/slash-runtime/model-choices.ts +125 -0
  219. package/src/domains/guardian-tick.ts +138 -0
  220. package/src/domains/outbound-alert.ts +44 -13
  221. package/src/domains/outbound-log-probe.ts +6 -1
  222. package/src/domains/schedule-registry.ts +23 -0
  223. package/src/domains/telegram-kind-route.ts +39 -0
  224. package/src/domains/watch-default-scheduler.ts +95 -0
  225. package/src/esc-abort-gate.ts +44 -0
  226. package/src/graph-runner/graph-run-api.ts +205 -0
  227. package/src/graph-runner/runner.ts +36 -6
  228. package/src/harness/harness-cli-command.ts +420 -51
  229. package/src/harness/harness-queue.ts +315 -25
  230. package/src/harness/harness-salvage-cli.ts +140 -0
  231. package/src/harness/harness-worktree-add.ts +68 -0
  232. package/src/harness/ledger-write-guard.ts +6 -16
  233. package/src/hq/fence-outcomes.ts +63 -0
  234. package/src/hq/fence-wrapper.ts +33 -0
  235. package/src/hq/hq.ts +17 -2
  236. package/src/index.ts +118 -6
  237. package/src/instance/test-write-guard-spy-first.fixture.ts +13 -0
  238. package/src/instance/test-write-guard.ts +200 -0
  239. package/src/intake-plane/digest.ts +39 -15
  240. package/src/intake-plane/harness-target.ts +4 -3
  241. package/src/intake-plane/intake-sources.ts +1 -0
  242. package/src/intake-plane/items.ts +1 -0
  243. package/src/loops/orchestrator/degradation.ts +57 -0
  244. package/src/loops/orchestrator/finish-rate.ts +33 -3
  245. package/src/loops/orchestrator/tick.ts +166 -45
  246. package/src/maturity/feature-maturity.ts +6 -4
  247. package/src/maturity/tui-slash-maturity.ts +1 -0
  248. package/src/mss/logging/log-store.ts +3 -0
  249. package/src/nexus/api/graph-kinds.ts +10 -4
  250. package/src/nexus/api/graphs-api.ts +178 -6
  251. package/src/nexus/api/http-server.ts +54 -8
  252. package/src/nexus/api/loop-edges.ts +292 -17
  253. package/src/nexus/api/ops-api.ts +112 -29
  254. package/src/nexus/api/ops-board-route.ts +48 -0
  255. package/src/nexus/index.ts +7 -0
  256. package/src/onboarding/entry-hints.ts +5 -0
  257. package/src/onboarding.ts +24 -6
  258. package/src/release-loop/checklist.ts +2 -0
  259. package/src/release-loop/feature-store.ts +68 -18
  260. package/src/release-loop/gate-shards.ts +158 -0
  261. package/src/release-loop/light-rc.ts +173 -0
  262. package/src/release-loop/placement.ts +51 -17
  263. package/src/release-loop/release-schedule.ts +6 -2
  264. package/src/release-loop/rubric.ts +57 -1
  265. package/src/retro/daily.ts +372 -0
  266. package/src/seat-loop/op-decide.ts +173 -0
  267. package/src/seat-loop/seat-loop.ts +60 -3
  268. package/src/seat-loop/seat-report.ts +58 -3
  269. package/src/self-dev/ask-launch-flow.ts +5 -4
  270. package/src/self-dev/ask-launch-io.ts +49 -1
  271. package/src/self-dev/ask-path-candidates.ts +40 -2
  272. package/src/self-dev/dev-cli.ts +36 -0
  273. package/src/self-dev/draft-sweep.ts +371 -16
  274. package/src/self-dev/draft-triage-rules.ts +19 -4
  275. package/src/self-dev/orchestrate.ts +1 -0
  276. package/src/self-dev/pr-preview.ts +247 -0
  277. package/src/self-dev/shard-run.ts +23 -7
  278. package/src/self-implement/blocked-draft-policy.ts +101 -0
  279. package/src/self-implement/execution-clarification.ts +11 -0
  280. package/src/self-implement/gate-cli.ts +29 -8
  281. package/src/self-implement/gate-env-retry.ts +83 -0
  282. package/src/self-implement/goal-author-clarification.ts +88 -13
  283. package/src/self-implement/goal-clarification-escalation.ts +2 -0
  284. package/src/self-implement/orchestrator.ts +220 -6
  285. package/src/self-implement/run-stop.ts +123 -0
  286. package/src/self-implement/run-ttl.ts +258 -0
  287. package/src/self-implement/salvage-branches.ts +254 -0
  288. package/src/self-implement/salvage-retention.ts +123 -0
  289. package/src/self-implement/seams.ts +4 -0
  290. package/src/self-implement/self-implement-cli.ts +2 -0
  291. package/src/self-implement/sibling-resync.ts +223 -0
  292. package/src/self-implement/stop-autoheal.ts +204 -0
  293. package/src/task-agent/actions.ts +63 -2
  294. package/src/task-agent/board.ts +132 -0
  295. package/src/task-agent/card-evidence.ts +292 -0
  296. package/src/task-agent/completion-evidence.ts +126 -0
  297. package/src/task-agent/judge.ts +25 -0
  298. package/src/task-agent/live-moves.ts +276 -0
  299. package/src/task-agent/mission.ts +33 -7
  300. package/src/task-agent/shadow.ts +192 -15
  301. package/src/task-agent/task-hand.ts +124 -17
  302. package/src/task-orchestrator/surfaces/pod-command-job.ts +82 -8
  303. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +4 -0
  304. package/src/task-orchestrator/surfaces/pod-install-slots.ts +89 -0
  305. package/src/task-orchestrator/surfaces/pod-lease.ts +233 -21
  306. package/src/task-orchestrator/surfaces/pod-pool.ts +155 -34
  307. package/src/task-orchestrator/surfaces/self-implement-pod.ts +21 -5
  308. package/src/telegram-away-command.ts +38 -0
  309. package/src/telegram-commands.ts +52 -0
  310. package/src/telegram-report-log-probe.ts +21 -0
  311. package/src/telegram-report.ts +27 -3
  312. package/src/testing/real-cli-spawn.ts +106 -0
  313. package/src/tui-client/dashboard-session.ts +1 -0
  314. package/src/user-config.ts +190 -5
  315. package/src/version/packed-revision.json +1 -1
  316. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
  324. package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/consult/page-a0eb02638b1e6f31.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/editor/page-c39554c321576982.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/loops/page-c130b63862437814.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-a3c58f5c72a5f3f9.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e8e1ecaea7cd71d2.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-44d5f767491692bf.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/term/page-61553851aa606146.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/today/page-a1f585e154e61c59.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/vault/page-5150ef17b5383a57.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +0 -1
  348. package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +0 -1
  349. /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → Bam1YziK2iH9bN4K0utGk}/_buildManifest.js +0 -0
  350. /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → Bam1YziK2iH9bN4K0utGk}/_ssgManifest.js +0 -0
@@ -1,15 +1,31 @@
1
- import { mkdtempSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
1
+ import { existsSync, mkdtempSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
2
2
  import { tmpdir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
  import { parse as parseYaml } from 'yaml';
5
5
  import { elanousStateRoot } from '../../autopilot/state-paths.js';
6
+ import { debug } from '../../debug/log.js';
6
7
  import { defaultGraphsDir, loadGraphTemplatesFrom } from '../../self-implement/graph-templates.js';
7
8
  import { loadGraphTemplates } from '../../self-implement/graph-yaml.js';
9
+ import { validateGraphYaml } from './graph-kinds.js';
8
10
  import { jsonResponse } from './json-response.js';
9
11
 
10
12
  /** Declared graph ids only. A user string is never joined onto a filesystem path. */
11
13
  const GRAPH_ID = /^[a-z0-9-]+$/;
12
14
 
15
+ /**
16
+ * Sidecar names a graph directory loads specially. `recipes.yaml` next to a graph is what
17
+ * the graph runner executes (`cmd:` recipes), so an HTTP write to `<mine>/recipes.yaml`
18
+ * would be a remote command path. These ids are never created, saved or reverted, and
19
+ * these files are never indexed as graphs.
20
+ */
21
+ export const RESERVED_GRAPH_IDS: ReadonlySet<string> = new Set(['recipes', 'overlay', 'overlays']);
22
+
23
+ function reserved(id: string): Response | null {
24
+ if (!RESERVED_GRAPH_IDS.has(id)) return null;
25
+ debug.log('nexus.graphs', 'rejected-reserved', { id });
26
+ return jsonResponse({ error: 'reserved-id', id }, 400);
27
+ }
28
+
13
29
  export interface GraphCatalogEntry {
14
30
  id: string;
15
31
  source: 'core' | 'mine';
@@ -45,6 +61,7 @@ function readDirYaml(dir: string): string[] {
45
61
  function indexByDeclaredId(dir: string): Map<string, { file: string; text: string }> {
46
62
  const found = new Map<string, { file: string; text: string }>();
47
63
  for (const file of readDirYaml(dir)) {
64
+ if (RESERVED_GRAPH_IDS.has(file.replace(/\.ya?ml$/, ''))) continue;
48
65
  let text: string;
49
66
  try { text = readFileSync(join(dir, file), 'utf8'); }
50
67
  catch { continue; }
@@ -125,6 +142,21 @@ function validateInTemp(text: string, expectedId: string): { ok: true } | { ok:
125
142
  }
126
143
  }
127
144
 
145
+ /** Same validator as POST /v1/graphs/validate. Invalid → 422; declared graph_id ≠ id → 400. Null = ok. */
146
+ function rejectGraphYaml(text: string, id: string, version?: string): Response | null {
147
+ const checked = validateGraphYaml('harness', text);
148
+ if (!checked.ok) {
149
+ debug.log('nexus.graphs', 'rejected-invalid', { id, ...(version ? { version } : {}), issues: checked.errors });
150
+ return jsonResponse({ error: 'invalid-graph', errors: checked.errors, ignoredKeys: checked.ignoredKeys }, 422);
151
+ }
152
+ if (checked.graphId !== id) {
153
+ const errors = [{ path: `${id}/graph_id`, message: 'graph_id 가 요청 id 와 다르다' }];
154
+ debug.log('nexus.graphs', 'rejected-invalid', { id, issues: errors });
155
+ return jsonResponse({ error: 'graph-id-mismatch', errors }, 400);
156
+ }
157
+ return null;
158
+ }
159
+
128
160
  /** Core graphs are read from the same YAML loader as the harness, never rewritten by this API. */
129
161
  export function handleGraphsGet(pathname: string, dir = defaultGraphsDir(), deps: GraphsApiDeps = {}): Response {
130
162
  const coreDir = deps.coreDir ?? dir;
@@ -168,6 +200,8 @@ export async function handleGraphsPut(pathname: string, req: Request, deps: Grap
168
200
  if (!match) return jsonResponse({ error: 'not-found' }, 404);
169
201
  const id = resolveId(match[1]!);
170
202
  if (!id) return jsonResponse({ error: 'bad_request', reason: 'invalid graph id' }, 400);
203
+ const reservedId = reserved(id);
204
+ if (reservedId) return reservedId;
171
205
  const coreDir = deps.coreDir ?? defaultGraphsDir();
172
206
  const mineDir = deps.mineDir ?? mineGraphsDir();
173
207
  const core = coreCatalog(coreDir);
@@ -178,12 +212,146 @@ export async function handleGraphsPut(pathname: string, req: Request, deps: Grap
178
212
  let body: { yaml?: unknown };
179
213
  try { body = await req.json() as { yaml?: unknown }; }
180
214
  catch { return jsonResponse({ error: 'bad_request', reason: 'json body required' }, 400); }
181
- if (typeof body.yaml !== 'string') return jsonResponse({ error: 'bad_request', reason: 'yaml string required' }, 400);
182
- const checked = validateInTemp(body.yaml, id);
183
- if (!checked.ok) return jsonResponse({ error: 'invalid-graph', errors: checked.errors }, 400);
215
+ if (typeof body?.yaml !== 'string') return jsonResponse({ error: 'bad_request', reason: 'yaml string required' }, 400);
216
+ const rejected = rejectGraphYaml(body.yaml, id);
217
+ if (rejected) return rejected;
184
218
  const existing = indexByDeclaredId(mineDir).get(id)!;
185
- writeFileSync(join(mineDir, existing.file), body.yaml);
186
- return jsonResponse({ id, source: 'mine', editable: true, saved: true });
219
+ // Writes go only to <mine>/<id>.yaml; a hand-placed file under another name is not edited over HTTP.
220
+ if (existing.file !== `${id}.yaml`) return jsonResponse({ error: 'file-name-mismatch', id, file: existing.file }, 409);
221
+ const { version, previous } = writeWithVersion(mineDir, id, existing, body.yaml);
222
+ debug.log('nexus.graphs', 'saved', { id, version, previous });
223
+ return jsonResponse({ id, source: 'mine', editable: true, saved: true, version, previous });
224
+ }
225
+
226
+ const VERSION_ID = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z(?:-\d{4})?$/;
227
+ export const GRAPH_VERSION_CAP = 20;
228
+
229
+ function versionsDir(mineDir: string, id: string): string {
230
+ return join(mineDir, '.versions', id);
231
+ }
232
+
233
+ /** Version ids are ISO timestamps (`:`/`.` → `-`), so name order is save order. */
234
+ export function listGraphVersions(mineDir: string, id: string): Array<{ version: string; savedAt: string; bytes: number }> {
235
+ const dir = versionsDir(mineDir, id);
236
+ // Only `.yaml` is ever written, read and reverted — a stray `.yml` is not a version.
237
+ return readDirYaml(dir)
238
+ .filter((file) => file.endsWith('.yaml'))
239
+ .map((file) => file.replace(/\.yaml$/, ''))
240
+ .filter((version) => VERSION_ID.test(version))
241
+ .sort()
242
+ .map((version) => {
243
+ const iso = /^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z/.exec(version);
244
+ let bytes = 0;
245
+ try { bytes = statSync(join(dir, `${version}.yaml`)).size; } catch { /* listed above; raced away */ }
246
+ return { version, savedAt: iso ? `${iso[1]}T${iso[2]}:${iso[3]}:${iso[4]}.${iso[5]}Z` : version, bytes };
247
+ });
248
+ }
249
+
250
+ function recordVersion(mineDir: string, id: string, text: string): string {
251
+ const dir = versionsDir(mineDir, id);
252
+ mkdirSync(dir, { recursive: true });
253
+ const base = new Date().toISOString().replace(/[:.]/g, '-');
254
+ let version = base;
255
+ for (let n = 1; existsSync(join(dir, `${version}.yaml`)); n += 1) version = `${base}-${String(n).padStart(4, '0')}`;
256
+ writeFileSync(join(dir, `${version}.yaml`), text);
257
+ const all = listGraphVersions(mineDir, id);
258
+ for (const old of all.slice(0, Math.max(0, all.length - GRAPH_VERSION_CAP))) {
259
+ rmSync(join(dir, `${old.version}.yaml`), { force: true });
260
+ }
261
+ return version;
262
+ }
263
+
264
+ /**
265
+ * Every saved state is a version. A graph with no history yet (e.g. a fresh clone)
266
+ * first records its current bytes, so `previous` always points at restorable bytes.
267
+ */
268
+ function writeWithVersion(mineDir: string, id: string, existing: { file: string; text: string }, text: string): { version: string; previous: string } {
269
+ const history = listGraphVersions(mineDir, id);
270
+ const previous = history.at(-1)?.version ?? recordVersion(mineDir, id, existing.text);
271
+ // Version first, then an atomic replace of the current file: a crash in between leaves
272
+ // the old current bytes intact (they are `previous`), never bytes that no version holds.
273
+ const version = recordVersion(mineDir, id, text);
274
+ const target = join(mineDir, `${id}.yaml`);
275
+ const temp = join(mineDir, `.${id}.yaml.${process.pid}.tmp`);
276
+ writeFileSync(temp, text);
277
+ renameSync(temp, target);
278
+ return { version, previous };
279
+ }
280
+
281
+ function mineOwnership(id: string, deps: GraphsApiDeps): { mineDir: string; existing: { file: string; text: string } } | Response {
282
+ const reservedId = reserved(id);
283
+ if (reservedId) return reservedId;
284
+ const coreDir = deps.coreDir ?? defaultGraphsDir();
285
+ const mineDir = deps.mineDir ?? mineGraphsDir();
286
+ const existing = indexByDeclaredId(mineDir).get(id);
287
+ if (!existing) {
288
+ const core = coreCatalog(coreDir);
289
+ if (core.ok && indexByDeclaredId(coreDir).has(id)) return jsonResponse({ error: 'core-read-only', id }, 403);
290
+ return jsonResponse({ error: 'not-found', id }, 404);
291
+ }
292
+ return { mineDir, existing };
293
+ }
294
+
295
+ /** GET /v1/graphs/<id>/versions — only «mine» graphs have history. */
296
+ export function handleGraphVersionsGet(pathname: string, deps: GraphsApiDeps = {}): Response {
297
+ const match = /^\/v1\/graphs\/([^/]+)\/versions$/.exec(pathname);
298
+ if (!match) return jsonResponse({ error: 'not-found' }, 404);
299
+ const id = resolveId(match[1]!);
300
+ if (!id) return jsonResponse({ error: 'bad_request', reason: 'invalid graph id' }, 400);
301
+ const owned = mineOwnership(id, deps);
302
+ if (owned instanceof Response) return owned;
303
+ return jsonResponse({ id, versions: listGraphVersions(owned.mineDir, id) });
304
+ }
305
+
306
+ /** POST /v1/graphs/<id>/revert {version} — restores that version; the restore is itself a new version. */
307
+ export async function handleGraphsRevert(pathname: string, req: Request, deps: GraphsApiDeps = {}): Promise<Response> {
308
+ const match = /^\/v1\/graphs\/([^/]+)\/revert$/.exec(pathname);
309
+ if (!match) return jsonResponse({ error: 'not-found' }, 404);
310
+ const id = resolveId(match[1]!);
311
+ if (!id) return jsonResponse({ error: 'bad_request', reason: 'invalid graph id' }, 400);
312
+ const owned = mineOwnership(id, deps);
313
+ if (owned instanceof Response) return owned;
314
+ if (owned.existing.file !== `${id}.yaml`) return jsonResponse({ error: 'file-name-mismatch', id, file: owned.existing.file }, 409);
315
+ let body: { version?: unknown };
316
+ try { body = await req.json() as { version?: unknown }; }
317
+ catch { return jsonResponse({ error: 'bad_request', reason: 'json body required' }, 400); }
318
+ if (typeof body?.version !== 'string' || !VERSION_ID.test(body.version)) {
319
+ return jsonResponse({ error: 'bad_request', reason: 'version string required' }, 400);
320
+ }
321
+ const target = body.version;
322
+ // Only a listed version id is ever joined onto a path.
323
+ if (!listGraphVersions(owned.mineDir, id).some((entry) => entry.version === target)) {
324
+ return jsonResponse({ error: 'version-not-found', id, version: target }, 404);
325
+ }
326
+ const text = readFileSync(join(versionsDir(owned.mineDir, id), `${target}.yaml`), 'utf8');
327
+ const rejected = rejectGraphYaml(text, id, target);
328
+ if (rejected) return rejected;
329
+ const { version, previous } = writeWithVersion(owned.mineDir, id, owned.existing, text);
330
+ debug.log('nexus.graphs', 'reverted', { id, version, previous, restoredFrom: target });
331
+ return jsonResponse({ id, source: 'mine', editable: true, reverted: true, version, previous, restoredFrom: target });
332
+ }
333
+
334
+ /** POST /v1/graphs {id, yaml} — a brand-new «mine» graph (blank canvas → save). Records version v1. */
335
+ export async function handleGraphsCreate(req: Request, deps: GraphsApiDeps = {}): Promise<Response> {
336
+ let body: { id?: unknown; yaml?: unknown };
337
+ try { body = await req.json() as { id?: unknown; yaml?: unknown }; }
338
+ catch { return jsonResponse({ error: 'bad_request', reason: 'json body required' }, 400); }
339
+ const id = typeof body?.id === 'string' ? resolveId(body.id) : null;
340
+ if (!id) return jsonResponse({ error: 'bad_request', reason: 'id must match [a-z0-9-]' }, 400);
341
+ const reservedId = reserved(id);
342
+ if (reservedId) return reservedId;
343
+ if (typeof body.yaml !== 'string') return jsonResponse({ error: 'bad_request', reason: 'yaml string required' }, 400);
344
+ const coreDir = deps.coreDir ?? defaultGraphsDir();
345
+ const mineDir = deps.mineDir ?? mineGraphsDir();
346
+ if (indexByDeclaredId(coreDir).has(id)) return jsonResponse({ error: 'core-read-only', id }, 403);
347
+ if (indexByDeclaredId(mineDir).has(id) || existsSync(join(mineDir, `${id}.yaml`))) return jsonResponse({ error: 'exists', id }, 409);
348
+ const rejected = rejectGraphYaml(body.yaml, id);
349
+ if (rejected) return rejected;
350
+ mkdirSync(mineDir, { recursive: true });
351
+ writeFileSync(join(mineDir, `${id}.yaml`), body.yaml, { flag: 'wx' });
352
+ const version = recordVersion(mineDir, id, body.yaml);
353
+ debug.log('nexus.graphs', 'saved', { id, version, created: true });
354
+ return jsonResponse({ id, source: 'mine', editable: true, saved: true, version, previous: null }, 201);
187
355
  }
188
356
 
189
357
  /** Copy core bytes into «my graphs», changing only the declared graph_id. */
@@ -198,6 +366,8 @@ export async function handleGraphsClone(pathname: string, req: Request, deps: Gr
198
366
  if (typeof body.newId !== 'string' || !GRAPH_ID.test(body.newId) || body.newId === id) {
199
367
  return jsonResponse({ error: 'bad_request', reason: 'newId must match [a-z0-9-]' }, 400);
200
368
  }
369
+ const reservedId = reserved(body.newId);
370
+ if (reservedId) return reservedId;
201
371
  const coreDir = deps.coreDir ?? defaultGraphsDir();
202
372
  const mineDir = deps.mineDir ?? mineGraphsDir();
203
373
  const source = indexByDeclaredId(coreDir).get(id) ?? indexByDeclaredId(mineDir).get(id);
@@ -215,7 +385,9 @@ export async function handleGraphsClone(pathname: string, req: Request, deps: Gr
215
385
  }
216
386
 
217
387
  export async function handleGraphsMutation(pathname: string, req: Request, deps: GraphsApiDeps = {}): Promise<Response | null> {
388
+ if (req.method === 'POST' && pathname === '/v1/graphs') return handleGraphsCreate(req, deps);
218
389
  if (req.method === 'PUT' && /^\/v1\/graphs\/[^/]+\/yaml$/.test(pathname)) return handleGraphsPut(pathname, req, deps);
219
390
  if (req.method === 'POST' && /^\/v1\/graphs\/[^/]+\/clone$/.test(pathname)) return handleGraphsClone(pathname, req, deps);
391
+ if (req.method === 'POST' && /^\/v1\/graphs\/[^/]+\/revert$/.test(pathname)) return handleGraphsRevert(pathname, req, deps);
220
392
  return null;
221
393
  }
@@ -24,6 +24,7 @@ import { createProject, listProjects } from '../../project/project-store.js';
24
24
  import { handleOutputsGet, type OutputsDeps } from './outputs-route.js';
25
25
  import { buildGridData, type GridDeps } from './grid.js';
26
26
  import { DECISIONS_PATH, handleDecisions, type DecisionsRouteDeps } from './decisions-route.js';
27
+ import { OPS_BOARD_PATH, handleOpsBoard, type OpsBoardDeps } from './ops-board-route.js';
27
28
  import { CARD_FOLLOWUP_PATH, createCardFollowupJobs, type CardFollowupJobs } from './card-followup-route.js';
28
29
  import { SKILL_EXEC_PATH, createSkillExecJobs, type SkillExecJobs } from './skill-exec-route.js';
29
30
  import { compareTokenConstTime } from '../../acp/transport/auth.js';
@@ -166,7 +167,8 @@ import { handleHarnessAskPost, handleHarnessAskStatusGet, handleHarnessRunEvents
166
167
  import { handleLoopEdgesGet } from './loop-edges.js';
167
168
  import { handleFabricPlans, type FabricPlansRouteOpts } from './fabric-plans.js';
168
169
  import { dispatchPersonaRoute } from './personas.js';
169
- import { handleMe } from './operator.js';
170
+ import { handleMe, operatorSignal } from './operator.js';
171
+ import { handleGraphRunRoute } from '../../graph-runner/graph-run-api.js';
170
172
  import { handleOpsApi } from './ops-api.js';
171
173
  import { handleRoleJudge } from './role-judge.js';
172
174
  import { handleAudioStt } from './audio-stt.js';
@@ -363,7 +365,7 @@ import {
363
365
  } from './workflows.js';
364
366
  import { handleTriggersSnapshot } from './triggers.js';
365
367
  import { handleWorkflowTemplatesList } from './workflow-templates.js';
366
- import { handleGraphsGet, handleGraphsMutation } from './graphs-api.js';
368
+ import { handleGraphVersionsGet, handleGraphsGet, handleGraphsMutation } from './graphs-api.js';
367
369
  import { handleGraphKindsGet, handleGraphsValidatePost } from './graph-kinds.js';
368
370
  import { syncInstalledPluginNodes } from '../../graph-kinds/installed-plugin-nodes.js';
369
371
  import { elanousStateRoot } from '../../autopilot/state-paths.js';
@@ -413,6 +415,8 @@ import { handleSelfEvent } from './self-event.js';
413
415
  /** 매매 «실행»은 애드온이다(대표 09-20 결정 ③ · 별도 상용 저장소 · release/trading-export.yaml) — 공개 코어엔 없다.
414
416
  * 있으면 그 경로를 켜고, 없으면 501. 경로를 변수로 둬 공개본 타입 검사가 «모듈 없음»으로 막히지 않게 한다. */
415
417
  const TRADE_PROPOSAL_MODULE: string = './trade-proposal.js';
418
+ /** PITCH-ROOM — 투자자 피치 방(덱·티저)은 비공개다(release/public-export.yaml 이 pitch-api 를 뺀다). 공개본엔 모듈이 없어 404. */
419
+ const PITCH_MODULE: string = './pitch-api.js';
416
420
  import {
417
421
  handleShowroomLayoutDelete,
418
422
  handleShowroomLayoutGet,
@@ -482,6 +486,7 @@ export interface NexusHttpServerOpts {
482
486
  /** Read-only grid measurement seam; omitted in production for live HQ and Pod pool reads. */
483
487
  grid?: GridDeps;
484
488
  decisions?: Pick<DecisionsRouteDeps, 'ledger'>;
489
+ opsBoard?: Pick<OpsBoardDeps, 'openStore'>;
485
490
  cardFollowup?: CardFollowupJobs;
486
491
  skillExec?: SkillExecJobs;
487
492
  execRequests?: ExecRequestRunner;
@@ -926,6 +931,30 @@ export async function routeRequest(
926
931
  if (pathname === '/v1/ops' || pathname.startsWith('/v1/ops/')) {
927
932
  return handleOpsApi(req, opts.metaApi);
928
933
  }
934
+ // PITCH-ROOM — same operatorSignal gate, inside the (private-only) pitch module.
935
+ if (pathname.startsWith('/v1/pitch/')) {
936
+ let pitch: { handlePitchApi(req: Request, metaApi: typeof opts.metaApi): Response };
937
+ try {
938
+ pitch = await import(PITCH_MODULE) as typeof pitch;
939
+ } catch {
940
+ return jsonResponse({ error: 'not-found' }, 404);
941
+ }
942
+ return pitch.handlePitchApi(req, opts.metaApi);
943
+ }
944
+ // CGE-RUN — 편집기 «실행»은 운영자 전용이다: 사용자 그래프가 셸 노드를 부를 수 있으므로 같은 출처 무토큰 통과는 받지 않는다
945
+ // (운영자 프록시 헤더 · owner bearer 일치 · owner 가 발급한 짧은 임시 토큰만 — 명시 토큰은 언제나 필요).
946
+ if (/^\/v1\/graphs\/[^/]+\/(?:run|runs\/[^/]+)$/.test(pathname)) {
947
+ if (!opts.metaApi) return jsonResponse({ error: 'meta-api-not-wired' }, 503);
948
+ const signal = operatorSignal(req, opts.metaApi);
949
+ const credential = bearerCredential(req, opts.metaApi);
950
+ if (!signal.operator || (signal.operatorSource !== 'op-proxy' && credential !== 'bearer-match' && credential !== 'temp-token')) {
951
+ debug.log('graphs.run', 'refused', { path: pathname, source: signal.operatorSource });
952
+ return jsonResponse({ error: 'forbidden' }, 403);
953
+ }
954
+ const routed = handleGraphRunRoute(method, pathname);
955
+ if (routed) return routed;
956
+ return jsonResponse({ error: 'method-not-allowed' }, 405);
957
+ }
929
958
 
930
959
  // Default-deny for every `/v1/` path that is not on PUBLIC_ROUTES.
931
960
  // Reuses checkAuth (same-origin exemption + constant-time bearer).
@@ -1299,12 +1328,11 @@ export async function routeRequest(
1299
1328
  return handleDevices(req, opts.devices);
1300
1329
  }
1301
1330
 
1331
+ // Same owner auth as the neighbouring GETs (short-lived owner tokens · same-origin PWA); #23932 compared the admin
1332
+ // bearer only, so a PWA on a short-lived token got 401 and the loop map stayed empty.
1302
1333
  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
- }
1334
+ if (!opts.metaApi) return jsonResponse({ error: 'meta-api-runtime-not-wired' }, 503);
1335
+ if (!checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
1308
1336
  return handleLoopEdgesGet(req);
1309
1337
  }
1310
1338
 
@@ -1381,6 +1409,21 @@ export async function routeRequest(
1381
1409
  return (opts.cardFollowup ?? defaultCardFollowupJobs()).get(pathname.slice(CARD_FOLLOWUP_PATH.length + 1));
1382
1410
  }
1383
1411
 
1412
+ if (pathname === OPS_BOARD_PATH && (method === 'GET' || method === 'POST')) {
1413
+ const peerAddress = (server as BunServerLike & { requestIP?: (request: Request) => { address: string } | null }).requestIP?.(req)?.address;
1414
+ const host = req.headers.get('host') ?? new URL(req.url).host;
1415
+ const tailnetPeer = peerAddress !== undefined && (/^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./.test(peerAddress)
1416
+ || /^fd7a:115c:a1e0:/i.test(peerAddress));
1417
+ const localPeer = peerAddress === '127.0.0.1' || peerAddress === '::1';
1418
+ const tailnetHost = /^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\.\d{1,3}\.\d{1,3}(?::\d+)?$/.test(host)
1419
+ || /^\[fd7a:115c:a1e0:[0-9a-f:]+\](?::\d+)?$/i.test(host) || /^[a-z0-9-]+(?:\.[a-z0-9-]+)*\.ts\.net(?::\d+)?$/i.test(host);
1420
+ const localHost = /^(?:localhost|127\.0\.0\.1)(?::\d+)?$/.test(host) || /^\[::1\](?::\d+)?$/.test(host);
1421
+ if ((!tailnetPeer && !localPeer) || (!tailnetHost && !localHost)) return jsonResponse({ error: 'forbidden' }, 403);
1422
+ return handleOpsBoard(req, {
1423
+ authorize: request => !!opts.metaApi && opts.metaApi.noAuth !== true && checkAuth(request, opts.metaApi),
1424
+ ...opts.opsBoard,
1425
+ });
1426
+ }
1384
1427
  if (method === 'GET' && pathname === DECISIONS_PATH) {
1385
1428
  return handleDecisions(req, {
1386
1429
  authorize: request => !!opts.metaApi && checkAuth(request, opts.metaApi),
@@ -1460,7 +1503,7 @@ export async function routeRequest(
1460
1503
  if (!opts.metaApi) return jsonResponse({ error: 'unauthorized' }, 401);
1461
1504
  return handlePluginsRemove(req, pluginRemove[1]!, opts.metaApi, opts.pluginStateRoot);
1462
1505
  }
1463
- if ((method === 'PUT' || method === 'POST') && pathname.startsWith('/v1/graphs/')) {
1506
+ if ((method === 'PUT' || method === 'POST') && (pathname.startsWith('/v1/graphs/') || pathname === '/v1/graphs')) {
1464
1507
  if (!opts.metaApi) return jsonResponse({ error: 'meta-api-not-wired' }, 503);
1465
1508
  if (!checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
1466
1509
  const mutated = await handleGraphsMutation(pathname, req);
@@ -2774,6 +2817,9 @@ export async function routeRequest(
2774
2817
  if (method === 'GET' && (pathname === '/v1/graphs' || /^\/v1\/graphs\/[^/]+$/.test(pathname) || /^\/v1\/graphs\/[^/]+\/yaml$/.test(pathname))) {
2775
2818
  return handleGraphsGet(pathname);
2776
2819
  }
2820
+ if (method === 'GET' && /^\/v1\/graphs\/[^/]+\/versions$/.test(pathname)) {
2821
+ return handleGraphVersionsGet(pathname);
2822
+ }
2777
2823
  // Archon-port T2.3 (2026-05-08) — workflow GET surface.
2778
2824
  // (POST /validate · POST /:name/run · PUT/DELETE /:name landed
2779
2825
  // inside the mutation block above.)