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
@@ -56,6 +56,30 @@ export function resolveInstance(deps: ResolveDeps = {}): InstanceResolution {
56
56
  return { kind: 'test', root: normRoot(join(process.cwd(), '.elanous-test')), layer: 'default', why: '소스 트리 밖 실행(격리)' };
57
57
  }
58
58
 
59
+ /** 부모가 도장으로 물려준 우주와 자식이 실제로 쓰는 우주가 다른가. 비어 있는 쪽은 비교하지 않는다. */
60
+ export function universeMismatch(parentRoot: string | undefined, childRoot: string | undefined): { parentRoot: string; childRoot: string } | null {
61
+ const parent = parentRoot?.trim();
62
+ const child = childRoot?.trim();
63
+ if (!parent || !child) return null;
64
+ const p = normRoot(parent);
65
+ const c = normRoot(child);
66
+ return p === c ? null : { parentRoot: p, childRoot: c };
67
+ }
68
+
69
+ export type UniverseMeasure = { measurable: true } | { measurable: false; reason: '빈 우주' };
70
+
71
+ /** 읽을 우주가 비어 있으면 «0» 이 아니라 «측정 불가» 로 말해야 한다. 디렉터리가 없거나 항목이 하나도 없으면 빈 우주다. */
72
+ export function measureUniverse(root: string, entries: (dir: string) => string[] = defaultEntries): UniverseMeasure {
73
+ return entries(root).length > 0 ? { measurable: true } : { measurable: false, reason: '빈 우주' };
74
+ }
75
+
76
+ function defaultEntries(dir: string): string[] {
77
+ try {
78
+ const { readdirSync } = require('node:fs') as typeof import('node:fs');
79
+ return readdirSync(dir);
80
+ } catch { return []; }
81
+ }
82
+
59
83
  /** 명시 config-dir 이 없으면 state-dir 과 config-dir 을 같은 우주로 유지한다. */
