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
@@ -11,280 +11,26 @@ import { spawn, spawnSync } from 'node:child_process';
11
11
  import { $ } from 'bun';
12
12
  import { LOCAL_SKILLS_DIR, SERVICE_NAMES, DATA_DIR, OBSIDIAN_VAULT } from '../config.js';
13
13
  import { dispatchDashboardSessionRuntimeTool } from './session-runtime-dispatch.js';
14
- import { buildAdRunSetup } from '../ad-pipeline/ad-run-setup.js';
15
14
  import { nextChatScrollOffset } from '../chat/log-scroll-key.js';
16
- import type { CommandRunner } from '../ad-pipeline/higgsfield-backend.js';
17
- import type { ShootRunOptions } from '../ad-pipeline/shoot-run.js';
18
- import type { QcThresholds } from '../ad-pipeline/qc.js';
19
- import { elanousStateRoot } from '../autopilot/state-paths.js';
20
15
  import { suggestProjectForFolder } from '../project/project-store.js';
21
16
  import { createProjectSuggester } from '../project/project-suggestion.js';
22
-
23
- const dashboardDefaultShootRunOptions: Required<Pick<ShootRunOptions, 'pollIntervalMs' | 'maxPollsPerJob' | 'submitStaggerMs'>> = {
24
- pollIntervalMs: 3_000,
25
- // 2026-09-12 seedance_2_0 measured 330–400s; 174 polls = 522s, leaving 402s after six 20s staggers.
26
- maxPollsPerJob: 174,
27
- submitStaggerMs: 20_000,
28
- };
29
-
30
- /**
31
- * ⛔ `src/ad-pipeline/ad-run-setup.ts` 의 `optionalProductionInputs` «사본»이다 — 그 목록은 export 가 아니다.
32
- * 여기서 다시 세는 이유: 이 파일이 `buildAdRunSetup` «뒤»에 `assemblyMaterials` 를 덧붙이므로,
33
- * 그 함수가 «입력» 기준으로 낸 목록은 최종 `production` 과 어긋난다(`#17880`).
34
- * ⚠️ 사본이라 «늙는다» — `src/dashboard/index.test.ts` 가 권위 목록과 같은지 단언한다.
35
- */
36
- export const dashboardOptionalProductionInputs = [
37
- 'assembly',
38
- 'assemblyMaterials',
39
- 'captionFontPath',
40
- 'musicBedPath',
41
- 'qcThresholds',
42
- 'voiceover',
43
- 'soundtrack',
44
- 'referenceAssets',
45
- 'shootRunOptions',
46
- 'ground',
47
- 'invariants',
48
- ] as const;
49
-
50
- export interface DashboardAdAssetPreset {
51
- readonly captionFontPath?: string;
52
- readonly musicBedPath?: string;
53
- readonly referenceAssets?: Readonly<Record<string, string[]>>;
54
- readonly shootRunOptions?: ShootRunOptions;
55
- readonly qcThresholds?: QcThresholds;
56
- }
57
-
58
- /** Operator-owned, instance-scoped optional asset preset; never a repository asset path. */
59
- export function dashboardAdAssetPresetPath(stateRoot = elanousStateRoot()): string {
60
- return join(stateRoot, 'ad-assets.json');
61
- }
62
-
63
- /** Reads optional operator-owned asset paths without inventing local defaults. */
64
- export function loadDashboardAdAssetPreset(path: string): DashboardAdAssetPreset {
65
- let raw: string;
66
- try {
67
- raw = readFileSync(path, 'utf8');
68
- } catch (error) {
69
- if ((error as NodeJS.ErrnoException).code === 'ENOENT') return {};
70
- throw new Error(`dashboard ad asset preset unreadable at ${path}: ${error instanceof Error ? error.message : String(error)}`);
71
- }
72
-
73
- let parsed: unknown;
74
- try {
75
- parsed = JSON.parse(raw);
76
- } catch (error) {
77
- throw new Error(`dashboard ad asset preset malformed at ${path}: ${error instanceof Error ? error.message : String(error)}`);
78
- }
79
- if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
80
- throw new Error(`dashboard ad asset preset malformed at ${path}: expected an object`);
81
- }
82
-
83
- const preset = parsed as Record<string, unknown>;
84
- for (const key of ['captionFontPath', 'musicBedPath'] as const) {
85
- if (preset[key] !== undefined && typeof preset[key] !== 'string') {
86
- throw new Error(`dashboard ad asset preset malformed at ${path}: ${key} must be a string`);
87
- }
88
- }
89
- if (preset.referenceAssets !== undefined && (
90
- preset.referenceAssets === null
91
- || typeof preset.referenceAssets !== 'object'
92
- || Array.isArray(preset.referenceAssets)
93
- || Object.values(preset.referenceAssets).some((value) => !Array.isArray(value) || value.some((entry) => typeof entry !== 'string'))
94
- )) {
95
- throw new Error(`dashboard ad asset preset malformed at ${path}: referenceAssets must be a record of string arrays`);
96
- }
97
- if (preset.shootRunOptions !== undefined && (
98
- preset.shootRunOptions === null
99
- || typeof preset.shootRunOptions !== 'object'
100
- || Array.isArray(preset.shootRunOptions)
101
- )) {
102
- throw new Error(`dashboard ad asset preset malformed at ${path}: shootRunOptions must be an object`);
103
- }
104
- const shootRunOptions = preset.shootRunOptions as Record<string, unknown> | undefined;
105
- for (const key of ['pollIntervalMs', 'maxPollsPerJob', 'maxPollElapsedMs', 'submitStaggerMs', 'submitRetries'] as const) {
106
- if (shootRunOptions?.[key] !== undefined && (typeof shootRunOptions[key] !== 'number' || !Number.isFinite(shootRunOptions[key]))) {
107
- throw new Error(`dashboard ad asset preset malformed at ${path}: shootRunOptions.${key} must be a finite number`);
108
- }
109
- }
110
- if (preset.qcThresholds !== undefined && (
111
- preset.qcThresholds === null
112
- || typeof preset.qcThresholds !== 'object'
113
- || Array.isArray(preset.qcThresholds)
114
- )) {
115
- throw new Error(`dashboard ad asset preset malformed at ${path}: qcThresholds must be an object`);
116
- }
117
- const qcThresholds = preset.qcThresholds as Record<string, unknown> | undefined;
118
- for (const key of ['dialogueLufsTolerance', 'colorDistance', 'frameDiffVariance'] as const) {
119
- if (qcThresholds?.[key] !== undefined && (typeof qcThresholds[key] !== 'number' || !Number.isFinite(qcThresholds[key]))) {
120
- throw new Error(`dashboard ad asset preset malformed at ${path}: qcThresholds.${key} must be a finite number`);
121
- }
122
- }
123
- if (qcThresholds?.durationRanges !== undefined && (
124
- qcThresholds.durationRanges === null
125
- || typeof qcThresholds.durationRanges !== 'object'
126
- || Array.isArray(qcThresholds.durationRanges)
127
- )) {
128
- throw new Error(`dashboard ad asset preset malformed at ${path}: qcThresholds.durationRanges must be an object`);
129
- }
130
- const durationRanges = qcThresholds?.durationRanges as Record<string, unknown> | undefined;
131
- for (const scope of ['cut', 'master'] as const) {
132
- if (durationRanges?.[scope] !== undefined && (
133
- durationRanges[scope] === null
134
- || typeof durationRanges[scope] !== 'object'
135
- || Array.isArray(durationRanges[scope])
136
- )) {
137
- throw new Error(`dashboard ad asset preset malformed at ${path}: qcThresholds.durationRanges.${scope} must be an object`);
138
- }
139
- const range = durationRanges?.[scope] as Record<string, unknown> | undefined;
140
- if (range !== undefined) {
141
- const minimumSeconds = range.minimumSeconds;
142
- const maximumSeconds = range.maximumSeconds;
143
- if (typeof minimumSeconds !== 'number' || !Number.isFinite(minimumSeconds)) {
144
- throw new Error(`dashboard ad asset preset malformed at ${path}: qcThresholds.durationRanges.${scope}.minimumSeconds must be a finite number`);
145
- }
146
- if (typeof maximumSeconds !== 'number' || !Number.isFinite(maximumSeconds)) {
147
- throw new Error(`dashboard ad asset preset malformed at ${path}: qcThresholds.durationRanges.${scope}.maximumSeconds must be a finite number`);
148
- }
149
- if (minimumSeconds > maximumSeconds) {
150
- throw new Error(`dashboard ad asset preset malformed at ${path}: qcThresholds.durationRanges.${scope}.minimumSeconds must be less than or equal to maximumSeconds`);
151
- }
152
- }
153
- }
154
- return {
155
- ...(typeof preset.captionFontPath === 'string' ? { captionFontPath: preset.captionFontPath } : {}),
156
- ...(typeof preset.musicBedPath === 'string' ? { musicBedPath: preset.musicBedPath } : {}),
157
- ...(preset.referenceAssets === undefined ? {} : { referenceAssets: preset.referenceAssets as Record<string, string[]> }),
158
- ...(shootRunOptions === undefined ? {} : { shootRunOptions: shootRunOptions as ShootRunOptions }),
159
- ...(qcThresholds === undefined ? {} : { qcThresholds: qcThresholds as QcThresholds }),
160
- };
161
- }
162
-
163
- export const dashboardAdCommandRunner: CommandRunner = {
164
- async run(argv, options) {
165
- const process = Bun.spawn([...argv], {
166
- stdout: 'pipe',
167
- stderr: 'pipe',
168
- ...(options?.timeoutMs ? { timeout: options.timeoutMs } : {}),
169
- });
170
- const [raw, stderr, exitCode] = await Promise.all([
171
- new Response(process.stdout).bytes(),
172
- new Response(process.stderr).text(),
173
- process.exited,
174
- ]);
175
- return { stdout: new TextDecoder().decode(raw), stderr, exitCode, raw };
176
- },
177
- };
178
-
179
- export function createDashboardAdRunSetupFactory(input: {
180
- readonly home: string;
181
- readonly date: string;
182
- readonly contractsJson: string;
183
- readonly runner: CommandRunner;
184
- readonly assetPresetPath?: string;
185
- }): (allowSpend: boolean) => ReturnType<typeof buildAdRunSetup> {
186
- let runSequence = 0;
187
- let activeRunSlug: string | undefined;
188
-
189
- return (allowSpend) => {
190
- if (!allowSpend) {
191
- const runStart = new Date();
192
- const time = [runStart.getHours(), runStart.getMinutes(), runStart.getSeconds()]
193
- .map((part) => String(part).padStart(2, '0'))
194
- .join('');
195
- activeRunSlug = `dashboard-ad-${time}-${++runSequence}`;
196
- }
197
- const slug = activeRunSlug ?? (() => {
198
- const runStart = new Date();
199
- const time = [runStart.getHours(), runStart.getMinutes(), runStart.getSeconds()]
200
- .map((part) => String(part).padStart(2, '0'))
201
- .join('');
202
- return `dashboard-ad-${time}-${++runSequence}`;
203
- })();
204
- const assetPreset = input.assetPresetPath === undefined ? {} : loadDashboardAdAssetPreset(input.assetPresetPath);
205
- let setup = buildAdRunSetup({
206
- home: input.home,
207
- slug,
208
- date: input.date,
209
- version: 1,
210
- aspect: '9x16',
211
- contractsJson: input.contractsJson,
212
- runner: input.runner,
213
- ...assetPreset,
214
- referenceAssets: assetPreset.referenceAssets ?? {},
215
- shootRunOptions: { ...dashboardDefaultShootRunOptions, ...assetPreset.shootRunOptions },
216
- soundtrack: input.runner,
217
- voiceover: { runner: input.runner, lines: [] },
218
- allowSpend,
219
- });
220
- if (!('error' in setup) && setup.production) {
221
- const production = {
222
- ...setup.production,
223
- assemblyMaterials: { options: { workDir: setup.workDir, outputName: setup.outputName } },
224
- };
225
- setup = {
226
- ...setup,
227
- production,
228
- missingProductionInputs: dashboardOptionalProductionInputs.filter((key) => production[key] === undefined),
229
- };
230
- }
231
- return setup;
232
- };
233
- }
234
-
235
- /** Builds the /ad runtime bindings used by showDashboard for each invocation's spend intent. */
236
- export function createDashboardAdRunSetupBindings(
237
- buildDashboardAdRunSetup: ReturnType<typeof createDashboardAdRunSetupFactory>,
238
- ) {
239
- let adRunSetup: ReturnType<typeof buildAdRunSetup> | undefined;
240
- let outputSetupError: string | undefined;
241
- try {
242
- adRunSetup = buildDashboardAdRunSetup(false);
243
- outputSetupError = 'error' in adRunSetup ? adRunSetup.error : undefined;
244
- } catch (error) {
245
- outputSetupError = error instanceof Error ? error.message : String(error);
246
- }
247
-
248
- return {
249
- production: adRunSetup && !('error' in adRunSetup) ? adRunSetup.production : undefined,
250
- productionForSpend: (allowSpend: boolean) => {
251
- try {
252
- const setup = buildDashboardAdRunSetup(allowSpend);
253
- if ('error' in setup) {
254
- outputSetupError = setup.error;
255
- return undefined;
256
- }
257
- return setup.production;
258
- } catch (error) {
259
- outputSetupError = error instanceof Error ? error.message : String(error);
260
- return undefined;
261
- }
262
- },
263
- missingProductionInputs: adRunSetup && !('error' in adRunSetup) ? adRunSetup.missingProductionInputs : undefined,
264
- get outputSetupError() {
265
- return outputSetupError;
266
- },
267
- };
268
- }
269
-
270
- /** Connects the dashboard's setup state to the actual /ad runtime. */
271
- export function createDashboardAdSlashRuntime(
272
- input: Omit<DashboardAdSlashRuntimeDeps, 'production' | 'productionForSpend' | 'missingProductionInputs' | 'outputSetupError'> & {
273
- readonly adRunSetupBindings?: ReturnType<typeof createDashboardAdRunSetupBindings>;
274
- readonly adRunSetupError?: string;
275
- },
276
- ): DashboardAdSlashRuntime {
277
- const { adRunSetupBindings, adRunSetupError, ...deps } = input;
278
- return createAdSlashRuntime({
279
- ...deps,
280
- production: adRunSetupBindings?.production,
281
- productionForSpend: adRunSetupBindings?.productionForSpend,
282
- missingProductionInputs: adRunSetupBindings?.missingProductionInputs,
283
- get outputSetupError() {
284
- return adRunSetupBindings?.outputSetupError ?? adRunSetupError;
285
- },
286
- });
287
- }
17
+ import {
18
+ createDashboardAdRunSetupBindings,
19
+ createDashboardAdRunSetupFactory,
20
+ createDashboardAdSlashRuntime,
21
+ dashboardAdAssetPresetPath,
22
+ dashboardAdCommandRunner,
23
+ } from './ad-run-setup-bindings.js';
24
+ export {
25
+ dashboardOptionalProductionInputs,
26
+ type DashboardAdAssetPreset,
27
+ dashboardAdAssetPresetPath,
28
+ loadDashboardAdAssetPreset,
29
+ dashboardAdCommandRunner,
30
+ createDashboardAdRunSetupFactory,
31
+ createDashboardAdRunSetupBindings,
32
+ createDashboardAdSlashRuntime,
33
+ } from './ad-run-setup-bindings.js';
288
34
 
