elanous 0.2.23 → 0.2.24

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 (347) 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/{272-b23d2e92161d11fd.js → 272-0eed0fb0aab716b6.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{3961-fbef064c213b2b18.js → 3961-aaae04ed1d32622f.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-d5341ac96203a983.js → page-3b9fc39362473ae7.js} +1 -1
  6. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f2b62c19ca68fef8.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/app/board/{page-55c631c032dcc47e.js → page-d7602d489edf5f37.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-ad3bd8084048bee4.js → page-24282bf9f90031a5.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/app/bots/page-8b7c5ab6b5bc8d64.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/ceo/{page-5c5d0e370f495ef6.js → page-4eb84d454f8c3415.js} +1 -1
  11. package/apps/pwa/out/_next/static/chunks/app/chat/{page-c325e21543cd48a8.js → page-b2e494bdad57fcb5.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/app/consult/{page-6168a7c43e6ef8f5.js → page-c0ab229673a8b0eb.js} +1 -1
  13. package/apps/pwa/out/_next/static/chunks/app/control/page-3e2b45f3c36dbac4.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/app/decisions/page-c22aa511b28743ad.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/app/design-check/page-26345242e4049f55.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/app/editor/{page-25bb425afdb27541.js → page-bf88c0f558d8ef54.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/app/exec/{page-32d72fdef1afe11a.js → page-52fec523e5b5d3ad.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/field/page-67723dfdcde92b31.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/app/inside/{page-2bd9ce9c020d02d4.js → page-22725e2570f1126b.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/intake/{page-a34096245e63b9f2.js → page-1891335950d68b70.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/layout-3267af1ebdc73cf5.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/app/live/{page-4cc66d22ca4ec502.js → page-c314b75c11234998.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/live-run/{page-83b14c729fcea837.js → page-9f9c85415f61782b.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/loops/page-c1d4276059085587.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/market/page-833aab587f26ad4c.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-ccdb83ed40c8d724.js → page-8526c17ae38e6a50.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/morning/{page-7ad3de8fa06fdea9.js → page-f998637a8402115c.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/not-found-b7bb53828b17b335.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/observatory/page-1b1850de0257e46d.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-786111f30b6f0969.js → page-6f23159fedb41f67.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-48fb31810693c357.js → page-1d809a87139dd024.js} +1 -1
  32. package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-4e17cce5e2ed0fee.js → page-da9e570234d481fd.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/outputs/{page-cb154acadb025414.js → page-b92caddb5c715f30.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/page-8ce3a38e210b810a.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-c2dd2b19c93f90ab.js → page-57ffeeb6019440fd.js} +1 -1
  36. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-abbb417bf514630b.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-d3c007c8a0ddd7ee.js → page-2c2b3f29cec1efa4.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-7837ce2aacb97b04.js → page-9369279f72fc7b51.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/app/settings/page-64861f14039499c3.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-ddc4c93efd8ab94b.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/app/setup/page-901a1391b81053bc.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/share/page-68d9bbaac9841a58.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-119be9a0444d2482.js → page-b5c0c996f329371e.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-fa806c018833e332.js → page-89881ccbcf728978.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/app/term/page-0e87a803b3d7165e.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/today/{page-d431fbdea6ad93dd.js → page-58d4e5cb30ef4871.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/app/trace/page-02ce4c3b5f39e4d2.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/vault/page-e620d1bb8eadcec2.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-847c3de6ca8a4416.js → page-acf868c64d77f950.js} +1 -1
  50. package/apps/pwa/out/_next/static/chunks/app/workflows/page-54fa605fa6dba457.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-74e69ba2c8e970d4.js → page-bc79f0833533fa84.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-70038f41ae7cf931.js → page-d5c4398eea03d8f3.js} +1 -1
  53. package/apps/pwa/out/_next/static/css/72becfd42807b50d.css +1 -0
  54. package/apps/pwa/out/approvals/index.html +1 -1
  55. package/apps/pwa/out/approvals/index.txt +14 -14
  56. package/apps/pwa/out/autopilot/index.html +1 -1
  57. package/apps/pwa/out/autopilot/index.txt +14 -14
  58. package/apps/pwa/out/board/index.html +1 -1
  59. package/apps/pwa/out/board/index.txt +14 -14
  60. package/apps/pwa/out/botlab/index.html +1 -1
  61. package/apps/pwa/out/botlab/index.txt +14 -14
  62. package/apps/pwa/out/bots/index.html +1 -1
  63. package/apps/pwa/out/bots/index.txt +14 -14
  64. package/apps/pwa/out/ceo/index.html +1 -1
  65. package/apps/pwa/out/ceo/index.txt +14 -14
  66. package/apps/pwa/out/chat/index.html +1 -1
  67. package/apps/pwa/out/chat/index.txt +14 -14
  68. package/apps/pwa/out/consult/index.html +1 -1
  69. package/apps/pwa/out/consult/index.txt +14 -14
  70. package/apps/pwa/out/control/index.html +1 -1
  71. package/apps/pwa/out/control/index.txt +14 -14
  72. package/apps/pwa/out/decisions/index.html +1 -1
  73. package/apps/pwa/out/decisions/index.txt +14 -14
  74. package/apps/pwa/out/design-check/index.html +1 -1
  75. package/apps/pwa/out/design-check/index.txt +14 -14
  76. package/apps/pwa/out/editor/index.html +1 -1
  77. package/apps/pwa/out/editor/index.txt +14 -14
  78. package/apps/pwa/out/exec/index.html +1 -1
  79. package/apps/pwa/out/exec/index.txt +14 -14
  80. package/apps/pwa/out/field/index.html +1 -1
  81. package/apps/pwa/out/field/index.txt +14 -14
  82. package/apps/pwa/out/healthz +1 -1
  83. package/apps/pwa/out/index.html +1 -1
  84. package/apps/pwa/out/index.txt +14 -14
  85. package/apps/pwa/out/inside/index.html +1 -1
  86. package/apps/pwa/out/inside/index.txt +14 -14
  87. package/apps/pwa/out/intake/index.html +1 -1
  88. package/apps/pwa/out/intake/index.txt +14 -14
  89. package/apps/pwa/out/live/index.html +1 -1
  90. package/apps/pwa/out/live/index.txt +14 -14
  91. package/apps/pwa/out/live-run/index.html +1 -1
  92. package/apps/pwa/out/live-run/index.txt +14 -14
  93. package/apps/pwa/out/loops/index.html +1 -1
  94. package/apps/pwa/out/loops/index.txt +14 -14
  95. package/apps/pwa/out/market/index.html +1 -1
  96. package/apps/pwa/out/market/index.txt +14 -14
  97. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  98. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  99. package/apps/pwa/out/morning/index.html +1 -1
  100. package/apps/pwa/out/morning/index.txt +14 -14
  101. package/apps/pwa/out/observatory/index.html +1 -1
  102. package/apps/pwa/out/observatory/index.txt +14 -14
  103. package/apps/pwa/out/ops/checklist/index.html +1 -1
  104. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  105. package/apps/pwa/out/ops/release/index.html +1 -1
  106. package/apps/pwa/out/ops/release/index.txt +14 -14
  107. package/apps/pwa/out/ops/seats/index.html +1 -1
  108. package/apps/pwa/out/ops/seats/index.txt +14 -14
  109. package/apps/pwa/out/outputs/index.html +1 -1
  110. package/apps/pwa/out/outputs/index.txt +14 -14
  111. package/apps/pwa/out/reflection/index.html +1 -1
  112. package/apps/pwa/out/reflection/index.txt +15 -15
  113. package/apps/pwa/out/scheduler/index.html +1 -1
  114. package/apps/pwa/out/scheduler/index.txt +14 -14
  115. package/apps/pwa/out/sessions/index.html +1 -1
  116. package/apps/pwa/out/sessions/index.txt +14 -14
  117. package/apps/pwa/out/settings/devices/index.html +1 -1
  118. package/apps/pwa/out/settings/devices/index.txt +14 -14
  119. package/apps/pwa/out/settings/index.html +1 -1
  120. package/apps/pwa/out/settings/index.txt +14 -14
  121. package/apps/pwa/out/setup/done/index.html +1 -1
  122. package/apps/pwa/out/setup/done/index.txt +14 -14
  123. package/apps/pwa/out/setup/index.html +1 -1
  124. package/apps/pwa/out/setup/index.txt +14 -14
  125. package/apps/pwa/out/share/index.html +1 -1
  126. package/apps/pwa/out/share/index.txt +14 -14
  127. package/apps/pwa/out/showroom/index.html +1 -1
  128. package/apps/pwa/out/showroom/index.txt +14 -14
  129. package/apps/pwa/out/tasks/index.html +1 -1
  130. package/apps/pwa/out/tasks/index.txt +14 -14
  131. package/apps/pwa/out/term/index.html +1 -1
  132. package/apps/pwa/out/term/index.txt +14 -14
  133. package/apps/pwa/out/today/index.html +1 -1
  134. package/apps/pwa/out/today/index.txt +14 -14
  135. package/apps/pwa/out/trace/index.html +1 -1
  136. package/apps/pwa/out/trace/index.txt +14 -14
  137. package/apps/pwa/out/vault/index.html +1 -1
  138. package/apps/pwa/out/vault/index.txt +14 -14
  139. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  140. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  141. package/apps/pwa/out/workflows/index.html +1 -1
  142. package/apps/pwa/out/workflows/index.txt +14 -14
  143. package/apps/pwa/out/workspace/index.html +1 -1
  144. package/apps/pwa/out/workspace/index.txt +14 -14
  145. package/apps/pwa/out/worktrees/index.html +1 -1
  146. package/apps/pwa/out/worktrees/index.txt +14 -14
  147. package/catalog/models/openrouter/deepseek-v4-pro-0813.yaml +39 -0
  148. package/catalog/models/openrouter/deepseek-v4.1-flash.yaml +38 -0
  149. package/catalog/models/openrouter/glm-5.3-flash.yaml +35 -0
  150. package/catalog/providers/openrouter.yaml +2 -0
  151. package/graphs/implement-loop.yaml +31 -0
  152. package/graphs/ops/harness-gc.yaml +17 -0
  153. package/graphs/ops/recipes.yaml +3 -0
  154. package/graphs/overlays/README.md +1 -1
  155. package/graphs/overlays/stop-heal-ladder.yaml +105 -0
  156. package/package.json +1 -1
  157. package/scripts/add-api-key.sh +1 -1
  158. package/scripts/ci-typecheck-changed.ts +71 -14
  159. package/scripts/fleet/bedrock-pod-secret.ts +122 -0
  160. package/scripts/fleet/node-setup-linux.sh +97 -0
  161. package/scripts/fleet/node-setup.sh +11 -0
  162. package/scripts/fleet/zai-pod-secret.ts +86 -0
  163. package/scripts/land-global-checks.ts +3 -0
  164. package/scripts/measure-log-diet-2.ts +213 -0
  165. package/scripts/measure-tsc-affected-roots.ts +289 -0
  166. package/scripts/release-loop/gate-executor.ts +28 -0
  167. package/scripts/release-loop/gate-node.ts +262 -26
  168. package/scripts/release-loop/resume-release.ts +9 -3
  169. package/scripts/release-loop/resume.ts +41 -0
  170. package/scripts/release-loop/unattended-release.ts +22 -7
  171. package/scripts/tsc-baseline-cache.ts +147 -0
  172. package/scripts/usage-rollup.ts +58 -6
  173. package/src/agent/run-context.ts +10 -0
  174. package/src/agent-mission/driver.ts +18 -3
  175. package/src/agent-mission/resource-ladder.ts +8 -3
  176. package/src/agent-mission/review-loop.ts +8 -2
  177. package/src/agent-substrate/acp-implementer.ts +139 -0
  178. package/src/agent-substrate/pr-reviewer.ts +31 -4
  179. package/src/agent-substrate/self-review-cli.ts +48 -9
  180. package/src/autopilot/mission-critique.ts +18 -5
  181. package/src/autopilot/mission-feasibility-negotiate.ts +11 -10
  182. package/src/autopilot/mission-trace.ts +12 -1
  183. package/src/autopilot/system-lookback-run.ts +16 -10
  184. package/src/bench/bench.ts +183 -0
  185. package/src/budget/fetchers/grok.ts +15 -5
  186. package/src/budget/llm-cost.ts +37 -5
  187. package/src/budget/types.ts +8 -2
  188. package/src/budget/unified-usage.ts +88 -6
  189. package/src/cli/bench-cli.ts +34 -0
  190. package/src/cli/guardian-cli.ts +26 -1
  191. package/src/cli/image-cli.ts +101 -0
  192. package/src/cli/log-event-names.ts +4 -0
  193. package/src/cli/pr-cli.ts +62 -5
  194. package/src/cli/pr-land-overlap-evidence.ts +160 -12
  195. package/src/cli/release-cli.ts +18 -9
  196. package/src/cli/rfc-status.ts +52 -2
  197. package/src/cli/self-update.ts +104 -0
  198. package/src/cli/tasks-cli.ts +109 -4
  199. package/src/cli/usage-cli.ts +91 -8
  200. package/src/core-turn/run-goal-loop.ts +5 -4
  201. package/src/dashboard/ad-run-setup-bindings.ts +279 -0
  202. package/src/dashboard/index.ts +18 -276
  203. package/src/dashboard/stale-install-check.ts +11 -0
  204. package/src/debug/log-diet.ts +38 -7
  205. package/src/debug/log.ts +19 -1
  206. package/src/domains/release-tool.ts +39 -0
  207. package/src/error-report/report.ts +21 -4
  208. package/src/github/pr-labels.ts +1 -0
  209. package/src/graph-runner/graph-cli.ts +42 -1
  210. package/src/graph-runner/runner.ts +167 -6
  211. package/src/grok/usage.ts +23 -2
  212. package/src/grounding/ref-index.ts +253 -0
  213. package/src/harness/dispatch-backpressure.ts +10 -2
  214. package/src/harness/harness-clean.ts +199 -78
  215. package/src/harness/harness-cli-command.ts +122 -7
  216. package/src/harness/harness-gc.ts +331 -0
  217. package/src/harness/harness-stop.ts +4 -2
  218. package/src/harness/harness-substrate-default.ts +142 -7
  219. package/src/harness/launch-registry.ts +6 -0
  220. package/src/harness/worktree-branch-prefix.ts +9 -0
  221. package/src/hq/fence.ts +187 -0
  222. package/src/hq/seats.ts +38 -22
  223. package/src/images/image-ledger.ts +179 -0
  224. package/src/index.ts +93 -11
  225. package/src/instance/child-scope.ts +32 -1
  226. package/src/instance/dev-project.ts +133 -0
  227. package/src/instance/resolve.ts +24 -0
  228. package/src/instance/spawn-elanous.ts +45 -0
  229. package/src/intake-plane/ref-prefetch.ts +110 -0
  230. package/src/intelligence-map/model-catalog.ts +72 -0
  231. package/src/llm/bedrock-eventstream.ts +215 -0
  232. package/src/llm/bedrock-provider.ts +114 -41
  233. package/src/llm/bedrock.ts +174 -28
  234. package/src/llm/openrouter-byok-cost.ts +96 -0
  235. package/src/llm/openrouter-deepseek.ts +151 -0
  236. package/src/llm/openrouter-upstream-pins.config.ts +25 -0
  237. package/src/llm/zai-coding-plan.ts +700 -0
  238. package/src/llm.ts +87 -16
  239. package/src/loops/checker.ts +148 -9
  240. package/src/loops/orchestrator/resync-shadow.ts +34 -7
  241. package/src/maturity/feature-maturity.ts +2 -2
  242. package/src/model-tier/llm-tier-map.ts +9 -5
  243. package/src/models/prompts.ts +2 -1
  244. package/src/mss/logging/log-store.ts +58 -11
  245. package/src/nexus/api/missions.ts +73 -2
  246. package/src/nexus/api/ops-api.ts +18 -0
  247. package/src/nexus/index.ts +7 -75
  248. package/src/nexus/tools-kind.ts +78 -0
  249. package/src/oauth/codex-account-rotation.ts +39 -2
  250. package/src/oauth/codex-account-store.ts +323 -24
  251. package/src/oauth/fallback-chain.ts +53 -15
  252. package/src/onboarding/first-run.ts +37 -18
  253. package/src/onboarding/web-first.ts +20 -2
  254. package/src/ops/machine-heartbeat.ts +185 -0
  255. package/src/ops/overview-cache.ts +175 -0
  256. package/src/ops/overview.ts +8 -0
  257. package/src/prompt-cache/openai.ts +3 -0
  258. package/src/prompt-cache/types.ts +5 -0
  259. package/src/prompt-enhance/enhance.ts +23 -0
  260. package/src/release-loop/placement.ts +16 -1
  261. package/src/requirements/requirement-ledger.ts +536 -0
  262. package/src/retro/daily.ts +5 -5
  263. package/src/seat-loop/seat-loop.ts +32 -10
  264. package/src/self-dev/dev-cli.ts +44 -10
  265. package/src/self-dev/dev-pipeline.ts +7 -2
  266. package/src/self-dev/draft-residue.ts +14 -2
  267. package/src/self-dev/draft-sweep.ts +103 -0
  268. package/src/self-dev/graph-journey-route.ts +6 -0
  269. package/src/self-dev/orchestrate-cli.ts +25 -3
  270. package/src/self-dev/orchestrate.ts +41 -6
  271. package/src/self-dev/run-supervisor.ts +188 -15
  272. package/src/self-dev/run-types.ts +13 -0
  273. package/src/self-dev/supervisor-verdict-edges.ts +1 -0
  274. package/src/self-implement/budget-gate.ts +53 -13
  275. package/src/self-implement/child-context-composition.ts +139 -0
  276. package/src/self-implement/child-llm-preference.ts +13 -5
  277. package/src/self-implement/claude-acp-implement.ts +67 -0
  278. package/src/self-implement/flex-signals.ts +59 -0
  279. package/src/self-implement/frozen-merges.ts +8 -3
  280. package/src/self-implement/goal-author-decompose.ts +10 -2
  281. package/src/self-implement/goal-author.ts +24 -9
  282. package/src/self-implement/graph-overlay-condition.ts +28 -0
  283. package/src/self-implement/graph-overlay-yaml.ts +41 -7
  284. package/src/self-implement/graph-templates.ts +33 -1
  285. package/src/self-implement/graph-yaml.ts +56 -0
  286. package/src/self-implement/heal-intake.ts +5 -1
  287. package/src/self-implement/orchestrator.ts +144 -15
  288. package/src/self-implement/running-runs.ts +75 -5
  289. package/src/self-implement/seams.ts +47 -1
  290. package/src/self-implement/self-implement-cli.ts +9 -4
  291. package/src/self-implement/stop-autoheal.ts +69 -2
  292. package/src/session-runtime/retry-policy.ts +2 -1
  293. package/src/steward/launch.ts +5 -4
  294. package/src/steward/triage-budget.ts +62 -0
  295. package/src/steward/triage.ts +83 -22
  296. package/src/task-agent/actions.ts +62 -5
  297. package/src/task-agent/completion-evidence.ts +5 -0
  298. package/src/task-agent/cover.ts +101 -3
  299. package/src/task-agent/judge.ts +16 -0
  300. package/src/task-agent/land-order-ledger.ts +95 -0
  301. package/src/task-agent/live-move-history.ts +87 -1
  302. package/src/task-agent/live-moves.ts +350 -20
  303. package/src/task-agent/rejudge-on-head.ts +75 -20
  304. package/src/task-agent/review-prior-fix.ts +146 -0
  305. package/src/task-agent/run-adopt.ts +136 -0
  306. package/src/task-agent/shadow.ts +53 -9
  307. package/src/task-agent/task-hand.ts +54 -7
  308. package/src/task-orchestrator/surfaces/pod-account-broker.ts +38 -5
  309. package/src/task-orchestrator/surfaces/pod-bedrock-child.ts +74 -0
  310. package/src/task-orchestrator/surfaces/pod-child-default.ts +50 -0
  311. package/src/task-orchestrator/surfaces/pod-child-providers.ts +14 -1
  312. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +79 -6
  313. package/src/task-orchestrator/surfaces/pod-lease.ts +17 -5
  314. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
  315. package/src/task-orchestrator/surfaces/pod-pool.ts +20 -4
  316. package/src/task-orchestrator/surfaces/pod-zai-child.ts +101 -0
  317. package/src/task-orchestrator/surfaces/self-implement-pod.ts +213 -23
  318. package/src/task-orchestrator/surfaces/self-implement.ts +19 -1
  319. package/src/typecheck-ratchet.ts +1 -0
  320. package/src/user-config.ts +51 -11
  321. package/src/version/packed-revision.json +1 -1
  322. package/src/workflow-runtime/triggers/registry.ts +59 -0
  323. package/src/workflow-runtime/triggers/webhook-source.ts +35 -5
  324. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-0160ab83c8685a12.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/app/bots/page-e6784759a2531031.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/control/page-31417d3604154a6d.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/decisions/page-e9606dc0bdec9305.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/design-check/page-a648bc464c9dc754.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/field/page-d27b794c8ba76bbb.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/layout-22170f4f00a15496.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/loops/page-6e2781c523dad761.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/market/page-26dd415c8f13f341.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/not-found-015c02c4fec4023e.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/observatory/page-6b0ae5b5c42f6d2f.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/page-16258e956ea93fc2.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f35dd1dd96ae87.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/settings/page-7350ee93f4fe1033.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-00723458575f2b19.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/setup/page-745d9b6c015076f8.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/share/page-736d33c7414a8a1a.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/term/page-c782041b5f5199ba.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/trace/page-0c056abff82b9891.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/vault/page-242d668e4fbf72c4.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/workflows/page-81dd0d8ae923ed72.js +0 -1
  345. package/apps/pwa/out/_next/static/css/5619d59e87eddf66.css +0 -1
  346. /package/apps/pwa/out/_next/static/{rfkMm4movqkqMebBrCXiR → Q-7iofoxOEv1WX18S35of}/_buildManifest.js +0 -0
  347. /package/apps/pwa/out/_next/static/{rfkMm4movqkqMebBrCXiR → Q-7iofoxOEv1WX18S35of}/_ssgManifest.js +0 -0
@@ -34,6 +34,7 @@ import { isWorktreeInUse, listActiveTerminalDirectories } from '../harness/harne
34
34
  import { readWorktreePorcelain } from './abandoned-classification.js';
35
35
  import { recordHarnessWorktreeGoalMetadata, recordHarnessWorktreeProvenance, type HarnessWorktreeGoalMetadata } from '../harness/harness-worktree-add.js';
36
36
  import { runGitCommand } from '../git-fs/runner.js';
37
+ import { BENCH_LABEL, BENCH_LOG_CATEGORY, benchIdFromBase, parseBenchArmBranch } from '../bench/bench.js';
37
38
  import { provisionDerivedUniverse } from '../instance/provision.js';
38
39
  import { DEFAULT_GATE_STEPS, runIntegrityGate, type GateResult, type GateStepName } from '../autopilot/build/integrity-gate.js';
39
40
  import { androidFilesIn, runAndroidUnitTestGate } from '../../scripts/ci-android-unit-tests.js';
@@ -94,6 +95,7 @@ import { isSupervisorDecisionSection, splitGoalSections, supervisorGoalDigest }
94
95
  import type { SupervisionReworkSource } from './supervision-vocabulary.js';
95
96
  import type { ReworkBudgetVerdict } from './run-outcome.js';
96
97
  import { pickImplementRoleForGoal, runHeadlessGoalLoopPty } from './headless-elanous-driver.js';
98
+ import { isClaudeAcpChild, runClaudeAcpImplement } from './claude-acp-implement.js';
97
99
  import { childLlmSelectionEnv } from '../agent/run-context.js';
98
100
  import { observeExternalEvent } from '../context-bus/external-events.js';
99
101
  import { buildLlmHitlRelay, SIDE_EFFECT_RE } from '../harness/llm-hitl-relay.js';
@@ -1067,6 +1069,8 @@ export interface DefaultSeamsOptions {
1067
1069
  buildLlmHitlRelay?: typeof buildLlmHitlRelay;
1068
1070
  /** 테스트용 PTY executor 주입. */
1069
1071
  runHeadlessGoalLoopPty?: typeof runHeadlessGoalLoopPty;
1072
+ /** CLAUDE-IMPL-LANE 시험 주입 — claude-acp 자식의 구독 ACP 구현 실행. */
1073
+ runClaudeAcpImplement?: typeof runClaudeAcpImplement;
1070
1074
  /** 테스트용 PTY 가용성 주입. */
1071
1075
  ptyAvailable?: () => boolean;
1072
1076
  /** Tests may inject the shared provenance writer to exercise the self-implement failure contract. */
@@ -1367,6 +1371,8 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
1367
1371
  const removeCleanupWorktree = o.removeWorktree ?? removeWorktree;
1368
1372
  const runCleanupGitCommand = o.runGitCommand ?? runGitCommand;
1369
1373
  const baselineRefs = new Map<string, string>();
1374
+ // BENCH-ISOLATED-ARMS — 벤치 base(`bench/<id>/base`) 위에서 만든 작업 트리 → bench id.
1375
+ const benchWorktrees = new Map<string, string>();
1370
1376
  const gateVerifyOrdinals = new Map<string, number>();
1371
1377
  const gateVerifyIdentity = (runId: string | undefined) => {
1372
1378
  if (!runId?.trim()) return { runId: runId === undefined ? null : runId, ordinal: null };
@@ -1535,6 +1541,18 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
1535
1541
  defaultBranchRef,
1536
1542
  // ★ G2(2026-07-21) — 호출부가 해석한 대상만 PR 직전에 정합한다.
1537
1543
  mergeMain: async (worktreePath, mergeTarget) => {
1544
+ // BENCH-ISOLATED-ARMS ③ — 벤치 팔은 main 에 정합하지 않는다: 판정은 고정 base 위 게이트·시험·리뷰.
1545
+ // ⭐ 표지는 둘 중 하나 — 이 프로세스가 벤치 base 로 만든 트리이거나, 트리 가지가 `bench/<id>/<arm>` 이다
1546
+ // (재개 프로세스는 앞의 Map 이 비어 있으므로 가지 이름으로 다시 안다).
1547
+ const benchArm = benchWorktrees.has(worktreePath) ? undefined : (() => {
1548
+ const head = runGitCommand(worktreePath, ['rev-parse', '--abbrev-ref', 'HEAD'], { encoding: 'utf8', timeout: GIT_TIMEOUT });
1549
+ return head.status === 0 ? parseBenchArmBranch(String(head.stdout).trim()) : null;
1550
+ })();
1551
+ const benchId = benchWorktrees.get(worktreePath) ?? benchArm?.id;
1552
+ if (benchId) {
1553
+ debug.log(BENCH_LOG_CATEGORY, 'main-sync-skipped', { benchId, worktreePath, mergeTarget, source: benchWorktrees.has(worktreePath) ? 'base' : 'branch' });
1554
+ return { status: 'up-to-date' as const };
1555
+ }
1538
1556
  const { mergeMainIntoWorktreeWithLlm } = await import('../autopilot/build/llm-conflict-merge.js');
1539
1557
  return mergeMainIntoWorktreeWithLlm(worktreePath, mergeTarget);
1540
1558
  },
@@ -1586,6 +1604,8 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
1586
1604
  ...(runId ? { currentOwner: `dev:${runId}` } : {}),
1587
1605
  });
1588
1606
  if (r.resolvedBase) baselineRefs.set(r.path, r.resolvedBase);
1607
+ const benchId = benchIdFromBase(base);
1608
+ if (benchId) benchWorktrees.set(r.path, benchId);
1589
1609
  const provenance = runId ? {
1590
1610
  owner: `dev:${runId}`,
1591
1611
  command: 'elanous dev',
@@ -1686,6 +1706,28 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
1686
1706
  ...(runId ? { runId } : {}),
1687
1707
  });
1688
1708
  const prompt = featurePrompt(feature, cwd, o.reviewerContext);
1709
+ // CLAUDE-IMPL-LANE — claude-acp 는 PTY 자식(`elanous chat` → getProvider)으로 보내지 않는다:
1710
+ // 거기서는 모델 이름(claude-…)이 anthropic(API 키 provider)으로 풀려 «구독» 런이 API 로 과금된다(#26277 보류 판독).
1711
+ // 구독 ACP 구현기만 쓰고, 구독 확인이 안 되면 실패로 끝낸다(다른 공급자로 조용히 새지 않는다).
1712
+ if (isClaudeAcpChild(childLlm)) {
1713
+ const outcome = await (o.runClaudeAcpImplement ?? runClaudeAcpImplement)({
1714
+ cwd, prompt, ...(childLlm?.model ? { model: childLlm.model } : {}), ...(runId ? { runId } : {}),
1715
+ });
1716
+ if (outcome.kind === 'refused') {
1717
+ debug.log('self-implement', 'implement.result', { reached: false, changed: false, transport: 'claude-acp', refused: outcome.reason, ...(runId ? { runId } : {}) });
1718
+ return { ok: false, summary: `claude-acp 구현 레인 거부: ${outcome.reason}` };
1719
+ }
1720
+ const changed = worktreeHasChanges(cwd, harnessSeededPaths ?? []);
1721
+ // 도구 호출 수 = 허가를 요청한 호출(허가 ⊕ 거부) — ACP 는 읽기 전용 호출 일부를 묻지 않으니 하한이다.
1722
+ const toolCalls = outcome.result.allowed + outcome.result.denied;
1723
+ debug.log('self-implement', 'implement.result', { reached: true, changed, toolCalls, transport: 'claude-acp', allowed: outcome.result.allowed, denied: outcome.result.denied, ...(runId ? { runId } : {}) });
1724
+ return {
1725
+ ok: changed,
1726
+ ...buildImplementReport(outcome.result.text, { changed, toolCalls, reached: true, timedOut: false }),
1727
+ toolCalls,
1728
+ terminalStatus: { reached: true, changed, toolCalls, timedOut: false },
1729
+ };
1730
+ }
1689
1731
  // S4 P2b/P3: input은 관측만 하며, done만 config와 stall 확증을 모두 만족할 때 부모 대기를 끝낸다.
1690
1732
  // 이 종료-보고 규칙은 self-implement child에만 적용한다. 범용 brain의 기본 프롬프트를 바꾸지 않는다.
1691
1733
  const brain = createSelfImplementControlBrain({ goal: feature });
@@ -2436,7 +2478,11 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
2436
2478
  commitMessage: title,
2437
2479
  ...(resolvedBase ? { base: resolvedBase } : {}),
2438
2480
  ...(draft !== undefined ? { draft } : {}),
2439
- ...(labels?.length ? { labels } : {}),
2481
+ // BENCH-ISOLATED-ARMS ② — base 가 `bench/<id>/base` 면 라벨 `elanous:bench` 를 더한다.
2482
+ ...(() => {
2483
+ const all = benchIdFromBase(resolvedBase) && !labels?.includes(BENCH_LABEL) ? [...(labels ?? []), BENCH_LABEL] : labels;
2484
+ return all?.length ? { labels: all } : {};
2485
+ })(),
2440
2486
  });
2441
2487
  if (!outcome.ok) throw new Error(`self-implement PR ${outcome.reason} 실패: ${outcome.detail}`);
2442
2488
  const number = Number.parseInt(outcome.url.match(/\/pull\/(\d+)/)?.[1] ?? '', 10);
@@ -120,6 +120,8 @@ export function singleRunAsJobResult(feature: string, r: SelfImplementResultWith
120
120
  ...(r.abandonedClassification?.classification
121
121
  ? { failureClassification: r.abandonedClassification.classification } : {}),
122
122
  ...(r.decomposeProposal ? { decomposeProposal: r.decomposeProposal } : {}),
123
+ ...(r.rework ? { rework: r.rework } : {}),
124
+ ...(r.review?.reviewed && r.review.mustFix?.length ? { reviewMustFix: r.review.mustFix } : {}),
123
125
  ...(r.goalPlanRevision ? { goalPlanRevision: r.goalPlanRevision } : {}),
124
126
  ...(r.salvage !== undefined ? { salvage: r.salvage } : {}),
125
127
  ...(r.review?.reviewed === true && r.review.verdict === 'pass'
@@ -240,8 +242,10 @@ export async function runSelfImplementCliCommand(
240
242
  }
241
243
  : undefined;
242
244
 
245
+ const initialJob = singleRunAsJobResult(feature, result);
243
246
  await superviseRun({
244
- initial: [singleRunAsJobResult(feature, result)],
247
+ initial: [initialJob],
248
+ repairGoalExecution: true,
245
249
  limits,
246
250
  ...(deliverableEye ? { observeDeliverables: deliverableEye } : {}),
247
251
  ...(opts.supervise.onDecision ? { onDecision: opts.supervise.onDecision } : {}),
@@ -250,11 +254,12 @@ export async function runSelfImplementCliCommand(
250
254
  },
251
255
  // ⛔ 단일의 「다시 걸기」는 «같은 골을 다시»다 — 연합의 resumeFrom 과 다르다.
252
256
  // ⇒ 그래서 판정은 «같고» 집행만 여기서 안다(superviseRun 은 그것을 모른다).
253
- rerun: async () => {
254
- const next = await exec(spec, pipelineDeps ? { pipelineDeps } : undefined);
257
+ rerun: async (_, invocation) => {
258
+ const repairGoal = invocation.repairGoals?.[initialJob.taskId];
259
+ const next = await exec(repairGoal ? { ...spec, input: { ...spec.input, text: repairGoal } } : spec, pipelineDeps ? { pipelineDeps } : undefined);
255
260
  result = next.result;
256
261
  exitCode = next.exitCode;
257
- return [singleRunAsJobResult(feature, next.result)];
262
+ return [{ ...singleRunAsJobResult(feature, next.result), taskId: initialJob.taskId }];
258
263
  },
259
264
  });
260
265
  }
@@ -15,12 +15,19 @@
15
15
  * evidence-uncovered → derive-evidence-rejudge 런 출력·시험·배선에서 증거를 끌어내 1회 재판정
16
16
  * review-repeat · review-out-of-scope · fabric · launch-failed · unclassified → needs-owner(새 라운드 없음)
17
17
  *
18
+ * 사다리의 «출처»: `graphs/overlays/stop-heal-ladder.yaml` 이 갈래마다 처방 노드(id = 처방 이름)를 선언한다.
19
+ * 그 갈래에 얹힌 오버레이가 더한 노드 id 가 처방이다(source='graph'). 선언이 없거나 못 읽으면
20
+ * 위 표(`AUTOHEAL_ACTION_BY_CLASS`)로 간다(source='code-default'). 둘이 같은 답을 내는지는 시험이 문다.
21
+ *
18
22
  * 모드 `tools.selfImplement.autoheal.mode` = `shadow`(기본: 할 일을 원장에만 적고 손대지 않는다) | `live`.
19
23
  * ⛔ live 에서도 행동은 주입 seam 으로만 한다 — seam 이 없는 행동은 `needs-owner`(action-unwired)로 떨어진다.
20
24
  * «안 이어진 손»을 「고쳤다」로 읽지 않게.
21
25
  * 관측 = `debug.log('self-implement.autoheal', …)` ⊕ 원장 `event='autoheal'`.
22
26
  */
23
27
  import { debug } from '../debug/log.js';
28
+ import { applyGraphOverlays, defaultOverlaysDir, loadGraphOverlays, selectOverlays, type GraphOverlaySpec } from './graph-overlay-yaml.js';
29
+ import type { GraphTemplateSpec } from './graph-yaml.js';
30
+ import { GRAPH_SPECS } from './graph-templates.js';
24
31
  import { appendRunLedgerEntry, loadRunLedger, type RunLedgerEntry, type RunLedgerWriter } from './run-ledger.js';
25
32
  import type { RunStopClass, RunStopRecord } from './run-stop.js';
26
33
 
@@ -48,6 +55,58 @@ export const AUTOHEAL_ACTION_BY_CLASS: Readonly<Record<RunStopClass, AutohealAct
48
55
  unclassified: 'needs-owner',
49
56
  };
50
57
 
58
+ /** 처방 노드 id 로 인정하는 이름 — 오버레이가 더한 노드 중 이 넷만 처방으로 읽는다. */
59
+ const HEAL_ACTION_NODE_IDS: ReadonlySet<string> = new Set<Exclude<AutohealAction, 'needs-owner'>>([
60
+ 'clean-workspace-regate', 'rebase-regate', 'resume-from-salvage', 'derive-evidence-rejudge',
61
+ ]);
62
+
63
+ /** 처방이 «어디서» 왔나 — `graph` = 그 갈래에 얹힌 오버레이가 더한 노드 · `code-default` = 위 표. */
64
+ export type AutohealActionSource = 'graph' | 'code-default';
65
+
66
+ export interface AutohealPick {
67
+ action: AutohealAction;
68
+ source: AutohealActionSource;
69
+ /** source='graph' 일 때 그 처방 노드를 더한 오버레이. */
70
+ overlayId?: string;
71
+ }
72
+
73
+ /** 사다리를 읽어 오는 자리 — 활성 템플릿 선언 ⊕ 오버레이 목록. 시험이 갈아 끼운다. */
74
+ export interface AutohealLadder {
75
+ template: GraphTemplateSpec | undefined;
76
+ overlays: readonly GraphOverlaySpec[];
77
+ }
78
+
79
+ function loadDefaultLadder(): AutohealLadder {
80
+ return { template: GRAPH_SPECS['self-implement'], overlays: loadGraphOverlays(defaultOverlaysDir()).overlays };
81
+ }
82
+
83
+ /**
84
+ * 갈래 → 처방. 그 `stop_class` 에 얹힌 오버레이가 활성 템플릿에 더한 처방 노드 id 를 고른다.
85
+ * ⛔ 오버레이가 없거나 · 조건 키가 없거나 · 로드·적용이 실패하거나 · 처방 노드가 정확히 하나가 아니면
86
+ * «조용히» 코드 표로 간다 — 그 사실은 `source` 가 말한다. 던지지 않는다.
87
+ */
88
+ export function pickAutohealAction(cls: RunStopClass, loadLadder: () => AutohealLadder = loadDefaultLadder): AutohealPick {
89
+ const codeDefault: AutohealPick = { action: AUTOHEAL_ACTION_BY_CLASS[cls] ?? 'needs-owner', source: 'code-default' };
90
+ try {
91
+ const { template, overlays } = loadLadder();
92
+ if (!template) return codeDefault;
93
+ const { applied } = selectOverlays(overlays, { graphId: template.graphId, stage: 'heal', state: { stop_class: cls } });
94
+ const declared = new Set(template.nodes.map((node) => node.nodeId));
95
+ for (const overlay of applied) {
96
+ const result = applyGraphOverlays(template, [overlay]);
97
+ if (!result.ok) {
98
+ observe('overlay-rejected', { stopClass: cls, overlayId: overlay.overlayId, rejections: result.rejections.map((r) => r.kind) });
99
+ continue;
100
+ }
101
+ const added = result.template.nodes.map((node) => node.nodeId).filter((id) => !declared.has(id) && HEAL_ACTION_NODE_IDS.has(id));
102
+ if (added.length === 1) return { action: added[0] as AutohealAction, source: 'graph', overlayId: overlay.overlayId };
103
+ }
104
+ } catch (error) {
105
+ observe('ladder-unreadable', { stopClass: cls, error: error instanceof Error ? error.message : String(error) });
106
+ }
107
+ return codeDefault;
108
+ }
109
+
51
110
  /** 설정 원값 → 모드. 모르는 값은 «좁은 쪽»(shadow). */
52
111
  export function parseAutohealMode(raw: unknown): AutohealMode {
53
112
  return raw === 'live' ? 'live' : 'shadow';
@@ -84,6 +143,10 @@ export interface AutohealResult {
84
143
  runId: string;
85
144
  class: RunStopClass;
86
145
  action: AutohealAction;
146
+ /** 처방을 «어디서» 골랐나 — 그래프 선언 또는 코드 표. */
147
+ source: AutohealActionSource;
148
+ /** source='graph' 일 때 처방 노드를 더한 오버레이. */
149
+ overlayId?: string;
87
150
  mode: AutohealMode;
88
151
  outcome: AutohealOutcome;
89
152
  /** needs-owner 일 때 사람이 볼 이유(멈춘 원인 그대로 또는 행동 실패). */
@@ -98,6 +161,8 @@ export interface AutohealDeps {
98
161
  /** 가드용 원장 읽기 — 기본 `loadRunLedger(runId)`. null = 원장 없음(첫 시도). */
99
162
  loadLedger?: (runId: string) => readonly RunLedgerEntry[] | null;
100
163
  writeLedger?: RunLedgerWriter;
164
+ /** 사다리 선언 읽기 — 기본은 활성 템플릿 ⊕ `graphs/overlays`. 던지면 코드 표로 간다. */
165
+ loadLadder?: () => AutohealLadder;
101
166
  }
102
167
 
103
168
  /**
@@ -128,8 +193,8 @@ function observe(event: string, data: Record<string, unknown>): void {
128
193
  export async function autohealFromStop(ctx: AutohealContext, deps: AutohealDeps = {}): Promise<AutohealResult> {
129
194
  const mode = deps.mode ?? DEFAULT_AUTOHEAL_MODE;
130
195
  const cls = ctx.record.class;
131
- const action = AUTOHEAL_ACTION_BY_CLASS[cls] ?? 'needs-owner';
132
- const base = { runId: ctx.runId, class: cls, action, mode };
196
+ const { action, source, overlayId } = pickAutohealAction(cls, deps.loadLadder);
197
+ const base = { runId: ctx.runId, class: cls, action, source, ...(overlayId ? { overlayId } : {}), mode };
133
198
 
134
199
  let ledger: readonly RunLedgerEntry[] | null = null;
135
200
  try {
@@ -145,6 +210,8 @@ export async function autohealFromStop(ctx: AutohealContext, deps: AutohealDeps
145
210
  return { ...base, outcome: 'already-attempted' };
146
211
  }
147
212
 
213
+ observe('action-source', { stopClass: cls, action, source, overlayId: overlayId ?? null });
214
+
148
215
  let outcome: AutohealOutcome;
149
216
  let reason: string | undefined;
150
217
  let detail: string | undefined;
@@ -225,7 +225,8 @@ export function providerNamesFromFallbackChain(chain: readonly string[]): string
225
225
  const out: string[] = [];
226
226
  const seen = new Set<string>();
227
227
  for (const step of chain) {
228
- const name = step === 'grok' ? 'grok' : step === 'codex-rotate' ? 'openai-codex' : undefined;
228
+ // `codex-credits`(체인 마지막 칸 · 크레딧)도 provider 는 openai-codex 다 — 이미 앞에 있으면 중복이라 빠진다.
229
+ const name = step === 'grok' ? 'grok' : step === 'codex-rotate' || step === 'codex-credits' ? 'openai-codex' : undefined;
229
230
  if (!name || seen.has(name)) continue;
230
231
  seen.add(name);
231
232
  out.push(name);
@@ -1,8 +1,8 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { closeSync, mkdirSync, openSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
3
- import { join, resolve } from 'node:path';
3
+ import { join } from 'node:path';
4
4
  import { getElanousConfigDir } from '../elanous-config-dir.js';
5
- import { resolveHqLaunchSeat } from '../hq/seats.js';
5
+ import { installedCliArgv, resolveHqLaunchSeat } from '../hq/seats.js';
6
6
  import { debug } from '../debug/log.js';
7
7
  import { publishInsideEvent } from '../nexus/api/inside-events.js';
8
8
  import { redactSecrets } from '../task-cards/card-store.js';
@@ -56,7 +56,7 @@ const DEFAULT_POOL = 'pool-node-b@node-b:8';
56
56
  // `harness budget --json` outcomes: proceed | next-provider | wait-reset | stop. next-provider still runs (the harness switches provider itself).
57
57
  const BUDGET_PROCEED: readonly string[] = ['proceed', 'next-provider'];
58
58
  const launchMode = (settings: StewardSettings): 'off' | 'shadow' | 'live' => launchModeOf(settings.mode ?? settings.launch ?? 'shadow');
59
- const cli = (args: string[]) => [process.execPath, resolve(import.meta.dir, '../../bin/elanous.mjs'), ...(process.env.NODE_ENV === 'test' ? ['--test'] : []), ...args];
59
+ const cli = (args: string[]) => installedCliArgv([...(process.env.NODE_ENV === 'test' ? ['--test'] : []), ...args]);
60
60
  const sourceOf = (issue: TriageIssue): string => /^출처: (telegram|pwa|tui|cli)$/m.exec(issue.body)?.[1] ?? `linear:${issue.identifier}`;
61
61
  const promptOf = (issue: TriageIssue): string => `${issue.title}\n${issue.body.slice(0, 1500)}`;
62
62
  const argvFor = (issue: TriageIssue, settings: StewardSettings): string[] =>
@@ -108,7 +108,8 @@ function stewardLaunchSeat(): string {
108
108
  return resolveHqLaunchSeat('OP', getElanousConfigDir());
109
109
  }
110
110
 
111
- function runCommand(args: string[], cwd = resolve(import.meta.dir, '../..')): { exitCode: number; stdout: string; stderr: string } {
111
+ /** Read commands run in the registered OP seat as well: the tree this module was loaded from may be a stale clone. */
112
+ function runCommand(args: string[], cwd = stewardLaunchSeat()): { exitCode: number; stdout: string; stderr: string } {
112
113
  const result = Bun.spawnSync(args, { cwd, stdout: 'pipe', stderr: 'pipe' });
113
114
  return { exitCode: result.exitCode, stdout: result.stdout.toString(), stderr: result.stderr.toString() };
114
115
  }
@@ -1,6 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { debug } from '../debug/log.js';
3
3
  import type { TriageDecision, TriageIssue } from './triage.js';
4
+ import { InvalidWorkingBackwardsDraft } from './working-backwards.js';
4
5
 
5
6
  export interface HashedTriageDecision extends TriageDecision {
6
7
  inputHash: string;
@@ -125,3 +126,64 @@ export async function triageWithinBudget({ issues, previous, judge, tracks, conc
125
126
  debug.log('steward.triage', 'budget', stats);
126
127
  return { decisions, stats };
127
128
  }
129
+
130
+ export interface CarriedDraft<T> {
131
+ wish: T;
132
+ /** `deadline` = never started before the stage deadline; `timeout` = per-item or stage deadline hit mid-draft. */
133
+ reason: 'timeout' | 'deadline' | 'error';
134
+ }
135
+
136
+ type DraftOutcome<T> = { kind: 'drafted' } | { kind: 'invalid' } | { kind: 'carried'; reason: CarriedDraft<T>['reason'] };
137
+
138
+ export async function draftWithinBudget<T>({ wishes, draft, concurrency = 4, perItemMs = 120_000, deadlineMs = 240_000, now = Date.now }: {
139
+ wishes: T[];
140
+ /** `expired` rejects once this item's own deadline passes; drafts should race their slow calls against it. */
141
+ draft: (wish: T, expired: Promise<never>) => Promise<void>;
142
+ concurrency?: number;
143
+ perItemMs?: number;
144
+ deadlineMs?: number;
145
+ now?: () => number;
146
+ }): Promise<{ drafted: T[]; carried: Array<CarriedDraft<T>>; invalid: T[]; stats: { drafted: number; carried: number; invalid: number } }> {
147
+ const end = now() + deadlineMs;
148
+ const slots = Number.isFinite(concurrency) ? Math.max(1, Math.floor(concurrency)) : 4;
149
+ const outcomes: Array<DraftOutcome<T> | undefined> = new Array(wishes.length);
150
+ let stageTimer: ReturnType<typeof setTimeout> | undefined;
151
+ const stageExpired = new Promise<void>(resolve => { stageTimer = setTimeout(resolve, Math.max(0, end - now())); });
152
+ const timedOut = Symbol('draft-timeout');
153
+ let cursor = 0;
154
+ const worker = async (): Promise<void> => {
155
+ while (cursor < wishes.length) {
156
+ const remaining = end - now();
157
+ if (remaining <= 0) return;
158
+ const index = cursor++;
159
+ let itemTimer: ReturnType<typeof setTimeout> | undefined;
160
+ const expired = new Promise<never>((_, reject) => { itemTimer = setTimeout(() => reject(timedOut), Math.min(perItemMs, remaining)); });
161
+ // The draft may finish without ever racing this promise — never leave its rejection unhandled.
162
+ expired.catch(() => undefined);
163
+ const work = Promise.resolve().then(() => draft(wishes[index]!, expired));
164
+ const settled = work.then(() => undefined, () => undefined);
165
+ try {
166
+ await Promise.race([work, expired]);
167
+ outcomes[index] = { kind: 'drafted' };
168
+ } catch (error) {
169
+ outcomes[index] = error instanceof InvalidWorkingBackwardsDraft ? { kind: 'invalid' }
170
+ : { kind: 'carried', reason: error === timedOut ? 'timeout' : 'error' };
171
+ // A timed-out draft keeps its concurrency slot until the underlying call settles or the stage ends.
172
+ await Promise.race([settled, stageExpired]);
173
+ } finally { if (itemTimer) clearTimeout(itemTimer); }
174
+ }
175
+ };
176
+ try {
177
+ await Promise.all(Array.from({ length: Math.min(slots, wishes.length) }, () => worker()));
178
+ } finally { if (stageTimer) clearTimeout(stageTimer); }
179
+ const result = { drafted: [] as T[], carried: [] as Array<CarriedDraft<T>>, invalid: [] as T[] };
180
+ for (const [index, wish] of wishes.entries()) {
181
+ const outcome = outcomes[index];
182
+ if (outcome?.kind === 'drafted') result.drafted.push(wish);
183
+ else if (outcome?.kind === 'invalid') result.invalid.push(wish);
184
+ else result.carried.push({ wish, reason: outcome?.kind === 'carried' ? outcome.reason : 'deadline' });
185
+ }
186
+ const stats = { drafted: result.drafted.length, carried: result.carried.length, invalid: result.invalid.length };
187
+ debug.log('steward.triage', 'draft-budget', stats);
188
+ return { ...result, stats };
189
+ }
@@ -8,6 +8,10 @@ import { getUserConfig } from '../user-config.js';
8
8
  import { emitDecision } from '../live/detail-switch.js';
9
9
  import { debug, redactSecretText } from '../debug/log.js';
10
10
  import { loopEvent } from '../loops/observe.js';
11
+ import { checkLoops, daemonScheduleEntries, entriesFromRegistry, extendHealthyLauncher, launcherEvidence, notifyOwners, scheduleFindings, versionEvidence, type CheckEntry } from '../loops/checker.js';
12
+ import { listSchedules, schedulesDbPath, type ScheduleRow } from '../domains/schedule-registry.js';
13
+ import { Database } from 'bun:sqlite';
14
+ import { defaultLoopRoot, listAllLoops, listLoops } from '../loops/registry.js';
11
15
  import { effectiveInstanceRoot } from '../instance/resolve.js';
12
16
  import { redactSecrets } from '../task-cards/card-store.js';
13
17
  import { directiveHash } from './directive.js';
@@ -17,7 +21,7 @@ import { planLaunches, launch, collectOutcomes, raiseHitl, readLaunchLedger, sav
17
21
  import { listFabricExecutionCandidates } from '../self-dev/fabric-plan-core.js';
18
22
  import { InvalidWorkingBackwardsDraft, recordWorkingBackwardsOnCards } from './working-backwards.js';
19
23
  import { CardStore } from '../task-cards/card-store.js';
20
- import { triageWithinBudget } from './triage-budget.js';
24
+ import { draftWithinBudget, triageWithinBudget } from './triage-budget.js';
21
25
  import { askSteward, classifyIssue, installedCapabilities, planIssues, ruleJudgment, unsafeReason, type ClassifiedIssue, type StewardAsk } from './triage-plan.js';
22
26
  import { failureAlertText, markStageStart, markStageEnd, readFailureStreak, shouldAlert, writeFailureStreak, type FailureStreak, type StewardStage } from './failure-streak.js';
23
27
 
@@ -59,6 +63,8 @@ export interface StewardDeps {
59
63
  ask?: StewardAsk;
60
64
  /** Maximum time for each working-backwards wish draft in schedule (milliseconds). */
61
65
  draftItemMs?: number;
66
+ /** Maximum time for the whole wish-drafting phase in schedule (milliseconds); wishes still undrafted are carried. */
67
+ draftDeadlineMs?: number;
62
68
  launchSettings?: Pick<StewardSettings, 'mode' | 'launch' | 'maxParallel' | 'podPool'>;
63
69
  launchCommand?: LaunchDeps['command'];
64
70
  spawnLaunch?: LaunchDeps['spawn'];
@@ -68,6 +74,9 @@ export interface StewardDeps {
68
74
  outcomeNotify?: Pick<OutcomeNotifyDeps, 'document' | 'message' | 'worktreeCommand' | 'loadOrigin' | 'loadOrigins'>;
69
75
  /** Internal command boundary: the current sync has already claimed this pid. */
70
76
  stageOwnedByCommand?: boolean;
77
+ /** Isolated scheduler inventory for this tick; production reads the unified registry. */
78
+ scheduleInventory?: () => CheckEntry[];
79
+ daemonSchedules?: () => ScheduleRow[];
71
80
  }
72
81
 
73
82
  /** rescue launches/harvests like live (HQ-SEAT). */
@@ -314,6 +323,61 @@ function logStageFailure(root: string, stage: StewardStage, reason: string): voi
314
323
  if (runId) try { rmSync(tickDecisionPath(root, runId), { force: true }); } catch { /* Observation must not change the failure. */ }
315
324
  logTickDecision(root, 'failed', `${stage}: ${reason}`);
316
325
  }
326
+ const SCHEDULE_CHECK_MS = 15 * 60_000;
327
+
328
+ /** Called by runStewardStage(sync) under its stage slot, never from the checker CLI. */
329
+ async function checkStewardSchedules(root: string, settings: StewardSettings, deps: StewardDeps): Promise<void> {
330
+ const now = (deps.now ?? (() => new Date()))();
331
+ const marker = join(root, 'steward', 'schedule-check.json');
332
+ let previous: { at?: string } = {};
333
+ try { previous = JSON.parse(readFileSync(marker, 'utf8')) as typeof previous; }
334
+ catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
335
+ const elapsed = now.getTime() - Date.parse(previous.at ?? '');
336
+ if (Number.isFinite(elapsed) && elapsed >= 0 && elapsed < SCHEDULE_CHECK_MS) return;
337
+ const inventory = deps.scheduleInventory ?? (() => {
338
+ const repo = deps.root ? process.cwd() : defaultLoopRoot();
339
+ const registryOptions = { now, root: repo, stateRoot: root,
340
+ ...(deps.root ? { schedules: [] } : {}) };
341
+ const { entries } = entriesFromRegistry({ now, listAllLoops: () => listAllLoops(registryOptions) });
342
+ const graphs = listLoops(registryOptions);
343
+ const byId = new Map(graphs.map(graph => [graph.id, graph.file]));
344
+ return versionEvidence(launcherEvidence(entries.map(entry => byId.has(entry.id)
345
+ ? { ...entry, file: byId.get(entry.id) } : entry), root), repo, root);
346
+ });
347
+ const daemonRows = deps.daemonSchedules ?? (() => {
348
+ const path = deps.root ? join(root, 'schedules.db') : schedulesDbPath();
349
+ if (!existsSync(path)) return [];
350
+ try {
351
+ const db = new Database(path, { readonly: true });
352
+ try { return listSchedules(db); } finally { db.close(); }
353
+ } catch (error) {
354
+ debug.log('steward.schedule-check', 'daemon-inventory-unavailable', { reason: redactSecretText(String(error)) });
355
+ return [];
356
+ }
357
+ });
358
+ const entries = inventory();
359
+ const seen = new Set(entries.map(entry => entry.id));
360
+ const checked = checkLoops([...entries, ...daemonScheduleEntries(daemonRows(), now).filter(row => !seen.has(row.id))], now);
361
+ const findings = scheduleFindings(checked, now);
362
+ if (settings.mode === 'live') {
363
+ let recovered = 0;
364
+ const unresolved = findings.filter(finding => {
365
+ try {
366
+ if (extendHealthyLauncher(root, finding, now)) { recovered++; return false; }
367
+ } catch (error) {
368
+ debug.log('steward.schedule-check', 'recovery-failed', { id: finding.id, reason: redactSecretText(String(error)) });
369
+ }
370
+ return true;
371
+ });
372
+ const remainingChecks = checked.filter(row => unresolved.some(finding => finding.id === row.id && finding.kind === row.state));
373
+ const ownerRequests = notifyOwners(remainingChecks, { root, now }, unresolved);
374
+ const opEscalations = notifyOwners(remainingChecks, { root, now }, unresolved, true);
375
+ debug.log('steward.schedule-check', 'routing', { ownerRequests, opEscalations, recovered });
376
+ }
377
+ debug.log('steward.schedule-check', 'checked', { count: checked.length, findings: findings.length, mode: settings.mode });
378
+ writeFileSync(marker, JSON.stringify({ at: now.toISOString() }));
379
+ }
380
+
317
381
  function readState(path: string): { issues: Record<string, string>; digestDay?: string } {
318
382
  try { return JSON.parse(readFileSync(path, 'utf8')) as ReturnType<typeof readState>; }
319
383
  catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { issues: {} }; throw error; }
@@ -343,6 +407,11 @@ export async function runStewardStage(stage: 'sync' | 'triage' | 'schedule' | 'r
343
407
  const runId = (graphRunId() ?? process.env.ELANOUS_RUN_ID?.trim()) || `steward-stage-${randomUUID()}`;
344
408
  let outcome: 'ok' | 'fail' = 'fail';
345
409
  try {
410
+ if (stage === 'sync') {
411
+ // The schedule check is an add-on to sync: its failure is observed, never allowed to stop the Linear sync.
412
+ try { await checkStewardSchedules(root, settings, deps); }
413
+ catch (error) { debug.log('steward.schedule-check', 'failed', { reason: redactSecretText(String(error)) }, { level: 'warn' }); }
414
+ }
346
415
  const key = await (deps.getSecret ?? getSecretAsync)('connector.linear.apiKey');
347
416
  if (!key) throw new Error('connector.linear.apiKey missing');
348
417
  if (stage === 'sync') {
@@ -420,27 +489,19 @@ export async function runStewardStage(stage: 'sync' | 'triage' | 'schedule' | 'r
420
489
  const carried = new Set<string>();
421
490
  if (store) try {
422
491
  recordTriageOnCards(wishes, issues, { root, store, now: deps.now });
423
- for (const wish of wishes) {
424
- const timedOut = Symbol('draft-timeout');
425
- let timer: ReturnType<typeof setTimeout> | undefined;
426
- const timeout = new Promise<never>((_, reject) => {
427
- timer = setTimeout(() => reject(timedOut), deps.draftItemMs ?? 120_000);
428
- });
429
- // The draft may finish without ever racing this promise (no ask needed) — never leave its rejection unhandled.
430
- timeout.catch(() => undefined);
431
- try {
432
- // Bound the ask itself so a late answer cannot write to a store closed after this tick.
433
- const ask: StewardAsk = (prompt, role) => Promise.race([(deps.ask ?? askSteward)(prompt, role), timeout]);
434
- await recordWorkingBackwardsOnCards([wish], issues, store, ask);
435
- } catch (error) {
436
- if (error instanceof InvalidWorkingBackwardsDraft) {
437
- invalid.add(wish.issue);
438
- debug.log('steward.stage', 'draft-invalid', { issue: wish.issue });
439
- } else {
440
- carried.add(wish.issue);
441
- debug.log('steward.schedule', 'draft-carried', { issue: wish.issue, reason: error === timedOut ? 'timeout' : 'error' });
442
- }
443
- } finally { if (timer) clearTimeout(timer); }
492
+ const outcome = await draftWithinBudget({
493
+ wishes, perItemMs: deps.draftItemMs ?? 120_000, deadlineMs: deps.draftDeadlineMs ?? 240_000,
494
+ // Bound the ask itself so a late answer cannot write to a store closed after this tick.
495
+ draft: (wish, expired) => recordWorkingBackwardsOnCards([wish], issues, store,
496
+ (prompt, role) => Promise.race([(deps.ask ?? askSteward)(prompt, role), expired])),
497
+ });
498
+ for (const wish of outcome.invalid) {
499
+ invalid.add(wish.issue);
500
+ debug.log('steward.stage', 'draft-invalid', { issue: wish.issue });
501
+ }
502
+ for (const { wish, reason } of outcome.carried) {
503
+ carried.add(wish.issue);
504
+ debug.log('steward.schedule', 'draft-carried', { issue: wish.issue, reason });
444
505
  }
445
506
  } finally { if (!deps.cardStore) store.close(); }
446
507
  if (invalid.size || carried.size) rows = rows.map(row => invalid.has(row.issue)