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
@@ -65,6 +65,7 @@ import { findGrokModel, GROK_MODELS } from '../grok/models.js';
65
65
  import { resolveModelAlias } from '../intelligence-map/model-alias.js';
66
66
  import { BUILTIN_CATALOG } from '../intelligence-map/model-catalog.js';
67
67
  import { BEDROCK_DEFAULT_MODEL, BEDROCK_MODELS, findBedrockModel, resolveBedrockModelId } from '../llm/bedrock.js';
68
+ import { ZAI_CODING_DEFAULT_MODEL, ZAI_CODING_MODELS } from '../llm/zai-coding-plan.js';
68
69
 
69
70
  /** `dev --plan` 도움말이 가리키는 대응 문. ⛔ 도움말에 이 문자열을 리터럴로 다시 적지 마라. */
70
71
  export const DEV_PLAN_REPLACEMENT = 'elanous harness say';
@@ -1097,6 +1098,9 @@ export async function executeDevSelfRun(
1097
1098
  retainConfirmedMerge(result);
1098
1099
  await superviseRun({
1099
1100
  initial: [initialJob],
1101
+ // A review-oscillation repair goal runs through the existing piece executor (fragment re-execution with the
1102
+ // authored goal as its feature). Without that executor the rerun could only replay the same goal — declare false.
1103
+ repairGoalExecution: supervise.executePiece !== undefined,
1100
1104
  enrich: (results) => hydrateDevGoalCauseObserved(
1101
1105
  hydrateDevDecomposeProposals(results, supervise.readProposals ?? readDecomposeProposals),
1102
1106
  ),
@@ -1157,7 +1161,24 @@ export async function executeDevSelfRun(
1157
1161
  }) + orderingNote,
1158
1162
  };
1159
1163
  },
