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,11 +1,12 @@
1
1
  import { withFileLockSync } from '../../storage/file-lock.js';
2
- import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
2
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
3
3
  import { dirname, join } from 'node:path';
4
4
  import { CardStore, taskCardsDir, type TaskCard } from '../../task-cards/card-store.js';
5
5
  import { Database } from 'bun:sqlite';
6
6
  import { type ChecklistItem } from '../../release-loop/checklist.js';
7
7
  import { effectiveInstanceRoot } from '../../instance/resolve.js';
8
8
  import { getUserConfig, type OrchestratorLoopConfig } from '../../user-config.js';
9
+ import { rubricPriority } from '../../release-loop/rubric.js';
9
10
  import { debug } from '../../debug/log.js';
10
11
  import { listSelfDevRuns, runSummaryLine, selfDevRunsDir } from '../../self-dev/run-store.js';
11
12
  import { planAction, runSeatLoopOnce, type SeatDeps, type SeatItem } from '../../seat-loop/seat-loop.js';
@@ -19,12 +20,19 @@ export type Window = '08' | '12' | '18';
19
20
  export type Node = 'intake' | 'split' | 'place' | 'delegate' | 'launch' | 'reconcile' | 'report';
20
21
  export type Mode = 'off' | 'shadow' | 'live';
21
22
  const SEAT_LOOP_IDS: Readonly<Record<string, string>> = { OP: 'op-seat', MK: 'cmo-seat', TC: 'tc-seat', UX: 'ux-seat' };
