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
@@ -166,6 +166,7 @@ export async function executeNextAction(action: NextAction, deps: ActionDeps): P
166
166
  catch { return card('PR labels unavailable'); }
167
167
  if (!Array.isArray(labels) || !labels.every(label => label && typeof label === 'object' && typeof label.name === 'string')) return card('PR labels unavailable');
168
168
  if (labels.some(label => label.name === 'elanous:release-path')) return card('release-path');
169
+ if (labels.some(label => label.name === 'elanous:keep')) return card('elanous:keep');
169
170
  const day = new Date().toLocaleDateString('en-CA', { timeZone: 'Asia/Seoul' });
170
171
  if (deps.landingDay && deps.landingDay !== day) deps.landingsToday = 0;
171
172
  deps.landingDay = day;
@@ -0,0 +1,140 @@
1
+ /** Attach an existing open PR to a TA card without starting another implementation run. */
2
+ import { randomBytes } from 'node:crypto';
3
+ import { loadRunLedger, runLedgerDir, type RunLedgerEntry } from '../self-implement/run-ledger.js';
4
+ import { remoteLedgerMirrorDir } from './parent-host.js';
5
+ import { prodInstanceRoot } from '../instance/resolve.js';
6
+ import { cardPrNumber, updateTaskCard, type TaskCard } from './task-hand.js';
7
+
8
+ export interface AdoptPrView {
9
+ number: number; url: string; state: string; baseRefName: string; headRefOid: string;
10
+ }
11
+ export interface AdoptPrInput {
12
+ pr: AdoptPrView;
13
+ repo: string;
14
+ statePath: string;
15
+ cardId?: string;
16
+ version?: string;
17
+ checklist?: { version: string; id: string; title: string; status: string };
18
+ now?: Date;
19
+ terminalRunEvidence?: boolean;
20
+ unlinkedTerminal?: boolean;
21
+ observedRun?: { runId?: string; runChildId?: string; parentHost?: string; launchId?: string };
22
+ observedChecklist?: { id: string; version: string; status: string; cardVersion?: string };
23
+ checklistAtWrite?: () => { id: string; status: string } | undefined;
24
+ }
25
+
26
+ /** A bound implementation may still push to its PR. Require the child to have stopped on this exact PR first. */
27
+ export function adoptedRunTerminal(card: TaskCard, pr: AdoptPrView,
28
+ load: (runId: string, dir: string) => RunLedgerEntry[] | null = loadRunLedger, allowUnlinked = false): boolean {
29
+ if (!card.runId) return false;
30
+ const dir = card.parentHost ? remoteLedgerMirrorDir(prodInstanceRoot(), card.parentHost.host) : runLedgerDir();
31
+ try {
32
+ const parent = load(card.runId, dir);
33
+ if (!parent) return false;
34
+ const childIds = parent.filter((entry) => entry.event === 'pod-child-run' && typeof entry.data?.childRunId === 'string')
35
+ .map((entry) => String(entry.data.childRunId));
36
+ const candidates = childIds.length ? childIds : [card.runId];
37
+ let allStopped = candidates.length > 0;
38
+ let noPrEvents = true;
39
+ let exactMatches = 0;
40
+ for (const childId of candidates) {
41
+ const child = childId === card.runId ? parent : load(childId, dir);
42
+ if (!child) { allStopped = false; continue; }
43
+ let prAt = -1;
44
+ let statusAt = -1;
45
+ for (let at = child.length - 1; at >= 0 && (prAt < 0 || statusAt < 0); at--) {
46
+ if (prAt < 0 && child[at]?.event === 'pr-opened') prAt = at;
47
+ if (statusAt < 0 && child[at]?.event === 'run-status') statusAt = at;
48
+ }
49
+ const opened = child[prAt];
50
+ const status = child[statusAt];
51
+ if (prAt >= 0) noPrEvents = false;
52
+ if (!status || !['completed', 'failed', 'cancelled'].includes(String(status.data?.runStatus))) allStopped = false;
53
+ if ((!card.runChildId || card.runChildId === childId) && opened?.data?.number === pr.number && opened.data?.url === pr.url && statusAt > prAt) exactMatches++;
54
+ }
55
+ if (!allStopped) return false;
56
+ if (exactMatches === 1) return true;
57
+ if (allowUnlinked && exactMatches === 0 && noPrEvents && (!card.runChildId || candidates.includes(card.runChildId))) return true;
58
+ } catch { /* unreadable is not terminal evidence */ }
59
+ return false;
60
+ }
61
+
62
+ /** The PR is read from the selected repository before this call; rejudge checks it again before every live move. */
63
+ export function adoptExistingPr(input: AdoptPrInput): { card: TaskCard; created: boolean; changed: boolean } {
64
+ const { pr } = input;
65
+ const slug = pr.url.match(/^https:\/\/github\.com\/([^/]+\/[^/]+)\/pull\/\d+\/?$/)?.[1];
66
+ if (!Number.isSafeInteger(pr.number) || pr.number < 1 || pr.state !== 'OPEN' || pr.baseRefName !== 'main'
67
+ || !slug || slug.toLowerCase() !== input.repo.toLowerCase()
68
+ || Number(pr.url.match(/\/pull\/(\d+)/)?.[1]) !== pr.number
69
+ || !/^[a-f0-9]{40}$/i.test(pr.headRefOid)) throw new Error('입양할 PR은 현재 저장소의 OPEN/main PR 번호·URL·head SHA40이어야 한다');
70
+ if ((input.cardId === undefined) === (input.checklist === undefined)) throw new Error('--card 또는 --checklist/--version 중 하나만 준다');
71
+ if (input.cardId && !input.version) throw new Error('기존 카드는 체크리스트 판을 확정해야 한다');
72
+ if (input.checklist && (!input.checklist.id || !input.checklist.title || !input.checklist.version
73
+ || ['green', 'done'].includes(input.checklist.status))) throw new Error('입양할 체크리스트 칸은 미완 판·id·제목이어야 한다');
74
+ const id = input.cardId ?? `ta-${(input.now ?? new Date()).toISOString().slice(0, 10).replaceAll('-', '')}-${randomBytes(3).toString('hex')}`;
75
+ const at = (input.now ?? new Date()).toISOString();
76
+ let created = false;
77
+ let changed = false;
78
+ const result = updateTaskCard(input.statePath, id, (current, tasks) => {
79
+ const duplicate = Object.values(tasks).find((card) => card.id !== id && cardPrNumber(card) === pr.number);
80
+ if (duplicate) throw new Error(`PR #${pr.number}는 이미 카드 ${duplicate.id}에 묶였다`);
81
+ const checklistId = current?.checklistId ?? input.checklist?.id;
82
+ const checklistVersion = input.version ?? input.checklist?.version;
83
+ const sameCell = Object.values(tasks).find((card) => card.id !== id && card.status === 'launched' && card.taLand === true
84
+ && cardPrNumber(card) !== undefined && card.checklistId === checklistId
85
+ && (!card.checklistVersion || !checklistVersion || card.checklistVersion === checklistVersion));
86
+ if (sameCell) throw new Error(`칸 ${checklistId}에 이미 TA 착지 후보 카드 ${sameCell.id}가 있다`);
87
+ if (!input.cardId) {
88
+ const owner = Object.values(tasks).find((card) => card.status === 'launched' && card.checklistId === checklistId
89
+ && (!card.checklistVersion || !checklistVersion || card.checklistVersion === checklistVersion));
90
+ if (owner) throw new Error(`칸 ${checklistId}에 기존 카드 ${owner.id}가 있다 — --card로 그 카드를 선택한다`);
91
+ }
92
+ if (input.cardId && (!input.observedRun || current?.runId !== input.observedRun.runId
93
+ || current?.runChildId !== input.observedRun.runChildId
94
+ || current?.parentHost?.host !== input.observedRun.parentHost
95
+ || current?.launchId !== input.observedRun.launchId))
96
+ throw new Error(`입양 중 카드 런 식별자가 바뀌었다: ${id}`);
97
+ if (input.cardId && (!input.observedChecklist || current?.checklistId !== input.observedChecklist.id
98
+ || current?.checklistVersion !== input.observedChecklist.cardVersion
99
+ || input.version !== input.observedChecklist.version
100
+ || ['green', 'done'].includes(input.observedChecklist.status)))
101
+ throw new Error(`입양 중 카드 칸·판이 바뀌었다: ${id}`);
102
+ const liveCell = input.checklistAtWrite?.();
103
+ if (input.checklistAtWrite && (!liveCell || liveCell.id !== checklistId || ['green', 'done'].includes(liveCell.status)))
104
+ throw new Error(`입양 중 체크리스트 칸이 바뀌거나 완료됐다: ${checklistId}`);
105
+ if (current?.adoptedPr?.pr === pr.number && cardPrNumber(current) === pr.number && current.taLand === true
106
+ && typeof current.pr === 'object' && current.pr.url === pr.url) {
107
+ if (current.status !== 'launched') throw new Error(`이미 닫힌 입양 카드다: ${id} (${current.status})`);
108
+ if (current.checklistVersion !== (input.version ?? input.checklist?.version)) throw new Error(`이미 입양한 카드의 판이 다르다: ${current.checklistVersion ?? '미기록'} ≠ ${input.version ?? input.checklist?.version}`);
109
+ return undefined;
110
+ }
111
+ if (input.cardId) {
112
+ const boundPr = current && cardPrNumber(current);
113
+ const sameBoundPr = boundPr === pr.number && typeof current?.pr === 'object' && current.pr.url === pr.url;
114
+ if (!current || current.status !== 'launched' || !current.checklistId
115
+ || (boundPr !== undefined && !sameBoundPr)
116
+ || (input.checklist && current.checklistId !== input.checklist.id)) throw new Error(`입양할 launched·미바인딩/동일 PR 바인딩 카드가 아니다: ${id}`);
117
+ if (boundPr !== undefined && input.unlinkedTerminal)
118
+ throw new Error(`이미 PR이 바인딩된 카드는 --unlinked-terminal로 입양할 수 없다: ${id}`);
119
+ if (input.terminalRunEvidence !== true) throw new Error(`구현 런 종결·PR 근거가 없다: ${current.runId ?? current.launchId ?? 'runId 없음'} — runId 없는 카드는 tasks close 후 --checklist 새 카드로 입양`);
120
+ if (input.version && current.checklistVersion && current.checklistVersion !== input.version)
121
+ throw new Error(`카드 판이 다르다: ${current.checklistVersion} ≠ ${input.version}`);
122
+ } else {
123
+ if (current) throw new Error(`새 카드 id 충돌: ${id}`);
124
+ }
125
+ const base: TaskCard = current ?? {
126
+ id, text: input.checklist!.title, checklistId: input.checklist!.id,
127
+ createdAt: at, status: 'launched', history: [],
128
+ };
129
+ created = !current;
130
+ changed = true;
131
+ return { ...base, ...(input.version || input.checklist ? { checklistVersion: input.version ?? input.checklist!.version } : {}),
132
+ taLand: true, pr: { number: pr.number, url: pr.url },
133
+ adoptedPr: { pr: pr.number, url: pr.url, head: pr.headRefOid, at },
134
+ history: [...base.history, { at, event: 'pr-adopted', pr: pr.number, head: pr.headRefOid,
135
+ detail: `기존 OPEN PR 입양 · ${pr.url} · 새 구현 발사 0${input.unlinkedTerminal ? ' · 명시적 비연결 종결 원장' : ''}` }],
136
+ };
137
+ });
138
+ if (!result) throw new Error(`카드 쓰기 실패: ${id}`);
139
+ return { card: result, created, changed };
140
+ }
@@ -12,7 +12,7 @@ import { releaseLedgerRoot } from '../instance/resolve.js';
12
12
  import { listChecklist, type ChecklistStatus } from '../release-loop/checklist.js';