60
84
  export function configDirFollowingStateDir(
61
85
  explicitConfigDir: string | undefined,
@@ -0,0 +1,45 @@
1
+ // 자식 elanous 기동의 단일 입구. 우주는 부모가 한 번 정해 ELANOUS_STATE_DIR 도장으로 물려준다.
2
+ import { spawn, spawnSync, type ChildProcess, type SpawnOptions, type SpawnSyncOptions, type SpawnSyncReturns } from 'node:child_process';
3
+ import { resolve } from 'node:path';
4
+ import { stateDirSourceForChild } from '../agent/identity-env.js';
5
+ import { effectiveInstanceRoot } from './resolve.js';
6
+
7
+ const DEFAULT_BIN = resolve(import.meta.dir, '../../bin/elanous.mjs');
8
+
9
+ export interface SpawnElanousOptions {
10
+ /** 자식이 쓸 우주. 생략하면 부모의 effectiveInstanceRoot(). 명시한 값이 이긴다. */
11
+ stateDir?: string;
12
+ /** 실행할 elanous 진입 스크립트. 생략하면 이 트리의 bin/elanous.mjs. */
13
+ bin?: string;
14
+ cwd?: string;
15
+ env?: NodeJS.ProcessEnv;
16
+ }
17
+
18
+ export interface ElanousInvocation {
19
+ command: string;
20
+ args: string[];
21
+ env: NodeJS.ProcessEnv;
22
+ }
23
+
24
+ /** 자식 기동 명세를 만든다. env 는 부모 env 위에 우주 도장(뿌리 ⊕ 출처)을 얹은 것이다. */
25
+ export function elanousInvocation(args: readonly string[], opts: SpawnElanousOptions = {}): ElanousInvocation {
26
+ const root = opts.stateDir?.trim() || effectiveInstanceRoot();
27
+ const base = opts.env ?? process.env;
28
+ return {
29
+ command: process.execPath,
30
+ args: [opts.bin ?? DEFAULT_BIN, ...args],
31
+ env: { ...base, ELANOUS_STATE_DIR: root, ELANOUS_STATE_DIR_SOURCE: stateDirSourceForChild(root, base) },
32
+ };
33
+ }
34
+
35
+ export function spawnElanous(args: readonly string[], opts: SpawnElanousOptions & Omit<SpawnOptions, 'env' | 'cwd'> = {}): ChildProcess {
36
+ const { stateDir: _s, bin: _b, env: _e, ...rest } = opts;
37
+ const inv = elanousInvocation(args, opts);
38
+ return spawn(inv.command, inv.args, { ...rest, cwd: opts.cwd, env: inv.env });
39
+ }
40
+
41
+ export function spawnElanousSync(args: readonly string[], opts: SpawnElanousOptions & Omit<SpawnSyncOptions, 'env' | 'cwd'> = {}): SpawnSyncReturns<string | Buffer> {
42
+ const { stateDir: _s, bin: _b, env: _e, ...rest } = opts;
43
+ const inv = elanousInvocation(args, opts);
44
+ return spawnSync(inv.command, inv.args, { ...rest, cwd: opts.cwd, env: inv.env });
45
+ }
@@ -0,0 +1,110 @@
1
+ import type { RegisteredIntakeSource } from './intake-sources.js';
2
+
3
+ /** listChecklist(v).items 꼴 — 호출 쪽이 다음 두 판 칸을 모아 넘긴다. */
4
+ export interface PrefetchCell {
5
+ id: string;
6
+ title: string;
7
+ status: string;
8
+ refs?: readonly string[];
9
+ /** 그 판 날짜(YYYY-MM-DD). 주제 until 로 실린다. */
10
+ until?: string;
11
+ }
12
+
13
+ export interface PrefetchTopic {
14
+ topic: string;
15
+ cellIds: string[];
16
+ until?: string;
17
+ }
18
+
19
+ export interface PrefetchTopicsOpts {
20
+ max?: number;
21
+ }
22
+
23
+ /**
24
+ * 의도적 경계: 이 파일은 제안만 만든다. intake CLI `prefetch --dry-run|--apply` 와
25
+ * REQ-LEDGER 테마 원천 교체는 다음 조각 — addSource · CLI 를 부르지 않는다.
26
+ */
27
+ function backtickTokens(title: string): string[] {
28
+ const tokens: string[] = [];
29
+ const re = /`([^`]+)`/g;
30
+ for (let match = re.exec(title); match; match = re.exec(title)) {
31
+ const token = match[1]!.trim();
32
+ if (token) tokens.push(token);
33
+ }
34
+ return tokens;
35
+ }
36
+
37
+ function englishProperNouns(title: string): string[] {
38
+ const stripped = title.replace(/`[^`]*`/g, ' ');
39
+ const tokens: string[] = [];
40
+ // CamelCase · 내부 대문자 식별자, 또는 2글자 이상 대문자(하이픈 칸 id 포함).
41
+ const re = /\b(?:[A-Z][A-Za-z0-9]*[A-Z][A-Za-z0-9]*|[A-Z]{2,}(?:-[A-Z0-9]+)*)\b/g;
42
+ for (let match = re.exec(stripped); match; match = re.exec(stripped)) tokens.push(match[0]);
43
+ return tokens;
44
+ }
45
+
46
+ function rfcNames(refs: readonly string[] | undefined): string[] {
47
+ if (!refs) return [];
48
+ const names: string[] = [];
49
+ for (const ref of refs) {
50
+ const file = (ref.split('#')[0] ?? '').split('/').pop() ?? '';
51
+ const stem = file.replace(/\.[A-Za-z0-9]+$/, '').replace(/-\d{4}-\d{2}-\d{2}$/, '');
52
+ if (stem.startsWith('RFC-') && stem.length > 4) names.push(stem);
53
+ }
54
+ return names;
55
+ }
56
+
57
+ function laterDate(a: string | undefined, b: string | undefined): string | undefined {
58
+ if (!a) return b;
59
+ if (!b) return a;
60
+ return a >= b ? a : b;
61
+ }
62
+
63
+ function sourceId(topic: string): string {
64
+ const slug = topic.replace(/[^\w.-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 48);
65
+ return `ref-prefetch-${slug || 'topic'}`;
66
+ }
67
+
68
+ export function prefetchTopics(cells: readonly PrefetchCell[], opts: PrefetchTopicsOpts = {}): PrefetchTopic[] {
69
+ const max = opts.max ?? 5;
70
+ const byTopic = new Map<string, { topic: string; count: number; cellIds: string[]; until?: string }>();
71
+ const order: string[] = [];
72
+ for (const cell of cells) {
73
+ if (cell.status !== 'yellow') continue;
74
+ const tokens = new Set([
75
+ ...englishProperNouns(cell.title),
76
+ ...backtickTokens(cell.title),
77
+ ...rfcNames(cell.refs),
78
+ ]);
79
+ for (const topic of tokens) {
80
+ let acc = byTopic.get(topic);
81
+ if (!acc) {
82
+ acc = { topic, count: 0, cellIds: [], until: cell.until };
83
+ byTopic.set(topic, acc);
84
+ order.push(topic);
85
+ }
86
+ acc.count++;
87
+ if (!acc.cellIds.includes(cell.id)) acc.cellIds.push(cell.id);
88
+ acc.until = laterDate(acc.until, cell.until);
89
+ }
90
+ }
91
+ return order
92
+ .map((topic) => byTopic.get(topic)!)
93
+ .sort((a, b) => b.count - a.count || order.indexOf(a.topic) - order.indexOf(b.topic))
94
+ .slice(0, Math.max(0, max))
95
+ .map(({ topic, cellIds, until }) => until === undefined ? { topic, cellIds } : { topic, cellIds, until });
96
+ }
97
+
98
+ export function prefetchSources(topics: readonly PrefetchTopic[], seat: string, now: Date): RegisteredIntakeSource[] {
99
+ // 판 날짜를 모르면 until 을 비운다 — now 로 채우면 untilMs(그날 00:00Z) 기준으로 이미 만료된 제안이 된다.
100
+ void now;
101
+ return topics.map((entry) => ({
102
+ id: sourceId(entry.topic),
103
+ seat,
104
+ kind: 'github-query',
105
+ spec: entry.topic,
106
+ every: '1d',
107
+ ...(entry.until === undefined ? {} : { until: entry.until }),
108
+ why: `REF-PREFETCH ⊕ ${entry.cellIds.join(' ')}`,
109
+ }));
110
+ }
@@ -620,6 +620,78 @@ export const BUILTIN_CATALOG: ModelCatalog = {
620
620
  notes: 'Apache 2.0 · 397B MoE · Q4 ~220GB · M3 Ultra 512GB only',
621
621
  },
622
622
 
623
+ // ── DeepSeek V4 · OpenRouter `openrouter/deepseek/<id>` 전용 (OR-DEEPSEEK-TUNE · 2026-10-11)
624
+ // id 는 OpenRouter slug(`deepseek/` 뒤) 그대로 — llm.ts openRouterReasoningField 가 이 id 로 openWeight 를
625
+ // 찾는다. 기본 effort 는 YAML 손 레코드(catalog/models/openrouter/ · #26055)의 `reasoning` 이 정본이고,
626
+ // 레코드가 없을 때만 이 openWeight 폴백(medium)을 탄다. 단가 = deepseek 공급자 엔드포인트 평시가
627
+ // (v4.1-flash·v4-pro-0813 은 #26055 와 같은 값) · 나머지는 /api/v1/models 목록가(2026-10-11 실측).
628
+ // effort 상한 xhigh(OpenRouter 는 max 를 400 으로 거부 → src/llm/openrouter-deepseek.ts 가 max→xhigh).
629
+ // provider 'other' — 직결 deepseek 공급자는 없다(새 공급자 이름을 만들지 않는다).
630
+ {
631
+ id: 'deepseek-v4-flash',
632
+ provider: 'other',
633
+ family: 'deepseek',
634
+ contextWindow: 1_048_576,
635
+ inputPerMtok: 0.03,
636
+ outputPerMtok: 1.28,
637
+ local: false,
638
+ openWeight: true,
639
+ localPullable: false,
640
+ tags: ['coding', 'moe', 'cheap', 'open-weight', 'long-context'],
641
+ bestFor: ['cheap-agentic', 'open-weight-bench'],
642
+ reasoningEffortCeiling: 'xhigh',
643
+ releasedAt: '2026-04-24',
644
+ notes: 'OpenRouter deepseek/deepseek-v4-flash · 1M ctx',
645
+ },
646
+ {
647
+ id: 'deepseek-v4.1-flash',
648
+ provider: 'other',
649
+ family: 'deepseek',
650
+ contextWindow: 1_048_576,
651
+ inputPerMtok: 0.15,
652
+ outputPerMtok: 0.60,
653
+ local: false,
654
+ openWeight: true,
655
+ localPullable: false,
656
+ tags: ['coding', 'moe', 'cheap', 'open-weight', 'long-context'],
657
+ bestFor: ['cheap-agentic', 'open-weight-bench'],
658
+ reasoningEffortCeiling: 'xhigh',
659
+ releasedAt: '2026-09-10',
660
+ notes: 'OpenRouter deepseek/deepseek-v4.1-flash · 1M ctx',
661
+ },
662
+ {
663
+ id: 'deepseek-v4-pro',
664
+ provider: 'other',
665
+ family: 'deepseek',
666
+ contextWindow: 1_048_576,
667
+ inputPerMtok: 0.2088,
668
+ outputPerMtok: 0.4176,
669
+ local: false,
670
+ openWeight: true,
671
+ localPullable: false,
672
+ tags: ['coding', 'moe', 'open-weight', 'flagship', 'long-context'],
673
+ bestFor: ['open-frontier', 'open-weight-bench'],
674
+ reasoningEffortCeiling: 'xhigh',
675
+ releasedAt: '2026-04-24',
676
+ notes: 'OpenRouter deepseek/deepseek-v4-pro · 0423 Preview 별칭 — 고정판은 deepseek-v4-pro-0813',
677
+ },
678
+ {
679
+ id: 'deepseek-v4-pro-0813',
680
+ provider: 'other',
681
+ family: 'deepseek',
682
+ contextWindow: 1_048_576,
683
+ inputPerMtok: 0.66,
684
+ outputPerMtok: 1.98,
685
+ local: false,
686
+ openWeight: true,
687
+ localPullable: false,
688
+ tags: ['coding', 'moe', 'open-weight', 'flagship', 'long-context'],
689
+ bestFor: ['open-frontier', 'open-weight-bench'],
690
+ reasoningEffortCeiling: 'xhigh',
691
+ releasedAt: '2026-08-13',
692
+ notes: 'OpenRouter deepseek/deepseek-v4-pro-0813 · 날짜 고정판',
693
+ },
694
+
623
695
  // ── GLM 5.x (Zhipu) · `api.z.ai` / `open.bigmodel.cn` · OpenAI-compat
624
696
  // GLM-5.1 (2026-04-07) is the current flagship — 754B MoE / 40B
625
697
  // active under MIT. All recent GLM models (4.6+) are open-weight,
@@ -0,0 +1,215 @@
1
+ // ── BEDROCK-RUNTIME-PATH — AWS eventstream(`application/vnd.amazon.eventstream`) 디코더 (2026-10-11) ──
2
+ //
3
+ // `InvokeModelWithResponseStream` 의 응답은 SSE 가 아니라 AWS eventstream «이진 프레임»이다.
4
+ // 📏 레포에 `@smithy/eventstream-codec` 이 없다(rg · node_modules/@smithy 전수) ⇒ 의존성을 늘리지 않고 작은 디코더를 둔다.
5
+ //
6
+ // 프레임 꼴(빅엔디언):
7
+ // [total length u32][headers length u32][prelude CRC32 u32][headers …][payload …][message CRC32 u32]
8
+ // prelude CRC = 앞 8바이트의 CRC32 · message CRC = 마지막 4바이트를 뺀 전부의 CRC32.
9
+ // 헤더: [name len u8][name utf8][type u8][value …] — type 7(string)·6(bytes)은 u16 길이 접두.
10
+ //
11
+ // ⛔ CRC 가 안 맞거나 길이가 말이 안 되면 «조용히 건너뛰지 않고» 고정 이름의 에러로 멈춘다 —
12
+ // 깨진 프레임을 JSON 으로 읽어 반쪽 답을 내는 것보다 실패가 낫다.
13
+
14
+ /** 디코더 에러 — 고정 이름(`BedrockEventStreamError`). 메시지에 페이로드 원문을 싣지 않는다. */
15
+ export class BedrockEventStreamError extends Error {
16
+ constructor(reason: string) {
17
+ super(`Bedrock eventstream: ${reason}`);
18
+ this.name = 'BedrockEventStreamError';
19
+ }
20
+ }
21
+
22
+ const CRC_TABLE: Uint32Array = (() => {
23
+ const t = new Uint32Array(256);
24
+ for (let n = 0; n < 256; n++) {
25
+ let c = n;
26
+ for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
27
+ t[n] = c >>> 0;
28
+ }
29
+ return t;
30
+ })();
31
+
32
+ /** CRC32(IEEE 802.3 · zlib 과 같은 다항식). */
33
+ export function crc32(bytes: Uint8Array): number {
34
+ let c = 0xffffffff;
35
+ for (let i = 0; i < bytes.length; i++) c = CRC_TABLE[(c ^ bytes[i]!) & 0xff]! ^ (c >>> 8);
36
+ return (c ^ 0xffffffff) >>> 0;
37
+ }
38
+
39
+ export type EventStreamHeaderValue = string | number | boolean | bigint | Uint8Array;
40
+
41
+ export interface EventStreamMessage {
42
+ headers: Record<string, EventStreamHeaderValue>;
43
+ payload: Uint8Array;
44
+ }
45
+
46
+ const PRELUDE = 12;
47
+ const MIN_FRAME = PRELUDE + 4;
48
+ /** 한 프레임 상한 — AWS 문서의 메시지 최대(16 MiB)와 같다. 넘으면 길이 칸이 깨진 것으로 본다. */
49
+ const MAX_FRAME = 16 * 1024 * 1024;
50
+
51
+ function decodeHeaders(bytes: Uint8Array): Record<string, EventStreamHeaderValue> {
52
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
53
+ const text = new TextDecoder();
54
+ const out: Record<string, EventStreamHeaderValue> = {};
55
+ let i = 0;
56
+ const need = (n: number) => { if (i + n > bytes.length) throw new BedrockEventStreamError('헤더가 잘렸다'); };
57
+ while (i < bytes.length) {
58
+ need(1);
59
+ const nameLen = bytes[i]!; i += 1;
60
+ need(nameLen);
61
+ const name = text.decode(bytes.subarray(i, i + nameLen)); i += nameLen;
62
+ need(1);
63
+ const type = bytes[i]!; i += 1;
64
+ switch (type) {
65
+ case 0: out[name] = true; break;
66
+ case 1: out[name] = false; break;
67
+ case 2: need(1); out[name] = view.getInt8(i); i += 1; break;
68
+ case 3: need(2); out[name] = view.getInt16(i); i += 2; break;
69
+ case 4: need(4); out[name] = view.getInt32(i); i += 4; break;
70
+ case 5: need(8); out[name] = view.getBigInt64(i); i += 8; break;
71
+ case 6:
72
+ case 7: {
73
+ need(2);
74
+ const len = view.getUint16(i); i += 2;
75
+ need(len);
76
+ const v = bytes.subarray(i, i + len); i += len;
77
+ out[name] = type === 7 ? text.decode(v) : v.slice();
78
+ break;
79
+ }
80
+ case 8: need(8); out[name] = view.getBigInt64(i); i += 8; break;
81
+ case 9: need(16); out[name] = bytes.slice(i, i + 16); i += 16; break;
82
+ default: throw new BedrockEventStreamError(`모르는 헤더 형식 ${type}`);
83
+ }
84
+ }
85
+ return out;
86
+ }
87
+
88
+ /** 프레임 «하나»를 푼다(정확히 한 프레임 길이의 바이트). CRC 둘 다 검사한다. */
89
+ export function decodeEventStreamFrame(frame: Uint8Array): EventStreamMessage {
90
+ if (frame.length < MIN_FRAME) throw new BedrockEventStreamError('프레임이 잘렸다');
91
+ const view = new DataView(frame.buffer, frame.byteOffset, frame.byteLength);
92
+ const total = view.getUint32(0);
93
+ const headersLen = view.getUint32(4);
94
+ if (total !== frame.length) throw new BedrockEventStreamError('프레임 길이가 맞지 않는다');
95
+ if (view.getUint32(8) !== crc32(frame.subarray(0, 8))) throw new BedrockEventStreamError('prelude CRC 불일치');
96
+ if (view.getUint32(total - 4) !== crc32(frame.subarray(0, total - 4))) throw new BedrockEventStreamError('message CRC 불일치');
97
+ if (PRELUDE + headersLen > total - 4) throw new BedrockEventStreamError('헤더 길이가 프레임을 넘는다');
98
+ const headers = decodeHeaders(frame.subarray(PRELUDE, PRELUDE + headersLen));
99
+ const payload = frame.slice(PRELUDE + headersLen, total - 4);
100
+ return { headers, payload };
101
+ }
102
+
103
+ /** 바이트 청크 스트림 → 프레임 스트림. 청크 경계가 프레임 경계와 무관해도 된다.
104
+ * 스트림이 프레임 «중간»에서 끝나면 `프레임이 잘렸다` 로 멈춘다. */
105
+ export async function* decodeEventStream(
106
+ chunks: AsyncIterable<Uint8Array> | ReadableStream<Uint8Array>,
107
+ ): AsyncGenerator<EventStreamMessage, void, unknown> {
108
+ let buf = new Uint8Array(0);
109
+ const iterable: AsyncIterable<Uint8Array> = (chunks as ReadableStream<Uint8Array>).getReader
110
+ ? readerIterable(chunks as ReadableStream<Uint8Array>)
111
+ : (chunks as AsyncIterable<Uint8Array>);
112
+ for await (const chunk of iterable) {
113
+ if (!chunk || chunk.length === 0) continue;
114
+ const next = new Uint8Array(buf.length + chunk.length);
115
+ next.set(buf, 0);
116
+ next.set(chunk, buf.length);
117
+ buf = next;
118
+ while (buf.length >= PRELUDE) {
119
+ const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
120
+ const total = view.getUint32(0);
121
+ if (total < MIN_FRAME || total > MAX_FRAME) throw new BedrockEventStreamError('프레임 길이 칸이 깨졌다');
122
+ // 길이 칸을 믿기 «전»에 prelude CRC 부터 — 깨진 길이로 끝없이 기다리지 않게.
123
+ if (view.getUint32(8) !== crc32(buf.subarray(0, 8))) throw new BedrockEventStreamError('prelude CRC 불일치');
124
+ if (buf.length < total) break;
125
+ yield decodeEventStreamFrame(buf.subarray(0, total));
126
+ buf = buf.slice(total);
127
+ }
128
+ }
129
+ if (buf.length > 0) throw new BedrockEventStreamError('프레임이 잘렸다');
130
+ }
131
+
132
+ async function* readerIterable(stream: ReadableStream<Uint8Array>): AsyncGenerator<Uint8Array, void, unknown> {
133
+ const reader = stream.getReader();
134
+ try {
135
+ for (;;) {
136
+ const { done, value } = await reader.read();
137
+ if (done) return;
138
+ if (value) yield value;
139
+ }
140
+ } finally {
141
+ reader.releaseLock();
142
+ }
143
+ }
144
+
145
+ /** 시험·도구용 인코더 — 디코더의 거울(문자열 헤더만). 실 송신 경로에서는 쓰지 않는다. */
146
+ export function encodeEventStreamFrame(headers: Record<string, string>, payload: Uint8Array): Uint8Array {
147
+ const enc = new TextEncoder();
148
+ const parts: number[] = [];
149
+ for (const [name, value] of Object.entries(headers)) {
150
+ const n = enc.encode(name);
151
+ const v = enc.encode(value);
152
+ parts.push(n.length, ...n, 7, (v.length >> 8) & 0xff, v.length & 0xff, ...v);
153
+ }
154
+ const headerBytes = Uint8Array.from(parts);
155
+ const total = PRELUDE + headerBytes.length + payload.length + 4;
156
+ const out = new Uint8Array(total);
157
+ const view = new DataView(out.buffer);
158
+ view.setUint32(0, total);
159
+ view.setUint32(4, headerBytes.length);
160
+ view.setUint32(8, crc32(out.subarray(0, 8)));
161
+ out.set(headerBytes, PRELUDE);
162
+ out.set(payload, PRELUDE + headerBytes.length);
163
+ view.setUint32(total - 4, crc32(out.subarray(0, total - 4)));
164
+ return out;
165
+ }
166
+
167
+ /** 오류 문면 가림 seam — 호출자가 `redactAwsIdentifiers ∘ redactSecrets` 를 넘긴다(이 모듈은 의존 0). */
168
+ export type BedrockTextRedactor = (text: string) => string;
169
+
170
+ /** 고정 꼴의 예외 이름만 싣는다(`throttlingException` 등). 그 밖은 `UnknownException`. */
171
+ function safeExceptionType(v: unknown): string {
172
+ return typeof v === 'string' && /^[A-Za-z][A-Za-z0-9]{0,63}$/.test(v) ? v : 'UnknownException';
173
+ }
174
+
175
+ /** 스트림 «안»의 AWS 예외 프레임(`:message-type: exception`) — 고정 이름. */
176
+ export class BedrockRuntimeStreamError extends Error {
177
+ readonly exceptionType: string;
178
+ constructor(exceptionType: string, detail: string) {
179
+ super(`Bedrock runtime stream ${exceptionType}${detail ? `: ${detail}` : ''}`);
180
+ this.name = 'BedrockRuntimeStreamError';
181
+ this.exceptionType = exceptionType;
182
+ }
183
+ }
184
+
185
+ /**
186
+ * runtime 응답 몸 → Anthropic SSE 꼴의 `data: <json>` 줄.
187
+ * ⭐ 각 `chunk` 이벤트의 `{"bytes":"<base64>"}` 를 풀면 1st-party 와 같은 Anthropic 스트림 이벤트 JSON 이다 —
188
+ * 줄로 바꿔 `parseAnthropicSSELines` 에 그대로 넣는다(사용량·도구·추론 파싱을 한 벌로 유지).
189
+ */
190
+ export async function* bedrockRuntimeSseLines(
191
+ body: AsyncIterable<Uint8Array> | ReadableStream<Uint8Array>,
192
+ redact: BedrockTextRedactor = (t) => t,
193
+ ): AsyncGenerator<string, void, unknown> {
194
+ const text = new TextDecoder();
195
+ for await (const msg of decodeEventStream(body)) {
196
+ const messageType = msg.headers[':message-type'];
197
+ if (messageType === 'exception' || messageType === 'error') {
198
+ const kind = safeExceptionType(msg.headers[':exception-type'] ?? msg.headers[':error-code']);
199
+ let detail = '';
200
+ try {
201
+ const parsed = JSON.parse(text.decode(msg.payload)) as { message?: unknown; Message?: unknown };
202
+ const m = parsed.message ?? parsed.Message;
203
+ if (typeof m === 'string') detail = m;
204
+ } catch { /* 본문 없음 */ }
205
+ if (!detail && typeof msg.headers[':error-message'] === 'string') detail = msg.headers[':error-message'] as string;
206
+ throw new BedrockRuntimeStreamError(kind, redact(detail).slice(0, 300));
207
+ }
208
+ if (messageType !== 'event' || msg.headers[':event-type'] !== 'chunk') continue;
209
+ let b64: unknown;
210
+ try { b64 = (JSON.parse(text.decode(msg.payload)) as { bytes?: unknown }).bytes; } catch { continue; }
211
+ if (typeof b64 !== 'string' || !b64) continue;
212
+ const json = Buffer.from(b64, 'base64').toString('utf8').trim();
213
+ if (json) yield `data: ${json}`;
214
+ }
215
+ }
@@ -8,9 +8,10 @@
8
8
  import type { LLMProvider } from '../llm.js';
9
9
  import type { LLMConfig as UCLLMConfig } from '../user-config.js';
10
10
  import { debug, redactSecrets } from '../debug/log.js';
11
- import { fetchApiWithRetry } from '../session-runtime/retry-api.js';
11
+ import { ApiHttpError, fetchApiWithRetry, type FetchApiWithRetryOpts } from '../session-runtime/retry-api.js';
12
12
  import { isVisionCapableModel } from '../llm-vision-capability.js';
13
13
  import * as bedrock from './bedrock.js';
14
+ import { bedrockRuntimeSseLines } from './bedrock-eventstream.js';
14
15
 
15
16
  /** BEDROCK-PROVIDER 시험 seam — 자격·서명·HTTP 를 주입한다(실 AWS 호출 0). */
16
17
  export interface BedrockProviderDeps {
@@ -21,6 +22,71 @@ export interface BedrockProviderDeps {
21
22
  send?: (url: string, init: RequestInit) => Promise<Response>;
22
23
  }
23
24
 
25
+ /** 서명 ⊕ 송신 ⊕ 에러 마스킹 — 세 경로(mantle·runtime·mantle-openai)가 «한 벌»을 쓴다. 응답은 `ok` 인 것만 돌려준다. */
26
+ async function signAndSend(input: {
27
+ region: string;
28
+ body: Record<string, unknown>;
29
+ wireModel: string;
30
+ route: bedrock.BedrockRoute;
31
+ deps: BedrockProviderDeps;
32
+ envDeps: { env?: Record<string, string | undefined> };
33
+ opts: { signal?: AbortSignal; remainingFallbacks?: number };
34
+ retryOptsFor: (provider: string, errorPrefix: string, remainingFallbacks?: number) => FetchApiWithRetryOpts;
35
+ requestLog: Record<string, unknown>;
36
+ }): Promise<Response> {
37
+ const { region, body, wireModel, route, deps, envDeps, opts } = input;
38
+ const signRequest = () => bedrock.buildSignedBedrockRequest({
39
+ region,
40
+ body,
41
+ ...(deps.env ? { env: deps.env } : {}),
42
+ ...(deps.resolveCredentials ? { resolveCredentials: deps.resolveCredentials } : {}),
43
+ ...(deps.sign ? { sign: deps.sign } : {}),
44
+ });
45
+ let signed: Awaited<ReturnType<typeof signRequest>>;
46
+ try {
47
+ signed = await signRequest();
48
+ } catch (err) {
49
+ debug.log('llm.bedrock', 'credentials-unresolved', {
50
+ provider: 'bedrock', region, model: wireModel,
51
+ // ⛔ 임의 err.name 을 싣지 않는다 — 우리 에러면 그 고정 이름, 아니면 허용 목록/고정 문자열.
52
+ errName: err instanceof bedrock.BedrockCredentialsUnresolvedError ? err.name : bedrock.sanitizeCredentialErrorName(err),
53
+ credentialSignal: bedrock.bedrockCredentialSignalLabel(envDeps),
54
+ }, { level: 'warn' });
55
+ throw err;
56
+ }
57
+ debug.log('llm.bedrock', 'request', {
58
+ provider: 'bedrock', region, model: wireModel, route,
59
+ auth: signed.headers['x-api-key'] !== undefined || signed.headers.authorization?.startsWith('Bearer ') ? 'bearer' : 'sigv4',
60
+ ...input.requestLog,
61
+ });
62
+ // ⛔ redirect:'error' — 검증한 호스트 밖으로 리다이렉트를 따라가 자격 헤더(x-api-key·x-amz-security-token)가 새지 않게.
63
+ const toInit = (req: typeof signed): RequestInit => ({
64
+ method: 'POST', headers: req.headers, body: req.body, redirect: 'error', ...(opts.signal ? { signal: opts.signal } : {}),
65
+ });
66
+ const init = toInit(signed);
67
+ // ⭐ 재시도마다 다시 서명한다 — SigV4 는 `x-amz-date` 기준 수 분만 유효(긴 retry-after 뒤 옛 서명은 403).
68
+ let response: Response;
69
+ try {
70
+ response = deps.send
71
+ ? await deps.send(signed.url, init)
72
+ : await fetchApiWithRetry(signed.url, init, {
73
+ ...input.retryOptsFor('bedrock', 'Bedrock API', opts.remainingFallbacks),
74
+ refreshInit: async () => toInit(await signRequest()),
75
+ });
76
+ } catch (err) {
77
+ // ⛔ 403 본문이 IAM 주체 ARN·계정 번호를 싣는다 — 같은 꼴(ApiHttpError · status·retryAfter 유지)로 본문만 가려 다시 던진다.
78
+ if (err instanceof ApiHttpError) {
79
+ throw new ApiHttpError({ status: err.status, bodyText: bedrock.redactAwsIdentifiers(err.bodyText), provider: err.provider, errorPrefix: 'Bedrock API', ...(err.retryAfter !== undefined ? { retryAfter: err.retryAfter } : {}) });
80
+ }
81
+ throw err;
82
+ }
83
+ if (!response.ok) {
84
+ const text = await response.text().catch(() => '');
85
+ throw new Error(`Bedrock API ${response.status}: ${bedrock.redactAwsIdentifiers(redactSecrets(text)).slice(0, 500)}`);
86
+ }
87
+ return response;
88
+ }
89
+
24
90
  /**
25
91
  * ⭐ AWS Bedrock 의 Claude(«Claude in Amazon Bedrock» 메시지 엔드포인트 · SigV4 `bedrock-mantle`).
26
92
  * 몸·SSE 는 Anthropic 1st-party 와 같은 꼴이라 ***메시지 변환·캐시 4슬롯·SSE 파서를 그대로 쓴다*** —
@@ -39,7 +105,7 @@ export function makeBedrockProvider(cfg: UCLLMConfig, deps: BedrockProviderDeps
39
105
  const {
40
106
  toAnthropicMessage, effectiveReasoningLevel, usesAdaptiveThinking,
41
107
  mapReasoningLevelToAnthropicThinking, mapReasoningLevelToAnthropicEffort,
42
- anthropicTemperatureField, parseAnthropicSSELines, sseLineStream, retryOptsFor,
108
+ anthropicTemperatureField, parseAnthropicSSELines, sseLineStream: anthropicSseLineStream, retryOptsFor,
43
109
  } = await import('../llm.js');
44
110
  const region = bedrock.resolveBedrockRegion(envDeps);
45
111
  if (!region) {
@@ -48,6 +114,49 @@ export function makeBedrockProvider(cfg: UCLLMConfig, deps: BedrockProviderDeps
48
114
  const wireModel = bedrock.resolveBedrockModelId(opts.model || configuredModel);
49
115
  // 계열 판정(adaptive·vision·temperature)은 1st-party 꼴 id 로 — 헬퍼들이 `anthropic.` 접두를 모른다.
50
116
  const familyModel = bedrock.bedrockCanonicalClaudeId(wireModel);
117
+ // ⭐ BEDROCK-RUNTIME-PATH — 경로는 모델 표의 `route` 칸(bedrock.ts)이 정한다. 몸·캐시 4슬롯·파서는 한 벌이고,
118
+ // 다른 것은 «주소·서명 서비스·응답 프레이밍» 셋뿐 — runtime 은 eventstream 프레임을 `data:` 줄로 바꿔 같은 파서에 넣는다.
119
+ const route = bedrock.bedrockRouteFor(wireModel);
120
+ debug.log('llm.bedrock', 'route', { model: wireModel, route });
121
+ if (route === 'mantle-openai') {
122
+ // 비-Anthropic 모델 — OpenAI chat.completions 몸·SSE. Anthropic 쪽 조립(캐시 4슬롯·추론 배선)은 거치지 않는다.
123
+ const { toOpenAIMessages, toOpenAITools, parseOpenAISSELines } = await import('../llm.js');
124
+ const tools = toOpenAITools(opts.tools);
125
+ const body = {
126
+ model: wireModel,
127
+ messages: messages.flatMap((m) => toOpenAIMessages(m)),
128
+ // OpenAI 추론 계열(openai.*)은 max_tokens 를 400 으로 거부한다(10-11 실물: unsupported_parameter) — max_completion_tokens 로.
129
+ ...(wireModel.startsWith('openai.') ? { max_completion_tokens: opts.maxTokens ?? 2048 } : { max_tokens: opts.maxTokens ?? 2048 }),
130
+ ...(opts.temperature !== undefined ? { temperature: opts.temperature } : {}),
131
+ ...(tools ? { tools } : {}),
132
+ stream_options: { include_usage: true },
133
+ };
134
+ // 이 모델들은 고정 리전(us-east-1)에서만 나온다 — 사용자 리전은 «자격 해석 가능 여부»에만 쓴다.
135
+ const openaiRegion = bedrock.bedrockRegionFor(wireModel, region);
136
+ const response = await signAndSend({
137
+ region: openaiRegion, body, wireModel, route, deps, envDeps, opts, retryOptsFor,
138
+ requestLog: { tools: opts.tools?.length ?? 0 },
139
+ });
140
+ try {
141
+ for await (const ev of parseOpenAISSELines(anthropicSseLineStream(response.body!))) {
142
+ if (ev.type === 'usage') {
143
+ debug.log('llm.bedrock', 'usage', {
144
+ provider: 'bedrock', region: openaiRegion, model: wireModel, route,
145
+ inputTokens: ev.usage.inputTokens ?? 0,
146
+ outputTokens: ev.usage.outputTokens ?? 0,
147
+ });
148
+ }
149
+ yield ev;
150
+ }
151
+ } catch (err: any) {
152
+ if (err?.name === 'AbortError') return;
153
+ throw err;
154
+ }
155
+ return;
156
+ }
157
+ const sseLineStream = route === 'runtime'
158
+ ? (b: ReadableStream<Uint8Array>) => bedrockRuntimeSseLines(b, (t) => bedrock.redactAwsIdentifiers(redactSecrets(t)))
159
+ : anthropicSseLineStream;
51
160
  // ⭐ 캐시: Anthropic 공급자와 «같은 자리» 4슬롯(system·tools·history·anchor).
52
161
  const { getDefaultCacheTTL } = await import('../config.js');
53
162
  const cache = opts.promptCache !== false;
@@ -85,46 +194,10 @@ export function makeBedrockProvider(cfg: UCLLMConfig, deps: BedrockProviderDeps
85
194
  ...anthropicTemperatureField(familyModel, thinkingActive, opts.temperature),
86
195
  ...(tools ? { tools } : {}),
87
196
  };
88
- const signRequest = () => bedrock.buildSignedBedrockRequest({
89
- region,
90
- body,
91
- ...(deps.env ? { env: deps.env } : {}),
92
- ...(deps.resolveCredentials ? { resolveCredentials: deps.resolveCredentials } : {}),
93
- ...(deps.sign ? { sign: deps.sign } : {}),
197
+ const response = await signAndSend({
198
+ region, body, wireModel, route, deps, envDeps, opts, retryOptsFor,
199
+ requestLog: { cache, ttl, tools: opts.tools?.length ?? 0 },
94
200
  });
95
- let signed: Awaited<ReturnType<typeof signRequest>>;
96
- try {
97
- signed = await signRequest();
98
- } catch (err) {
99
- debug.log('llm.bedrock', 'credentials-unresolved', {
100
- provider: 'bedrock', region, model: wireModel,
101
- // ⛔ 임의 err.name 을 싣지 않는다 — 우리 에러면 그 고정 이름, 아니면 허용 목록/고정 문자열.
102
- errName: err instanceof bedrock.BedrockCredentialsUnresolvedError ? err.name : bedrock.sanitizeCredentialErrorName(err),
103
- credentialSignal: bedrock.bedrockCredentialSignalLabel(envDeps),
104
- }, { level: 'warn' });
105
- throw err;
106
- }
107
- debug.log('llm.bedrock', 'request', {
108
- provider: 'bedrock', region, model: wireModel,
109
- auth: signed.headers['x-api-key'] !== undefined ? 'bearer' : 'sigv4',
110
- cache, ttl, tools: opts.tools?.length ?? 0,
111
- });
112
- // ⛔ redirect:'error' — 검증한 호스트 밖으로 리다이렉트를 따라가 자격 헤더(x-api-key·x-amz-security-token)가 새지 않게.
113
- const toInit = (req: typeof signed): RequestInit => ({
114
- method: 'POST', headers: req.headers, body: req.body, redirect: 'error', ...(opts.signal ? { signal: opts.signal } : {}),
115
- });
116
- const init = toInit(signed);
117
- // ⭐ 재시도마다 다시 서명한다 — SigV4 는 `x-amz-date` 기준 수 분만 유효(긴 retry-after 뒤 옛 서명은 403).
118
- const response = deps.send
119
- ? await deps.send(signed.url, init)
120
- : await fetchApiWithRetry(signed.url, init, {
121
- ...retryOptsFor('bedrock', 'Bedrock API', opts.remainingFallbacks),
122
- refreshInit: async () => toInit(await signRequest()),
123
- });
124
- if (!response.ok) {
125
- const text = await response.text().catch(() => '');
126
- throw new Error(`Bedrock API ${response.status}: ${redactSecrets(text).slice(0, 500)}`);
127
- }
128
201
  try {
129
202
  for await (const ev of parseAnthropicSSELines(sseLineStream(response.body!))) {
130
203
  if (ev.type === 'usage') {