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
@@ -652,20 +652,47 @@ export function buildReviewPrompt(
652
652
  * `none of the tests …`. 표지 낱말로 시작하는 실제 블로커를 지우면 PASS/WARN 리뷰에서 블로커가 사라진다. */
653
653
  const NONE_WORD = String.raw`(?:없음|해당\s*없음|n\/a|none|nothing)`;
654
654
  const BARE_SENTINEL = new RegExp(String.raw`^${NONE_WORD}\s*[.。]?$`, 'i');
655
- const BRACKETED_SENTINEL = new RegExp(String.raw`^[([(【]\s*${NONE_WORD}(?:\s*[—–\-:.,;]\s*([^)\])】]*))?\s*[)\])】]$`, 'i');
655
+ const BRACKETED_SENTINEL = new RegExp(String.raw`^[([(【]\s*${NONE_WORD}(?:\s*[—–\-:.,;·]\s*([^)\])】]*))?\s*[)\])】]$`, 'i');
656
656
  // ⭐ 설명 «전체»가 「지적 없음」 한 문장일 때만 — 일부만 맞아도 접으면 `(none: no blockers are checked because …)` 같은 지적이 사라진다.
657
657
  const NO_FINDING_REMARK = new RegExp([
658
658
  String.raw`^(?:(?:블로커|차단)\s*)?(?:수준의?\s*)?(?:결함|문제|이슈|지적|블로커)(?:은|는|이|가)?\s*(?:발견되지\s*않(?:음|았(?:다|습니다)?)|없(?:음|다|습니다))[.。]?$`,
659
659
  String.raw`^(?:no|zero)\s+(?:blocking\s+)?(?:blockers?|issues?|problems?|findings?|must-?fix(?:es)?)(?:\s+found)?\.?$`,
660
660
  String.raw`^nothing\s+(?:blocking|to\s+fix)\.?$`,
661
+ // REVIEW-MUSTFIX-NONE-SENTINEL — `(없음 — 수용기준 충족)` · `(none — acceptance criteria met)` (GLM 실측 6건). «미충족» 은 ^ 앵커로 안 맞는다.
662
+ String.raw`^수용\s*기준(?:을|이)?\s*(?:(?:모두|전부)\s*)?충족(?:함|됨|했다|합니다)?[.。]?$`,
663
+ String.raw`^(?:all\s+)?acceptance\s+criteria\s+(?:are\s+)?(?:all\s+)?met\.?$`,
661
664
  ].join('|'), 'i');
665
+ // REVIEW-MUSTFIX-NONE-SENTINEL 후속 — GLM 이 괄호 안에 «무소견 머리 + 긴 근거»를 쓴다(10-11 TC `pr land` 가 `review-must-fix-1` 로 거부):
666
+ // `(없음 — 수용기준 충족: 머리말에 … 반영됨 …)` · `(없음 — 수용기준 3항 모두 충족: … (\`'failed'\` 반환) · …)`(근거 안에 괄호가 또 있다).
667
+ // ⭐ 길이 무관 0 은 «머리»가 무소견 서술이고 바로 구분자가 올 때만 — 머리가 결함 서술이면(`(없음 — 하지만 X 가 깨진다)`) 지적으로 남긴다.
668
+ // ⛔ 근거에 역접·미충족(`하지만`·`다만`·`미충족`·`but`·`except`·`not met` …)이 있으면 보수적으로 지적으로 남긴다.
669
+ const LONG_BRACKETED_SENTINEL = new RegExp(String.raw`^[([(【]\s*${NONE_WORD}\s*[—–\-:·]\s*([\s\S]+?)\s*[)\])】]$`, 'i');
670
+ const NO_FINDING_HEAD = new RegExp('^(?:' + [
671
+ String.raw`수용\s*기준(?:\s*\d+\s*(?:항|개|건)(?:목)?)?(?:을|이)?\s*(?:(?:모두|전부)\s*)?충족(?:함|됨|했다|합니다)?`,
672
+ String.raw`(?:all\s+)?acceptance\s+criteria\s+(?:are\s+)?(?:all\s+)?met`,
673
+ String.raw`must-?fix\s*(?:항목)?(?:은|는|이|가)?\s*(?:없(?:음|다|습니다)|0\s*건)`,
674
+ String.raw`(?:no|zero)\s+(?:blocking\s+)?(?:blockers?|must-?fix(?:es)?)(?:\s+found)?`,
675
+ String.raw`(?:(?:블로커|차단)\s*)?(?:수준의?\s*)?(?:결함|블로커)(?:은|는|이|가)?\s*(?:발견되지\s*않(?:음|았(?:다|습니다)?)|없(?:음|다|습니다))`,
676
+ ].join('|') + String.raw`)\s*(?:[::—–\-·,;.。(]|$)`, 'i');
677
+ const LONG_REMARK_DISSENT = /하지만|그러나|다만|(?<![가-힣])단(?:,|\s)|미\s*충족|불\s*충족|충족하지|충족되지|\bbut\b|\bhowever\b|\bexcept\b|\balthough\b|\bnot\s+(?:yet\s+)?met\b|\bunmet\b/i;
678
+ /** 괄호 안이 «없음 + 구분자 + 무소견 머리 …» 인가 — 길이 무관(근거 안 괄호도 허용). `isNoneSentinelFinding` 의 갈래. */
679
+ function isLongNoFindingBracket(text: string): boolean {
680
+ const m = LONG_BRACKETED_SENTINEL.exec(text.trim());
681
+ if (!m) return false;
682
+ const remark = m[1]!.trim();
683
+ return NO_FINDING_HEAD.test(remark) && !LONG_REMARK_DISSENT.test(remark);
684
+ }
685
+ /** must-fix 항목이 «지적 없음» 표지인가 — must-fix 를 세는 자리는 전부 `parseReviewResult` 를 거친다
686
+ * (`self review --json` 의 `mustFix` → TA `parseReviewResultOutput` → `pr land --overlap-evidence` 의 `review-must-fix-N`). */
662
687
  function isNoneSentinelFinding(finding: string): boolean {
663
688
  const text = finding.trim();
664
689
  if (BARE_SENTINEL.test(text)) return true;
665
690
  const bracketed = BRACKETED_SENTINEL.exec(text);
666
- if (!bracketed) return false;
667
- const remark = bracketed[1]?.trim();
668
- return !remark || NO_FINDING_REMARK.test(remark);
691
+ if (bracketed) {
692
+ const remark = bracketed[1]?.trim();
693
+ if (!remark || NO_FINDING_REMARK.test(remark)) return true;
694
+ }
695
+ return isLongNoFindingBracket(text);
669
696
  }
670
697
 
671
698
  /** critic 응답 파싱 → ReviewResult. 순수함수. VERDICT + MUST-FIX/SHOULD-FIX/REQUIREMENTS 섹션 분해.
@@ -24,6 +24,9 @@ import type { ReferencedFileReader } from '../self-implement/goal-file-reader.js
24
24
  import type { SubscriptionReviewerSpec } from '../user-config.js';
25
25
  import { listRunLedgers, lookupPrGoalAcceptance, lookupRunLedger, type PrGoalAcceptanceLookup, type RunLedgerEntry, type RunLedgerLookup } from '../self-implement/run-ledger.js';
26
26
 
27
+ /** SUBSCRIPTION-EXHAUST-REROUTE — API reviewer when the configured `roleLlm.reviewer` lane is unavailable (off codex). */
28
+ export const ROLE_REVIEWER_FALLBACK_MODEL = 'openrouter/z-ai/glm-5.3';
29
+
27
30
  export interface SelfReviewCliOpts {
28
31
  intent?: string | undefined;
29
32
  model?: string | undefined;
@@ -54,8 +57,13 @@ export interface SelfReviewCliDeps {
54
57
  * (`#7486` 리뷰 must-fix · 실제로 `index.ts` 래퍼가 그렇게 한 번 떨어뜨렸다). */
55
58
  reviewPullRequest(input: ReviewInput, llm: (prompt: string, images?: readonly ReviewImage[]) => Promise<string>): Promise<ReviewResult>;
56
59
  renderReview(review: ReviewResult): string;
57
- /** 기본 경로 리뷰어 LLM(API). */
58
- makeApiLlm(model: string): (prompt: string) => Promise<string>;
60
+ /**
61
+ * 기본 경로 리뷰어 LLM(API).
62
+ * `o.pinProvider` — 명시 `--model` 일 때 true: 구현은 streamLLM 의 provider 를 그 모델의 provider 로 고정해
63
+ * 라우터 폴백(grok 등)으로 조용히 넘어가지 않고 실패를 오류로 낸다.
64
+ * `o.onResolved` — streamLLM 이 «실제로 응답한» provider·model 을 알린다.
65
+ */
66
+ makeApiLlm(model: string, o?: { pinProvider?: boolean; onResolved?: (provider: string, model: string) => void }): (prompt: string) => Promise<string>;
59
67
  /** `--acp` 경로 리뷰어 LLM(독립 프로세스). */
60
68
  /**
61
69
  * ⭐ `P4b` — 두 번째 인자가 **멀티미디어 통로**다.
@@ -76,6 +84,11 @@ export interface SelfReviewCliDeps {
76
84
  makeRoleReviewerLlm?(spec: SubscriptionReviewerSpec, o: { timeoutMs: number }):
77
85
  | { llm: (prompt: string, images?: readonly ReviewImage[]) => Promise<string>; backend: string }
78
86
  | { reason: string };
87
+ /**
88
+ * SUBSCRIPTION-EXHAUST-REROUTE — API model used when `roleLlm.reviewer` is configured (no explicit flag) but its
89
+ * lane is unavailable or fails. Absent seam or empty answer = the codex-ladder default as before.
90
+ */
91
+ roleFallbackModel?(): string | undefined;
79
92
  /** 관측 sink 등록 — ⭐ 이 호출이 분기 밖에 있는지가 이 트랙의 계약이다. */
80
93
  registerSink(surface: string): Promise<void>;
81
94
  /** `data` 는 payload 객체 — 순수 헬퍼가 만든 관측 타입도 그대로 받는다(인덱스 시그니처 강요 금지). */
@@ -95,6 +108,8 @@ export interface SelfReviewCliDeps {
95
108
 
96
109
  export interface SelfReviewCliOutcome {
97
110
  results: Array<Record<string, unknown>>;
111
+ /** 리뷰하지 못한 PR 수(`gh pr diff` 실패 · ACP 리뷰 reviewed=false) — json·비json 둘 다 센다. index.ts 가 1 이상이면 rc=1. */
112
+ failed: number;
98
113
  }
99
114
 
100
115
  interface GhResult {
@@ -1175,7 +1190,7 @@ export async function runSelfReviewCliCommand(
1175
1190
  invalidPrArgs: safeInvalidPrArgs,
1176
1191
  }, { level: 'warn' });
1177
1192
  deps.error(`PR 인자가 올바르지 않습니다: ${safeInvalidPrArgs.join(', ')}. PR 번호는 숫자 형식이어야 합니다 (예: elanous self review 5502).`);
1178
- return { results: [] };
1193
+ return { results: [], failed: 0 };
1179
1194
  }
1180
1195
 
1181
1196
  const useAcp = opts.acp === true;
@@ -1197,11 +1212,19 @@ export async function runSelfReviewCliCommand(
1197
1212
  if (useAcp) {
1198
1213
  acpReviewLLM = deps.makeAcpLlm({ backend: acpBackend, ...(acpModel ? { model: acpModel } : {}), timeoutMs: acpTimeoutSec * 1000 });
1199
1214
  }
1200
- const model = useAcp ? `acp:${acpBackend}${acpModel ? `/${acpModel}` : ''}` : (opts.model || deps.envModel() || tierModel('better'));
1201
1215
  // ⭐ Reviewer selection source. An explicit flag always wins byte-for-byte; only without one does
1202
1216
  // `roleLlm.reviewer` (the harness's reviewer row) pick the ACP lane. Worst case = today's API review.
1203
1217
  const explicitReviewerFlag = opts.acp === true || Boolean(opts.acpBackend) || Boolean(opts.acpModel?.trim()) || Boolean(opts.model?.trim());
1204
1218
  const reviewerSource: 'cli' | 'role-config' | 'default' = explicitReviewerFlag ? 'cli' : opts.roleReviewer ? 'role-config' : 'default';
1219
+ // SUBSCRIPTION-EXHAUST-REROUTE (0.2.24): when `roleLlm.reviewer` is configured but its lane is unavailable
1220
+ // (10-11 prod: «Claude subscription usage unavailable or at harness cap (60%)»), the API fallback was the codex
1221
+ // ladder default (`tierModel('better')` = gpt-6-sol) — the CLI read roleLlm.reviewer, then spent codex anyway.
1222
+ // ⇒ the role-config lane falls back to `roleFallbackModel()` (off codex) when the seam gives one. Explicit flag
1223
+ // and ELANOUS_PR_REVIEW_MODEL still win; no seam / no answer = today's default byte-for-byte.
1224
+ const envModel = deps.envModel();
1225
+ const roleFallbackModel = !useAcp && !opts.model && !envModel && reviewerSource === 'role-config'
1226
+ ? deps.roleFallbackModel?.()?.trim() || undefined : undefined;
1227
+ const model = useAcp ? `acp:${acpBackend}${acpModel ? `/${acpModel}` : ''}` : (opts.model || envModel || roleFallbackModel || tierModel('better'));
1205
1228
  let roleReviewer: { llm: (prompt: string, images?: readonly ReviewImage[]) => Promise<string>; model: string } | undefined;
1206
1229
  let roleReviewerUnavailableReason: string | undefined;
1207
1230
  let roleReviewerAvailabilityMs: number | undefined;
@@ -1223,6 +1246,7 @@ export async function runSelfReviewCliCommand(
1223
1246
  deps.log('role-reviewer-fallback', {
1224
1247
  reviewerSource, roleReviewerProvider: opts.roleReviewer.provider, roleReviewerAvailabilityMs,
1225
1248
  stage: 'availability', reason: roleReviewerUnavailableReason, fallbackModel: safeLogText(model),
1249
+ fallbackModelSource: roleFallbackModel ? 'role-fallback' : 'default',
1226
1250
  }, { level: 'warn' });
1227
1251
  }
1228
1252
  }
@@ -1234,6 +1258,7 @@ export async function runSelfReviewCliCommand(
1234
1258
  ...(roleReviewerUnavailableReason ? { roleReviewerFallbackReason: roleReviewerUnavailableReason } : {}),
1235
1259
  });
1236
1260
  const results: Array<Record<string, unknown>> = [];
1261
+ let failed = 0;
1237
1262
  const providedReviewerContext = loadReviewerContext(opts, deps.readReferencedFile);
1238
1263
  const hasProvidedReviewerContext = (opts.contextOrder?.length ?? 0) > 0 || (opts.context?.length ?? 0) > 0 || (opts.contextText?.length ?? 0) > 0;
1239
1264
  for (const pr of prArgs) {
@@ -1311,6 +1336,7 @@ export async function runSelfReviewCliCommand(
1311
1336
  const contextOutput = reviewerContextOutput(reviewerContext, decompositionContext);
1312
1337
  if (opts.json) results.push({ pr, error: err, ...contextOutput, ...comparisonData, ...(mismatchWarning ?? {}) });
1313
1338
  else deps.error(`PR ${pr}: ${err}\n${reviewerContextStatus}`);
1339
+ failed += 1;
1314
1340
  continue;
1315
1341
  }
1316
1342
  // ⭐ S2(2026-07-30) — 리뷰 증거를 **이 PR 이 저작한 변경**으로 좁힌다. 실측 7건에서 1건만
@@ -1375,7 +1401,12 @@ export async function runSelfReviewCliCommand(
1375
1401
  }
1376
1402
  const startedAt = deps.now();
1377
1403
  // Role-config lane builds the API reviewer only when it falls back (`made().api` stays 0 on ACP success).
1378
- const llmReview = roleReviewer ? undefined : (acpReviewLLM ?? deps.makeApiLlm(model));
1404
+ const responded: { provider?: string; model?: string } = {};
1405
+ const makeApi = () => deps.makeApiLlm(model, {
1406
+ pinProvider: Boolean(opts.model?.trim()),
1407
+ onResolved: (provider, respondedModel) => { responded.provider = provider; responded.model = respondedModel; },
1408
+ });
1409
+ const llmReview = roleReviewer ? undefined : (acpReviewLLM ?? makeApi());
1379
1410
  // ⛔ evidenceNote 는 **별도 채널**이다 — phaseIntent 에 섞으면 의도 채널이 오염된다(실측: 그렇게
1380
1411
  // 했다가 intent 배선 테스트 3개가 즉시 깨졌다).
1381
1412
  // ⛔ 사람 `--intent`(phaseIntent) 와 골 acceptance 는 다른 인자 — 충돌하지 않고, 충돌해도 intent 가 이긴다.
@@ -1414,7 +1445,7 @@ export async function runSelfReviewCliCommand(
1414
1445
  }, { level: 'warn' });
1415
1446
  }
1416
1447
  }
