elanous 0.2.15 → 0.2.17

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 (309) 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-33ffcc49ab45123b.js → 1307-aac7332576c4d6fc.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{6877-7c6208705c284124.js → 6877-fe94e76a5841ee9b.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/8401-4ce1c42c8d89903f.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/899-f41be85e8a680651.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/{9103-7a3ae7f60af3b99f.js → 9103-60fbef33e66f0a46.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-0e2c57fe6add4b2a.js → page-3143b07a817d1da9.js} +1 -1
  10. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-e05a77df7e005b04.js → page-17ba086ef82d339b.js} +1 -1
  11. package/apps/pwa/out/_next/static/chunks/app/board/page-7eb661ff23af7da2.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/app/botlab/page-6b8444914da74ca6.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/app/bots/{page-b8c49f6b4fa2df91.js → page-6e5c3a867557298b.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/app/ceo/page-7110f732f45bf944.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/app/chat/{page-976813c91d7394ea.js → page-9a41d65355fc25b9.js} +1 -1
  16. package/apps/pwa/out/_next/static/chunks/app/consult/page-5e77cd1802567c89.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/control/{page-1d4901eb7c2d8822.js → page-41afec0b255ed2bb.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-72d3e7ff35a8c954.js → page-4187666132fc98c4.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/editor/{page-60c7a8e1a4fe62ce.js → page-09328d499162bae4.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/exec/{page-2a52b316cb143d9c.js → page-29ff3a8f84f03b6a.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/field/{page-c3ce483c8d5db370.js → page-80ec7401bc83f511.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/inside/page-ab93b240f687d69a.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/app/intake/{page-96f074f6538e8a2c.js → page-fc7d86467291ae38.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/{layout-e97be5334d83a9f5.js → layout-de43a186e5ba5683.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/app/live/page-8b08cafad27759c9.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/loops/page-0e2d6985fb42cd2f.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/market/page-13e4108999f58d13.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e8296ee284aa1293.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/morning/{page-c1343f9499d86f3f.js → page-043a9111e74f6b24.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/{not-found-8739a2e0ae793130.js → not-found-23a4601ceb7f452e.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/observatory/page-1ed1324b1dc36754.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-d23b12bb92242558.js → page-10d958605a77b3bd.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-166745e51ac0c7b0.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-c7fea475488201a8.js → page-a04260b8ccc968d4.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/outputs/page-7a1d85e121065cc8.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/page-1f50e3d4e13435f4.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-5efbf81124c474ae.js → page-12916918988ac97e.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d8a3347a9d0c3f4a.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/sessions/page-a9117d2cd3093d36.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-06ee266953d9c879.js → page-5a64cb5208c6a8fd.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/settings/{page-153c2b3bcc89e8de.js → page-1407ec83ea64c1f9.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-1d824510df025d18.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/setup/page-6e878ebbe1737d00.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/share/{page-0d6d23b40263a7ea.js → page-7f49fe645d5132f7.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/app/showroom/page-81a4e7f8dbfe74cb.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/tasks/page-27d71f131acd9320.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/term/page-3ab59d19913b200e.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/today/page-bf85b2f5bf077925.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/trace/{page-73735b64bfe1ae67.js → page-424659bacfb8cdd7.js} +1 -1
  50. package/apps/pwa/out/_next/static/chunks/app/vault/page-ab32a8f522a60371.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-f7e631e9b4daf66e.js → page-fc2df261f8a2c7df.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/workflows/page-09461366af82ef13.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-9817c7f9007466cc.js → page-5d4fef9ccd7d406b.js} +1 -1
  54. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ea90a8ac73ce2749.js +1 -0
  55. package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +1 -0
  56. package/apps/pwa/out/_next/static/css/{04dabcbcc4bfa6db.css → 085673d826b98b62.css} +1 -1
  57. package/apps/pwa/out/approvals/index.html +1 -1
  58. package/apps/pwa/out/approvals/index.txt +15 -15
  59. package/apps/pwa/out/autopilot/index.html +1 -1
  60. package/apps/pwa/out/autopilot/index.txt +15 -15
  61. package/apps/pwa/out/board/index.html +1 -1
  62. package/apps/pwa/out/board/index.txt +15 -15
  63. package/apps/pwa/out/botlab/index.html +1 -1
  64. package/apps/pwa/out/botlab/index.txt +15 -15
  65. package/apps/pwa/out/bots/index.html +1 -1
  66. package/apps/pwa/out/bots/index.txt +15 -15
  67. package/apps/pwa/out/ceo/index.html +1 -1
  68. package/apps/pwa/out/ceo/index.txt +15 -15
  69. package/apps/pwa/out/chat/index.html +1 -1
  70. package/apps/pwa/out/chat/index.txt +15 -15
  71. package/apps/pwa/out/consult/index.html +1 -1
  72. package/apps/pwa/out/consult/index.txt +15 -15
  73. package/apps/pwa/out/control/index.html +1 -1
  74. package/apps/pwa/out/control/index.txt +15 -15
  75. package/apps/pwa/out/design-check/index.html +1 -1
  76. package/apps/pwa/out/design-check/index.txt +15 -15
  77. package/apps/pwa/out/editor/index.html +1 -1
  78. package/apps/pwa/out/editor/index.txt +15 -15
  79. package/apps/pwa/out/exec/index.html +1 -1
  80. package/apps/pwa/out/exec/index.txt +15 -15
  81. package/apps/pwa/out/field/index.html +1 -1
  82. package/apps/pwa/out/field/index.txt +15 -15
  83. package/apps/pwa/out/healthz +1 -1
  84. package/apps/pwa/out/index.html +1 -1
  85. package/apps/pwa/out/index.txt +15 -15
  86. package/apps/pwa/out/inside/index.html +1 -1
  87. package/apps/pwa/out/inside/index.txt +15 -15
  88. package/apps/pwa/out/intake/index.html +1 -1
  89. package/apps/pwa/out/intake/index.txt +15 -15
  90. package/apps/pwa/out/live/index.html +1 -1
  91. package/apps/pwa/out/live/index.txt +15 -15
  92. package/apps/pwa/out/loops/index.html +1 -1
  93. package/apps/pwa/out/loops/index.txt +15 -15
  94. package/apps/pwa/out/market/index.html +1 -1
  95. package/apps/pwa/out/market/index.txt +15 -15
  96. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  97. package/apps/pwa/out/missions/placeholder/index.txt +15 -15
  98. package/apps/pwa/out/morning/index.html +1 -1
  99. package/apps/pwa/out/morning/index.txt +15 -15
  100. package/apps/pwa/out/observatory/index.html +1 -1
  101. package/apps/pwa/out/observatory/index.txt +15 -15
  102. package/apps/pwa/out/ops/checklist/index.html +1 -1
  103. package/apps/pwa/out/ops/checklist/index.txt +15 -15
  104. package/apps/pwa/out/ops/release/index.html +1 -1
  105. package/apps/pwa/out/ops/release/index.txt +15 -15
  106. package/apps/pwa/out/ops/seats/index.html +1 -1
  107. package/apps/pwa/out/ops/seats/index.txt +15 -15
  108. package/apps/pwa/out/outputs/index.html +1 -1
  109. package/apps/pwa/out/outputs/index.txt +15 -15
  110. package/apps/pwa/out/reflection/index.html +1 -1
  111. package/apps/pwa/out/reflection/index.txt +16 -16
  112. package/apps/pwa/out/scheduler/index.html +1 -1
  113. package/apps/pwa/out/scheduler/index.txt +15 -15
  114. package/apps/pwa/out/sessions/index.html +1 -1
  115. package/apps/pwa/out/sessions/index.txt +15 -15
  116. package/apps/pwa/out/settings/devices/index.html +1 -1
  117. package/apps/pwa/out/settings/devices/index.txt +15 -15
  118. package/apps/pwa/out/settings/index.html +1 -1
  119. package/apps/pwa/out/settings/index.txt +15 -15
  120. package/apps/pwa/out/setup/done/index.html +1 -1
  121. package/apps/pwa/out/setup/done/index.txt +15 -15
  122. package/apps/pwa/out/setup/index.html +1 -1
  123. package/apps/pwa/out/setup/index.txt +15 -15
  124. package/apps/pwa/out/share/index.html +1 -1
  125. package/apps/pwa/out/share/index.txt +15 -15
  126. package/apps/pwa/out/showroom/index.html +1 -1
  127. package/apps/pwa/out/showroom/index.txt +15 -15
  128. package/apps/pwa/out/tasks/index.html +1 -1
  129. package/apps/pwa/out/tasks/index.txt +15 -15
  130. package/apps/pwa/out/term/index.html +1 -1
  131. package/apps/pwa/out/term/index.txt +15 -15
  132. package/apps/pwa/out/today/index.html +1 -1
  133. package/apps/pwa/out/today/index.txt +15 -15
  134. package/apps/pwa/out/trace/index.html +1 -1
  135. package/apps/pwa/out/trace/index.txt +15 -15
  136. package/apps/pwa/out/vault/index.html +1 -1
  137. package/apps/pwa/out/vault/index.txt +15 -15
  138. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  139. package/apps/pwa/out/workflows/chat-ui/index.txt +15 -15
  140. package/apps/pwa/out/workflows/index.html +1 -1
  141. package/apps/pwa/out/workflows/index.txt +15 -15
  142. package/apps/pwa/out/workspace/index.html +1 -1
  143. package/apps/pwa/out/workspace/index.txt +15 -15
  144. package/apps/pwa/out/worktrees/index.html +1 -1
  145. package/apps/pwa/out/worktrees/index.txt +15 -15
  146. package/graphs/doc-gen/doc-gen.yaml +24 -0
  147. package/graphs/doc-gen/recipes.yaml +3 -0
  148. package/graphs/intake/intake-daily.yaml +9 -2
  149. package/graphs/intake/news-sources.yaml +4 -0
  150. package/graphs/intake/recipes.yaml +6 -1
  151. package/graphs/orchestrator/orchestrator.yaml +7 -0
  152. package/graphs/orchestrator/recipes.yaml +3 -0
  153. package/graphs/steward/recipes.yaml +2 -1
  154. package/package.json +1 -1
  155. package/scripts/__fixtures__/intake-news.xml +7 -0
  156. package/scripts/ci-public-export-import-gate.ts +154 -0
  157. package/scripts/ci-test-interference-gate.ts +5 -1
  158. package/scripts/ci-typecheck-changed.ts +34 -15
  159. package/scripts/docs/doc-rot.ts +164 -0
  160. package/scripts/docs/generate-graph-anatomy.ts +131 -9
  161. package/scripts/docs-cli-check.ts +4 -4
  162. package/scripts/goals/goal-census.ts +343 -0
  163. package/scripts/intake-news.ts +169 -0
  164. package/scripts/measure/author-section-contribution.ts +84 -0
  165. package/scripts/release-loop/cut-branch.ts +43 -13
  166. package/scripts/release-loop/docs-land-node.ts +69 -10
  167. package/scripts/release-loop/gate-node.ts +3 -1
  168. package/scripts/release-loop/publish-node.ts +2 -2
  169. package/scripts/release-loop/release-docs.ts +134 -0
  170. package/scripts/release-loop/vault-note-node.ts +19 -2
  171. package/scripts/release-loop/version-node.ts +7 -4
  172. package/scripts/release-story/draft.ts +42 -14
  173. package/scripts/rhythm/monthly.ts +97 -0
  174. package/src/acp/client.ts +17 -2
  175. package/src/agent-mission/driver.ts +104 -11
  176. package/src/agent-mission/supervisor-metrics-cli.ts +40 -0
  177. package/src/agent-mission/supervisor-metrics.ts +218 -0
  178. package/src/autopilot/track-agent-shadow.ts +2 -1
  179. package/src/autopilot/track-agent.ts +5 -3
  180. package/src/briefing/brief-items.ts +191 -36
  181. package/src/budget/usage-outcomes.ts +204 -0
  182. package/src/cli/autopilot-cli.ts +66 -0
  183. package/src/cli/brief-cli.ts +73 -0
  184. package/src/cli/decisions-cli.ts +4 -1
  185. package/src/cli/intake-cli.ts +1 -1
  186. package/src/cli/know-cli.ts +20 -0
  187. package/src/cli/log-event-names.ts +1 -0
  188. package/src/cli/logs-cli.ts +43 -1
  189. package/src/cli/pr-cli.ts +92 -13
  190. package/src/cli/provider-cli.ts +14 -0
  191. package/src/cli/release-cli.ts +11 -8
  192. package/src/cli/self-update-canary.ts +46 -0
  193. package/src/cli/self-update.ts +399 -1
  194. package/src/cli/usage-cli.ts +26 -0
  195. package/src/context-card/index.ts +45 -0
  196. package/src/context-card/task.ts +76 -0
  197. package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -0
  198. package/src/decisions/decision-ledger.ts +10 -1
  199. package/src/discord-self-message.ts +9 -0
  200. package/src/flow-loop/split.ts +24 -0
  201. package/src/harness/harness-cli-command.ts +178 -12
  202. package/src/harness/harness-pod-dispatch.ts +36 -55
  203. package/src/harness/harness-queue.ts +86 -7
  204. package/src/harness/harness-seams.ts +13 -3
  205. package/src/harness/helper-repair.ts +139 -8
  206. package/src/harness/helper-scan.ts +13 -2
  207. package/src/harness/nested-elanous-policy.ts +133 -0
  208. package/src/hq/fence-wrapper.ts +123 -0
  209. package/src/hq/hq.ts +16 -2
  210. package/src/hq/lease.ts +5 -3
  211. package/src/hq/transfer.ts +185 -0
  212. package/src/index.ts +168 -13
  213. package/src/intake-plane/digest.ts +85 -28
  214. package/src/knowledge/know-find.ts +105 -0
  215. package/src/llm.ts +75 -19
  216. package/src/loops/orchestrator/finish-rate.ts +14 -3
  217. package/src/loops/orchestrator/tick.ts +96 -25
  218. package/src/maturity/bot-slash-catalog.ts +11 -2
  219. package/src/maturity/feature-maturity.ts +2 -2
  220. package/src/maturity/tui-slash-availability.ts +9 -0
  221. package/src/nexus/api/grid.ts +61 -0
  222. package/src/nexus/api/http-server.ts +19 -1
  223. package/src/nexus/api/task-cards-api.ts +19 -0
  224. package/src/oauth/codex.ts +82 -2
  225. package/src/oauth/overload-failover-launch.ts +75 -0
  226. package/src/oauth/overload-failover.ts +137 -0
  227. package/src/release-loop/checklist.ts +10 -5
  228. package/src/release-loop/feature-store.ts +10 -9
  229. package/src/release-loop/landing-freeze.ts +18 -10
  230. package/src/release-loop/placement.ts +17 -8
  231. package/src/seat-dispatch/seat-ask.ts +33 -6
  232. package/src/seat-loop/seat-loop.ts +172 -21
  233. package/src/self-dev/ask-launch-flow.ts +48 -8
  234. package/src/self-dev/authored-goal-trace.ts +64 -0
  235. package/src/self-dev/dev-cli.ts +19 -1
  236. package/src/self-dev/dev-pipeline.ts +6 -5
  237. package/src/self-dev/entrance-registry.ts +10 -6
  238. package/src/self-dev/old-door.ts +73 -0
  239. package/src/self-dev/run-store.ts +5 -1
  240. package/src/self-dev/run-supervisor.ts +7 -1
  241. package/src/self-dev/supervisor-verdict-edges.ts +46 -0
  242. package/src/self-implement/app-api-budget.ts +166 -0
  243. package/src/self-implement/budget-gate.ts +7 -5
  244. package/src/self-implement/follow-up-goals.ts +289 -0
  245. package/src/self-implement/frozen-merges.ts +25 -20
  246. package/src/self-implement/goal-author-clarification.ts +81 -0
  247. package/src/self-implement/goal-author-cli.ts +33 -5
  248. package/src/self-implement/goal-author.ts +107 -25
  249. package/src/self-implement/goal-clarification-escalation.ts +37 -1
  250. package/src/self-implement/goal-document-write.ts +28 -0
  251. package/src/self-implement/heal-intake.ts +4 -0
  252. package/src/self-implement/heal-triage.ts +44 -0
  253. package/src/self-implement/host-regate.ts +3 -1
  254. package/src/self-implement/orchestrator.ts +157 -8
  255. package/src/self-implement/run-ledger.ts +4 -0
  256. package/src/self-implement/seams.ts +44 -15
  257. package/src/session-runtime/index.ts +2 -0
  258. package/src/session-runtime/retry-api.ts +87 -0
  259. package/src/session-runtime/retry-policy.ts +107 -6
  260. package/src/steward/working-backwards.ts +16 -1
  261. package/src/task-agent/actions.ts +118 -0
  262. package/src/task-agent/judge.ts +96 -0
  263. package/src/task-cards/card-cli.ts +25 -0
  264. package/src/task-cards/card-store.ts +9 -4
  265. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +33 -4
  266. package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
  267. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +4 -3
  268. package/src/task-orchestrator/surfaces/pod-ledger-prod-sink.ts +75 -0
  269. package/src/task-orchestrator/surfaces/pod-pool.ts +95 -18
  270. package/src/task-orchestrator/surfaces/pod-source-receive.ts +37 -1
  271. package/src/task-orchestrator/surfaces/self-implement-pod.ts +168 -37
  272. package/src/task-orchestrator/surfaces/self-implement.ts +2 -0
  273. package/src/telegram-commands.ts +6 -1
  274. package/src/telegram-loops-command.ts +21 -14
  275. package/src/telegram.ts +47 -0
  276. package/src/user-config.ts +59 -4
  277. package/src/version/packed-revision.json +1 -1
  278. package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +0 -1
  279. package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +0 -1
  280. package/apps/pwa/out/_next/static/chunks/9247-29d889b809288e65.js +0 -1
  281. package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +0 -1
  282. package/apps/pwa/out/_next/static/chunks/app/botlab/page-5491c115b1f9a923.js +0 -1
  283. package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +0 -1
  284. package/apps/pwa/out/_next/static/chunks/app/consult/page-28b9448091776c06.js +0 -1
  285. package/apps/pwa/out/_next/static/chunks/app/inside/page-13c7b53e0c2519a9.js +0 -1
  286. package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +0 -1
  287. package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +0 -1
  288. package/apps/pwa/out/_next/static/chunks/app/market/page-955ec2da3be4c560.js +0 -1
  289. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-9f88fbc15d9058d1.js +0 -1
  290. package/apps/pwa/out/_next/static/chunks/app/observatory/page-c9fc5706b8abaa11.js +0 -1
  291. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +0 -1
  292. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e2ee2896811e98d0.js +0 -1
  293. package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +0 -1
  294. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f1a1fc0a82738c.js +0 -1
  295. package/apps/pwa/out/_next/static/chunks/app/sessions/page-da6c08aae0f92768.js +0 -1
  296. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-87a11e6a05f15199.js +0 -1
  297. package/apps/pwa/out/_next/static/chunks/app/setup/page-7d4182964407c09c.js +0 -1
  298. package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +0 -1
  299. package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +0 -1
  300. package/apps/pwa/out/_next/static/chunks/app/term/page-f5597e894c02cd33.js +0 -1
  301. package/apps/pwa/out/_next/static/chunks/app/today/page-0337fdf7fb5e37b6.js +0 -1
  302. package/apps/pwa/out/_next/static/chunks/app/vault/page-8fcec4c9ea2e7f70.js +0 -1
  303. package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +0 -1
  304. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-97f9f43172a3a90e.js +0 -1
  305. package/apps/pwa/out/_next/static/css/1f7c958c41ffa3b9.css +0 -1
  306. package/src/launch-head/seat-goal-queue-lock.fixture.ts +0 -15
  307. package/src/launch-head/seat-goal-queue.ts +0 -198
  308. /package/apps/pwa/out/_next/static/{LZcstaO9_NXypPFmujolv → yRA5ur6DcPl0YHSRheH2O}/_buildManifest.js +0 -0
  309. /package/apps/pwa/out/_next/static/{LZcstaO9_NXypPFmujolv → yRA5ur6DcPl0YHSRheH2O}/_ssgManifest.js +0 -0
@@ -0,0 +1,105 @@
1
+ import { createRequire } from 'node:module';
2
+ import { LessonLedger, type LessonRow } from '../lessons/lesson-ledger.js';
3
+ import { DecisionLedger, type DecisionEntry } from '../decisions/decision-ledger.js';
4
+ import { devVersion, listChecklist, type Checklist } from '../release-loop/checklist.js';
5
+ import { debug } from '../debug/log.js';
6
+
7
+ /** Directive row fields this search reads — kept local because `src/directives/directive-index` is excluded from the public export. */
8
+ export interface DirectiveRow { ts: string; text: string; source_file: string; line_no: number }
9
+
10
+ /** Loads the private directive index at run time only, so public builds bundle cleanly and report the directive source as unreadable. */
11
+ function openDirectiveIndex(): { search(q: string): DirectiveRow[]; close(): void } {
12
+ const spec = ['..', 'directives', 'directive-index.js'].join('/');
13
+ const mod = createRequire(import.meta.url)(spec) as { DirectiveIndex: new () => ReturnType<typeof openDirectiveIndex> };
14
+ return new mod.DirectiveIndex();
15
+ }
16
+
17
+ export type KnowSource = 'directive' | 'decision' | 'checklist' | 'lesson';
18
+ export interface KnowHit {
19
+ source: KnowSource;
20
+ id: string;
21
+ title: string;
22
+ status: string;
23
+ at: string;
24
+ current: boolean;
25
+ ref: string;
26
+ }
27
+ export interface KnowResult {
28
+ rows: KnowHit[];
29
+ unavailable: Array<{ source: KnowSource; reason: string }>;
30
+ }
31
+ export interface KnowFindDeps {
32
+ directives: (query: string) => DirectiveRow[];
33
+ decisions: () => DecisionEntry[];
34
+ checklist: (version: string) => Checklist;
35
+ lessons: (query: string) => LessonRow[];
36
+ versions: () => string[];
37
+ now: () => Date;
38
+ }
39
+
40
+ function versions(): string[] {
41
+ const current = devVersion().replace(/-dev\.\d+$/, '');
42
+ const parts = current.split('.').map(Number);
43
+ return [current, `${parts[0]}.${parts[1]}.${parts[2]! + 1}`];
44
+ }
45
+
46
+ const defaults: KnowFindDeps = {
47
+ directives: (query) => {
48
+ const index = openDirectiveIndex();
49
+ try { return index.search(query); } finally { index.close(); }
50
+ },
51
+ decisions: () => new DecisionLedger().list({ status: 'all' }),
52
+ checklist: (version) => listChecklist(version),
53
+ lessons: (query) => new LessonLedger().find(query),
54
+ versions,
55
+ now: () => new Date(),
56
+ };
57
+
58
+ function matches(text: string, words: string[]): boolean {
59
+ const normalized = text.toLocaleLowerCase();
60
+ return words.every((word) => normalized.includes(word));
61
+ }
62
+
63
+ export function knowFind(query: string, deps: Partial<KnowFindDeps> = {}): KnowResult {
64
+ const read = { ...defaults, ...deps };
65
+ const words = query.trim().toLocaleLowerCase().split(/\s+/).filter(Boolean);
66
+ if (!words.length) throw new Error('know: 질문이 필요합니다');
67
+ const rows: KnowHit[] = [];
68
+ const unavailable: KnowResult['unavailable'] = [];
69
+ const attempt = (source: KnowSource, work: () => void): void => {
70
+ try { work(); }
71
+ catch (error) { unavailable.push({ source, reason: error instanceof Error ? error.message : String(error) }); }
72
+ };
73
+ const cutoff = read.now().getTime() - 30 * 86400000;
74
+ attempt('directive', () => {
75
+ for (const row of read.directives(query)) rows.push({ source: 'directive', id: `${row.source_file}:${row.line_no}`,
76
+ title: row.text.replace(/\s+/g, ' ').slice(0, 160), status: 'recorded', at: row.ts,
77
+ current: Number.isFinite(Date.parse(row.ts)) && Date.parse(row.ts) >= cutoff, ref: `${row.source_file}:${row.line_no}` });
78
+ });
79
+ attempt('decision', () => {
80
+ for (const row of read.decisions()) {
81
+ if (!matches([row.title, row.scqa.s, row.scqa.c, row.scqa.q, row.scqa.a].filter(Boolean).join(' '), words)) continue;
82
+ rows.push({ source: 'decision', id: row.id, title: row.title, status: row.status,
83
+ at: row.decidedAt ?? row.withdrawnAt ?? row.raisedAt ?? row.importedAt ?? '', current: row.status === 'open',
84
+ ref: row.refs?.[0] ?? `decisions show ${row.id}` });
85
+ }
86
+ });
87
+ attempt('checklist', () => {
88
+ for (const version of new Set(read.versions())) {
89
+ for (const row of read.checklist(version).items) {
90
+ if (!matches(`${row.id} ${row.title}`, words)) continue;
91
+ rows.push({ source: 'checklist', id: row.id, title: row.title, status: row.status,
92
+ at: row.updatedAt, current: row.status === 'yellow', ref: `release checklist status --version ${version}` });
93
+ }
94
+ }
95
+ });
96
+ attempt('lesson', () => {
97
+ for (const row of read.lessons(query)) rows.push({ source: 'lesson', id: row.id, title: row.incident,
98
+ status: row.status, at: row.updated_at, current: row.occurrence_count > 1, ref: `lesson show ${row.id}` });
99
+ });
100
+ rows.sort((a, b) => Number(b.current) - Number(a.current) || (Date.parse(b.at) || 0) - (Date.parse(a.at) || 0)
101
+ || a.source.localeCompare(b.source) || a.id.localeCompare(b.id));
102
+ debug.log('knowledge.know', 'query', { query, counts: Object.fromEntries((['directive', 'decision', 'checklist', 'lesson'] as const)
103
+ .map(source => [source, rows.filter(row => row.source === source).length])), unavailable });
104
+ return { rows, unavailable };
105
+ }
package/src/llm.ts CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  type ProviderFallbackAttempt,
37
37
  type ProviderFallbackTerminalVerdict,
38
38
  } from './session-runtime/retry-policy.js';
39
- import { fetchApiWithRetry } from './session-runtime/retry-api.js';
39
+ import { fetchApiWithRetry, OverloadFailoverError, type FetchApiWithRetryOpts } from './session-runtime/retry-api.js';
40
40
  import { resolveModelAlias } from './intelligence-map/model-alias.js';
41
41
  import { reasoningEffortCeiling as reasoningEffortCeilingOf } from './intelligence-map/model-catalog.js';
42
42
  import { inferProviderFromModel as registryInferProviderFromModel } from './registry/normalize.js';
@@ -407,6 +407,10 @@ export interface LLMOpts {
407
407
  background?: 'transparent' | 'opaque' | 'auto';
408
408
  };
409
409
  };
410
+ /** Unused configured fallbacks. The HTTP retry layer switches provider
411
+ * after k consecutive overload responses before spending those retries.
412
+ * Callers that already pinned a provider omit this (0). */
413
+ remainingFallbacks?: number;
410
414
  }
411
415
 
412
416
  export interface LLMProvider {
@@ -1310,6 +1314,20 @@ export async function* parseOpenAISSELines(
1310
1314
  * the request URL. The OpenAI streamer is shared by openai/grok/local
1311
1315
  * endpoints — all three pass through here. The string lands in the
1312
1316
  * `llm.retry` debug snapshot and the `ApiHttpError.provider` field. */
1317
+ /** Retries still left for this HTTP call. Callers that already know a
1318
+ * fallback remains pass it so overload switches before those retries. */
1319
+ function retryOptsFor(
1320
+ provider: string,
1321
+ errorPrefix: string,
1322
+ remainingFallbacks?: number,
1323
+ ): FetchApiWithRetryOpts {
1324
+ return {
1325
+ provider,
1326
+ errorPrefix,
1327
+ ...(remainingFallbacks !== undefined && remainingFallbacks > 0 ? { remainingFallbacks } : {}),
1328
+ };
1329
+ }
1330
+
1313
1331
  function inferOpenAIProviderName(url: string): string {
1314
1332
  if (url.includes('x.ai')) return 'grok';
1315
1333
  if (url.includes('openai.com')) return 'openai';
@@ -1326,6 +1344,7 @@ async function* streamOpenAIEvents(
1326
1344
  * `x-grok-client-version` 이 빠지면 HTTP 426 으로 거절된다(실측).
1327
1345
  * ⛔ 생략하면 종전과 «바이트 동일** — 기존 호출자 무회귀. */
1328
1346
  extraHeaders?: Record<string, string>,
1347
+ remainingFallbacks?: number,
1329
1348
  ): AsyncGenerator<LLMStreamEvent, void, unknown> {
1330
1349
  const headers: Record<string, string> = { 'Content-Type': 'application/json', ...(extraHeaders ?? {}) };
1331
1350
  if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`;
@@ -1353,10 +1372,7 @@ async function* streamOpenAIEvents(
1353
1372
  body: JSON.stringify({ ...body, stream: true }),
1354
1373
  signal,
1355
1374
  },
1356
- {
1357
- provider: inferOpenAIProviderName(url),
1358
- errorPrefix: 'LLM API',
1359
- },
1375
+ retryOptsFor(inferOpenAIProviderName(url), 'LLM API', remainingFallbacks),
1360
1376
  );
1361
1377
 
1362
1378
  debug.log('llm.response.status', `${response.status} ${response.statusText || ''}`.trim(), {
@@ -2030,6 +2046,8 @@ export async function* streamCodexResponsesEvents(
2030
2046
  * call. Best-effort; null/undefined if the backend skips the
2031
2047
  * event. */
2032
2048
  onResponseCreated?: (id: string) => void;
2049
+ /** Unused configured fallbacks. Overload k times switches before retries. */
2050
+ remainingFallbacks?: number;
2033
2051
  } = {},
2034
2052
  ): AsyncGenerator<LLMStreamEvent, void, unknown> {
2035
2053
  const startedAt = Date.now();
@@ -2118,10 +2136,7 @@ export async function* streamCodexResponsesEvents(
2118
2136
  }),
2119
2137
  signal,
2120
2138
  },
2121
- {
2122
- provider: 'codex',
2123
- errorPrefix: 'Codex API',
2124
- },
2139
+ retryOptsFor('codex', 'Codex API', extra.remainingFallbacks),
2125
2140
  );
2126
2141
  debug.log('llm.response.status', `codex ${response.status}`, {
2127
2142
  url, status: response.status, elapsedMs: Date.now() - startedAt,
@@ -3103,6 +3118,7 @@ async function* streamAnthropicEvents(
3103
3118
  apiKey: string,
3104
3119
  body: any,
3105
3120
  signal?: AbortSignal,
3121
+ remainingFallbacks?: number,
3106
3122
  ): AsyncGenerator<LLMStreamEvent, void, unknown> {
3107
3123
  // Wave 6 (2026-05-04) — Anthropic beta headers from
3108
3124
  // ref/opencode `provider/provider.ts:181-185` analysis.
@@ -3139,10 +3155,7 @@ async function* streamAnthropicEvents(
3139
3155
  body: JSON.stringify({ ...body, stream: true }),
3140
3156
  signal,
3141
3157
  },
3142
- {
3143
- provider: 'anthropic',
3144
- errorPrefix: 'Anthropic API',
3145
- },
3158
+ retryOptsFor('anthropic', 'Anthropic API', remainingFallbacks),
3146
3159
  );
3147
3160
 
3148
3161
  try {
@@ -4507,7 +4520,11 @@ export async function streamLLM(
4507
4520
  const consumed = await consumeProviderText(
4508
4521
  activeProvider,
4509
4522
  messages,
4510
- { ...opts, model: activeModel },
4523
+ {
4524
+ ...opts,
4525
+ model: activeModel,
4526
+ remainingFallbacks: opts.provider ? 0 : remainingFallbackProviderNames(attemptedProviders, opts).length,
4527
+ },
4511
4528
  onChunk,
4512
4529
  );
4513
4530
  const full = consumed.full;
@@ -4528,6 +4545,22 @@ export async function streamLLM(
4528
4545
  }
4529
4546
 
4530
4547
  const err: any = consumed.error;
4548
+ if (err instanceof OverloadFailoverError && !opts.provider) {
4549
+ const switched = providerByName('grok');
4550
+ if (switched && !attemptedProviders.has(switched.name) && switched.available() && full.length === 0) {
4551
+ debug.log('llm.router', 'overload-failover', {
4552
+ blockedProvider: activeProvider.name,
4553
+ fallbackProvider: switched.name,
4554
+ streak: err.streak,
4555
+ status: err.status,
4556
+ kind: err.kind,
4557
+ }, { level: 'warn' });
4558
+ activeProvider = switched;
4559
+ modelOverride = undefined;
4560
+ attemptedProviders.add(activeProvider.name);
4561
+ continue;
4562
+ }
4563
+ }
4531
4564
  const reason = sanitizeProviderFailureReason(err?.message || String(err));
4532
4565
  debug.log('llm.router.error', 'streamLLM', {
4533
4566
  provider: activeProvider.name,
@@ -8350,9 +8383,11 @@ export async function streamLLMWithTools(
8350
8383
  // `tools: activeTools` (not opts.tools) so mid-loop hydration reaches the
8351
8384
  // provider on the next turn. turn-0 keeps opts.toolChoice; later turns
8352
8385
  // revert to auto (see above).
8353
- const buildTurnOpts = () => turn === 0
8354
- ? { ...opts, model: activeModel, tools: activeTools }
8355
- : { ...opts, model: activeModel, tools: activeTools, toolChoice: undefined };
8386
+ const buildTurnOpts = () => {
8387
+ const remainingFallbacks = opts.provider ? 0 : remainingFallbackProviderNames(attemptedProviders, opts).length;
8388
+ const base = { ...opts, model: activeModel, tools: activeTools, remainingFallbacks };
8389
+ return turn === 0 ? base : { ...base, toolChoice: undefined };
8390
+ };
8356
8391
  let turnOpts = buildTurnOpts();
8357
8392
  // reasoning-heavy family(codex/claude/gemini/grok/local)는 침묵 추론 구간이 길어 idle watchdog 를
8358
8393
  // family-aware 로 상향(2026-07-19 goal-exec → 아크4 전-family). fast/mini(gpt·other)만 45s 유지.
@@ -8390,6 +8425,26 @@ export async function streamLLMWithTools(
8390
8425
  ]);
8391
8426
  } catch (err) {
8392
8427
  if (idleTimer) clearTimeout(idleTimer);
8428
+ if (err instanceof OverloadFailoverError && !opts.provider) {
8429
+ const switched = providerByName('grok');
8430
+ if (switched && !attemptedProviders.has(switched.name) && switched.available() && evCount === 0) {
8431
+ debug.log('llm.router', 'overload-failover', {
8432
+ turn,
8433
+ blockedProvider: activeProvider.name,
8434
+ fallbackProvider: switched.name,
8435
+ streak: err.streak,
8436
+ status: err.status,
8437
+ kind: err.kind,
8438
+ }, { level: 'warn' });
8439
+ const finalized = finalizeStreamingProviderModel(switched, switched.defaultModel);
8440
+ activeProvider = finalized.provider;
8441
+ activeModel = finalized.model;
8442
+ attemptedProviders.add(activeProvider.name);
8443
+ turnOpts = buildTurnOpts();
8444
+ streamIter = undefined;
8445
+ continue;
8446
+ }
8447
+ }
8393
8448
  // 명시 provider 선택만 폴백 대상이 아니다 — `streamLLM` 과 같은 계약으로
8394
8449
  // «원본 오류»를 그대로 올린다. 모델만 전달된 호출은 다음 provider의 기본 모델로 재시도한다.
8395
8450
  if (opts.provider) throw err;
@@ -10958,6 +11013,7 @@ function makeCodexProvider(cfg: UCLLMConfig): LLMProvider {
10958
11013
  // lastResponseId stays undefined and we fall back to full
10959
11014
  // input on the next turn (safe — just no token savings).
10960
11015
  onResponseCreated: useStore ? (id) => { lastResponseId = id; } : undefined,
11016
+ remainingFallbacks: opts.remainingFallbacks,
10961
11017
  });
10962
11018
  // Snapshot full input AFTER the stream completes so the next
10963
11019
  // call's delta computation uses what the backend actually has
@@ -10991,7 +11047,7 @@ function makeCodexProvider(cfg: UCLLMConfig): LLMProvider {
10991
11047
  ...openAiTemperatureField(apiKeyModel, opts.temperature ?? 0.3),
10992
11048
  ...openAiOutputTokenField(apiKeyModel, opts.maxTokens ?? 2048),
10993
11049
  ...(tools ? { tools } : {}),
10994
- }, opts.signal);
11050
+ }, opts.signal, undefined, opts.remainingFallbacks);
10995
11051
  },
10996
11052
  async *chat(messages, opts = {}) { yield* textOnly(this.streamChat!(messages, opts)); },
10997
11053
  };
@@ -11085,7 +11141,7 @@ function makeAnthropicProvider(cfg: UCLLMConfig): LLMProvider {
11085
11141
  ...(effort ? { thinking: { type: 'adaptive' }, output_config: { effort } } : {}),
11086
11142
  ...anthropicTemperatureField(anthropicModel, thinkingActive, opts.temperature),
11087
11143
  ...(tools ? { tools } : {}),
11088
- }, opts.signal);
11144
+ }, opts.signal, opts.remainingFallbacks);
11089
11145
  },
11090
11146
  async *chat(messages, opts = {}) { yield* textOnly(this.streamChat!(messages, opts)); },
11091
11147
  };
@@ -6,6 +6,8 @@ import { loadRunLedger, resolveFederatedRunLedgerTargets } from '../../self-impl
6
6
  import type { LogTarget } from '../../cli/logs-cli.js';
7
7
 
8
8
  const DAY_MS = 24 * 60 * 60 * 1_000;
9
+ /** Host-side records a Pod-dispatched launch leaves when its run starts inside the Pod. */
10
+ const LAUNCH_ONLY_EVENTS = new Set(['launch-quota-policy', 'author-on-pod-receipt']);
9
11
  const GH_LIMIT = 1000;
10
12
 
11
13
  export type FinishMetric = 'launched' | 'landed' | 'landingRate' | 'staleDrafts' | 'conflictRatio' | 'unknownMergeable';
@@ -38,6 +40,7 @@ function defaultListStarts(cutoff: number, targets?: readonly LogTarget[]): Star
38
40
  const childLedgers = new Set<string>();
39
41
  let unreadableDirectories = 0;
40
42
  let unresolvedDirectories = 0;
43
+ const launchOnlyIds = new Set<string>();
41
44
  for (const { ledgerDirectory } of resolveFederatedRunLedgerTargets({ includeTest: true, ...(targets ? { targets } : {}) })) {
42
45
  let dir: string;
43
46
  try {
@@ -68,16 +71,24 @@ function defaultListStarts(cutoff: number, targets?: readonly LogTarget[]): Star
68
71
  continue;
69
72
  }
70
73
  const start = entries.find(entry => entry.event === 'start');
71
- if (!start?.timestamp) {
74
+ // A Pod-dispatched launch leaves only a host launch record (launch-quota-policy · author-on-pod-receipt) — the run
75
+ // itself starts inside the Pod. That is still a launch: its time is the record's timestamp, else the ledger mtime.
76
+ const launchRecord = !start ? entries.find(entry => LAUNCH_ONLY_EVENTS.has(entry.event)) : undefined;
77
+ const startedAt = start?.timestamp ?? (launchRecord ? (launchRecord.timestamp ?? new Date(statSync(path).mtimeMs).toISOString()) : undefined);
78
+ if (!startedAt) {
72
79
  if (!starts.has(runId)) unreadable.set(runId, { runId, reason: entries[0]?.event ?? 'start missing' });
73
80
  continue;
74
81
  }
75
- starts.set(runId, { runId, startedAt: start.timestamp });
82
+ // One runId can leave a host launch record in one directory and a Pod `start` in another: a real `start`
83
+ // always wins and is never overwritten by a launch record, whatever order the directories are read in.
84
+ if (launchRecord && starts.has(runId)) continue;
85
+ if (launchRecord) launchOnlyIds.add(runId); else launchOnlyIds.delete(runId);
86
+ starts.set(runId, { runId, startedAt });
76
87
  unreadable.delete(runId);
77
88
  }
78
89
  }
79
90
  debug.log('loop.orchestrator', 'finish-launch-population', {
80
- directories: directories.size + unresolvedDirectories, launched: starts.size, childLedgers: childLedgers.size, unreadable: unreadable.size,
91
+ directories: directories.size + unresolvedDirectories, launched: starts.size, launchOnly: launchOnlyIds.size, childLedgers: childLedgers.size, unreadable: unreadable.size,
81
92
  });
82
93
  return { starts: [...starts.values()], unreadable: [...unreadable.values()], unreadableDirectories };
83
94
  }
@@ -8,9 +8,11 @@ import { effectiveInstanceRoot } from '../../instance/resolve.js';
8
8
  import { getUserConfig } from '../../user-config.js';
9
9
  import { debug } from '../../debug/log.js';
10
10
  import { listSelfDevRuns, runSummaryLine, selfDevRunsDir } from '../../self-dev/run-store.js';
11
+ import { planAction, runSeatLoopOnce, type SeatItem } from '../../seat-loop/seat-loop.js';
12
+ import { harnessQueueOutcome } from '../../harness/harness-queue.js';
11
13
 
12
14
  export type Window = '08' | '12' | '18';
13
- export type Node = 'intake' | 'split' | 'place' | 'delegate' | 'reconcile' | 'report';
15
+ export type Node = 'intake' | 'split' | 'place' | 'delegate' | 'launch' | 'reconcile' | 'report';
14
16
  export type Mode = 'off' | 'shadow' | 'live';
15
17
  const SEAT_LOOP_IDS: Readonly<Record<string, string>> = { OP: 'op-seat', MK: 'cmo-seat', TC: 'tc-seat', UX: 'ux-seat' };
16
18
  export interface Cell { id: string; title: string; seat?: string; host?: string; version?: string; cardId: string; origin: 'candidate' | 'flow1a'; priority?: 'P0' | 'P1' | 'P2'; predecessors?: string[] }
@@ -19,6 +21,9 @@ export interface TickState {
19
21
  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 }>;
20
22
  reconciled: { reached: number; progressing: number; blocked: number; unknown: number };
21
23
  runSummaries?: string[];
24
+ launched?: Array<{ seat: string; status: string; key?: string; queueId?: string; runId?: string }>;
25
+ queueOutcomes?: Array<{ queueId: string; key?: string; outcome: string }>;
26
+ steps: Array<{ node: Node; reason: string; count: number; targetLoopId?: string }>;
22
27
  nodes: Partial<Record<Node, 'ok' | 'skipped'>>;
23
28
  missing: string[];
24
29
  }
@@ -26,11 +31,13 @@ export interface TickState {
26
31
  export type SplitAdapter = (card: TaskCard, opts?: { shadow?: boolean }) => Promise<Array<{ id: string; title: string; seat?: string; host?: string }>> | Array<{ id: string; title: string; seat?: string; host?: string }>;
27
32
  /** RELPLAN1 `placeCell(input: PlacementCell)` — owner·priority·predecessors are required; the result carries no seat. */
28
33
  export type PlacementInput = { id: string; title: string; owner: string; priority: 'P0' | 'P1' | 'P2'; predecessors: string[] };
29
- export type PlaceAdapter = (cell: PlacementInput) => Promise<{ version?: string } | null> | { version?: string } | null;
34
+ export type PlaceAdapter = (cell: PlacementInput, deps?: { dryRun?: boolean; now?: Date }) => Promise<{ version?: string } | null> | { version?: string } | null;
30
35
  export type RebalanceAdapter = (version: string) => Promise<{ decisions: unknown[]; blocked: Array<{ id: string; reason: string }> }> | { decisions: unknown[]; blocked: Array<{ id: string; reason: string }> };
31
36
  export interface TickDeps {
32
37
  root?: string; now?: Date; mode?: Mode; runId?: string; window?: Window;
33
38
  cards?: () => TaskCard[]; split?: SplitAdapter; placeCell?: PlaceAdapter;
39
+ seatTurn?: (seat: string, root: string) => Promise<{ status: string; queueId?: string; runId?: string; item?: { source: string; id: string } }>;
40
+ queueOutcome?: (queueId: string, root: string) => 'pending' | 'unknown' | 'retryable' | 'succeeded';
34
41
  loadAdapter?: (path: string, name: string) => Promise<unknown>;
35
42
  checklist?: (version: string) => Pick<ChecklistItem, 'id' | 'status'>[];
36
43
  observe?: (event: string, data: { loopId: string; runId: string; window: Window; node: Node; reason: string; count: number; targetLoopId?: string }) => void;
@@ -54,7 +61,10 @@ export function wishCards(cards: readonly TaskCard[]): TaskCard[] {
54
61
  return cards.filter(card => card.status === 'open' && card.goalId.startsWith('wish:') &&
55
62
  !card.sections.some(section => section.key === 'orch:split' || section.key.startsWith('orch:split:')));
56
63
  }
57
- export function reconciliation(cells: readonly Cell[], requests: readonly { key: string; status: string }[], checklist: (version: string) => Pick<ChecklistItem, 'id' | 'status'>[]): TickState['reconciled'] {
64
+ export function reconciliation(cells: readonly Cell[], requests: readonly { key: string; status: string }[], checklist: (version: string) => Pick<ChecklistItem, 'id' | 'status'>[],
65
+ queue: readonly { key: string; outcome: string }[] = []): TickState['reconciled'] {
66
+ // 체크리스트가 정본이고, 칸이 아직 체크리스트에 없을 때만 «그 칸의 의뢰 키»로 발사된 대기열 결과를 본다.
67
+ const queueByKey = new Map(queue.map(row => [row.key, row.outcome]));
58
68
  const result = { reached: 0, progressing: 0, blocked: 0, unknown: 0 };
59
69
  const byVersion = new Map<string, Pick<ChecklistItem, 'id' | 'status'>[]>();
60
70
  const keys = new Set(requests.filter(row => row.status === 'queued').map(row => row.key));
@@ -65,7 +75,12 @@ export function reconciliation(cells: readonly Cell[], requests: readonly { key:
65
75
  if (status === 'green' || status === 'done') result.reached++;
66
76
  else if (status === 'yellow') result.progressing++;
67
77
  else if (status === 'red') result.blocked++;
68
- else result.unknown++;
78
+ else {
79
+ const outcome = queueByKey.get(`orch:${cell.cardId}:${cell.id}`);
80
+ if (outcome === 'pending' || outcome === 'succeeded') result.progressing++;
81
+ else if (outcome === 'retryable') result.blocked++;
82
+ else result.unknown++;
83
+ }
69
84
  }
70
85
  return result;
71
86
  }
@@ -116,7 +131,7 @@ function stateFile(root: string, runId: string): string {
116
131
  if (!/^[a-zA-Z0-9._-]+$/.test(runId) || runId === '.' || runId === '..') throw new Error('Invalid orchestrator runId');
117
132
  return join(root, 'loop', 'orchestrator', `${runId}.json`);
118
133
  }
119
- function restorePrevious(root: string, day: string, mode: Mode): Pick<TickState, 'cards' | 'cells'> | null {
134
+ function restorePrevious(root: string, day: string, mode: Mode): Pick<TickState, 'cards' | 'cells' | 'launched'> | null {
120
135
  const dir = join(root, 'loop', 'orchestrator');
121
136
  if (!existsSync(dir)) return null;
122
137
  const states = readdirSync(dir).filter(name => /^[a-zA-Z0-9._-]+\.json$/.test(name))
@@ -126,15 +141,17 @@ function restorePrevious(root: string, day: string, mode: Mode): Pick<TickState,
126
141
  states.sort((a, b) => a.name.localeCompare(b.name));
127
142
  const cards = new Map<string, TaskCard>();
128
143
  const cells = new Map<string, Cell>();
144
+ const launched = new Map<string, NonNullable<TickState['launched']>[number]>();
129
145
  for (const { state } of states) {
130
146
  for (const card of state.cards) cards.set(card.id, card);
131
147
  for (const cell of state.cells) cells.set(`${cell.cardId}:${cell.id}`, cell);
148
+ for (const turn of state.launched ?? []) if (turn.queueId) launched.set(turn.queueId, turn);
132
149
  }
133
- return { cards: [...cards.values()], cells: [...cells.values()] };
150
+ return { cards: [...cards.values()], cells: [...cells.values()], launched: [...launched.values()] };
134
151
  }
135
152
  function initial(runId: string, window: Window, mode: Mode, day: string): TickState {
136
153
  return { runId, window, mode, day, cards: [], skippedCards: [], cells: [], placed: 0, delegated: 0, wouldDelegate: 0,
137
- reconciled: { reached: 0, progressing: 0, blocked: 0, unknown: 0 }, nodes: {}, missing: [] };
154
+ reconciled: { reached: 0, progressing: 0, blocked: 0, unknown: 0 }, steps: [], nodes: {}, missing: [] };
138
155
  }
139
156
  function graphRunId(): string | undefined {
140
157
  const context = process.env.ELANOUS_GRAPH_CONTEXT;
@@ -163,8 +180,9 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
163
180
  const configured = deps.mode ?? getUserConfig().loops?.orchestrator?.mode;
164
181
  const mode: Mode = configured === 'off' || configured === 'live' ? configured : 'shadow';
165
182
  const state: TickState = existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) as TickState : initial(runId, window, mode, kstDay(now));
183
+ state.steps ??= [];
166
184
  if (state.window !== window || state.mode !== mode) throw new Error('orchestrator run window/mode changed');
167
- const predecessor: Partial<Record<Node, Node>> = { split: 'intake', place: 'split', delegate: 'place', reconcile: 'delegate', report: 'reconcile' };
185
+ const predecessor: Partial<Record<Node, Node>> = { split: 'intake', place: 'split', delegate: 'place', launch: 'delegate', reconcile: 'launch', report: 'reconcile' };
168
186
  const preceding = predecessor[node];
169
187
  if (preceding && !state.nodes[preceding]) throw new Error(`orchestrator ${node}: missing ${preceding} result`);
170
188
  const loadAdapter = deps.loadAdapter ?? optionalAdapter;
@@ -174,6 +192,7 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
174
192
  };
175
193
  const observe = (event: string, reason = '', count = 0, targetLoopId?: string): void => {
176
194
  const data = { loopId: 'orchestrator', runId, window, node, reason, count, ...(targetLoopId ? { targetLoopId } : {}) };
195
+ if (event !== 'tick') state.steps.push({ node, reason, count, ...(targetLoopId ? { targetLoopId } : {}) });
177
196
  try { (deps.observe ?? ((name, payload) => debug.log('loop.orchestrator', name, payload)))(event, data); }
178
197
  catch { /* observation must not change the run */ }
179
198
  };
@@ -198,6 +217,7 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
198
217
  if (!cell.id?.trim() || !cell.title?.trim()) throw new Error('invalid split cell');
199
218
  state.cells.push({ cardId: card.id, id: cell.id, title: cell.title, origin: split ? 'flow1a' : 'candidate', ...(cell.seat ? { seat: cell.seat } : {}), ...(cell.host !== undefined ? { host: cell.host } : {}) });
200
219
  }
220
+ observe('exchange', `split:${card.id}`, cells.length);
201
221
  if (split && cells.length && mode === 'live') {
202
222
  const store = new CardStore(root);
203
223
  try {
@@ -209,15 +229,24 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
209
229
  const place = deps.placeCell ?? await adapter<PlaceAdapter>('../../release-loop/placement.js', 'placeCell');
210
230
  const available = typeof place === 'function';
211
231
  if (!available) { state.missing.push('relplan1-absent'); observe('node-missing', 'relplan1-absent', state.cells.length); }
212
- else if (mode === 'shadow') observe('exchange', 'relplan1-shadow-not-invoked', state.cells.length);
213
- if (mode === 'live' && available) for (const cell of state.cells) {
214
- if (cell.origin !== 'flow1a') continue;
215
- // TC review must-fix ②: map to the placer's required shape; a cell without an owner is unplaced, not a crash.
216
- if (!cell.seat) { state.unplaced = (state.unplaced ?? 0) + 1; observe('exchange', 'unplaced-no-owner', 1); continue; }
217
- const placed = await place({ id: cell.id, title: cell.title, owner: cell.seat, priority: cell.priority ?? 'P2', predecessors: cell.predecessors ?? [] });
218
- if (validVersion(placed?.version)) {
219
- cell.version = placed.version;
220
- state.placed++;
232
+ if (mode === 'shadow' && deps.placeCell) observe('exchange', 'relplan1-shadow-not-invoked', state.cells.length);
233
+ // 담당 없는 칸은 배치를 부르든 안 부르든 센다 — 배치 호출 여부와 집계를 묶지 않는다.
234
+ for (const cell of state.cells) {
235
+ if (cell.origin === 'flow1a' && (!cell.seat || !SEAT_LOOP_IDS[cell.seat])) { state.unplaced = (state.unplaced ?? 0) + 1; observe('exchange', 'unplaced-no-owner', 1); }
236
+ }
237
+ if (available && (mode === 'live' || !deps.placeCell)) for (const cell of state.cells) {
238
+ if (cell.origin !== 'flow1a' || !cell.seat || !SEAT_LOOP_IDS[cell.seat]) continue;
239
+ // RELPLAN's dryRun calculates the same release without writing a checklist in shadow.
240
+ try {
241
+ const placed = await place({ id: cell.id, title: cell.title, owner: cell.seat, priority: cell.priority ?? 'P2', predecessors: cell.predecessors ?? [] }, { dryRun: mode === 'shadow', now });
242
+ if (validVersion(placed?.version)) {
243
+ cell.version = placed.version;
244
+ state.placed++;
245
+ observe('exchange', mode === 'shadow' ? `would-place:${cell.id}:${cell.version}` : `placed:${cell.id}:${cell.version}`, 1);
246
+ } else { state.unplaced = (state.unplaced ?? 0) + 1; observe('exchange', 'unplaced-no-release', 1); }
247
+ } catch (error) {
248
+ state.unplaced = (state.unplaced ?? 0) + 1;
249
+ observe('exchange', `unplaced:${cell.id}:${String(error).slice(0, 160)}`, 1);
221
250
  }
222
251
  }
223
252
  } else if (node === 'delegate') {
@@ -229,9 +258,11 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
229
258
  if (!targetLoopId) continue;
230
259
  const key = `orch:${cell.cardId}:${cell.id}`;
231
260
  if (keys.has(key)) continue;
232
- // Shadow shows what it would hand over even before a release is chosen (placement is not invoked in shadow).
233
- if (mode === 'shadow') { keys.add(key); state.wouldDelegate++; observe('would-delegate', validVersion(cell.version) ? key : `${key} (unplaced)`, 1, targetLoopId); continue; }
234
- if (!validVersion(cell.version)) continue;
261
+ if (!validVersion(cell.version)) {
262
+ if (mode === 'shadow') { keys.add(key); state.wouldDelegate++; observe('would-delegate', `${key} (unplaced)`, 1, targetLoopId); }
263
+ continue;
264
+ }
265
+ if (mode === 'shadow') { keys.add(key); state.wouldDelegate++; observe('would-delegate', key, 1, targetLoopId); continue; }
235
266
  keys.add(key);
236
267
  mkdirSync(dirname(path), { recursive: true });
237
268
  // TC review must-fix ①: the seat-requests journal contract needs receiptId, and appends are serialized under the
@@ -249,13 +280,53 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
249
280
  state.delegated++;
250
281
  observe('exchange', 'queued', 1, targetLoopId);
251
282
  }
283
+ } else if (node === 'launch') {
284
+ const eligible = state.cells.filter(cell => cell.origin === 'flow1a' && cell.seat && SEAT_LOOP_IDS[cell.seat]
285
+ && validVersion(cell.version) && (mode === 'shadow' || readRequests(root).some(row => row.key === `orch:${cell.cardId}:${cell.id}` && row.status === 'queued')));
286
+ const seats = [...new Set(eligible.map(cell => cell.seat!))];
287
+ state.launched = [];
288
+ for (const seat of seats) {
289
+ if (mode === 'shadow') {
290
+ const cell = eligible.find(row => row.seat === seat)!;
291
+ const item: SeatItem = { source: 'request', id: `orch:${cell.cardId}:${cell.id}`, title: cell.title, text: cell.title };
292
+ const action = planAction(item, seat);
293
+ state.launched.push({ seat, key: item.id, status: action.kind === 'harness' ? 'would-launch' : `would-${action.kind}` });
294
+ observe('exchange', `would-${action.kind}:${seat}`, 1, SEAT_LOOP_IDS[seat]);
295
+ } else {
296
+ const turn = await (deps.seatTurn ?? ((assigned: string, stateRoot: string) => runSeatLoopOnce(assigned, {
297
+ root: stateRoot, config: { ...getUserConfig().loops?.seat, mode: 'live-safe', seats: [assigned] },
298
+ })))(seat, root);
299
+ // 자리 턴이 «이 틱이 넣은 의뢰»를 처리했을 때만 칸에 잇는다 — 대기열의 다른 의뢰는 이 카드의 발사가 아니다.
300
+ const targets = new Set(eligible.filter(row => row.seat === seat).map(row => `orch:${row.cardId}:${row.id}`));
301
+ const handled = 'item' in turn && turn.item?.source === 'request' ? turn.item.id : undefined;
302
+ const key = handled && targets.has(handled) ? handled : undefined;
303
+ if (handled && !key) observe('exchange', `seat-turn-other-request:${seat}`, 1, SEAT_LOOP_IDS[seat]);
304
+ state.launched.push({ seat, status: turn.status, ...(key ? { key } : {}),
305
+ ...('queueId' in turn && turn.queueId ? { queueId: turn.queueId } : {}),
306
+ ...('runId' in turn && turn.runId ? { runId: turn.runId } : {}) });
307
+ observe('exchange', `seat-turn:${seat}:${turn.status}`, 1, SEAT_LOOP_IDS[seat]);
308
+ }
309
+ }
252
310
  } else if (node === 'reconcile') {
253
- if (window !== '08' && state.cells.length === 0) {
311
+ if (window !== '08') {
312
+ // 새 카드가 있어도 앞 틱의 의뢰·발사를 같이 대조한다 — 지금 틱 것이 같은 키를 이긴다.
254
313
  const previous = restorePrevious(root, state.day, mode);
255
- if (previous) { state.cards = previous.cards; state.cells = previous.cells; }
314
+ if (previous) {
315
+ const merge = <T>(older: readonly T[], newer: readonly T[], key: (row: T) => string) =>
316
+ [...new Map([...older, ...newer].map(row => [key(row), row])).values()];
317
+ state.cards = merge(previous.cards, state.cards, card => card.id);
318
+ state.cells = merge(previous.cells, state.cells, cell => `${cell.cardId}:${cell.id}`);
319
+ state.launched = merge(previous.launched ?? [], state.launched ?? [], turn => turn.queueId ?? `${turn.seat}:${turn.key ?? ''}`);
320
+ }
256
321
  }
322
+ state.queueOutcomes = (state.launched ?? []).filter(turn => turn.queueId).map(turn => {
323
+ const outcome = (deps.queueOutcome ?? ((id: string, stateRoot: string) => harnessQueueOutcome(id, { root: stateRoot })))(turn.queueId!, root);
324
+ observe('exchange', `queue-outcome:${turn.queueId}:${outcome}`, 1, SEAT_LOOP_IDS[turn.seat]);
325
+ return { queueId: turn.queueId!, ...(turn.key ? { key: turn.key } : {}), outcome };
326
+ });
257
327
  const requests = readRequests(root);
258
- state.reconciled = reconciliation(state.cells, requests, mode === 'shadow' ? version => readChecklist(root, version) : (deps.checklist ?? (version => readChecklist(root, version))));
328
+ state.reconciled = reconciliation(state.cells, requests, mode === 'shadow' ? version => readChecklist(root, version) : (deps.checklist ?? (version => readChecklist(root, version))),
329
+ state.queueOutcomes.flatMap(row => row.key ? [{ key: row.key, outcome: row.outcome }] : []));
259
330
  const from = new Date(`${state.day}T00:00:00+09:00`).getTime();
260
331
  state.runSummaries = listSelfDevRuns(selfDevRunsDir(root))
261
332
  .filter(run => run.updatedAt >= from && run.updatedAt < from + 86_400_000)
@@ -295,9 +366,9 @@ if (import.meta.main) {
295
366
  const node = process.argv[2];
296
367
  const index = process.argv.indexOf('--window');
297
368
  const window = index < 0 ? undefined : process.argv[index + 1];
298
- if (!['intake', 'split', 'place', 'delegate', 'reconcile', 'report'].includes(node ?? '') ||
369
+ if (!['intake', 'split', 'place', 'delegate', 'launch', 'reconcile', 'report'].includes(node ?? '') ||
299
370
  (window !== undefined && !['08', '12', '18'].includes(window))) {
300
- console.error('orchestrator: expected <intake|split|place|delegate|reconcile|report> [--window 08|12|18]');
371
+ console.error('orchestrator: expected <intake|split|place|delegate|launch|reconcile|report> [--window 08|12|18]');
301
372
  process.exitCode = 2;
302
373
  } else {
303
374
  void (async () => {
@@ -1,6 +1,7 @@
1
1
  import type { SlashCommand } from '../chat/index.js';
2
2
  import type { Maturity } from './feature-maturity.js';
3
3
  import type { BotSurface } from './bot-command-maturity.js';
4
+ import { botUnavailableSlashReply, deriveTuiSlashAvailability, formatBotUnavailableSlashReply } from './tui-slash-availability.js';
4
5
 
5
6
  export interface BotSlashCatalogEntry {
6
7
  name: string;
@@ -56,11 +57,19 @@ export function buildBotSlashCatalog(options: {
56
57
  commands.push({ name, description: `${description.slice(0, 100 - suffix.length)}${suffix}`, supported: false });
57
58
  seen.add(name);
58
59
  }
59
- const unsupported = new Set(commands.filter((command) => !command.supported).map((command) => command.name));
60
+ const handled = new Set(handledCommands.map(({ name }) => name));
61
+ const unsupported = new Set(commands.filter(({ supported }) => !supported).map(({ name }) => name));
62
+ const availability = new Map(deriveTuiSlashAvailability(
63
+ coreCommands.filter(({ name }) => Object.hasOwn(maturity.tuiSlash, name)), maturity,
64
+ ).map((entry) => [entry.name, entry]));
60
65
  return {
61
66
  commands,
62
67
  unsupportedReply(name) {
63
- return unsupported.has(name) ? `/${name}은(는) ${surface === 'telegram' ? '텔레그램' : '디스코드'}에서 아직 지원되지 않습니다. TUI에서 /${name}을(를) 사용하세요.` : null;
68
+ if (handled.has(name)) return null;
69
+ const entry = availability.get(name);
70
+ if (entry?.maturity === 'system') return null;
71
+ if (unsupported.has(name)) return formatBotUnavailableSlashReply(name, surface);
72
+ return botUnavailableSlashReply(entry, surface);
64
73
  },
65
74
  };
66
75
  }
@@ -59,7 +59,7 @@ export const FEATURE_MATURITY = {
59
59
  pty: 'beta', pr: 'beta', repo: 'beta', role: 'beta', machine: 'beta', 'agent-env': 'beta',
60
60
  where: 'stable', shadow: 'beta', llm: 'beta', storage: 'beta', leader: 'beta',
61
61
  pod: 'beta', browser: 'beta', questions: 'beta', usage: 'stable', live: 'beta',
62
- research: 'beta', release: 'beta', freeze: 'beta', flow: 'beta', seat: 'beta', hq: 'beta', steward: 'beta', coord: 'beta', context: 'beta', 'model-watch': 'beta', doctor: 'stable',
62
+ 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',
63
63
  control: 'beta', resources: 'beta', hooks: 'beta', setup: 'stable', start: 'stable',
64
64
  grounding: 'beta', graph: 'beta', loop: 'stable', card: 'stable', 'launch-head': 'beta',
65
65
  a2a: 'beta', plugin: 'stable', make: 'beta', skills: 'beta', connect: 'beta', import: 'beta',
@@ -75,7 +75,7 @@ export const FEATURE_MATURITY = {
75
75
  schedule: 'beta', intake: 'beta', logs: 'stable', ad: 'beta', docs: 'beta',
76
76
  decisions: 'beta', directives: 'beta', claims: 'beta', lesson: 'beta', fleet: 'beta', ops: 'stable', outputs: 'beta', publish: 'beta',
77
77
  env: 'beta', 'agent-mission': 'beta', dev: 'beta', keys: 'beta', finance: 'beta',
78
- config: 'stable', tasks: 'stable', labels: 'beta', msg: 'beta', ask: 'stable',
78
+ config: 'stable', tasks: 'stable', brief: 'beta', labels: 'beta', msg: 'beta', ask: 'stable',
79
79
  onboarding: 'beta', login: 'stable', session: 'beta', chat: 'stable', repl: 'stable',
80
80
  agent: 'stable', registry: 'beta', tier: 'stable', local: 'beta', scheduler: 'beta',
81
81
  attach: 'beta', wf: 'beta', nexus: 'stable', phone: 'beta', token: 'beta',