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
package/src/llm.ts CHANGED
@@ -21,6 +21,7 @@ import {
21
21
  } from './config.js';
22
22
  import { debug, redactSecrets, redactSecretText } from './debug/log.js';
23
23
  import { llmUsageCostFields } from './budget/llm-cost.js';
24
+ import { accumulateByokUsage } from './llm/openrouter-byok-cost.js';
24
25
  import { ToolCallTiming } from './chat/tool-call-timing.js';
25
26
  import {
26
27
  dispatchWithParallelSafety,
@@ -41,6 +42,8 @@ import {
41
42
  } from './session-runtime/retry-policy.js';
42
43
  import { fetchApiWithRetry, OverloadFailoverError, type FetchApiWithRetryOpts } from './session-runtime/retry-api.js';
43
44
  import { BedrockProvider, isBedrockCompatibleModel, makeBedrockProvider } from './llm/bedrock-provider.js';
45
+ import { makeZaiCodingPlanProvider, zaiCodingPlanAvailable } from './llm/zai-coding-plan.js';
46
+ import { isOpenRouterDeepSeekWireModel, nonEmptyReasoningDetails, openRouterDeepSeekCatalogEffort, openRouterDeepSeekEffort, openRouterDeepSeekRequestExtras, openRouterNeedsKimiToolSchema } from './llm/openrouter-deepseek.js';
44
47
  import { resolveModelAlias } from './intelligence-map/model-alias.js';
45
48
  import { BUILTIN_CATALOG, reasoningEffortCeiling as reasoningEffortCeilingOf } from './intelligence-map/model-catalog.js';
46
49
  import { inferProviderFromModel as registryInferProviderFromModel } from './registry/normalize.js';
@@ -94,7 +97,8 @@ export interface ProviderToolMeta { thoughtSignature?: string; reasoningDetails?
94
97
  export interface ToolArgsInvalid { chars: number; finishReason: string | null }
95
98
 
96
99
  export type ContentBlock =
97
- | { type: 'text'; text: string }
100
+ /** `providerMeta` — OR-DEEPSEEK-TUNE: OpenRouter deepseek/* 의 «도구 없는» assistant 턴 추론(reasoning_details) 운반. 다른 경로는 안 싣는다. */
101
+ | { type: 'text'; text: string; providerMeta?: ProviderToolMeta }
98
102
  | { type: 'image'; mediaType: string; base64: string }
99
103
  | { type: 'audio'; mediaType: string; base64: string }
100
104
  | { type: 'video'; mediaType: string; base64: string }
@@ -318,7 +322,9 @@ export type LLMStreamEvent =
318
322
  source?: string;
319
323
  revisedPrompt?: string;
320
324
  }
321
- | { type: 'usage'; usage: import('./prompt-cache/types.js').LLMUsage };
325
+ | { type: 'usage'; usage: import('./prompt-cache/types.js').LLMUsage }
326
+ /** OR-DEEPSEEK-TUNE — 도구 호출이 없는 턴의 OpenRouter `reasoning_details`(스트림 끝에 한 번). deepseek/* 에서만 나온다. */
327
+ | { type: 'reasoning_details'; details: Array<Record<string, unknown>> };
322
328
 
323
329
  /** Tool-calling forcing (opencode parity · 2026-07-09). `'auto'` (default) lets
324
330
  * the model decide; `'required'` forces at least one tool call; `'none'` blocks
@@ -338,6 +344,8 @@ export interface LLMOpts {
338
344
  onUsage?(usage: LLMUsage): void;
339
345
  /** Optional model role attached to this request's usage observation. */
340
346
  usageRole?: ModelRole;
347
+ /** Optional streamLLM usage-ledger site override (defaults to "stream-llm"). */
348
+ usageSite?: string;
341
349
  /**
342
350
  * ⭐⭐⭐ `B3`(2026-08-19 · 대표 지시 ②) — ***턴 «안»으로 들어오는 사용자 발화의 배수구.***
343
351
  *
@@ -473,6 +481,8 @@ export interface ToOpenAIMessagesOpts {
473
481
  acceptUserMessageImages?: boolean;
474
482
  /** OpenRouter 전용 — 도구 호출이 든 어시스턴트 메시지에 `reasoning_details` 를 되돌린다. 다른 제공자엔 안 싣는다(모르는 칸 400 방지). */
475
483
  echoReasoningDetails?: boolean;
484
+ /** OR-DEEPSEEK-TUNE — deepseek/* 만: 도구 없는 assistant 턴에도 되돌리고, 빈 조각은 걷는다(`src/llm/openrouter-deepseek.ts`). */
485
+ deepSeekReplay?: boolean;
476
486
  }
477
487
 
478
488
  /**
@@ -561,9 +571,10 @@ function toOpenAIMessages(
561
571
  .filter((b): b is Extract<ContentBlock, { type: 'text' }> => b.type === 'text')
562
572
  .map(b => b.text)
563
573
  .join('');
564
- const reasoningDetails = opts.echoReasoningDetails
574
+ const foundDetails = opts.echoReasoningDetails
565
575
  ? toolUses.find((t) => t.providerMeta?.reasoningDetails?.length)?.providerMeta?.reasoningDetails
566
576
  : undefined;
577
+ const reasoningDetails = opts.deepSeekReplay ? nonEmptyReasoningDetails(foundDetails) : foundDetails;
567
578
  return [{
568
579
  role: m.role,
569
580
  content: textContent || null,
@@ -589,8 +600,12 @@ function toOpenAIMessages(
589
600
  // Composer Q3=B is the primary handler; this is the wire-side
590
601
  // defensive fallback for non-vision OpenAI / Grok / local routes.
591
602
  const acceptUserImages = opts.acceptUserMessageImages ?? true;
603
+ const textTurnDetails = opts.deepSeekReplay && m.role === 'assistant'
604
+ ? nonEmptyReasoningDetails(blocks.find((b): b is Extract<ContentBlock, { type: 'text' }> => b.type === 'text' && !!b.providerMeta?.reasoningDetails?.length)?.providerMeta?.reasoningDetails)
605
+ : undefined;
592
606
  return [{
593
607
  role: m.role,
608
+ ...(textTurnDetails ? { reasoning_details: textTurnDetails } : {}),
594
609
  content: blocks.map(block => {
595
610
  if (block.type === 'text') return { type: 'text', text: block.text };
596
611
  if (block.type === 'image') {
@@ -1126,7 +1141,7 @@ export async function* parseOpenAISSELines(
1126
1141
  /** OR-TOOLARGS-REPAIR (2026-10-10) — `flagInvalidToolArgs` 가 참이면(OpenRouter 경로) 인자 JSON 파싱 실패를
1127
1142
  * `{}` 로 «조용히» 바꾸지 않고 `argsInvalid` 표지를 단다(도구 루프가 실행을 막는다) ⊕ 관측을 남긴다.
1128
1143
  * ⛔ 생략·거짓이면 종전과 바이트 동일(다른 공급자 무변경). */