1160
- rerun: async (previous, { relaunch }) => {
1164
+ rerun: async (previous, { relaunch, repairGoals }) => {
1165
+ const repairJobs = previous.flatMap((job) => repairGoals?.[job.taskId] === undefined ? [] : [{ job, goal: repairGoals[job.taskId]! }]);
1166
+ if (repairJobs.length > 0 && supervise.executePiece !== undefined) {
1167
+ if (recordRestart('relaunch', previous.at(-1)!)) return [];
1168
+ executedRounds += 1;
1169
+ // Every authored repair goal runs (one per oscillating task), each keeping its task's goal feature so a
1170
+ // second oscillation is recognised as «already repaired».
1171
+ const repaired: ReturnType<typeof asSupervisorJobResult>[] = [];
1172
+ for (const { job, goal } of repairJobs) {
1173
+ try { debug.log('self-dev.supervisor', 'oscillation-repair.executed', { surface: 'dev', taskId: job.taskId, chars: goal.length }); } catch { /* fail-open */ }
1174
+ const repairResult = recordResult(await supervise.executePiece(goal));
1175
+ retainConfirmedMerge(repairResult);
1176
+ if (shardResults === undefined) result = repairResult;
1177
+ repaired.push({ ...asSupervisorJobResult(job.feature, repairResult), oscillationRepairOf: job.taskId });
1178
+ }
1179
+ // Jobs without a repair goal are not dropped: their last result stays in the round for the next decision.
1180
+ return [...repaired, ...previous.filter((job) => repairGoals?.[job.taskId] === undefined)];
1181
+ }
1161
1182
  if (promotedPieces !== undefined) {
1162
1183
  const pieces = promotedPieces;
1163
1184
  promotedPieces = undefined;
@@ -1340,12 +1361,19 @@ export function normalizeChildLlmProvider(provider: string): string {
1340
1361
  : p;
1341
1362
  }
1342
1363
 
1364
+ /** CLAUDE-IMPL-LANE — claude-acp = Claude 구독 ACP 구현 레인(src/agent-substrate/acp-implementer.ts). 모델 카탈로그·사다리는 anthropic 과 같다. */
1365
+ function childModelCatalogProvider(provider: string): string {
1366
+ const normalized = normalizeChildLlmProvider(provider);
1367
+ return normalized === 'claude-acp' ? 'anthropic' : normalized;
1368
+ }
1369
+
1343
1370
  function isKnownChildLlmProvider(provider: string): boolean {
1344
- return ['grok', 'anthropic', 'gemini', 'openai', 'openai-codex', 'local', 'openrouter', 'bedrock'].includes(normalizeChildLlmProvider(provider));
1371
+ // ZAI-POD-CHILD (0.2.24) — glm = Z.ai GLM Coding Plan 직결(`ZAI_API_KEY` · src/llm/zai-coding-plan.ts). Pod 자식이 `--child-llm-provider glm` 으로 뜬다.
1372
+ return ['grok', 'anthropic', 'claude-acp', 'gemini', 'openai', 'openai-codex', 'local', 'openrouter', 'bedrock', 'glm'].includes(normalizeChildLlmProvider(provider));
1345
1373
  }
1346
1374
 
1347
1375
  function findPickerCatalogModel(provider: string, id: string): CatalogModelView | undefined {
1348
- switch (normalizeChildLlmProvider(provider)) {
1376
+ switch (childModelCatalogProvider(provider)) {
1349
1377
  case 'grok': return findGrokModel(id);
1350
1378
  case 'anthropic': return findClaudeModel(id);
1351
1379
  case 'gemini': return findGeminiModel(id);
@@ -1353,12 +1381,15 @@ function findPickerCatalogModel(provider: string, id: string): CatalogModelView
1353
1381
  case 'openai-codex': return findCodexModel(id);
1354
1382
  case 'openrouter': return findOpenRouterChildModel(id);
1355
1383
  case 'bedrock': return findBedrockChildModel(id);
1384
+ case 'glm': return (ZAI_CODING_MODELS as readonly string[]).includes(id)
1385
+ ? { id, tier: /flash/i.test(id) ? 'budget' : 'balanced', supportsThinking: true, recommended: id === ZAI_CODING_DEFAULT_MODEL }
1386
+ : undefined;
1356
1387
  default: return undefined;
1357
1388
  }
1358
1389
  }
1359
1390
 
1360
1391
  /** BEDROCK-PROVIDER — `anthropic.claude-sonnet-5-5` · `bedrock/claude-sonnet-5-5` · (provider=bedrock 일 때) `claude-sonnet-5-5`
1361
- * 를 받아 와이어 id(`anthropic.` 접두)로 해석한다. 표 밖은 «모른다» → 거부(다른 provider 와 같은 규율). */
1392
+ * 를 받아 와이어 id 로 해석한다(5.5 = 전역 추론 프로필 `global.anthropic.…` · BEDROCK-RUNTIME-PATH). 표 밖은 «모른다» → 거부(다른 provider 와 같은 규율). */
1362
1393
  function findBedrockChildModel(id: string): CatalogModelView | undefined {
1363
1394
  const entry = findBedrockModel(id);
1364
1395
  if (!entry) return undefined;
@@ -1394,7 +1425,7 @@ function tierFromBuiltin(tags: readonly string[] | undefined): string {
1394
1425
  }
1395
1426
 
1396
1427
  function findBuiltinCatalogModel(provider: string, id: string): CatalogModelView | undefined {
1397
- const expected = normalizeChildLlmProvider(provider);
1428
+ const expected = childModelCatalogProvider(provider);
1398
1429
  const entry = BUILTIN_CATALOG.models.find((model) => (
1399
1430
  model.id === id && normalizeChildLlmProvider(model.provider) === expected
1400
1431
  ));
@@ -1408,7 +1439,7 @@ function findBuiltinCatalogModel(provider: string, id: string): CatalogModelView
1408
1439
  }
1409
1440
 
1410
1441
  function listImplementationChildModelCandidates(provider: string): string[] {
1411
- const expected = normalizeChildLlmProvider(provider);
1442
+ const expected = childModelCatalogProvider(provider);
1412
1443
  const picker = (() => {
1413
1444
  switch (expected) {
1414
1445
  case 'grok': return GROK_MODELS.map((model) => model.id);
@@ -1418,6 +1449,7 @@ function listImplementationChildModelCandidates(provider: string): string[] {
1418
1449
  case 'openai-codex': return CODEX_MODELS.map((model) => model.id);
1419
1450
  case 'openrouter': return [...new Set(MODEL_TIERS.map((t) => LLM_TIER_MAP_BY_PROVIDER.openrouter[t].model))];
1420
1451
  case 'bedrock': return BEDROCK_MODELS.map((model) => model.id);
1452
+ case 'glm': return [...ZAI_CODING_MODELS];
1421
1453
  default: return [];
1422
1454
  }
1423
1455
  })();
@@ -1576,10 +1608,12 @@ export function grokWeeklyUsedPctFromUsageJson(stdout: string): number | undefin
1576
1608
  /** `elanous usage --json` 의 grok 행 → 잔량 판정. 한 행이라도 100% 미만이면 usable, 모두 100% 이상이면 exhausted. */
1577
1609
  export function grokQuotaFromUsageJson(stdout: string): 'usable' | 'exhausted' | 'unknown' {
1578
1610
  try {
1579
- const parsed = JSON.parse(stdout) as { rows?: Array<{ provider?: string; credits?: { status?: string; usedPercent?: number | null } }> };
1611
+ const parsed = JSON.parse(stdout) as { rows?: Array<{ provider?: string; credits?: { status?: string; periodType?: string | null; usedPercent?: unknown } }> };
1580
1612
  const percents = (parsed.rows ?? [])
1581
- .filter((row) => row.provider === 'grok' && row.credits?.status === 'ok' && typeof row.credits.usedPercent === 'number')
1582
- .map((row) => row.credits!.usedPercent as number);
1613
+ .filter((row) => row.provider === 'grok' && row.credits?.status === 'ok'
1614
+ && row.credits.periodType === 'USAGE_PERIOD_TYPE_WEEKLY')
1615
+ .map((row) => row.credits?.usedPercent)
1616
+ .filter((pct): pct is number => typeof pct === 'number' && Number.isFinite(pct) && pct >= 0 && pct <= 100);
1583
1617
  if (percents.length === 0) return 'unknown';
1584
1618
  return percents.some((p) => p < 100) ? 'usable' : 'exhausted';
1585
1619
  } catch {
@@ -1722,7 +1756,7 @@ function announceChildLlmInterpretation(
1722
1756
  export const CHILD_LLM_DEFAULT_TIER = 'better' as const;
1723
1757
 
1724
1758
  export function defaultChildLlmModel(provider: string): string {
1725
- return tierModel(CHILD_LLM_DEFAULT_TIER, provider as never);
1759
+ return tierModel(CHILD_LLM_DEFAULT_TIER, childModelCatalogProvider(provider) as never);
1726
1760
  }
1727
1761
 
1728
1762
  export type ChildLlmConfigSnapshot = { provider?: string; model?: string };
@@ -15,7 +15,8 @@
15
15
  // self 는 backend 리스트 항목이 아니라 transport 축을 붕괴시키는 특수 갈래. 감독 축도 self=turn 레벨.
16
16
 
17
17
  import { execFileSync } from 'node:child_process';
18
- import { createHash } from 'node:crypto';
18
+ import { createHash, randomUUID } from 'node:crypto';
19
+ import { benchArmBranchForBase } from '../bench/bench.js';
19
20
  import { readFileSync } from 'node:fs';
20
21
  import { dispatchTask, type DispatchTaskDeps } from '../execution-loop/dispatch-task.js';
21
22
  import { getNestDepth } from '../agent/nest-depth.js';
@@ -609,7 +610,11 @@ export function toSelfImplementOptions(text: string, plan: ResolvedDevPlan, seam
609
610
  ...(s.naturalLanguageDispatch ? { naturalLanguageDispatch: true } : {}),
610
611
  ...(s.childLlm ? { childLlm: s.childLlm } : {}),
611
612
  ...(s.correlationId !== undefined ? { correlationId: s.correlationId } : {}),
612
- ...(s.branchName ? { branchName: s.branchName } : {}),
613
+ // BENCH-ISOLATED-ARMS ② — base 가 `bench/<id>/base` 면 팔 가지 `bench/<id>/<arm>` (명시 branchName 이 이긴다).
614
+ ...(() => {
615
+ const branchName = s.branchName ?? benchArmBranchForBase(plan.base, s.childLlm?.model, process.env.ELANOUS_RUN_ID?.trim() || randomUUID());
616
+ return branchName ? { branchName } : {};
617
+ })(),
613
618
  ...(s.maxReworkRounds !== undefined ? { maxReworkRounds: s.maxReworkRounds } : {}),
614
619
  launch: resolveLaunchStamp({ entrance: plan.entrance, queueId: process.env.ELANOUS_HARNESS_QUEUE_LAUNCH }),
615
620
  };
@@ -23,11 +23,14 @@ export interface PrTerminalRecord {
23
23
  ownerSource: 'seat' | 'card' | 'unknown';
24
24
  card: string | null;
25
25
  at: string;
26
+ /** Which host-regate gate failed and why — null for every other terminal. */
27
+ hostRegateFailure: { step: string; detail: string; os: string } | null;
26
28
  }
27
29
  export interface PodTerminalInput {
28
30
  childRunId: string;
29
31
  state: 'complete' | 'failed' | 'aborted';
30
- disposition?: { prNumber?: number | null; prUrl?: string | null; checkedHeadCommit?: string | null; stage?: string; merged?: boolean; ok?: boolean } | null;
32
+ disposition?: { prNumber?: number | null; prUrl?: string | null; checkedHeadCommit?: string | null; stage?: string; merged?: boolean; ok?: boolean;
33
+ hostRegate?: { passed: boolean; failures: Array<{ step: string; detail: string }>; os: string } | null } | null;
31
34
  env?: Readonly<Record<string, string | undefined>>;
32
35
  prBody?: string;
33
36
  goalText?: string;
@@ -47,13 +50,22 @@ export function classifyPodTerminal(input: PodTerminalInput): PrTerminalRecord {
47
50
  const card = cardOf(input.prBody) ?? cardOf(input.goalText);
48
51
  const seat = input.env?.ELANOUS_HARNESS_SEAT;
49
52
  const ownerSource = seat === 'OP' || seat === 'TC' || seat === 'MK' || seat === 'UX' ? 'seat' : card ? 'card' : 'unknown';
53
+ const hostRegate = d?.hostRegate;
54
+ const firstFailure = hostRegate?.passed === false && Array.isArray(hostRegate.failures) ? hostRegate.failures[0] : undefined;
55
+ const hostRegateFailure = d?.stage === 'host-regate-failed' && hostRegate?.passed === false
56
+ ? { step: typeof firstFailure?.step === 'string' && firstFailure.step ? firstFailure.step : 'unknown',
57
+ detail: typeof firstFailure?.detail === 'string' && firstFailure.detail
58
+ ? firstFailure.detail.split(/\s+/).filter(Boolean).join(' ').slice(0, 300)
59
+ : 'failures empty',
60
+ os: hostRegate.os }
61
+ : null;
50
62
  return {
51
63
  childRunId: input.childRunId, prNumber, prUrl, repository,
52
64
  headSha: typeof d?.checkedHeadCommit === 'string' && /^[a-f0-9]{40}$/i.test(d.checkedHeadCommit) ? d.checkedHeadCommit : null,
53
65
  terminalClass: !d ? 'failed-pr-unobserved' : d.merged === true ? 'landed'
54
66
  : prNumber === null ? 'failed-no-pr'
55
67
  : input.state === 'complete' && d.ok !== false ? 'merge-ready-not-merged' : 'failed-with-pr',
56
- stage: d?.stage ?? null,
68
+ stage: d?.stage ?? null, hostRegateFailure,
57
69
  owner: ownerSource === 'seat' ? seat! : ownerSource === 'card' ? card! : 'unknown',
58
70
  ownerSource, card, at: input.at ?? new Date().toISOString(),
59
71
  };
@@ -4,6 +4,7 @@ import { decideDraft, draftClaimComment, draftClaimState, draftReleaseComment, l
4
4
  import { cardPrNumber, draftTaskOwner, type TaskCard } from '../task-agent/task-hand.js';
5
5
  import type { PrTerminalRecord } from './draft-residue.js';
6
6
  import { branchLineageSlug } from '../cli/pr-lineage.js';
7
+ import { branchLineageKey } from '../harness/worktree-branch-prefix.js';
7
8
 
8
9
  export interface SweepDraft extends DraftTriagePr {
9
10
  readonly title: string;
@@ -60,6 +61,12 @@ export interface DraftSweepAdapters {
60
61
  getReviewGate?(draft: SweepDraft, repository: string): Promise<SweepReviewGate | undefined>;
61
62
  /** One PR by number (any state); undefined means it could not be read. Only overlap metrics need it. */
62
63
  getPr?(repository: string, number: number): Promise<SweepMergedPr | undefined>;
64
+ /**
65
+ * Stops the still-live run behind a draft that a newer, also live, same-lineage run supersedes (`--apply` only).
66
+ * Must reject when the run could not be stopped — the draft is then neither labelled nor closed.
67
+ * Absent ⇒ a live duplicate is kept, never closed.
68
+ */
69
+ supersedeRun?(draft: SweepDraft, repository: string, newer: SweepDraft): Promise<void>;
63
70
  }
64
71
 
65
72
  /** Review PASS and gate posture used only by the daily old-draft count. */
@@ -138,6 +145,19 @@ export interface DraftMetrics {
138
145
  readonly conversion48h: number | null;
139
146
  }
140
147
 
148
+ /** Default maximum unclaimed open harness drafts for one seat. */
149
+ export const DRAFT_NEEDS_OWNER_SEAT_CAP = 15;
150
+
151
+ /** Compare one seat's observed needsOwner count; the caller supplies counts of open drafts by seat. */
152
+ export function isSeatDraftNeedsOwnerOverCap(
153
+ seat: string,
154
+ needsOwnerBySeat: Readonly<Record<string, number>>,
155
+ cap = DRAFT_NEEDS_OWNER_SEAT_CAP,
156
+ ): boolean {
157
+ const count = Object.hasOwn(needsOwnerBySeat, seat) ? needsOwnerBySeat[seat] : undefined;
158
+ return typeof count === 'number' && Number.isFinite(count) && count > cap;
159
+ }
160
+
141
161
  export async function collectDraftMetrics(repository: string, adapters: Pick<DraftSweepAdapters, 'listDrafts' | 'listRecentClosed' | 'getActiveClaimOwner'>, now: Date): Promise<DraftMetrics> {
142
162
  const clock = now.getTime();
143
163
  if (!Number.isFinite(clock)) throw new Error('Invalid draft metric clock');
@@ -444,6 +464,7 @@ const stalledLabel = label('close', 'stalled');
444
464
  const supersededLabel = label('close', 'superseded');
445
465
  const approvalLabel = PR_LABELS.find((entry) => entry.axis === 'state' && entry.sweep.action === 'none')?.name;
446
466
  const keepLabel = PR_LABELS.find((entry) => entry.sweep.action === 'exclude')?.name;
467
+ const benchLabel = PR_LABELS.find((entry) => entry.name === 'elanous:bench')!.name;
447
468
  const releaseHoldLabel = PR_LABELS.find((entry) => entry.axis === 'addon' && entry.sweep.action === 'none')?.name;
448
469
  const harvestableLabel = PR_LABELS.find((entry) => entry.name === 'elanous:harvestable')!.name;
449
470
  const PAGE_SIZE = 100;
@@ -596,9 +617,26 @@ export async function mapBounded<T, R>(items: readonly T[], limit: number, work:
596
617
  }
597
618
 
598
619
  function harnessDraft(draft: SweepDraft): boolean {
620
+ // BENCH-ISOLATED-ARMS — a bench arm PR is comparison material: the sweep never marks, supersedes or closes it.
621
+ if (draft.labels.includes(benchLabel)) return false;
599
622
  return draft.branch.startsWith('self-impl/') || draft.labels.some((name) => name.startsWith('elanous:'));
600
623
  }
601
624
 
625
+ /**
626
+ * The newest open draft of the same goal lineage that was created strictly after `draft` — a rerun
627
+ * (e.g. a forced requeue) of the same goal. Reads only the branch, so a draft with no task card still has a lineage.
628
+ * ⛔ A branch without a goal id has no lineage key and never matches.
629
+ */
630
+ export function newerLineageDraft(draft: SweepDraft, openDrafts: readonly SweepDraft[]): SweepDraft | undefined {
631
+ const key = branchLineageKey(draft.branch);
632
+ const created = Date.parse(draft.createdAt);
633
+ if (key === null || !Number.isFinite(created)) return undefined;
634
+ return openDrafts
635
+ .filter((pr) => pr.number !== draft.number && branchLineageKey(pr.branch) === key
636
+ && Number.isFinite(Date.parse(pr.createdAt)) && Date.parse(pr.createdAt) > created)
637
+ .sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt) || b.number - a.number)[0];
638
+ }
639
+
602
640
  /**
603
641
  * Census of harness drafts older than 24h. Same-goal landing uses `sameGoalPr` unchanged.
604
642
  * A draft counts as closable when a same-goal merged PR exists, harvestable when review PASS and the gate passed,
@@ -842,6 +880,71 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
842
880
  const ageHours = (now.getTime() - Date.parse(draft.createdAt)) / 3_600_000;
843
881
  const idleHours = typeof draft.updatedAt === 'string' && Number.isFinite(Date.parse(draft.updatedAt))
844
882
  ? (now.getTime() - Date.parse(draft.updatedAt)) / 3_600_000 : NaN;
883
+ // A newer open draft of the same goal lineage (a rerun, e.g. a forced requeue) supersedes this one even when
884
+ // its running claim is fresh or its run is still live. Holds, merged twins and the title-based duplicate rule keep their priority.
885
+ const lineageNewer = contradiction || draft.labels.includes(harvestableLabel) ? undefined : newerLineageDraft(draft, drafts);
886
+ if (lineageNewer) {
887
+ const base = decideDraft({ draft, runStatus, mergedTwins: merged, openDrafts: drafts, liveBranches,
888
+ finalRunResult, ageHours: runStatus ? ageHours : idleHours });
889
+ if (!base.reason.startsWith('label:') && !base.reason.startsWith('superseded-by #') && !base.reason.startsWith('duplicate-of-open #')) {
890
+ const live = (status: string | undefined, branch: string) => status === 'running' || status === 'probable-running'
891
+ || (status !== 'self-implement.result final' && liveBranches.has(branch));
892
+ const runLive = live(runStatus, draft.branch);
893
+ const lineageKey = branchLineageKey(draft.branch);
894
+ const reason = `duplicate-of-open #${lineageNewer.number} (lineage ${lineageKey})${runLive ? ` · stop run ${draft.runId ?? 'unknown'}` : ''}`;
895
+ // Every lineage outcome that does not close falls through to the existing rules (preservation contract):
896
+ // a duplicate this rule cannot settle is decided exactly as it was before this rule existed.
897
+ const fallThrough = (why: string) => {
898
+ try { debug.log('self-dev.draft-sweep', 'lineage-fall-through', { number: draft.number, newer: lineageNewer.number, lineageKey, why }); } catch { /* fail-soft */ }
899
+ };
900
+ if (runLive && !adapters.supersedeRun) fallThrough('lineage-duplicate-run-live');
901
+ // Safety (OP 10-11): a live run is stopped only when the newer run of the same lineage is itself live —
902
+ // never trade a running attempt for one that already ended or was never observed running.
903
+ else if (runLive && !live(statuses.get(lineageNewer.number), lineageNewer.branch)) fallThrough('lineage-newer-run-not-live');
904
+ else if (closes >= closeCap) {
905
+ record({ number: draft.number, action: 'keep', reason: 'close-cap', applied: false });
906
+ continue;
907
+ } else {
908
+ let stopFailed = false;
909
+ // --apply only: the run is stopped before anything is written; a stop that cannot be confirmed writes nothing.
910
+ if (apply && runLive) {
911
+ const observed = { number: draft.number, newer: lineageNewer.number, runId: draft.runId ?? null, newerRunId: lineageNewer.runId ?? null, lineageKey };
912
+ try {
913
+ await adapters.supersedeRun!(draft, repository, lineageNewer);
914
+ try { debug.log('self-dev.draft-sweep', 'supersede-run', observed); } catch { /* fail-soft */ }
915
+ } catch (error) {
916
+ stopFailed = true;
917
+ try { debug.log('self-dev.draft-sweep', 'supersede-run-failed', { ...observed, reason: sweepFailureReason(error) }, { level: 'warn' }); } catch { /* fail-soft */ }
918
+ }
919
+ }
920
+ if (stopFailed) fallThrough('supersede-run-failed');
921
+ else {
922
+ // A close is counted against the cap only once it is decided here; a failed write gives the slot back.
923
+ closes += 1;
924
+ const entry: DraftSweepEntry = { number: draft.number, action: 'close', reason, statusLabel: supersededLabel, applied: false };
925
+ record(entry);
926
+ if (!apply) continue;
927
+ let labelsChanged = false;
928
+ try {
929
+ if (!draft.labels.includes(supersededLabel)) {
930
+ await adapters.setLabels(repository, draft.number, {
931
+ add: supersededLabel, remove: states.map((state) => state.name).filter((name) => name !== supersededLabel),
932
+ });
933
+ labelsChanged = true;
934
+ }
935
+ await adapters.closeDraft(repository, draft.number,
936
+ `Draft sweep: ${reason} (https://github.com/${repository}/pull/${lineageNewer.number}). Branch preserved.`);
937
+ entry.applied = true;
938
+ } catch (error) {
939
+ if (labelsChanged) entry.partialApplied = true;
940
+ else closes -= 1;
941
+ entry.error = String(error);
942
+ }
943
+ continue;
944
+ }
945
+ }
946
+ }
947
+ }
845
948
  // An unobserved claim may still have a merged twin: liveness/holds retain their priority,
846
949
  // but a superseded draft must not be hidden by the six-hour claim window.
847
950
  const mergedDecision = isClaimed ? decideDraft({ draft, runStatus, mergedTwins: merged, openDrafts: drafts, liveBranches,
@@ -103,3 +103,9 @@ export function intakeTakesQueue(label: 'queued' | 'direct', route: Pick<Journey
103
103
  const graphQueues = route.source === 'graph' ? route.to === 'queue' : label === 'queued';
104
104
  return graphQueues && eligible;
105
105
  }
106
+
107
+ /** 병합 직전 동결 검사의 «승인 요구» — 동결이 아닌(open) 병합을 그래프가 hold 로 보내면 true.
108
+ * ⛔ 그래프가 답을 못 내면(code-default) false — 오늘의 입장 판정 그대로. 동결·선점(frozen·taken)은 이미 코드가 정했다. */
109
+ export function freezeCheckHolds(label: 'open' | 'frozen' | 'taken', route: Pick<JourneyRoute, 'source' | 'to'>): boolean {
110
+ return label === 'open' && route.source === 'graph' && route.to === 'hold';
111
+ }
@@ -421,6 +421,9 @@ export async function runSelfOrchestrateCliCommand(
421
421
  await recordRoundPrs(results);
422
422
  results = await superviseRun({
423
423
  initial: results,
424
+ // The add-repair-task branch below runs each authored review-oscillation repair goal as that goal's new
425
+ // feature (a fresh piece, same goal id) — the supervisor records the repair text, so a second oscillation stops.
426
+ repairGoalExecution: true,
424
427
  limits,
425
428
  enrich: (rs) => backfillDecomposeProposals([...rs], deps),
426
429
  onDecision: (decision) => {
@@ -431,9 +434,28 @@ export async function runSelfOrchestrateCliCommand(
431
434
  onRound: (rs) => { try { baseRuntime.checkpoint?.([...rs]); } catch { /* fail-open */ } },
432
435
  // ⭐ 방금 끝난 결과를 넘긴다 — orchestrateSelfDev 가 classifyResumeDisposition 으로
433
436
  // skip/rerun 을 갈라준다(재발명 0 · 착지한 조각은 다시 안 돈다).
434
- rerun: async (previous) => {
437
+ rerun: async (previous, invocation) => {
435
438
  let addedRepairIds: string[] = [];
436
- if (currentDecision?.action === 'add-repair-task') {
439
+ // A requested repair that cannot be bound to exactly one goal is held (not relaunched with the old strategy).
440
+ const unrepairedHold: string[] = [];
441
+ if (invocation.repairGoals) {
442
+ const replacements = new Map<SelfDevGoal, string>();
443
+ for (const [taskId, repairGoal] of Object.entries(invocation.repairGoals)) {
444
+ const source = previous.find((result) => result.taskId === taskId);
445
+ const matches = source ? currentGoals.filter((goal) => resumeKey(goal.feature) === resumeKey(source.feature)) : [];
446
+ if (matches.length === 1 && !replacements.has(matches[0]!)) replacements.set(matches[0]!, repairGoal);
447
+ else unrepairedHold.push(taskId);
448
+ }
449
+ currentGoals = currentGoals.map((goal) => replacements.has(goal) ? { ...goal, feature: replacements.get(goal)! } : goal);
450
+ spec = buildSelfOrchestrateDevSpec(currentGoals, input.concurrency);
451
+ try {
452
+ debug.log('self-dev.supervisor', 'oscillation-repair.executed', {
453
+ surface: 'orchestrate', replaced: replacements.size, requested: Object.keys(invocation.repairGoals).length, held: unrepairedHold,
454
+ });
455
+ } catch { /* fail-open */ }
456
+ }
457
+ // A review-oscillation decision attaches only its authored repair goals (above) — never deliverable fragments too.
458
+ if (currentDecision?.action === 'add-repair-task' && !currentDecision.oscillation) {
437
459
  const appended = appendRepairFragments(currentGoals, currentDecision.classifications, {
438
460
  seenFingerprints: repairFingerprints,
439
461
  addedCount: repairFragmentsAdded,
@@ -487,7 +509,7 @@ export async function runSelfOrchestrateCliCommand(
487
509
  baseRuntime = runtimeWithParentRequest();
488
510
  // ⛔⭐ 판정이 「사람이 볼 것」이라 한 조각은 집행이 다시 돌리지 않는다 — 판정과 집행이 갈리면
489
511
  // `pr-opened` 조각이 duplicate-risk 로 재실행돼 PR 이 겹친다(2026-09-25 실측).
490
- const resumeHold = currentDecision?.needsHuman ?? [];
512
+ const resumeHold = [...new Set([...(currentDecision?.needsHuman ?? []), ...unrepairedHold])];
491
513
  // The engine's resume lookup is last-result-wins for each normalized feature.
492
514
  // Use that exact predecessor, not any earlier task sharing the same feature.
493
515
  const selectedPrior = new Map(previous.map((result) => [resumeKey(result.feature), result.taskId]));
@@ -23,6 +23,7 @@ import { parentUnlandedProgressLine } from './run-supervisor.js';
23
23
  import { mkdirSync, openSync, writeFileSync, closeSync, renameSync, unlinkSync } from 'node:fs';
24
24
  import { join, resolve } from 'node:path';
25
25
  import { goalPathFromArgv, launchRegistryDir, removeLaunchBinding, writeLaunchBinding } from '../harness/launch-registry.js';
26
+ import { adoptRunCard } from '../task-agent/run-adopt.js';
26
27
  import { availableParallelism, cpus } from 'node:os';
27
28
  import { selfDevRunsDir } from './run-store.js';
28
29
  import { HARNESS_RUN_ID_ENV, normalizeRunId } from '../harness/harness-space.js';
@@ -369,6 +370,11 @@ export type TriageAction =
369
370
  | 'decompose-and-retry'
370
371
  /** 기존 조각을 치환하거나 재실행하지 않고 수리 조각을 추가한다. */
371
372
  | 'add-repair-task'
373
+ /**
374
+ * main 정합(LLM 충돌 해결)이 막힌 조각을 «조각당 한 번» 최신 main 위에서 다시 건다.
375
+ * 보존 가지는 지우지 않고 재발사 골의 참고로만 쓴다. 두 번째부터는 `needs-human`.
376
+ */
377
+ | 'relaunch-on-main'
372
378
  /** 실패가 아니었다(전송 불일치) — 건드리지 않는다. */
373
379
  | 'no-action'
374
380
  /** 모른다 — 사람이 본다. */
@@ -386,7 +392,11 @@ export interface FailureClassification {
386
392
  }
387
393
 
388
394
  /** 순수: 분류 → 다음 행동. ⛔ 한 자리에서만 정한다(소비자가 각자 매핑하면 갈린다). */
389
- export function triageActionFor(kind: FailureKind): TriageAction {
395
+ export function triageActionFor(
396
+ kind: FailureKind,
397
+ /** `main-sync-blocked` 만 읽는다 — 이 조각의 재발사 한 번을 이미 썼으면 true. */
398
+ options: { mainSyncRelaunchSpent?: boolean } = {},
399
+ ): TriageAction {
390
400
  switch (kind) {
391
401
  case 'transient': return 'rerun';
392
402
  case 'blocked-upstream': return 'rerun-after-upstream';
@@ -396,14 +406,22 @@ export function triageActionFor(kind: FailureKind): TriageAction {
396
406
  case 'deliverable-broken':
397
407
  case 'oversized-goal': return 'add-repair-task';
398
408
  case 'false-failure': return 'no-action';
409
+ case 'main-sync-blocked': return options.mainSyncRelaunchSpent === true ? 'needs-human' : 'relaunch-on-main';
399
410
  case 'awaiting-human':
400
411
  case 'parent-unlanded':
401
- case 'main-sync-blocked':
402
412
  case 'provider-rejected': return 'needs-human';
403
413
  default: return 'needs-human';
404
414
  }
405
415
  }
406
416
 
417
+ /**
418
+ * main 정합 충돌로 멈춘 결과만 «최신 main 위 재발사» 후보다. harvestable 이라도 다른 단계(timed-out 등)에서
419
+ * 멈춘 결과는 후보가 아니다 — 지금처럼 사람이 수확한다.
420
+ */
421
+ export function isMainSyncRelaunchCandidate(result: Pick<SelfDevJobResult, 'stage'>): boolean {
422
+ return result.stage === 'merge-conflict';
423
+ }
424
+
407
425
  const LEDGER_FAILURE_KIND: Partial<Record<AbandonedClassification, FailureKind>> = {
408
426
  'implementation-deficit': 'unconverged',
409
427
  'report-deficit': 'unconverged',
@@ -588,6 +606,8 @@ export function failureFromDeployFindings(
588
606
  export function classifyFailures(
589
607
  results: SelfDevJobResult[],
590
608
  queryPreflight?: PreflightLogQuery,
609
+ /** 이 조각이 main 정합 재발사 한 번을 이미 썼나 — 생략은 «감독 없음» 이라 종전대로 needs-human. */
610
+ mainSyncRelaunchSpent?: (result: SelfDevJobResult) => boolean,
591
611
  ): FailureClassification[] {
592
612
  return results.flatMap((result) => {
593
613
  const preflightIdentifiers = [...new Set([result.runId, result.taskId].filter((id): id is string => Boolean(id)))];
@@ -603,7 +623,10 @@ export function classifyFailures(
603
623
  taskId: result.taskId,
604
624
  stage: result.stage ?? null,
605
625
  kind,
606
- action: triageActionFor(kind),
626
+ action: triageActionFor(kind, {
627
+ // 생략 = 감독 없이 도는 경로(재발사할 주체가 없다) — 종전대로 needs-human. 재발사는 감독이 이력으로 셀 때만.
628
+ mainSyncRelaunchSpent: !isMainSyncRelaunchCandidate(result) || !mainSyncRelaunchSpent || mainSyncRelaunchSpent(result) === true,
629
+ }),
607
630
  ...(awaitingHuman ?? failureObservationFields(error?.code ?? 'UNKNOWN', error?.message ?? '')),
608
631
  }];
609
632
  });
@@ -651,8 +674,12 @@ export function triageRun(
651
674
  hasUnmeasuredDeliverables = false,
652
675
  /** Optional injected lookup of launch preflight observations; absence remains unknown. */
653
676
  queryPreflight?: PreflightLogQuery,
677
+ /** 이 조각이 main 정합 재발사 한 번을 이미 썼나 — 생략은 «감독 없음» 이라 종전대로 needs-human. */
678
+ mainSyncRelaunchSpent?: (result: SelfDevJobResult) => boolean,
654
679
  ): {
655
680
  classifications: FailureClassification[];
681
+ /** ⭐ main 정합이 막힌 조각 중 «첫 번» — 사람 수확 대신 최신 main 위에서 다시 건다. */
682
+ relaunchOnMain: string[];
656
683
  /** 그대로 다시 걸면 되는 조각(일시적·상류대기). */
657
684
  rerunnable: string[];
658
685
  /** 다시 걸 수는 있으나 같은 결과일 수 있는 조각(수렴 실패). */
@@ -678,21 +705,23 @@ export function triageRun(
678
705
  failureFromDeployFindings(taskId, observation.target, observation.findings));
679
706
  const classifications = [
680
707
  ...deployClassifications,
681
- ...classifyFailures(results, queryPreflight),
708
+ ...classifyFailures(results, queryPreflight, mainSyncRelaunchSpent),
682
709
  ];
683
710
  const pick = (a: TriageAction) => classifications.filter((c) => c.action === a).map((c) => c.taskId);
684
711
  const rerunnable = [...pick('rerun'), ...pick('rerun-after-upstream')];
685
712
  const reworkable = pick('rework');
686
713
  const decomposable = pick('decompose-and-retry');
687
714
  const repairable = [...new Set(pick('add-repair-task'))]; // ⛔ finding 이 여럿이면 같은 taskId 가 겹친다
715
+ const relaunchOnMain = [...new Set(pick('relaunch-on-main'))];
688
716
  return {
689
717
  classifications,
718
+ relaunchOnMain,
690
719
  rerunnable,
691
720
  reworkable,
692
721
  decomposable,
693
722
  repairable,
694
723
  needsHuman: pick('needs-human'),
695
- actionable: rerunnable.length + reworkable.length + decomposable.length + repairable.length > 0,
724
+ actionable: rerunnable.length + reworkable.length + decomposable.length + repairable.length + relaunchOnMain.length > 0,
696
725
  deliverableUnmeasured,
697
726
  };
698
727
  }
@@ -897,7 +926,11 @@ export function writeRunPidRecord(runId: string, runsDir: string = selfDevRunsDi
897
926
  const cwd = process.cwd();
898
927
  const goalPath = goalPathFromArgv(process.argv, cwd);
899
928
  writeLaunchBinding(runId, { pid: process.pid, startedAt: Date.now() - Math.floor(process.uptime() * 1000), argv0: process.argv0,
900
- cwd, ...(goalPath ? { goalPath } : {}), runsDir: resolve(runsDir) }, registryDir);
929
+ cwd, ...(goalPath ? { goalPath } : {}), runsDir: resolve(runsDir),
930
+ ...(process.env.ELANOUS_POD_NAME ? { podName: process.env.ELANOUS_POD_NAME } : {}),
931
+ ...(process.env.ELANOUS_TASK_CARD_ID ? { taskCardId: process.env.ELANOUS_TASK_CARD_ID } : {}) }, registryDir);
932
+ try { adoptRunCard({ runId, cwd, goalPath, runsDir, env: process.env }); }
933
+ catch (error) { try { debug.log('task-agent', 'run-adopt-failed', { runId, error: String(error) }); } catch { /* launch must continue */ } }
901
934
  }
902
935
  return pidPath;
903
936
  }
@@ -1381,6 +1414,8 @@ export function orchestrateSelfDev(opts: OrchestrateSelfDevOptions): Promise<Sel
1381
1414
  ...(disp?.completionDisposition ? { completionDisposition: disp.completionDisposition } : {}),
1382
1415
  ...(disp?.failureClassification ? { failureClassification: disp.failureClassification } : {}),
1383
1416
  ...(disp?.providerErrors ? { providerErrors: disp.providerErrors } : {}),
1417
+ ...(disp?.rework ? { rework: disp.rework } : {}),
1418
+ ...(disp?.mustFix ? { reviewMustFix: disp.mustFix } : {}),
1384
1419
  ...(goalCauseObservedFromFailureClassification(disp?.failureClassification) === true
1385
1420
  ? { goalCauseObserved: true } : {}),
1386
1421
  ...(disp?.runId ? { runId: disp.runId } : {}),