1417
- let review = roleReview ?? await deps.reviewPullRequest(reviewInput, llmReview ?? acpReviewLLM ?? deps.makeApiLlm(model));
1448
+ let review = roleReview ?? await deps.reviewPullRequest(reviewInput, llmReview ?? acpReviewLLM ?? makeApi());
1418
1449
  let selectedModel = roleReview && roleReviewer ? roleReviewer.model : model;
1419
1450
  let reviewRoute: 'api' | 'acp' | 'acp-fallback' = useAcp || roleReview ? 'acp' : 'api';
1420
1451
  let fallbackAttempted = false;
@@ -1463,6 +1494,11 @@ export async function runSelfReviewCliCommand(
1463
1494
  ...(fallbackFailures.length ? { fallbackFailures } : {}),
1464
1495
  ...(skippedAcpFallbackBackends.length ? { skippedAcpFallbackBackends } : {}),
1465
1496
  };
1497
+ // API 경로로 «성공한» 리뷰만 응답 provider·model 을 싣는다(요청 model 과 다르면 둘 다 보인다).
1498
+ // ⚠️ streamLLM 의 onResolvedProvider 는 시도 «전»에 불린다 — 실패한 리뷰에 실으면 응답 안 한 provider 를 «응답했다»로 적는다.
1499
+ const respondedOutput = reviewRoute === 'api' && review.reviewed === true && responded.provider
1500
+ ? { respondedProvider: responded.provider, respondedModel: responded.model }
1501
+ : {};
1466
1502
  // ⚠️ `buildReviewObservation` drops unknown keys — the selection rides next to it, not inside it.
