elanous 0.2.15 → 0.2.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/chunks/{1307-33ffcc49ab45123b.js → 1307-aac7332576c4d6fc.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{6877-7c6208705c284124.js → 6877-fe94e76a5841ee9b.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/8401-4ce1c42c8d89903f.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/899-f41be85e8a680651.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/{9103-7a3ae7f60af3b99f.js → 9103-60fbef33e66f0a46.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-0e2c57fe6add4b2a.js → page-3143b07a817d1da9.js} +1 -1
  10. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-e05a77df7e005b04.js → page-17ba086ef82d339b.js} +1 -1
  11. package/apps/pwa/out/_next/static/chunks/app/board/page-7eb661ff23af7da2.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/app/botlab/page-6b8444914da74ca6.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/app/bots/{page-b8c49f6b4fa2df91.js → page-6e5c3a867557298b.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/app/ceo/page-7110f732f45bf944.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/app/chat/{page-976813c91d7394ea.js → page-9a41d65355fc25b9.js} +1 -1
  16. package/apps/pwa/out/_next/static/chunks/app/consult/page-5e77cd1802567c89.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/control/{page-1d4901eb7c2d8822.js → page-41afec0b255ed2bb.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-72d3e7ff35a8c954.js → page-4187666132fc98c4.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/editor/{page-60c7a8e1a4fe62ce.js → page-09328d499162bae4.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/exec/{page-2a52b316cb143d9c.js → page-29ff3a8f84f03b6a.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/field/{page-c3ce483c8d5db370.js → page-80ec7401bc83f511.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/inside/page-ab93b240f687d69a.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/app/intake/{page-96f074f6538e8a2c.js → page-fc7d86467291ae38.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/{layout-e97be5334d83a9f5.js → layout-de43a186e5ba5683.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/app/live/page-8b08cafad27759c9.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/loops/page-0e2d6985fb42cd2f.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/market/page-13e4108999f58d13.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e8296ee284aa1293.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/morning/{page-c1343f9499d86f3f.js → page-043a9111e74f6b24.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/{not-found-8739a2e0ae793130.js → not-found-23a4601ceb7f452e.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/observatory/page-1ed1324b1dc36754.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-d23b12bb92242558.js → page-10d958605a77b3bd.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-166745e51ac0c7b0.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-c7fea475488201a8.js → page-a04260b8ccc968d4.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/outputs/page-7a1d85e121065cc8.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/page-1f50e3d4e13435f4.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-5efbf81124c474ae.js → page-12916918988ac97e.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d8a3347a9d0c3f4a.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/sessions/page-a9117d2cd3093d36.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-06ee266953d9c879.js → page-5a64cb5208c6a8fd.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/settings/{page-153c2b3bcc89e8de.js → page-1407ec83ea64c1f9.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-1d824510df025d18.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/setup/page-6e878ebbe1737d00.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/share/{page-0d6d23b40263a7ea.js → page-7f49fe645d5132f7.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/app/showroom/page-81a4e7f8dbfe74cb.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/tasks/page-27d71f131acd9320.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/term/page-3ab59d19913b200e.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/today/page-bf85b2f5bf077925.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/trace/{page-73735b64bfe1ae67.js → page-424659bacfb8cdd7.js} +1 -1
  50. package/apps/pwa/out/_next/static/chunks/app/vault/page-ab32a8f522a60371.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-f7e631e9b4daf66e.js → page-fc2df261f8a2c7df.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/workflows/page-09461366af82ef13.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-9817c7f9007466cc.js → page-5d4fef9ccd7d406b.js} +1 -1
  54. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ea90a8ac73ce2749.js +1 -0
  55. package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +1 -0
  56. package/apps/pwa/out/_next/static/css/{04dabcbcc4bfa6db.css → 085673d826b98b62.css} +1 -1
  57. package/apps/pwa/out/approvals/index.html +1 -1
  58. package/apps/pwa/out/approvals/index.txt +15 -15
  59. package/apps/pwa/out/autopilot/index.html +1 -1
  60. package/apps/pwa/out/autopilot/index.txt +15 -15
  61. package/apps/pwa/out/board/index.html +1 -1
  62. package/apps/pwa/out/board/index.txt +15 -15
  63. package/apps/pwa/out/botlab/index.html +1 -1
  64. package/apps/pwa/out/botlab/index.txt +15 -15
  65. package/apps/pwa/out/bots/index.html +1 -1
  66. package/apps/pwa/out/bots/index.txt +15 -15
  67. package/apps/pwa/out/ceo/index.html +1 -1
  68. package/apps/pwa/out/ceo/index.txt +15 -15
  69. package/apps/pwa/out/chat/index.html +1 -1
  70. package/apps/pwa/out/chat/index.txt +15 -15
  71. package/apps/pwa/out/consult/index.html +1 -1
  72. package/apps/pwa/out/consult/index.txt +15 -15
  73. package/apps/pwa/out/control/index.html +1 -1
  74. package/apps/pwa/out/control/index.txt +15 -15
  75. package/apps/pwa/out/design-check/index.html +1 -1
  76. package/apps/pwa/out/design-check/index.txt +15 -15
  77. package/apps/pwa/out/editor/index.html +1 -1
  78. package/apps/pwa/out/editor/index.txt +15 -15
  79. package/apps/pwa/out/exec/index.html +1 -1
  80. package/apps/pwa/out/exec/index.txt +15 -15
  81. package/apps/pwa/out/field/index.html +1 -1
  82. package/apps/pwa/out/field/index.txt +15 -15
  83. package/apps/pwa/out/healthz +1 -1
  84. package/apps/pwa/out/index.html +1 -1
  85. package/apps/pwa/out/index.txt +15 -15
  86. package/apps/pwa/out/inside/index.html +1 -1
  87. package/apps/pwa/out/inside/index.txt +15 -15
  88. package/apps/pwa/out/intake/index.html +1 -1
  89. package/apps/pwa/out/intake/index.txt +15 -15
  90. package/apps/pwa/out/live/index.html +1 -1
  91. package/apps/pwa/out/live/index.txt +15 -15
  92. package/apps/pwa/out/loops/index.html +1 -1
  93. package/apps/pwa/out/loops/index.txt +15 -15
  94. package/apps/pwa/out/market/index.html +1 -1
  95. package/apps/pwa/out/market/index.txt +15 -15
  96. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  97. package/apps/pwa/out/missions/placeholder/index.txt +15 -15
  98. package/apps/pwa/out/morning/index.html +1 -1
  99. package/apps/pwa/out/morning/index.txt +15 -15
  100. package/apps/pwa/out/observatory/index.html +1 -1
  101. package/apps/pwa/out/observatory/index.txt +15 -15
  102. package/apps/pwa/out/ops/checklist/index.html +1 -1
  103. package/apps/pwa/out/ops/checklist/index.txt +15 -15
  104. package/apps/pwa/out/ops/release/index.html +1 -1
  105. package/apps/pwa/out/ops/release/index.txt +15 -15
  106. package/apps/pwa/out/ops/seats/index.html +1 -1
  107. package/apps/pwa/out/ops/seats/index.txt +15 -15
  108. package/apps/pwa/out/outputs/index.html +1 -1
  109. package/apps/pwa/out/outputs/index.txt +15 -15
  110. package/apps/pwa/out/reflection/index.html +1 -1
  111. package/apps/pwa/out/reflection/index.txt +16 -16
  112. package/apps/pwa/out/scheduler/index.html +1 -1
  113. package/apps/pwa/out/scheduler/index.txt +15 -15
  114. package/apps/pwa/out/sessions/index.html +1 -1
  115. package/apps/pwa/out/sessions/index.txt +15 -15
  116. package/apps/pwa/out/settings/devices/index.html +1 -1
  117. package/apps/pwa/out/settings/devices/index.txt +15 -15
  118. package/apps/pwa/out/settings/index.html +1 -1
  119. package/apps/pwa/out/settings/index.txt +15 -15
  120. package/apps/pwa/out/setup/done/index.html +1 -1
  121. package/apps/pwa/out/setup/done/index.txt +15 -15
  122. package/apps/pwa/out/setup/index.html +1 -1
  123. package/apps/pwa/out/setup/index.txt +15 -15
  124. package/apps/pwa/out/share/index.html +1 -1
  125. package/apps/pwa/out/share/index.txt +15 -15
  126. package/apps/pwa/out/showroom/index.html +1 -1
  127. package/apps/pwa/out/showroom/index.txt +15 -15
  128. package/apps/pwa/out/tasks/index.html +1 -1
  129. package/apps/pwa/out/tasks/index.txt +15 -15
  130. package/apps/pwa/out/term/index.html +1 -1
  131. package/apps/pwa/out/term/index.txt +15 -15
  132. package/apps/pwa/out/today/index.html +1 -1
  133. package/apps/pwa/out/today/index.txt +15 -15
  134. package/apps/pwa/out/trace/index.html +1 -1
  135. package/apps/pwa/out/trace/index.txt +15 -15
  136. package/apps/pwa/out/vault/index.html +1 -1
  137. package/apps/pwa/out/vault/index.txt +15 -15
  138. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  139. package/apps/pwa/out/workflows/chat-ui/index.txt +15 -15
  140. package/apps/pwa/out/workflows/index.html +1 -1
  141. package/apps/pwa/out/workflows/index.txt +15 -15
  142. package/apps/pwa/out/workspace/index.html +1 -1
  143. package/apps/pwa/out/workspace/index.txt +15 -15
  144. package/apps/pwa/out/worktrees/index.html +1 -1
  145. package/apps/pwa/out/worktrees/index.txt +15 -15
  146. package/graphs/doc-gen/doc-gen.yaml +24 -0
  147. package/graphs/doc-gen/recipes.yaml +3 -0
  148. package/graphs/intake/intake-daily.yaml +9 -2
  149. package/graphs/intake/news-sources.yaml +4 -0
  150. package/graphs/intake/recipes.yaml +6 -1
  151. package/graphs/orchestrator/orchestrator.yaml +7 -0
  152. package/graphs/orchestrator/recipes.yaml +3 -0
  153. package/graphs/steward/recipes.yaml +2 -1
  154. package/package.json +1 -1
  155. package/scripts/__fixtures__/intake-news.xml +7 -0
  156. package/scripts/ci-public-export-import-gate.ts +154 -0
  157. package/scripts/ci-test-interference-gate.ts +5 -1
  158. package/scripts/ci-typecheck-changed.ts +34 -15
  159. package/scripts/docs/doc-rot.ts +164 -0
  160. package/scripts/docs/generate-graph-anatomy.ts +131 -9
  161. package/scripts/docs-cli-check.ts +4 -4
  162. package/scripts/goals/goal-census.ts +343 -0
  163. package/scripts/intake-news.ts +169 -0
  164. package/scripts/measure/author-section-contribution.ts +84 -0
  165. package/scripts/release-loop/cut-branch.ts +43 -13
  166. package/scripts/release-loop/docs-land-node.ts +69 -10
  167. package/scripts/release-loop/gate-node.ts +3 -1
  168. package/scripts/release-loop/publish-node.ts +2 -2
  169. package/scripts/release-loop/release-docs.ts +134 -0
  170. package/scripts/release-loop/vault-note-node.ts +19 -2
  171. package/scripts/release-loop/version-node.ts +7 -4
  172. package/scripts/release-story/draft.ts +42 -14
  173. package/scripts/rhythm/monthly.ts +97 -0
  174. package/src/acp/client.ts +17 -2
  175. package/src/agent-mission/driver.ts +104 -11
  176. package/src/agent-mission/supervisor-metrics-cli.ts +40 -0
  177. package/src/agent-mission/supervisor-metrics.ts +218 -0
  178. package/src/autopilot/track-agent-shadow.ts +2 -1
  179. package/src/autopilot/track-agent.ts +5 -3
  180. package/src/briefing/brief-items.ts +191 -36
  181. package/src/budget/usage-outcomes.ts +204 -0
  182. package/src/cli/autopilot-cli.ts +66 -0
  183. package/src/cli/brief-cli.ts +73 -0
  184. package/src/cli/decisions-cli.ts +4 -1
  185. package/src/cli/intake-cli.ts +1 -1
  186. package/src/cli/know-cli.ts +20 -0
  187. package/src/cli/log-event-names.ts +1 -0
  188. package/src/cli/logs-cli.ts +43 -1
  189. package/src/cli/pr-cli.ts +92 -13
  190. package/src/cli/provider-cli.ts +14 -0
  191. package/src/cli/release-cli.ts +11 -8
  192. package/src/cli/self-update-canary.ts +46 -0
  193. package/src/cli/self-update.ts +399 -1
  194. package/src/cli/usage-cli.ts +26 -0
  195. package/src/context-card/index.ts +45 -0
  196. package/src/context-card/task.ts +76 -0
  197. package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -0
  198. package/src/decisions/decision-ledger.ts +10 -1
  199. package/src/discord-self-message.ts +9 -0
  200. package/src/flow-loop/split.ts +24 -0
  201. package/src/harness/harness-cli-command.ts +178 -12
  202. package/src/harness/harness-pod-dispatch.ts +36 -55
  203. package/src/harness/harness-queue.ts +86 -7
  204. package/src/harness/harness-seams.ts +13 -3
  205. package/src/harness/helper-repair.ts +139 -8
  206. package/src/harness/helper-scan.ts +13 -2
  207. package/src/harness/nested-elanous-policy.ts +133 -0
  208. package/src/hq/fence-wrapper.ts +123 -0
  209. package/src/hq/hq.ts +16 -2
  210. package/src/hq/lease.ts +5 -3
  211. package/src/hq/transfer.ts +185 -0
  212. package/src/index.ts +168 -13
  213. package/src/intake-plane/digest.ts +85 -28
  214. package/src/knowledge/know-find.ts +105 -0
  215. package/src/llm.ts +75 -19
  216. package/src/loops/orchestrator/finish-rate.ts +14 -3
  217. package/src/loops/orchestrator/tick.ts +96 -25
  218. package/src/maturity/bot-slash-catalog.ts +11 -2
  219. package/src/maturity/feature-maturity.ts +2 -2
  220. package/src/maturity/tui-slash-availability.ts +9 -0
  221. package/src/nexus/api/grid.ts +61 -0
  222. package/src/nexus/api/http-server.ts +19 -1
  223. package/src/nexus/api/task-cards-api.ts +19 -0
  224. package/src/oauth/codex.ts +82 -2
  225. package/src/oauth/overload-failover-launch.ts +75 -0
  226. package/src/oauth/overload-failover.ts +137 -0
  227. package/src/release-loop/checklist.ts +10 -5
  228. package/src/release-loop/feature-store.ts +10 -9
  229. package/src/release-loop/landing-freeze.ts +18 -10
  230. package/src/release-loop/placement.ts +17 -8
  231. package/src/seat-dispatch/seat-ask.ts +33 -6
  232. package/src/seat-loop/seat-loop.ts +172 -21
  233. package/src/self-dev/ask-launch-flow.ts +48 -8
  234. package/src/self-dev/authored-goal-trace.ts +64 -0
  235. package/src/self-dev/dev-cli.ts +19 -1
  236. package/src/self-dev/dev-pipeline.ts +6 -5
  237. package/src/self-dev/entrance-registry.ts +10 -6
  238. package/src/self-dev/old-door.ts +73 -0
  239. package/src/self-dev/run-store.ts +5 -1
  240. package/src/self-dev/run-supervisor.ts +7 -1
  241. package/src/self-dev/supervisor-verdict-edges.ts +46 -0
  242. package/src/self-implement/app-api-budget.ts +166 -0
  243. package/src/self-implement/budget-gate.ts +7 -5
  244. package/src/self-implement/follow-up-goals.ts +289 -0
  245. package/src/self-implement/frozen-merges.ts +25 -20
  246. package/src/self-implement/goal-author-clarification.ts +81 -0
  247. package/src/self-implement/goal-author-cli.ts +33 -5
  248. package/src/self-implement/goal-author.ts +107 -25
  249. package/src/self-implement/goal-clarification-escalation.ts +37 -1
  250. package/src/self-implement/goal-document-write.ts +28 -0
  251. package/src/self-implement/heal-intake.ts +4 -0
  252. package/src/self-implement/heal-triage.ts +44 -0
  253. package/src/self-implement/host-regate.ts +3 -1
  254. package/src/self-implement/orchestrator.ts +157 -8
  255. package/src/self-implement/run-ledger.ts +4 -0
  256. package/src/self-implement/seams.ts +44 -15
  257. package/src/session-runtime/index.ts +2 -0
  258. package/src/session-runtime/retry-api.ts +87 -0
  259. package/src/session-runtime/retry-policy.ts +107 -6
  260. package/src/steward/working-backwards.ts +16 -1
  261. package/src/task-agent/actions.ts +118 -0
  262. package/src/task-agent/judge.ts +96 -0
  263. package/src/task-cards/card-cli.ts +25 -0
  264. package/src/task-cards/card-store.ts +9 -4
  265. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +33 -4
  266. package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
  267. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +4 -3
  268. package/src/task-orchestrator/surfaces/pod-ledger-prod-sink.ts +75 -0
  269. package/src/task-orchestrator/surfaces/pod-pool.ts +95 -18
  270. package/src/task-orchestrator/surfaces/pod-source-receive.ts +37 -1
  271. package/src/task-orchestrator/surfaces/self-implement-pod.ts +168 -37
  272. package/src/task-orchestrator/surfaces/self-implement.ts +2 -0
  273. package/src/telegram-commands.ts +6 -1
  274. package/src/telegram-loops-command.ts +21 -14
  275. package/src/telegram.ts +47 -0
  276. package/src/user-config.ts +59 -4
  277. package/src/version/packed-revision.json +1 -1
  278. package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +0 -1
  279. package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +0 -1
  280. package/apps/pwa/out/_next/static/chunks/9247-29d889b809288e65.js +0 -1
  281. package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +0 -1
  282. package/apps/pwa/out/_next/static/chunks/app/botlab/page-5491c115b1f9a923.js +0 -1
  283. package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +0 -1
  284. package/apps/pwa/out/_next/static/chunks/app/consult/page-28b9448091776c06.js +0 -1
  285. package/apps/pwa/out/_next/static/chunks/app/inside/page-13c7b53e0c2519a9.js +0 -1
  286. package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +0 -1
  287. package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +0 -1
  288. package/apps/pwa/out/_next/static/chunks/app/market/page-955ec2da3be4c560.js +0 -1
  289. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-9f88fbc15d9058d1.js +0 -1
  290. package/apps/pwa/out/_next/static/chunks/app/observatory/page-c9fc5706b8abaa11.js +0 -1
  291. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +0 -1
  292. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e2ee2896811e98d0.js +0 -1
  293. package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +0 -1
  294. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f1a1fc0a82738c.js +0 -1
  295. package/apps/pwa/out/_next/static/chunks/app/sessions/page-da6c08aae0f92768.js +0 -1
  296. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-87a11e6a05f15199.js +0 -1
  297. package/apps/pwa/out/_next/static/chunks/app/setup/page-7d4182964407c09c.js +0 -1
  298. package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +0 -1
  299. package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +0 -1
  300. package/apps/pwa/out/_next/static/chunks/app/term/page-f5597e894c02cd33.js +0 -1
  301. package/apps/pwa/out/_next/static/chunks/app/today/page-0337fdf7fb5e37b6.js +0 -1
  302. package/apps/pwa/out/_next/static/chunks/app/vault/page-8fcec4c9ea2e7f70.js +0 -1
  303. package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +0 -1
  304. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-97f9f43172a3a90e.js +0 -1
  305. package/apps/pwa/out/_next/static/css/1f7c958c41ffa3b9.css +0 -1
  306. package/src/launch-head/seat-goal-queue-lock.fixture.ts +0 -15
  307. package/src/launch-head/seat-goal-queue.ts +0 -198
  308. /package/apps/pwa/out/_next/static/{LZcstaO9_NXypPFmujolv → yRA5ur6DcPl0YHSRheH2O}/_buildManifest.js +0 -0
  309. /package/apps/pwa/out/_next/static/{LZcstaO9_NXypPFmujolv → yRA5ur6DcPl0YHSRheH2O}/_ssgManifest.js +0 -0
@@ -34,6 +34,7 @@ import { judgePredictionAccuracy } from './judge-prediction-accuracy.js';
34
34
  import { measureReviewFindingRecurrence, type ReviewFindingRecurrence } from './review-finding-recurrence.js';
35
35
  import { createHash } from 'node:crypto';
36
36
  import { appendFileSync, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync } from 'node:fs';
37
+ import { writeGoalDocumentAtomic } from './goal-document-write.js';
37
38
  import { hostname, loadavg } from 'node:os';
38
39
  import { resolveHostId } from '../platform/host-id.js';
39
40
  import { resolveInstanceName } from '../instance-identity.js';
@@ -66,9 +67,11 @@ import { appendRunLedgerEntry, loadRunLedger, parseRunShardIdentity, queryRunCha
66
67
  import { emitSessionEvent, type SessionEventInput } from '../context-bus/session-events.js';
67
68
  import { decideLineageSupersede, lineageSupersedeCloseComment, type LineageSupersedeOpenDraft } from './lineage-supersede.js';
68
69
  import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path';
70
+ import { homedir } from 'node:os';
69
71
  import { enqueueControlMemo, readSoftStopRequestStatus, type SoftStopRequestRead } from '../harness/control-inbox.js';
70
72
  import { debug } from '../debug/log.js';
71
73
  import { recordFailureEvent } from './heal-intake.js';
74
+ import { classifyReworkCapExhaustion, type ReworkCapHealDecision } from './heal-triage.js';
72
75
  import { releasePathHold, releasePathHoldComment, RELEASE_PATH_LABEL } from '../self-dev/release-path-guard.js';
73
76
  import { emitDecision } from '../live/detail-switch.js';
74
77
  import { reworkDecision } from './decision-events.js';
@@ -101,6 +104,26 @@ import { elanousStateRoot } from '../autopilot/state-paths.js';
101
104
  import { addNextMdReleaseNote, harnessReleaseNote, parseReleaseNoteSection, releaseNotesDir, renderReleaseNoteSection, writeReleaseNote, type ReleaseNoteFragment, type ReleaseNoteSection } from '../release-loop/release-note.js';
102
105
  import { landingFreezeMessage } from '../release-loop/landing-freeze.js';
103
106
  import { admitLandingMerge, owningRepoRoot } from './frozen-merges.js';
107
+ import { recordFollowUpOnMerge, type FollowUpMergeInput, type FollowUpSeams } from './follow-up-goals.js';
108
+
109
+ /** Merge result and exit stay untouched: a draft failure is one debug line. */
110
+ async function draftFollowUpAfterMerge(input: FollowUpMergeInput, followUp: FollowUpSeams | undefined): Promise<void> {
111
+ try {
112
+ await recordFollowUpOnMerge(input, {
113
+ ...(followUp ? {
114
+ ...(followUp.mode ? { mode: followUp.mode } : {}),
115
+ ...(followUp.readDrafts ? { readDrafts: followUp.readDrafts } : {}),
116
+ ...(followUp.appendDraft ? { appendDraft: followUp.appendDraft } : {}),
117
+ ...(followUp.enqueue ? { enqueue: followUp.enqueue } : {}),
118
+ ...(followUp.cellOwner ? { cellOwner: followUp.cellOwner } : {}),
119
+ ...(followUp.stateRoot ? { stateRoot: followUp.stateRoot } : {}),
120
+ } : {}),
121
+ log: (category, event, data) => { debug.log(category, event, data); },
122
+ });
123
+ } catch (error) {
124
+ debug.log('self-implement.follow-up', 'draft-failed', { prNumber: input.prNumber, error: error instanceof Error ? error.message : String(error) });
125
+ }
126
+ }
104
127
  import { plannedSelfImplBranch } from '../harness/worktree-branch-prefix.js';
105
128
  export { slugifyFeature } from '../harness/worktree-branch-prefix.js';
106
129
  import { AUTO_REVIEW_LABEL, resolveAutoReviewLabels } from './context-capsule.js';
@@ -220,7 +243,7 @@ export type { QuotaAccountAvailabilityEvidence };
220
243
  import { decideReworkSalvage, type ReworkSalvageEvidence } from './rework-salvage.js';
221
244
  import { type SupervisorGoalFillEvidence, fillUnverifiableGoalSlots, type SupervisorGoalFillResult } from './goal-supervisor-fill.js';
222
245
  import { escalateGoalDocumentClarifications, recordSupervisorPlanRevision, type GoalClarificationEscalationResult, type SupervisorPlanRevisionRelaxation, type SupervisorPlanRevisionResult } from './goal-clarification-escalation.js';
223
- import { parseGoalDocumentClarifications } from './goal-author-clarification.js';
246
+ import { parseGoalDocumentClarifications, selfAnswerGoalDocumentClarifications, type GoalClarificationSelfResolver } from './goal-author-clarification.js';
224
247
  import {
225
248
  createFileAskUserQuestionResolver,
226
249
  type AskUserQuestionDispatchContext,
@@ -1056,7 +1079,7 @@ export interface SelfImplementSeams {
1056
1079
  /** PR 생성 뒤 런의 라운드 대화를 게시한다. 실패는 PR 개설을 되돌리지 않는다. */
1057
1080
  postPrComment?: (opts: { number: number; body: string; cwd: string }) => Promise<void>;
1058
1081
  addPrLabel?: (opts: { number: number; label: string; cwd: string }) => Promise<void>;
1059
- readPrFiles?: (opts: { number: number; cwd: string }) => Promise<string[]>;
1082
+ readPrFiles?: (opts: { number: number; cwd: string; base?: string }) => Promise<string[]>;
1060
1083
  /** PR의 base/head SHA와 관측된 PR base를 한 응답에서 고정한다. 미주입·실패·빈 SHA면 검사 대상을 증명할 수 없어 fail-closed로 PR을 열어 둔다. */
1061
1084
  readPrCommitShas?: (opts: { number: number; cwd: string }) => Promise<{ baseCommit: string; headCommit: string; baseRefName?: string }>;
1062
1085
  /** 고정한 base/head SHA 쌍의 unified diff. 가변 PR 조회를 다시 사용하면 ABA head 변경에 안전하지 않으므로 두 SHA는 필수 입력이다. */
@@ -1090,6 +1113,12 @@ export interface SelfImplementSeams {
1090
1113
  readRunLedger?: (runId: string) => RunLedgerEntry[] | null | Promise<RunLedgerEntry[] | null>;
1091
1114
  closeDraft: (input: { number: number; comment: string }) => void | Promise<void>;
1092
1115
  };
1116
+ /**
1117
+ * FOLLOWUP-LOOP① — after a successful merge, leftover findings become one follow-up draft.
1118
+ * Tests inject every store. Omission writes nothing (no operational ledger, no queue).
1119
+ * Production `defaultSeams` fills the state-root ledger and, only in `live`, the harness queue.
1120
+ */
1121
+ followUp?: FollowUpSeams;
1093
1122
  /** ⑥ HITL 게이트 — PR open 전 승인. ★fail-closed: 생략/false 면 PR 안 열림(자동 승인 없음). review 동봉
1094
1123
  * (2026-07-21) — HITL 이 리뷰 verdict 를 보고 판단, auto 모드는 verdict 로 자동 결정. */
1095
1124
  approvePr?: (summary: { branch: string; gateLog?: string; implSummary: string; review?: SelfImplementReview }) => Promise<boolean>;
@@ -1126,6 +1155,8 @@ export interface SelfImplementSeams {
1126
1155
  /** ⭐ 「이 표면에 답할 사람이 있나」. 기본은 실제 stdin 의 TTY 여부다 —
1127
1156
  * ⛔ 실물에 매이면 테스트가 «환경»을 검사하게 되므로 주입 가능하게 둔다. */
1128
1157
  stdinIsInteractive?: () => boolean;
1158
+ /** Optional author self-answer seam for non-TTY Pods; never installs a terminal HITL resolver. */
1159
+ selfResolveClarification?: GoalClarificationSelfResolver;
1129
1160
  }
1130
1161
 
1131
1162
  /** onProgress seam 이 통지하는 단계(관측·UX 라벨). */
@@ -3394,7 +3425,26 @@ function mergeDecisionEvidenceCoverage(evidenceCoverage: {
3394
3425
  : { evidenceCoverageMeasured: false };
3395
3426
  }
3396
3427
 
3428
+ function installedBuildForOrigin(env: NodeJS.ProcessEnv): { installedVersion: string; installedCommit: string } {
3429
+ const root = env.ELANOUS_TEST_HOME?.trim()
3430
+ ? join(env.ELANOUS_TEST_HOME.trim(), '.local/share/elanous')
3431
+ : join(homedir(), '.local/share/elanous');
3432
+ try {
3433
+ const parsed: unknown = JSON.parse(readFileSync(join(root, 'install.json'), 'utf8'));
3434
+ if (typeof parsed !== 'object' || parsed === null) return { installedVersion: 'unknown', installedCommit: 'unknown' };
3435
+ const version = (parsed as { version?: unknown }).version;
3436
+ const commit = (parsed as { commit?: unknown }).commit;
3437
+ return {
3438
+ installedVersion: typeof version === 'string' && version.trim() ? version.trim() : 'unknown',
3439
+ installedCommit: typeof commit === 'string' && commit.trim() ? commit.trim() : 'unknown',
3440
+ };
3441
+ } catch {
3442
+ return { installedVersion: 'unknown', installedCommit: 'unknown' };
3443
+ }
3444
+ }
3445
+
3397
3446
  function runOriginData(env: NodeJS.ProcessEnv = process.env): RunOriginData {
3447
+ const installed = installedBuildForOrigin(env);
3398
3448
  return {
3399
3449
  hostId: resolveHostId(env),
3400
3450
  hostname: hostname(),
@@ -3403,6 +3453,8 @@ function runOriginData(env: NodeJS.ProcessEnv = process.env): RunOriginData {
3403
3453
  substrate: env.ELANOUS_SUBSTRATE || 'host',
3404
3454
  instance: resolveInstanceName(),
3405
3455
  elanousVersion: (JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8')) as { version: string }).version,
3456
+ installedVersion: installed.installedVersion,
3457
+ installedCommit: installed.installedCommit,
3406
3458
  ...(env.ELANOUS_POD_NAME ? { podName: env.ELANOUS_POD_NAME } : {}),
3407
3459
  ...(env.ELANOUS_NODE_NAME ? { nodeName: env.ELANOUS_NODE_NAME } : {}),
3408
3460
  ...(env.ELANOUS_POD_NAMESPACE ? { podNamespace: env.ELANOUS_POD_NAMESPACE } : {}),
@@ -3616,6 +3668,7 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
3616
3668
  },
3617
3669
  } satisfies LogSink);
3618
3670
  let traversalFinalized = false;
3671
+ reworkCapProceed = false;
3619
3672
  const progressDelivery = { delivered: 0, unwired: 0, callbackFailed: 0 };
3620
3673
  let capturedPrNumber: number | undefined;
3621
3674
  let openPrInvoked = false;
@@ -4179,10 +4232,31 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
4179
4232
  const terminalStage = terminalResult?.stage ?? 'crashed';
4180
4233
  try {
4181
4234
  const mappedStatus = mapStageToRunStatus(terminalStage)?.runStatus;
4182
- if (!terminalResult || mappedStatus === 'failed' || (mappedStatus === undefined && terminalResult.ok === false) || signalIncompleteState(roundClassifications).latestSignalIncomplete) {
4235
+ const reworkCapStop = terminalResult?.stage === 'pr-opened' && (
4236
+ terminalResult.outcome === 'budget-exhausted'
4237
+ || (terminalResult.outcome === 'completed' && reworkCapProceed)
4238
+ );
4239
+ if (!terminalResult || mappedStatus === 'failed' || (mappedStatus === undefined && terminalResult.ok === false) || signalIncompleteState(roundClassifications).latestSignalIncomplete || reworkCapStop) {
4240
+ const reworkCap = reworkCapStop ? classifyReworkCapExhaustion({
4241
+ introduced: terminalResult?.gate?.reflectGateFacts?.introduced
4242
+ ? [`introduced=${terminalResult.gate.reflectGateFacts.introduced}`]
4243
+ : [],
4244
+ mustFix: terminalResult?.review?.mustFix ?? terminalResult?.mustFix,
4245
+ summary: terminalResult?.review?.summary,
4246
+ }) : undefined;
4247
+ if (reworkCap) {
4248
+ observeOuter('rework-cap-heal', {
4249
+ stage: terminalStage,
4250
+ defectClass: reworkCap.defectClass,
4251
+ nextAction: reworkCap.nextAction,
4252
+ evidence: reworkCap.evidence,
4253
+ });
4254
+ }
4183
4255
  recordFailureEvent({
4184
- source: 'harness-run', kind: terminalStage, ref: runId,
4185
- summary: opts.feature.split(/\r?\n/, 1)[0]?.trim() || `Harness run ${runId} failed`,
4256
+ source: 'harness-run', kind: reworkCapStop ? 'rework-cap-exhausted' : terminalStage, ref: runId,
4257
+ summary: reworkCap
4258
+ ? reworkCapFailureSummary(opts.feature, reworkCap)
4259
+ : opts.feature.split(/\r?\n/, 1)[0]?.trim() || `Harness run ${runId} failed`,
4186
4260
  at: new Date().toISOString(),
4187
4261
  });
4188
4262
  }
@@ -4544,6 +4618,14 @@ function observeRunRollup(
4544
4618
  }, effectiveOutcome.runStatus === 'failed' ? { level: 'warn' } : undefined);
4545
4619
  }
4546
4620
 
4621
+ let reworkCapProceed = false;
4622
+
4623
+ function reworkCapFailureSummary(feature: string, decision: ReworkCapHealDecision): string {
4624
+ const title = feature.split(/\\r?\\n/, 1)[0]?.trim() || 'rework cap exhausted';
4625
+ const evidence = decision.evidence.length ? decision.evidence.join(' | ') : 'no gate introduced list and no review finding';
4626
+ return `${title} [${decision.defectClass} → ${decision.nextAction}] ${evidence}`;
4627
+ }
4628
+
4547
4629
  async function runSelfImplementInner(
4548
4630
  opts: SelfImplementOptions,
4549
4631
  identity?: { runId: string; source: ReturnType<typeof resolveRunIdentity>['source'] },
@@ -5030,6 +5112,8 @@ async function runSelfImplementInner(
5030
5112
  } | undefined;
5031
5113
  let gate!: SelfImplementGateResult;
5032
5114
  let review: SelfImplementReview | undefined;
5115
+ /** Review-budget leftovers and the body that carried them, so a later merge drafts from the same text. */
5116
+ let preparedReviewBudgetPrBody: string | undefined;
5033
5117
  const reviewMustFixCountHistory: number[] = [];
5034
5118
  const reviewMustFixTrend = (): string => {
5035
5119
  if (reviewMustFixCountHistory.length < 2) return '아직 못 잰다';
@@ -5283,6 +5367,7 @@ async function runSelfImplementInner(
5283
5367
  ? { resolvedDelivery: installDecision.delivery }
5284
5368
  : {}),
5285
5369
  ...(s.escalateGoalClarifications ? { dispatch: s.escalateGoalClarifications } : {}),
5370
+ ...(!stdinIsInteractive && !clarificationResolverLease.installed ? { selfAnswerPath: 'ask' as const, selfResolveClarification: s.selfResolveClarification } : {}),
5286
5371
  fallback: (message) => process.stdout.write(`${message}\n`),
5287
5372
  });
5288
5373
  } finally {
@@ -5365,6 +5450,23 @@ async function runSelfImplementInner(
5365
5450
  observe('pr.comment.seam-missing', { number: pr.number, buffered: prCommentBuffer.length }, { level: 'warn' });
5366
5451
  return;
5367
5452
  }
5453
+ const marker = '<!-- elanous:run-status -->';
5454
+ if (s.findPrComment && s.editPrComment) {
5455
+ try {
5456
+ const existing = await s.findPrComment({ number: pr.number, marker, cwd: wt.path });
5457
+ const history = prCommentBuffer.join('\n\n');
5458
+ const body = existing
5459
+ ? existing.body.includes('</details>')
5460
+ ? existing.body.replace(/<\/details>(?![\s\S]*<\/details>)/, `\n\n${history}\n</details>`)
5461
+ : `${existing.body}\n\n<details>\n<summary>Round history</summary>\n\n${history}\n</details>`
5462
+ : `${marker}\n<details>\n<summary>Round history</summary>\n\n${history}\n</details>`;
5463
+ if (existing) await s.editPrComment({ id: existing.id, body, cwd: wt.path });
5464
+ else await s.postPrComment({ number: pr.number, body, cwd: wt.path });
5465
+ } catch (error) {
5466
+ // Unknown lookup/edit outcome is not proof of absence: never create a second status comment.
5467
+ observe('pr.comment.status-failed', { number: pr.number, error: error instanceof Error ? error.message : String(error) }, { level: 'warn' });
5468
+ }
5469
+ }
5368
5470
  for (const body of prCommentBuffer) {
5369
5471
  try {
5370
5472
  await s.postPrComment({ number: pr.number, body, cwd: wt.path });
@@ -5798,6 +5900,7 @@ async function runSelfImplementInner(
5798
5900
  'self-implement-review-budget-pr',
5799
5901
  followUpMustFix,
5800
5902
  );
5903
+ preparedReviewBudgetPrBody = preparedPrBody.body;
5801
5904
  const pr = await withStepTimeout(s.openPr({
5802
5905
  title: prTitle(opts.feature),
5803
5906
  body: preparedPrBody.body,
@@ -6076,6 +6179,20 @@ async function runSelfImplementInner(
6076
6179
  if (relaxation) pendingPlanRevision = { ...pendingPlanRevision!, relaxation };
6077
6180
  if (opts.goalFile) {
6078
6181
  try {
6182
+ if (!(s.stdinIsInteractive ?? (() => Boolean(process.stdin.isTTY && process.stdout.isTTY)))()) {
6183
+ try {
6184
+ const source = readFileSync(opts.goalFile, 'utf8');
6185
+ // Always run the shared self-answer so every rework pass leaves one observation, even with nothing pending.
6186
+ const resolved = await selfAnswerGoalDocumentClarifications(source, {
6187
+ path: 'rework', ask: source, resolver: s.selfResolveClarification,
6188
+ });
6189
+ if (resolved.newlyAnswered > 0) writeGoalDocumentAtomic(opts.goalFile, resolved.document);
6190
+ } catch (error) {
6191
+ debug.log('self-implement.clarification-escalation', 'self-answer-failed', {
6192
+ path: 'rework', reason: error instanceof Error ? error.message : String(error),
6193
+ });
6194
+ }
6195
+ }
6079
6196
  const recorded: SupervisorPlanRevisionResult = recordSupervisorPlanRevision(opts.goalFile, { reason: parsedDecision.reason, round, ...(relaxation ? { relaxation } : {}) });
6080
6197
  pendingPlanRevision = { ...pendingPlanRevision!, ...(relaxation ? { relaxation } : {}), application: {
6081
6198
  status: recorded.status === 'applied' || recorded.status === 'already-applied' ? recorded.status : 'failed',
@@ -6211,6 +6328,7 @@ async function runSelfImplementInner(
6211
6328
  if (budgetDecision.stop) {
6212
6329
  if (budgetDecision.exit === 'proceed') {
6213
6330
  finalizeSupervisorDeliveries('supervisor-rework-proceed');
6331
+ if (round > effectiveMax) reworkCapProceed = true;
6214
6332
  break;
6215
6333
  }
6216
6334
  finalizeSupervisorDeliveries('supervisor-rework-blocked');
@@ -6271,12 +6389,25 @@ async function runSelfImplementInner(
6271
6389
  if (isReviewRework(reworkKind)) {
6272
6390
  if (reflectRejectedAudit.length && review) review.shouldFix = [...review.shouldFix, ...reflectRejectedAudit];
6273
6391
  const reason = `review must-fix unresolved after ${round - 1} rework round(s)`;
6392
+ // Pod keeps review-blocked work as a labelled draft (or aborts with the preserved branch) instead of
6393
+ // accepting the review budget into a non-draft PR — restored after #24341 (0.2.16 gate shard 5).
6274
6394
  if (!podReviewBlockedDraft) {
6275
6395
  const acceptance = await acceptReviewBudget(
6276
6396
  review?.mustFix ?? [],
6277
6397
  reworkParts.some((part) => part.source === 'supervisor'),
6278
6398
  );
6279
6399
  if (acceptance.decision.action === 'accept') {
6400
+ if (acceptance.pr) {
6401
+ await draftFollowUpAfterMerge({
6402
+ prNumber: acceptance.pr.number,
6403
+ followUpMustFix: acceptance.decision.unresolvedMustFix,
6404
+ shouldFix: review?.shouldFix,
6405
+ prBody: preparedReviewBudgetPrBody,
6406
+ feature: opts.feature,
6407
+ ...(opts.goalId ? { cellId: opts.goalId } : {}),
6408
+ ...(opts.goalFile ? { goalFile: opts.goalFile } : {}),
6409
+ }, s.followUp);
6410
+ }
6280
6411
  return {
6281
6412
  ok: true,
6282
6413
  stage: 'pr-opened',
@@ -7523,11 +7654,15 @@ async function runSelfImplementInner(
7523
7654
  let releasePathInspectionError: string | undefined;
7524
7655
  if (s.readPrFiles) {
7525
7656
  try {
7526
- const files = await withStepTimeout(s.readPrFiles({ number: pr.number, cwd: wt.path }), T.pr, 'pr');
7657
+ // Rate-limit recovery may wait up to 15 minutes for X-RateLimit-Reset; the PR step cap must not cut that wait.
7658
+ const files = await s.readPrFiles({ number: pr.number, cwd: wt.path, ...(opts.base ? { base: opts.base } : {}) });
7527
7659
  if (!Array.isArray(files) || !files.every((path) => typeof path === 'string')) throw new Error('invalid PR file list');
7528
7660
  releaseHoldPath = releasePathHold(files);
7529
7661
  } catch (error) {
7530
- releasePathInspectionError = safeErrorDescription(error);
7662
+ const rateLimited = error instanceof Error && (error as { rateLimited?: boolean }).rateLimited === true;
7663
+ releasePathInspectionError = rateLimited
7664
+ ? `rate-limit: ${safeErrorDescription(error)}`
7665
+ : safeErrorDescription(error);
7531
7666
  }
7532
7667
  } else if (canAuto || labels?.includes(AUTO_REVIEW_LABEL)) {
7533
7668
  releasePathInspectionError = 'readPrFiles seam unavailable';
@@ -7566,7 +7701,9 @@ async function runSelfImplementInner(
7566
7701
  if (releasePathInspectionError && (canAuto || labels?.includes(AUTO_REVIEW_LABEL))) {
7567
7702
  observe('release-path-inspection-failed', { number: pr.number, error: releasePathInspectionError }, { level: 'error' });
7568
7703
  canAuto = false;
7569
- mergeSkipReason = mergeReason = 'release-path-inspection-failed';
7704
+ mergeSkipReason = mergeReason = releasePathInspectionError.startsWith('rate-limit:')
7705
+ ? 'release-path-inspection-failed: rate-limit'
7706
+ : 'release-path-inspection-failed';
7570
7707
  recordMergeDecision();
7571
7708
  progress('pr-opened', `OP 승인 대기 (#${pr.number}): PR 경로 조회 실패`);
7572
7709
  return { ok: true, stage: 'pr-opened', node: 'open-pr', ...resolveRunOutcome({ termination: 'completed' }), ...decisionSignalResult, ...preservedCompletionResult, ...(completionStatus ? { completionStatus } : {}), sessionId, worktreePath: wt.path, branch: wt.branch, gate, ...(review ? { review } : {}), mergeReason, prUrl: pr.url, prNumber: pr.number };
@@ -7700,6 +7837,18 @@ async function runSelfImplementInner(
7700
7837
  prNumber: pr.number,
7701
7838
  openedAt: openedAtFromLedger(mergedLedger, new Date().toISOString()),
7702
7839
  }, observe);
7840
+ // FOLLOWUP-LOOP① — leftover findings become one follow-up draft. Failure is a log line only;
7841
+ // this return (stage, merged, exit via ok) stays the merge result.
7842
+ await draftFollowUpAfterMerge({
7843
+ prNumber: pr.number,
7844
+ // 정상 병합은 리뷰를 통과한 뒤라 미해결 must-fix 가 없다 — 남은 것은 should-fix ⊕ PR 본문 «남은 조각».
7845
+ // 미해결 must-fix 는 리뷰 예산 수락 경로가 직접 넘긴다(위 acceptance 분기).
7846
+ shouldFix: review?.shouldFix,
7847
+ prBody: [preparedReviewBudgetPrBody, preparedPrBody.body].filter(Boolean).join('\n'),
7848
+ feature: opts.feature,
7849
+ ...(opts.goalId ? { cellId: opts.goalId } : {}),
7850
+ ...(opts.goalFile ? { goalFile: opts.goalFile } : {}),
7851
+ }, s.followUp);
7703
7852
  return { ok: true, stage: 'merged', node: 'merge', ...resolveRunOutcome({ termination: 'completed' }), ...decisionSignalResult, ...preservedCompletionResult, ...(completionStatus ? { completionStatus } : {}), sessionId, worktreePath: wt.path, branch: wt.branch, gate, ...(review ? { review } : {}), merged: true, ...(mergedBase ? { mergedBase } : {}), prUrl: pr.url, prNumber: pr.number };
7704
7853
  }
7705
7854
  progress('pr-opened', `병합 실패 — PR 개설됨(#${pr.number}·수동 병합). ${m.detail ?? ''}`);
@@ -94,6 +94,10 @@ export interface RunOriginData extends Record<string, unknown> {
94
94
  substrate: string;
95
95
  instance: string;
96
96
  elanousVersion: string;
97
+ /** install.json version. 못 읽으면 «unknown». */
98
+ installedVersion: string;
99
+ /** install.json commit. 못 읽으면 «unknown». */
100
+ installedCommit: string;
97
101
  podName?: string;
98
102
  nodeName?: string;
99
103
  podNamespace?: string;
@@ -24,6 +24,9 @@ import { isAbsolute, join, relative, resolve, sep } from 'node:path';
24
24
  import { promisify } from 'node:util';
25
25
  import { forkSessionById, HARNESS_SESSION_ORIGIN, isHarnessSessionOrigin } from '../session/index.js';
26
26
  import { configuredWorktreeRoot, getUserConfig, resolveRoleLlm } from '../user-config.js';
27
+ import { elanousStateRoot } from '../autopilot/state-paths.js';
28
+ import { addHarnessQueue } from '../harness/harness-queue.js';
29
+ import { readFollowUpMode } from './follow-up-goals.js';
27
30
  import { getProviderForConfig } from '../llm.js';
28
31
  import { createWorktree, DEFAULT_BRANCH_WORKTREE_BASE, linkWorktreeDependencies, removeWorktree, resolveDefaultBranchBase, resolveMainRepoRoot } from '../git-fs/worktree.js';
29
32
  import { isWorktreeInUse, listActiveTerminalDirectories } from '../harness/harness-clean.js';
@@ -77,7 +80,8 @@ import { tscEnv, assessTypecheckExecution, classifyTypecheckErrors, diffTypechec
77
80
  import { nestCapReached, childNestEnv, nestInfo } from '../agent/nest-depth.js';
78
81
  import { harnessBoundaryEnv, harnessBoundaryRequestsEnv, harnessBoundaryResponsesEnv, harnessSpaceEnv, getHarnessSpace, normalizeSpaceId, getHarnessRunId, resolveRunIdentity } from '../harness/harness-space.js';
79
82
  import { debug } from '../debug/log.js';
80
- import { RELEASE_PATH_HOLD_MARKER, RELEASE_PATH_LABEL, releasePathHoldShouldPost, releasePathHoldCommentsArgs, releasePrFilePaths } from '../self-dev/release-path-guard.js';
83
+ import { RELEASE_PATH_HOLD_MARKER, RELEASE_PATH_LABEL, releasePathHoldShouldPost, releasePathHoldCommentsArgs } from '../self-dev/release-path-guard.js';
84
+ import { readPrFilesWithRateLimitFallback, runGhApp } from './app-api-budget.js';
81
85
  import { emitDecision } from '../live/detail-switch.js';
82
86
  import { gateDecision, reviewDecision, mergeDecision } from './decision-events.js';
83
87
  import { LogStore } from '../mss/logging/log-store.js';
@@ -110,6 +114,10 @@ declare module './orchestrator.js' {
110
114
  /** PR close(+브랜치 삭제). 기본 구성은 PrManager.closePr 위임(같은 인자·같은 반환).
111
115
  * 오케스트레이터는 아직 부르지 않는다 — 그 배선은 조각 ②다. */
112
116
  closePr?: (prUrl: string, comment?: string) => boolean;
117
+ /** Find an issue comment on this PR by its body marker; absence is distinct from a failed lookup. */
118
+ findPrComment?: (opts: { number: number; marker: string; cwd: string; comments?: unknown }) => Promise<{ id: number; body: string } | undefined>;
119
+ /** Replace an existing PR issue comment by ID, using the same GitHub App identity as postPrComment. */
120
+ editPrComment?: (opts: { id: number; body: string; cwd: string }) => Promise<void>;
113
121
  }
114
122
  }
115
123
  import { harnessPolicyEnv } from './harness-policy.js';
@@ -1415,6 +1423,11 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
1415
1423
  const { supersedeMergedGoalDrafts } = await import('../harness/harness-cli-command.js');
1416
1424
  await supersedeMergedGoalDrafts(number, cwd);
1417
1425
  },
1426
+ followUp: {
1427
+ mode: readFollowUpMode(),
1428
+ stateRoot: elanousStateRoot(),
1429
+ enqueue: (item) => addHarnessQueue({ seat: item.seat, say: item.say, idempotencyKey: item.idempotencyKey }),
1430
+ },
1418
1431
  lineageSupersede: {
1419
1432
  listOpenDrafts: () => listOpenDraftsForLineage(),
1420
1433
  readRunLedger: (runId) => {
@@ -2394,46 +2407,62 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
2394
2407
  postPrComment: async ({ number, body, cwd }) => {
2395
2408
  // Round comments go out as the GitHub App like the PR itself — a personal `gh` login made the owner a
2396
2409
  // participant and mailed them on merge (#22089).
2397
- if (body.startsWith(RELEASE_PATH_HOLD_MARKER)) {
2410
+ if (body.startsWith(RELEASE_PATH_HOLD_MARKER)) {
2398
2411
  // Several merge surfaces may hold the same PR; post the release-path hold comment once.
2399
2412
  let history: unknown;
2400
2413
  try {
2401
- history = JSON.parse((await execFileAsync('gh', releasePathHoldCommentsArgs(number), {
2414
+ history = JSON.parse((await runGhApp('post-pr-comment-history', releasePathHoldCommentsArgs(number), {
2402
2415
  cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, maxBuffer: 64 * 1024 * 1024, env: ghAutomationEnv(process.env),
2403
2416
  })).stdout);
2404
2417
  } catch { history = undefined; }
2405
2418
  if (!releasePathHoldShouldPost(() => history)) return;
2406
2419
  }
2407
- await execFileAsync('gh', ['pr', 'comment', String(number), '--body', body], {
2420
+ await runGhApp('post-pr-comment', ['pr', 'comment', String(number), '--body', body], {
2421
+ cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, maxBuffer: 64 * 1024 * 1024, env: ghAutomationEnv(process.env),
2422
+ });
2423
+ },
2424
+
2425
+ findPrComment: async ({ number, marker, cwd, comments }) => {
2426
+ const pages: unknown = comments ?? JSON.parse((await runGhApp('find-pr-comment', releasePathHoldCommentsArgs(number), {
2427
+ cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, maxBuffer: 64 * 1024 * 1024, env: ghAutomationEnv(process.env),
2428
+ })).stdout);
2429
+ if (!Array.isArray(pages)) throw new Error('gh api PR comments returned an invalid list');
2430
+ const entries = pages.flatMap((page: unknown) => Array.isArray(page) ? page : [page]);
2431
+ for (const entry of entries) {
2432
+ if (!entry || typeof entry !== 'object' || !('body' in entry) || typeof entry.body !== 'string'
2433
+ || !('id' in entry) || typeof entry.id !== 'number' || !Number.isSafeInteger(entry.id) || entry.id <= 0) {
2434
+ throw new Error('gh api PR comments returned an invalid comment');
2435
+ }
2436
+ }
2437
+ return entries.find((entry: { id: number; body: string }) => entry.body.includes(marker));
2438
+ },
2439
+
2440
+ editPrComment: async ({ id, body, cwd }) => {
2441
+ await runGhApp('edit-pr-comment', ['api', '--method', 'PATCH', `repos/{owner}/{repo}/issues/comments/${id}`, '-f', `body=${body}`], {
2408
2442
  cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, maxBuffer: 64 * 1024 * 1024, env: ghAutomationEnv(process.env),
2409
2443
  });
2410
2444
  },
2411
2445
 
2412
2446
  addPrLabel: async ({ number, label, cwd }) => {
2413
2447
  if (label === RELEASE_PATH_LABEL) {
2414
- const existing = await execFileAsync('gh', ['label', 'list', '--search', label, '--json', 'name'], {
2448
+ const existing = await runGhApp('add-pr-label-list', ['label', 'list', '--search', label, '--json', 'name'], {
2415
2449
  cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, env: ghAutomationEnv(process.env),
2416
2450
  });
2417
2451
  const names: unknown = JSON.parse(existing.stdout);
2418
2452
  if (!Array.isArray(names)) throw new Error('gh label list returned an invalid list');
2419
2453
  if (!names.some((item: unknown) => !!item && typeof item === 'object' && 'name' in item && item.name === label)) {
2420
- await execFileAsync('gh', ['label', 'create', label, '--color', 'D93F0B', '--description', 'Release path requires OP approval'], {
2454
+ await runGhApp('add-pr-label-create', ['label', 'create', label, '--color', 'D93F0B', '--description', 'Release path requires OP approval'], {
2421
2455
  cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, env: ghAutomationEnv(process.env),
2422
2456
  });
2423
2457
  }
2424
2458
  }
2425
- await execFileAsync('gh', ['pr', 'edit', String(number), '--add-label', label], {
2459
+ await runGhApp('add-pr-label', ['pr', 'edit', String(number), '--add-label', label], {
2426
2460
  cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, env: ghAutomationEnv(process.env),
2427
2461
  });
2428
2462
  },
2429
- readPrFiles: async ({ number, cwd }) => {
2430
- // Paginate the PR file API: a single `gh pr view --json files` can omit files after the first page.
2431
- const { stdout } = await execFileAsync('gh', [
2432
- 'api', '--paginate', '--slurp', '--method', 'GET', '-f', 'per_page=100',
2433
- `repos/{owner}/{repo}/pulls/${number}/files`,
2434
- ], { cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, maxBuffer: 64 * 1024 * 1024, env: ghAutomationEnv(process.env) });
2435
- return releasePrFilePaths(JSON.parse(stdout));
2436
- },
2463
+ readPrFiles: async ({ number, cwd, base }) => readPrFilesWithRateLimitFallback({
2464
+ number, cwd, env: ghAutomationEnv(process.env), ...(base ? { base } : {}),
2465
+ }),
2437
2466
 
2438
2467
  ...(o.approvePr ? { approvePr: o.approvePr } : {}),
2439
2468
 
@@ -66,6 +66,8 @@ export {
66
66
  export {
67
67
  fetchApiWithRetry,
68
68
  ApiHttpError,
69
+ OverloadFailoverError,
70
+ recordLlmCallOutcome,
69
71
  type FetchApiWithRetryOpts,
70
72
  } from './retry-api.js';
71
73
 
@@ -25,6 +25,8 @@ import {
25
25
  decideRetry,
26
26
  DoomLoopTracker,
27
27
  fingerprintError,
28
+ llmCallOutcomeKind,
29
+ type LlmCallOutcomeKind,
28
30
  type RetryAction,
29
31
  } from './retry-policy.js';
30
32
 
@@ -75,6 +77,35 @@ export interface FetchApiWithRetryOpts {
75
77
  * row" pattern within a single request. Pass a session-scoped
76
78
  * tracker if you want cross-request doom detection. */
77
79
  doomTracker?: DoomLoopTracker;
80
+ /** Fallbacks still unused. When > 0, k consecutive overload-class
81
+ * failures throw OverloadFailoverError before another retry sleep. */
82
+ remainingFallbacks?: number;
83
+ }
84
+
85
+ /** Raised when overload repeats k times and a fallback is still unused.
86
+ * The caller switches provider; this is not a retry exhaustion. */
87
+ export class OverloadFailoverError extends Error {
88
+ readonly provider: string;
89
+ readonly status: number;
90
+ readonly kind: LlmCallOutcomeKind;
91
+ readonly streak: number;
92
+ readonly cause: unknown;
93
+
94
+ constructor(opts: {
95
+ provider: string;
96
+ status: number;
97
+ kind: LlmCallOutcomeKind;
98
+ streak: number;
99
+ cause: unknown;
100
+ }) {
101
+ super(`overload failover after ${opts.streak} ${opts.kind} from ${opts.provider} (status ${opts.status})`);
102
+ this.name = 'OverloadFailoverError';
103
+ this.provider = opts.provider;
104
+ this.status = opts.status;
105
+ this.kind = opts.kind;
106
+ this.streak = opts.streak;
107
+ this.cause = opts.cause;
108
+ }
78
109
  }
79
110
 
80
111
  /** POST a fetch with the standard retry policy. On 2xx returns the
@@ -91,13 +122,17 @@ export async function fetchApiWithRetry(
91
122
  ): Promise<Response> {
92
123
  const maxAttempts = opts.maxAttempts ?? 4;
93
124
  const tracker = opts.doomTracker ?? new DoomLoopTracker();
125
+ const remainingFallbacks = opts.remainingFallbacks ?? 0;
94
126
  let attempt = 0;
127
+ let overloadStreak = 0;
95
128
  let lastErr: unknown;
96
129
  while (attempt < maxAttempts) {
97
130
  let response: Response | null = null;
98
131
  try {
99
132
  response = await fetch(url, init);
100
133
  if (response.ok) {
134
+ recordLlmCallOutcome(opts.provider, response.status, 'ok');
135
+ overloadStreak = 0;
101
136
  return response;
102
137
  }
103
138
  // Drain body so the error can carry the API's explanation. We
@@ -136,6 +171,11 @@ export async function fetchApiWithRetry(
136
171
  // would couple the helper to the caller's signal.
137
172
  if (isAbortLike(err)) throw err;
138
173
 
174
+ const status = err instanceof ApiHttpError ? err.status : 0;
175
+ const kind = llmCallOutcomeKind(status, err instanceof Error ? err.message : String(err));
176
+ recordLlmCallOutcome(opts.provider, status, kind);
177
+ if (kind === 'overloaded' || kind === '5xx' || kind === 'rate-limit') overloadStreak += 1;
178
+ else overloadStreak = 0;
139
179
  const fp = fingerprintError(err, `api:${opts.provider}`);
140
180
  const doomStatus = tracker.record(fp);
141
181
  const retryAfter = err instanceof ApiHttpError ? err.retryAfter : undefined;
@@ -143,6 +183,8 @@ export async function fetchApiWithRetry(
143
183
  attempt,
144
184
  doomStatus,
145
185
  retryAfter,
186
+ remainingFallbacks,
187
+ overloadStreak,
146
188
  });
147
189
  debug.log('llm.retry', decisionEvent(decision.action), {
148
190
  provider: opts.provider,
@@ -151,7 +193,19 @@ export async function fetchApiWithRetry(
151
193
  reason: decision.reason,
152
194
  delayMs: decision.delayMs,
153
195
  status: err instanceof ApiHttpError ? err.status : undefined,
196
+ kind,
197
+ overloadStreak,
198
+ remainingFallbacks,
154
199
  });
200
+ if (decision.action === 'switch-provider') {
201
+ throw new OverloadFailoverError({
202
+ provider: opts.provider,
203
+ status,
204
+ kind,
205
+ streak: overloadStreak,
206
+ cause: err,
207
+ });
208
+ }
155
209
  if (decision.action !== 'retry') {
156
210
  throw err;
157
211
  }
@@ -172,6 +226,39 @@ export async function fetchApiWithRetry(
172
226
  throw lastErr ?? new Error(`${opts.errorPrefix}: retry loop terminated unexpectedly`);
173
227
  }
174
228
 
229
+ type OutcomeWriter = (row: { provider: string; status: number; kind: LlmCallOutcomeKind }) => void;
230
+
231
+ let outcomeWriterForTesting: OutcomeWriter | undefined;
232
+
233
+ /** Test seam. Production uses the log store. `undefined` restores it. */
234
+ export function setLlmCallOutcomeWriterForTesting(writer: OutcomeWriter | undefined): void {
235
+ outcomeWriterForTesting = writer;
236
+ }
237
+
238
+ /** The one event launch failover is allowed to read.
239
+ * Category `llm.call`, event `outcome`, payload {provider,status,kind}.
240
+ * Written straight to the log store — `debug.log` drops the row when no
241
+ * sink is armed, which is the normal launch path. */
242
+ export function recordLlmCallOutcome(provider: string, status: number, kind: LlmCallOutcomeKind): void {
243
+ const data = { provider, status, kind };
244
+ // Trace only — a store sink would otherwise write a second `llm.call`/`outcome` row and halve the streak threshold.
245
+ debug.log('llm.call', 'outcome-trace', data);
246
+ if (outcomeWriterForTesting) {
247
+ outcomeWriterForTesting(data);
248
+ return;
249
+ }
250
+ if (process.env.NODE_ENV === 'test') return;
251
+ try {
252
+ const { getDefaultLogStore } = require('../mss/logging/log-store.js') as typeof import('../mss/logging/log-store.js');
253
+ getDefaultLogStore()?.insertBatch([{
254
+ rec: { ts: new Date().toISOString(), category: 'llm.call', event: 'outcome', data },
255
+ surface: 'llm',
256
+ }]);
257
+ } catch {
258
+ // A failed observation must not fail the call. Launch then sees no sample.
259
+ }
260
+ }
261
+
175
262
  function decisionEvent(action: RetryAction): string {
176
263
  // Stable category names for log filtering. The action is the most
177
264
  // useful axis ("how many turns hit retry vs abort"); the category