elanous 0.2.14 → 0.2.16

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-6e7e0377c1db0bdc.js → 1307-b46d45b705e8c9ae.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/{9103-7a3ae7f60af3b99f.js → 9103-60fbef33e66f0a46.js} +1 -1
  7. package/apps/pwa/out/_next/static/chunks/app/approvals/page-79c3ed84baaedd03.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-7b2b4f378354869a.js → page-3cb52e5ea8fc2fa5.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/botlab/page-c1e20d7d338713f1.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/app/bots/{page-1512d91c7491ed62.js → page-482c5dbde699947c.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/app/ceo/page-1e962b31d1f11081.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/app/chat/{page-a627db94ec6904a0.js → page-8c2d9526164f5d09.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/app/control/{page-e6b84ab07d3567d5.js → page-a0e2a6d1bfb52fc5.js} +1 -1
  16. package/apps/pwa/out/_next/static/chunks/app/design-check/page-c53598cfce6156f2.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/editor/{page-fed69f9759d248d0.js → page-13a63953f53c6a23.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/exec/{page-15374909d8f1aebd.js → page-5d3c8429d04f3831.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/field/{page-012a742f81218b01.js → page-3193bdc0bbab047a.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/inside/page-76270efaee638f17.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/app/intake/{page-99effd9d893a43dc.js → page-6107ca8ddd5e3bdd.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/{layout-938de57ae0315e46.js → layout-dce686e582c23937.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/live/page-d3d53b146416e422.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/app/loops/{page-160089123a40dd91.js → page-1a61c7ca1ceb3ae7.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/app/market/{page-629e1ede482ec6eb.js → page-51f811fa119308b6.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-dacff46049e1557e.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/morning/{page-27e99b4e54a849d4.js → page-4167348049166852.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/not-found-de52e8fdf9095f7a.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/observatory/page-36a7d994f4ca0299.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-32cde713be47cddb.js → page-ca3952cd78a83ca2.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-e7e10cd3386222e5.js → page-5b042736aa113756.js} +1 -1
  32. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-acb749f1e432bcf3.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-e64caff084f7ce21.js → page-1ebfe049c640e171.js} +1 -1
  36. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-db07eaffba0df7e3.js → page-57924393e3e4e539.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-31fa1ce4c4aff9ae.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/settings/{page-86f91034bd5cabe5.js → page-0f5cd57ca928f42b.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-abe8e3b74bea1ffc.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/share/{page-ce4d8a1215ce53a3.js → page-d28573d6792b4275.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-58043db78ba519ae.js → page-59f03fda76c03d22.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-357750dfb91bf960.js → page-d751de5f8062ff5e.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/app/term/{page-274703bc41b71333.js → page-fe1759f94003b32b.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/today/page-d64c984e855c8c68.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/vault/{page-3b7e821dc492bdfd.js → page-9afdee66b820c76e.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d4aef67abf264874.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-f8da5dc6f3752276.js → page-1ea64acadd704ace.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-2f810b18d89ecba6.js → page-3544f154c87b43ee.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-30c2073bfc21c004.js +1 -0
  53. package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +1 -0
  54. package/apps/pwa/out/_next/static/css/{04dabcbcc4bfa6db.css → 085673d826b98b62.css} +1 -1
  55. package/apps/pwa/out/_next/static/rv9cierV0jSOvDBm1g0XB/_buildManifest.js +1 -0
  56. package/apps/pwa/out/approvals/index.html +1 -1
  57. package/apps/pwa/out/approvals/index.txt +15 -15
  58. package/apps/pwa/out/autopilot/index.html +1 -1
  59. package/apps/pwa/out/autopilot/index.txt +15 -15
  60. package/apps/pwa/out/board/index.html +1 -1
  61. package/apps/pwa/out/board/index.txt +15 -15
  62. package/apps/pwa/out/botlab/index.html +1 -1
  63. package/apps/pwa/out/botlab/index.txt +15 -15
  64. package/apps/pwa/out/bots/index.html +1 -1
  65. package/apps/pwa/out/bots/index.txt +15 -15
  66. package/apps/pwa/out/ceo/index.html +1 -0
  67. package/apps/pwa/out/ceo/index.txt +33 -0
  68. package/apps/pwa/out/chat/index.html +1 -1
  69. package/apps/pwa/out/chat/index.txt +15 -15
  70. package/apps/pwa/out/consult/index.html +1 -1
  71. package/apps/pwa/out/consult/index.txt +15 -15
  72. package/apps/pwa/out/control/index.html +1 -1
  73. package/apps/pwa/out/control/index.txt +15 -15
  74. package/apps/pwa/out/design-check/index.html +1 -1
  75. package/apps/pwa/out/design-check/index.txt +15 -15
  76. package/apps/pwa/out/editor/index.html +1 -1
  77. package/apps/pwa/out/editor/index.txt +15 -15
  78. package/apps/pwa/out/exec/index.html +1 -1
  79. package/apps/pwa/out/exec/index.txt +15 -15
  80. package/apps/pwa/out/field/index.html +1 -1
  81. package/apps/pwa/out/field/index.txt +15 -15
  82. package/apps/pwa/out/healthz +1 -1
  83. package/apps/pwa/out/index.html +1 -1
  84. package/apps/pwa/out/index.txt +15 -15
  85. package/apps/pwa/out/inside/index.html +1 -1
  86. package/apps/pwa/out/inside/index.txt +15 -15
  87. package/apps/pwa/out/intake/index.html +1 -1
  88. package/apps/pwa/out/intake/index.txt +15 -15
  89. package/apps/pwa/out/live/index.html +1 -1
  90. package/apps/pwa/out/live/index.txt +15 -15
  91. package/apps/pwa/out/loops/index.html +1 -1
  92. package/apps/pwa/out/loops/index.txt +15 -15
  93. package/apps/pwa/out/market/index.html +1 -1
  94. package/apps/pwa/out/market/index.txt +15 -15
  95. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  96. package/apps/pwa/out/missions/placeholder/index.txt +15 -15
  97. package/apps/pwa/out/morning/index.html +1 -1
  98. package/apps/pwa/out/morning/index.txt +15 -15
  99. package/apps/pwa/out/observatory/index.html +1 -1
  100. package/apps/pwa/out/observatory/index.txt +15 -15
  101. package/apps/pwa/out/ops/checklist/index.html +1 -1
  102. package/apps/pwa/out/ops/checklist/index.txt +15 -15
  103. package/apps/pwa/out/ops/release/index.html +1 -1
  104. package/apps/pwa/out/ops/release/index.txt +15 -15
  105. package/apps/pwa/out/ops/seats/index.html +1 -1
  106. package/apps/pwa/out/ops/seats/index.txt +15 -15
  107. package/apps/pwa/out/outputs/index.html +1 -1
  108. package/apps/pwa/out/outputs/index.txt +15 -15
  109. package/apps/pwa/out/reflection/index.html +1 -1
  110. package/apps/pwa/out/reflection/index.txt +16 -16
  111. package/apps/pwa/out/scheduler/index.html +1 -1
  112. package/apps/pwa/out/scheduler/index.txt +15 -15
  113. package/apps/pwa/out/sessions/index.html +1 -1
  114. package/apps/pwa/out/sessions/index.txt +15 -15
  115. package/apps/pwa/out/settings/devices/index.html +1 -1
  116. package/apps/pwa/out/settings/devices/index.txt +15 -15
  117. package/apps/pwa/out/settings/index.html +1 -1
  118. package/apps/pwa/out/settings/index.txt +15 -15
  119. package/apps/pwa/out/setup/done/index.html +1 -1
  120. package/apps/pwa/out/setup/done/index.txt +15 -15
  121. package/apps/pwa/out/setup/index.html +1 -1
  122. package/apps/pwa/out/setup/index.txt +15 -15
  123. package/apps/pwa/out/share/index.html +1 -1
  124. package/apps/pwa/out/share/index.txt +15 -15
  125. package/apps/pwa/out/showroom/index.html +1 -1
  126. package/apps/pwa/out/showroom/index.txt +15 -15
  127. package/apps/pwa/out/tasks/index.html +1 -1
  128. package/apps/pwa/out/tasks/index.txt +15 -15
  129. package/apps/pwa/out/term/index.html +1 -1
  130. package/apps/pwa/out/term/index.txt +15 -15
  131. package/apps/pwa/out/today/index.html +1 -1
  132. package/apps/pwa/out/today/index.txt +15 -15
  133. package/apps/pwa/out/trace/index.html +1 -1
  134. package/apps/pwa/out/trace/index.txt +15 -15
  135. package/apps/pwa/out/vault/index.html +1 -1
  136. package/apps/pwa/out/vault/index.txt +15 -15
  137. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  138. package/apps/pwa/out/workflows/chat-ui/index.txt +15 -15
  139. package/apps/pwa/out/workflows/index.html +1 -1
  140. package/apps/pwa/out/workflows/index.txt +15 -15
  141. package/apps/pwa/out/workspace/index.html +1 -1
  142. package/apps/pwa/out/workspace/index.txt +15 -15
  143. package/apps/pwa/out/worktrees/index.html +1 -1
  144. package/apps/pwa/out/worktrees/index.txt +15 -15
  145. package/graphs/doc-gen/doc-gen.yaml +24 -0
  146. package/graphs/doc-gen/recipes.yaml +3 -0
  147. package/graphs/heal/heal-loop.yaml +1 -1
  148. package/graphs/implement-loop.yaml +1 -1
  149. package/graphs/orchestrator/orchestrator.yaml +7 -0
  150. package/graphs/orchestrator/recipes.yaml +3 -0
  151. package/graphs/release/recipes.yaml +3 -0
  152. package/graphs/release/release-loop.yaml +4 -2
  153. package/graphs/steward/recipes.yaml +2 -1
  154. package/package.json +1 -1
  155. package/scripts/brand/check.ts +7 -5
  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 +82 -31
  159. package/scripts/docs/doc-rot.ts +164 -0
  160. package/scripts/docs/generate-graph-anatomy.ts +437 -0
  161. package/scripts/docs-cli-check.ts +4 -4
  162. package/scripts/expose-rubric-suggest.ts +18 -12
  163. package/scripts/goals/goal-census.ts +343 -0
  164. package/scripts/lecture-note/node.ts +31 -11
  165. package/scripts/lessons/handbook-page.ts +30 -3
  166. package/scripts/marketing/generate-ax-brief.ts +140 -0
  167. package/scripts/marketing/generate-feature-map.ts +118 -0
  168. package/scripts/measure/author-section-contribution.ts +84 -0
  169. package/scripts/release-loop/checklist-gate-node.ts +53 -14
  170. package/scripts/release-loop/cut-branch.ts +43 -13
  171. package/scripts/release-loop/gate-node.ts +82 -14
  172. package/scripts/release-loop/vault-note-node.ts +257 -8
  173. package/scripts/release-story/draft.ts +57 -9
  174. package/scripts/rhythm/daily.ts +263 -76
  175. package/scripts/rhythm/monthly.ts +97 -0
  176. package/scripts/rhythm/weekly.ts +30 -9
  177. package/scripts/smart-merge/resync-shadow.ts +147 -0
  178. package/src/acp/client.ts +17 -2
  179. package/src/agent-env/profile.ts +161 -0
  180. package/src/agent-mission/driver.ts +51 -1
  181. package/src/agent-mission/supervisor-metrics-cli.ts +40 -0
  182. package/src/agent-mission/supervisor-metrics.ts +218 -0
  183. package/src/agent-substrate/pr-reviewer.ts +2 -0
  184. package/src/ask-user-question/pending-questions.ts +5 -0
  185. package/src/auth/github-app-token.ts +112 -13
  186. package/src/autopilot/build/llm-conflict-merge.ts +32 -6
  187. package/src/autopilot/track-agent-shadow.ts +2 -1
  188. package/src/briefing/brief-items.ts +276 -0
  189. package/src/budget/usage-outcomes.ts +204 -0
  190. package/src/claims/claims-render.ts +14 -5
  191. package/src/cli/agent-env-cli.ts +21 -0
  192. package/src/cli/brief-cli.ts +73 -0
  193. package/src/cli/decisions-cli.ts +9 -0
  194. package/src/cli/doctor-cli.ts +23 -1
  195. package/src/cli/doctor-node-modules.ts +147 -0
  196. package/src/cli/intake-cli.ts +46 -17
  197. package/src/cli/know-cli.ts +20 -0
  198. package/src/cli/logs-cli.ts +43 -1
  199. package/src/cli/pod-cli.ts +2 -0
  200. package/src/cli/pr-cli.ts +80 -12
  201. package/src/cli/release-cli.ts +66 -29
  202. package/src/cli/seat-requests-cli.ts +52 -0
  203. package/src/cli/self-update.ts +200 -1
  204. package/src/cli/usage-cli.ts +26 -0
  205. package/src/context-bus/context-now-public.ts +16 -6
  206. package/src/context-bus/context-now.ts +3 -1
  207. package/src/context-bus/coord-events.ts +6 -4
  208. package/src/decisions/decision-cards.ts +33 -7
  209. package/src/decisions/proact-meter.ts +286 -0
  210. package/src/decisions/proact1-lite.ts +86 -0
  211. package/src/decisions/web-push-decision-cards.ts +1 -1
  212. package/src/discord-self-message.ts +9 -0
  213. package/src/domains/alert-audit.ts +271 -0
  214. package/src/domains/core-tools.ts +4 -3
  215. package/src/domains/ops-facts-tool.ts +31 -1
  216. package/src/flow-loop/split.ts +24 -0
  217. package/src/harness/harness-cli-command.ts +147 -17
  218. package/src/harness/harness-incidents.ts +2 -0
  219. package/src/harness/harness-pod-dispatch.ts +67 -8
  220. package/src/harness/harness-queue.ts +380 -50
  221. package/src/harness/harness-seams.ts +13 -3
  222. package/src/harness/helper-repair.ts +218 -0
  223. package/src/harness/helper-scan.ts +27 -4
  224. package/src/harness/launch-stamp.ts +236 -0
  225. package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
  226. package/src/harness/ledger-write-guard.ts +35 -0
  227. package/src/harness/nested-elanous-policy.ts +114 -0
  228. package/src/harness/pod-memory-policy.ts +15 -0
  229. package/src/harness/repository-name.ts +55 -0
  230. package/src/hq/fence-audit.ts +90 -0
  231. package/src/hq/hq.ts +70 -6
  232. package/src/hq/lease.ts +9 -7
  233. package/src/hq/move-plan.ts +126 -0
  234. package/src/hq/promote.ts +31 -0
  235. package/src/hq/transfer.ts +185 -0
  236. package/src/index.ts +189 -13
  237. package/src/intake-plane/check.ts +63 -0
  238. package/src/intake-plane/digest.ts +65 -21
  239. package/src/intake-plane/front-classifier.ts +6 -0
  240. package/src/intake-plane/items.ts +3 -2
  241. package/src/intake-plane/lens.ts +177 -0
  242. package/src/intake-plane/route.ts +145 -1
  243. package/src/knowledge/know-find.ts +105 -0
  244. package/src/llm.ts +75 -19
  245. package/src/loops/budget.ts +36 -5
  246. package/src/loops/checker.ts +2 -2
  247. package/src/loops/orchestrator/author-depth.ts +65 -9
  248. package/src/loops/orchestrator/author-input-check.ts +8 -3
  249. package/src/loops/orchestrator/author-ledger.ts +3 -1
  250. package/src/loops/orchestrator/finish-rate.ts +14 -3
  251. package/src/loops/orchestrator/seat-share.ts +56 -0
  252. package/src/loops/orchestrator/tick.ts +107 -29
  253. package/src/loops/orchestrator/traffic.ts +117 -11
  254. package/src/loops/registry.ts +142 -61
  255. package/src/maturity/bot-slash-catalog.ts +11 -2
  256. package/src/maturity/feature-maturity.ts +4 -3
  257. package/src/maturity/tui-slash-availability.ts +40 -0
  258. package/src/mss/logging/log-store.ts +35 -4
  259. package/src/nexus/api/http-server.ts +28 -14
  260. package/src/nexus/api/loop-edges.ts +173 -0
  261. package/src/nexus/api/schedules-read.ts +13 -0
  262. package/src/nexus/api/seat-requests.ts +79 -56
  263. package/src/oauth/overload-failover-launch.ts +75 -0
  264. package/src/oauth/overload-failover.ts +137 -0
  265. package/src/prompt-enhance/enhance.ts +6 -1
  266. package/src/release-loop/auto-start.ts +6 -3
  267. package/src/release-loop/checklist.ts +19 -5
  268. package/src/release-loop/feature-store.ts +59 -31
  269. package/src/release-loop/merged-pr-checklist.ts +125 -0
  270. package/src/release-loop/placement.ts +98 -29
  271. package/src/seat-dispatch/seat-ask.ts +33 -6
  272. package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
  273. package/src/seat-dispatch/seat-request-ledger.ts +133 -0
  274. package/src/seat-loop/seat-loop.ts +215 -12
  275. package/src/self-dev/ask-launch-flow.ts +22 -1
  276. package/src/self-dev/authored-goal-trace.ts +64 -0
  277. package/src/self-dev/dev-cli.ts +19 -1
  278. package/src/self-dev/dev-pipeline.ts +14 -9
  279. package/src/self-dev/draft-sweep.ts +90 -10
  280. package/src/self-dev/draft-triage-rules.ts +25 -8
  281. package/src/self-dev/entrance-registry.ts +10 -6
  282. package/src/self-dev/old-door.ts +73 -0
  283. package/src/self-dev/orchestrate.ts +15 -1
  284. package/src/self-dev/review-provider-fallback.ts +6 -1
  285. package/src/self-dev/run-supervisor.ts +49 -2
  286. package/src/self-implement/app-api-budget.ts +166 -0
  287. package/src/self-implement/budget-gate.ts +7 -5
  288. package/src/self-implement/follow-up-goals.ts +289 -0
  289. package/src/self-implement/frozen-merges.ts +3 -2
  290. package/src/self-implement/goal-author.ts +50 -6
  291. package/src/self-implement/goal-clarification-escalation.ts +5 -0
  292. package/src/self-implement/heal-intake.ts +120 -2
  293. package/src/self-implement/heal-triage.ts +44 -0
  294. package/src/self-implement/host-regate.ts +9 -2
  295. package/src/self-implement/l8-merge-queue.ts +132 -0
  296. package/src/self-implement/orchestrator.ts +325 -47
  297. package/src/self-implement/pipeline-shape.ts +1 -1
  298. package/src/self-implement/revert-guard.ts +42 -7
  299. package/src/self-implement/run-ledger.ts +6 -0
  300. package/src/self-implement/run-status-mapping.ts +2 -0
  301. package/src/self-implement/seams.ts +45 -16
  302. package/src/self-implement/self-implement-cli.ts +1 -0
  303. package/src/self-implement/speed-claims.ts +338 -0
  304. package/src/session-runtime/index.ts +2 -0
  305. package/src/session-runtime/retry-api.ts +87 -0
  306. package/src/session-runtime/retry-policy.ts +107 -6
  307. package/src/steward/triage.ts +74 -3
  308. package/src/steward/working-backwards.ts +20 -2
  309. package/src/task-cards/card-cli.ts +25 -0
  310. package/src/task-cards/card-store.ts +5 -3
  311. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
  312. package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
  313. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
  314. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +4 -3
  315. package/src/task-orchestrator/surfaces/pod-ledger-prod-sink.ts +75 -0
  316. package/src/task-orchestrator/surfaces/pod-pool.ts +70 -9
  317. package/src/task-orchestrator/surfaces/pod-source-receive.ts +37 -1
  318. package/src/task-orchestrator/surfaces/self-implement-pod.ts +166 -46
  319. package/src/task-orchestrator/surfaces/self-implement.ts +7 -1
  320. package/src/telegram-commands.ts +5 -0
  321. package/src/telegram.ts +72 -4
  322. package/src/user-config.ts +64 -7
  323. package/src/version/packed-revision.json +1 -1
  324. package/src/workflow-runtime/nodes/bash.ts +9 -1
  325. package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/5977-2df489622a52a33a.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/approvals/page-74383f890f075511.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/botlab/page-1abfe3aec3be6bdc.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2ce5471926761fdd.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e1e47d8d940aba3d.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/setup/page-b3759cc14da80908.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.js +0 -1
  348. package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +0 -1
  349. package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +0 -1
  350. /package/apps/pwa/out/_next/static/{x6mjI6vqOkVVu0jFuDkz1 → rv9cierV0jSOvDBm1g0XB}/_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
  };
@@ -8,6 +8,8 @@ export interface SpawnBudgetInput {
8
8
  caps?: Partial<Record<OrchestratorSeat, number>>;
9
9
  /** Release-gate seat limits. Missing seats use the gate default of four. */
10
10
  gate?: Partial<Record<OrchestratorSeat, number>>;
11
+ /** Optional queue-injected cap; it may lower, never replace, the configured limit. */
12
+ injectedCap?: number;
11
13
  }
12
14
 
13
15
  export interface SpawnBudgetDecision {
@@ -16,13 +18,42 @@ export interface SpawnBudgetDecision {
16
18
  cap: number;
17
19
  }
18
20
 
19
- /** A release gate can lower, but never raise, the configured per-seat cap. */
20
- export function decideSpawn({ seat, running, caps, gate }: SpawnBudgetInput): SpawnBudgetDecision {
21
+ export type SeatCapDetails = {
22
+ cap: number;
23
+ seatCaps: number | '?';
24
+ releaseGate: number | '?';
25
+ injectedCap?: number | '?';
26
+ winners: ('releaseGate' | 'seatCaps' | 'injectedCap')[];
27
+ };
28
+
29
+ /** Keep the cap calculation and the names shown to operators on the same source of truth. */
30
+ export function seatCapDetails({ seat, caps, gate, injectedCap }: Pick<SpawnBudgetInput, 'seat' | 'caps' | 'gate' | 'injectedCap'>): SeatCapDetails {
21
31
  const configured = caps?.[seat] ?? ORCHESTRATOR_DEFAULTS.seatCaps[seat];
22
32
  const gated = gate?.[seat] ?? 4;
23
- const cap = Number.isSafeInteger(configured) && configured >= 0 && Number.isSafeInteger(gated) && gated >= 0
24
- ? Math.min(configured, gated)
25
- : 0;
33
+ const valid = (value: number): boolean => Number.isSafeInteger(value) && value >= 0;
34
+ const seatCaps = valid(configured) ? configured : '?';
35
+ const releaseGate = valid(gated) ? gated : '?';
36
+ const injected = injectedCap === undefined ? undefined : valid(injectedCap) ? injectedCap : '?';
37
+ const cap = [seatCaps, releaseGate, injected].some((value) => value === '?') ? 0
38
+ : Math.min(seatCaps as number, releaseGate as number, injected === undefined ? Infinity : injected as number);
39
+ const winners: SeatCapDetails['winners'] = [];
40
+ if (seatCaps !== '?' && releaseGate !== '?' && injected !== '?') {
41
+ if (releaseGate === cap) winners.push('releaseGate');
42
+ if (seatCaps === cap) winners.push('seatCaps');
43
+ if (injected === cap) winners.push('injectedCap');
44
+ }
45
+ return { cap, seatCaps, releaseGate, ...(injected === undefined ? {} : { injectedCap: injected }), winners };
46
+ }
47
+
48
+ export function seatCapReason(seat: OrchestratorSeat, active: number, details: SeatCapDetails): string {
49
+ const keys = (['releaseGate', 'seatCaps', 'injectedCap'] as const).filter((name) => name !== 'injectedCap' || details.injectedCap !== undefined);
50
+ const candidates = [...details.winners, ...keys.filter((name) => !details.winners.includes(name))];
51
+ return `seat ${seat}: ${active}/${details.cap} (${candidates.map((name) => `${name}.${seat}=${details[name]}`).join(' · ')})`;
52
+ }
53
+
54
+ /** A release gate or injected queue cap can lower, but never raise, the configured per-seat cap. */
55
+ export function decideSpawn({ seat, running, caps, gate, injectedCap }: SpawnBudgetInput): SpawnBudgetDecision {
56
+ const { cap } = seatCapDetails({ seat, caps, gate, injectedCap });
26
57
  if (!Number.isSafeInteger(running) || running === null || running === undefined || running < 0) {
27
58
  return { allow: false, reason: 'unknown-running', cap };
28
59
  }
@@ -14,8 +14,8 @@ export type LoopScope = 'graph-only' | 'registry';
14
14
  export interface CheckEntry {
15
15
  id: string;
16
16
  owner?: string;
17
- ownerSource?: 'header' | 'config' | 'seat' | 'default';
18
- evidence?: 'log-mtime';
17
+ ownerSource?: 'header' | 'config' | 'seat' | 'seat-arg' | 'default';
18
+ evidence?: 'log-mtime' | 'registry';
19
19
  mode?: string;
20
20
  expectEveryMinutes?: number;
21
21
  /** The scheduled fire before the latest one (cron loops). A run older than this missed two scheduled fires. */
@@ -1,5 +1,7 @@
1
- import { existsSync } from 'node:fs';
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
2
+ import { randomUUID } from 'node:crypto';
2
3
  import { join } from 'node:path';
4
+ import { effectiveInstanceRoot } from '../../instance/resolve.js';
3
5
  import { debug } from '../../debug/log.js';
4
6
  import { defaultListHarnessProcesses } from '../../harness/harness-cli-command.js';
5
7
  import { listHarnessQueue, type QueueItem } from '../../harness/harness-queue.js';
@@ -7,15 +9,19 @@ import { releaseLedgerRoot } from '../../instance/resolve.js';
7
9
  import { listChecklist, type ChecklistItem } from '../../release-loop/checklist.js';
8
10
  import { listSchedules } from '../../release-loop/release-schedule.js';
9
11
  import { getUserConfig, ORCHESTRATOR_DEFAULTS, type OrchestratorSeat } from '../../user-config.js';
12
+ import { checkAuthorInput } from './author-input-check.js';
13
+ import { AuthorLedger } from './author-ledger.js';
10
14
  import { collectWork, findOverlaps, type WorkItem } from './overlap.js';
11
15
  import { seatOfTree, trafficTick, TRAFFIC_SEATS } from './traffic.js';
12
16
 
13
- export type AuthorCell = Pick<ChecklistItem, 'id' | 'title' | 'owner' | 'status'> & { version: string };
17
+ export type AuthorCell = Pick<ChecklistItem, 'id' | 'title' | 'owner' | 'status' | 'evidence'> & { version: string };
14
18
  export type AuthorQueueRow = Pick<QueueItem, 'seat' | 'status'> & Partial<Pick<QueueItem, 'input' | 'kind'>>;
15
19
  export type AuthorDepthResult = {
16
20
  seats: Array<{ seat: OrchestratorSeat; cap: number | null; running: number | null; queued: number | null;
17
21
  target: number | null; short: number | null; wouldAuthor: Array<{ cellId: string; version: string; title: string }> }>;
18
22
  unreadable: Array<{ source: string; reason: string }>;
23
+ receiptCounts?: { held: number; queuedForAuthor: number };
24
+ receiptFailures?: number;
19
25
  };
20
26
  export type AuthorDepthInput = {
21
27
  seats: readonly OrchestratorSeat[];
@@ -84,6 +90,7 @@ export function authorDepth({ seats, caps, running, queued, cells, overlaps, now
84
90
  }
85
91
 
86
92
  export interface CollectAuthorDepthDeps {
93
+ root?: string;
87
94
  seats?: readonly OrchestratorSeat[];
88
95
  now?: Date;
89
96
  caps?: () => Partial<Record<OrchestratorSeat, number>>;
@@ -92,12 +99,13 @@ export interface CollectAuthorDepthDeps {
92
99
  cells?: (version: string) => readonly AuthorCell[];
93
100
  overlaps?: () => readonly WorkItem[];
94
101
  versions?: () => readonly [string, string];
102
+ ledger?: Pick<AuthorLedger, 'request'>;
95
103
  log?: (category: string, event: string, data: Record<string, unknown>) => void;
96
104
  }
97
105
 
98
- function defaultVersions(now: Date): readonly [string, string] {
99
- if (!existsSync(join(releaseLedgerRoot(), 'release', 'features.sqlite'))) throw new Error('release schedule ledger unavailable');
100
- const upcoming = listSchedules().filter(row => row.cutAt && Date.parse(row.cutAt) > now.getTime())
106
+ function defaultVersions(now: Date, root = releaseLedgerRoot()): readonly [string, string] {
107
+ if (!existsSync(join(root, 'release', 'features.sqlite'))) throw new Error('release schedule ledger unavailable');
108
+ const upcoming = listSchedules(root).filter(row => row.cutAt && Date.parse(row.cutAt) > now.getTime())
101
109
  .sort((a, b) => Date.parse(a.cutAt) - Date.parse(b.cutAt));
102
110
  if (upcoming.length < 2) throw new Error('current and next release schedules unavailable');
103
111
  return [upcoming[0]!.version, upcoming[1]!.version];
@@ -123,12 +131,13 @@ export function collectAuthorDepth(deps: CollectAuthorDepthDeps = {}): AuthorDep
123
131
  if (measured.unassigned) throw new Error(`${measured.unassigned} unassigned harness launch process(es)`);
124
132
  return Object.fromEntries(measured.seats.map(row => [row.seat, row.running]));
125
133
  });
126
- const queued = observe('queue', deps.queued ?? (() => listHarnessQueue()));
127
- const versions = observe('versions', deps.versions ?? (() => defaultVersions(now)));
134
+ const queued = observe('queue', deps.queued ?? (() => listHarnessQueue({ root: deps.root })));
135
+ const releaseRoot = deps.root ?? releaseLedgerRoot();
136
+ const versions = observe('versions', deps.versions ?? (() => defaultVersions(now, releaseRoot)));
128
137
  const cells = versions === null ? null : observe('checklist', () => versions.flatMap(version => {
129
- if (!deps.cells && !existsSync(join(releaseLedgerRoot(), 'release', 'features.sqlite')))
138
+ if (!deps.cells && !existsSync(join(releaseRoot, 'release', 'features.sqlite')))
130
139
  throw new Error('checklist ledger unavailable');
131
- return deps.cells?.(version) ?? listChecklist(version).items.map(item => ({ ...item, version }));
140
+ return deps.cells?.(version) ?? listChecklist(version, deps.root).items.map(item => ({ ...item, version }));
132
141
  }));
133
142
  const overlaps = observe('overlap', deps.overlaps ?? (() => {
134
143
  const observed = collectWork();
@@ -141,9 +150,56 @@ export function collectAuthorDepth(deps: CollectAuthorDepthDeps = {}): AuthorDep
141
150
  for (const row of result.seats) log('loop.orchestrator', 'would-author', {
142
151
  seat: row.seat, target: row.target, have: row.queued, short: row.short, cells: row.wouldAuthor,
143
152
  });
153
+ const ledger = deps.ledger ?? new AuthorLedger(deps.root === undefined ? {} : { path: join(deps.root, 'orchestrator', 'author-ledger.sqlite') });
154
+ const receiptCounts = { held: 0, queuedForAuthor: 0 };
155
+ let receiptFailures = 0;
156
+ for (const row of result.seats) for (const candidate of row.wouldAuthor) {
157
+ const cell = cells?.find(item => item.id === candidate.cellId && item.version === candidate.version);
158
+ try {
159
+ if (!cell) throw new Error('selected cell unavailable');
160
+ const text = cell.evidence ?? '';
161
+ const check = checkAuthorInput({ title: cell.title, text, cellId: cell.id, version: cell.version });
162
+ const receipt = ledger.request({ seat: row.seat, cellId: cell.id, version: cell.version, title: cell.title,
163
+ text, check: { verdict: check.verdict, signals: check.signals, ratio: check.implementationRatio } });
164
+ if (receipt.status === 'held') receiptCounts.held++;
165
+ if (receipt.status === 'queued-for-author') receiptCounts.queuedForAuthor++;
166
+ } catch (error) {
167
+ receiptFailures++;
168
+ result.unreadable.push({ source: 'author-ledger', reason: `${candidate.version} ${candidate.cellId}: ${error instanceof Error ? error.message : String(error)}` });
169
+ }
170
+ }
171
+ result.receiptCounts = receiptCounts;
172
+ result.receiptFailures = receiptFailures;
144
173
  return result;
145
174
  }
146
175
 
176
+ const SHADOW_INTERVAL_MS = 30 * 60_000;
177
+
178
+ /** The queue tick serializes callers; stamp before collection so even a failing shadow cannot run every two minutes. */
179
+ export function runAuthorDepthShadow(root = effectiveInstanceRoot(), now = new Date(), collect = collectAuthorDepth): void {
180
+ const path = join(root, 'orchestrator', 'author-depth-shadow-at');
181
+ const time = now.getTime();
182
+ if (!Number.isFinite(time)) throw new Error('invalid author-depth shadow clock');
183
+ if (existsSync(path)) {
184
+ const last = Date.parse(readFileSync(path, 'utf8').trim());
185
+ if (!Number.isFinite(last)) throw new Error('invalid author-depth shadow marker');
186
+ if (time - last < SHADOW_INTERVAL_MS) return;
187
+ }
188
+ mkdirSync(join(root, 'orchestrator'), { recursive: true });
189
+ const temporary = `${path}.${randomUUID()}.tmp`;
190
+ try {
191
+ writeFileSync(temporary, now.toISOString(), { flag: 'wx', mode: 0o600 });
192
+ renameSync(temporary, path);
193
+ } catch (error) {
194
+ try { unlinkSync(temporary); } catch { /* No temporary file to remove. */ }
195
+ throw error;
196
+ }
197
+ const result = collect({ root, now });
198
+ if (result.unreadable.length || result.receiptFailures || !result.receiptCounts) {
199
+ throw new Error(`author-depth shadow incomplete: ${JSON.stringify({ unreadable: result.unreadable, receiptFailures: result.receiptFailures })}`);
200
+ }
201
+ }
202
+
147
203
  export function authorDepthLines(result: AuthorDepthResult): string[] {
148
204
  return result.seats.map(row => `${row.seat} ${row.running ?? '?'}/${row.cap ?? '?'} · 대기 ${row.queued ?? '?'} · 목표 ${row.target ?? '?'} · 모자람 ${row.short ?? '?'} → ${row.wouldAuthor.map(cell => cell.cellId).join(', ') || '없음'}`);
149
205
  }
@@ -8,7 +8,7 @@ export interface AuthorInput {
8
8
  }
9
9
 
10
10
  export interface AuthorInputSignal {
11
- kind: 'path' | 'symbol' | 'command' | 'marker';
11
+ kind: 'path' | 'symbol' | 'command' | 'marker' | 'missing';
12
12
  match: string;
13
13
  field: 'title' | 'text';
14
14
  reason: string;
@@ -40,15 +40,19 @@ function sentences(value: string): string[] {
40
40
  }
41
41
 
42
42
  export function checkAuthorInput({ title, text, cellId, version }: AuthorInput): AuthorInputCheck {
43
- const valid = [title, text, cellId, version].every(value => typeof value === 'string' && !!value.trim())
43
+ const validIdentity = [title, cellId, version].every(value => typeof value === 'string' && !!value.trim())
44
44
  && /^\d+\.\d+\.\d+$/.test(version);
45
+ const missingText = typeof text !== 'string' || !text.trim();
46
+ const valid = validIdentity && !missingText;
45
47
  const signals: AuthorInputSignal[] = [];
48
+ if (validIdentity && missingText) signals.push({ kind: 'missing', match: '', field: 'text', reason: 'missing cell text' });
46
49
  let checked = 0;
47
50
  let candidates = 0;
48
51
  let directed = false;
49
52
 
50
- if (valid) {
53
+ if (validIdentity) {
51
54
  for (const field of ['title', 'text'] as const) {
55
+ if (field === 'text' && missingText) continue;
52
56
  for (const sentence of sentences(field === 'title' ? title : text)) {
53
57
  checked++;
54
58
  let found = false;
@@ -70,6 +74,7 @@ export function checkAuthorInput({ title, text, cellId, version }: AuthorInput):
70
74
  const verdict: AuthorInputCheck['verdict'] = !valid ? 'uncheckable'
71
75
  : signals.length === 0 ? 'approved' : directed ? 'resubmit' : 'confirm';
72
76
  const implementationRatio = valid ? candidates / checked : null;
77
+ if (!valid) { checked = 0; candidates = 0; }
73
78
  debug.log('author.par', 'input-checked', {
74
79
  cellId, version, verdict, signals: signals.length, ratio: implementationRatio,
75
80
  });
@@ -120,9 +120,11 @@ export class AuthorLedger {
120
120
  }
121
121
 
122
122
  request(input: AuthorRequest): AuthorRow & { duplicate: boolean } {
123
- for (const key of ['seat', 'cellId', 'version', 'title', 'text'] as const) {
123
+ for (const key of ['seat', 'cellId', 'version', 'title'] as const) {
124
124
  if (typeof input[key] !== 'string' || !input[key].trim()) throw new Error(`${key} is required`);
125
125
  }
126
+ if (typeof input.text !== 'string' || (!input.text.trim() && input.check?.verdict !== 'uncheckable'))
127
+ throw new Error('text is required unless uncheckable');
126
128
  if (!['approved', 'resubmit', 'confirm', 'uncheckable'].includes(input.check?.verdict)
127
129
  || !Array.isArray(input.check.signals)
128
130
  || (input.check.ratio !== null && (typeof input.check.ratio !== 'number' || !Number.isFinite(input.check.ratio))))
@@ -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
  }