1467
1503
  const reviewerSelection = {
1468
1504
  ...reviewerSourceObservation(),
@@ -1492,14 +1528,17 @@ export async function runSelfReviewCliCommand(
1492
1528
  if (useAcp && review.reviewed === false) {
1493
1529
  const err = 'ACP 리뷰 실행 실패(reviewed=false) — 관측: elanous logs --category acp-review';
1494
1530
  const contextOutput = reviewerContextOutput(reviewerContext, decompositionContext);
1495
- if (opts.json) results.push({ pr, error: err, ...review, verdict: null, ...reviewProvenance, ...reviewerSelection, ...provenance, ...contextOutput });
1531
+ if (opts.json) results.push({ pr, error: err, ...review, verdict: null, ...reviewProvenance, ...respondedOutput, ...reviewerSelection, ...provenance, ...contextOutput });
1496
1532
  else deps.error(`PR ${pr}: ${err}\n${renderProvenance(diffRead)}\n${reviewerContextStatus}`);
1533
+ failed += 1;
1497
1534
  continue;
1498
1535
  }
1499
1536
  const contextOutput = reviewerContextOutput(reviewerContext, decompositionContext);
1500
1537
  const jsonReview = review.reviewed === true ? review : { ...review, verdict: null };
1501
- if (opts.json) results.push({ pr, model: selectedModel, ...jsonReview, ...reviewProvenance, ...reviewerSelection, ...provenance, ...contextOutput, evidence: evidence.evidence, prDiffFiles: evidence.prDiffFiles, ...(evidence.authoredFiles !== undefined ? { authoredFiles: evidence.authoredFiles } : {}) });
1538
+ if (opts.json) results.push({ pr, model: selectedModel, ...jsonReview, ...reviewProvenance, ...respondedOutput, ...reviewerSelection, ...provenance, ...contextOutput, evidence: evidence.evidence, prDiffFiles: evidence.prDiffFiles, ...(evidence.authoredFiles !== undefined ? { authoredFiles: evidence.authoredFiles } : {}) });
1502
1539
  else deps.print(`\n━━ PR ${pr} (${selectedModel}) ━━\n${renderProvenance(diffRead)}\n${evidenceLine}\n${reviewerContextStatus}\n${deps.renderReview(review)}`);
1540
+ // REVIEW-ERROR-RC — 기본(API)·role 경로가 1차·폴백 모두 실패해 reviewed≠true 로 끝나도 «못 리뷰한 PR» 이다(verdict:null).
1541
+ if (review.reviewed !== true) failed += 1;
1503
1542
  }
1504
- return { results };
1543
+ return { results, failed };
1505
1544
  }