13
13
  import { buildRunProgressRows, queryRunningRuns, type RunningRunsResult, type RunProgressRow } from '../self-implement/running-runs.js';
14
14
  import {
15
- cardPrNumber, CLOSED_BY_HAND_EVENT, HAND_MOVE_HISTORY_EVENT, LIVE_MOVE_HISTORY_EVENT, LIVE_MOVE_RESULT_EVENT, readTaskAgentState,
15
+ boundPrTerminal, cardPrNumber, CLOSED_BY_HAND_EVENT, HAND_MOVE_HISTORY_EVENT, LIVE_MOVE_HISTORY_EVENT, LIVE_MOVE_RESULT_EVENT, readTaskAgentState,
16
16
  type CompletionKind, type TaskCard,
17
17
  } from './task-hand.js';
18
18
 
@@ -29,6 +29,10 @@ export interface BoardTaskLedger {
29
29
  lastEvent: { at: string; event: string; detail: string | null } | null;
30
30
  /** 카드가 failed · launch-failed 일 때 마지막 줄의 사유(예: `failed/needs-owner — exit 1 · pod-failure`). 그 밖에는 null. */
31
31
  exitReason: string | null;
32
+ /** Terminal PR observation, only when it still matches this card's binding. */
33
+ prTerminal?: NonNullable<TaskCard['prTerminal']>;
34
+ /** Checklist judgement after merge; partial means this slice ended while the cell remains open. */
35
+ postLand?: NonNullable<TaskCard['postLand']>;
32
36
  }
