elanous 0.2.25 → 0.2.28

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 (337) 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/{1454-37fd16f479f5554a.js → 1454-29d1d773d5996a49.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{599-a914cc0468f56143.js → 599-56f2c0a5cc4a7295.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/app/approvals/page-5ab1e47978577c5e.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-24453f6a302816b4.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/app/board/page-6c131d30bb1cf871.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-b12149c51f7d04b1.js → page-fa46d4588e8442c2.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/app/bots/page-dc0772b11861af51.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/ceo/page-66ad46ed0279feb9.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/app/chat/page-5159dc8752c6cd29.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/app/consult/page-c5a2478861c08b87.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/app/control/page-f722a9dec084e08b.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/app/decisions/{page-305cd294e754906e.js → page-256ffd365d32e100.js} +1 -1
  15. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-dea4b795741126fd.js → page-e0de78ae7b99f01c.js} +1 -1
  16. package/apps/pwa/out/_next/static/chunks/app/editor/page-cfbdc44b708d9801.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/exec/{page-45889e7070d11126.js → page-4c741eb760f2f77c.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/field/{page-16a2e893f83ae4d0.js → page-e9ba98f5f1440a21.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/inside/page-dafc901bb1b72b5f.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/app/intake/{page-56888bc744e08dac.js → page-2f6f303fa114ec24.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/layout-1634081c5272006c.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/app/live/{page-952c6ceec6d9de38.js → page-ced0bcfc6542b3fc.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/live-run/{page-6d0202af568f4c63.js → page-bb1f05cd51c38944.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/loops/page-646b50f63b6fe732.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/market/page-db069b48a59c8c00.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-4fb6fc34e20c07b7.js → page-f736324afa2117c1.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/morning/{page-e9502921f4f01b1f.js → page-6f131b51193a36f9.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/{not-found-b6169ea2f09cf287.js → not-found-435f5e2faf02bc01.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/app/observatory/{page-6a14333d1b1b3967.js → page-497e0eedf20e7890.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-8c91a9a66c9ec90a.js → page-a8d28fdf995e16ea.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8966452228753c5e.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fd5d18656cb432b6.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/outputs/page-5663779cb1a37609.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/{page-3d3fc73dd187ed52.js → page-66a67eb19ebd976b.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/reflection/page-3b492aa78cf3c4a6.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-9c04dac0831c2bbb.js → page-c71267e7681da982.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/sessions/page-a21d1e1797172692.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-04de9ca6d52a3893.js → page-cb249e81ed339318.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/app/settings/{page-62527677bbc2fdc4.js → page-781cfc5114e097da.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-9a2e09c4a311764c.js → page-478157214db6a023.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/setup/{page-bfeb35d69e8b24bb.js → page-86141f12cf090b7f.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/app/share/{page-89cb33bced824e4a.js → page-7256c73bb1c36751.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/showroom/page-5c738ca7adef3de5.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/tasks/page-0138c8091ec17224.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/term/page-cc2f24f16d85e735.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/today/{page-6445ac4cbb8dd869.js → page-979d318a534e92da.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/app/trace/page-182d394f7e6e096b.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/vault/page-21050928a697ed7a.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-44b9d807005a9030.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-20162174b77f96cf.js → page-601fb77340e63c9a.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/workspace/page-75d3fb5d47af0edf.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-05a4b2c9b91034aa.js → page-11bf9e5725a72c22.js} +1 -1
  53. package/apps/pwa/out/approvals/index.html +1 -1
  54. package/apps/pwa/out/approvals/index.txt +13 -13
  55. package/apps/pwa/out/autopilot/index.html +1 -1
  56. package/apps/pwa/out/autopilot/index.txt +13 -13
  57. package/apps/pwa/out/board/index.html +1 -1
  58. package/apps/pwa/out/board/index.txt +13 -13
  59. package/apps/pwa/out/botlab/index.html +1 -1
  60. package/apps/pwa/out/botlab/index.txt +13 -13
  61. package/apps/pwa/out/bots/index.html +1 -1
  62. package/apps/pwa/out/bots/index.txt +13 -13
  63. package/apps/pwa/out/ceo/index.html +1 -1
  64. package/apps/pwa/out/ceo/index.txt +13 -13
  65. package/apps/pwa/out/chat/index.html +1 -1
  66. package/apps/pwa/out/chat/index.txt +13 -13
  67. package/apps/pwa/out/consult/index.html +1 -1
  68. package/apps/pwa/out/consult/index.txt +13 -13
  69. package/apps/pwa/out/control/index.html +1 -1
  70. package/apps/pwa/out/control/index.txt +13 -13
  71. package/apps/pwa/out/decisions/index.html +1 -1
  72. package/apps/pwa/out/decisions/index.txt +13 -13
  73. package/apps/pwa/out/design-check/index.html +1 -1
  74. package/apps/pwa/out/design-check/index.txt +13 -13
  75. package/apps/pwa/out/editor/index.html +1 -1
  76. package/apps/pwa/out/editor/index.txt +13 -13
  77. package/apps/pwa/out/exec/index.html +1 -1
  78. package/apps/pwa/out/exec/index.txt +13 -13
  79. package/apps/pwa/out/field/index.html +1 -1
  80. package/apps/pwa/out/field/index.txt +13 -13
  81. package/apps/pwa/out/healthz +1 -1
  82. package/apps/pwa/out/index.html +1 -1
  83. package/apps/pwa/out/index.txt +13 -13
  84. package/apps/pwa/out/inside/index.html +1 -1
  85. package/apps/pwa/out/inside/index.txt +13 -13
  86. package/apps/pwa/out/intake/index.html +1 -1
  87. package/apps/pwa/out/intake/index.txt +13 -13
  88. package/apps/pwa/out/live/index.html +1 -1
  89. package/apps/pwa/out/live/index.txt +13 -13
  90. package/apps/pwa/out/live-run/index.html +1 -1
  91. package/apps/pwa/out/live-run/index.txt +13 -13
  92. package/apps/pwa/out/loops/index.html +1 -1
  93. package/apps/pwa/out/loops/index.txt +13 -13
  94. package/apps/pwa/out/market/index.html +1 -1
  95. package/apps/pwa/out/market/index.txt +13 -13
  96. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  97. package/apps/pwa/out/missions/placeholder/index.txt +13 -13
  98. package/apps/pwa/out/morning/index.html +1 -1
  99. package/apps/pwa/out/morning/index.txt +13 -13
  100. package/apps/pwa/out/observatory/index.html +1 -1
  101. package/apps/pwa/out/observatory/index.txt +13 -13
  102. package/apps/pwa/out/ops/checklist/index.html +1 -1
  103. package/apps/pwa/out/ops/checklist/index.txt +13 -13
  104. package/apps/pwa/out/ops/release/index.html +1 -1
  105. package/apps/pwa/out/ops/release/index.txt +13 -13
  106. package/apps/pwa/out/ops/seats/index.html +1 -1
  107. package/apps/pwa/out/ops/seats/index.txt +13 -13
  108. package/apps/pwa/out/outputs/index.html +1 -1
  109. package/apps/pwa/out/outputs/index.txt +13 -13
  110. package/apps/pwa/out/reflection/index.html +1 -1
  111. package/apps/pwa/out/reflection/index.txt +14 -14
  112. package/apps/pwa/out/scheduler/index.html +1 -1
  113. package/apps/pwa/out/scheduler/index.txt +13 -13
  114. package/apps/pwa/out/sessions/index.html +1 -1
  115. package/apps/pwa/out/sessions/index.txt +13 -13
  116. package/apps/pwa/out/settings/devices/index.html +1 -1
  117. package/apps/pwa/out/settings/devices/index.txt +13 -13
  118. package/apps/pwa/out/settings/index.html +1 -1
  119. package/apps/pwa/out/settings/index.txt +13 -13
  120. package/apps/pwa/out/setup/done/index.html +1 -1
  121. package/apps/pwa/out/setup/done/index.txt +13 -13
  122. package/apps/pwa/out/setup/index.html +1 -1
  123. package/apps/pwa/out/setup/index.txt +13 -13
  124. package/apps/pwa/out/share/index.html +1 -1
  125. package/apps/pwa/out/share/index.txt +13 -13
  126. package/apps/pwa/out/showroom/index.html +1 -1
  127. package/apps/pwa/out/showroom/index.txt +13 -13
  128. package/apps/pwa/out/tasks/index.html +1 -1
  129. package/apps/pwa/out/tasks/index.txt +13 -13
  130. package/apps/pwa/out/term/index.html +1 -1
  131. package/apps/pwa/out/term/index.txt +13 -13
  132. package/apps/pwa/out/today/index.html +1 -1
  133. package/apps/pwa/out/today/index.txt +13 -13
  134. package/apps/pwa/out/trace/index.html +1 -1
  135. package/apps/pwa/out/trace/index.txt +13 -13
  136. package/apps/pwa/out/vault/index.html +1 -1
  137. package/apps/pwa/out/vault/index.txt +13 -13
  138. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  139. package/apps/pwa/out/workflows/chat-ui/index.txt +13 -13
  140. package/apps/pwa/out/workflows/index.html +1 -1
  141. package/apps/pwa/out/workflows/index.txt +13 -13
  142. package/apps/pwa/out/workspace/index.html +1 -1
  143. package/apps/pwa/out/workspace/index.txt +13 -13
  144. package/apps/pwa/out/worktrees/index.html +1 -1
  145. package/apps/pwa/out/worktrees/index.txt +13 -13
  146. package/graphs/implement-loop.yaml +38 -1
  147. package/graphs/steward/recipes.yaml +6 -0
  148. package/graphs/steward/release-day.yaml +23 -0
  149. package/graphs/steward/steward.yaml +9 -1
  150. package/graphs/work/code-pr.yaml +23 -0
  151. package/package.json +1 -1
  152. package/scripts/browser-observe-cycle.ts +76 -2
  153. package/scripts/ci-typecheck-changed.ts +119 -7
  154. package/scripts/coord-msg-bridge.ts +209 -0
  155. package/scripts/coord-post.sh +40 -7
  156. package/scripts/coord-requests-bridge.ts +10 -5
  157. package/scripts/detect-test-interference.ts +31 -1
  158. package/scripts/expose-rubric-suggest.ts +1 -1
  159. package/scripts/measure-tsc-affected-roots.ts +10 -1
  160. package/scripts/release-loop/docs-node.ts +51 -22
  161. package/scripts/release-loop/export-check-node.ts +8 -5
  162. package/scripts/release-loop/gate-node.ts +37 -4
  163. package/scripts/release-loop/node-verdict.ts +10 -3
  164. package/scripts/release-loop/publish-node.ts +3 -0
  165. package/scripts/release-loop/tui-node-checks.ts +5 -2
  166. package/scripts/skills/public-check.ts +5 -2
  167. package/scripts/test-deterministic.ts +520 -37
  168. package/scripts/ux-sim.ts +7 -1
  169. package/scripts/verify-kpack6-live.ts +230 -0
  170. package/src/acp/codex-plugins.ts +17 -0
  171. package/src/agent/task-notification.ts +32 -2
  172. package/src/agent-mission/in-agent-plugin.ts +8 -3
  173. package/src/agent-substrate/review-observation.ts +9 -2
  174. package/src/agent-substrate/self-review-cli.ts +62 -16
  175. package/src/boot/daemon-tools/self-implement.ts +72 -4
  176. package/src/briefing/brief-items.ts +151 -35
  177. package/src/budget/llm-cost.ts +30 -3
  178. package/src/budget/usage-outcomes.ts +32 -0
  179. package/src/claims/claims-ledger.ts +23 -0
  180. package/src/claims/claims-render.ts +22 -2
  181. package/src/cli/brief-cli.ts +131 -10
  182. package/src/cli/claims-cli.ts +24 -4
  183. package/src/cli/control-cli.ts +29 -0
  184. package/src/cli/doctor-cli.ts +90 -8
  185. package/src/cli/msg-cli.ts +177 -11
  186. package/src/cli/plugin-cli.ts +11 -3
  187. package/src/cli/pr-cli.ts +133 -18
  188. package/src/cli/pr-land-overlap-evidence.ts +18 -10
  189. package/src/cli/pr-land-refusal.ts +20 -0
  190. package/src/cli/release-cli.ts +34 -5
  191. package/src/cli/rfc-cli.ts +24 -0
  192. package/src/cli/seat-cli.ts +121 -0
  193. package/src/cli/self-utterances-cli.ts +140 -0
  194. package/src/cli/tailscale-serve.ts +5 -8
  195. package/src/cli/tasks-cli.ts +528 -31
  196. package/src/cli/usage-cli.ts +9 -4
  197. package/src/context-card/cell.ts +40 -0
  198. package/src/dashboard/boot-timing.ts +35 -0
  199. package/src/dashboard/first-screen-band.ts +63 -13
  200. package/src/dashboard/index.ts +94 -27
  201. package/src/dashboard/input/chat-main-plain-turn-runtime.ts +26 -0
  202. package/src/dashboard/skill-detection-runtime.ts +24 -4
  203. package/src/dashboard/slash-runtime/dashboard-handlers.ts +6 -0
  204. package/src/debug/log.ts +1 -1
  205. package/src/domains/morning-report.ts +3 -1
  206. package/src/domains/release-tool.ts +29 -12
  207. package/src/domains/surface-events.ts +16 -0
  208. package/src/elanous-config-dir.ts +2 -2
  209. package/src/fleet/host-doctor.ts +73 -2
  210. package/src/github/pr-labels.ts +10 -4
  211. package/src/graph-runner/runner.ts +104 -15
  212. package/src/harness/harness-clean.ts +21 -3
  213. package/src/harness/harness-cli-command.ts +68 -10
  214. package/src/harness/harness-pod-dispatch.ts +11 -4
  215. package/src/harness/harness-stop.ts +11 -7
  216. package/src/hq/seats.ts +17 -3
  217. package/src/index.ts +69 -9
  218. package/src/instance/current.ts +2 -6
  219. package/src/instance/resolve.ts +5 -6
  220. package/src/intelligence-map/model-bench-scores.ts +102 -0
  221. package/src/llm/openai-sampling-fields.ts +89 -0
  222. package/src/llm/runtime-model-family.ts +61 -0
  223. package/src/llm/zai-coding-plan.ts +27 -1
  224. package/src/llm.ts +13 -89
  225. package/src/loops/orchestrator/inbox.ts +73 -0
  226. package/src/loops/orchestrator/resync-shadow.ts +100 -12
  227. package/src/loops/orchestrator/tick.ts +73 -2
  228. package/src/machines/machine-ledger.ts +31 -3
  229. package/src/maturity/cli-maturity.ts +13 -3
  230. package/src/maturity/feature-maturity.ts +1 -1
  231. package/src/msg/msg-sse-server.ts +141 -0
  232. package/src/msg/msg-store.ts +149 -17
  233. package/src/ops/overview-collect.ts +3 -2
  234. package/src/ops/overview-owner.ts +29 -0
  235. package/src/patents/ledger.ts +128 -0
  236. package/src/plugin-hooks/shell-hook.ts +17 -2
  237. package/src/plugin-missions/evaluator-runner.ts +18 -2
  238. package/src/plugins/install/market-fetch.ts +32 -1
  239. package/src/plugins/install/plugin-install.ts +11 -1
  240. package/src/pod-lease/admission.ts +116 -41
  241. package/src/prompt-enhance/enhance.ts +11 -0
  242. package/src/release-loop/checklist.ts +52 -4
  243. package/src/release-loop/feature-store.ts +10 -9
  244. package/src/release-loop/merged-pr-checklist.ts +24 -5
  245. package/src/release-loop/rubric.ts +70 -0
  246. package/src/rfc-registry/registry.ts +88 -0
  247. package/src/seat-dispatch/seat-request-ledger.ts +4 -0
  248. package/src/seat-loop/knowhow-lint.ts +233 -0
  249. package/src/seat-loop/seat-loop.ts +5 -1
  250. package/src/seat-loop/seat-pack.ts +16 -4
  251. package/src/seat-loop/traffic-signal.ts +7 -0
  252. package/src/self-dev/ask-launch-flow.ts +48 -8
  253. package/src/self-dev/ask-path-candidates.ts +69 -14
  254. package/src/self-dev/decompose-proposal.ts +43 -1
  255. package/src/self-dev/draft-sweep.ts +23 -14
  256. package/src/self-dev/graph-journey-nodes.ts +2 -0
  257. package/src/self-dev/launch-preflight-text.ts +5 -1
  258. package/src/self-dev/launch-preflight.ts +143 -10
  259. package/src/self-dev/orchestrate.ts +6 -2
  260. package/src/self-dev/run-supervisor.ts +35 -1
  261. package/src/self-implement/gate-cli.ts +6 -2
  262. package/src/self-implement/goal-author.ts +5 -28
  263. package/src/self-implement/goal-command-execution.ts +26 -0
  264. package/src/self-implement/goal-new-paths.ts +4 -5
  265. package/src/self-implement/graph-authority.ts +17 -3
  266. package/src/self-implement/graph-yaml.ts +15 -7
  267. package/src/self-implement/headless-elanous-driver.ts +16 -1
  268. package/src/self-implement/host-regate.ts +44 -9
  269. package/src/self-implement/orchestrator.ts +5 -11
  270. package/src/self-implement/pwa-import-graph.ts +194 -7
  271. package/src/self-implement/rework-policy.ts +80 -2
  272. package/src/self-implement/self-implement-runtime.ts +126 -21
  273. package/src/self-implement/speed-claims.ts +145 -6
  274. package/src/session/index.ts +14 -4
  275. package/src/session-runtime/index.ts +55 -11
  276. package/src/skills/tools/persistent-grounding.ts +59 -4
  277. package/src/skills/tools/shell.ts +79 -1
  278. package/src/steward/rejudge-tick.ts +12 -1
  279. package/src/steward/release-day.ts +158 -0
  280. package/src/steward/resync-tick.ts +123 -0
  281. package/src/task-agent/actions.ts +1 -0
  282. package/src/task-agent/adopt-pr.ts +140 -0
  283. package/src/task-agent/board.ts +11 -1
  284. package/src/task-agent/card-evidence.ts +37 -5
  285. package/src/task-agent/cover.ts +105 -19
  286. package/src/task-agent/failure-notify.ts +61 -0
  287. package/src/task-agent/green-sweep.ts +170 -0
  288. package/src/task-agent/land-worktree.ts +52 -5
  289. package/src/task-agent/live-move-history.ts +1 -0
  290. package/src/task-agent/live-moves.ts +56 -17
  291. package/src/task-agent/parent-host.ts +1 -1
  292. package/src/task-agent/postland-card.ts +139 -0
  293. package/src/task-agent/rejudge-on-head.ts +321 -25
  294. package/src/task-agent/remote-rejudge-bind.ts +62 -0
  295. package/src/task-agent/repair-branch-cas.ts +182 -0
  296. package/src/task-agent/repair-cycle.ts +275 -0
  297. package/src/task-agent/repair-next-move.ts +31 -0
  298. package/src/task-agent/repair-review.ts +24 -0
  299. package/src/task-agent/repair-worker.ts +87 -0
  300. package/src/task-agent/review-result-capture.ts +66 -2
  301. package/src/task-agent/shadow.ts +150 -29
  302. package/src/task-agent/task-hand.ts +133 -8
  303. package/src/task-orchestrator/surfaces/pod-repair-commit.ts +96 -0
  304. package/src/task-orchestrator/surfaces/self-implement-pod.ts +71 -12
  305. package/src/telegram.ts +30 -1
  306. package/src/tool-runtime/tool-search-runtime.ts +25 -0
  307. package/src/tui.ts +20 -1
  308. package/src/user-config.ts +11 -60
  309. package/src/ux-sim/fault-sqlite-busy.ts +50 -0
  310. package/src/version/packed-revision.json +1 -1
  311. package/apps/pwa/out/_next/static/chunks/app/approvals/page-9360b1285b2a346e.js +0 -1
  312. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f08f6529d09ffae1.js +0 -1
  313. package/apps/pwa/out/_next/static/chunks/app/board/page-f000d54d258f9ef6.js +0 -1
  314. package/apps/pwa/out/_next/static/chunks/app/bots/page-22f674e7e4c2c9d5.js +0 -1
  315. package/apps/pwa/out/_next/static/chunks/app/ceo/page-8888733e40cea6d1.js +0 -1
  316. package/apps/pwa/out/_next/static/chunks/app/chat/page-9717ec6964b92276.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/app/consult/page-44eaad486bc98a45.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/app/control/page-97fa6cf3d34c7eb9.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/app/editor/page-35e05d7450461a67.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/app/inside/page-b2638013c0bf7dfd.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/app/layout-c44f93a41167536f.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/app/loops/page-1b810474507f3702.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/app/market/page-74366ae2693a7d95.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4e634c5b98fd4e93.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ed3070d637850548.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/outputs/page-b9cc8a10bc6078b4.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/reflection/page-128458c8d86fbb5f.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/sessions/page-cf0c2b0183991d09.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/showroom/page-b008aff96be56654.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/tasks/page-814394dcbae789b1.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/term/page-6a87325c87f09dd2.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/trace/page-852170303c6cd755.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/vault/page-087ceeb975ef272b.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-97c7361cc2a19319.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/workspace/page-8c6901833f897496.js +0 -1
  336. /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → y1ltt7oCZxVpC6zVPhZ2O}/_buildManifest.js +0 -0
  337. /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → y1ltt7oCZxVpC6zVPhZ2O}/_ssgManifest.js +0 -0
@@ -4,6 +4,7 @@ import { getUserConfig } from '../user-config.js';
4
4
  import { resolve } from 'node:path';
5
5
  import type { ModelFamily } from '../models/prompts.js';
6
6
  import type { InputSourceKind, InputSourceRef } from '../input/input-source-kind.js';
7
+ import type { ControlTurnIntent, SubmitTurnIntent } from '../input/input-intent.js';
7
8
  import { getSessionCwd } from '../session/working-dir.js';
8
9
  import {
9
10
  armQuickControlOnce,
@@ -22,6 +23,7 @@ import {
22
23
  import type { LLMMessage, LLMToolSpec } from '../llm.js';
23
24
  import type { LLMToolDef } from '../plugins/core/types.js';
24
25
  import type { ToolRunResult, ToolRuntime } from '../tool-runtime/types.js';
26
+ import { blockWriteToolSearchHydration, type ToolSearchRunResult } from '../tool-runtime/tool-search-runtime.js';
25
27
  import { findNativeTool, isNativeToolModelExposed } from '../native-tool-catalog.js';
26
28
  import { hasAstGrep } from '../skills/tools/ast-grep.js';
27
29
  import { buildOmniSearchTool, omniSearchAvailable } from '../skills/tools/omni-search.js';
@@ -1731,6 +1733,8 @@ const ESSENTIAL_NATIVE_RULE_IDS = [
1731
1733
  'agent', 'agent-output', 'agent-reply', 'agent-stop', 'agent-list', 'self-implement', 'self-orchestrate', 'dev-harness',
1732
1734
  ] as const;
1733
1735
  const ESSENTIAL_HOST_FAMILY_IDS = ['self-ops', 'release-read'] as const;
1736
+ // Keep this TUI essential addition out of the shared control/agent self-ops family.
1737
+ const ESSENTIAL_HOST_EXTRA_NAMES = ['logs_query'] as const;
1734
1738
  const ESSENTIAL_RUNTIME_FAMILY_IDS = ['bash', 'run-shell', 'pty-shell', 'run-tests', 'elanous_skills_list', 'skill_exec', 'code-grounding'] as const;
1735
1739
  const ESSENTIAL_OPTIONAL_TOOL_NAMES = [
1736
1740
  'GetDashboardState',
@@ -1764,7 +1768,11 @@ function buildEssentialSessionRuntimeToolSpecs(opts: {
1764
1768
  .filter((tool): tool is LLMToolDef => !!tool)
1765
1769
  .map(toSpec)
1766
1770
  : [];
1767
- });
1771
+ }).concat(ESSENTIAL_HOST_EXTRA_NAMES
1772
+ .filter(name => isToolAvailable(name, opts.toolAvailability))
1773
+ .map(name => hostByName.get(name))
1774
+ .filter((tool): tool is LLMToolDef => !!tool)
1775
+ .map(toSpec));
1768
1776
  const runtimeById = new Map(opts.runtimeTools.map(tool => [tool.id, tool] as const));
1769
1777
  const runtimeSpecs = ESSENTIAL_RUNTIME_FAMILY_IDS.flatMap(id => {
1770
1778
  const rule = RUNTIME_TOOL_FAMILY_RULE_MAP.get(id);
@@ -1833,12 +1841,23 @@ export function buildSessionRuntimeToolSpecs(opts: {
1833
1841
  // toolset (incident 2026-05-04 — Opus replied "no file tools in this
1834
1842
  // session" while Bash was actually exposed at position 7+).
1835
1843
  const finalSpecs: LLMToolSpec[] = opts.rich === false
1836
- ? buildEssentialSessionRuntimeToolSpecs({
1837
- hostTools: opts.hostTools,
1838
- runtimeTools: opts.runtimeTools ?? [],
1839
- optionalTools: opts.optionalTools ?? [],
1840
- toolAvailability: opts.toolAvailability,
1841
- })
1844
+ ? (() => {
1845
+ const essentialSpecs = buildEssentialSessionRuntimeToolSpecs({
1846
+ hostTools: opts.hostTools,
1847
+ runtimeTools: opts.runtimeTools ?? [],
1848
+ optionalTools: opts.optionalTools ?? [],
1849
+ toolAvailability: opts.toolAvailability,
1850
+ });
1851
+ if (!hasDevHarnessIntent(opts.userText)) return essentialSpecs;
1852
+ const droppedNames = new Set(['Edit', 'Write']);
1853
+ const filteredSpecs = essentialSpecs.filter(spec => !droppedNames.has(spec.name));
1854
+ if (filteredSpecs.length !== essentialSpecs.length) {
1855
+ debug.log('session-runtime.tools', 'harness-phrase-write-tools-dropped', {
1856
+ dropped: ['Edit', 'Write'],
1857
+ });
1858
+ }
1859
+ return filteredSpecs;
1860
+ })()
1842
1861
  : [
1843
1862
  ...dynamicNativeTools,
1844
1863
  ...hostSpecs,
@@ -2084,6 +2103,10 @@ export async function dispatchSessionRuntimeTool(
2084
2103
  args: Record<string, unknown>,
2085
2104
  deps: SessionRuntimeDispatchDeps,
2086
2105
  ): Promise<unknown> {
2106
+ const finishRuntimeResult = (result: unknown): unknown =>
2107
+ name === 'ToolSearch' && isDevHarnessIntent(deps.userText ?? '')
2108
+ ? blockWriteToolSearchHydration(result as ToolSearchRunResult)
2109
+ : result;
2087
2110
  // Fix W-bis (2026-04-25): codex family always enables scoped-analysis
2088
2111
  // mode regardless of userText vocabulary. Rationale (see docs/RESEARCH-
2089
2112
  // codex-reread-pathology-2refs-2026-04-25.md): codex's search-heavy
@@ -2178,8 +2201,9 @@ export async function dispatchSessionRuntimeTool(
2178
2201
  const nativeRuntime = nativeRule?.preferRuntime ? deps.getToolRuntime(name) : undefined;
2179
2202
  if (nativeRuntime) {
2180
2203
  const result = await deps.dispatchToolRuntime(name, normalizedArgs, deps.toolCallId);
2181
- rememberCandidateListingResult(name, normalizedArgs, result, scopedAnalysis, deps.searchPlannerState);
2182
- return result;
2204
+ const safeResult = finishRuntimeResult(result);
2205
+ rememberCandidateListingResult(name, normalizedArgs, safeResult, scopedAnalysis, deps.searchPlannerState);
2206
+ return safeResult;
2183
2207
  }
2184
2208
  if (nativeRule?.dispatch) {
2185
2209
  let result: unknown;
@@ -2213,8 +2237,9 @@ export async function dispatchSessionRuntimeTool(
2213
2237
  const isPtyTool = name.startsWith('PtyShell') || name.startsWith('pty_shell');
2214
2238
  if (!isPtyTool || deps.ptyDashboardOn) {
2215
2239
  const result = await deps.dispatchToolRuntime(name, normalizedArgs, deps.toolCallId);
2216
- rememberCandidateListingResult(name, normalizedArgs, result, scopedAnalysis, deps.searchPlannerState);
2217
- return result;
2240
+ const safeResult = finishRuntimeResult(result);
2241
+ rememberCandidateListingResult(name, normalizedArgs, safeResult, scopedAnalysis, deps.searchPlannerState);
2242
+ return safeResult;
2218
2243
  }
2219
2244
  }
2220
2245
 
@@ -2699,6 +2724,25 @@ function hasDevHarnessIntent(text: string): boolean {
2699
2724
  return hasAny(text, DEV_HARNESS_INTENT_TERMS);
2700
2725
  }
2701
2726
 
2727
+ export function isDevHarnessIntent(text: string): boolean {
2728
+ return hasDevHarnessIntent(text);
2729
+ }
2730
+
2731
+ /** The dashboard calls this immediately after submitIntent resolution. A
2732
+ * fixed plain turn becomes the same control turn used by `/harness ask`, so
2733
+ * the model dispatch branch is never reached for that turn. */
2734
+ export function routeDevHarnessSubmitIntent(
2735
+ intent: SubmitTurnIntent | ControlTurnIntent,
2736
+ ): SubmitTurnIntent | ControlTurnIntent {
2737
+ if (intent.kind !== 'submit-turn' || intent.route !== 'plain' || !isDevHarnessIntent(intent.text)) return intent;
2738
+ return {
2739
+ kind: 'control-turn',
2740
+ source: intent.source,
2741
+ command: 'slash-command',
2742
+ commandText: `/harness ask ${intent.text}`,
2743
+ };
2744
+ }
2745
+
2702
2746
  export function shouldExposeDevHarnessSessionTool(text: string): boolean {
2703
2747
  return isDevHarnessModelSurfaceEnabled() && hasDevHarnessIntent(text);
2704
2748
  }
@@ -212,6 +212,26 @@ function groundingMessages(goal: string, terms: readonly string[]): LLMMessage[]
212
212
  }];
213
213
  }
214
214
 
215
+ function unreadEvidencePaths(evidence: string, readFiles: ReadonlySet<string>): string[] {
216
+ return Array.from(new Set(evidence.split(/\r?\n/)
217
+ .flatMap(repositoryRelativeCodePaths)
218
+ .filter((path) => isCodeRootPath(path) && !readFiles.has(path)))).slice(0, 8);
219
+ }
220
+
221
+ function rejectedEvidenceFeedback(evidence: string, readFiles: ReadonlySet<string>): LLMMessage {
222
+ const unread = unreadEvidencePaths(evidence, readFiles);
223
+ const read = Array.from(readFiles).slice(0, 8);
224
+ return {
225
+ role: 'user',
226
+ content: [
227
+ 'Your completion evidence was rejected because it cited a repository path that Read did not verify.',
228
+ `Unverified paths: ${unread.length ? unread.join(', ') : '(none in allowed code roots)'}`,
229
+ `Paths actually Read: ${read.length ? read.join(', ') : '(none)'}`,
230
+ 'Try once more. Read each relevant unverified path before citing it, or remove it from the evidence. Cite only repository-relative code paths that this grounding run actually Read, each with a concrete reason. If no path is relevant, do not invent evidence.',
231
+ ].join('\n'),
232
+ };
233
+ }
234
+
215
235
  /** Bounded, shared, read-only grounding loop. Failure or no candidates returns null. */
216
236
  export async function groundPersistently(goal: string, cwd: string, deps: PersistentGroundingDeps = {}): Promise<PersistentGroundingResult | null> {
217
237
  const startedAt = performance.now();
@@ -250,20 +270,53 @@ export async function groundPersistently(goal: string, cwd: string, deps: Persis
250
270
  },
251
271
  };
252
272
  const options: GoalLoopOptions = { maxIterations, objective: `Persistent repository grounding: ${goal.slice(0, 200)}` };
253
- const result = await loop(ctx, options);
254
- const verified = result.goalComplete && result.stopReason === 'goal_complete'
273
+ const first = await loop(ctx, options);
274
+ const firstVerified = first.goalComplete && first.stopReason === 'goal_complete'
255
275
  ? verifiedEvidenceFromCompletion(completionEvidence, readFiles)
256
276
  : {
257
277
  files: [],
258
278
  evidence: [],
259
279
  rejection: { noPath: 0, outsideRoot: 0, notRead: 0, noMeaningfulText: 0, samples: [] },
260
280
  };
281
+ let result = first;
282
+ let verified = firstVerified;
283
+ let feedbackRetry = false;
284
+ let retryIterations = 0;
285
+ if (firstVerified.rejection.notRead > 0) {
286
+ feedbackRetry = true;
287
+ const feedback = rejectedEvidenceFeedback(completionEvidence, readFiles);
288
+ completionEvidence = '';
289
+ try {
290
+ const retry = await loop({ ...ctx, sessionId: `${sessionId}-evidence-retry`, messages: [...ctx.messages, feedback] },
291
+ { ...options, maxIterations: 2 });
292
+ retryIterations = retry.iterations;
293
+ const retryVerified = retry.goalComplete && retry.stopReason === 'goal_complete'
294
+ ? verifiedEvidenceFromCompletion(completionEvidence, readFiles)
295
+ : null;
296
+ if (retryVerified) {
297
+ verified = {
298
+ files: Array.from(new Set([...firstVerified.files, ...retryVerified.files])),
299
+ evidence: Array.from(new Set([...firstVerified.evidence, ...retryVerified.evidence])),
300
+ rejection: retryVerified.rejection,
301
+ };
302
+ result = retry;
303
+ }
304
+ } catch (error) {
305
+ try {
306
+ debug.log('grounding.persistent', 'evidence-retry-failed', { sessionId,
307
+ reason: error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200) }, { level: 'warn' });
308
+ } catch {
309
+ // A failed retry and its telemetry cannot erase evidence already verified by Read.
310
+ }
311
+ }
312
+ }
313
+ const totalIterations = first.iterations + retryIterations;
261
314
  try {
262
315
  debug.log('grounding.persistent', 'finished', {
263
316
  sessionId,
264
317
  maxIterations,
265
318
  durationMs: durationMs(),
266
- iterations: result.iterations,
319
+ iterations: totalIterations,
267
320
  stopReason: result.stopReason,
268
321
  // ⛔ 「끝났다」가 두 뜻이다 — ⓐ 스스로 목표를 끝냈다 ⓑ 턴이 «그냥» 끝났다.
269
322
  // 📏 2026-08-11 72차 전수(400건): 후보 0인 finished 가 5건이고 ***다섯 다 end_turn*** 이었다.
@@ -273,6 +326,8 @@ export async function groundPersistently(goal: string, cwd: string, deps: Persis
273
326
  candidateCount: verified.files.length,
274
327
  evidenceCount: verified.evidence.length,
275
328
  evidenceRejection: verified.rejection,
329
+ feedbackRetry,
330
+ ...(feedbackRetry ? { firstEvidenceRejection: firstVerified.rejection } : {}),
276
331
  toolCalls,
277
332
  });
278
333
  } catch {
@@ -281,7 +336,7 @@ export async function groundPersistently(goal: string, cwd: string, deps: Persis
281
336
  return {
282
337
  files: verified.files,
283
338
  evidence: verified.evidence,
284
- iterations: result.iterations,
339
+ iterations: totalIterations,
285
340
  stopReason: result.stopReason,
286
341
  };
287
342
  } catch (error) {
@@ -31,9 +31,13 @@ import {
31
31
  runShell as runnerDispatch,
32
32
  getShellRunnerDeps,
33
33
  } from '../../shell-runner/dispatch.js';
34
- import type { ShellResult as RunnerResult } from '../../shell-runner/types.js';
34
+ import type { ShellHandle, ShellResult as RunnerResult } from '../../shell-runner/types.js';
35
35
  import { debug } from '../../debug/log.js';
36
36
  import { getSessionCwd } from '../../session/working-dir.js';
37
+ import {
38
+ globalTaskNotificationQueue,
39
+ type TaskNotificationQueue,
40
+ } from '../../agent/task-notification.js';
37
41
 
38
42
  /** LLMToolSpec — the shape surfaced to the model. Kept narrow so
39
43
  * accidental misuse is hard: no env override, no cwd outside the
@@ -178,6 +182,62 @@ function logShellDispatch(event: string, data: Record<string, unknown>): void {
178
182
  debug.log('shell.dispatch', event, data);
179
183
  }
180
184
 
185
+ // ── bg completion → turn notification queue (TUI-BG-SHELL) ───────────
186
+ // When a bg-mode shell handle settles, its result is no longer awaited
187
+ // by anyone — the spawn already returned the `outcome=started` stub.
188
+ // To surface the eventual result we enqueue one task-notification into
189
+ // the existing turn notification queue (injected at turn-start by
190
+ // chat/skill-runner). One notice per handle; opening an idle turn from
191
+ // the wake signal is deliberately NOT wired here (TUI-EVENT-TURN-QUEUE).
192
+
193
+ export interface ShellCompletionNoticeInput {
194
+ handle: ShellHandle;
195
+ ok: boolean;
196
+ exitCode: number | null;
197
+ durationMs: number;
198
+ /** Set when ok === false — the runner's failure reason. */
199
+ errorMessage?: string;
200
+ /** Caller-supplied free-form label (RunShell's `description` arg). */
201
+ label?: string;
202
+ }
203
+
204
+ /** Per-handle dedup guard — module-local, keyed by the handle OBJECT.
205
+ * A registry replacement may mint a NEW handle that reuses an id; the
206
+ * shell layer must not swallow that fresh completion as a duplicate
207
+ * settle (an id-keyed record here would). */
208
+ const notifiedHandles = new WeakSet<ShellHandle>();
209
+ let nextShellCompletionNoticeId = 0;
210
+
211
+ /** Enqueue one bg shell-handle completion notice. Returns true when this
212
+ * handle had not notified before (the notice was handed to `queue`),
213
+ * false on a duplicate settle of the same handle. The queue's own
214
+ * The shared queue deduplicates by taskId even after drain, so each
215
+ * completed handle gets its own notification id. The original handle id
216
+ * remains in the output for ShellPoll and human follow-up.
217
+ * `queue` defaults to the global turn notification queue; injectable
218
+ * so tests can isolate. */
219
+ export function enqueueShellCompletionNotice(
220
+ input: ShellCompletionNoticeInput,
221
+ queue: TaskNotificationQueue = globalTaskNotificationQueue,
222
+ ): boolean {
223
+ if (notifiedHandles.has(input.handle)) return false;
224
+ notifiedHandles.add(input.handle);
225
+ const header = `RunShell bg handle=${input.handle.id} ${input.ok ? 'completed' : 'failed'}`;
226
+ const output = `${header}${input.ok || input.exitCode !== null ? ` exitCode=${input.exitCode ?? 'null'}` : ''} elapsed=${input.durationMs}ms${input.errorMessage ? ` error=${input.errorMessage}` : ''}`;
227
+ queue.enqueue({
228
+ taskId: `shell:${input.handle.id}:${++nextShellCompletionNoticeId}`,
229
+ agentName: 'RunShell',
230
+ ...(input.label ? { label: input.label } : {}),
231
+ state: input.ok ? 'done' : 'error',
232
+ output,
233
+ truncated: false,
234
+ ...(input.errorMessage ? { errorMessage: input.errorMessage } : {}),
235
+ durationMs: input.durationMs,
236
+ finishedAt: Date.now(),
237
+ });
238
+ return true;
239
+ }
240
+
181
241
  export async function dispatchRunShell(
182
242
  rawArgs: Record<string, unknown>,
183
243
  opts: { signal?: AbortSignal } = {},
@@ -269,6 +329,16 @@ async function dispatchViaRunner(args: {
269
329
  exitCode: result.exitCode ?? null,
270
330
  durationMs: result.durationMs,
271
331
  });
332
+ const ok = result.outcome === 'exit' && result.exitCode === 0;
333
+ enqueueShellCompletionNotice({
334
+ handle,
335
+ ok,
336
+ exitCode: result.exitCode ?? null,
337
+ durationMs: result.durationMs,
338
+ ...(!ok ? { errorMessage: result.exitCode !== undefined
339
+ ? `exitCode=${result.exitCode}` : `outcome=${result.outcome}` } : {}),
340
+ ...(args.description !== undefined ? { label: args.description } : {}),
341
+ });
272
342
  },
273
343
  (err: unknown) => {
274
344
  debug.log('skill-tool.shell', 'bg-finished', {
@@ -277,6 +347,14 @@ async function dispatchViaRunner(args: {
277
347
  durationMs: Date.now() - bgStartedAt,
278
348
  error: err instanceof Error ? err.message : String(err),
279
349
  });
350
+ enqueueShellCompletionNotice({
351
+ handle,
352
+ ok: false,
353
+ exitCode: null,
354
+ durationMs: Date.now() - bgStartedAt,
355
+ errorMessage: err instanceof Error ? err.message : String(err),
356
+ ...(args.description !== undefined ? { label: args.description } : {}),
357
+ });
280
358
  },
281
359
  );
282
360
  return {
@@ -19,6 +19,7 @@ import { debug } from '../debug/log.js';
19
19
  import { effectiveInstanceRoot } from '../instance/resolve.js';
20
20
  import { spawnElanous } from '../instance/spawn-elanous.js';
21
21
  import { getUserConfig } from '../user-config.js';
22
+ import { resyncPendingPath } from './resync-tick.js';
22
23
 
23
24
  export const REJUDGE_TICK_EVENT = 'rejudge-tick';
24
25
  export const REJUDGE_DEFAULT_MAX_LOAD = 60;
@@ -49,6 +50,7 @@ export interface RejudgeTickDeps {
49
50
  config?: () => { rejudgeEveryMinutes?: number; rejudgeMaxLoad?: number } | undefined;
50
51
  load?: () => number;
51
52
  stampPath?: string;
53
+ pendingPath?: string;
52
54
  run?: () => Promise<RejudgeRun>;
53
55
  log?: (event: string, data: Record<string, unknown>) => void;
54
56
  }
@@ -110,7 +112,9 @@ export async function rejudgeTick(deps: RejudgeTickDeps = {}): Promise<RejudgeTi
110
112
  const now = (deps.now ?? (() => new Date()))();
111
113
  const stampPath = deps.stampPath ?? defaultStampPath();
112
114
  const lastStartedAt = readStamp(stampPath);
113
- if (lastStartedAt && now.getTime() - Date.parse(lastStartedAt) < everyMinutes * 60_000 - REJUDGE_JITTER_MS) {
115
+ const pendingAt = readPending(deps.pendingPath ?? resyncPendingPath());
116
+ const savedSinceLastRun = pendingAt !== null && (!lastStartedAt || Date.parse(pendingAt) > Date.parse(lastStartedAt));
117
+ if (!savedSinceLastRun && lastStartedAt && now.getTime() - Date.parse(lastStartedAt) < everyMinutes * 60_000 - REJUDGE_JITTER_MS) {
114
118
  return emit({ decision: 'not-due', everyMinutes, lastStartedAt });
115
119
  }
116
120
  const maxLoad = positive(cfg?.rejudgeMaxLoad) ?? REJUDGE_DEFAULT_MAX_LOAD;
@@ -133,6 +137,13 @@ export async function rejudgeTick(deps: RejudgeTickDeps = {}): Promise<RejudgeTi
133
137
  }
134
138
  }
135
139
 
140
+ function readPending(path: string): string | null {
141
+ try {
142
+ const value = (JSON.parse(readFileSync(path, 'utf8')) as { savedAt?: unknown }).savedAt;
143
+ return typeof value === 'string' && Number.isFinite(Date.parse(value)) ? value : null;
144
+ } catch { return null; }
145
+ }
146
+
136
147
  if (import.meta.main) {
137
148
  try { await (await import('../domains/standalone-log-sink.js')).registerStandaloneLogSink('steward'); }
138
149
  catch (error) { console.error(`steward rejudge: log sink registration failed — ${error instanceof Error ? error.message : String(error)}`); }
@@ -0,0 +1,158 @@
1
+ /** Release-day shadow: observe the scheduled phase without performing its action. */
2
+ import { chmodSync, mkdirSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { Database } from 'bun:sqlite';
5
+ import { debug } from '../debug/log.js';
6
+ import { effectiveInstanceRoot } from '../instance/resolve.js';
7
+ import { listSchedules, type ReleaseSchedule } from '../release-loop/release-schedule.js';
8
+
9
+ export type ReleaseDayPhase = 'gate-mode' | 'carry-p0' | 'cut-run' | 'green-back' | 'none' | 'unmeasured';
10
+ export interface ReleaseDayDecision {
11
+ version: string;
12
+ phase: ReleaseDayPhase;
13
+ dueAt: string | null;
14
+ scheduleUpdatedAt: string;
15
+ reason?: string;
16
+ }
17
+
18
+ /** The release record, rather than publishAt (a planned time), opens green-back. */
19
+ export function decideReleaseDayPhase(schedule: ReleaseSchedule, now: Date, publishedAt: string | null): ReleaseDayDecision {
20
+ const cut = Date.parse(schedule.cutAt);
21
+ const land = schedule.landBy ? Date.parse(schedule.landBy) : cut;
22
+ const gate = cut - 30 * 60_000;
23
+ const time = now.getTime();
24
+ let phase: ReleaseDayPhase = 'none';
25
+ let dueAt: string | null = null;
26
+ if (publishedAt && time >= Date.parse(publishedAt)) {
27
+ phase = 'green-back'; dueAt = publishedAt;
28
+ } else if (time >= cut) {
29
+ phase = 'cut-run'; dueAt = schedule.cutAt;
30
+ } else if (time >= land) {
31
+ phase = 'carry-p0'; dueAt = schedule.landBy;
32
+ } else if (time >= gate) {
33
+ phase = 'gate-mode'; dueAt = new Date(gate).toISOString();
34
+ }
35
+ return { version: schedule.version, phase, dueAt, scheduleUpdatedAt: schedule.updatedAt };
36
+ }
37
+
38
+ export type PublicationProbe = { kind: 'published'; at: string } | { kind: 'unpublished' } | { kind: 'unmeasured'; reason: string };
39
+
40
+ function actualPublicationProbe(version: string, root: string): PublicationProbe {
41
+ const path = join(root, 'release', version, 'release.json');
42
+ let raw: string;
43
+ try { raw = readFileSync(path, 'utf8'); }
44
+ catch (error) {
45
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { kind: 'unpublished' };
46
+ return { kind: 'unmeasured', reason: 'release-record-read-failed' };
47
+ }
48
+ try {
49
+ const record = JSON.parse(raw) as { version?: unknown; publishedAt?: unknown };
50
+ if (record.version !== version) return { kind: 'unmeasured', reason: 'release-record-version-mismatch' };
51
+ if (record.publishedAt == null) return { kind: 'unmeasured', reason: 'release-record-published-at-missing' };
52
+ if (typeof record.publishedAt !== 'string' || !Number.isFinite(Date.parse(record.publishedAt)))
53
+ return { kind: 'unmeasured', reason: 'release-record-published-at-invalid' };
54
+ return { kind: 'published', at: record.publishedAt };
55
+ } catch { return { kind: 'unmeasured', reason: 'release-record-json-failed' }; }
56
+ }
57
+
58
+ export interface ReleaseDayScheduleState { publication: PublicationProbe; greenObserved: boolean }
59
+
60
+ /** A late release and the next gate can both be due; return both instead of starving either. */
61
+ export function selectReleaseDaySchedules(
62
+ schedules: ReleaseSchedule[], now: Date, state: (version: string) => ReleaseDayScheduleState,
63
+ ): ReleaseSchedule[] {
64
+ const ordered = schedules.filter(s => Number.isFinite(Date.parse(s.cutAt))).sort((a, b) => Date.parse(a.cutAt) - Date.parse(b.cutAt));
65
+ const time = now.getTime();
66
+ const dueFuture = ordered.filter(s => {
67
+ const cut = Date.parse(s.cutAt);
68
+ const firstDue = Math.min(cut - 30 * 60_000, s.landBy ? Date.parse(s.landBy) : cut);
69
+ return time < cut && time >= firstDue;
70
+ });
71
+ const past = ordered.filter(s => Date.parse(s.cutAt) <= time);
72
+ const unpublished = past.filter(s => state(s.version).publication.kind === 'unpublished');
73
+ const unmeasured = past.filter(s => state(s.version).publication.kind === 'unmeasured');
74
+ const pendingGreen = past.filter(s => {
75
+ const status = state(s.version);
76
+ return status.publication.kind === 'published' && !status.greenObserved;
77
+ });
78
+ const due = [...dueFuture, ...unpublished, ...unmeasured, ...pendingGreen];
79
+ if (due.length) return [...new Map(due.map(s => [s.version, s])).values()];
80
+ const upcoming = ordered.find(s => Date.parse(s.cutAt) > time);
81
+ return upcoming && Date.parse(upcoming.cutAt) - time <= 24 * 60 * 60_000 ? [upcoming] : [];
82
+ }
83
+
84
+ export interface ReleaseDayShadowDeps {
85
+ now?: () => Date;
86
+ releaseRoot?: string;
87
+ stateRoot?: string;
88
+ schedules?: () => ReleaseSchedule[];
89
+ publishedAt?: (version: string, root: string) => string | null;
90
+ log?: (event: string, data: Record<string, unknown>) => void;
91
+ }
92
+
93
+ export interface ReleaseDayShadowResult extends ReleaseDayDecision { recorded: boolean }
94
+
95
+ export function runReleaseDayShadow(deps: ReleaseDayShadowDeps = {}): ReleaseDayShadowResult[] {
96
+ const now = (deps.now ?? (() => new Date()))();
97
+ // The graph child's stamped universe also owns its release schedule under --test.
98
+ const releaseRoot = deps.releaseRoot ?? effectiveInstanceRoot();
99
+ const schedules = (deps.schedules ?? (() => listSchedules(releaseRoot)))();
100
+ const log = deps.log ?? ((event: string, data: Record<string, unknown>) => debug.log('steward.release-day', event, data));
101
+ if (!schedules.length) { log('decision', { phase: 'none', reason: 'no-release-schedule', at: now.toISOString(), recorded: false, mode: 'shadow' }); return []; }
102
+ const dir = join(deps.stateRoot ?? effectiveInstanceRoot(), 'steward');
103
+ mkdirSync(dir, { recursive: true });
104
+ const path = join(dir, 'release-day-shadow.sqlite');
105
+ const db = new Database(path, { create: true, strict: true });
106
+ try {
107
+ chmodSync(path, 0o600);
108
+ db.exec('PRAGMA busy_timeout = 10000');
109
+ db.exec('CREATE TABLE IF NOT EXISTS phase_observations (version TEXT NOT NULL, phase TEXT NOT NULL, due_at TEXT NOT NULL, observed_at TEXT NOT NULL, schedule_updated_at TEXT NOT NULL, PRIMARY KEY (version, phase))');
110
+ const states = new Map<string, ReleaseDayScheduleState>();
111
+ for (const schedule of schedules) {
112
+ const observed = db.query('SELECT phase FROM phase_observations WHERE version = ?').all(schedule.version) as Array<{ phase: string }>;
113
+ const injected = deps.publishedAt?.(schedule.version, releaseRoot);
114
+ states.set(schedule.version, {
115
+ publication: deps.publishedAt
116
+ ? injected == null ? { kind: 'unpublished' }
117
+ : Number.isFinite(Date.parse(injected)) ? { kind: 'published', at: injected }
118
+ : { kind: 'unmeasured', reason: 'injected-published-at-invalid' }
119
+ : actualPublicationProbe(schedule.version, releaseRoot),
120
+ greenObserved: observed.some(row => row.phase === 'green-back'),
121
+ });
122
+ }
123
+ const selected = selectReleaseDaySchedules(schedules, now, version => states.get(version)!);
124
+ if (!selected.length) { log('decision', { phase: 'none', reason: 'outside-release-window', at: now.toISOString(), recorded: false, mode: 'shadow' }); return []; }
125
+ const results: ReleaseDayShadowResult[] = [];
126
+ for (const schedule of selected) {
127
+ const publication = states.get(schedule.version)!.publication;
128
+ if (publication.kind === 'unmeasured') {
129
+ const result: ReleaseDayShadowResult = { version: schedule.version, phase: 'unmeasured', dueAt: null,
130
+ scheduleUpdatedAt: schedule.updatedAt, reason: publication.reason, recorded: false };
131
+ log('unmeasured', { ...result, at: now.toISOString(), mode: 'shadow' });
132
+ results.push(result);
133
+ continue;
134
+ }
135
+ const publishedAt = publication.kind === 'published' ? publication.at : null;
136
+ const decision = decideReleaseDayPhase(schedule, now, publishedAt);
137
+ let recorded = false;
138
+ if (decision.phase !== 'none') {
139
+ const row = db.query('INSERT OR IGNORE INTO phase_observations (version, phase, due_at, observed_at, schedule_updated_at) VALUES (?, ?, ?, ?, ?)')
140
+ .run(decision.version, decision.phase, decision.dueAt!, now.toISOString(), decision.scheduleUpdatedAt);
141
+ recorded = row.changes === 1;
142
+ }
143
+ log('decision', { ...decision, at: now.toISOString(), recorded, mode: 'shadow', publishedAt });
144
+ results.push({ ...decision, recorded });
145
+ }
146
+ return results;
147
+ } finally { db.close(); }
148
+ }
149
+
150
+ if (import.meta.main) {
151
+ try {
152
+ const sinkReady = await (await import('../domains/standalone-log-sink.js')).registerStandaloneLogSink('steward');
153
+ if (!sinkReady) throw new Error('durable log sink unavailable');
154
+ const results = runReleaseDayShadow();
155
+ console.log(`steward release-day shadow: ${results.map(result => `${result.version}:${result.phase}${result.recorded ? ' · recorded' : ''}`).join(', ') || 'none'}`);
156
+ }
157
+ catch (error) { console.error(`steward release-day shadow failed: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
158
+ }
@@ -0,0 +1,123 @@
1
+ /** Resync labeled PRs before the steward's TA rejudge node sees their heads. */
2
+ import { debug } from '../debug/log.js';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { mkdirSync, writeFileSync } from 'node:fs';
5
+ import { dirname, join } from 'node:path';
6
+ import { getElanousConfigDir } from '../elanous-config-dir.js';
7
+ import { resolveHqLaunchSeat } from '../hq/seats.js';
8
+ import { effectiveInstanceRoot } from '../instance/resolve.js';
9
+ import { runResyncShadow, type ResyncShadowSummary } from '../loops/orchestrator/resync-shadow.js';
10
+ import { listSchedules, type ReleaseSchedule } from '../release-loop/release-schedule.js';
11
+
12
+ type ReleaseRunState = 'active' | 'idle' | 'unknown';
13
+ type ReleaseWindowState = 'inside' | 'outside' | 'unknown';
14
+ type ResyncTickResult = { decision: 'ran' | 'failed' | 'skipped-release' | 'skipped-release-window'; summary?: ResyncShadowSummary; reason?: string };
15
+
16
+ export function resyncPendingPath(): string { return join(effectiveInstanceRoot(), 'steward', 'resync-pending.json'); }
17
+
18
+ /** The same registered, detached OP checkout used by steward launch; the installed package is not a git repository. */
19
+ export function stewardResyncRepoRoot(
20
+ resolveSeat: typeof resolveHqLaunchSeat = resolveHqLaunchSeat,
21
+ configDir: () => string = getElanousConfigDir,
22
+ ): string { return resolveSeat('OP', configDir()); }
23
+
24
+ /** A release may detach the OP checkout. Unknown process state also skips remote branch writes. */
25
+ export function probeReleaseRun(
26
+ list: () => { status: number | null; stdout?: string; error?: Error } = () => spawnSync('pgrep', ['-f', 'release[[:space:]]+(run|resume)'], { encoding: 'utf8', timeout: 5_000 }),
27
+ command: (pid: string) => string | null = pid => {
28
+ const result = spawnSync('ps', ['-p', pid, '-o', 'command='], { encoding: 'utf8', timeout: 5_000 });
29
+ return result.status === 0 ? result.stdout.trim() : null;
30
+ },
31
+ ): ReleaseRunState {
32
+ try {
33
+ const result = list();
34
+ if (result.error || result.status === null) return 'unknown';
35
+ if (result.status === 1) return 'idle';
36
+ if (result.status !== 0) return 'unknown';
37
+ const pids = (result.stdout ?? '').trim().split(/\s+/).filter(pid => /^\d+$/.test(pid));
38
+ if (!pids.length) return 'unknown';
39
+ for (const pid of pids) {
40
+ const argv = command(pid);
41
+ if (argv === null) continue;
42
+ const executable = argv.trim().split(/\s+/)[0]?.split('/').at(-1);
43
+ if (['bun', 'node', 'eln', 'elanous'].includes(executable ?? '') && /(?:^|\s)release\s+(?:run|resume)(?:\s|$)/.test(argv)) return 'active';
44
+ // pgrep saw a release-shaped command, but a shell/wrapper cannot prove whether it will launch the CLI.
45
+ }
46
+ return 'unknown';
47
+ } catch { return 'unknown'; }
48
+ }
49
+
50
+ /** A release cut may start after the process probe; the scheduled cut/publication window is a fixed safety boundary. */
51
+ export function releaseWindowState(
52
+ nowMs = Date.now(),
53
+ read: () => ReleaseSchedule[] = listSchedules,
54
+ ): ReleaseWindowState {
55
+ try {
56
+ const schedules = read();
57
+ if (!schedules.length) return 'unknown';
58
+ const rows = schedules.map(row => ({ cut: Date.parse(row.cutAt), publish: Date.parse(row.publishAt ?? '') }));
59
+ if (rows.some(row => !Number.isFinite(row.cut))) return 'unknown';
60
+ const latestPast = rows.filter(row => row.cut <= nowMs).sort((a, b) => b.cut - a.cut)[0];
61
+ if (latestPast && !Number.isFinite(latestPast.publish)) return 'unknown';
62
+ if (latestPast && nowMs <= latestPast.publish + 60 * 60_000) return 'inside';
63
+ const next = rows.filter(row => row.cut > nowMs).sort((a, b) => a.cut - b.cut)[0];
64
+ if (!next || !Number.isFinite(next.publish) || next.publish < next.cut) return 'unknown';
65
+ return nowMs >= next.cut - 40 * 60_000 ? 'inside' : 'outside';
66
+ } catch { return 'unknown'; }
67
+ }
68
+
69
+ export function defaultResyncRun(
70
+ repoRoot = stewardResyncRepoRoot(),
71
+ execute: typeof runResyncShadow = runResyncShadow,
72
+ ): Promise<ResyncShadowSummary> { return execute(3, { repoRoot }, 'live'); }
73
+
74
+ export async function resyncTick(
75
+ run: () => Promise<ResyncShadowSummary> = () => defaultResyncRun(),
76
+ log: (event: string, data: Record<string, unknown>) => void = (event, data) => debug.log('loop.steward', event, data),
77
+ pendingPath: string = resyncPendingPath(),
78
+ ): Promise<ResyncTickResult> { return resyncTickWithReleaseProbe(run, log, pendingPath, probeReleaseRun); }
79
+
80
+ export async function resyncTickWithReleaseProbe(
81
+ run: () => Promise<ResyncShadowSummary>,
82
+ log: (event: string, data: Record<string, unknown>) => void,
83
+ pendingPath: string,
84
+ releaseProbe: () => ReleaseRunState,
85
+ windowProbe: () => ReleaseWindowState = releaseWindowState,
86
+ ): Promise<ResyncTickResult> {
87
+ let result: ResyncTickResult;
88
+ try {
89
+ const release = releaseProbe();
90
+ if (release !== 'idle') {
91
+ result = { decision: 'skipped-release', reason: release === 'active' ? 'release run active' : 'release process probe unavailable' };
92
+ } else {
93
+ const window = windowProbe();
94
+ if (window !== 'outside') {
95
+ result = { decision: 'skipped-release-window', reason: window === 'inside' ? 'scheduled release window' : 'release schedule unavailable' };
96
+ } else {
97
+ const summary = await run();
98
+ if (summary.saved > 0) {
99
+ mkdirSync(dirname(pendingPath), { recursive: true });
100
+ writeFileSync(pendingPath, JSON.stringify({ savedAt: new Date().toISOString() }));
101
+ }
102
+ result = { decision: 'ran', summary };
103
+ }
104
+ }
105
+ }
106
+ catch (error) { result = { decision: 'failed', reason: (error instanceof Error ? error.message : String(error)).slice(-300) }; }
107
+ try { log('resync-tick', { ...result }); }
108
+ catch (error) {
109
+ result = { decision: 'failed', reason: `log failed: ${error instanceof Error ? error.message : String(error)}`.slice(-300) };
110
+ console.error(`steward resync: ${result.reason}`);
111
+ }
112
+ return result;
113
+ }
114
+
115
+ if (import.meta.main) {
116
+ let sinkReady = false;
117
+ try { sinkReady = await (await import('../domains/standalone-log-sink.js')).registerStandaloneLogSink('steward'); }
118
+ catch (error) { console.error(`steward resync: log sink registration failed — ${error instanceof Error ? error.message : String(error)}`); }
119
+ // A missing durable sink must not precede a remote branch update.
120
+ const result = sinkReady ? await resyncTick() : { decision: 'failed' as const, reason: 'log sink unavailable' };
121
+ console.log(`steward resync: ${result.decision}${result.reason ? ` — ${result.reason}` : ''}`);
122
+ process.exitCode = 0;
123
+ }