289
35
  export interface DashboardGlobalKeyRegistration {
290
36
  id: string;
@@ -1005,11 +751,7 @@ import { createDashboardControlSignalSlashRuntime } from './control-signal-slash
1005
751
  import { createDashboardBrowserCdpSlashRuntime } from './browser-cdp-slash-runtime.js';
1006
752
  import { collectGroundingFactsViaCdp } from '../product-grounding/cdp-collect.js';
1007
753
  import { createDashboardSimSlashRuntime } from './sim-slash-runtime.js';
1008
- import {
1009
- createDashboardAdSlashRuntime as createAdSlashRuntime,
1010
- type DashboardAdSlashRuntime,
1011
- type DashboardAdSlashRuntimeDeps,
1012
- } from './ad-slash-runtime.js';
754
+ import type { DashboardAdSlashRuntime } from './ad-slash-runtime.js';
1013
755
  import { openDashboardLocalSimulationWebCockpit } from './sim-web-cockpit-runtime.js';
1014
756
  import { createDashboardCompanionFeedbackRuntime } from './companion-feedback-runtime.js';
1015
757
  import { resolveDashboardMediaSlash } from './media-slash-runtime.js';
@@ -12,6 +12,17 @@ export type StaleInstallResult = {
12
12
 
13
13
  export const STALE_INSTALL_CHECK_INTERVAL_MS = 60_000;
14
14
 
15
+ /**
16
+ * Builds the command that relaunches the TUI on the same daemon session: `elanous --resume <id>` boots the dashboard,
17
+ * loads that session (ACP session/load) and replays its turns (src/index.ts resumeSessionId · dashboard acp-boot).
18
+ * The user-facing hint and its key binding land with the dashboard wiring slice (`r` is the render toggle today).
19
+ * Intentional wiring boundary: the dashboard owns the key binding and executes this command;
20
+ * this scoped helper only constructs it and does not spawn a child process.
21
+ */
22
+ export function buildStaleInstallRelaunchCommand(sessionId: string): string {
23
+ return `elanous --resume ${JSON.stringify(sessionId)}`;
24
+ }
25
+
15
26
  export function formatStaleInstallNotice(result: StaleInstallResult, notifiedCount: number): string {
16
27
  const message = `새 판이 깔렸습니다 (부팅 판 ${result.bootCommit!.slice(0, 7)} → 지금 ${result.installedCommit!.slice(0, 7)}) · TUI 를 다시 띄우면 새 판으로 돕니다`;
17
28
  return notifiedCount >= 2 ? `${message} · 이 TUI 가 뜬 뒤 ${notifiedCount}번째 새 판` : message;
@@ -2,6 +2,13 @@ import type { DebugEvent } from './log.js';
2
2
 
3
3
  export const LOG_DIET_WINDOW_MS = 60_000;
4
4
 
5
+ /** LOG-DIET-2 — minimum spacing between two `log.diet/suppressed` summary releases.
6
+ * One diet window: a folded stream costs at most one summary row per window instead of one per flush. */
7
+ export const LOG_DIET_SUMMARY_MIN_INTERVAL_MS = LOG_DIET_WINDOW_MS;
8
+
9
+ /** LOG-DIET-2 — pending-stream pressure that releases summaries before the interval elapses. */
10
+ export const LOG_DIET_SUMMARY_MAX_PENDING = 256;
11
+
5
12
  export const LOG_DIET_KEYS: Readonly<Record<string, 'repeat' | 'consecutive'>> = {
6
13
  'watchdog/activity': 'consecutive',
7
14
  'user-config.llm/provider-resolved': 'repeat',
@@ -9,6 +16,11 @@ export const LOG_DIET_KEYS: Readonly<Record<string, 'repeat' | 'consecutive'>> =
9
16
  'input.cursor.claim/chat-main': 'repeat',
10
17
  'tox.loop/waiting-capacity': 'repeat',
11
18
  'tox.loop/tick': 'repeat',
19
+ 'llm.credential/provider-key-missing': 'repeat',
20
+ 'llm.credential/provider-key-resolved': 'repeat',
21
+ 'pty.manifest/output-bytes-flushed': 'repeat',
22
+ // `pod-lease/admit-by-usage` is NOT folded: dispatch backpressure and the TA parent-host admission
23
+ // read it back from logs and count rows (`admissionCounts`) — it is a judgment-path event.
12
24
  };
13
25
 
14
26
  export interface LogDietSummary {
@@ -57,14 +69,20 @@ export class LogDietGuard {
57
69
  private readonly now: () => number;
58
70
  private readonly keys: Readonly<Record<string, Policy>>;
59
71
  private readonly maxStreams: number;
72
+ private readonly summaryMinIntervalMs: number;
73
+ private readonly summaryMaxPending: number;
60
74
  private readonly disabled: boolean;
61
75
  private readonly streams = new Map<string, Stream>();
76
+ private lastSummaryReleaseAt = Number.NEGATIVE_INFINITY;
77
+ private pendingStreams = 0;
62
78
 
63
- constructor(opts: { windowMs?: number; now?: () => number; keys?: Readonly<Record<string, Policy>>; maxStreams?: number } = {}) {
79
+ constructor(opts: { windowMs?: number; now?: () => number; keys?: Readonly<Record<string, Policy>>; maxStreams?: number; summaryMinIntervalMs?: number; summaryMaxPending?: number } = {}) {
64
80
  this.windowMs = opts.windowMs ?? LOG_DIET_WINDOW_MS;
65
81
  this.now = opts.now ?? Date.now;
66
82
  this.keys = opts.keys ?? LOG_DIET_KEYS;
67
83
  this.maxStreams = Math.max(1, opts.maxStreams ?? 2000);
84
+ this.summaryMinIntervalMs = Math.max(0, opts.summaryMinIntervalMs ?? LOG_DIET_SUMMARY_MIN_INTERVAL_MS);
85
+ this.summaryMaxPending = Math.max(1, opts.summaryMaxPending ?? LOG_DIET_SUMMARY_MAX_PENDING);
68
86
  this.disabled = process.env.ELANOUS_LOG_DIET === 'off';
69
87
  }
70
88
 
@@ -84,8 +102,9 @@ export class LogDietGuard {
84
102
 
85
103
  admit(rec: DietRecord): { pass: boolean; summaries: LogDietSummary[] } {
86
104
  const policy = this.keys[`${rec.category}/${rec.event}`];
105
+ // LOG-DIET-2 — a registered key folds at warn too; unregistered warn/error/critical alarms never fold.
87
106
  if (this.disabled || !policy || rec.category === 'log.diet'
88
- || rec.level === 'warn' || rec.level === 'error' || rec.level === 'critical') {
107
+ || rec.level === 'error' || rec.level === 'critical') {
89
108
  return { pass: true, summaries: [] };
90
109
  }
91
110
 
@@ -108,6 +127,7 @@ export class LogDietGuard {
108
127
  this.streams.delete(key); // Evict the least recently observed flow.
109
128
  this.streams.set(key, stream);
110
129
  if ((policy === 'repeat' || stream.value === value) && time - stream.lastEmittedAt < this.windowMs) {
130
+ if (stream.count === 0) this.pendingStreams++;
111
131
  stream.count++;
112
132
  const at = new Date(time).toISOString();
113
133
  stream.firstSuppressedAt ??= at;
@@ -115,7 +135,7 @@ export class LogDietGuard {
115
135
  return { pass: false, summaries };
116
136
  }
117
137
  const pending = this.summary(stream);
118
- if (pending) summaries.push(pending);
138
+ if (pending) { summaries.push(pending); this.pendingStreams--; }
119
139
  stream.count = 0;
120
140
  stream.firstSuppressedAt = undefined;
121
141
  stream.lastSuppressedAt = undefined;
@@ -128,7 +148,7 @@ export class LogDietGuard {
128
148
  const oldestKey = this.streams.keys().next().value!;
129
149
  const oldest = this.streams.get(oldestKey)!;
130
150
  const pending = this.summary(oldest);
131
- if (pending) summaries.push(pending);
151
+ if (pending) { summaries.push(pending); this.pendingStreams--; }
132
152
  this.streams.delete(oldestKey);
133
153
  }
134
154
  this.streams.set(key, { category: rec.category, event: rec.event,
@@ -138,8 +158,12 @@ export class LogDietGuard {
138
158
  return { pass: true, summaries };
139
159
  }
140
160
 
141
- /** Emit pending counts without reopening the current sample windows. */
142
- flushPending(): LogDietSummary[] {
161
+ /** Emit pending counts without reopening the current sample windows.
162
+ * LOG-DIET-2 — releases are spaced by `summaryMinIntervalMs` unless forced (drain)
163
+ * or the pending-stream pressure cap is hit. Deferred counts stay accumulated on
164
+ * their streams, so a held-back release never drops a suppressed event. */
165
+ flushPending(force = false): LogDietSummary[] {
166
+ if (!force && !this.summaryReleaseDue()) return [];
143
167
  const summaries: LogDietSummary[] = [];
144
168
  for (const stream of this.streams.values()) {
145
169
  const pending = this.summary(stream);
@@ -148,11 +172,18 @@ export class LogDietGuard {
148
172
  stream.firstSuppressedAt = undefined;
149
173
  stream.lastSuppressedAt = undefined;
150
174
  }
175
+ this.pendingStreams = 0;
176
+ this.lastSummaryReleaseAt = this.now();
151
177
  return summaries;
152
178
  }
153
179
 
180
+ private summaryReleaseDue(): boolean {
181
+ if (this.pendingStreams >= this.summaryMaxPending) return true;
182
+ return this.now() - this.lastSummaryReleaseAt >= this.summaryMinIntervalMs;
183
+ }
184
+
154
185
  drain(): LogDietSummary[] {
155
- const summaries = this.flushPending();
186
+ const summaries = this.flushPending(true);
156
187
  this.streams.clear();
157
188
  return summaries;
158
189
  }
package/src/debug/log.ts CHANGED
@@ -561,6 +561,24 @@ class DebugLog {
561
561
  for (const summary of summaries) this.log('log.diet', 'suppressed', summary);
562
562
  }
563
563
 
564
+ /** LOG-DIET-2 — summaries are now spaced by the diet window, so a short-lived process could exit
565
+ * holding folded counts. On the first suppression we arm one exit drain that force-releases them
566
+ * (and flushes the sinks synchronously) so a held-back release never drops a count at shutdown. */
567
+ private _logDietExitArmed = false;
568
+ private armLogDietExitDrain(): void {
569
+ if (this._logDietExitArmed) return;
570
+ this._logDietExitArmed = true;
571
+ const drain = (): void => { try { this.drainLogDietPending(); } catch { /* exit path must not throw */ } };
572
+ process.once('beforeExit', drain);
573
+ process.once('exit', drain);
574
+ }
575
+
576
+ /** Force-release every pending diet summary (ignores the summary interval) and flush sinks. */
577
+ drainLogDietPending(): void {
578
+ if (this._logDietGuard) this.emitLogDietSummaries(this._logDietGuard.flushPending(true));
579
+ this.flush();
580
+ }
581
+
564
582
  /** Force any buffered events to disk immediately. */
565
583
  flush(): void {
566
584
  if (this._logDietGuard) this.emitLogDietSummaries(this._logDietGuard.flushPending());
@@ -615,7 +633,7 @@ class DebugLog {
615
633
  if (this._logDietGuard) {
616
634
  const decision = this._logDietGuard.admit(rec);
617
635
  this.emitLogDietSummaries(decision.summaries);
618
- if (!decision.pass) return;
636
+ if (!decision.pass) { this.armLogDietExitDrain(); return; }
619
637
  }
620
638
 
621
639
  // Ring buffer — always populated when at least one sink is live so
@@ -9,6 +9,7 @@ import { requestConfirmation, type ConfirmOpts, type ConfirmResult } from '../hi
9
9
  import { devVersion, checklistDevVersion, listChecklist, summarizeChecklist, type Checklist, type ChecklistItem } from '../release-loop/checklist.js';
10
10
  import { add, move, validateVersion } from '../release-loop/feature-store.js';
11
11
  import { formatKst, formatSchedule, getSchedule, listSchedules, setSchedule, type ReleaseSchedule } from '../release-loop/release-schedule.js';
12
+ import { mergedPrsLast24h as countMergedPrsLast24h } from '../release-loop/placement.js';
12
13
  import { latestGraphRun, type GraphRunState } from '../graph-runner/runner.js';
13
14
  import { formatElanousCard, type ElanousCard } from './elanous-card.js';
14
15
 
@@ -24,6 +25,7 @@ export const RELEASE_STATUS_SPEC: LLMToolSpec = {
24
25
  parameters: { type: 'object', properties: {
25
26
  version: { type: 'string', description: '조회할 판. 생략하면 개발 판과 다음 판.' },
26
27
  topic: { type: 'string', enum: ['status', 'features', 'cell', 'schedule', 'ops'], description: '읽을 대상(기본 status).' },
28
+ action: { type: 'string', enum: ['schedule', 'blocked', 'landed'], description: '짧은 읽기 요약: 다음 컷 일정·막힌 칸·최근 24시간 병합 PR.' },
27
29
  id: { type: 'string', description: 'topic=cell 때 찾을 칸 id(필수).' },
28
30
  }, required: [] },
29
31
  };
@@ -57,6 +59,7 @@ export interface ReleaseToolDeps {
57
59
  add?: typeof add;
58
60
  move?: typeof move;
59
61
  setSchedule?: typeof setSchedule;
62
+ mergedPrsLast24h?: (now?: Date) => number;
60
63
  }
61
64
 
62
65
  function nextVersion(version: string): string {
@@ -129,6 +132,42 @@ export function dispatchReleaseStatus(args: Record<string, unknown>, deps: Relea
129
132
  return { text: facts.length ? facts.map(fact => JSON.stringify(fact)).join('\n') : '운영 개관: 관측된 사실 없음', structured: { facts } };
130
133
  }
131
134
  const dev = checklistDevVersion(deps.devVersion?.() ?? devVersion());
135
+ if (args.action !== undefined) {
136
+ const action = args.action;
137
+ if (typeof action !== 'string' || !['schedule', 'blocked', 'landed'].includes(action)) {
138
+ throw new Error('action 은 schedule, blocked, landed 중 하나여야 합니다');
139
+ }
140
+ if (action === 'schedule') {
141
+ const released = checklist(dev).released;
142
+ const schedules = (deps.schedules ?? listSchedules)()
143
+ .filter(row => requested ? row.version === requested : !released || versionOrder(row.version, released) > 0)
144
+ .sort((a, b) => versionOrder(a.version, b.version));
145
+ const text = schedules.length ? `다음 컷 시각:\n${schedules.map(formatSchedule).join('\n')}` : '다음 컷 시각: 일정 없음';
146
+ debug.log('release.tool', 'status', { action, version: requested ?? dev });
147
+ return { text, structured: { schedules: schedules.map(row => ({ version: row.version, cutAt: row.cutAt, landBy: row.landBy,
148
+ publishAt: row.publishAt ?? null, freezeFrom: row.freezeFrom ?? null, freezeUntil: row.freezeUntil ?? null })) } };
149
+ }
150
+ if (action === 'blocked') {
151
+ const versions = requested ? [requested as string] : [dev, nextVersion(dev)];
152
+ const blocked = versions.flatMap(version => {
153
+ validateVersion(version);
154
+ return checklist(version).items
155
+ .filter(item => item.status === 'red' || (item.status === 'yellow' && item.disposition === 'block'))
156
+ .map(item => ({ version, id: item.id, title: firstLine(item.title), status: item.status, disposition: item.disposition ?? null, owner: item.owner ?? null }));
157
+ });
158
+ const text = blocked.length ? `막힌 칸 ${blocked.length}개:\n${blocked.map(item => `${item.version} · ${item.id} ${item.title} · ${item.status === 'red' ? '빨강' : '보류'}${item.owner ? ` · 담당 ${item.owner}` : ''}`).join('\n')}` : '막힌 칸 없음';
159
+ debug.log('release.tool', 'status', { action, version: requested ?? versions.join(',') });
160
+ return { text, structured: { blocked } };
161
+ }
162
+ const now = deps.now ?? new Date();
163
+ const merged = (deps.mergedPrsLast24h ?? countMergedPrsLast24h)(now);
164
+ debug.log('release.tool', 'status', { action, since: new Date(now.getTime() - 86_400_000).toISOString(), until: now.toISOString(), merged });
165
+ return { text: `어제 착지: 최근 24시간 병합 PR ${merged}건`, structured: {
166
+ mergedPrsLast24h: merged,
167
+ since: new Date(now.getTime() - 86_400_000).toISOString(),
168
+ until: now.toISOString(),
169
+ } };
170
+ }
132
171
  if (topic === 'features' || topic === 'cell' || topic === 'schedule') {
133
172
  let result: { text: string; structured: Record<string, unknown> };
134
173
  if (topic === 'features') {
@@ -31,7 +31,7 @@ export interface ErrorReport {
31
31
  at: string; consent: true;
32
32
  }
33
33
 
34
- export interface ErrorReportSettings { enabled: boolean; identity: boolean; email?: string; telegramUser?: string }
34
+ export interface ErrorReportSettings { enabled: boolean; identity: boolean; consented?: boolean; email?: string; telegramUser?: string }
35
35
 
36
36
  export interface ErrorReportDeps {
37
37
  configDir?: () => string;
@@ -100,10 +100,18 @@ export function buildErrorReport(input: ErrorReportInput, ctx: { installId: stri
100
100
  return report;
101
101
  }
102
102
 
103
+ /** First-run notice: says what is actually sent — identity fields go along only when errorReports.identity is on. */
104
+ export function errorReportNoticeLine(config: { identity?: boolean; email?: string; telegramUser?: string } | undefined): string {
105
+ const fields = [config?.email ? '이메일' : '', config?.telegramUser ? '텔레그램 사용자' : ''].filter(Boolean).join('·');
106
+ // Mirrors buildErrorReport: a field goes along only when identity is on AND the field is set.
107
+ const who = config?.identity === true && fields ? `진단과 함께 설정된 신원(${fields})을` : '익명 진단을';
108
+ return `오류 보고 안내: 오류 발생 시 ${who} 운영팀에 전송합니다. 끄려면 \`elanous config set errorReports.enabled false\`.`;
109
+ }
110
+
103
111
  function defaultSettings(): ErrorReportSettings {
104
112
  try {
105
113
  const er = getUserConfig().errorReports;
106
- return { enabled: er?.enabled !== false, identity: er?.identity === true, ...(er?.email ? { email: er.email } : {}), ...(er?.telegramUser ? { telegramUser: er.telegramUser } : {}) };
114
+ return { enabled: er?.enabled !== false, consented: er?.noticeShown === true, identity: er?.identity === true, ...(er?.email ? { email: er.email } : {}), ...(er?.telegramUser ? { telegramUser: er.telegramUser } : {}) };
107
115
  } catch { return { enabled: false, identity: false }; }
108
116
  }
109
117
 
@@ -137,10 +145,17 @@ export async function reportError(input: ErrorReportInput, deps: ErrorReportDeps
137
145
  if (!settings.enabled) return { sent: false, reason: 'disabled' };
138
146
  const now = (deps.now ?? Date.now)();
139
147
  const key = `${input.code}\u0000${input.message.slice(0, 200)}`;
148
+ const configDir = (deps.configDir ?? getElanousConfigDir)();
149
+ if (settings.consented !== true) {
150
+ const report = buildErrorReport(input, {
151
+ installId: installId(configDir), settings, nexus: { alive: false }, at: new Date(now).toISOString(),
152
+ });
153
+ keepPending(report, configDir, deps);
154
+ return { sent: false, reason: 'awaiting-notice' };
155
+ }
140
156
  const previous = lastSent.get(key);
141
157
  if (previous !== undefined && now - previous < SAME_ERROR_GAP_MS) return { sent: false, reason: 'recently-sent' };
142
158
  lastSent.set(key, now);
143
- const configDir = (deps.configDir ?? getElanousConfigDir)();
144
159
  const report = buildErrorReport(input, {
145
160
  installId: installId(configDir), settings, nexus: await (deps.nexusMeta ?? defaultNexusMeta)(), at: new Date(now).toISOString(),
146
161
  });
@@ -186,7 +201,9 @@ function keepPending(report: ErrorReport, configDir: string, deps: ErrorReportDe
186
201
  export async function retryPendingErrorReport(deps: ErrorReportDeps = {}): Promise<ReportOutcome> {
187
202
  try {
188
203
  if (!(deps.enabledByEnvironment ?? defaultEnabledByEnvironment)()) return { sent: false, reason: 'environment' };
189
- if (!(deps.settings ?? defaultSettings)().enabled) return { sent: false, reason: 'disabled' };
204
+ const settings = (deps.settings ?? defaultSettings)();
205
+ if (!settings.enabled) return { sent: false, reason: 'disabled' };
206
+ if (settings.consented !== true) return { sent: false, reason: 'awaiting-notice' };
190
207
  const configDir = (deps.configDir ?? getElanousConfigDir)();
191
208
  const file = pendingFile(configDir);
192
209
  if (!existsSync(file)) return { sent: false, reason: 'none' };
@@ -32,6 +32,7 @@ export const PR_LABELS = [
32
32
  { name: 'elanous:release-hold', axis: 'addon', color: 'D4C5F9', description: 'Exclude from release notes until verified in production', appliedBy: ['launcher', 'session', 'human'], sweep: { action: 'none' } },
33
33
  { name: 'elanous:idea-stale', axis: 'addon', color: 'FBCA04', description: 'Approval pending beyond the notification threshold', appliedBy: ['sweeper'], sweep: { action: 'notify', afterDays: IDEA_STALE_DAYS } },
34
34
  { name: 'elanous:needs-rebase', axis: 'addon', color: 'E99695', description: 'Approval blocked by conflicts or failing checks', appliedBy: ['sweeper'], sweep: { action: 'block-approval' } },
35
+ { name: 'elanous:bench', axis: 'addon', color: 'C5DEF5', description: 'Bench arm output: never landed or swept; the owner harvests the winner onto main', appliedBy: ['harness'], sweep: { action: 'none' } },
35
36
  { name: 'elanous:harvestable', axis: 'addon', color: 'FBCA04', description: 'Pod terminal marked this PR for harvest', appliedBy: ['harness'], sweep: { action: 'close', afterHours: STALLED_DRAFT_HOURS } },
36
37
  { name: 'elanous:from-harness', axis: 'origin', color: 'C5DEF5', description: 'Launched from a harness goal', appliedBy: ['harness'], sweep: { action: 'none' } },
37
38
  { name: 'elanous:from-intake', axis: 'origin', color: 'C5DEF5', description: 'Launched from an absorbed idea', appliedBy: ['launcher'], sweep: { action: 'none' } },