33
37
 
34
38
  /** `GET /api/task-agent/board` 응답 — progress 는 진전 관측(`queryRunningRuns`)을 싣는다; 관측을 못 얻으면 null(0을 «없다»로 쓰지 않는다). */
@@ -87,11 +91,14 @@ function aggregate(node: BoardNode): void {
87
91
  }
88
92
 
89
93
  function cardStatus(card: TaskCard): BoardStatus {
94
+ const terminal = boundPrTerminal(card);
95
+ if (terminal) return terminal.state === 'MERGED' ? 'done' : 'blocked';
90
96
  if (card.status === 'launch-failed' || card.status === 'failed') return 'blocked';
91
97
  return card.greenProposal ? 'done' : 'open';
92
98
  }
93
99
 
94
100
  function cardLedger(card: TaskCard): BoardTaskLedger {
101
+ const terminal = boundPrTerminal(card);
95
102
  const last = (card.history ?? [])
96
103
  .filter((item) => item.event !== LIVE_MOVE_HISTORY_EVENT && item.event !== LIVE_MOVE_RESULT_EVENT && item.event !== HAND_MOVE_HISTORY_EVENT)
97
104
  .at(-1);
@@ -105,6 +112,9 @@ function cardLedger(card: TaskCard): BoardTaskLedger {
105
112
  lastStatus: card.status,
106
113
  lastEvent: last ? { at: last.at, event: last.event, detail: last.detail ?? null } : null,
107
114
  exitReason: ended ? reason ?? null : null,
115
+ ...(terminal ? { prTerminal: terminal } : {}),
116
+ ...(terminal && terminal.state === 'MERGED' && card.postLand?.pr === terminal.pr && card.postLand.head === terminal.head
117
+ ? { postLand: card.postLand } : {}),
108
118
  };
109
119
  }
110
120
 
@@ -17,6 +17,7 @@ import { appendRunLedgerEntry, type RunLedgerEntry } from '../self-implement/run
17
17
  import { cardPrNumber, taskAgentStatePath, updateTaskCard, readTaskCard, type TaskCard } from './task-hand.js';
18
18
  import { judgeNextMove, type TaskJudgeInput } from './judge.js';
19
19
  import { remoteParentAlive, type SshRunner } from './parent-host.js';
20
+ import { notifyRemoteFailure, remoteFailureReason } from './failure-notify.js';
20
21
 
21
22
  const GOAL_ID = /^[a-f0-9]{16}$/;
22
23
  const PR_URL = /^https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/pull\/(\d+)$/;
@@ -134,6 +135,8 @@ export interface CardRunEvidenceDeps {
134
135
  dispatchPod?: (runId: string, sinceIso: string) => boolean;
135
136
  runExit?: (runId: string) => RunExit | undefined;
136
137
  ssh?: SshRunner;
138
+ /** Isolated tests can put incident messages in their own mailbox; production uses the resolved default store. */
139
+ incidentStorePath?: string;
137
140
  }
138
141
 