1129
- parseOpts: { flagInvalidToolArgs?: boolean } = {},
1144
+ parseOpts: { flagInvalidToolArgs?: boolean; emitTrailingReasoningDetails?: boolean } = {},
1130
1145
  ): AsyncGenerator<LLMStreamEvent, void, unknown> {
1131
1146
  const flagInvalidToolArgs = parseOpts.flagInvalidToolArgs === true;
1132
1147
  const { parseOpenAIUsage } = await import('./prompt-cache/openai.js');
@@ -1352,6 +1367,11 @@ export async function* parseOpenAISSELines(
1352
1367
  yield { type: 'reasoning', kind: 'inline_delta', delta: tail.reasoningDelta };
1353
1368
  }
1354
1369
  yield* emitHarmonyTools(tail.toolCalls);
1370
+ // OR-DEEPSEEK-TUNE — 도구 호출에 실리지 못한 추론(텍스트만 낸 턴)을 끝에 한 번 낸다. deepseek/* 만 켠다.
1371
+ if (parseOpts.emitTrailingReasoningDetails) {
1372
+ const details = takeReasoningDetails();
1373
+ if (details) yield { type: 'reasoning_details', details };
1374
+ }
1355
1375
  }
1356
1376
 
1357
1377
  /** Pick a stable provider name for `fetchApiWithRetry` telemetry from
@@ -1430,7 +1450,8 @@ async function* streamOpenAIEvents(
1430
1450
  try {
1431
1451
  // OR-TOOLARGS-REPAIR — 깨진 도구 인자 표지는 OpenRouter 엔드포인트에서만 켠다(다른 공급자 무변경).
1432
1452
  const flagInvalidToolArgs = inferOpenAIProviderName(url) === 'openrouter';
1433
- for await (const ev of parseOpenAISSELines(sseLineStream(response.body!), flagInvalidToolArgs ? { flagInvalidToolArgs } : {})) {
1453
+ const emitTrailingReasoningDetails = flagInvalidToolArgs && typeof body?.model === 'string' && isOpenRouterDeepSeekWireModel(body.model);
1454
+ for await (const ev of parseOpenAISSELines(sseLineStream(response.body!), flagInvalidToolArgs ? { flagInvalidToolArgs, ...(emitTrailingReasoningDetails ? { emitTrailingReasoningDetails } : {}) } : {})) {
1434
1455
  if (ev.type === 'text') textChars += ev.delta.length;
1435
1456
  else if (ev.type === 'tool_call') toolCalls++;
1436
1457
  yield ev;
@@ -4294,6 +4315,26 @@ function finalizeStreamingProviderModel(provider: LLMProvider, model: string): {
4294
4315
  return { provider: finalizedProvider, model: finalized.model };
4295
4316
  }
4296
4317
 
4318
+ /** ZAI-POD-CHILD(10-11 실측 · si-task-aaf5d691e4ed) — 레지스트리는 `glm-` 접두를 local 로 본다.
4319
+ * 활성 provider 가 `glm`(`ELANOUS_LLM_PROVIDER=glm` 포함)일 때 모델 이름으로 provider 를 고르는 길
4320
+ * (gate 진단·리뷰 `getProvider(model)`)이 local 로 떨어져 «Local LLM unavailable» 로 죽었다.
4321
+ * ⇒ 그때만 설정 경로(`getProviderForConfig`)를 탄다 — 키가 있으면 Z.ai 공급자, 없으면 OpenRouter 안내 오류.
4322
+ * 활성 provider 가 glm 이 아니면 undefined(종전 그대로). */
4323
+ function activeGlmProviderForModel(model: string): LLMProvider | undefined {
4324
+ if (!model.trim().toLowerCase().startsWith('glm-')) return undefined;
4325
+ let cfg: UserConfig;
4326
+ try {
4327
+ const { getUserConfig } = require('./user-config.js') as typeof import('./user-config.js');
4328
+ cfg = getUserConfig();
4329
+ } catch (e) {
4330
+ debug.log('llm.router', 'getProvider.glm-config-unreadable', { requestedModel: model, error: e instanceof Error ? e.message : String(e) }, { level: 'warn' });
4331
+ return undefined;
4332
+ }
4333
+ if (cfg.llm.provider !== 'glm') return undefined;
4334
+ debug.log('llm.router', 'getProvider.glm-active', { requestedModel: model }, { level: 'info' });
4335
+ return getProviderForConfig(cfg, model);
4336
+ }
4337
+
4297
4338
  /**
4298
4339
  * Pick a provider based on the model name. If model is undefined, use first
4299
4340
  * available provider (preferring DEFAULT_PROVIDER env var if set).
@@ -4305,6 +4346,8 @@ export function getProvider(model?: string): LLMProvider {
4305
4346
  // ids (e.g. `claude-opus-4-7`) so prefix matching works. Existing
4306
4347
  // full ids pass through unchanged.
4307
4348
  const resolved = resolveModelAlias(model) ?? model;
4349
+ const glmDirect = activeGlmProviderForModel(resolved);
4350
+ if (glmDirect) return glmDirect;
4308
4351
  // Preserve the historical Chat Completions route for gpt-* model-only
4309
4352
  // requests, including future ids that the current catalog does not list.
4310
4353
  if (resolved.toLowerCase().startsWith('gpt-')) return PROVIDERS.openai!;
@@ -4443,6 +4486,7 @@ export function buildMessagesWithContext(
4443
4486
  // Re-export converters so tests/adapters can assert wire-format correctness.
4444
4487
  export { toOpenAIMessage, toOpenAIMessages, toAnthropicMessage, systemToAnthropicString };
4445
4488
  export { usesAdaptiveThinking, mapReasoningLevelToAnthropicThinking, mapReasoningLevelToAnthropicEffort, sseLineStream, retryOptsFor }; // src/llm/bedrock-provider.ts
4489
+ export { makeOpenAICompatProvider, makeOpenRouterProvider }; // src/llm/zai-coding-plan.ts
4446
4490
 
4447
4491
  /**
4448
4492
  * Best-effort classification of whether a model accepts image inputs.
@@ -4502,7 +4546,7 @@ export function isLikelyVisionModel(model: string | undefined): boolean {
4502
4546
  // ⭐ OPENROUTER-OPTIMIZE G9 — `reportedCostUsd`(OpenRouter `usage.cost`)도 싣는다. 빠지면 `stream-llm` 행이
4503
4547
  // 실청구액을 잃고 카탈로그 추정(대개 `cost.kind=unknown`)으로 떨어진다(리뷰·저작 행 실측).
4504
4548
  // 합산은 토큰 칸과 같은 규약이다 — OpenAI 호환 스트림은 `usage` 를 마지막 청크에 «한 번» 싣는다(누적 반복 보고 아님).
4505
- type ObservedUsage = Pick<LLMUsage, 'inputTokens' | 'outputTokens' | 'cacheReadInputTokens' | 'cacheCreationInputTokens' | 'reportedCostUsd'>;
4549
+ type ObservedUsage = Pick<LLMUsage, 'inputTokens' | 'outputTokens' | 'cacheReadInputTokens' | 'cacheCreationInputTokens' | 'reportedCostUsd' | 'byok' | 'upstreamCostUsd'>;
4506
4550
 
4507
4551
  type ProviderTextResult =
4508
4552
  | { ok: true; full: string; usage: ObservedUsage | 'unmeasured' }
@@ -4513,7 +4557,7 @@ function accumulateUsage(usage: ObservedUsage | undefined, received: LLMUsage):
4513
4557
  for (const field of ['inputTokens', 'outputTokens', 'cacheReadInputTokens', 'cacheCreationInputTokens', 'reportedCostUsd'] as const) {
4514
4558
  if (received[field] !== undefined) total[field] = (total[field] ?? 0) + received[field];
4515
4559
  }
4516
- return total;
4560
+ return accumulateByokUsage(total, received); // OR-BYOK-COST
4517
4561
  }
4518
4562
 
4519
4563
  async function consumeProviderText(
@@ -4637,7 +4681,7 @@ export async function streamLLM(
4637
4681
  if (consumed.ok) {
4638
4682
  if (consumed.usage !== 'unmeasured') {
4639
4683
  try {
4640
- logAgentTurnUsage(activeModel, consumed.usage, { providerName: activeProvider.name, site: 'stream-llm', role: opts.usageRole });
4684
+ logAgentTurnUsage(activeModel, consumed.usage, { providerName: activeProvider.name, site: opts.usageSite ?? 'stream-llm', role: opts.usageRole });
4641
4685
  } catch { /* best-effort observation */ }