@@ -106,12 +106,25 @@ export function buildCritiquePrompt(input: { planBody: string; changedFiles: str
106
106
  ].join('\n');
107
107
  }
108
108
 
109
- /** LLM 응답 파싱 → verdict + findings. 순수함수(LLM node 규칙). */
109
+ /** 판정 줄 — «줄 머리» VERDICT 만 판정으로 읽는다(본문 중간 인용·설명 속 «VERDICT:» 오인 방지).
110
+ * 들여쓰기는 허용(mission-engine 의 미션 판정 파서와 같은 앵커). */
111
+ const CRITIQUE_VERDICT_LINE = /^[ \t]*VERDICT:\s*(PASS|WARN|FAIL)/im;
112
+ /** 판정 줄이 없을 때 findings 에 남기는 fail-closed 사유(REVIEW-NO-VERDICT-FAILCLOSED #25937). */
113
+ const NO_VERDICT_FINDING = '비평 판정 줄(VERDICT) 없음 — 판정 없음은 통과가 아니다(fail-closed · REVIEW-NO-VERDICT-FAILCLOSED #25937)';
114
+ const MAX_CRITIQUE_FINDINGS = 4;
115
+
116
+ /** LLM 응답 파싱 → verdict + findings. 순수함수(LLM node 규칙).
117
+ * ⛔ 판정 줄이 «없으면» pass 로 기본하지 않는다(칸 MISSION-CRITIQUE-FAILCLOSED · #25937):
118
+ * «판정 없음 = 통과 아님» ⇒ verdict 'warn' + 사유 finding.
119
+ * 'fail' 이 아니라 'warn' 인 이유 — 소비측(nocturnal-runner)은 critique verdict==='fail' 에
120
+ * gate-failed(재구현 요구)를 건다. «비평가 응답의 형식 결함»을 «구현 훼손」으로
121
+ * 오귀속시키는 것이다. 'warn' 은 PR 본문(renderCritique)·자율 원장에 그대로 표시되어 사람(HITL)이 판단한다.
122
+ * (post-PR 리뷰 축 parseReviewResult 는 substrate 가 `reviewed:false` 로 같은 fail-closed 를 이미 한다.) */
110
123
  export function parseLLMCritique(text: string): { verdict: CritiqueResult['verdict']; findings: string[] } {
111
- const vm = /VERDICT:\s*(PASS|WARN|FAIL)/i.exec(text);
112
- const verdict = (vm ? vm[1]!.toLowerCase() : 'pass') as CritiqueResult['verdict'];
113
- const findings = text.split('\n').map((l) => l.trim()).filter((l) => l.startsWith('- ')).map((l) => l.slice(2).trim()).slice(0, 4);
114
- return { verdict, findings };
124
+ const bullets = text.split('\n').map((l) => l.trim()).filter((l) => l.startsWith('- ')).map((l) => l.slice(2).trim());
125
+ const vm = CRITIQUE_VERDICT_LINE.exec(text);
126
+ if (!vm) return { verdict: 'warn', findings: [NO_VERDICT_FINDING, ...bullets].slice(0, MAX_CRITIQUE_FINDINGS) };
127
+ return { verdict: vm[1]!.toLowerCase() as CritiqueResult['verdict'], findings: bullets.slice(0, MAX_CRITIQUE_FINDINGS) };
115
128
  }