139
142
  async function defaultLoadLedger(): Promise<(runId: string) => RunLedgerEntry[] | null> {
@@ -145,8 +148,7 @@ async function defaultLoadLedger(): Promise<(runId: string) => RunLedgerEntry[]
145
148
  // HARNESS-PARENT-ON-MSB1: 원격 부모 런의 원장 거울(`tasks show` 가 ssh 로 당긴다) — 이 우주 ⊕ 연합 «뒤»에만 붙는다.
146
149
  try {
147
150
  const { remoteLedgerMirrorDirs } = await import('./parent-host.js');
148
- const { prodInstanceRoot } = await import('../instance/resolve.js');
149
- dirs = [...new Set([...dirs, ...remoteLedgerMirrorDirs(prodInstanceRoot())])];
151
+ dirs = [...new Set([...dirs, ...remoteLedgerMirrorDirs(effectiveInstanceRoot())])];
150
152
  } catch { /* 거울이 없으면 종전 그대로 */ }
151
153
  return ledgerLoader(dirs, (runId, dir) => loadRunLedger(runId, dir), federationFailure);
152
154
  }
@@ -216,16 +218,46 @@ export async function readCardRunSnapshot(card: Pick<TaskCard, 'runId' | 'create
216
218
  */
217
219
  export async function refreshCardRunBinding(cardId: string, statePath = taskAgentStatePath(), deps: CardRunEvidenceDeps = {}): Promise<{ card: TaskCard | undefined; evidence?: CardRunEvidence }> {
218
220
  let card = readTaskCard(cardId, statePath);
219
- // 수동 근거(refSource manual)는 덮지 않는다 · 둘 다 차 있으면 다시 읽지 않는다(한 번만) —
220
- // 단 «10분 발사 근거 없음»으로 failed 된 카드는 PR·골이 먼저 묶였어도 되살림 판정을 계속 받는다.
221
+ // 수동 근거(refSource manual)는 아래 바인딩에서 덮지 않는다. 원격 부모의 확정 실패는
222
+ // 수동 근거가 있어도 읽어야 하므로 여기서 반환하지 않는다.
223
+ // 둘 다 차 있으면 다시 읽지 않는다(한 번만) — 단 «10분 발사 근거 없음»으로 failed 된 카드는
224
+ // PR·골이 먼저 묶였어도 되살림 판정을 계속 받는다.
221
225
  const awaitingRevival = card?.status === 'failed' && [...card.history].reverse().find((item) => item.event === 'failed')?.detail === LATE_LAUNCH_FAILURE;
222
- if (!card?.runId || card.refSource === 'manual' || (card.pr !== undefined && card.goalId !== undefined && !awaitingRevival)) return { card };
226
+ if (!card?.runId || (card.pr !== undefined && card.goalId !== undefined && !awaitingRevival)) return { card };
223
227
  const runId = card.runId;
228
+ // A stale HQ mirror cannot prove a terminal remote failure. Refresh it here as well as in `tasks show`,
229
+ // since mission advance and direct callers also reach this function.
230
+ if (card.parentHost && !deps.loadLedger) {
231
+ try {
232
+ const { mirrorRemoteRunLedgers } = await import('./parent-host.js');
233
+ mirrorRemoteRunLedgers(card.parentHost, runId, effectiveInstanceRoot(), deps.ssh);
234
+ } catch (error) {
235
+ try { debug.log('task-agent', 'card-bind-failed', { card: cardId, runId, reason: `remote ledger refresh failed: ${error instanceof Error ? error.message : String(error)}` }); } catch { /* fail-soft */ }
236
+ return { card };
237
+ }
238
+ }
224
239
  const snapshot = await readCardRunSnapshot(card, deps);
225
240
  if (snapshot.readErrors?.length) {
226
241
  try { debug.log('task-agent', 'card-bind-failed', { card: cardId, runId: card.runId, errors: snapshot.readErrors }); } catch { /* fail-soft */ }
227
242
  }
228
243
  const evidence = bindCardEvidence(card, snapshot);
244
+ const remoteReason = remoteFailureReason(card, snapshot, evidence, deps.ssh);
245
+ if (remoteReason && (card.status === 'launched' || card.status === 'failed')) {
246
+ if (card.status === 'launched') {
247
+ const at = (deps.now ?? (() => new Date()))().toISOString();
248
+ card = updateTaskCard(statePath, cardId, (current) => current?.status === 'launched' && current.runId === runId && !current.pr
249
+ ? { ...current, status: 'failed', history: [...current.history, { at, event: 'failed', detail: `failed/needs-owner — ${remoteReason}`, runId }] }
250
+ : undefined) ?? card;
251
+ }
252
+ // An older generic Pod/no-launch failure may have closed this same run before its supervisor tail arrived.
253
+ // The terminal evidence still warrants an incident; message provenance keeps retries at one row.
254
+ const lastFailure = [...card.history].reverse().find((row) => row.event === 'failed');
255
+ if (card.status === 'failed' && lastFailure?.runId === runId && !card.history.some((row) => row.event === 'closed-by-hand')) {
256
+ try { notifyRemoteFailure(card, remoteReason, deps.incidentStorePath); }
257
+ catch (error) { try { debug.log('task-agent', 'failure-notify-failed', { card: cardId, runId, reason: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ } }
258
+ return { card, evidence };
259
+ }
260
+ }
229
261
  let revived = false;
230
262
  const lastFailure = [...card.history].reverse().find((item) => item.event === 'failed');
231
263
  if (card.status === 'failed' && lastFailure?.event === 'failed' && lastFailure.detail === LATE_LAUNCH_FAILURE
@@ -8,7 +8,7 @@ export interface CoverRow {
8
8
  byTaskAgent: number | null;
9
9
  /** Total work by all seats; null when no independent denominator is available. */
10
10
  total: number | null;
11
- /** TASK-AGENT events observed for this verb, including non-done outcomes; null if unreadable. */
11
+ /** TASK-AGENT action events observed for this verb; null if unreadable. */
12
12
  observedActions: number | null;
13
13
  ratio: number | null;
14
14
  state: 'measured' | 'no-denominator' | 'unreadable';
@@ -23,6 +23,8 @@ export interface CoverRow {
23
23
  /** null means the source could not be read; [] means it was read and had no matching events. */
24
24
  export interface TaskAgentCoverInput {
25
25
  taskAgentActions: readonly { kind: NextActionKind; result: string; reason?: string; taskId?: string }[] | null;
26
+ /** Actual land executor outcomes; omitted or null means the source was not read. */
27
+ liveLandMoves?: readonly { card: string; executed: boolean; ok: boolean }[] | null;
26
28
  allLands: readonly { step: string; ok: boolean }[] | null;
27
29
  /** Card-ledger denominator for review·retry·green. undefined = not requested; null = ledger unreadable; [] = no cards. */
28
30
  taskCards?: readonly { id: string }[] | null;
@@ -32,7 +34,26 @@ export interface TaskAgentCoverInput {
32
34
  /** null means the pending-landing age source could not be read; [] means no pending landings. */
33
35
  pendingLandingAgeSeconds?: readonly number[] | null;
34
36
  /** A source reached the query safety limit; its aggregate is not known to be complete. */
35
- truncated?: { taskAgentActions: boolean; allLands: boolean };
37
+ truncated?: { taskAgentActions: boolean; allLands: boolean; liveLandMoves?: boolean; goalAsks?: boolean; goalRuns?: boolean; implementCardRuns?: boolean; implementRunMerges?: boolean };
38
+ /** Completed authoring provenance and terminal goal records. undefined = not wired; null = unreadable. */
39
+ goalAsks?: readonly { authorRunId: string; goalId: string }[] | null;
40
+ goalRuns?: readonly { goalId: string; outcome: string; stage?: string }[] | null;
41
+ /** 구현 출처 — TA 카드의 런 결합. undefined = 이 동사의 출처 배선이 없다(no-source); null = 카드 원장을 못 읽었다; [] = 읽고 카드 없음. */
42
+ implementCardRuns?: readonly ImplementCardRunBinding[] | null;
43
+ /** 구현 출처 — 런 원장의 merged 사건. undefined = 배선 없음; null = 런 원장을 못 읽음. */
44
+ implementRunMerges?: ReadonlyMap<string, ImplementRunMergeEvent> | null;
45
+ }
46
+
47
+ /** 구현 동사의 카드 결합 — runId·runChildId 는 그 카드가 발사한 런이다. */
48
+ export interface ImplementCardRunBinding {
49
+ cardId: string;
50
+ runId?: string | null;
51
+ runChildId?: string | null;
52
+ }
53
+
54
+ /** 구현 동사의 원장 merged 사건 — merged=true 만 완료 구현으로 센다. */
55
+ export interface ImplementRunMergeEvent {
56
+ merged: boolean;
36
57
  }
37
58
 
38
59
  export type LandingFailureReason = 'load' | 'oom' | 'time-limit' | 'conflict' | 'review' | 'other';
@@ -49,6 +70,24 @@ export interface LandingThroughput {
49
70
 
50
71
  const FAILURE_REASONS: readonly LandingFailureReason[] = ['load', 'oom', 'time-limit', 'conflict', 'review', 'other'];
51
72
 
73
+ function successfulLandCount(input: TaskAgentCoverInput): number | null {
74
+ if (input.taskAgentActions === null || input.truncated?.taskAgentActions
75
+ || input.liveLandMoves == null || input.truncated?.liveLandMoves) return null;
76
+ const cards = new Set<string>();
77
+ for (const action of input.taskAgentActions) {
78
+ if (action.kind !== 'land' || action.result !== 'done') continue;
79
+ // Without a card identity an action may duplicate a live-move; its count is unknown.
80
+ if (!action.taskId) return null;
81
+ cards.add(action.taskId);
82
+ }
83
+ for (const move of input.liveLandMoves) {
84
+ if (!move.executed || !move.ok) continue;
85
+ if (!move.card) return null;
86
+ cards.add(move.card);
87
+ }
88
+ return cards.size;
89
+ }
90
+
52
91
  function landingFailureReason(action: { kind: NextActionKind; reason?: string }): LandingFailureReason {
53
92
  const reason = action.reason ?? '';
54
93
  if (/\boom\b|out[- ]of[- ]memory|oomkilled|메모리.{0,12}(초과|부족|킬)/i.test(reason)) return 'oom';
@@ -62,9 +101,7 @@ function landingFailureReason(action: { kind: NextActionKind; reason?: string })
62
101
  /** Daily funnel, failure-reason distribution, and oldest pending-landing age. */
63
102
  export function measureLandingThroughput(input: TaskAgentCoverInput): LandingThroughput {
64
103
  const actionsReadable = input.taskAgentActions !== null && input.truncated?.taskAgentActions !== true;
65
- const taskAgentLandings = actionsReadable
66
- ? input.taskAgentActions!.filter((action) => action.kind === 'land' && action.result === 'done').length
67
- : null;
104
+ const taskAgentLandings = actionsReadable ? successfulLandCount(input) : null;
68
105
  const failureReasons = actionsReadable
69
106
  ? Object.fromEntries(FAILURE_REASONS.map((reason) => [reason, 0])) as Record<LandingFailureReason, number>
70
107
  : null;
@@ -109,7 +146,8 @@ export function measureTaskAgentCover(input: TaskAgentCoverInput): CoverRow[] {
109
146
  const rows = verbs.map((verb): CoverRow => {
110
147
  const actions = input.taskAgentActions?.filter((action) => action.kind === verb);
111
148
  const actionsUnreadable = actions === undefined || input.truncated?.taskAgentActions === true;
112
- const byTaskAgent = actionsUnreadable ? null : actions.filter((action) => action.result === 'done').length;
149
+ const byTaskAgent = verb === 'land' ? successfulLandCount(input)
150
+ : actionsUnreadable ? null : actions.filter((action) => action.result === 'done').length;
113
151
  const observedActions = actionsUnreadable ? null : actions.length;
114
152
  const liveActions = actionsUnreadable ? null : actions.filter((action) => action.result !== 'shadow').length;
115
153
  const shadowActions = actionsUnreadable ? null : actions.filter((action) => action.result === 'shadow').length;
@@ -134,7 +172,7 @@ export function measureTaskAgentCover(input: TaskAgentCoverInput): CoverRow[] {
134
172
  };
135
173
  const total = input.allLands === null || input.truncated?.allLands
136
174
  ? null : input.allLands.filter((land) => land.step === 'merge' && land.ok === true).length;
137
- const state = actionsUnreadable || total === null ? 'unreadable'
175
+ const state = byTaskAgent === null || total === null ? 'unreadable'
138
176
  : total === 0 ? 'no-denominator' : 'measured';
139
177
  const ratio = state === 'measured' && byTaskAgent !== null && total !== null ? byTaskAgent / total : null;
140
178
  return { verb, byTaskAgent, total, observedActions, ratio, state, ...counts,
@@ -161,23 +199,23 @@ export function formatTaskAgentCover(row: CoverRow): string {
161
199
  /** 7동사의 이름 — 리뷰·착지·수리·검증·저작·구현·정리 (decision·deliver 는 7동사 밖이라 세지 않는다). */
162
200
  export type SevenVerbName = '리뷰' | '착지' | '수리' | '검증' | '저작' | '구현' | '정리';
163
201
 
164
- /** no-source 는 출처 배선이 아직 없는 동사 · unreadable 은 TASK-AGENT 행동 원장을 못 읽은 것이다. */
202
+ /** no-source 는 출처 배선이 없는 동사 · unreadable 은 해당 동사의 원천을 완전히 못 읽은 것이다. */
165
203
  export type SevenVerbState = 'live' | 'shadow-only' | 'none' | 'no-source' | 'unreadable';
166
204
 
167
- /** 7동사 출처 표의 한 칸 — source 는 그 동사를 관측하는 TASK-AGENT action kind, null 이면 출처가 아직 없다. */
205
+ /** 7동사 출처 표의 한 칸 — source 는 그 동사를 관측하는 원천, null 이면 출처가 아직 없다. */
168
206
  export interface SevenVerbSource {
169
207
  verb: SevenVerbName;
170
- source: Exclude<NextActionKind, 'decision' | 'deliver'> | null;
208
+ source: Exclude<NextActionKind, 'decision' | 'deliver'> | 'goal-ask+goal-run' | 'card-run+merged' | null;
171
209
  }
172
210
 
173
- /** TA-THEME-VERBS-LIVE — 7동사와 출처 표(코드로 고정). 저작·구현·정리의 출처 배선은 다음 조각. */
211
+ /** TA-THEME-VERBS-LIVE — 저작은 완료 골 조인; 구현은 카드 런 결합 ⊕ 원장 merged 조인; 정리는 아직 출처가 없다. */
174
212
  export const SEVEN_VERB_SOURCES: readonly SevenVerbSource[] = [
175
213
  { verb: '리뷰', source: 'review' },
176
214
  { verb: '착지', source: 'land' },
177
215
  { verb: '수리', source: 'retry' },
178
216
  { verb: '검증', source: 'green' },
179
- { verb: '저작', source: null },
180
- { verb: '구현', source: null },
217
+ { verb: '저작', source: 'goal-ask+goal-run' },
218
+ { verb: '구현', source: 'card-run+merged' },
181
219
  { verb: '정리', source: null },
182
220
  ];
183
221
 
@@ -197,28 +235,76 @@ export interface SevenVerbLive {
197
235
  measuredVerbs: number;
198
236
  }
199
237
 
200
- /** live = 창 안에 result done 인 non-shadow 사건 ≥1. 원장이 null 이거나 절단되었으면 전부 unreadable. */
238
+ /** 행동 동사는 창 안의 done, 저작은 완료한 저작 원장과 착지 골의 조인, 구현은 카드 런 결합과 원장 merged 사건의 조인. 각 출처의 null·절단은 해당 동사만 unreadable. */
201
239
  export function measureSevenVerbLive(input: TaskAgentCoverInput): SevenVerbLive {
202
240
  const unreadable = input.taskAgentActions === null || input.truncated?.taskAgentActions === true;
203
241
  const verbs = SEVEN_VERB_SOURCES.map(({ verb, source }): SevenVerbLiveRow => {
204
- if (unreadable) return { verb, source, liveDone: null, shadow: null, state: 'unreadable' };
242
+ if (source === 'card-run+merged') {
243
+ // ⛔ 배선이 없으면 출처가 «있는» 동사로 거짓 측정을 하지 않는다 — no-source 로 그대로 둔다.
244
+ if (input.implementCardRuns === undefined && input.implementRunMerges === undefined) {
245
+ return { verb, source: null, liveDone: null, shadow: null, state: 'no-source' };
246
+ }
247
+ if (input.implementCardRuns == null || input.implementRunMerges == null
248
+ || input.truncated?.implementCardRuns === true || input.truncated?.implementRunMerges === true) {
249
+ return { verb, source, liveDone: null, shadow: null, state: 'unreadable' };
250
+ }
251
+ const merges = input.implementRunMerges;
252
+ const cards = new Set<string>();
253
+ for (const binding of input.implementCardRuns) {
254
+ // 카드 신원 없이는 중복 없는 분자를 낼 수 없다 — 그 원장은 못 읽은 것으로 한다.
255
+ if (!binding.cardId) return { verb, source, liveDone: null, shadow: null, state: 'unreadable' };
256
+ const bound = [binding.runId, binding.runChildId];
257
+ if (bound.some((runId) => typeof runId === 'string' && runId && merges.get(runId)?.merged === true)) {
258
+ cards.add(binding.cardId);
259
+ }
260
+ }
261
+ const liveDone = cards.size;
262
+ return { verb, source, liveDone, shadow: 0, state: liveDone >= 1 ? 'live' : 'none' };
263
+ }
205
264
  if (source === null) return { verb, source: null, liveDone: null, shadow: null, state: 'no-source' };
265
+ if (source !== 'goal-ask+goal-run' && (unreadable || (source === 'land'
266
+ && (input.liveLandMoves == null || input.truncated?.liveLandMoves)))) {
267
+ return { verb, source, liveDone: null, shadow: null, state: 'unreadable' };
268
+ }
269
+ if (source === 'goal-ask+goal-run') {
270
+ if (input.goalAsks == null || input.goalRuns == null || input.truncated?.goalAsks || input.truncated?.goalRuns) {
271
+ return { verb, source, liveDone: null, shadow: null, state: 'unreadable' };
272
+ }
273
+ const landed = new Set(input.goalRuns.filter((run) =>
274
+ run.stage === 'merged' && (run.outcome === 'completed' || run.outcome === 'merged')).map((run) => run.goalId));
275
+ const liveDone = new Set(input.goalAsks.filter((ask) => ask.authorRunId && landed.has(ask.goalId)).map((ask) => ask.goalId)).size;
276
+ return { verb, source, liveDone, shadow: 0, state: liveDone > 0 ? 'live' : 'none' };
277
+ }
206
278
  const actions = input.taskAgentActions!.filter((action) => action.kind === source);
207
- const liveDone = actions.filter((action) => action.result === 'done').length;
279
+ const liveDone = source === 'land' ? successfulLandCount(input) : actions.filter((action) => action.result === 'done').length;
280
+ if (liveDone === null) return { verb, source, liveDone: null, shadow: null, state: 'unreadable' };
208
281
  const shadow = actions.filter((action) => action.result === 'shadow').length;
209
282
  return { verb, source, liveDone, shadow, state: liveDone >= 1 ? 'live' : shadow >= 1 ? 'shadow-only' : 'none' };
210
283
  });
211
284
  return {
212
285
  verbs,
213
- liveVerbs: unreadable ? null : verbs.filter((row) => row.state === 'live').length,
214
- measuredVerbs: SEVEN_VERB_SOURCES.filter((row) => row.source !== null).length,
286
+ liveVerbs: verbs.some((row) => row.state === 'unreadable' && row.source !== null) ? null : verbs.filter((row) => row.state === 'live').length,
287
+ measuredVerbs: verbs.filter((row) => row.source !== null).length,
215
288
  };
216
289
  }
217
290
 
218
291
  /** 한 줄 요약 — 못 읽었으면 n 자리를 `-` 로 적는다(0 으로 읽히지 않게). */
219
292
  export function formatSevenVerbLive(measurement: SevenVerbLive): string {
220
293
  const total = SEVEN_VERB_SOURCES.length;
221
- if (measurement.liveVerbs === null) return `${total}동사 live -/${total} · 못 잼(행동 원장 null·절단)`;
294
+ if (measurement.liveVerbs === null) {
295
+ const actionUnreadable = measurement.verbs.some((row) => row.source === 'review' && row.state === 'unreadable');
296
+ const landUnreadable = measurement.verbs.some((row) => row.source === 'land' && row.state === 'unreadable');
297
+ const authorUnreadable = measurement.verbs.some((row) => row.source === 'goal-ask+goal-run' && row.state === 'unreadable');
298
+ const implementUnreadable = measurement.verbs.some((row) => row.source === 'card-run+merged' && row.state === 'unreadable');
299
+ const failures = [
300
+ actionUnreadable ? '행동' : landUnreadable ? '착지' : null,
301
+ authorUnreadable ? '저작·골' : null,
302
+ implementUnreadable ? '구현 카드·런' : null,
303
+ ].filter((part): part is string => part !== null);
304
+ const reason = `${failures.join('·') || '저작·골'} 원장 null·절단`;
305
+ const noSource = measurement.verbs.filter((row) => row.state === 'no-source').map((row) => row.verb).join('·');
306
+ return `${total}동사 live -/${total} · 못 잼(${reason})${noSource ? ` · 출처 없음 ${noSource}` : ''}`;
307
+ }
222
308
  const noSource = measurement.verbs.filter((row) => row.state === 'no-source').map((row) => row.verb).join('·');
223
309
  return `${total}동사 live ${measurement.liveVerbs}/${total}${noSource ? ` · 출처 없음 ${noSource}` : ''}`;
224
310
  }
@@ -0,0 +1,61 @@
1
+ import { defaultMsgStorePath, openMsgStore } from '../msg/msg-store.js';
2
+ import type { RunLedgerEntry } from '../self-implement/run-ledger.js';
3
+ import type { CardRunSnapshot, CardRunEvidence } from './card-evidence.js';
4
+ import { remoteParentAlive, type SshRunner } from './parent-host.js';
5
+ import type { TaskCard } from './task-hand.js';
6
+
7
+ const STOP_LINE = /\[supervisor\] 정지\((needs-human|no-progress)\)\s*[—-]\s*(.*)/u;
8
+
9
+ /** A remote parent's terminal stop is usable only after every referenced child ledger was read. */
10
+ export function remoteFailureReason(card: TaskCard, snapshot: CardRunSnapshot, evidence: CardRunEvidence, ssh?: SshRunner): string | undefined {
11
+ if (!card.parentHost || !card.runId || !snapshot.host || snapshot.readErrors?.length || card.pr || evidence.pr) return undefined;
12
+ const launchRows = snapshot.host.filter((row) => row.event === 'pod-child-run');
13
+ const childRefs = launchRows.map((row) => row.data?.childRunId);
14
+ // An unreadable or malformed child reference is unknown evidence, never "no children".
15
+ if (childRefs.some((id) => typeof id !== 'string' || !/^run-[A-Za-z0-9_-]{4,64}$/.test(id) || id === card.runId)) return undefined;
16
+ const children = childRefs as string[];
17
+ if (children.some((id) => !snapshot.children[id])) return undefined;
18
+ // The parent can stop while a child still runs. That child may publish a PR or salvage
19
+ // branch later, so every launched child must have a terminal last status first.
20
+ if (children.some((id) => {
21
+ const lastStatus = [...(snapshot.children[id] ?? [])].reverse().find((row) => row.event === 'run-status');
22
+ return !lastStatus || !['completed', 'failed', 'cancelled'].includes(String(lastStatus.data?.runStatus));
23
+ })) return undefined;
24
+ const rows: RunLedgerEntry[] = [...snapshot.host, ...children.flatMap((id) => snapshot.children[id] ?? [])];
25
+ if (rows.some((row) => row.event === 'pr-opened' || row.event === 'salvaged'
26
+ || (typeof row.data.harvestBranch === 'string' && row.data.harvestBranch.trim())
27
+ || (typeof row.data.salvageBranch === 'string' && row.data.salvageBranch.trim()))) return undefined;
28
+ const parentRows = snapshot.host.filter((row) => row.runId === card.runId);
29
+ const stopIndex = parentRows.map((row) => row.event).lastIndexOf('stop');
30
+ const stop = parentRows[stopIndex];
31
+ if (!stop) return undefined;
32
+ // A running status before stop is superseded by the supervisor's terminal stop.
33
+ // A fresh execution event after stop makes that stop stale for the current parent.
34
+ if (parentRows.slice(stopIndex + 1).some((row) => row.event === 'start' || row.event === 'pod-child-run'
35
+ || (row.event === 'run-status' && !['completed', 'failed', 'cancelled'].includes(String(row.data?.runStatus))))) return undefined;
36
+ const tail = Array.isArray(stop?.data.tail) ? stop.data.tail : [];
37
+ const lines = tail.filter((line): line is string => typeof line === 'string');
38
+ // A parent or terminal child stop can carry a PR or salvage branch only in raw text.
39
+ // Any nonempty branch token is harvest evidence, regardless of its naming prefix.
40
+ const stopText = rows.filter((row) => row.event === 'stop')
41
+ .flatMap((row) => [row.data.cause, ...(Array.isArray(row.data.tail) ? row.data.tail : [])])
42
+ .filter((value): value is string => typeof value === 'string').join('\n');
43
+ if (/https:\/\/github\.com\/[^\s]+\/pull\/\d+|ELANOUS_POD_SALVAGE(?:\s*=\s*|\s+)\S+|수확할 브랜치:\s*\S+/.test(stopText)) return undefined;
44
+ const matched = [...lines].reverse().map((line) => STOP_LINE.exec(line.replace(/\x1b\[[0-9;]*m/g, ''))).find((match) => match !== null);
45
+ if (!matched || remoteParentAlive({ ...card.parentHost, runId: card.runId }, ssh) !== 'dead') return undefined;
46
+ const raw = matched[2]?.replace(/[\r\n]+/g, ' ').trim();
47
+ if (!raw) return undefined;
48
+ return `${matched[1]} — ${raw}`;
49
+ }
50
+
51
+ /** SQLite provenance makes repeated refreshes insert precisely one message for this card/run/seat. */
52
+ export function notifyRemoteFailure(card: TaskCard, reason: string, messageStorePath = defaultMsgStorePath()): void {
53
+ if (!card.seat || !card.runId || !card.parentHost) return;
54
+ const log = card.parentHost.log ?? `${card.parentHost.configDir ?? '$HOME/.elanous'}/remote-parents/${card.runId}.log`;
55
+ const store = openMsgStore(messageStorePath);
56
+ try {
57
+ store.appendWithProvenance({ from: 'TC', to: card.seat, kind: 'incident',
58
+ body: `TA 카드 ${card.id} · 원격 부모 ${card.parentHost.host} 종결 · 사유: ${reason}\n원격 로그: ${log}` },
59
+ `ta-failure:${card.id}-${card.runId}:${card.seat}`);
60
+ } finally { store.close(); }
61
+ }