22
- export interface Cell { id: string; title: string; kind?: string; seat?: string; host?: string; gridHost?: boolean; version?: string; cardId: string; origin: 'candidate' | 'flow1a'; priority?: 'P0' | 'P1' | 'P2'; predecessors?: string[] }
23
+ export interface Cell { id: string; title: string; kind?: string; seat?: string; host?: string; gridHost?: boolean; version?: string; cardId: string; origin: 'candidate' | 'flow1a'; priority?: 'P0' | 'P1' | 'P2' | 'P3' | 'P4'; predecessors?: string[] }
23
24
  export interface TickState {
24
25
  runId: string; window: Window; mode: Mode; day: string;
25
26
  cards: TaskCard[]; skippedCards: Array<{ id: string; reason: string }>; cells: Cell[]; placed: number; unplaced?: number; delegated: number; wouldDelegate: number; rebalanced?: number; rebalanceBlocked?: Array<{ id: string; reason: string }>;
26
27
  reconciled: { reached: number; progressing: number; blocked: number; unknown: number };
27
28
  runSummaries?: string[];
29
+ degradation?: { level: 1 | 2; reason: string };
30
+ /** The highest degradation actually applied in this run — the report keeps it even if the signal recovers before report. */
31
+ degradationPeak?: { level: 1 | 2; reason: string };
32
+ deferredCells?: Cell[];
33
+ /** place ran at L2 (skipped) in this run — its unplaced cells are carried even if the board is back by delegate. */
34
+ placeDegraded?: boolean;
35
+ delegatedKeys?: string[];
28
36
  launched?: Array<{ seat: string; status: string; key?: string; queueId?: string; runId?: string }>;
29
37
  /** ORCH-TA-HAND — cells handed to TASK-AGENT this run (card id · mode · launched). */
30
38
  handed?: Array<{ key: string; cell: string; cardId: string; mode: 'shadow' | 'live'; launched: boolean }>;
@@ -42,6 +50,7 @@ export type RebalanceAdapter = (version: string) => Promise<{ decisions: unknown
42
50
  export interface TickDeps {
43
51
  root?: string; now?: Date; mode?: Mode; runId?: string; window?: Window;
44
52
  cards?: () => TaskCard[]; split?: SplitAdapter; placeCell?: PlaceAdapter;
53
+ degradation?: () => { llm: boolean; board: boolean };
45
54
  gridHosts?: readonly GridHost[];
46
55
  seatTurn?: (seat: string, root: string, host?: string, options?: Pick<SeatDeps, 'enqueue'>) => Promise<{ status: string; queueId?: string; runId?: string; item?: { source: string; id: string } }>;
47
56
  queueOutcome?: (queueId: string, root: string) => 'pending' | 'unknown' | 'retryable' | 'succeeded';
@@ -132,7 +141,9 @@ export function reportLine(state: TickState): string {
132
141
  const r = state.reconciled;
133
142
  const line = `orchestrator ${state.window} cards=${state.cards.length} cells=${state.cells.length} placed=${state.missing.includes('relplan1-absent') ? 'missing' : state.placed} delegated=${state.mode === 'shadow' ? `would ${state.wouldDelegate}` : state.delegated} reconciled=${r.reached}/${r.progressing}/${r.blocked}/${r.unknown} mode=${state.mode}`;
134
143
  const details = [...(state.rebalanceBlocked ?? []).map(row => `⛔ 이월 거부 ${row.id}: ${row.reason}`), ...(state.runSummaries ?? [])];
135
- return details.length ? `${line}\n${details.join('\n')}` : line;
144
+ const body = details.length ? `${line}\n${details.join('\n')}` : line;
145
+ const applied = state.degradationPeak ?? state.degradation;
146
+ return applied ? `강등 L${applied.level}: ${applied.reason}\n${body}` : body;
136
147
  }
137
148
  function readCardSnapshot(root: string): { cards: TaskCard[]; skippedCards: TickState['skippedCards'] } {
138
149
  const dir = taskCardsDir(root);
@@ -321,6 +332,22 @@ function restorePrevious(root: string, day: string, mode: Mode): Pick<TickState,
321
332
  }
322
333
  return { cards: [...cards.values()], cells: [...cells.values()], launched: [...launched.values()] };
323
334
  }
335
+ function previousDelegations(root: string, day: string, mode: Mode, runId: string): { deferred: Cell[]; keys: string[] } {
336
+ const dir = join(root, 'loop', 'orchestrator');
337
+ if (!existsSync(dir)) return { deferred: [], keys: [] };
338
+ const states = readdirSync(dir).filter(name => /^[a-zA-Z0-9._-]+\.json$/.test(name))
339
+ .flatMap(name => {
340
+ try {
341
+ const path = join(dir, name);
342
+ const state = JSON.parse(readFileSync(path, 'utf8')) as TickState;
343
+ return state.runId !== runId && state.day <= day && state.mode === mode && state.window === '08' &&
344
+ state.nodes?.delegate === 'ok' && Array.isArray(state.deferredCells)
345
+ ? [{ name, state, mtime: statSync(path).mtimeMs }] : [];
346
+ } catch { return []; }
347
+ });
348
+ states.sort((a, b) => a.state.day.localeCompare(b.state.day) || a.mtime - b.mtime || a.name.localeCompare(b.name));
349
+ return { deferred: states.at(-1)?.state.deferredCells ?? [], keys: states.flatMap(({ state }) => Array.isArray(state.delegatedKeys) ? state.delegatedKeys : []) };
350
+ }
324
351
  function initial(runId: string, window: Window, mode: Mode, day: string): TickState {
325
352
  return { runId, window, mode, day, cards: [], skippedCards: [], cells: [], placed: 0, delegated: 0, wouldDelegate: 0,
326
353
  reconciled: { reached: 0, progressing: 0, blocked: 0, unknown: 0 }, steps: [], nodes: {}, missing: [] };
@@ -349,11 +376,20 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
349
376
  const root = deps.root ?? effectiveInstanceRoot();
350
377
  const runId = deps.runId ?? graphRunId() ?? `${kstDay(now)}-${window}`;
351
378
  const path = stateFile(root, runId);
352
- const configured = deps.mode ?? getUserConfig().loops?.orchestrator?.mode;
379
+ const orchestratorConfig = getUserConfig().loops?.orchestrator;
380
+ const configured = deps.mode ?? orchestratorConfig?.mode;
353
381
  const mode: Mode = configured === 'off' || configured === 'live' ? configured : 'shadow';
354
382
  const state: TickState = existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) as TickState : initial(runId, window, mode, kstDay(now));
355
383
  state.steps ??= [];
356
384
  if (state.window !== window || state.mode !== mode) throw new Error('orchestrator run window/mode changed');
385
+ if (deps.degradation) {
386
+ const signal = deps.degradation();
387
+ state.degradation = !signal.board ? { level: 2, reason: signal.llm ? 'board 불가' : 'LLM·board 불가' }
388
+ : !signal.llm ? { level: 1, reason: 'LLM 불가' } : undefined;
389
+ // Only nodes that act on the degradation (split · place · delegate · launch) count toward the reported peak.
390
+ const applies = (['split', 'place', 'delegate', 'launch'] as Node[]).includes(node) && activeNode(node, window);
391
+ if (applies && state.degradation && (!state.degradationPeak || state.degradation.level >= state.degradationPeak.level)) state.degradationPeak = state.degradation;
392
+ }
357
393
  const predecessor: Partial<Record<Node, Node>> = { split: 'intake', place: 'split', delegate: 'place', launch: 'delegate', reconcile: 'launch', report: 'reconcile' };
358
394
  const preceding = predecessor[node];
359
395
  if (preceding && !state.nodes[preceding]) throw new Error(`orchestrator ${node}: missing ${preceding} result`);
@@ -380,14 +416,17 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
380
416
  observe('exchange', 'wish-cards', state.cards.length);
381
417
  } else if (node === 'split') {
382
418
  // Shadow also asks the splitter (it only proposes cells); only live records `orch:split` on the card.
383
- const split = deps.split ?? await adapter<SplitAdapter>('../../flow-loop/split.js', 'splitCard');
384
- if (!split) { state.missing.push('flow1a-absent'); observe('node-missing', 'flow1a-absent', state.cards.length); }
419
+ const ruleOnly = (state.degradation?.level ?? 0) >= 1;
420
+ const split = ruleOnly ? undefined : deps.split ?? await adapter<SplitAdapter>('../../flow-loop/split.js', 'splitCard');
421
+ if (!split && !ruleOnly) { state.missing.push('flow1a-absent'); observe('node-missing', 'flow1a-absent', state.cards.length); }
385
422
  state.cells = [];
386
423
  for (const card of state.cards) {
387
- const cells = split ? await split(card, { shadow: mode === 'shadow' }) : [{ id: card.id, title: card.title }];
424
+ const cells = ruleOnly ? [{ id: card.id, title: card.title }] : split ? await split(card, { shadow: mode === 'shadow' }) : [{ id: card.id, title: card.title }];
388
425
  for (const cell of cells) {
389
426
  if (!cell.id?.trim() || !cell.title?.trim()) throw new Error('invalid split cell');
390
- state.cells.push({ cardId: card.id, id: cell.id, title: cell.title, origin: split ? 'flow1a' : 'candidate', ...(cell.kind !== undefined ? { kind: cell.kind } : {}), ...(cell.seat ? { seat: cell.seat } : {}), ...(cell.host !== undefined ? { host: cell.host } : {}) });
427
+ state.cells.push({ cardId: card.id, id: cell.id, title: cell.title, origin: split ? 'flow1a' : 'candidate',
428
+ ...(ruleOnly ? { priority: rubricPriority(card.sections.map(section => section.content).join('\n')) } : {}),
429
+ ...(cell.kind !== undefined ? { kind: cell.kind } : {}), ...(cell.seat ? { seat: cell.seat } : {}), ...(cell.host !== undefined ? { host: cell.host } : {}) });
391
430
  }
392
431
  observe('exchange', `split:${card.id}`, cells.length);
393
432
  if (split && cells.length && mode === 'live') {
@@ -398,33 +437,54 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
398
437
  }
399
438
  }
400
439
  } else if (node === 'place') {
401
- const gridHosts = resolveGridHosts(deps, observe);
402
- refreshGridHosts(state.cells, gridHosts, mode, root, observe);
403
- if (mode === 'live') await syncQueuedPoolHints(root, state.launched ?? [], deps, observe);
404
- const place = deps.placeCell ?? await adapter<PlaceAdapter>('../../release-loop/placement.js', 'placeCell');
405
- const available = typeof place === 'function';
406
- if (!available) { state.missing.push('relplan1-absent'); observe('node-missing', 'relplan1-absent', state.cells.length); }
407
- if (mode === 'shadow' && deps.placeCell) observe('exchange', 'relplan1-shadow-not-invoked', state.cells.length);
408
- // 담당 없는 칸은 배치를 부르든 안 부르든 센다 — 배치 호출 여부와 집계를 묶지 않는다.
409
- for (const cell of state.cells) {
410
- if (cell.origin === 'flow1a' && (!cell.seat || !SEAT_LOOP_IDS[cell.seat])) { state.unplaced = (state.unplaced ?? 0) + 1; observe('exchange', 'unplaced-no-owner', 1); }
440
+ // Carry unfinished assignments into the same place/delegate path before applying this tick's cap.
441
+ const carry = previousDelegations(root, state.day, mode, runId).deferred;
442
+ if (carry.length) {
443
+ // Carried cells keep their place ahead of this tick's new cells (deduplicated) — a cell deferred by the cap is not
444
+ // pushed back again by a newly split cell.
445
+ const carried = new Set(carry.map(cell => `orch:${cell.cardId}:${cell.id}`));
446
+ state.cells = [...carry, ...state.cells.filter(cell => !carried.has(`orch:${cell.cardId}:${cell.id}`))];
411
447
  }
412
- if (available && (mode === 'live' || !deps.placeCell)) for (const cell of state.cells) {
413
- if (cell.origin !== 'flow1a' || !cell.seat || !SEAT_LOOP_IDS[cell.seat]) continue;
414
- // RELPLAN's dryRun calculates the same release without writing a checklist in shadow.
415
- try {
416
- const placed = await place({ id: cell.id, title: cell.title, owner: cell.seat, priority: cell.priority ?? 'P2', predecessors: cell.predecessors ?? [] }, { dryRun: mode === 'shadow', now });
417
- if (validVersion(placed?.version)) {
418
- cell.version = placed.version;
419
- state.placed++;
420
- observe('exchange', mode === 'shadow' ? `would-place:${cell.id}:${cell.version}` : `placed:${cell.id}:${cell.version}`, 1);
421
- } else { state.unplaced = (state.unplaced ?? 0) + 1; observe('exchange', 'unplaced-no-release', 1); }
422
- } catch (error) {
423
- state.unplaced = (state.unplaced ?? 0) + 1;
424
- observe('exchange', `unplaced:${cell.id}:${String(error).slice(0, 160)}`, 1);
448
+ if (state.degradation?.level === 2) {
449
+ state.placeDegraded = true;
450
+ observe('degraded-skip', 'place', 0);
451
+ } else {
452
+ const gridHosts = resolveGridHosts(deps, observe);
453
+ refreshGridHosts(state.cells, gridHosts, mode, root, observe);
454
+ if (mode === 'live') await syncQueuedPoolHints(root, state.launched ?? [], deps, observe);
455
+ const place = deps.placeCell ?? await adapter<PlaceAdapter>('../../release-loop/placement.js', 'placeCell');
456
+ const available = typeof place === 'function';
457
+ if (!available) { state.missing.push('relplan1-absent'); observe('node-missing', 'relplan1-absent', state.cells.length); }
458
+ if (mode === 'shadow' && deps.placeCell) observe('exchange', 'relplan1-shadow-not-invoked', state.cells.length);
459
+ // 담당 없는 칸은 배치를 부르든 안 부르든 센다 — 배치 호출 여부와 집계를 묶지 않는다.
460
+ for (const cell of state.cells) {
461
+ if (cell.origin === 'flow1a' && (!cell.seat || !SEAT_LOOP_IDS[cell.seat])) { state.unplaced = (state.unplaced ?? 0) + 1; observe('exchange', 'unplaced-no-owner', 1); }
462
+ }
463
+ if (available && (mode === 'live' || !deps.placeCell)) for (const cell of state.cells) {
464
+ if (cell.origin !== 'flow1a' || !cell.seat || !SEAT_LOOP_IDS[cell.seat]) continue;
465
+ // RELPLAN's dryRun calculates the same release without writing a checklist in shadow.
466
+ try {
467
+ const priority = cell.priority === 'P3' || cell.priority === 'P4' ? 'P2' : cell.priority ?? 'P2';
468
+ const placed = await place({ id: cell.id, title: cell.title, owner: cell.seat, priority, predecessors: cell.predecessors ?? [] }, { dryRun: mode === 'shadow', now });
469
+ if (validVersion(placed?.version)) {
470
+ cell.version = placed.version;
471
+ state.placed++;
472
+ observe('exchange', mode === 'shadow' ? `would-place:${cell.id}:${cell.version}` : `placed:${cell.id}:${cell.version}`, 1);
473
+ } else { state.unplaced = (state.unplaced ?? 0) + 1; observe('exchange', 'unplaced-no-release', 1); }
474
+ } catch (error) {
475
+ state.unplaced = (state.unplaced ?? 0) + 1;
476
+ observe('exchange', `unplaced:${cell.id}:${String(error).slice(0, 160)}`, 1);
477
+ }
425
478
  }
426
479
  }
427
480
  } else if (node === 'delegate') {
481
+ if (state.degradation?.level === 2) observe('degraded-skip', 'delegate', 0);
482
+ const cap = orchestratorConfig?.maxDelegatePerTick;
483
+ const previous = previousDelegations(root, state.day, mode, runId);
484
+ if (cap !== undefined || state.degradation?.level === 2 || state.placeDegraded || previous.deferred.length) {
485
+ state.deferredCells = [];
486
+ state.delegatedKeys = [];
487
+ }
428
488
  const path = journal(root);
429
489
  // 모드와 칸 필터는 따로 푼다 — 주입으로 모드만 줘도 설정의 칸 필터는 그대로 먹는다.
430
490
  const handConfig: Partial<OrchestratorLoopConfig> = deps.handToTaskAgent !== undefined && deps.handToTaskAgentCell !== undefined ? {} : getUserConfig().loops?.orchestrator ?? {};
@@ -432,7 +492,9 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
432
492
  // ⭐ 넘김 claim 과 자리 저널 기록은 «같은 잠금»(자리 저널 잠금) 아래에서 서로를 다시 읽는다 — 동시 틱이 낡은 값을 보지 않게.
433
493
  const ledger = handedLedger(root);
434
494
  const liveHanded = handedKeys(ledger);
435
- const keys = new Set(readRequests(root).map(row => row.key));
495
+ // Earlier runs' (would-)assignments only count while a cap is configured — they are what the cap carried over.
496
+ // With no cap the tick behaves as before: shadow would-delegates every cell again, live dedups on the seat journal.
497
+ const keys = new Set([...readRequests(root).map(row => row.key), ...(cap !== undefined ? previous.keys : [])]);
436
498
  // 넘김 뒤 상태 저장 전에 죽은 같은 run 이 다시 돌면 원장에서 handed[] 를 되살린다.
437
499
  const ledgerHanded = handedRows(ledger);
438
500
  const restoreHanded = (handKey: string, key: string, rows: Map<string, HandedRow> = handedRows(ledger)): void => {
@@ -441,6 +503,26 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
441
503
  if (!row || row.runId !== runId || (state.handed ?? []).some(entry => entry.key === key && entry.mode === row.mode)) return;
442
504
  (state.handed ??= []).push({ key, cell: row.cell, cardId: row.cardId, mode: row.mode, launched: row.launched });
443
505
  };
506
+ // A retried delegate (died after assigning, before saving state) rebuilds this run's cap usage from what it already
507
+ // wrote: seat rows tagged with this run and live hands claimed by this run.
508
+ if (cap !== undefined && state.delegatedKeys) {
509
+ const used = new Set(state.delegatedKeys);
510
+ if (existsSync(path)) for (const line of readFileSync(path, 'utf8').split('\n')) {
511
+ if (!line) continue;
512
+ let row: { key?: unknown; source?: unknown; orchRunId?: unknown };
513
+ try { row = JSON.parse(line) as typeof row; } catch { throw new Error('invalid seat request journal'); }
514
+ if (row.source === 'orchestrator' && row.orchRunId === runId && typeof row.key === 'string') used.add(row.key);
515
+ }
516
+ for (const row of handedRows(ledger).values()) if (row.runId === runId && row.mode === 'live') used.add(row.key);
517
+ if (existsSync(ledger)) for (const line of readFileSync(ledger, 'utf8').split('\n')) {
518
+ if (!line) continue;
519
+ try {
520
+ const claim = JSON.parse(line) as { key?: unknown; runId?: unknown; mode?: unknown; status?: unknown };
521
+ if (claim.runId === runId && claim.mode === 'live' && claim.status === 'claimed' && typeof claim.key === 'string') used.add(claim.key);
522
+ } catch { /* handedKeys already rejects a broken ledger */ }
523
+ }
524
+ state.delegatedKeys = [...used];
525
+ }
444
526
  const journalHas = (key: string): boolean => {
445
527
  if (!existsSync(path)) return false;
446
528
  return readFileSync(path, 'utf8').split('\n').some(line => {
@@ -458,13 +540,24 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
458
540
  restoreHanded(`shadow:${key}`, key, ledgerHanded);
459
541
  if (keys.has(key)) continue;
460
542
  if (liveHanded.has(key)) { keys.add(key); observe('exchange', `handed-to-task-agent:${key}`, 1, 'task-agent'); continue; }
543
+ // COORD-HA ②: a live tick skips unplaced cells for assignment; L2 assigns nothing new (live hands included) and the
544
+ // per-tick cap defers the rest to the next tick — both before the TASK-AGENT hand so a hand never bypasses them.
545
+ const unplacedLive = mode === 'live' && !validVersion(cell.version);
546
+ // L2 carries every cell — placed or not — to the next tick: split already marked the card, so a cell skipped here
547
+ // would never be split again.
548
+ if (state.degradation?.level === 2 || (unplacedLive && state.placeDegraded)) {
549
+ state.deferredCells?.push(cell);
550
+ continue;
551
+ }
552
+ // The cap limits assignments (a live hand or a seat row); a shadow card is not an assignment and is still written.
553
+ const capped = !unplacedLive && cap !== undefined && state.delegatedKeys!.length >= cap;
461
554
  // ORCH-TA-HAND: hand the picked cell to TASK-AGENT. shadow = card only (the seat path below is unchanged);
462
555
  // live (tick live ⊕ hand live) = launch through TASK-AGENT and skip the seat journal so the cell launches once.
463
556
  const handMode = deps.handToTaskAgent ?? handConfig.handToTaskAgent ?? 'off';
464
557
  const handCell = deps.handToTaskAgentCell ?? handConfig.handToTaskAgentCell;
465
558
  if (handMode !== 'off' && (!handCell || handCell === cell.id) && (TASK_SEATS as readonly string[]).includes(cell.seat)) {
466
559
  // live 는 tick 도 live 이고 배치(유효한 판)가 된 칸만 — 나머지는 카드만(shadow · 배치 전 칸도 표본이 된다).
467
- const handLive = handMode === 'live' && mode === 'live' && validVersion(cell.version);
560
+ const handLive = handMode === 'live' && mode === 'live' && validVersion(cell.version) && !capped;
468
561
  // shadow 표본은 따로 센다 — 자리 경로로 아직 안 간 칸(shadow 틱·배치 전)이 나중에 live 로 바뀌면 그때 뜬다.
469
562
  // ⚠️ 이미 자리 저널에 들어간 칸은 live 로 바꿔도 다시 안 넘긴다(keys.has 에서 끝) — 한 칸의 발사 주체는 하나다(의도).
470
563
  const handKey = handLive ? key : `shadow:${key}`;
@@ -482,12 +575,13 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
482
575
  const result = await (deps.handTask ?? (async (opts: HandTaskOptions) => (await import('../../task-agent/task-hand.js')).handTask(opts)))({
483
576
  text: cell.title, seat: cell.seat as TaskSeat, checklistId: cell.id, live: handLive, now: () => now,
484
577
  statePath: join(root, 'task-agent-actions.json'),
485
- ...(handLive ? { launcher: deps.taskLauncher ?? (async (args: string[]) => (await import('../../cli/tasks-cli.js')).defaultTaskLauncher(args)) } : {}),
578
+ ...(handLive ? { launcher: deps.taskLauncher ?? (async (args: string[], _cwd?: string, context?: Parameters<TaskLauncher>[2]) => (await import('../../cli/tasks-cli.js')).defaultTaskLauncher(args, undefined, context)) } : {}),
486
579
  });
487
580
  withFileLockSync(`${path}.lock`, () => appendFileSync(ledger, `${JSON.stringify({ key: handKey, cell: cell.id, cardId: result.card.id, mode: result.mode, launched: result.launched, status: 'handed', runId, at: now.toISOString() })}\n`));
488
581
  (state.handed ??= []).push({ key, cell: cell.id, cardId: result.card.id, mode: result.mode, launched: result.launched });
489
582
  observe('hand-to-task-agent', `${key}:${result.card.id}:${result.mode}${result.launched ? ':launched' : ''}`, 1, 'task-agent');
490
- if (handLive) { keys.add(key); continue; }
583
+ // A live hand is an assignment: it counts toward maxDelegatePerTick like a seat row (COORD-HA ②).
584
+ if (handLive) { keys.add(key); state.delegatedKeys?.push(key); continue; }
491
585
  } catch (error) {
492
586
  observe('hand-to-task-agent-failed', `${key}:${String(error).slice(0, 160)}`, 1, 'task-agent');
493
587
  // A failed live hand stays claimed (no relaunch loop) and does not fall back to the seat path — one launcher per cell.
@@ -514,11 +608,17 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
514
608
  if (handLive) { keys.add(key); continue; }
515
609
  }
516
610
  }
611
+ if (capped) {
612
+ state.deferredCells!.push(cell);
613
+ observe('cap-deferred', key, 1, targetLoopId);
614
+ continue;
615
+ }
616
+ if (unplacedLive) continue;
517
617
  if (!validVersion(cell.version)) {
518
- if (mode === 'shadow') { keys.add(key); state.wouldDelegate++; observe('would-delegate', `${key} (unplaced)`, 1, targetLoopId); }
618
+ if (mode === 'shadow') { keys.add(key); state.wouldDelegate++; state.delegatedKeys?.push(key); observe('would-delegate', `${key} (unplaced)`, 1, targetLoopId); }
519
619
  continue;
520
620
  }
521
- if (mode === 'shadow') { keys.add(key); state.wouldDelegate++; observe('would-delegate', key, 1, targetLoopId); continue; }
621
+ if (mode === 'shadow') { keys.add(key); state.wouldDelegate++; state.delegatedKeys?.push(key); observe('would-delegate', key, 1, targetLoopId); continue; }
522
622
  keys.add(key);
523
623
  mkdirSync(dirname(path), { recursive: true });
524
624
  // TC review must-fix ①: the seat-requests journal contract needs receiptId, and appends are serialized under the
@@ -527,19 +627,23 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
527
627
  if (journalHas(key)) return false;
528
628
  // 잠금 안에서 다시 본다 — 동시 틱이 이 칸을 방금 live 로 넘겼으면 자리 경로로 보내지 않는다.
529
629
  if (handedKeys(ledger).has(key)) return 'handed';
530
- appendFileSync(path, `${JSON.stringify({ key, receiptId: key, seat: cell.seat, ...(cell.kind !== undefined ? { kind: cell.kind } : {}), ...(cell.host !== undefined ? { host: cell.host } : {}), ...(cell.gridHost ? { gridHost: true } : {}), loopId: targetLoopId, text: cell.title, status: 'queued', queuedAt: now.toISOString(), source: 'orchestrator', cell: cell.id, version: cell.version })}\n`);
630
+ appendFileSync(path, `${JSON.stringify({ key, receiptId: key, seat: cell.seat, ...(cell.kind !== undefined ? { kind: cell.kind } : {}), ...(cell.host !== undefined ? { host: cell.host } : {}), ...(cell.gridHost ? { gridHost: true } : {}), loopId: targetLoopId, text: cell.title, status: 'queued', queuedAt: now.toISOString(), source: 'orchestrator', cell: cell.id, version: cell.version, orchRunId: runId })}\n`);
531
631
  return true;
532
632
  });
533
633
  if (appended === 'handed') { observe('exchange', `seat-suppressed-handed:${key}`, 1, 'task-agent'); continue; }
534
634
  if (!appended) continue;
535
635
  state.delegated++;
636
+ state.delegatedKeys?.push(key);
536
637
  observe('exchange', 'queued', 1, targetLoopId);
537
638
  }
538
639
  } else if (node === 'launch') {
640
+ if (state.degradation?.level === 2) observe('degraded-skip', 'launch', 0);
539
641
  // shadow 는 저널을 읽지 않는다 — 깨진 live 저널이 관측 전용 틱을 넘어뜨리지 않게.
540
- const latestRequests = new Map(mode === 'live' ? readRequests(root).map(row => [row.key, row]) : []);
541
- const eligible = state.cells.filter(cell => cell.origin === 'flow1a' && cell.seat && SEAT_LOOP_IDS[cell.seat]
542
- && validVersion(cell.version) && (mode === 'shadow' || latestRequests.get(`orch:${cell.cardId}:${cell.id}`)?.status === 'queued'));
642
+ const latestRequests = new Map(mode === 'live' && state.degradation?.level !== 2 ? readRequests(root).map(row => [row.key, row]) : []);
643
+ const eligible = state.cells.filter(cell => state.degradation?.level !== 2 && cell.origin === 'flow1a' && cell.seat && SEAT_LOOP_IDS[cell.seat]
644
+ && validVersion(cell.version) && (mode === 'shadow' || latestRequests.get(`orch:${cell.cardId}:${cell.id}`)?.status === 'queued')
645
+ && (orchestratorConfig?.maxDelegatePerTick === undefined || state.delegatedKeys?.includes(`orch:${cell.cardId}:${cell.id}`)
646
+ || (mode === 'live' && latestRequests.has(`orch:${cell.cardId}:${cell.id}`))));
543
647
  const seats = [...new Set(eligible.map(cell => cell.seat!))];
544
648
  state.launched = [];
545
649
  for (const seat of seats) {
@@ -637,19 +741,36 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
637
741
  return state;
638
742
  }
639
743
 
744
+ /** COORD-HA — the production supply of `TickDeps.degradation` (config `loops.orchestrator.degradationSignal`). Shadow (the
745
+ * default) probes and logs what the tick would have done; only live hands the signal to the tick. */
746
+ export async function degradationDeps(node: Node, deps: { config?: () => Partial<OrchestratorLoopConfig> | undefined; root?: string;
747
+ probe?: (root: string) => { llm: boolean; board: boolean; why: string[] } } = {}): Promise<Pick<TickDeps, 'degradation'>> {
748
+ const mode = (deps.config ?? (() => getUserConfig().loops?.orchestrator))()?.degradationSignal ?? 'shadow';
749
+ if (mode === 'off') return {};
750
+ const root = deps.root ?? effectiveInstanceRoot();
751
+ const signal = (deps.probe ?? (await import('./degradation.js')).probeDegradation)(root);
752
+ const level = !signal.board ? 2 : !signal.llm ? 1 : 0;
753
+ debug.log('loop.orchestrator', 'degradation-signal', { node, mode, llm: signal.llm, board: signal.board, level, why: signal.why });
754
+ return mode === 'live' ? { degradation: () => ({ llm: signal.llm, board: signal.board }) } : {};
755
+ }
756
+
640
757
  if (import.meta.main) {
641
758
  const node = process.argv[2];
642
759
  const index = process.argv.indexOf('--window');
643
- const window = index < 0 ? undefined : process.argv[index + 1];
760
+ // ORCH-LIVE-1008: `graph run` recipes pass no flags, so a manual/rehearsal tick picks its window through the
761
+ // environment (`ELANOUS_ORCH_WINDOW=08 elanous graph run graphs/orchestrator/orchestrator.yaml`). The flag wins.
762
+ const window = index < 0 ? process.env.ELANOUS_ORCH_WINDOW : process.argv[index + 1];
763
+ // A given flag (even without a value) or a set variable must name a real window — never fall back to «now».
764
+ const windowGiven = index >= 0 || process.env.ELANOUS_ORCH_WINDOW !== undefined;
644
765
  if (!['intake', 'split', 'place', 'delegate', 'launch', 'reconcile', 'report'].includes(node ?? '') ||
645
- (window !== undefined && !['08', '12', '18'].includes(window))) {
646
- console.error('orchestrator: expected <intake|split|place|delegate|launch|reconcile|report> [--window 08|12|18]');
766
+ (windowGiven && !['08', '12', '18'].includes(window ?? ''))) {
767
+ console.error('orchestrator: expected <intake|split|place|delegate|launch|reconcile|report> [--window 08|12|18] (or ELANOUS_ORCH_WINDOW=08|12|18)');
647
768
  process.exitCode = 2;
648
769
  } else {
649
770
  void (async () => {
650
771
  try { await (await import('../../domains/standalone-log-sink.js')).registerStandaloneLogSink('orchestrator'); }
651
772
  catch { /* local execution can still use the per-run file */ }
652
- await runOrchestratorNode(node as Node, { window: window as Window | undefined });
773
+ await runOrchestratorNode(node as Node, { window: window as Window | undefined, ...await degradationDeps(node as Node) });
653
774
  })().catch(error => { console.error(`orchestrator ${node}: ${String(error)}`); process.exitCode = 1; });
654
775
  }
655
776
  }
@@ -48,6 +48,7 @@ export const FEATURE_MATURITY = {
48
48
  '/ceo': { pwa: 'ops' },
49
49
  '/decisions': { pwa: 'ops' },
50
50
  '/dashboard': { pwa: 'ops' },
51
+ '/pitch': { pwa: 'ops' }, // PITCH-ROOM — 투자자 피치 방(운영자 전용 · 공개 내보내기 제외)
51
52
  '/bots': { pwa: 'ops' },
52
53
  '/botlab': { pwa: 'ops' },
53
54
  '/morning': { pwa: 'broken' },
@@ -57,7 +58,7 @@ export const FEATURE_MATURITY = {
57
58
  },
58
59
  // Canonical CLI command names only; aliases inherit their command's grade.
59
60
  cliRoot: {
60
- 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',
61
62
  where: 'stable', shadow: 'beta', llm: 'beta', storage: 'beta', leader: 'beta',
62
63
  pod: 'beta', browser: 'beta', questions: 'beta', usage: 'stable', live: 'beta',
63
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',
@@ -73,10 +74,10 @@ export const FEATURE_MATURITY = {
73
74
  loops: 'beta', buzz: 'beta', harness: 'stable', self: 'beta', factcheck: 'beta',
74
75
  provider: 'stable', 'provider:set': 'beta', 'provider:restore': 'beta',
75
76
  'provider:rotate': 'beta', 'provider:use': 'beta', 'status-bar': 'beta',
76
- schedule: 'beta', intake: 'beta', logs: 'stable', ad: 'beta', docs: 'beta',
77
+ schedule: 'beta', guardian: 'beta', intake: 'beta', logs: 'stable', retro: 'beta', ad: 'beta', docs: 'beta',
77
78
  decisions: 'beta', directives: 'beta', claims: 'beta', lesson: 'beta', fleet: 'beta', ops: 'stable', outputs: 'beta', publish: 'beta',
78
79
  env: 'beta', 'agent-mission': 'beta', dev: 'beta', keys: 'beta', finance: 'beta',
79
- config: 'stable', tasks: 'stable', brief: 'beta', notify: 'beta', labels: 'beta', msg: 'beta', ask: 'stable',
80
+ config: 'stable', tasks: 'stable', brief: 'beta', notify: 'beta', away: 'beta', labels: 'beta', msg: 'beta', ask: 'stable',
80
81
  onboarding: 'beta', login: 'stable', session: 'beta', chat: 'stable', repl: 'stable',
81
82
  agent: 'stable', registry: 'beta', tier: 'stable', local: 'beta', scheduler: 'beta',
82
83
  attach: 'beta', wf: 'beta', nexus: 'stable', phone: 'beta', token: 'beta',
@@ -99,6 +100,7 @@ export const FEATURE_MATURITY = {
99
100
  'run-skill': 'stable',
100
101
  ad: 'beta',
101
102
  design: 'beta',
103
+ decide: 'beta',
102
104
  provider: 'stable',
103
105
  reasoning: 'stable',
104
106
  local: 'tool',
@@ -158,7 +160,7 @@ export const FEATURE_MATURITY = {
158
160
  ping: 'stable', provider: 'stable', sessions: 'stable', fork: 'stable', resume: 'stable',
159
161
  decisions: 'stable', work: 'stable', cancel: 'stable',
160
162
  skills: 'beta', skill: 'beta', digest: 'beta', intake: 'beta', ad: 'beta',
161
- 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', runs: 'beta', release: 'beta', away: 'beta', project: 'beta', wish: 'beta',
162
164
  brain: 'tool', cc: 'tool', cdx: 'tool', gem: 'tool', local: 'tool',
163
165
  harness: 'ops',
164
166
  // Existing commands outside the CXO four-grade menu list are owner-only.
@@ -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++) {
@@ -58,12 +58,18 @@ export async function handleGraphsValidatePost(req: Request, opts: MetaApiOpts):
58
58
  return jsonResponse({ error: 'bad_request', reason: 'graph (harness|workflow) and yaml (string) required' }, 400);
59
59
  }
60
60
  const { graph, yaml } = body as { graph: GraphKind; yaml: string };
61
+ const { ok, errors, ignoredKeys } = validateGraphYaml(graph, yaml);
62
+ debug.log('graph.kinds', 'validate', { graph, ok, errors, ignored: ignoredKeys });
63
+ return jsonResponse({ ok, errors, ignoredKeys }, ok ? 200 : 422);
64
+ }
65
+
66
+ /** The one validator behind `POST /v1/graphs/validate` and the graph save path. */
67
+ export function validateGraphYaml(graph: GraphKind, yaml: string): { ok: boolean; errors: readonly unknown[]; ignoredKeys: string[]; graphId?: string } {
61
68
  const result = graph === 'harness' ? parseGraphTemplateYaml(yaml) : parseWorkflowYaml(yaml);
62
- const errors = graph === 'harness' ? (result as ReturnType<typeof parseGraphTemplateYaml>).errors : (result as ReturnType<typeof parseWorkflowYaml>).issues;
69
+ const errors: readonly unknown[] = graph === 'harness' ? (result as ReturnType<typeof parseGraphTemplateYaml>).errors : (result as ReturnType<typeof parseWorkflowYaml>).issues;
63
70
  let input: unknown;
64
71
  try { input = parseYaml(yaml); } catch { /* The parser already reports the YAML error. */ }
65
72
  const ignoredKeys = ignoredPaths(input, graph === 'harness' ? harnessShape : workflowShape);
66
- const ok = errors.length === 0;
67
- debug.log('graph.kinds', 'validate', { graph, ok, errors, ignored: ignoredKeys });
68
- return jsonResponse({ ok, errors, ignoredKeys }, ok ? 200 : 422);
73
+ const graphId = graph === 'harness' ? (result as ReturnType<typeof parseGraphTemplateYaml>).template?.graphId : undefined;
74
+ return { ok: errors.length === 0, errors, ignoredKeys, ...(graphId === undefined ? {} : { graphId }) };
69
75
  }