116
129
 
117
130
  /** ★ R1 결정론 + LLM 비평(대표 2026-07-12) — 결정론(R0) 먼저, llmReview 주입 시 LLM 심층 비평
@@ -11,7 +11,7 @@
11
11
  // 2 예산(§8-5·config-first): maxStalls(언제 협상) · maxReplans(언제 partial 포기). a6230f 실측 튜닝.
12
12
  // 제1원칙: 협상 제안을 debug.log('mission.negotiate')로 관측.
13
13
 
14
- import { getUserConfig } from '../user-config.js';
14
+ import { getUserConfig, resolveRoleLlm } from '../user-config.js';
15
15
  import { debug } from '../debug/log.js';
16
16
 
17
17
  export interface FeasibilityBudget {
@@ -104,16 +104,17 @@ function buildNegotiatePrompt(input: NegotiateInput, overBudget: boolean): strin
104
104
  ].filter(Boolean).join('\n');
105
105
  }
106
106
 
107
- async function defaultJudge(prompt: string): Promise<string> {
108
- // ★ 모델 기반 provider 라우팅(2026-07-14) — 협상은 강한 추론이 핵심이라 기본 opus 4.8(대표 셋업).
109
- // getProvider(model)로 모델 계열 라우팅(claude-* → anthropic·env/config 키)해 활성 provider(codex 등)
110
- // 무관하게 지목 모델이 닿는다. resolveDefaultProvider(활성 반환)면 opus 를 codex 로 보내 400→fail-soft.
111
- // env(ELANOUS_NEGOTIATE_MODEL·ELANOUS_DECOMPOSE_MODEL)로 override 가능.
112
- const { streamLLM, getProvider } = await import('../llm.js');
113
- const model = process.env.ELANOUS_NEGOTIATE_MODEL || process.env.ELANOUS_DECOMPOSE_MODEL || 'claude-opus-4-8';
114
- const provider = getProvider(model);
107
+ export async function defaultJudge(prompt: string): Promise<string> {
108
+ const { streamLLM, getProvider, PROVIDERS } = await import('../llm.js');
109
+ const override = process.env.ELANOUS_NEGOTIATE_MODEL || process.env.ELANOUS_DECOMPOSE_MODEL;
110
+ const resolved = override ? undefined : resolveRoleLlm('planning');
111
+ const model = override || resolved!.model;
112
+ const provider = override ? getProvider(model) : PROVIDERS[resolved!.provider];
113
+ const providerName = override ? provider.name : resolved!.provider;
114
+ if (!provider) throw new Error(`Unknown planning provider: ${providerName}`);
115
+ debug.log('llm.role-pin', 'resolved', { site: 'feasibility-negotiate', role: 'planning', model, provider: providerName, source: override ? 'env' : resolved!.source });
115
116
  return streamLLM([{ role: 'user', content: prompt }], () => {}, {
116
- model, reasoningEffort: 'medium', ...(provider ? { provider } : {}),
117
+ model, provider, usageRole: 'planning', reasoningEffort: 'medium',
117
118
  });
118
119
  }
119
120
 
@@ -91,6 +91,17 @@ export function rollupDerived(derived: DerivedJob[]): MissionRollup {
91
91
 
92
92
  export interface TracePaths { schedulesDb?: string; tasksDb?: string; surfaceEventsDb?: string }
93
93
 
94
+ /** 크론 행 목록 → 파생물(순수 · 행→DerivedJob 매핑의 «단일 소유»).
95
+ * ⛔ 목록 화면(`/v1/missions` 카드)도 이 함수를 쓴다 — 크론 매핑을 두 번
96
+ * 쓰면 「트레이스는 stale 인데 카드는 ok」 갈림이 생긴다.
97
+ * `staleIds` 는 `scheduleHealth(rows).stale` 로 «한 번» 구해 넣는다. */
98
+ export function cronRowsToDerived(
99
+ rows: readonly ScheduleRow[],
100
+ staleIds: ReadonlySet<string>,
101
+ ): DerivedJob[] {
102
+ return rows.map(r => cronToDerived(r, staleIds.has(r.id)));
103
+ }
104
+
94
105
  /** 크론 파생물 — schedule_registry.autopilot_id = apmId + scheduleHealth 상태. */