4642
4686
  }
4643
4687
  debug.log('llm', 'outcome', { provider: activeProvider.name, status: 'ok', kind: 'streamLLM', durationMs: Date.now() - started });
@@ -8214,11 +8258,16 @@ export async function streamLLMWithTools(
8214
8258
  },
8215
8259
  };
8216
8260
  }
8261
+ // OR-DEEPSEEK-TUNE — 마지막 턴의 «도구 없는» 추론(deepseek/* 만 채워진다). 매 턴 시작에 비운다.
8262
+ let turnReasoningDetails: Array<Record<string, unknown>> | undefined;
8217
8263
  const emitTurnComplete = (finalAssistantText?: string): void => {
8218
8264
  if (finalAssistantText && finalAssistantText.length > 0) {
8219
8265
  history.push({
8220
8266
  role: 'assistant',
8221
- content: [{ type: 'text', text: finalAssistantText }],
8267
+ content: [{
8268
+ type: 'text', text: finalAssistantText,
8269
+ ...(turnReasoningDetails ? { providerMeta: { reasoningDetails: turnReasoningDetails } } : {}),
8270
+ }],
8222
8271
  });
8223
8272
  }
8224
8273
  handlers.onTurnComplete?.(history.slice(initialHistoryLen));
@@ -8427,6 +8476,7 @@ export async function streamLLMWithTools(
8427
8476
  }
8428
8477
  }
8429
8478
  let turnText = '';
