elanous 0.2.23 → 0.2.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (347) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/chunks/{272-b23d2e92161d11fd.js → 272-0eed0fb0aab716b6.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{3961-fbef064c213b2b18.js → 3961-aaae04ed1d32622f.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-d5341ac96203a983.js → page-3b9fc39362473ae7.js} +1 -1
  6. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f2b62c19ca68fef8.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/app/board/{page-55c631c032dcc47e.js → page-d7602d489edf5f37.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-ad3bd8084048bee4.js → page-24282bf9f90031a5.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/app/bots/page-8b7c5ab6b5bc8d64.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/ceo/{page-5c5d0e370f495ef6.js → page-4eb84d454f8c3415.js} +1 -1
  11. package/apps/pwa/out/_next/static/chunks/app/chat/{page-c325e21543cd48a8.js → page-b2e494bdad57fcb5.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/app/consult/{page-6168a7c43e6ef8f5.js → page-c0ab229673a8b0eb.js} +1 -1
  13. package/apps/pwa/out/_next/static/chunks/app/control/page-3e2b45f3c36dbac4.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/app/decisions/page-c22aa511b28743ad.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/app/design-check/page-26345242e4049f55.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/app/editor/{page-25bb425afdb27541.js → page-bf88c0f558d8ef54.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/app/exec/{page-32d72fdef1afe11a.js → page-52fec523e5b5d3ad.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/field/page-67723dfdcde92b31.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/app/inside/{page-2bd9ce9c020d02d4.js → page-22725e2570f1126b.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/intake/{page-a34096245e63b9f2.js → page-1891335950d68b70.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/layout-3267af1ebdc73cf5.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/app/live/{page-4cc66d22ca4ec502.js → page-c314b75c11234998.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/live-run/{page-83b14c729fcea837.js → page-9f9c85415f61782b.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/loops/page-c1d4276059085587.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/market/page-833aab587f26ad4c.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-ccdb83ed40c8d724.js → page-8526c17ae38e6a50.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/morning/{page-7ad3de8fa06fdea9.js → page-f998637a8402115c.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/not-found-b7bb53828b17b335.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/observatory/page-1b1850de0257e46d.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-786111f30b6f0969.js → page-6f23159fedb41f67.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-48fb31810693c357.js → page-1d809a87139dd024.js} +1 -1
  32. package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-4e17cce5e2ed0fee.js → page-da9e570234d481fd.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/outputs/{page-cb154acadb025414.js → page-b92caddb5c715f30.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/page-8ce3a38e210b810a.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-c2dd2b19c93f90ab.js → page-57ffeeb6019440fd.js} +1 -1
  36. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-abbb417bf514630b.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-d3c007c8a0ddd7ee.js → page-2c2b3f29cec1efa4.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-7837ce2aacb97b04.js → page-9369279f72fc7b51.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/app/settings/page-64861f14039499c3.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-ddc4c93efd8ab94b.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/app/setup/page-901a1391b81053bc.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/share/page-68d9bbaac9841a58.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-119be9a0444d2482.js → page-b5c0c996f329371e.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-fa806c018833e332.js → page-89881ccbcf728978.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/app/term/page-0e87a803b3d7165e.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/today/{page-d431fbdea6ad93dd.js → page-58d4e5cb30ef4871.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/app/trace/page-02ce4c3b5f39e4d2.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/vault/page-e620d1bb8eadcec2.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-847c3de6ca8a4416.js → page-acf868c64d77f950.js} +1 -1
  50. package/apps/pwa/out/_next/static/chunks/app/workflows/page-54fa605fa6dba457.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-74e69ba2c8e970d4.js → page-bc79f0833533fa84.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-70038f41ae7cf931.js → page-d5c4398eea03d8f3.js} +1 -1
  53. package/apps/pwa/out/_next/static/css/72becfd42807b50d.css +1 -0
  54. package/apps/pwa/out/approvals/index.html +1 -1
  55. package/apps/pwa/out/approvals/index.txt +14 -14
  56. package/apps/pwa/out/autopilot/index.html +1 -1
  57. package/apps/pwa/out/autopilot/index.txt +14 -14
  58. package/apps/pwa/out/board/index.html +1 -1
  59. package/apps/pwa/out/board/index.txt +14 -14
  60. package/apps/pwa/out/botlab/index.html +1 -1
  61. package/apps/pwa/out/botlab/index.txt +14 -14
  62. package/apps/pwa/out/bots/index.html +1 -1
  63. package/apps/pwa/out/bots/index.txt +14 -14
  64. package/apps/pwa/out/ceo/index.html +1 -1
  65. package/apps/pwa/out/ceo/index.txt +14 -14
  66. package/apps/pwa/out/chat/index.html +1 -1
  67. package/apps/pwa/out/chat/index.txt +14 -14
  68. package/apps/pwa/out/consult/index.html +1 -1
  69. package/apps/pwa/out/consult/index.txt +14 -14
  70. package/apps/pwa/out/control/index.html +1 -1
  71. package/apps/pwa/out/control/index.txt +14 -14
  72. package/apps/pwa/out/decisions/index.html +1 -1
  73. package/apps/pwa/out/decisions/index.txt +14 -14
  74. package/apps/pwa/out/design-check/index.html +1 -1
  75. package/apps/pwa/out/design-check/index.txt +14 -14
  76. package/apps/pwa/out/editor/index.html +1 -1
  77. package/apps/pwa/out/editor/index.txt +14 -14
  78. package/apps/pwa/out/exec/index.html +1 -1
  79. package/apps/pwa/out/exec/index.txt +14 -14
  80. package/apps/pwa/out/field/index.html +1 -1
  81. package/apps/pwa/out/field/index.txt +14 -14
  82. package/apps/pwa/out/healthz +1 -1
  83. package/apps/pwa/out/index.html +1 -1
  84. package/apps/pwa/out/index.txt +14 -14
  85. package/apps/pwa/out/inside/index.html +1 -1
  86. package/apps/pwa/out/inside/index.txt +14 -14
  87. package/apps/pwa/out/intake/index.html +1 -1
  88. package/apps/pwa/out/intake/index.txt +14 -14
  89. package/apps/pwa/out/live/index.html +1 -1
  90. package/apps/pwa/out/live/index.txt +14 -14
  91. package/apps/pwa/out/live-run/index.html +1 -1
  92. package/apps/pwa/out/live-run/index.txt +14 -14
  93. package/apps/pwa/out/loops/index.html +1 -1
  94. package/apps/pwa/out/loops/index.txt +14 -14
  95. package/apps/pwa/out/market/index.html +1 -1
  96. package/apps/pwa/out/market/index.txt +14 -14
  97. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  98. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  99. package/apps/pwa/out/morning/index.html +1 -1
  100. package/apps/pwa/out/morning/index.txt +14 -14
  101. package/apps/pwa/out/observatory/index.html +1 -1
  102. package/apps/pwa/out/observatory/index.txt +14 -14
  103. package/apps/pwa/out/ops/checklist/index.html +1 -1
  104. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  105. package/apps/pwa/out/ops/release/index.html +1 -1
  106. package/apps/pwa/out/ops/release/index.txt +14 -14
  107. package/apps/pwa/out/ops/seats/index.html +1 -1
  108. package/apps/pwa/out/ops/seats/index.txt +14 -14
  109. package/apps/pwa/out/outputs/index.html +1 -1
  110. package/apps/pwa/out/outputs/index.txt +14 -14
  111. package/apps/pwa/out/reflection/index.html +1 -1
  112. package/apps/pwa/out/reflection/index.txt +15 -15
  113. package/apps/pwa/out/scheduler/index.html +1 -1
  114. package/apps/pwa/out/scheduler/index.txt +14 -14
  115. package/apps/pwa/out/sessions/index.html +1 -1
  116. package/apps/pwa/out/sessions/index.txt +14 -14
  117. package/apps/pwa/out/settings/devices/index.html +1 -1
  118. package/apps/pwa/out/settings/devices/index.txt +14 -14
  119. package/apps/pwa/out/settings/index.html +1 -1
  120. package/apps/pwa/out/settings/index.txt +14 -14
  121. package/apps/pwa/out/setup/done/index.html +1 -1
  122. package/apps/pwa/out/setup/done/index.txt +14 -14
  123. package/apps/pwa/out/setup/index.html +1 -1
  124. package/apps/pwa/out/setup/index.txt +14 -14
  125. package/apps/pwa/out/share/index.html +1 -1
  126. package/apps/pwa/out/share/index.txt +14 -14
  127. package/apps/pwa/out/showroom/index.html +1 -1
  128. package/apps/pwa/out/showroom/index.txt +14 -14
  129. package/apps/pwa/out/tasks/index.html +1 -1
  130. package/apps/pwa/out/tasks/index.txt +14 -14
  131. package/apps/pwa/out/term/index.html +1 -1
  132. package/apps/pwa/out/term/index.txt +14 -14
  133. package/apps/pwa/out/today/index.html +1 -1
  134. package/apps/pwa/out/today/index.txt +14 -14
  135. package/apps/pwa/out/trace/index.html +1 -1
  136. package/apps/pwa/out/trace/index.txt +14 -14
  137. package/apps/pwa/out/vault/index.html +1 -1
  138. package/apps/pwa/out/vault/index.txt +14 -14
  139. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  140. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  141. package/apps/pwa/out/workflows/index.html +1 -1
  142. package/apps/pwa/out/workflows/index.txt +14 -14
  143. package/apps/pwa/out/workspace/index.html +1 -1
  144. package/apps/pwa/out/workspace/index.txt +14 -14
  145. package/apps/pwa/out/worktrees/index.html +1 -1
  146. package/apps/pwa/out/worktrees/index.txt +14 -14
  147. package/catalog/models/openrouter/deepseek-v4-pro-0813.yaml +39 -0
  148. package/catalog/models/openrouter/deepseek-v4.1-flash.yaml +38 -0
  149. package/catalog/models/openrouter/glm-5.3-flash.yaml +35 -0
  150. package/catalog/providers/openrouter.yaml +2 -0
  151. package/graphs/implement-loop.yaml +31 -0
  152. package/graphs/ops/harness-gc.yaml +17 -0
  153. package/graphs/ops/recipes.yaml +3 -0
  154. package/graphs/overlays/README.md +1 -1
  155. package/graphs/overlays/stop-heal-ladder.yaml +105 -0
  156. package/package.json +1 -1
  157. package/scripts/add-api-key.sh +1 -1
  158. package/scripts/ci-typecheck-changed.ts +71 -14
  159. package/scripts/fleet/bedrock-pod-secret.ts +122 -0
  160. package/scripts/fleet/node-setup-linux.sh +97 -0
  161. package/scripts/fleet/node-setup.sh +11 -0
  162. package/scripts/fleet/zai-pod-secret.ts +86 -0
  163. package/scripts/land-global-checks.ts +3 -0
  164. package/scripts/measure-log-diet-2.ts +213 -0
  165. package/scripts/measure-tsc-affected-roots.ts +289 -0
  166. package/scripts/release-loop/gate-executor.ts +28 -0
  167. package/scripts/release-loop/gate-node.ts +262 -26
  168. package/scripts/release-loop/resume-release.ts +9 -3
  169. package/scripts/release-loop/resume.ts +41 -0
  170. package/scripts/release-loop/unattended-release.ts +22 -7
  171. package/scripts/tsc-baseline-cache.ts +147 -0
  172. package/scripts/usage-rollup.ts +58 -6
  173. package/src/agent/run-context.ts +10 -0
  174. package/src/agent-mission/driver.ts +18 -3
  175. package/src/agent-mission/resource-ladder.ts +8 -3
  176. package/src/agent-mission/review-loop.ts +8 -2
  177. package/src/agent-substrate/acp-implementer.ts +139 -0
  178. package/src/agent-substrate/pr-reviewer.ts +31 -4
  179. package/src/agent-substrate/self-review-cli.ts +48 -9
  180. package/src/autopilot/mission-critique.ts +18 -5
  181. package/src/autopilot/mission-feasibility-negotiate.ts +11 -10
  182. package/src/autopilot/mission-trace.ts +12 -1
  183. package/src/autopilot/system-lookback-run.ts +16 -10
  184. package/src/bench/bench.ts +183 -0
  185. package/src/budget/fetchers/grok.ts +15 -5
  186. package/src/budget/llm-cost.ts +37 -5
  187. package/src/budget/types.ts +8 -2
  188. package/src/budget/unified-usage.ts +88 -6
  189. package/src/cli/bench-cli.ts +34 -0
  190. package/src/cli/guardian-cli.ts +26 -1
  191. package/src/cli/image-cli.ts +101 -0
  192. package/src/cli/log-event-names.ts +4 -0
  193. package/src/cli/pr-cli.ts +62 -5
  194. package/src/cli/pr-land-overlap-evidence.ts +160 -12
  195. package/src/cli/release-cli.ts +18 -9
  196. package/src/cli/rfc-status.ts +52 -2
  197. package/src/cli/self-update.ts +104 -0
  198. package/src/cli/tasks-cli.ts +109 -4
  199. package/src/cli/usage-cli.ts +91 -8
  200. package/src/core-turn/run-goal-loop.ts +5 -4
  201. package/src/dashboard/ad-run-setup-bindings.ts +279 -0
  202. package/src/dashboard/index.ts +18 -276
  203. package/src/dashboard/stale-install-check.ts +11 -0
  204. package/src/debug/log-diet.ts +38 -7
  205. package/src/debug/log.ts +19 -1
  206. package/src/domains/release-tool.ts +39 -0
  207. package/src/error-report/report.ts +21 -4
  208. package/src/github/pr-labels.ts +1 -0
  209. package/src/graph-runner/graph-cli.ts +42 -1
  210. package/src/graph-runner/runner.ts +167 -6
  211. package/src/grok/usage.ts +23 -2
  212. package/src/grounding/ref-index.ts +253 -0
  213. package/src/harness/dispatch-backpressure.ts +10 -2
  214. package/src/harness/harness-clean.ts +199 -78
  215. package/src/harness/harness-cli-command.ts +122 -7
  216. package/src/harness/harness-gc.ts +331 -0
  217. package/src/harness/harness-stop.ts +4 -2
  218. package/src/harness/harness-substrate-default.ts +142 -7
  219. package/src/harness/launch-registry.ts +6 -0
  220. package/src/harness/worktree-branch-prefix.ts +9 -0
  221. package/src/hq/fence.ts +187 -0
  222. package/src/hq/seats.ts +38 -22
  223. package/src/images/image-ledger.ts +179 -0
  224. package/src/index.ts +93 -11
  225. package/src/instance/child-scope.ts +32 -1
  226. package/src/instance/dev-project.ts +133 -0
  227. package/src/instance/resolve.ts +24 -0
  228. package/src/instance/spawn-elanous.ts +45 -0
  229. package/src/intake-plane/ref-prefetch.ts +110 -0
  230. package/src/intelligence-map/model-catalog.ts +72 -0
  231. package/src/llm/bedrock-eventstream.ts +215 -0
  232. package/src/llm/bedrock-provider.ts +114 -41
  233. package/src/llm/bedrock.ts +174 -28
  234. package/src/llm/openrouter-byok-cost.ts +96 -0
  235. package/src/llm/openrouter-deepseek.ts +151 -0
  236. package/src/llm/openrouter-upstream-pins.config.ts +25 -0
  237. package/src/llm/zai-coding-plan.ts +700 -0
  238. package/src/llm.ts +87 -16
  239. package/src/loops/checker.ts +148 -9
  240. package/src/loops/orchestrator/resync-shadow.ts +34 -7
  241. package/src/maturity/feature-maturity.ts +2 -2
  242. package/src/model-tier/llm-tier-map.ts +9 -5
  243. package/src/models/prompts.ts +2 -1
  244. package/src/mss/logging/log-store.ts +58 -11
  245. package/src/nexus/api/missions.ts +73 -2
  246. package/src/nexus/api/ops-api.ts +18 -0
  247. package/src/nexus/index.ts +7 -75
  248. package/src/nexus/tools-kind.ts +78 -0
  249. package/src/oauth/codex-account-rotation.ts +39 -2
  250. package/src/oauth/codex-account-store.ts +323 -24
  251. package/src/oauth/fallback-chain.ts +53 -15
  252. package/src/onboarding/first-run.ts +37 -18
  253. package/src/onboarding/web-first.ts +20 -2
  254. package/src/ops/machine-heartbeat.ts +185 -0
  255. package/src/ops/overview-cache.ts +175 -0
  256. package/src/ops/overview.ts +8 -0
  257. package/src/prompt-cache/openai.ts +3 -0
  258. package/src/prompt-cache/types.ts +5 -0
  259. package/src/prompt-enhance/enhance.ts +23 -0
  260. package/src/release-loop/placement.ts +16 -1
  261. package/src/requirements/requirement-ledger.ts +536 -0
  262. package/src/retro/daily.ts +5 -5
  263. package/src/seat-loop/seat-loop.ts +32 -10
  264. package/src/self-dev/dev-cli.ts +44 -10
  265. package/src/self-dev/dev-pipeline.ts +7 -2
  266. package/src/self-dev/draft-residue.ts +14 -2
  267. package/src/self-dev/draft-sweep.ts +103 -0
  268. package/src/self-dev/graph-journey-route.ts +6 -0
  269. package/src/self-dev/orchestrate-cli.ts +25 -3
  270. package/src/self-dev/orchestrate.ts +41 -6
  271. package/src/self-dev/run-supervisor.ts +188 -15
  272. package/src/self-dev/run-types.ts +13 -0
  273. package/src/self-dev/supervisor-verdict-edges.ts +1 -0
  274. package/src/self-implement/budget-gate.ts +53 -13
  275. package/src/self-implement/child-context-composition.ts +139 -0
  276. package/src/self-implement/child-llm-preference.ts +13 -5
  277. package/src/self-implement/claude-acp-implement.ts +67 -0
  278. package/src/self-implement/flex-signals.ts +59 -0
  279. package/src/self-implement/frozen-merges.ts +8 -3
  280. package/src/self-implement/goal-author-decompose.ts +10 -2
  281. package/src/self-implement/goal-author.ts +24 -9
  282. package/src/self-implement/graph-overlay-condition.ts +28 -0
  283. package/src/self-implement/graph-overlay-yaml.ts +41 -7
  284. package/src/self-implement/graph-templates.ts +33 -1
  285. package/src/self-implement/graph-yaml.ts +56 -0
  286. package/src/self-implement/heal-intake.ts +5 -1
  287. package/src/self-implement/orchestrator.ts +144 -15
  288. package/src/self-implement/running-runs.ts +75 -5
  289. package/src/self-implement/seams.ts +47 -1
  290. package/src/self-implement/self-implement-cli.ts +9 -4
  291. package/src/self-implement/stop-autoheal.ts +69 -2
  292. package/src/session-runtime/retry-policy.ts +2 -1
  293. package/src/steward/launch.ts +5 -4
  294. package/src/steward/triage-budget.ts +62 -0
  295. package/src/steward/triage.ts +83 -22
  296. package/src/task-agent/actions.ts +62 -5
  297. package/src/task-agent/completion-evidence.ts +5 -0
  298. package/src/task-agent/cover.ts +101 -3
  299. package/src/task-agent/judge.ts +16 -0
  300. package/src/task-agent/land-order-ledger.ts +95 -0
  301. package/src/task-agent/live-move-history.ts +87 -1
  302. package/src/task-agent/live-moves.ts +350 -20
  303. package/src/task-agent/rejudge-on-head.ts +75 -20
  304. package/src/task-agent/review-prior-fix.ts +146 -0
  305. package/src/task-agent/run-adopt.ts +136 -0
  306. package/src/task-agent/shadow.ts +53 -9
  307. package/src/task-agent/task-hand.ts +54 -7
  308. package/src/task-orchestrator/surfaces/pod-account-broker.ts +38 -5
  309. package/src/task-orchestrator/surfaces/pod-bedrock-child.ts +74 -0
  310. package/src/task-orchestrator/surfaces/pod-child-default.ts +50 -0
  311. package/src/task-orchestrator/surfaces/pod-child-providers.ts +14 -1
  312. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +79 -6
  313. package/src/task-orchestrator/surfaces/pod-lease.ts +17 -5
  314. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
  315. package/src/task-orchestrator/surfaces/pod-pool.ts +20 -4
  316. package/src/task-orchestrator/surfaces/pod-zai-child.ts +101 -0
  317. package/src/task-orchestrator/surfaces/self-implement-pod.ts +213 -23
  318. package/src/task-orchestrator/surfaces/self-implement.ts +19 -1
  319. package/src/typecheck-ratchet.ts +1 -0
  320. package/src/user-config.ts +51 -11
  321. package/src/version/packed-revision.json +1 -1
  322. package/src/workflow-runtime/triggers/registry.ts +59 -0
  323. package/src/workflow-runtime/triggers/webhook-source.ts +35 -5
  324. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-0160ab83c8685a12.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/app/bots/page-e6784759a2531031.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/control/page-31417d3604154a6d.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/decisions/page-e9606dc0bdec9305.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/design-check/page-a648bc464c9dc754.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/field/page-d27b794c8ba76bbb.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/layout-22170f4f00a15496.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/loops/page-6e2781c523dad761.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/market/page-26dd415c8f13f341.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/not-found-015c02c4fec4023e.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/observatory/page-6b0ae5b5c42f6d2f.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/page-16258e956ea93fc2.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f35dd1dd96ae87.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/settings/page-7350ee93f4fe1033.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-00723458575f2b19.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/setup/page-745d9b6c015076f8.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/share/page-736d33c7414a8a1a.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/term/page-c782041b5f5199ba.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/trace/page-0c056abff82b9891.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/vault/page-242d668e4fbf72c4.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/workflows/page-81dd0d8ae923ed72.js +0 -1
  345. package/apps/pwa/out/_next/static/css/5619d59e87eddf66.css +0 -1
  346. /package/apps/pwa/out/_next/static/{rfkMm4movqkqMebBrCXiR → Q-7iofoxOEv1WX18S35of}/_buildManifest.js +0 -0
  347. /package/apps/pwa/out/_next/static/{rfkMm4movqkqMebBrCXiR → Q-7iofoxOEv1WX18S35of}/_ssgManifest.js +0 -0
@@ -9,9 +9,10 @@ import { readNexusRuntime } from '../nexus/runtime.js';
9
9
  import { checklistDevVersion } from '../release-loop/checklist.js';
10
10
  import { cardsBoard, countBoardTasks, INTERNAL_PACK_PROJECT, mergeBoards, releasePackBoard, type BoardNode } from '../task-agent/board.js';
11
11
  import type { Task } from '../task-orchestrator/types.js';
12
- import { cardPrNumber, COMPLETION_KINDS, handTask, LIVE_MOVE_HISTORY_EVENT, LIVE_MOVE_RESULT_EVENT, nextMoveFor, resolveHandText, readTaskAgentState, readTaskCard, taskAgentStatePath as taskStatePathDefault, shellQuote, TA_LAND_ENV, TASK_CARD_PREFIX, TASK_SEATS, type CompletionKind, type TaskCard, type TaskLaunchContext, type TaskLaunchReceipt, type TaskLauncher, type TaskSeat } from '../task-agent/task-hand.js';
12
+ import { cardPrNumber, CLOSED_BY_HAND_EVENT, closeTaskCard, COMPLETION_KINDS, handTask, updateTaskCard, LIVE_MOVE_HISTORY_EVENT, LIVE_MOVE_RESULT_EVENT, nextMoveFor, resolveHandText, readTaskAgentState, readTaskCard, taskAgentStatePath as taskStatePathDefault, shellQuote, TA_LAND_ENV, TASK_CARD_PREFIX, TASK_SEATS, type CompletionKind, type TaskCard, type TaskLaunchContext, type TaskLaunchReceipt, type TaskLauncher, type TaskSeat } from '../task-agent/task-hand.js';
13
13
  import { readCardSalvageBranch, refreshCardRunBinding, type CardRunEvidenceDeps } from '../task-agent/card-evidence.js';
14
14
  import { captureReviewResults } from '../task-agent/review-result-capture.js';
15
+ import { HAND_MOVE_KINDS, recordHandMove } from '../task-agent/live-move-history.js';
15
16
  import { advanceMission, handMission, MissionAdvanceError, openMissionIds, recordPieceRef, type AdvanceMissionResult, type MissionDecompose, type PieceEvidenceReader } from '../task-agent/mission.js';
16
17
  import { formatTaskAgentCover, measureTaskAgentCover, type CoverRow, type TaskAgentCoverInput } from '../task-agent/cover.js';
17
18
  import { LogStore, STORE_SAFETY_MAX, type LogQuery, type LogStoreRow } from '../mss/logging/log-store.js';
@@ -44,9 +45,13 @@ export interface TasksCliDeps {
44
45
  remoteLedgerMirror?: (card: TaskCard) => void;
45
46
  /** `tasks parents` 의 낡은 부모 보고 의존(시험 주입). */
46
47
  staleParents?: import('../task-agent/parent-host.js').StaleParentDeps;
48
+ /** `tasks adopt-runs` 의 백필 의존(시험 주입 · 등록부·상태 파일). */
49
+ adoptRuns?: import('../task-agent/run-adopt.js').RunAdoptDeps;
47
50
  /** `tasks cover` logs 조회 seam. 기본은 등록된 모든 우주(운영 ⊕ test)의 logs.db 를 read-only 로 연다 —
48
51
  * pr land 는 작업 트리마다 파생 test 우주에 기록되므로 현재 우주만 읽으면 분모가 늘 0 이다. */
49
52
  coverLogs?: (query: LogQuery) => LogStoreRow[] | CoverLogRead;
53
+ /** `tasks cover --by-card` 카드 원장 읽기 seam(읽기 전용). 플래그가 있을 때만 부른다. 기본 = TA 카드 원장(task-agent-actions.json). null = 못 읽음. */
54
+ coverCards?: () => CoverCardRow[] | null | Promise<CoverCardRow[] | null>;
50
55
  /** TA-REJUDGE-ON-HEAD — `tasks rejudge` 의 판단 의존(시험 주입 · 기본 = 설정 liveMoves ⊕ 실물 gh·리뷰·재게이트·land). */
51
56
  rejudge?: Partial<import('../task-agent/rejudge-on-head.js').RejudgeDeps>;
52
57
  /** TA-REJUDGE-ON-HEAD — `tasks regate-run` 의 재게이트(시험 주입 · 기본 `runHostRegate`). */
@@ -469,9 +474,31 @@ function coverEvents<T>(rows: readonly LogStoreRow[], category: string, event: s
469
474
  return events;
470
475
  }
471
476
 
477
+ export interface CoverCardRow { id: string; createdAt: string }
478
+
479
+ /**
480
+ * 분모 = TA 카드 원장(`task-agent-actions.json` · `ta-…` id) — 분자 `task-agent`/`action` 로그의 taskId 가 이 id 다.
481
+ * (task-cards CardStore 의 UUID 카드는 taskId 와 겹치지 않아 언제나 0/N 을 «잰 값처럼» 그린다 — 리뷰 must-fix.)
482
+ */
483
+ function readCoverCards(statePath: string): CoverCardRow[] {
484
+ const tasks = readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(statePath).tasks ?? {};
485
+ return Object.values(tasks).filter((card) => typeof card?.id === 'string' && typeof card.createdAt === 'string')
486
+ .map(({ id, createdAt }) => ({ id, createdAt }));
487
+ }
488
+
489
+ /** Cards whose createdAt falls in the window; an unparsable createdAt is not placed in any window. */
490
+ export function coverCardsInWindow(cards: readonly CoverCardRow[] | null, window: { sinceMs?: number; untilMs?: number }): { id: string }[] | null {
491
+ if (cards === null) return null;
492
+ return cards.filter(({ createdAt }) => {
493
+ const time = Date.parse(createdAt);
494
+ return Number.isFinite(time) && (window.sinceMs === undefined || time >= window.sinceMs) && (window.untilMs === undefined || time <= window.untilMs);
495
+ }).map(({ id }) => ({ id }));
496
+ }
497
+
472
498
  export function readTaskAgentCover(
473
499
  window: { sinceMs?: number; untilMs?: number } = {},
474
500
  query: NonNullable<TasksCliDeps['coverLogs']> = queryCoverLogs,
501
+ taskCards?: readonly { id: string }[] | null,
475
502
  ): { rows: CoverRow[]; limitReached: boolean; unreadableStores: number } {
476
503
  let unreadableStores = 0;
477
504
  const read = (category: 'task-agent' | 'pr.land', event: 'action' | 'step'): { rows: LogStoreRow[]; limitReached: boolean } | null => {
@@ -490,10 +517,11 @@ export function readTaskAgentCover(
490
517
  const input: TaskAgentCoverInput = {
491
518
  taskAgentActions: actions === null ? null : coverEvents(actions, 'task-agent', 'action', (data) =>
492
519
  typeof data.kind === 'string' && typeof data.result === 'string'
493
- ? { kind: data.kind as NonNullable<TaskAgentCoverInput['taskAgentActions']>[number]['kind'], result: data.result } : null),
520
+ ? { kind: data.kind as NonNullable<TaskAgentCoverInput['taskAgentActions']>[number]['kind'], result: data.result, ...(typeof data.taskId === 'string' ? { taskId: data.taskId } : {}) } : null),
494
521
  allLands: lands === null ? null : coverEvents(lands, 'pr.land', 'step', (data) =>
495
522
  typeof data.step === 'string' && typeof data.ok === 'boolean' ? { step: data.step, ok: data.ok } : null),
496
523
  truncated,
524
+ ...(taskCards === undefined ? {} : { taskCards }),
497
525
  };
498
526
  return { rows: measureTaskAgentCover(input), limitReached: truncated.taskAgentActions || truncated.allLands, unreadableStores };
499
527
  }
@@ -516,7 +544,8 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
516
544
  .option('--until <iso>', '끝 시각 (ISO)')
517
545
  .option('--json', 'JSON 출력')
518
546
  .option('--lines', '동사별 한 줄 요약 출력 (읽기 전용)')
519
- .action((opts: { since?: string; until?: string; json?: boolean; lines?: boolean }) => {
547
+ .option('--by-card', 'review·retry·green 분모를 카드 원장(창 안 createdAt 카드 수)으로 센다 (읽기 전용)')
548
+ .action(async (opts: { since?: string; until?: string; json?: boolean; lines?: boolean; byCard?: boolean }) => {
520
549
  const parseIso = (value: string | undefined, flag: string): number | undefined => {
521
550
  if (value === undefined) return undefined;
522
551
  const time = Date.parse(value);
@@ -527,7 +556,16 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
527
556
  const sinceMs = parseIso(opts.since, '--since');
528
557
  const untilMs = parseIso(opts.until, '--until');
529
558
  if (sinceMs !== undefined && untilMs !== undefined && sinceMs > untilMs) throw new Error('--since 가 --until 보다 늦다');
530
- const { rows, limitReached, unreadableStores } = readTaskAgentCover({ sinceMs, untilMs }, deps.coverLogs);
559
+ let taskCards: { id: string }[] | null | undefined;
560
+ if (opts.byCard) {
561
+ let cards: CoverCardRow[] | null;
562
+ try { cards = await (deps.coverCards ?? (() => readCoverCards(deps.taskStatePath ?? taskStatePathDefault())))(); } catch (error) {
563
+ cards = null;
564
+ try { debug.log('task-agent.cover', 'by-card-unreadable', { error: String(error).slice(-200) }, { level: 'warn' }); } catch { /* fail-soft */ }
565
+ }
566
+ taskCards = coverCardsInWindow(cards, { sinceMs, untilMs });
567
+ }
568
+ const { rows, limitReached, unreadableStores } = readTaskAgentCover({ sinceMs, untilMs }, deps.coverLogs, taskCards);
531
569
  const limitWarning = limitReached ? `조회 상한 ${COVER_QUERY_LIMIT} 도달 — 더 오래된 사건이 있을 수 있다(절단된 수를 전체로 읽지 말 것)` : null;
532
570
  const storeWarning = unreadableStores > 0 ? `logs.db ${unreadableStores}개를 못 읽었다 — 그 우주의 사건은 수에 없다(«없다»로 읽지 말 것)` : null;
533
571
  if (opts.json) out(JSON.stringify({ rows, limitReached, unreadableStores, ...(limitWarning ? { warning: limitWarning } : {}), ...(storeWarning ? { storeWarning } : {}) }));
@@ -677,6 +715,61 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
677
715
  }
678
716
  });
679
717
 
718
+ tasks.command('hand-move <id>')
719
+ .description('HAND-MOVE-LEDGER — 자리가 손으로 마무리한 수(기존 실패 면제 · diff 재적용 · 범위 자르기 · 쪼개기 · 다른 모델 의견)를 그 카드 history 에 한 줄로 남긴다 — live-move 와 같은 원장 칸 · 기존 줄은 건드리지 않는다')
720
+ .requiredOption('--kind <kind>', `손으로 쓴 수 (${HAND_MOVE_KINDS.join('|')})`)
721
+ .requiredOption('--by <seat>', '남긴 자리(예: OP·MK·TC·UX)')
722
+ .requiredOption('--detail <text>', '무엇을 했나 — 한 줄')
723
+ .option('--pr <number>', '관련 PR 번호')
724
+ .action(async (id: string, opts: { kind: string; by: string; detail: string; pr?: string }) => {
725
+ await registerTaskAgentSink(deps.registerSink);
726
+ const pr = opts.pr === undefined ? undefined : Number(opts.pr);
727
+ if (pr !== undefined && (!Number.isInteger(pr) || pr <= 0)) { out(`--pr 은 자연수다: ${opts.pr}`); process.exitCode = 1; return; }
728
+ const outcome = recordHandMove(deps.taskStatePath ?? taskStatePathDefault(), id,
729
+ { kind: opts.kind, by: opts.by, detail: opts.detail, ...(pr !== undefined ? { pr } : {}) });
730
+ try {
731
+ debug.log('task-agent', 'hand-move', outcome.written
732
+ ? { card: id, kind: outcome.entry.kind, by: outcome.entry.by, ...(outcome.entry.pr !== undefined ? { pr: outcome.entry.pr } : {}) }
733
+ : { card: id, kind: opts.kind, by: opts.by, reason: outcome.reason });
734
+ } catch { /* fail-soft */ }
735
+ if (!outcome.written) { out(`hand-move 한 줄을 못 남겼다 — ${outcome.reason}`); process.exitCode = 1; return; }
736
+ out([id, 'hand-move', outcome.entry.kind, outcome.entry.by, outcome.entry.detail, ...(outcome.entry.pr !== undefined ? [`pr ${outcome.entry.pr}`] : [])].join('\t'));
737
+ });
738
+
739
+ tasks.command('close <cardId>')
740
+ .description('과제 카드를 손으로 닫는다 — 상태를 failed 로 두고 사유를 history(closed-by-hand)에 한 줄 남긴다 · 없는 카드 id 는 거부(exit 1)')
741
+ .requiredOption('--reason <사유>', '닫는 사유(비면 거부)')
742
+ .option('--by <seat>', '닫은 자리(예: OP·MK·TC·UX)', 'operator')
743
+ .option('--json', 'JSON 출력')
744
+ .action(async (cardId: string, opts: { reason: string; by: string; json?: boolean }) => {
745
+ await registerTaskAgentSink(deps.registerSink);
746
+ const statePath = deps.taskStatePath ?? taskStatePathDefault();
747
+ try {
748
+ let found: TaskCard | undefined;
749
+ let changed = false;
750
+ const updated = updateTaskCard(statePath, cardId, (current) => {
751
+ if (!current) return undefined;
752
+ found = current;
753
+ const next = closeTaskCard(current, { reason: opts.reason, by: opts.by, at: new Date().toISOString() });
754
+ if (next === current) return undefined; // 이미 손으로 닫힘 — 다시 쓰지 않는다(멱등)
755
+ changed = true;
756
+ return next;
757
+ });
758
+ if (!found) { out(`과제 카드 없음: ${cardId}`); process.exitCode = 1; return; }
759
+ const card: TaskCard = updated ?? found;
760
+ const from = (found as TaskCard).status;
761
+ // 이미 닫힌 카드면 «저장된» 사유를 낸다 — 새로 준 사유는 원장에 없다.
762
+ const stored = [...card.history].reverse().find((item) => item.event === CLOSED_BY_HAND_EVENT);
763
+ const reason = changed ? opts.reason.trim() : (stored?.reason ?? stored?.detail ?? opts.reason.trim());
764
+ if (changed) { try { debug.log('task-agent', 'card-closed-by-hand', { id: cardId, from, by: opts.by, reason }); } catch { /* fail-soft */ } }
765
+ if (opts.json) out(JSON.stringify({ id: card.id, from, to: card.status, reason, ...(changed ? {} : { already: true }) }));
766
+ else out([card.id, card.status, changed ? 'closed-by-hand' : 'already-closed-by-hand', changed ? opts.by : (stored?.by ?? '-'), reason].join('\t'));
767
+ } catch (error) {
768
+ out(error instanceof Error ? error.message : '과제 카드 닫기 실패');
769
+ process.exitCode = 1;
770
+ }
771
+ });
772
+
680
773
  tasks.command('parents')
681
774
  .description('낡은 하니스 부모 보고(읽기 전용 · 죽이지 않는다) — 나이 ≥ N 시간이고 런 원장이 끝났거나(run-status) Pod 이 없는 `harness say|ask` 부모')
682
775
  .option('--older-than <hours>', '나이 하한(시간)', '6')
@@ -703,6 +796,18 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
703
796
  for (const row of report.rows) out([row.pid, row.ageHours, row.seat ?? '-', row.version ?? '-', row.runId ?? '-', row.reasons.join(',')].join('\t'));
704
797
  });
705
798
 
799
+ tasks.command('adopt-runs')
800
+ .description('TA-OWN-ALL-RUNS 백필 — 발사 등록부의 이 우주 런 중 카드 없는 것에 `ta-<runId>` 카드를 만든다(발사 훅 이전 런 몫 · 있으면 건드리지 않는다)')
801
+ .option('--json', 'JSON 출력')
802
+ .action(async (opts: { json?: boolean }) => {
803
+ const { adoptUncardedRuns } = await import('../task-agent/run-adopt.js');
804
+ const report = adoptUncardedRuns({ ...(deps.taskStatePath ? { statePath: deps.taskStatePath } : {}), ...deps.adoptRuns });
805
+ try { debug.log('task-agent', 'run-adopt-backfill', report); } catch { /* fail-soft */ }
806
+ if (opts.json) { out(JSON.stringify(report)); return; }
807
+ const skipped = Object.entries(report.skipped).sort(([a], [b]) => a.localeCompare(b)).map(([reason, n]) => `${reason} ${n}`).join(' · ') || '없음';
808
+ out(`등록부 런 ${report.scanned} · 카드 만듦 ${report.adopted} · 건너뜀 ${skipped}`);
809
+ });
810
+
706
811
  tasks.command('board')
707
812
  .description('보드 — 프로젝트 › 목표 › 이정표 › 과제. 과제 카드(project·goal·milestone)와 내부 팩(릴리스 체크리스트 = elanous › 판 › 칸)을 합쳐 읽는다(쓰지 않는다)')
708
813
  .option('--project <id>', '이 프로젝트만')
@@ -60,15 +60,87 @@ function readRunLogs(query: LogQuery): LogStoreRow[] {
60
60
  }
61
61
  }
62
62
 
63
- function usageData(row: LogStoreRow): RunUsageInput | null {
63
+ type UsageAxis = 'stage' | 'model' | 'account';
64
+ type ExtendedRunUsageInput = RunUsageInput & { stage?: string | null; account?: string | null };
65
+ type AxisUsageRow = {
66
+ stage?: string;
67
+ model?: string;
68
+ account?: string;
69
+ hostIds: string[];
70
+ calls: number;
71
+ inputTokens: number;
72
+ outputTokens: number;
73
+ cacheReadInputTokens: number;
74
+ cacheCreationInputTokens: number;
75
+ reasoningOutputTokens: number;
76
+ usdKnown: number;
77
+ unknownCostCalls: number;
78
+ includedCalls: number;
79
+ apiEquivalentUsd: number;
80
+ };
81
+
82
+ function usageData(row: LogStoreRow, unwrapPod = false): ExtendedRunUsageInput | null {
64
83
  if (row.category !== 'llm.usage' || row.event !== 'llm-usage' || !row.data) return null;
65
84
  try {
66
- const value: unknown = JSON.parse(row.data);
85
+ let value: unknown = JSON.parse(row.data);
67
86
  if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
68
- return value as RunUsageInput;
87
+ const wrapper = value as { origin?: unknown; originalData?: unknown };
88
+ if (unwrapPod && wrapper.origin === 'pod') {
89
+ value = typeof wrapper.originalData === 'string' ? JSON.parse(wrapper.originalData) : wrapper.originalData;
90
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
91
+ }
92
+ return value as ExtendedRunUsageInput;
69
93
  } catch { return null; }
70
94
  }
71
95
 
96
+ function stageForUsage(data: ExtendedRunUsageInput): string {
97
+ const stages = ['authoring', 'implement', 'review', 'gate'];
98
+ if (typeof data.stage === 'string' && data.stage.length > 0) return stages.includes(data.stage) ? data.stage : '기타';
99
+ // 역할 대체: 저작 호출은 role 'planning' 으로 실린다(ModelRole 에 authoring 이 없다) → authoring 단계로 센다.
100
+ const role = data.role === 'planning' ? 'authoring' : data.role;
101
+ if (typeof role === 'string' && role.length > 0) return stages.includes(role) ? role : '기타';
102
+ return '(none)';
103
+ }
104
+
105
+ function rollupByAxis(inputs: readonly ExtendedRunUsageInput[], axis: UsageAxis): AxisUsageRow[] {
106
+ const groups = new Map<string, ExtendedRunUsageInput[]>();
107
+ for (const data of inputs) {
108
+ const value = axis === 'stage' ? stageForUsage(data)
109
+ : axis === 'model' ? (data.model || '(none)')
110
+ : (data.account || '(none)');
111
+ const group = groups.get(value) ?? [];
112
+ group.push(data);
113
+ groups.set(value, group);
114
+ }
115
+ return [...groups].map(([value, group]) => {
116
+ const rows = rollupRunUsage(group);
117
+ const result: AxisUsageRow = {
118
+ [axis]: value,
119
+ hostIds: [...new Set(rows.flatMap((row) => row.hostIds))],
120
+ calls: rows.reduce((sum, row) => sum + row.calls, 0),
121
+ inputTokens: rows.reduce((sum, row) => sum + row.inputTokens, 0),
122
+ outputTokens: rows.reduce((sum, row) => sum + row.outputTokens, 0),
123
+ cacheReadInputTokens: rows.reduce((sum, row) => sum + row.cacheReadInputTokens, 0),
124
+ cacheCreationInputTokens: rows.reduce((sum, row) => sum + row.cacheCreationInputTokens, 0),
125
+ reasoningOutputTokens: rows.reduce((sum, row) => sum + row.reasoningOutputTokens, 0),
126
+ usdKnown: rows.reduce((sum, row) => sum + row.usdKnown, 0),
127
+ unknownCostCalls: rows.reduce((sum, row) => sum + row.unknownCostCalls, 0),
128
+ includedCalls: rows.reduce((sum, row) => sum + row.includedCalls, 0),
129
+ apiEquivalentUsd: rows.reduce((sum, row) => sum + row.apiEquivalentUsd, 0),
130
+ };
131
+ return result;
132
+ }).sort((a, b) => b.calls - a.calls);
133
+ }
134
+
135
+ function isUsageAxis(value: string): value is 'run' | 'role' | UsageAxis {
136
+ return value === 'run' || value === 'role' || value === 'stage' || value === 'model' || value === 'account';
137
+ }
138
+
139
+ function formatAxisUsage(row: AxisUsageRow, axis: UsageAxis): string {
140
+ const value = row[axis] ?? '(none)';
141
+ return `${axis}=${value} hosts=${row.hostIds.join(',') || '(none)'} calls=${row.calls} input=${row.inputTokens} output=${row.outputTokens} cacheRead=${row.cacheReadInputTokens} cacheCreation=${row.cacheCreationInputTokens} reasoning=${row.reasoningOutputTokens} usdKnown=${row.usdKnown} unknownCostCalls=${row.unknownCostCalls} includedCalls=${row.includedCalls} apiEquivalentUsd=${row.apiEquivalentUsd}`;
142
+ }
143
+
72
144
  function defaultCreditPlan(): CodexCreditPlan | null {
73
145
  try { return codexCreditPlanFromDisk(); } catch { return null; }
74
146
  }
@@ -94,15 +166,15 @@ export function registerUsageCommand(program: Command, deps: UsageCliDeps = {}):
94
166
 
95
167
  usage
96
168
  .command('runs')
97
- .description('저장된 llm-usage 로그를 런 또는 역할·호출 자리별로 집계한다')
169
+ .description('저장된 llm-usage 로그를 런·역할·단계·모델·계정별로 집계한다')
98
170
  .option('--run <runId>', '런 ID 정확 일치')
99
- .option('--by <axis>', '집계 축 (run | role)', 'run')
171
+ .option('--by <axis>', '집계 축 (run | role | stage | model | account)', 'run')
100
172
  .option(...LOGS_SINCE_OPTION)
101
173
  .option('--json', '집계 행 JSON 출력')
102
174
  .action((opts: { run?: string; by: string; since?: string; json?: boolean }, command: Command) => {
103
175
  const json = opts.json || command.optsWithGlobals<{ json?: boolean }>().json;
104
- if (opts.by !== 'run' && opts.by !== 'role') {
105
- out.log('⛔ --by 는 run | role');
176
+ if (!isUsageAxis(opts.by)) {
177
+ out.log('⛔ --by 는 run | role | stage | model | account');
106
178
  exit(2);
107
179
  return;
108
180
  }
@@ -116,8 +188,19 @@ export function registerUsageCommand(program: Command, deps: UsageCliDeps = {}):
116
188
  exactCategories: ['llm.usage'], events: ['llm-usage'],
117
189
  ...(sinceMs !== undefined ? { sinceMs } : {}),
118
190
  });
119
- const inputs = rows.map(usageData).filter((data): data is RunUsageInput => data !== null)
191
+ const unwrapPod = opts.by === 'stage' || opts.by === 'model' || opts.by === 'account';
192
+ const inputs = rows.map((row) => usageData(row, unwrapPod)).filter((data): data is ExtendedRunUsageInput => data !== null)
120
193
  .filter((data) => opts.run === undefined || data.runId === opts.run);
194
+ if (opts.by === 'stage' || opts.by === 'model' || opts.by === 'account') {
195
+ const report = rollupByAxis(inputs, opts.by);
196
+ if (json) {
197
+ out.log(JSON.stringify(report, null, 2));
198
+ return;
199
+ }
200
+ if (report.length === 0) { out.log('(일치하는 런 사용량 없음)'); return; }
201
+ for (const row of report) out.log(formatAxisUsage(row, opts.by));
202
+ return;
203
+ }
121
204
  const report = rollupRunUsage(inputs, { by: opts.by });
122
205
  if (opts.by === 'role') report.sort((a, b) => b.calls - a.calls);
123
206
  if (json) {
@@ -212,9 +212,10 @@ export const GOAL_INTENT_STALL_PROMPT = [
212
212
  /** 재촉 상한 — 이 횟수를 쓰고도 텍스트만이면 종전 ④(텍스트 수용)로 그대로 떨어진다. */
213
213
  export const GOAL_INTENT_STALL_MAX_REPROMPTS = 2;
214
214
 
215
- /** 재촉 가드가 켜지는 provider — ⭐ 이번 조각은 OpenRouter(오픈웨이트 모델) 구현 자식 «한정»이다
216
- * (OP 10-10 결정 · 실측 GLM Flash 첫 턴 40토큰 말만 하고 abandoned). 다른 provider 는 종전 그대로. */
217
- export const GOAL_INTENT_STALL_PROVIDERS: ReadonlySet<string> = new Set(['openrouter']);
215
+ /** 재촉 가드가 켜지는 provider — 구현 자식을 내는 provider 전부(10-11 · 첫 조각 #25876 은 OpenRouter 한정이었다 ·
216
+ * 실측 GLM Flash 첫 턴 40토큰 말만 하고 abandoned). 재촉은 «도구 0회·텍스트만» 턴에만 걸리므로 도구를 부르는 자식에는 영향이 없다.
217
+ * 목록 밖 provider(local 등)는 종전 그대로 바로 수용한다. */
218
+ export const GOAL_INTENT_STALL_PROVIDERS: ReadonlySet<string> = new Set(['openrouter', 'openai-codex', 'anthropic', 'grok', 'bedrock']);
218
219
 
219
220
  /** evidence 누락 complete 재주입 — update_goal(complete) 를 호출했으나 evidence 가 비었을 때. */
220
221
  export const GOAL_EVIDENCE_REQUIRED_PROMPT = [
@@ -280,7 +281,7 @@ export interface GoalLoopOptions {
280
281
  controlSpaceId?: string | null;
281
282
  /** Test seam — file-backed child liveness heartbeat. Default starts the workspace-file writer. */
282
283
  startLivenessHeartbeat?: typeof startChildLivenessHeartbeat;
283
- /** GOAL-LOOP-INTENT-STALL — 구현 자식의 LLM provider. `openrouter` 일 때만 «도구 0회·텍스트만» 턴을
284
+ /** GOAL-LOOP-INTENT-STALL — 구현 자식의 LLM provider. GOAL_INTENT_STALL_PROVIDERS 에 있을 때만 «도구 0회·텍스트만» 턴을
284
285
  * 최대 2번 재촉한다. 생략하면 harness role 이 executor 인 프로세스에서만 활성 provider 로 푼다.
285
286
  * null = 가드 꺼짐. 시험은 이 인자를 명시해 env 에 기대지 않는다. */
286
287
  intentStallProvider?: string | null;
@@ -0,0 +1,279 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { buildAdRunSetup } from '../ad-pipeline/ad-run-setup.js';
4
+ import type { CommandRunner } from '../ad-pipeline/higgsfield-backend.js';
5
+ import type { ShootRunOptions } from '../ad-pipeline/shoot-run.js';
6
+ import type { QcThresholds } from '../ad-pipeline/qc.js';
7
+ import { elanousStateRoot } from '../autopilot/state-paths.js';
8
+ import {
9
+ createDashboardAdSlashRuntime as createAdSlashRuntime,
10
+ type DashboardAdSlashRuntime,
11
+ type DashboardAdSlashRuntimeDeps,
12
+ } from './ad-slash-runtime.js';
13
+
14
+ const dashboardDefaultShootRunOptions: Required<Pick<ShootRunOptions, 'pollIntervalMs' | 'maxPollsPerJob' | 'submitStaggerMs'>> = {
15
+ pollIntervalMs: 3_000,
16
+ // 2026-09-12 seedance_2_0 measured 330–400s; 174 polls = 522s, leaving 402s after six 20s staggers.
17
+ maxPollsPerJob: 174,
18
+ submitStaggerMs: 20_000,
19
+ };
20
+
21
+ /**
22
+ * ⛔ `src/ad-pipeline/ad-run-setup.ts` 의 `optionalProductionInputs` «사본»이다 — 그 목록은 export 가 아니다.
23
+ * 여기서 다시 세는 이유: 이 파일이 `buildAdRunSetup` «뒤»에 `assemblyMaterials` 를 덧붙이므로,
24
+ * 그 함수가 «입력» 기준으로 낸 목록은 최종 `production` 과 어긋난다(`#17880`).
25
+ * ⚠️ 사본이라 «늙는다» — `src/dashboard/index.test.ts` 가 권위 목록과 같은지 단언한다.
26
+ */
27
+ export const dashboardOptionalProductionInputs = [
28
+ 'assembly',
29
+ 'assemblyMaterials',
30
+ 'captionFontPath',
31
+ 'musicBedPath',
32
+ 'qcThresholds',
33
+ 'voiceover',
34
+ 'soundtrack',
35
+ 'referenceAssets',
36
+ 'shootRunOptions',
37
+ 'ground',
38
+ 'invariants',
39
+ ] as const;
40
+
41
+ export interface DashboardAdAssetPreset {
42
+ readonly captionFontPath?: string;
43
+ readonly musicBedPath?: string;
44
+ readonly referenceAssets?: Readonly<Record<string, string[]>>;
45
+ readonly shootRunOptions?: ShootRunOptions;
46
+ readonly qcThresholds?: QcThresholds;
47
+ }
48
+
49
+ /** Operator-owned, instance-scoped optional asset preset; never a repository asset path. */
50
+ export function dashboardAdAssetPresetPath(stateRoot = elanousStateRoot()): string {
51
+ return join(stateRoot, 'ad-assets.json');
52
+ }
53
+
54
+ /** Reads optional operator-owned asset paths without inventing local defaults. */
55
+ export function loadDashboardAdAssetPreset(path: string): DashboardAdAssetPreset {
56
+ let raw: string;
57
+ try {
58
+ raw = readFileSync(path, 'utf8');
59
+ } catch (error) {
60
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return {};
61
+ throw new Error(`dashboard ad asset preset unreadable at ${path}: ${error instanceof Error ? error.message : String(error)}`);
62
+ }
63
+
64
+ let parsed: unknown;
65
+ try {
66
+ parsed = JSON.parse(raw);
67
+ } catch (error) {
68
+ throw new Error(`dashboard ad asset preset malformed at ${path}: ${error instanceof Error ? error.message : String(error)}`);
69
+ }
70
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
71
+ throw new Error(`dashboard ad asset preset malformed at ${path}: expected an object`);
72
+ }
73
+
74
+ const preset = parsed as Record<string, unknown>;
75
+ for (const key of ['captionFontPath', 'musicBedPath'] as const) {
76
+ if (preset[key] !== undefined && typeof preset[key] !== 'string') {
77
+ throw new Error(`dashboard ad asset preset malformed at ${path}: ${key} must be a string`);
78
+ }
79
+ }
80
+ if (preset.referenceAssets !== undefined && (
81
+ preset.referenceAssets === null
82
+ || typeof preset.referenceAssets !== 'object'
83
+ || Array.isArray(preset.referenceAssets)
84
+ || Object.values(preset.referenceAssets).some((value) => !Array.isArray(value) || value.some((entry) => typeof entry !== 'string'))
85
+ )) {
86
+ throw new Error(`dashboard ad asset preset malformed at ${path}: referenceAssets must be a record of string arrays`);
87
+ }
88
+ if (preset.shootRunOptions !== undefined && (
89
+ preset.shootRunOptions === null
90
+ || typeof preset.shootRunOptions !== 'object'
91
+ || Array.isArray(preset.shootRunOptions)
92
+ )) {
93
+ throw new Error(`dashboard ad asset preset malformed at ${path}: shootRunOptions must be an object`);
94
+ }
95
+ const shootRunOptions = preset.shootRunOptions as Record<string, unknown> | undefined;
96
+ for (const key of ['pollIntervalMs', 'maxPollsPerJob', 'maxPollElapsedMs', 'submitStaggerMs', 'submitRetries'] as const) {
97
+ if (shootRunOptions?.[key] !== undefined && (typeof shootRunOptions[key] !== 'number' || !Number.isFinite(shootRunOptions[key]))) {
98
+ throw new Error(`dashboard ad asset preset malformed at ${path}: shootRunOptions.${key} must be a finite number`);
99
+ }
100
+ }
101
+ if (preset.qcThresholds !== undefined && (
102
+ preset.qcThresholds === null
103
+ || typeof preset.qcThresholds !== 'object'
104
+ || Array.isArray(preset.qcThresholds)
105
+ )) {
106
+ throw new Error(`dashboard ad asset preset malformed at ${path}: qcThresholds must be an object`);
107
+ }
108
+ const qcThresholds = preset.qcThresholds as Record<string, unknown> | undefined;
109
+ for (const key of ['dialogueLufsTolerance', 'colorDistance', 'frameDiffVariance'] as const) {
110
+ if (qcThresholds?.[key] !== undefined && (typeof qcThresholds[key] !== 'number' || !Number.isFinite(qcThresholds[key]))) {
111
+ throw new Error(`dashboard ad asset preset malformed at ${path}: qcThresholds.${key} must be a finite number`);
112
+ }
113
+ }
114
+ if (qcThresholds?.durationRanges !== undefined && (
115
+ qcThresholds.durationRanges === null
116
+ || typeof qcThresholds.durationRanges !== 'object'
117
+ || Array.isArray(qcThresholds.durationRanges)
118
+ )) {
119
+ throw new Error(`dashboard ad asset preset malformed at ${path}: qcThresholds.durationRanges must be an object`);
120
+ }
121
+ const durationRanges = qcThresholds?.durationRanges as Record<string, unknown> | undefined;
122
+ for (const scope of ['cut', 'master'] as const) {
123
+ if (durationRanges?.[scope] !== undefined && (
124
+ durationRanges[scope] === null
125
+ || typeof durationRanges[scope] !== 'object'
126
+ || Array.isArray(durationRanges[scope])
127
+ )) {
128
+ throw new Error(`dashboard ad asset preset malformed at ${path}: qcThresholds.durationRanges.${scope} must be an object`);
129
+ }
130
+ const range = durationRanges?.[scope] as Record<string, unknown> | undefined;
131
+ if (range !== undefined) {
132
+ const minimumSeconds = range.minimumSeconds;
133
+ const maximumSeconds = range.maximumSeconds;
134
+ if (typeof minimumSeconds !== 'number' || !Number.isFinite(minimumSeconds)) {
135
+ throw new Error(`dashboard ad asset preset malformed at ${path}: qcThresholds.durationRanges.${scope}.minimumSeconds must be a finite number`);
136
+ }
137
+ if (typeof maximumSeconds !== 'number' || !Number.isFinite(maximumSeconds)) {
138
+ throw new Error(`dashboard ad asset preset malformed at ${path}: qcThresholds.durationRanges.${scope}.maximumSeconds must be a finite number`);
139
+ }
140
+ if (minimumSeconds > maximumSeconds) {
141
+ throw new Error(`dashboard ad asset preset malformed at ${path}: qcThresholds.durationRanges.${scope}.minimumSeconds must be less than or equal to maximumSeconds`);
142
+ }
143
+ }
144
+ }
145
+ return {
146
+ ...(typeof preset.captionFontPath === 'string' ? { captionFontPath: preset.captionFontPath } : {}),
147
+ ...(typeof preset.musicBedPath === 'string' ? { musicBedPath: preset.musicBedPath } : {}),
148
+ ...(preset.referenceAssets === undefined ? {} : { referenceAssets: preset.referenceAssets as Record<string, string[]> }),
149
+ ...(shootRunOptions === undefined ? {} : { shootRunOptions: shootRunOptions as ShootRunOptions }),
150
+ ...(qcThresholds === undefined ? {} : { qcThresholds: qcThresholds as QcThresholds }),
151
+ };
152
+ }
153
+
154
+ export const dashboardAdCommandRunner: CommandRunner = {
155
+ async run(argv, options) {
156
+ const process = Bun.spawn([...argv], {
157
+ stdout: 'pipe',
158
+ stderr: 'pipe',
159
+ ...(options?.timeoutMs ? { timeout: options.timeoutMs } : {}),
160
+ });
161
+ const [raw, stderr, exitCode] = await Promise.all([
162
+ new Response(process.stdout).bytes(),
163
+ new Response(process.stderr).text(),
164
+ process.exited,
165
+ ]);
166
+ return { stdout: new TextDecoder().decode(raw), stderr, exitCode, raw };
167
+ },
168
+ };
169
+
170
+ export function createDashboardAdRunSetupFactory(input: {
171
+ readonly home: string;
172
+ readonly date: string;
173
+ readonly contractsJson: string;
174
+ readonly runner: CommandRunner;
175
+ readonly assetPresetPath?: string;
176
+ }): (allowSpend: boolean) => ReturnType<typeof buildAdRunSetup> {
177
+ let runSequence = 0;
178
+ let activeRunSlug: string | undefined;
179
+
180
+ return (allowSpend) => {
181
+ if (!allowSpend) {
182
+ const runStart = new Date();
183
+ const time = [runStart.getHours(), runStart.getMinutes(), runStart.getSeconds()]
184
+ .map((part) => String(part).padStart(2, '0'))
185
+ .join('');
186
+ activeRunSlug = `dashboard-ad-${time}-${++runSequence}`;
187
+ }
188
+ const slug = activeRunSlug ?? (() => {
189
+ const runStart = new Date();
190
+ const time = [runStart.getHours(), runStart.getMinutes(), runStart.getSeconds()]
191
+ .map((part) => String(part).padStart(2, '0'))
192
+ .join('');
193
+ return `dashboard-ad-${time}-${++runSequence}`;
194
+ })();
195
+ const assetPreset = input.assetPresetPath === undefined ? {} : loadDashboardAdAssetPreset(input.assetPresetPath);
196
+ let setup = buildAdRunSetup({
197
+ home: input.home,
198
+ slug,
199
+ date: input.date,
200
+ version: 1,
201
+ aspect: '9x16',
202
+ contractsJson: input.contractsJson,
203
+ runner: input.runner,
204
+ ...assetPreset,
205
+ referenceAssets: assetPreset.referenceAssets ?? {},
206
+ shootRunOptions: { ...dashboardDefaultShootRunOptions, ...assetPreset.shootRunOptions },
207
+ soundtrack: input.runner,
208
+ voiceover: { runner: input.runner, lines: [] },
209
+ allowSpend,
210
+ });
211
+ if (!('error' in setup) && setup.production) {
212
+ const production = {
213
+ ...setup.production,
214
+ assemblyMaterials: { options: { workDir: setup.workDir, outputName: setup.outputName } },
215
+ };
216
+ setup = {
217
+ ...setup,
218
+ production,
219
+ missingProductionInputs: dashboardOptionalProductionInputs.filter((key) => production[key] === undefined),
220
+ };
221
+ }
222
+ return setup;
223
+ };
224
+ }
225
+
226
+ /** Builds the /ad runtime bindings used by showDashboard for each invocation's spend intent. */
227
+ export function createDashboardAdRunSetupBindings(
228
+ buildDashboardAdRunSetup: ReturnType<typeof createDashboardAdRunSetupFactory>,
229
+ ) {
230
+ let adRunSetup: ReturnType<typeof buildAdRunSetup> | undefined;
231
+ let outputSetupError: string | undefined;
232
+ try {
233
+ adRunSetup = buildDashboardAdRunSetup(false);
234
+ outputSetupError = 'error' in adRunSetup ? adRunSetup.error : undefined;
235
+ } catch (error) {
236
+ outputSetupError = error instanceof Error ? error.message : String(error);
237
+ }
238
+
239
+ return {
240
+ production: adRunSetup && !('error' in adRunSetup) ? adRunSetup.production : undefined,
241
+ productionForSpend: (allowSpend: boolean) => {
242
+ try {
243
+ const setup = buildDashboardAdRunSetup(allowSpend);
244
+ if ('error' in setup) {
245
+ outputSetupError = setup.error;
246
+ return undefined;
247
+ }
248
+ return setup.production;
249
+ } catch (error) {
250
+ outputSetupError = error instanceof Error ? error.message : String(error);
251
+ return undefined;
252
+ }
253
+ },
254
+ missingProductionInputs: adRunSetup && !('error' in adRunSetup) ? adRunSetup.missingProductionInputs : undefined,
255
+ get outputSetupError() {
256
+ return outputSetupError;
257
+ },
258
+ };
259
+ }
260
+
261
+ /** Connects the dashboard's setup state to the actual /ad runtime. */
262
+ export function createDashboardAdSlashRuntime(
263
+ input: Omit<DashboardAdSlashRuntimeDeps, 'production' | 'productionForSpend' | 'missingProductionInputs' | 'outputSetupError'> & {
264
+ readonly adRunSetupBindings?: ReturnType<typeof createDashboardAdRunSetupBindings>;
265
+ readonly adRunSetupError?: string;
266
+ },
267
+ ): DashboardAdSlashRuntime {
268
+ const { adRunSetupBindings, adRunSetupError, ...deps } = input;
269
+ return createAdSlashRuntime({
270
+ ...deps,
271
+ production: adRunSetupBindings?.production,
272
+ productionForSpend: adRunSetupBindings?.productionForSpend,
273
+ missingProductionInputs: adRunSetupBindings?.missingProductionInputs,
274
+ get outputSetupError() {
275
+ return adRunSetupBindings?.outputSetupError ?? adRunSetupError;
276
+ },
277
+ });
278
+ }
279
+