95
106
  function fetchCronDerived(apmId: string, schedulesDb?: string): DerivedJob[] {
96
107
  try {
@@ -98,7 +109,7 @@ function fetchCronDerived(apmId: string, schedulesDb?: string): DerivedJob[] {
98
109
  try {
99
110
  const rows = listSchedules(db);
100
111
  const staleIds = new Set(scheduleHealth(rows).stale.map(s => s.id));
101
- return rows.filter(r => r.autopilot_id === apmId).map(r => cronToDerived(r, staleIds.has(r.id)));
112
+ return cronRowsToDerived(rows.filter(r => r.autopilot_id === apmId), staleIds);
102
113
  } finally { db.close(); }
103
114
  } catch { return []; }
104
115
  }
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // system-lookback.ts 는 순수(의심 소스 매핑 + 프롬프트 빌더)였고, "실제 LLM 조사·소스 read 는
4
4
  // 배선측이 수행"이라 명시돼 있었다. 이 모듈이 그 배선이다: 모순 신호 → 의심 소스 파일 READ-ONLY
5
- // 발췌 → buildLookbackPrompt → Opus(claude-opus-4-8) 조사 → 결함 리포트. 수정 권한 없음(READ-ONLY).
5
+ // 발췌 → buildLookbackPrompt → 역할 audit 이 고른 모델 조사 → 결함 리포트. 수정 권한 없음(READ-ONLY).
6
6
  //
7
7
  // escalate(mission-tool)가 시스템 결함 의심(systemSuspect) 페이즈에서 이 러너를 호출해 결함
8
8
  // 리포트를 얻고, 그 리포트를 수리 미션 골에 실어 스폰한다(system-repair-spawn). 진단 해상도 사다리
@@ -12,18 +12,24 @@ import { readFileSync } from 'node:fs';
12
12
  import { join } from 'node:path';
13
13
  import type { ContradictionSignal } from './contradiction-detector.js';
14
14
  import { buildLookbackPrompt, suspectSourceFiles } from './system-lookback.js';
15
+ import { resolveRoleLlm } from '../user-config.js';
16
+ import { debug } from '../debug/log.js';
15
17
 
16
- /** R3 조사 LLM 호출 seam — 기본은 Opus(claude-opus-4-8·READ-ONLY 진단). 테스트/재사용 위해 주입 가능.
17
- * llmReviewCritique(mission-se-bridge) 와 같은 streamLLM 패턴(모델만 Opus·진단은 길게 maxTokens 2500). */
18
+ /** R3 조사 LLM 호출 seam — 역할 audit 모델(READ-ONLY 진단). 테스트/재사용 위해 주입 가능. */
18
19
  async function opusLookback(prompt: string): Promise<string> {
19
- const { streamLLM, resolveDefaultProvider, getProvider } = await import('../llm.js');
20
- const model = process.env.ELANOUS_LOOKBACK_MODEL || 'claude-opus-4-8';
21
- // ★ claude 모델은 prefix 매칭(getProvider)으로 anthropic 확정(dogfood 2026-07-13 발견) —
22
- // resolveDefaultProvider 는 user-config provider(codex 등)를 모델 무관하게 반환해 claude 를
23
- // codex 로 오라우팅한다(Codex 400: model not supported). 폴백 방어까지.
24
- const provider = model.startsWith('claude-') ? getProvider(model) : resolveDefaultProvider(model);
20
+ const { streamLLM, resolveDefaultProvider, getProvider, PROVIDERS } = await import('../llm.js');
21
+ const override = process.env.ELANOUS_LOOKBACK_MODEL;
22
+ const resolved = override ? undefined : resolveRoleLlm('audit');
23
+ const model = override || resolved!.model;
24
+ // 명시 env 모델의 provider 선택은 종전과 동일하다.
25
+ const provider = override
26
+ ? (model.startsWith('claude-') ? getProvider(model) : resolveDefaultProvider(model))
27
+ : PROVIDERS[resolved!.provider];
28
+ const providerName = override ? provider?.name : resolved!.provider;
29
+ if (!provider) throw new Error(`Unknown audit provider: ${providerName}`);
30
+ debug.log('llm.role-pin', 'resolved', { site: 'system-lookback', role: 'audit', model, provider: providerName, source: override ? 'env' : resolved!.source });
25
31
  return streamLLM([{ role: 'user', content: prompt }], () => {}, {
26
- model, maxTokens: 2500, ...(provider ? { provider } : {}),
32
+ model, provider, usageRole: 'audit', maxTokens: 2500,
27
33
  });
28
34
  }
29
35
 
@@ -0,0 +1,183 @@
1
+ /**
2
+ * BENCH-ISOLATED-ARMS — 같은 골을 모델만 바꿔 여러 «팔»로 쏘는 벤치를 기준선과 떼어 놓는다.
3
+ *
4
+ * S: 벤치는 손 스크립트가 base sha 를 `bench/base-night-<sha12>` 로 push 하고 `harness ask --base` 로 쏜다.
5
+ * C: 그 벤치 가지로 다른 PR 이 squash 병합돼 기준선이 오염됐고(#26022), 팔 PR 이 PR 직전 main 동기화에서
6
+ * merge-conflict 로 «실패»로 셈돼 판정이 흐려졌다.
7
+ * A: 기준선 가지 이름을 `bench/<id>/base` 하나로 고정하고, 그 이름 «자체»를 벤치 표지로 쓴다 —
8
+ * 호스트 → self orchestrate → Pod → 안쪽 harness ask 까지 `--base` 가 이미 흐르므로 새 배관이 없다.
9
+ * - 팔 가지 = `bench/<id>/<arm>` (arm = 자식 모델에서 파생 ⊕ 런 접미 — 같은 모델 n 팔이 안 겹친다)
10
+ * - PR base = `bench/<id>/base` · 라벨 `elanous:bench`
11
+ * - PR 직전 main 동기화는 건너뛴다(관측 `bench`/`main-sync-skipped`) — 판정은 base 위 게이트·시험·리뷰
12
+ * - 원장 = `<config dir>/bench/<id>.jsonl` · 읽기 = `elanous bench ls <id>`
13
+ * ⛔ base 는 «자식 작업 가지의 출발점»(⊕ PR base)으로만 쓰인다 — 부모 cwd 를 base 트리로 옮기지 않는다.
14
+ * 부모·Pod 발사 코드는 지금 도는 코드다(원장 `parentCodeCommit` 이 그 커밋을 base sha 와 따로 적는다).
15
+ * ⛔ 착지 막기는 여기 몫이 아니다(LAND-BASE-MAIN-GUARD · base≠main 재사용 PR 거부).
16
+ */
17
+ import { spawnSync } from 'node:child_process';
18
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
19
+ import { join } from 'node:path';
20
+
21
+ export const BENCH_LABEL = 'elanous:bench';
22
+ export const BENCH_LOG_CATEGORY = 'bench';
23
+
24
+ const BENCH_ID = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
25
+ const SHA = /^[0-9a-f]{7,40}$/i;
26
+
27
+ export interface BenchSpec { readonly id: string; readonly sha?: string }
28
+
29
+ /** `--bench <id>[:<base-sha>]` 를 푼다. ⛔ 모르는 꼴은 «거부»한다 — 조용히 고치면 다른 기준선으로 돈다. */
30
+ export function parseBenchSpec(spec: string): BenchSpec {
31
+ const raw = spec.trim();
32
+ const colon = raw.indexOf(':');
33
+ const id = colon < 0 ? raw : raw.slice(0, colon);
34
+ const sha = colon < 0 ? undefined : raw.slice(colon + 1);
35
+ if (!BENCH_ID.test(id) || id === 'base') {
36
+ throw new Error(`--bench id 가 잘못됐다: ${JSON.stringify(id)} — 영숫자로 시작하는 [A-Za-z0-9._-] 64자 이내(슬래시 금지 · 'base' 금지)`);
37
+ }
38
+ if (sha !== undefined && !SHA.test(sha)) {
39
+ throw new Error(`--bench base sha 가 잘못됐다: ${JSON.stringify(sha)} — 16진 7~40자`);
40
+ }
41
+ return { id, ...(sha !== undefined ? { sha: sha.toLowerCase() } : {}) };
42
+ }
43
+
44
+ export function benchBaseBranch(id: string): string { return `bench/${id}/base`; }
45
+
46
+ function stripRef(ref: string): string {
47
+ return ref.trim().replace(/^refs\/heads\//, '').replace(/^refs\/remotes\/origin\//, '').replace(/^origin\//, '');
48
+ }
49
+
50
+ /** `bench/<id>/base` 면 id, 아니면 null. `origin/`·`refs/heads/` 접두는 벗긴다. */
51
+ export function benchIdFromBase(ref: string | undefined | null): string | null {
52
+ if (!ref) return null;
53
+ const m = /^bench\/([^/]+)\/base$/.exec(stripRef(ref));
54
+ return m && BENCH_ID.test(m[1]!) ? m[1]! : null;
55
+ }
56
+
57
+ /** `bench/<id>/<arm>`(arm ≠ base) 면 {id, arm}, 아니면 null. */
58
+ export function parseBenchArmBranch(ref: string | undefined | null): { id: string; arm: string } | null {
59
+ if (!ref) return null;
60
+ const m = /^bench\/([^/]+)\/([^/]+)$/.exec(stripRef(ref));
61
+ if (!m || m[2] === 'base' || !BENCH_ID.test(m[1]!)) return null;
62
+ return { id: m[1]!, arm: m[2]! };
63
+ }
64
+
65
+ /** 팔 이름 — 자식 모델의 마지막 토막(`openrouter/z-ai/glm-5.3-flash` → `glm-5.3-flash`) ⊕ 런 접미 6자.
66
+ * ⭐ 접미가 있어야 같은 모델 n 팔(sonnet1~3)이 같은 가지로 겹쳐 서로의 PR 을 덮지 않는다. */
67
+ export function benchArmName(childModel: string | undefined, runId: string | undefined): string {
68
+ const last = childModel?.trim().split('/').filter(Boolean).at(-1) ?? '';
69
+ const slug = last.toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^[-.]+|[-.]+$/g, '').slice(0, 40) || 'default';
70
+ const suffix = (runId ?? '').toLowerCase().replace(/^run-/, '').replace(/[^a-z0-9]/g, '').slice(0, 6);
71
+ return suffix ? `${slug}-${suffix}` : slug;
72
+ }
73
+
74
+ /** 벤치 base 위의 런이면 팔 가지 이름, 아니면 undefined(동작 불변). */
75
+ export function benchArmBranchForBase(base: string | undefined, childModel: string | undefined, runId: string | undefined): string | undefined {
76
+ const id = benchIdFromBase(base);
77
+ return id ? `bench/${id}/${benchArmName(childModel, runId)}` : undefined;
78
+ }
79
+
80
+ export type BenchGitRun = (args: readonly string[]) => { status: number | null; stdout: string; stderr: string };
81
+
82
+ export function defaultBenchGitRun(cwd: string): BenchGitRun {
83
+ return (args) => {
84
+ const r = spawnSync('git', [...args], { cwd, encoding: 'utf8', timeout: 120_000 });
85
+ return { status: r.status, stdout: String(r.stdout ?? ''), stderr: String(r.stderr ?? '') };
86
+ };
87
+ }
88
+
89
+ export interface BenchBase { readonly id: string; readonly branch: string; readonly sha: string; readonly created: boolean }
90
+
91
+ /**
92
+ * 기준선 가지를 고정한다 — 없으면 만들고, 있으면 sha 일치를 확인한다(불일치 = 거부).
93
+ * sha 생략: 가지가 «없으면» 발사 시점 origin/main 으로 만들고, «있으면» 그 가지의 sha 를 그대로 쓴다
94
+ * (첫 팔이 정한 기준선을 뒤 팔이 따라간다 — main 이 그 사이 움직여도 팔끼리 같은 땅).
95
+ */
96
+ export function ensureBenchBase(spec: BenchSpec, run: BenchGitRun): BenchBase {
97
+ const branch = benchBaseBranch(spec.id);
98
+ const must = (args: readonly string[], what: string) => {
99
+ const r = run(args);
100
+ if (r.status !== 0) throw new Error(`bench: ${what} 실패 (git ${args.join(' ')} rc=${r.status ?? 'signal'}): ${r.stderr.trim().split('\n')[0] ?? ''}`);
101
+ return r.stdout.trim();
102
+ };
103
+ const trackingFetch = ['fetch', '--no-tags', 'origin', `+refs/heads/${branch}:refs/remotes/origin/${branch}`] as const;
104
+ const remote = run(['ls-remote', '--exit-code', 'origin', `refs/heads/${branch}`]);
105
+ if (remote.status !== 0 && remote.status !== 2) {
106
+ throw new Error(`bench: origin 의 ${branch} 를 못 읽었다 (rc=${remote.status ?? 'signal'}) — 「없다」로 읽지 않는다: ${remote.stderr.trim().split('\n')[0] ?? ''}`);
107
+ }
108
+ const existing = remote.status === 0 ? remote.stdout.trim().split(/\s+/)[0]?.toLowerCase() : undefined;
109
+ if (existing) {
110
+ if (spec.sha && !existing.startsWith(spec.sha)) {
111
+ throw new Error(`bench: ${branch} 가 이미 ${existing.slice(0, 12)} 에 있다 — 요청 ${spec.sha.slice(0, 12)} 와 다르다(거부 · 다른 기준선이면 새 bench id 를 써라)`);
112
+ }
113
+ must(trackingFetch, `${branch} fetch`);
114
+ return { id: spec.id, branch, sha: existing, created: false };
115
+ }
116
+ must(['fetch', '--no-tags', 'origin', 'main'], 'origin main fetch');
117
+ const sha = must(['rev-parse', '--verify', `${spec.sha ?? 'origin/main'}^{commit}`], 'base sha 해석').toLowerCase();
118
+ must(['push', 'origin', `${sha}:refs/heads/${branch}`], `${branch} push`);
119
+ // push 는 원격 추적 ref 를 안 만든다 — 로컬 기질의 worktree 가 `origin/<branch>` 를 찾도록 한 번 당긴다.
120
+ must(trackingFetch, `${branch} fetch`);
121
+ return { id: spec.id, branch, sha, created: true };
122
+ }
123
+
124
+ export interface BenchLedgerRow {
125
+ readonly ts: string;
126
+ readonly event: 'launched' | 'finished';
127
+ readonly benchId: string;
128
+ readonly baseBranch: string;
129
+ readonly baseSha: string;
130
+ /** 부모(발사 프로세스)가 도는 코드 커밋 — base sha 와 따로 적는다(base 는 작업 가지 출발점에만 쓰인다). */
131
+ readonly parentCodeCommit?: string | null;
132
+ /** `harness.imageCommit` (Pod 이미지 pin) — 없으면 null(«못 읽음»과 «없음»을 같은 값으로: 원장엔 설정값만 싣는다). */
133
+ readonly imageCommit: string | null;
134
+ readonly childLlmProvider: string | null;
135
+ readonly childLlmModel: string | null;
136
+ /** 같은 bench id ⊕ 같은 자식 모델의 몇 번째 발사인가(1부터). */
137
+ readonly round: number;
138
+ readonly entrance?: string;
139
+ readonly substrate?: string;
140
+ readonly seat?: string;
141
+ readonly exitCode?: number;
142
+ }
143
+
144
+ export function benchLedgerPath(id: string, configDir: string): string {
145
+ return join(configDir, 'bench', `${id}.jsonl`);
146
+ }
147
+
148
+ export function readBenchLedger(id: string, configDir: string): BenchLedgerRow[] {
149
+ const path = benchLedgerPath(id, configDir);
150
+ if (!existsSync(path)) return [];
151
+ const rows: BenchLedgerRow[] = [];
152
+ for (const line of readFileSync(path, 'utf8').split('\n')) {
153
+ if (!line.trim()) continue;
154
+ try { rows.push(JSON.parse(line) as BenchLedgerRow); } catch { /* 깨진 줄은 건너뛴다 — 수는 renderBenchLs 가 따로 안 센다 */ }
155
+ }
156
+ return rows;
157
+ }
158
+
159
+ export function nextBenchRound(rows: readonly BenchLedgerRow[], childLlmModel: string | null): number {
160
+ return rows.filter((row) => row.event === 'launched' && (row.childLlmModel ?? null) === childLlmModel).length + 1;
161
+ }
162
+
163
+ export function appendBenchLedger(row: BenchLedgerRow, configDir: string): void {
164
+ const path = benchLedgerPath(row.benchId, configDir);
165
+ mkdirSync(join(configDir, 'bench'), { recursive: true, mode: 0o700 });
166
+ appendFileSync(path, `${JSON.stringify(row)}\n`, { mode: 0o600 });
167
+ }
168
+
169
+ export function renderBenchLs(id: string, rows: readonly BenchLedgerRow[]): string {
170
+ if (rows.length === 0) return `bench ${id}: 원장 줄 0 (기록 없음)`;
171
+ const lines = [`bench ${id}: ${rows.length}줄`];
172
+ for (const row of rows) {
173
+ lines.push([
174
+ row.ts, row.event, `round=${row.round}`, `base=${row.baseSha.slice(0, 12)}`,
175
+ `parent=${row.parentCodeCommit ? row.parentCodeCommit.slice(0, 12) : 'unknown'}`,
176
+ `image=${row.imageCommit ? row.imageCommit.slice(0, 12) : 'none'}`,
177
+ `child=${row.childLlmProvider ?? '-'}/${row.childLlmModel ?? '-'}`,
178
+ ...(row.substrate ? [`substrate=${row.substrate}`] : []),
179
+ ...(row.exitCode !== undefined ? [`rc=${row.exitCode}`] : []),
180
+ ].join(' '));
181
+ }
182
+ return lines.join('\n');
183
+ }
@@ -1,12 +1,12 @@
1
1
  // Grok budget fetcher adapter.
2
2
  //
3
3
  // Reuses `fetchGrokUsage` (src/grok/usage.ts). Existing subscription token
4
- // only. Does not infer a subscription-pool remaining/reset and does not
5
- // scrape a web page. Credit figures stay on the credits axis — they are
6
- // never written into RateWindow.remainingPercent.
4
+ // only. Maps an explicitly returned weekly period + creditUsagePercent into
5
+ // the provider's weekly quota window; credit balances remain on the credits axis.
7
6
 
8
7
  import {
9
8
  fetchGrokUsage,
9
+ grokUsageToSubscriptionAxis,
10
10
  grokUsageToCreditAxis,
11
11
  type GrokUsage,
12
12
  } from '../../grok/usage.js';
@@ -36,10 +36,20 @@ export function createGrokFetcher(opts: GrokFetcherOpts = {}): ProviderFetcher {
36
36
 
37
37
  export function grokUsageToSnapshot(usage: GrokUsage, fetchedAt: number = Date.now()): UsageSnapshot {
38
38
  const credits = creditsFromGrokUsage(usage);
39
+ const subscription = grokUsageToSubscriptionAxis(usage);
40
+ const windows = subscription.status === 'available'
41
+ ? [{
42
+ kind: 'weekly' as const,
43
+ windowMinutes: 10_080,
44
+ limit: 100,
45
+ used: 100 - subscription.remainingPercent,
46
+ remainingPercent: subscription.remainingPercent,
47
+ resetsAt: subscription.resetsAt,
48
+ }]
49
+ : [];
39
50
  return {
40
51
  provider: 'grok' as UsageProvider,
41
- // ⛔ 크레딧 사용률을 구독 창 잔량처럼 보이게 하는 RateWindow 를 만들지 않는다.
42
- windows: [],
52
+ windows,
43
53
  ...(credits ? { credits } : {}),
44
54
  fetchedAt,
45
55
  source: 'oauth-api',