8479
+ turnReasoningDetails = undefined;
8430
8480
  const pendingCalls: Array<{
8431
8481
  id: string;
8432
8482
  name: string;
@@ -8642,6 +8692,8 @@ export async function streamLLMWithTools(
8642
8692
  turnText += ev.delta;
8643
8693
  fullText += ev.delta;
8644
8694
  handlers.onText(ev.delta, fullText);
8695
+ } else if (ev.type === 'reasoning_details') {
8696
+ turnReasoningDetails = ev.details;
8645
8697
  } else if (ev.type === 'tool_call') {
8646
8698
  pendingCalls.push({
8647
8699
  id: ev.id,
@@ -10938,7 +10990,8 @@ function makeOpenAICompatProvider(
10938
10990
  throw new Error(`${name} unavailable: configure apiKey via \`elanous setup\``);
10939
10991
  }
10940
10992
  const activeModel = opts.model || model;
10941
- const tools = toOpenAITools(opts.tools, name === 'openrouter' && openRouterWireModel(activeModel).startsWith('moonshotai/'));
10993
+ const tools = toOpenAITools(opts.tools, name === 'openrouter' && openRouterNeedsKimiToolSchema(openRouterWireModel(activeModel)));
10994
+ const deepSeek = name === 'openrouter' && isOpenRouterDeepSeekWireModel(openRouterWireModel(activeModel));
10942
10995
  // OR-TOOLCHOICE-PASS (2026-10-10) — OpenRouter 경로만 호출자 toolChoice 를 싣는다.
10943
10996
  // ⛔ grok/openai 호환 경로는 손대지 않는다(무회귀). toolChoice 가 없거나 tools 가 없으면 키 자체를 안 싣는다.
10944
10997
  const toolChoice = name === 'openrouter' && tools
@@ -10967,7 +11020,11 @@ function makeOpenAICompatProvider(
10967
11020
  acceptToolImagesViaFollowup: followup,
10968
11021
  acceptUserMessageImages: followup,
10969
11022
  ...(name === 'openrouter' ? { echoReasoningDetails: true } : {}),
11023
+ ...(deepSeek ? { deepSeekReplay: true } : {}),
10970
11024
  }));
11025
+ // OR-DEEPSEEK-TUNE — deepseek/* 만: 설정 기본값의 상류 고정(명시 라우팅이 없을 때) ⊕ 관측 `deepseek-replay`.
11026
+ const deepSeekProvider = deepSeek ? openRouterDeepSeekRequestExtras(openRouterWireModel(activeModel), wireMessages,
11027
+ (wire.extraBody as { provider?: unknown } | undefined)?.provider, cfg.openrouter?.providerPreferences, cfg.openrouter?.upstreamPins) : {};
10971
11028
  // OR-ANTHROPIC-CACHE (2026-10-10) — OpenRouter 뒤 anthropic/* · moonshotai/* 만 cache_control 중단점을 싣는다.
10972
11029
  // ⛔ 다른 OpenRouter 모델과 grok/openai/local 호환 경로는 이 분기를 안 탄다 — 본문 바이트 동일(무회귀).
10973
11030
  const cacheFamily = name === 'openrouter' ? openRouterPromptCacheFamily(openRouterWireModel(activeModel)) : null;
@@ -11013,6 +11070,7 @@ function makeOpenAICompatProvider(
11013
11070
  ...(tools ? { tools } : {}),
11014
11071
  ...(toolChoice !== undefined ? { tool_choice: toolChoice } : {}),
11015
11072
  ...(wire.extraBody ?? {}),
11073
+ ...deepSeekProvider,
11016
11074
  // OpenRouter alone resolves per-call effort against the configured level and catalog default.
11017
11075
  ...(name === 'openrouter'
11018
11076
  ? openRouterReasoningField(activeModel, opts.reasoningEffort, cfg.reasoningLevel)
@@ -11112,7 +11170,7 @@ const OPENROUTER_REASONING_EFFORT: Record<string, 'low' | 'medium' | 'high'> = {
11112
11170
  };
11113
11171
  function openRouterReasoningField(
11114
11172
  model: string, requested: string | undefined, configured: UCLLMConfig['reasoningLevel'],
11115
- ): { reasoning?: { effort: 'low' | 'medium' | 'high' }; verbosity?: 'low' | 'medium' | 'high' } {
11173
+ ): { reasoning?: { effort: 'low' | 'medium' | 'high' | 'xhigh' }; verbosity?: 'low' | 'medium' | 'high' } {
11116
11174
  const wireModel = openRouterWireModel(model);
11117
11175
  // The catalog's model-specific open-weight verdict, not a vendor/slug prefix,
11118
11176
  // gates the default: an unregistered release may be closed-weight.
@@ -11121,13 +11179,18 @@ function openRouterReasoningField(
11121
11179
  (entry) => entry.id === slug && entry.openWeight === true,
11122
11180
  );
11123
11181
  const configEffort = configured === 'off' ? undefined : configured;
11124
- const source = requested !== undefined ? 'call' : configured !== undefined ? 'config' : openWeight ? 'default' : 'none';
11125
- const effort = requested ?? (configured !== undefined ? configEffort : openWeight ? 'medium' : undefined);
11126
- const sent = effort !== undefined && Object.hasOwn(OPENROUTER_REASONING_EFFORT, effort)
11127
- ? OPENROUTER_REASONING_EFFORT[effort] : undefined;
11182
+ // OR-DEEPSEEK-TUNE — deepseek/* 의 기본은 카탈로그 손 레코드(YAML `reasoning`) · 없으면 아래 기존 폴백.
11183
+ const catalogEffort = requested === undefined && configured === undefined ? openRouterDeepSeekCatalogEffort(model) : undefined;
11184
+ const source = requested !== undefined ? 'call' : configured !== undefined ? 'config' : catalogEffort ? 'catalog' : openWeight ? 'default' : 'none';
11185
+ const effort = requested ?? (configured !== undefined ? configEffort : catalogEffort ?? (openWeight ? 'medium' : undefined));
11186
+ // OR-DEEPSEEK-TUNE — DeepSeek V4 는 자기 표(xhigh·max→xhigh · off→칸 없음 · src/llm/openrouter-deepseek.ts).
11187
+ const deepSeekEffort = openRouterDeepSeekEffort(wireModel, effort);
11188
+ const sent = deepSeekEffort !== undefined ? (deepSeekEffort ?? undefined)
11189
+ : effort !== undefined && Object.hasOwn(OPENROUTER_REASONING_EFFORT, effort)
11190
+ ? OPENROUTER_REASONING_EFFORT[effort] : undefined;
11128
11191
  const field = sent ? (wireModel.startsWith('anthropic/') ? 'verbosity' : 'reasoning') : 'none';
11129
11192
  debug.log('llm.openrouter', 'reasoning', { model, requested: requested ?? null, sent: sent ?? null, source, field });
11130
- return field === 'verbosity' ? { verbosity: sent }
11193
+ return field === 'verbosity' ? { verbosity: sent as 'low' | 'medium' | 'high' }
11131
11194
  : field === 'reasoning' ? { reasoning: { effort: sent! } } : {};
11132
11195
  }
11133
11196
 
@@ -11532,8 +11595,16 @@ export function getProviderForConfig(
11532
11595
  model?: string,
11533
11596
  ): LLMProvider {
11534
11597
  const { provider } = userConfig.llm;
11598
+ // CLAUDE-IMPL-LANE(10-11) — `claude-acp` 는 LLM provider 가 아니라 구독 ACP 구현 실행기 선택이다.
11599
+ // 여기서 풀리면 모델 이름(claude-…)이 anthropic(API 키 provider)으로 넘어가 «구독» 런이 API 로 과금된다 → 거부.
11600
+ // 구현은 implement seam 의 claude-acp 갈래(src/self-implement/claude-acp-implement.ts)만 쓴다.
11601
+ if ((provider as string) === 'claude-acp') {
11602
+ throw new Error('claude-acp 는 LLM provider 가 아니다 — 구독 ACP 구현 레인 전용(src/self-implement/claude-acp-implement.ts) · API provider 로 풀지 않는다');
11603
+ }
11535
11604
  // ⛔ 2026-09-23 — `kimi`·`qwen`·`glm` 은 union·사다리에 있지만 «직결» 배선이 없다(사다리 `wip`).
11536
11605
  // 종전엔 `unknown provider: kimi` 로 «아무것도» 안 말하고 죽었다. 이제 실제로 도는 길이 있다(`openrouter`).
11606
+ // ZAI-CODING-PLAN-PROVIDER(대표 10-11) — `ZAI_API_KEY` 가 있을 때만 glm 직결. 없으면 아래 안내 그대로(src/llm/zai-coding-plan.ts).
11607
+ if (provider === 'glm' && zaiCodingPlanAvailable()) return makeZaiCodingPlanProvider({ ...userConfig.llm, model: model || userConfig.llm.model });
11537
11608
  const direct = OPENROUTER_ROUTE_FOR_WIP_PROVIDER[provider as keyof typeof OPENROUTER_ROUTE_FOR_WIP_PROVIDER];
11538
11609
  if (direct) {
11539
11610
  throw new Error(
@@ -1,12 +1,13 @@
1
- import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
2
- import { join, dirname } from 'node:path';
3
- import { randomUUID } from 'node:crypto';
1
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
2
+ import { join, dirname, resolve } from 'node:path';
3
+ import { createHash, randomUUID } from 'node:crypto';
4
4
  import { Database } from 'bun:sqlite';
5
5
  import { debug } from '../debug/log.js';
6
6
  import { effectiveInstanceRoot } from '../instance/resolve.js';
7
7
  import * as registry from './registry.js';
8
8
  import { cronMatches } from '../domains/cron-match.js';
9
9
  import { resolveTimeZone } from '../time/format.js';
10
+ import type { ScheduleRow } from '../domains/schedule-registry.js';
10
11
  import type { LoopEntry, LoopRun } from './registry.js';
11
12
 
12
13
  type LoopState = 'off' | 'unregistered' | 'failing' | 'late' | 'unknown' | 'alive';
@@ -23,6 +24,15 @@ export interface CheckEntry {
23
24
  lastRunAt?: string;
24
25
  lastStatus?: string;
25
26
  recentStatuses?: string[];
27
+ /** Inventory sighting of the scheduled registration (schedule-registry `last_seen`, refreshed on each inventory — not the first sighting); never represented as an execution. */
28
+ observedSince?: string;
29
+ /** Optional launcher evidence; absent values are not inferred from a missing heartbeat. */
30
+ health?: 'healthy' | 'degraded';
31
+ endsAt?: string;
32
+ installedVersion?: string;
33
+ expectedVersion?: string;
34
+ /** Registry graph path, supplied only by graph inventory. */
35
+ file?: string;
26
36
  enabled: boolean;
27
37
  registered: boolean;
28
38
  }
@@ -59,6 +69,110 @@ export function checkLoops(entries: CheckEntry[], now: Date, opts: { failureStat
59
69
  });
60
70
  }
61
71
 
72
+ export type ScheduleFinding = { id: string; owner?: string; kind: 'late' | 'failing' | 'degraded' | 'end-expired' | 'version-mismatch'; reason: string; lastRunAt?: string };
73
+
74
+ /** Additional scheduler evidence is evaluated only when supplied; the six-state CLI contract stays intact. */
75
+ export function scheduleFindings(results: CheckResult[], now: Date): ScheduleFinding[] {
76
+ const findings: ScheduleFinding[] = [];
77
+ for (const row of results) {
78
+ if (!row.enabled || !row.registered) continue;
79
+ const common = { id: row.id, ...(row.owner ? { owner: row.owner } : {}),
80
+ ...(row.lastRunAt ? { lastRunAt: row.lastRunAt } : {}) };
81
+ if (row.state === 'late' || row.state === 'failing') findings.push({ ...common, kind: row.state, reason: row.reason });
82
+ else if (row.state === 'unknown' && !row.lastRunAt && row.observedSince && row.dueAt &&
83
+ Number.isFinite(Date.parse(row.observedSince)) && Number.isFinite(Date.parse(row.dueAt)) &&
84
+ Date.parse(row.observedSince) < Date.parse(row.dueAt))
85
+ findings.push({ ...common, kind: 'late', reason: `no run since observed registration ${row.observedSince}` });
86
+ if (row.health === 'degraded') findings.push({ ...common, kind: 'degraded', reason: 'launcher degraded' });
87
+ if (row.endsAt && Number.isFinite(Date.parse(row.endsAt)) && Date.parse(row.endsAt) <= now.getTime())
88
+ findings.push({ ...common, kind: 'end-expired', reason: `launcher ended at ${row.endsAt}` });
89
+ if (row.installedVersion && row.expectedVersion && row.installedVersion !== row.expectedVersion)
90
+ findings.push({ ...common, kind: 'version-mismatch', reason: `installed ${row.installedVersion}; expected ${row.expectedVersion}` });
91
+ }
92
+ return findings;
93
+ }
94
+
95
+ export function daemonScheduleEntries(rows: readonly ScheduleRow[], now: Date): CheckEntry[] {
96
+ return rows.filter(row => row.enabled && row.cron && (row.run_via === 'elanous' || row.run_via === 'trigger'))
97
+ .map(row => ({ id: row.id, owner: 'OP', enabled: true, registered: true, dueAt: dueBefore(row.cron!, now) ?? undefined,
98
+ ...(row.last_seen ? { observedSince: row.last_seen } : {}),
99
+ ...(row.last_run ? { lastRunAt: row.last_run } : {}),
100
+ ...(row.last_status ? { lastStatus: row.last_status, recentStatuses: [row.last_status] } : {}),
101
+ }));
102
+ }
103
+
104
+ export function versionEvidence(entries: CheckEntry[], repoRoot: string, stateRoot: string): CheckEntry[] {
105
+ return entries.map(entry => {
106
+ if (!entry.file || !/^[\w./-]+\.ya?ml$/.test(entry.file) || entry.file.split('/').includes('..')) return entry;
107
+ const graphPath = resolve(repoRoot, entry.file);
108
+ if (!graphPath.startsWith(resolve(repoRoot) + '/')) return entry;
109
+ const runsPath = join(stateRoot, 'graph-runs', entry.id);
110
+ try {
111
+ if (!existsSync(graphPath) || !existsSync(runsPath)) return entry;
112
+ const latest = readdirSync(runsPath).filter(name => name.endsWith('.json') && !name.endsWith('.decision.json'))
113
+ .sort((a, b) => statSync(join(runsPath, b)).mtimeMs - statSync(join(runsPath, a)).mtimeMs)[0];
114
+ if (!latest) return entry;
115
+ const run = JSON.parse(readFileSync(join(runsPath, latest), 'utf8')) as { graphId?: string; variant?: unknown; graphSnapshot?: { graphSha?: string } };
116
+ if (run.graphId !== entry.id || run.variant || !run.graphSnapshot?.graphSha) return entry;
117
+ const installedVersion = run.graphSnapshot.graphSha;
118
+ const current = createHash('sha256').update(readFileSync(graphPath)).digest('hex');
119
+ return { ...entry, installedVersion, expectedVersion: current };
120
+ } catch (error) {
121
+ debug.log('loop.checker', 'version-unavailable', { id: entry.id, reason: String(error) });
122
+ return entry;
123
+ }
124
+ });
125
+ }
126
+
127
+ export function launcherEvidence(entries: CheckEntry[], root: string): CheckEntry[] {
128
+ const path = join(root, 'msg', 'messages.db');
129
+ if (!existsSync(path)) return entries;
130
+ try {
131
+ const db = new Database(path, { readonly: true });
132
+ try {
133
+ if (!db.query("SELECT 1 FROM sqlite_master WHERE type='table' AND name='loop_signals'").get()) return entries;
134
+ const signals = db.query('SELECT loop_id, health, ends_at FROM loop_signals').all() as Array<{
135
+ loop_id: string; health: string; ends_at: string | null;
136
+ }>;
137
+ const byId = new Map(signals.map(signal => [signal.loop_id, signal]));
138
+ return entries.map(entry => {
139
+ const signal = byId.get(entry.id);
140
+ return signal ? { ...entry, ...(signal.health === 'degraded' ? { health: 'degraded' as const } : {}),
141
+ ...(signal.ends_at ? { endsAt: signal.ends_at } : {}) } : entry;
142
+ });
143
+ } finally { db.close(); }
144
+ } catch (error) {
145
+ debug.log('loop.checker', 'launcher-evidence-unavailable', { reason: String(error) });
146
+ return entries;
147
+ }
148
+ }
149
+
150
+ // Extend only an observed healthy launcher that is still heartbeating. The CAS prevents replacing a renewed lease.
151
+ export function extendHealthyLauncher(root: string, finding: ScheduleFinding, now: Date): boolean {
152
+ if (finding.kind !== 'end-expired' || !Number.isFinite(now.getTime())) return false;
153
+ const path = join(root, 'msg', 'messages.db');
154
+ if (!existsSync(path)) return false;
155
+ const db = new Database(path);
156
+ try {
157
+ db.exec('PRAGMA busy_timeout = 10000');
158
+ db.exec('BEGIN IMMEDIATE');
159
+ try {
160
+ const row = db.query('SELECT at, health, ends_at FROM loop_signals WHERE loop_id=?')
161
+ .get(finding.id) as { at: string; health: string; ends_at: string | null } | null;
162
+ const age = row ? now.getTime() - Date.parse(row.at) : NaN;
163
+ if (!row || row.health !== 'healthy' || !Number.isFinite(age) || age < 0 || age > 15 * 60_000 ||
164
+ !row.ends_at || !Number.isFinite(Date.parse(row.ends_at)) || Date.parse(row.ends_at) > now.getTime()) {
165
+ db.exec('COMMIT');
166
+ return false;
167
+ }
168
+ const result = db.query('UPDATE loop_signals SET ends_at=? WHERE loop_id=? AND at=? AND health=? AND ends_at=?')
169
+ .run(new Date(now.getTime() + 15 * 60_000).toISOString(), finding.id, row.at, row.health, row.ends_at);
170
+ db.exec('COMMIT');
171
+ return result.changes === 1;
172
+ } catch (error) { db.exec('ROLLBACK'); throw error; }
173
+ } finally { db.close(); }
174
+ }
175
+
62
176
  export function countLoopStates(results: CheckResult[]): LoopCounts {
63
177
  const counts: LoopCounts = { alive: 0, late: 0, failing: 0, off: 0, unregistered: 0, unknown: 0 };
64
178
  for (const result of results) counts[result.state]++;
@@ -118,8 +232,14 @@ export interface CheckerDeps {
118
232
  }
119
233
 
120
234
  /** Appends only new daily keys to the seat request journal; never rewrites existing lines. */
121
- export function notifyOwners(results: CheckResult[], deps: CheckerDeps = {}): number {
122
- const eligible = results.filter(result => result.owner && (result.state === 'late' || result.state === 'failing'));
235
+ export function notifyOwners(results: CheckResult[], deps: CheckerDeps = {}, findings: ScheduleFinding[] = [], escalation = false): number {
236
+ const eligible = [
237
+ ...results.filter(result => result.owner && (result.state === 'late' || result.state === 'failing'))
238
+ .map(result => ({ ...result, alertState: result.state })),
239
+ ...findings.filter(result => result.owner && (result.kind !== 'late' && result.kind !== 'failing' ||
240
+ !results.some(row => row.id === result.id && row.state === result.kind)))
241
+ .map(result => ({ ...result, alertState: result.kind })),
242
+ ].filter(result => escalation ? result.owner !== 'OP' : true);
123
243
  if (!eligible.length) return 0;
124
244
  const now = deps.now ?? new Date();
125
245
  const path = join(deps.root ?? effectiveInstanceRoot(), 'seat-requests', 'requests.jsonl');
@@ -132,19 +252,38 @@ export function notifyOwners(results: CheckResult[], deps: CheckerDeps = {}): nu
132
252
  lock.exec('BEGIN EXCLUSIVE');
133
253
  try {
134
254
  const existing = new Set<string>();
255
+ const ownerRequests = new Map<string, { queuedAt?: string; status?: string }>();
135
256
  if (existsSync(path)) {
136
257
  for (const line of readFileSync(path, 'utf8').split('\n')) {
137
258
  if (line.trim()) {
138
- const row = JSON.parse(line) as { key?: string };
139
- if (row.key) existing.add(row.key);
259
+ const row = JSON.parse(line) as { key?: string; queuedAt?: string; status?: string };
260
+ if (row.key) {
261
+ existing.add(row.key);
262
+ ownerRequests.set(row.key, row);
263
+ }
140
264
  }
141
265
  }
142
266
  }
143
267
  let added = 0;
144
268
  for (const result of eligible) {
145
- const key = `loopcheck:${result.id}:${now.toISOString().slice(0, 10)}:${result.state}`;
269
+ const ownerKey = `loopcheck:${result.id}:${now.toISOString().slice(0, 10)}:${result.alertState}`;
270
+ let requestKey = ownerKey;
271
+ if (escalation) {
272
+ const yesterday = new Date(now.getTime() - 24 * 3600_000).toISOString().slice(0, 10);
273
+ const priorKey = `loopcheck:${result.id}:${yesterday}:${result.alertState}`;
274
+ const prior = ownerRequests.get(priorKey);
275
+ if (prior?.status === 'queued' && prior.queuedAt &&
276
+ now.getTime() - Date.parse(prior.queuedAt) >= 30 * 60_000 && !existing.has(`${priorKey}:op`))
277
+ requestKey = priorKey;
278
+ }
279
+ const key = escalation ? `${requestKey}:op` : ownerKey;
146
280
  if (existing.has(key)) continue;
147
- const row = { key, receiptId: key, seat: result.owner, text: `${result.id} ${result.state} — 마지막 ${result.lastRunAt ?? '없음'} · ${result.reason}`,
281
+ if (escalation) {
282
+ const request = ownerRequests.get(requestKey);
283
+ if (!request || request.status !== 'queued' || !request.queuedAt ||
284
+ now.getTime() - Date.parse(request.queuedAt) < 30 * 60_000) continue;
285
+ }
286
+ const row = { key, receiptId: key, seat: escalation ? 'OP' : result.owner, text: `${result.id} ${result.alertState} — 마지막 ${result.lastRunAt ?? '없음'} · ${result.reason}`,
148
287
  status: 'queued', queuedAt: now.toISOString(), source: 'loop-checker' };
149
288
  appendFileSync(path, JSON.stringify(row) + '\n');
150
289
  existing.add(key);
@@ -10,11 +10,15 @@ import {
10
10
  type LlmMergeOutcome, type MergeGitSeam,
11
11
  } from '../../autopilot/build/llm-conflict-merge.js';
12
12
 
13
- type ResyncVerdict = 'saved' | 'merge-ok-tests-failed' | 'merge-ok-no-tests' | 'unresolved' | 'guard-tripped' | 'error';
13
+ type ResyncVerdict = 'saved' | 'merge-ok-tests-failed' | 'merge-ok-no-tests' | 'unresolved' | 'guard-tripped' | 'push-rejected' | 'error';
14
+ export type ResyncShadowMode = 'shadow' | 'live';
14
15
  interface ResyncShadowEntry {
15
16
  at: string;
16
17
  pr: number;
17
18
  head: string;
19
+ mode: ResyncShadowMode;
20
+ pushed: boolean;
21
+ labelRemoved: boolean;
18
22
  verdict: ResyncVerdict;
19
23
  status: LlmMergeOutcome['status'] | 'error';
20
24
  resolvedFiles: string[];
@@ -28,6 +32,7 @@ export interface ResyncShadowDeps {
28
32
  repoRoot?: string;
29
33
  instanceRoot?: string;
30
34
  runGh?: (args: string[]) => string;
35
+ runGit?: (args: string[]) => void;
31
36
  resolve?: (file: string, conflicted: string) => Promise<string>;
32
37
  gitSeam?: MergeGitSeam;
33
38
  runTests?: (cwd: string, files: string[]) => { ok: boolean; failedTests?: string[]; noTests?: boolean };
@@ -134,9 +139,10 @@ function defaultRunTests(cwd: string, files: string[]): { ok: boolean; failedTes
134
139
  return failedTests.length ? { ok: false, failedTests } : { ok: true };
135
140
  }
136
141
 
137
- /** Re-merge open conflicts only in disposable detached worktrees; no remote writes. */
138
- export async function runResyncShadow(max = 3, deps: ResyncShadowDeps = {}): Promise<ResyncShadowSummary> {
142
+ /** Re-merge open conflicts only in disposable detached worktrees; shadow mode never writes to remotes. */
143
+ export async function runResyncShadow(max = 3, deps: ResyncShadowDeps = {}, mode: ResyncShadowMode = 'shadow'): Promise<ResyncShadowSummary> {
139
144
  if (!Number.isSafeInteger(max) || max < 0) throw new Error('--max must be a nonnegative integer');
145
+ if (mode !== 'shadow' && mode !== 'live') throw new Error('mode must be shadow or live');
140
146
  const repoRoot = deps.repoRoot ?? resolve(import.meta.dir, '../../..');
141
147
  const gh = deps.runGh ?? ((args: string[]) => command('gh', args, repoRoot));
142
148
  const observed: unknown = JSON.parse(gh(['pr', 'list', '--state', 'open', '--json', 'number,headRefName,mergeable,isDraft,labels', '--limit', '1000']));
@@ -146,6 +152,7 @@ export async function runResyncShadow(max = 3, deps: ResyncShadowDeps = {}): Pro
146
152
  throw new Error('incomplete or invalid PR observation');
147
153
  const candidates = (observed as OpenPr[]).filter(pr => pr.headRefName.startsWith('self-impl/')
148
154
  && pr.mergeable === 'CONFLICTING'
155
+ && (mode === 'shadow' || pr.labels.some(label => label.name === 'elanous:needs-rebase'))
149
156
  && !pr.labels.some(label => label.name === 'elanous:stalled' || label.name === 'elanous:superseded'))
150
157
  .sort((a, b) => a.number - b.number).slice(0, max);
151
158
  const ledger = join(deps.instanceRoot ?? effectiveInstanceRoot(), 'orchestrator', 'resync-shadow.jsonl');
@@ -154,6 +161,7 @@ export async function runResyncShadow(max = 3, deps: ResyncShadowDeps = {}): Pro
154
161
  for (const pr of candidates) {
155
162
  const entry: ResyncShadowEntry = {
156
163
  at: (deps.now?.() ?? new Date()).toISOString(), pr: pr.number, head: pr.headRefName,
164
+ mode, pushed: false, labelRemoved: false,
157
165
  verdict: 'error', status: 'error', resolvedFiles: [], testFiles: [],
158
166
  };
159
167
  let tmp: string | undefined;
@@ -179,6 +187,21 @@ export async function runResyncShadow(max = 3, deps: ResyncShadowDeps = {}): Pro
179
187
  if (!test.ok) entry.failedTests = test.failedTests?.length ? test.failedTests : ['test runner failed'];
180
188
  }
181
189
  } else entry.verdict = outcome.status === 'conflict-unresolved' || outcome.status === 'up-to-date' ? 'unresolved' : 'error';
190
+ if (mode === 'live' && entry.verdict === 'saved') {
191
+ try {
192
+ (deps.runGit ?? (args => { command('git', args, tmp!); }))(
193
+ ['push', 'origin', `HEAD:refs/heads/${pr.headRefName}`]);
194
+ entry.pushed = true;
195
+ gh(['pr', 'edit', String(pr.number), '--remove-label', 'elanous:needs-rebase']);
196
+ entry.labelRemoved = true;
197
+ } catch (error) {
198
+ const message = error instanceof Error ? error.message : String(error);
199
+ // push 가 이미 됐으면 원격은 살았다 — verdict 는 saved 로 두고 라벨 실패만 남긴다(다음 회차가 같은 PR 을 다시 고를 수 있다).
200
+ // push 실패는 거부 문구(rejected · non-fast-forward)일 때만 push-rejected · 인증·네트워크·시간초과는 error.
201
+ if (!entry.pushed) entry.verdict = /rejected|non-fast-forward|fetch first/i.test(message) ? 'push-rejected' : 'error';
202
+ entry.failedTests = [message];
203
+ }
204
+ }
182
205
  } catch (error) {
183
206
  entry.verdict = 'error';
184
207
  entry.failedTests = [error instanceof Error ? error.message : String(error)];
@@ -199,6 +222,9 @@ export async function runResyncShadow(max = 3, deps: ResyncShadowDeps = {}): Pro
199
222
  mkdirSync(dirname(ledger), { recursive: true });
200
223
  appendFileSync(ledger, `${JSON.stringify(entry)}\n`);
201
224
  debug.log('loop.orchestrator', 'resync-shadow', { pr: entry.pr, verdict: entry.verdict, status: entry.status });
225
+ if (mode === 'live') debug.log('loop.orchestrator', 'resync-live', {
226
+ pr: entry.pr, verdict: entry.verdict, pushed: entry.pushed, labelRemoved: entry.labelRemoved,
227
+ });
202
228
  if (entry.verdict !== 'error') tried++;
203
229
  if (entry.verdict === 'saved') saved++;
204
230
  }
@@ -211,13 +237,14 @@ if (import.meta.main) {
211
237
  try {
212
238
  const args = process.argv.slice(2);
213
239
  if (args.includes('--help')) {
214
- console.log('Usage: bun src/loops/orchestrator/resync-shadow.ts [--max N] [--json]');
240
+ console.log('Usage: bun src/loops/orchestrator/resync-shadow.ts [--max N] [--live] [--json]');
215
241
  } else {
216
242
  const index = args.indexOf('--max');
217
- if (args.some((arg, i) => arg !== '--json' && arg !== '--max' && (index < 0 || i !== index + 1))
218
- || (index >= 0 && (index === args.length - 1 || args.lastIndexOf('--max') !== index))) throw new Error('usage: resync-shadow.ts [--max N] [--json]');
243
+ if (args.some((arg, i) => arg !== '--json' && arg !== '--live' && arg !== '--max' && (index < 0 || i !== index + 1))
244
+ || (index >= 0 && (index === args.length - 1 || args.lastIndexOf('--max') !== index))) throw new Error('usage: resync-shadow.ts [--max N] [--live] [--json]');
219
245
  const max = index < 0 ? 3 : Number(args[index + 1]);
220
- const summary = await runResyncShadow(max);
246
+ const mode: ResyncShadowMode = args.includes('--live') ? 'live' : 'shadow';
247
+ const summary = await runResyncShadow(max, {}, mode);
221
248
  console.log(process.argv.includes('--json') ? JSON.stringify(summary) : `resync-shadow candidates=${summary.candidates} tried=${summary.tried} saved=${summary.saved} savedRatio=${summary.savedRatio === null ? 'n/a' : `${(summary.savedRatio * 100).toFixed(1)}%`}`);
222
249
  }
223
250
  } catch (error) {
@@ -59,8 +59,8 @@ export const FEATURE_MATURITY = {
59
59
  },
60
60
  // Canonical CLI command names only; aliases inherit their command's grade.
61
61
  cliRoot: {
62
- preview: 'beta', pty: 'beta', pr: 'beta', repo: 'beta', role: 'beta', machine: 'beta', 'agent-env': 'beta',
63
- where: 'stable', shadow: 'beta', llm: 'beta', storage: 'beta', leader: 'beta',
62
+ preview: 'beta', pty: 'beta', pr: 'beta', repo: 'beta', role: 'beta', machine: 'beta', image: 'beta', 'agent-env': 'beta',
63
+ where: 'stable', bench: 'beta', shadow: 'beta', llm: 'beta', storage: 'beta', leader: 'beta',
64
64
  pod: 'beta', browser: 'beta', questions: 'beta', usage: 'stable', live: 'beta',
65
65
  research: 'beta', release: 'beta', know: 'beta', freeze: 'beta', flow: 'beta', seat: 'beta', hq: 'beta', steward: 'beta', coord: 'beta', context: 'beta', 'model-watch': 'beta', doctor: 'stable',
66
66
  control: 'beta', resources: 'beta', hooks: 'beta', setup: 'stable', start: 'stable',
@@ -425,12 +425,16 @@ const OPENROUTER: TierMap = {
425
425
  // ⛔ bedrock 은 명시 선택 전용 — 이 표는 `--role-llm <role>=bedrock/<tier>` 같은 «명시» 해석에만 쓰인다.
426
426
  // ⚠️ status = `wip`: 실 AWS 호출(계정별 5.5 접근 조건)을 아직 안 쟀고, 셋업 키 감지 표(PROVIDER_ENV_SPEC)에도
427
427
  // 없다(자격이 API 키가 아니라 AWS 체인이라). 첫 실호출 스모크 뒤 `shipping` 으로 올린다.
428
+ // ⭐ BEDROCK-RUNTIME-PATH(10-11): 메시지 엔드포인트(mantle)에 5.5 가 없어(📏 `GET /v1/models`) 사다리 id 를 전역 추론 프로필
429
+ // `global.anthropic.…`(runtime 경로 · bedrock.ts BEDROCK_MODELS 의 `route` 칸)로 옮겼다. 공급자 이름은 그대로 `bedrock`.
430
+ // 📏 budget·balanced(haiku/sonnet 5.5)는 runtime 실호출 200 실측(ap-northeast-2) · opus 5.5 는 «추론 프로필 목록에 있음»까지만(호출 0).
431
+ // status 는 그대로 `wip` — `shipping` 은 셋업 키 감지 표(PROVIDER_ENV_SPEC) 칸을 요구한다(provider-wiring-contract 시험 · 범위 밖).
428
432
  const BEDROCK: TierMap = {
429
- budget: { model: 'anthropic.claude-haiku-5-5', reasoningLevel: 'off', label: 'Claude Haiku 5.5 (Bedrock)', rationale: 'Fast · cheap · bulk/classification', status: 'wip' },
430
- balanced: { model: 'anthropic.claude-sonnet-5-5', reasoningLevel: 'off', label: 'Claude Sonnet 5.5 (Bedrock)', rationale: 'Default · everyday coding (BEDROCK_DEFAULT_MODEL)', status: 'wip' },
431
- better: { model: 'anthropic.claude-opus-5-5', reasoningLevel: 'medium', label: 'Claude Opus 5.5 · thinking medium (Bedrock)', rationale: 'ANTHROPIC better 와 같은 배치(구현 = Opus 5.5)', status: 'wip' },
432
- best: { model: 'anthropic.claude-opus-5-5', reasoningLevel: 'medium', label: 'Claude Opus 5.5 · thinking medium (Bedrock)', rationale: 'Best reasoning · architecture', status: 'wip' },
433
- loaded: { model: 'anthropic.claude-opus-5-5', reasoningLevel: 'high', label: 'Claude Opus 5.5 · extended thinking (Bedrock)', rationale: 'Loaded · deep multi-step', status: 'wip' },
433
+ budget: { model: 'global.anthropic.claude-haiku-5-5', reasoningLevel: 'off', label: 'Claude Haiku 5.5 (Bedrock)', rationale: 'Fast · cheap · bulk/classification', status: 'wip' },
434
+ balanced: { model: 'global.anthropic.claude-sonnet-5-5', reasoningLevel: 'off', label: 'Claude Sonnet 5.5 (Bedrock)', rationale: 'Default · everyday coding (BEDROCK_DEFAULT_MODEL)', status: 'wip' },
435
+ better: { model: 'global.anthropic.claude-opus-5-5', reasoningLevel: 'medium', label: 'Claude Opus 5.5 · thinking medium (Bedrock)', rationale: 'ANTHROPIC better 와 같은 배치(구현 = Opus 5.5)', status: 'wip' },
436
+ best: { model: 'global.anthropic.claude-opus-5-5', reasoningLevel: 'medium', label: 'Claude Opus 5.5 · thinking medium (Bedrock)', rationale: 'Best reasoning · architecture', status: 'wip' },
437
+ loaded: { model: 'global.anthropic.claude-opus-5-5', reasoningLevel: 'high', label: 'Claude Opus 5.5 · extended thinking (Bedrock)', rationale: 'Loaded · deep multi-step', status: 'wip' },
434
438
  };
435
439
 
436
440
  /** 가격·창 미측정이라 기존 tier 배치를 바꾸지 않고 명시 모델로만 고르는 칸.