okengine 0.18.4 → 0.19.0

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 (512) hide show
  1. package/AGENTS.md +18 -10
  2. package/README.md +2 -2
  3. package/package.json +51 -50
  4. package/site/content/docs/ai/index.mdx +25 -6
  5. package/site/content/docs/ai/llms-txt.mdx +1 -1
  6. package/site/content/docs/ai/meta.json +1 -1
  7. package/site/content/docs/ai/skills.mdx +4 -1
  8. package/site/content/docs/ai/try-it.mdx +58 -0
  9. package/site/content/docs/client/auth.mdx +208 -0
  10. package/site/content/docs/client/calling.mdx +451 -0
  11. package/site/content/docs/client/index.mdx +209 -0
  12. package/site/content/docs/client/live.mdx +234 -0
  13. package/site/content/docs/client/meta.json +5 -0
  14. package/site/content/docs/client/react.mdx +249 -0
  15. package/site/content/docs/elements/ai/agents.mdx +232 -0
  16. package/site/content/docs/elements/ai/index.mdx +341 -0
  17. package/site/content/docs/elements/ai/mcp.mdx +275 -0
  18. package/site/content/docs/elements/ai/meta.json +5 -0
  19. package/site/content/docs/elements/ai/models.mdx +283 -0
  20. package/site/content/docs/elements/ai/prompts.mdx +256 -0
  21. package/site/content/docs/elements/channel/email.mdx +266 -0
  22. package/site/content/docs/elements/channel/index.mdx +344 -0
  23. package/site/content/docs/elements/channel/meta.json +5 -0
  24. package/site/content/docs/elements/channel/push.mdx +218 -0
  25. package/site/content/docs/elements/channel/receipts.mdx +206 -0
  26. package/site/content/docs/elements/channel/sms.mdx +264 -0
  27. package/site/content/docs/elements/channel/whatsapp.mdx +220 -0
  28. package/site/content/docs/elements/clock/index.mdx +300 -0
  29. package/site/content/docs/elements/clock/meta.json +5 -0
  30. package/site/content/docs/elements/clock/schedules.mdx +508 -0
  31. package/site/content/docs/elements/clock/sleep.mdx +445 -0
  32. package/site/content/docs/elements/flow/consumers.mdx +707 -0
  33. package/site/content/docs/elements/flow/http.mdx +1080 -0
  34. package/site/content/docs/elements/flow/index.mdx +618 -0
  35. package/site/content/docs/elements/flow/meta.json +5 -0
  36. package/site/content/docs/elements/flow/routing.mdx +559 -0
  37. package/site/content/docs/elements/flow/workflows.mdx +640 -0
  38. package/site/content/docs/elements/gate/auth.mdx +371 -0
  39. package/site/content/docs/elements/gate/authorization.mdx +298 -0
  40. package/site/content/docs/elements/gate/index.mdx +418 -0
  41. package/site/content/docs/elements/gate/meta.json +5 -0
  42. package/site/content/docs/elements/gate/rate-limits.mdx +365 -0
  43. package/site/content/docs/elements/gate/rls.mdx +303 -0
  44. package/site/content/docs/elements/gate/tenancy.mdx +372 -0
  45. package/site/content/docs/elements/index.mdx +52 -11
  46. package/site/content/docs/elements/signal/broadcast.mdx +517 -0
  47. package/site/content/docs/elements/signal/index.mdx +352 -0
  48. package/site/content/docs/elements/signal/live.mdx +590 -0
  49. package/site/content/docs/elements/signal/meta.json +5 -0
  50. package/site/content/docs/elements/signal/once.mdx +596 -0
  51. package/site/content/docs/elements/store/files.mdx +659 -0
  52. package/site/content/docs/elements/store/index.mdx +310 -0
  53. package/site/content/docs/elements/store/kv.mdx +629 -0
  54. package/site/content/docs/elements/store/meta.json +5 -0
  55. package/site/content/docs/elements/store/search.mdx +946 -0
  56. package/site/content/docs/elements/store/sql.mdx +937 -0
  57. package/site/content/docs/elements/vault/config.mdx +256 -0
  58. package/site/content/docs/elements/vault/index.mdx +356 -0
  59. package/site/content/docs/elements/vault/meta.json +5 -0
  60. package/site/content/docs/elements/vault/rotation.mdx +273 -0
  61. package/site/content/docs/elements/vault/secrets.mdx +285 -0
  62. package/site/content/docs/index.mdx +78 -30
  63. package/site/content/docs/meta.json +11 -8
  64. package/site/content/docs/plugins/cors.mdx +2 -1
  65. package/site/content/docs/plugins/csrf.mdx +4 -1
  66. package/site/content/docs/plugins/headers.mdx +2 -1
  67. package/site/content/docs/plugins/ip-allowlist.mdx +2 -1
  68. package/site/content/docs/plugins/magic-link.mdx +6 -0
  69. package/site/content/docs/plugins/maintenance-mode.mdx +2 -1
  70. package/site/content/docs/plugins/oauth.mdx +1 -1
  71. package/site/content/docs/plugins/otp.mdx +12 -1
  72. package/site/content/docs/plugins/passkey.mdx +39 -15
  73. package/site/content/docs/plugins/two-factor.mdx +91 -34
  74. package/site/content/docs/plugins/username.mdx +1 -1
  75. package/site/content/docs/recipes/caddy.mdx +3 -3
  76. package/site/content/docs/recipes/index.mdx +18 -28
  77. package/site/content/docs/recipes/mailpit.mdx +1 -1
  78. package/site/content/docs/recipes/meilisearch.mdx +3 -3
  79. package/site/content/docs/recipes/meta.json +1 -4
  80. package/site/content/docs/recipes/nginx.mdx +2 -2
  81. package/site/content/docs/recipes/openrouter.mdx +283 -0
  82. package/site/content/docs/recipes/pgdog.mdx +3 -3
  83. package/site/content/docs/recipes/postgres.mdx +1 -2
  84. package/site/content/docs/recipes/rustfs.mdx +2 -2
  85. package/site/content/docs/recipes/traefik.mdx +3 -3
  86. package/site/content/docs/reference/cli.mdx +231 -0
  87. package/site/content/docs/reference/configuration.mdx +30 -10
  88. package/site/content/docs/reference/environment-variables.mdx +93 -20
  89. package/site/content/docs/reference/errors.mdx +132 -29
  90. package/site/content/docs/reference/fx.mdx +137 -73
  91. package/site/content/docs/reference/index.mdx +13 -21
  92. package/site/content/docs/reference/meta.json +4 -5
  93. package/site/content/docs/reference/okid.mdx +42 -17
  94. package/site/content/docs/reference/plugins.mdx +4 -3
  95. package/site/content/docs/reference/security.mdx +197 -0
  96. package/site/content/docs/understand/meta.json +5 -0
  97. package/site/content/docs/understand/the-anatomy.mdx +132 -0
  98. package/site/content/docs/understand/the-model.mdx +32 -0
  99. package/site/content/docs/understand/the-problem.mdx +74 -0
  100. package/site/content/docs/understand/the-vocabulary.mdx +26 -0
  101. package/src/auth/api-keys.ts +2 -1
  102. package/src/auth/bindings.ts +64 -16
  103. package/src/auth/gate-auth.test.ts +6 -1
  104. package/src/auth/identity.ts +152 -5
  105. package/src/auth/index.ts +29 -0
  106. package/src/auth/invites.ts +2 -1
  107. package/src/auth/method-context.ts +41 -0
  108. package/src/auth/oauth-as/crypto.ts +2 -1
  109. package/src/auth/oauth-as/stores.ts +2 -1
  110. package/src/auth/operator.ts +2 -1
  111. package/src/auth/sessions-jwt.test.ts +77 -0
  112. package/src/auth/sessions.ts +20 -4
  113. package/src/auth/tenants.ts +3 -2
  114. package/src/auth/two-factor-challenge.test.ts +61 -0
  115. package/src/auth/two-factor-challenge.ts +284 -0
  116. package/src/auth/verification.ts +5 -0
  117. package/src/bench/REPORT.md +49 -0
  118. package/src/bench/g03-signal-once.bench.ts +1 -1
  119. package/src/bench/g10-observability-contention.bench.ts +1 -1
  120. package/src/bench/g17-hybrid-search.bench.ts +572 -0
  121. package/src/bench/load-app.ts +2 -10
  122. package/src/cli/ai-setup/ai-setup.test.ts +331 -42
  123. package/src/cli/ai-setup/apply.ts +294 -52
  124. package/src/cli/ai-setup/catalog.ts +238 -1342
  125. package/src/cli/ai-setup/index.ts +14 -99
  126. package/src/cli/ai-setup/prompts.ts +60 -443
  127. package/src/cli/ask-seed.test.ts +96 -0
  128. package/src/cli/ask-seed.ts +82 -0
  129. package/src/cli/ask-vault-gaps.test.ts +84 -0
  130. package/src/cli/ask-vault-gaps.ts +146 -0
  131. package/src/cli/build.ts +2 -2
  132. package/src/cli/client-add.test.ts +26 -1
  133. package/src/cli/client-add.ts +106 -27
  134. package/src/cli/competitor-mention-removal.test.ts +1 -1
  135. package/src/cli/db-seed.ts +2 -0
  136. package/src/cli/db.ts +150 -11
  137. package/src/cli/dev.ts +119 -211
  138. package/src/cli/docker-clean.ts +2 -2
  139. package/src/cli/docker-cli.test.ts +1 -1
  140. package/src/cli/doctor-diff.ts +4 -2
  141. package/src/cli/doctor-pii.test.ts +1 -1
  142. package/src/cli/doctor.ts +14 -1
  143. package/src/cli/hero-meta.test.ts +4 -2
  144. package/src/cli/load-config.images.test.ts +8 -10
  145. package/src/cli/load-config.ts +1 -1
  146. package/src/cli/registry.ts +22 -10
  147. package/src/cli/replay.ts +3 -1
  148. package/src/cli/start.ts +1 -1
  149. package/src/cli/tui/keys.ts +2 -3
  150. package/src/client/auth/cookies.ts +74 -0
  151. package/src/client/auth/create-auth-client.ts +636 -0
  152. package/src/client/auth/denials.ts +99 -0
  153. package/src/client/auth/session.ts +283 -0
  154. package/src/client/auth.test.ts +251 -0
  155. package/src/client/auth.ts +39 -114
  156. package/src/client/create-with-session.ts +245 -0
  157. package/src/client/create.ts +51 -5
  158. package/src/client/index.ts +11 -1
  159. package/src/client/live.ts +11 -79
  160. package/src/client/notes-contract.test.ts +5 -10
  161. package/src/client/sse.ts +137 -0
  162. package/src/client/stream.ts +147 -0
  163. package/src/client/transport.test.ts +32 -0
  164. package/src/client/transport.ts +123 -26
  165. package/src/client/types.ts +118 -14
  166. package/src/client-react/index.ts +185 -24
  167. package/src/client-react/use-live-query.ts +13 -3
  168. package/src/compiler/aot.test.ts +7 -4
  169. package/src/compiler/effects-embed.test.ts +56 -0
  170. package/src/compiler/effects-fetch.test.ts +43 -0
  171. package/src/compiler/effects-infer.ts +58 -2
  172. package/src/compiler/extract.test.ts +258 -44
  173. package/src/compiler/extract.ts +566 -48
  174. package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -4
  175. package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +2 -8
  176. package/src/compiler/fixtures/skyport.expected.json +4 -4
  177. package/src/compiler/fixtures/triggers/five-triggers.ts +14 -10
  178. package/src/compiler/response.ts +2 -2
  179. package/src/compiler/search-writer-isolation.test.ts +40 -0
  180. package/src/config/index.ts +11 -0
  181. package/src/console/server/app.ts +17 -23
  182. package/src/console/server/bind.ts +4 -0
  183. package/src/console/server/console.test.ts +2 -2
  184. package/src/console/server/flows-invoke.test.ts +50 -34
  185. package/src/console/server/flows.ts +436 -255
  186. package/src/console/server/runs-ingest.test.ts +6 -3
  187. package/src/console/server/serve.ts +1 -1
  188. package/src/console/server/signals.test.ts +1 -5
  189. package/src/console/server/signals.ts +8 -6
  190. package/src/console/server/state.ts +6 -1
  191. package/src/console/server/store.ts +1 -1
  192. package/src/console/ui-next/dist/assets/FileExportIcon-Ck-5od4R.js +1 -0
  193. package/src/console/ui-next/dist/assets/MoreHorizontalCircle01Icon-gMNGsE37.js +1 -0
  194. package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +1 -0
  195. package/src/console/ui-next/dist/assets/UnavailableIcon-D9cvHVPr.js +1 -0
  196. package/src/console/ui-next/dist/assets/UserIcon-DaE2PB5_.js +1 -0
  197. package/src/console/ui-next/dist/assets/access-page-DFeymU07.js +4 -0
  198. package/src/console/ui-next/dist/assets/agent-disclosure-U1rdfblp.js +1 -0
  199. package/src/console/ui-next/dist/assets/cache-glyph-BeFJeqBG.js +1 -0
  200. package/src/console/ui-next/dist/assets/call-pii-button-CVAONPii.js +1 -0
  201. package/src/console/ui-next/dist/assets/collapsible-D2A6NJ-3.js +1 -0
  202. package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +1 -0
  203. package/src/console/ui-next/dist/assets/dagre.esm-B1_XeuLP.js +1 -0
  204. package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +1 -0
  205. package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +1 -0
  206. package/src/console/ui-next/dist/assets/duration-tone-Cgk_h5ja.js +9 -0
  207. package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +1 -0
  208. package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +1 -0
  209. package/src/console/ui-next/dist/assets/flows-page-Dss7941e.js +1 -0
  210. package/src/console/ui-next/dist/assets/highlighted-json-MYZQtRnw.js +154 -0
  211. package/src/console/ui-next/dist/assets/http-method-Jrh39p7A.js +1 -0
  212. package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
  213. package/src/console/ui-next/dist/assets/index-DXP2dBIF.js +63 -0
  214. package/src/console/ui-next/dist/assets/observability-page-HvolXxTI.js +4 -0
  215. package/src/console/ui-next/dist/assets/react-dom-Ddte4I-Q.js +9 -0
  216. package/src/console/ui-next/dist/assets/replica-lag-CSh2dzrb.js +18 -0
  217. package/src/console/ui-next/dist/assets/request-meta-BatF8KrK.js +1 -0
  218. package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +1 -0
  219. package/src/console/ui-next/dist/assets/{sql-BskegiFM.js → sql-BsFa4tDR.js} +1 -1
  220. package/src/console/ui-next/dist/assets/store-page-eiKiHnNe.js +41 -0
  221. package/src/console/ui-next/dist/assets/trace-detail-sheet-CZkMeKS-.js +2 -0
  222. package/src/console/ui-next/dist/assets/tree-expand-toggle-CW8y5A2h.js +55 -0
  223. package/src/console/ui-next/dist/assets/units-page-B_RWJrEO.js +1 -0
  224. package/src/console/ui-next/dist/assets/{use-vault-list-uk4WVboC.js → use-vault-list-CT4-gajj.js} +1 -1
  225. package/src/console/ui-next/dist/assets/vault-page-CWrg-A68.js +2 -0
  226. package/src/console/ui-next/dist/assets/xyflow-CSyC6ryz.css +1 -0
  227. package/src/console/ui-next/dist/assets/xyflow-yApv7D4e.js +7 -0
  228. package/src/console/ui-next/dist/index.html +6 -13
  229. package/src/console/ui-next/seed-invoke-host.ts +21 -18
  230. package/src/console/ui-next/src/client.ts +7 -2
  231. package/src/console/ui-next/src/features/flows/graph/element-map.ts +2 -0
  232. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +12 -2
  233. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +4 -0
  234. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +8 -0
  235. package/src/console/ui-next/src/features/flows/traces/waterfall-bars.ts +4 -0
  236. package/src/console/ui-next/src/features/flows/traces/waterfall-tooltip.ts +9 -3
  237. package/src/console/ui-next/src/features/store/detail/reveal-cell.tsx +7 -11
  238. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +2 -2
  239. package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +2 -0
  240. package/src/console/ui-next/src/features/store/lib/grid-model.test.ts +25 -1
  241. package/src/console/ui-next/src/features/store/lib/grid-model.ts +36 -1
  242. package/src/console/ui-next/src/features/store/query/query-results.tsx +3 -3
  243. package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +2 -0
  244. package/src/console/ui-next/src/features/units/lib/call-read-safe.ts +3 -1
  245. package/src/console/ui-next/src/features/units/lib/fields-from-schema.ts +6 -0
  246. package/src/docker/ai-model-status.test.ts +1 -12
  247. package/src/docker/ai-model-status.ts +10 -38
  248. package/src/docker/compose-up.test.ts +100 -0
  249. package/src/docker/compose-up.ts +292 -0
  250. package/src/docker/compose.ts +4 -14
  251. package/src/docker/derive.ts +9 -34
  252. package/src/docker/docker.test.ts +16 -261
  253. package/src/docker/images-config.test.ts +13 -25
  254. package/src/docker/index.ts +1 -24
  255. package/src/docker/recipes/index.ts +0 -23
  256. package/src/docker/recipes/pgdog.ts +6 -4
  257. package/src/docker/stack-id.test.ts +2 -2
  258. package/src/docker/stack-id.ts +0 -1
  259. package/src/docker/types.ts +2 -2
  260. package/src/drivers/ai-anthropic.ts +10 -0
  261. package/src/drivers/ai-openai-compatible.ts +34 -7
  262. package/src/drivers/ai-providers.test.ts +0 -107
  263. package/src/drivers/ai-stream.test.ts +0 -34
  264. package/src/drivers/ai-types.ts +17 -8
  265. package/src/drivers/channel-fcm.ts +6 -1
  266. package/src/drivers/channel-msegat.ts +6 -1
  267. package/src/drivers/channel-resend.ts +5 -1
  268. package/src/drivers/channel-smtp.ts +5 -1
  269. package/src/drivers/channel-sndr.ts +10 -1
  270. package/src/drivers/channel-taqnyat-mail.ts +5 -1
  271. package/src/drivers/channel-taqnyat-whatsapp.ts +6 -1
  272. package/src/drivers/channel-taqnyat.ts +6 -1
  273. package/src/drivers/channel-types.ts +10 -0
  274. package/src/drivers/channel-unifonic.ts +6 -1
  275. package/src/drivers/channel-wa-cloud.ts +6 -1
  276. package/src/drivers/channel-webpush.ts +6 -1
  277. package/src/drivers/external.ts +33 -0
  278. package/src/drivers/index.ts +0 -10
  279. package/src/drivers/meilisearch.ts +6 -0
  280. package/src/drivers/oauth-types.ts +4 -0
  281. package/src/drivers/postgres.test.ts +74 -3
  282. package/src/drivers/postgres.ts +175 -6
  283. package/src/drivers/signal-types.ts +5 -5
  284. package/src/drivers/types.ts +7 -0
  285. package/src/drivers/vault-types.ts +5 -0
  286. package/src/elements/ai/declare.ts +17 -4
  287. package/src/elements/ai/eval.ts +1 -1
  288. package/src/elements/ai/mcp-http.ts +1 -1
  289. package/src/elements/ai/pii.ts +1 -1
  290. package/src/elements/ai/providers.test.ts +289 -0
  291. package/src/elements/ai/providers.ts +169 -0
  292. package/src/elements/ai/runtime.ts +68 -5
  293. package/src/elements/ai/schema.ts +2 -2
  294. package/src/elements/ai.test.ts +7 -7
  295. package/src/elements/ai.ts +17 -1
  296. package/src/elements/channel/runtime.ts +105 -12
  297. package/src/elements/clock/cron-fields.test.ts +144 -0
  298. package/src/elements/clock/cron-fields.ts +185 -0
  299. package/src/elements/clock/declare.ts +247 -3
  300. package/src/elements/clock.ts +21 -1
  301. package/src/elements/index.ts +15 -0
  302. package/src/elements/signal/chaos-child.ts +1 -2
  303. package/src/elements/signal/declare.ts +65 -37
  304. package/src/elements/signal/delivery-modes.test.ts +11 -31
  305. package/src/elements/signal/dry-run-replay.test.ts +1 -7
  306. package/src/elements/signal/dry-run-write-isolation.test.ts +1 -7
  307. package/src/elements/signal/key-ordering.test.ts +6 -26
  308. package/src/elements/signal/lease-reclaim.test.ts +2 -6
  309. package/src/elements/signal/optional-emit.test.ts +6 -9
  310. package/src/elements/signal/order-lifecycle.test.ts +4 -17
  311. package/src/elements/signal/orphan-messages.test.ts +4 -15
  312. package/src/elements/signal/reconcile.test.ts +2 -2
  313. package/src/elements/signal/runtime.ts +1 -1
  314. package/src/elements/signal/schema-emit.test.ts +4 -8
  315. package/src/elements/signal.test.ts +24 -49
  316. package/src/elements/signal.ts +9 -2
  317. package/src/elements/store/domain-ddl.test.ts +1 -1
  318. package/src/elements/store/live-http.test.ts +9 -4
  319. package/src/elements/store/prepare-row.test.ts +123 -0
  320. package/src/elements/store/resource.ts +43 -12
  321. package/src/elements/store/schema-decl.ts +117 -0
  322. package/src/elements/store/search-backfill.ts +205 -0
  323. package/src/elements/store/search-bind.ts +66 -0
  324. package/src/elements/store/search-bm25.ts +78 -0
  325. package/src/elements/store/search-ddl.ts +156 -0
  326. package/src/elements/store/search-embed-flow.ts +141 -0
  327. package/src/elements/store/search-errors.ts +34 -0
  328. package/src/elements/store/search-fusion.ts +112 -0
  329. package/src/elements/store/search-lsh.ts +179 -0
  330. package/src/elements/store/search-runtime.ts +300 -0
  331. package/src/elements/store/search.test.ts +144 -0
  332. package/src/elements/store/sql-session.test.ts +1 -0
  333. package/src/elements/store/sql-session.ts +83 -3
  334. package/src/elements/store/table.ts +23 -2
  335. package/src/elements/store/upsert-app.test.ts +1 -3
  336. package/src/elements/store.ts +50 -0
  337. package/src/full.ts +4 -2
  338. package/src/http.ts +4 -1
  339. package/src/i18n/catalogs/ar.ts +14 -6
  340. package/src/i18n/catalogs/en.ts +14 -6
  341. package/src/index.ts +6 -1
  342. package/src/kernel/adopt-barrel-fresh.test.ts +7 -7
  343. package/src/kernel/adopt-routes.ts +9 -1
  344. package/src/kernel/app-auth.ts +5 -0
  345. package/src/kernel/app.ts +102 -9
  346. package/src/kernel/auto-cache.test.ts +6 -12
  347. package/src/kernel/auto-registry.test.ts +3 -3
  348. package/src/kernel/boot-bind/ai.test.ts +22 -19
  349. package/src/kernel/boot-bind/ai.ts +5 -43
  350. package/src/kernel/boot-bind/clock.ts +23 -5
  351. package/src/kernel/boot-bind/honor-config.test.ts +4 -4
  352. package/src/kernel/boot.test.ts +11 -6
  353. package/src/kernel/boot.ts +66 -16
  354. package/src/kernel/boundary-contract.ts +93 -0
  355. package/src/kernel/budget.test.ts +1 -1
  356. package/src/kernel/call.ts +89 -0
  357. package/src/kernel/capability.ts +6 -0
  358. package/src/kernel/client-descriptor.ts +123 -0
  359. package/src/kernel/clock-timezone.test.ts +80 -0
  360. package/src/kernel/correlation.test.ts +1 -1
  361. package/src/kernel/dry-run.ts +11 -8
  362. package/src/kernel/effects-stamping.test.ts +42 -9
  363. package/src/kernel/effects.test.ts +4 -2
  364. package/src/kernel/effects.ts +51 -9
  365. package/src/kernel/errors-channel.ts +17 -0
  366. package/src/kernel/errors-live-resume.ts +3 -2
  367. package/src/kernel/errors-tenant.ts +7 -4
  368. package/src/kernel/errors.registry-helpers.ts +96 -0
  369. package/src/kernel/errors.registry.test.ts +139 -25
  370. package/src/kernel/errors.ts +102 -23
  371. package/src/kernel/external-effects.test.ts +196 -0
  372. package/src/kernel/flow.test.ts +13 -4
  373. package/src/kernel/flow.ts +69 -71
  374. package/src/kernel/fx-ask-telemetry.test.ts +2 -2
  375. package/src/kernel/fx-dead-letters.test.ts +3 -3
  376. package/src/kernel/fx-fetch.ts +55 -0
  377. package/src/kernel/fx-live-stream.ts +2 -2
  378. package/src/kernel/fx-live.test.ts +8 -8
  379. package/src/kernel/fx.test.ts +17 -11
  380. package/src/kernel/fx.ts +180 -57
  381. package/src/kernel/horizontal-child.ts +1 -1
  382. package/src/kernel/http-query.test.ts +3 -6
  383. package/src/kernel/index.ts +13 -3
  384. package/src/kernel/instance-id.ts +1 -1
  385. package/src/kernel/instances.test.ts +40 -0
  386. package/src/kernel/instances.ts +47 -9
  387. package/src/kernel/live-http.test.ts +6 -7
  388. package/src/kernel/live-http.ts +6 -2
  389. package/src/kernel/live-resume.test.ts +1 -1
  390. package/src/kernel/mcp-tool.test.ts +3 -3
  391. package/src/kernel/on.ts +98 -4
  392. package/src/kernel/pipeline.test.ts +12 -15
  393. package/src/kernel/plugin-elements.test.ts +1 -1
  394. package/src/kernel/stamp-http.test.ts +3 -3
  395. package/src/kernel/triggers.ts +210 -95
  396. package/src/kernel-entry.ts +0 -1
  397. package/src/manifest/diff.ts +11 -1
  398. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  399. package/src/manifest/types.ts +46 -2
  400. package/src/mcp/docs-index.ts +3 -3
  401. package/src/mcp/docs-mcp.test.ts +2 -2
  402. package/src/mcp/docs-tools.ts +1 -1
  403. package/src/okid.test.ts +56 -0
  404. package/src/okid.ts +55 -11
  405. package/src/plugins/anonymous.ts +8 -4
  406. package/src/plugins/auth/shared.ts +27 -3
  407. package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
  408. package/src/plugins/auth-methods.security.test.ts +561 -23
  409. package/src/plugins/config-source.ts +1 -1
  410. package/src/plugins/csrf.test.ts +65 -0
  411. package/src/plugins/index.ts +2 -0
  412. package/src/plugins/magic-link.ts +36 -16
  413. package/src/plugins/oauth/shared.ts +3 -1
  414. package/src/plugins/oauth.ts +22 -14
  415. package/src/plugins/otp.ts +37 -21
  416. package/src/plugins/passkey-webauthn.ts +9 -6
  417. package/src/plugins/passkey.ts +98 -26
  418. package/src/plugins/pre-account-hijack.test.ts +223 -0
  419. package/src/plugins/two-factor.ts +466 -37
  420. package/src/plugins/username.ts +19 -7
  421. package/src/release/build-lib.ts +1 -0
  422. package/src/release/limits.ts +2 -2
  423. package/src/release/measure.ts +1 -1
  424. package/src/runs/collect.test.ts +0 -2
  425. package/src/runtime/json-code-block.test.ts +250 -20
  426. package/src/runtime/json-code-block.ts +1261 -41
  427. package/src/term.test.ts +1 -1
  428. package/src/test/create-test-app.test.ts +12 -12
  429. package/src/test/live-signals.test.ts +6 -6
  430. package/src/test/provisions.integration.test.ts +10 -14
  431. package/src/test/reset-element-registries.ts +1 -1
  432. package/src/test/tenant-isolation.test.ts +12 -6
  433. package/site/content/docs/deployment/docker-swarm.mdx +0 -164
  434. package/site/content/docs/deployment/docker.mdx +0 -227
  435. package/site/content/docs/deployment/index.mdx +0 -83
  436. package/site/content/docs/deployment/kubernetes.mdx +0 -176
  437. package/site/content/docs/deployment/meta.json +0 -5
  438. package/site/content/docs/deployment/reverse-proxy.mdx +0 -234
  439. package/site/content/docs/elements/ai.mdx +0 -385
  440. package/site/content/docs/elements/channel.mdx +0 -346
  441. package/site/content/docs/elements/clock.mdx +0 -244
  442. package/site/content/docs/elements/flow.mdx +0 -420
  443. package/site/content/docs/elements/gate.mdx +0 -436
  444. package/site/content/docs/elements/signal.mdx +0 -380
  445. package/site/content/docs/elements/store.mdx +0 -1099
  446. package/site/content/docs/elements/vault.mdx +0 -405
  447. package/site/content/docs/get-started/basic-usage.mdx +0 -173
  448. package/site/content/docs/get-started/index.mdx +0 -43
  449. package/site/content/docs/get-started/installation.mdx +0 -220
  450. package/site/content/docs/get-started/introduction.mdx +0 -132
  451. package/site/content/docs/get-started/meta.json +0 -13
  452. package/site/content/docs/get-started/project-structure.mdx +0 -925
  453. package/site/content/docs/get-started/testing.mdx +0 -328
  454. package/site/content/docs/get-started/why.mdx +0 -162
  455. package/site/content/docs/recipes/llama-cpp.mdx +0 -151
  456. package/site/content/docs/recipes/ollama.mdx +0 -142
  457. package/site/content/docs/recipes/sglang.mdx +0 -105
  458. package/site/content/docs/recipes/vllm.mdx +0 -106
  459. package/site/content/docs/reference/cli.md +0 -232
  460. package/site/content/docs/reference/client.mdx +0 -469
  461. package/site/content/docs/reference/security.md +0 -74
  462. package/src/cli/ai-setup/detect-ollama.ts +0 -213
  463. package/src/cli/ai-setup/recommend.test.ts +0 -225
  464. package/src/cli/ai-setup/recommend.ts +0 -225
  465. package/src/cli/dev-controls.test.ts +0 -122
  466. package/src/cli/dev-controls.ts +0 -164
  467. package/src/cli/tui/DevLive.tsx +0 -126
  468. package/src/console/ui-next/dist/assets/access-page-De7Lc2JC.js +0 -4
  469. package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +0 -1
  470. package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +0 -1
  471. package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +0 -1
  472. package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +0 -1
  473. package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +0 -1
  474. package/src/console/ui-next/dist/assets/dagre.esm-ZwcdTuZZ.js +0 -1
  475. package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +0 -1
  476. package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +0 -1
  477. package/src/console/ui-next/dist/assets/duration-tone-sC3lGABz.js +0 -9
  478. package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +0 -1
  479. package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +0 -1
  480. package/src/console/ui-next/dist/assets/flows-page-RGy7VEA_.js +0 -1
  481. package/src/console/ui-next/dist/assets/highlighted-json-Awq7gYdu.js +0 -154
  482. package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +0 -1
  483. package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
  484. package/src/console/ui-next/dist/assets/index-_rgpdVzo.js +0 -66
  485. package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
  486. package/src/console/ui-next/dist/assets/observability-page-Ds6pcnh-.js +0 -4
  487. package/src/console/ui-next/dist/assets/preload-helper-oH4irX4C.js +0 -1
  488. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +0 -1
  489. package/src/console/ui-next/dist/assets/react-dom-Bph1y7z7.js +0 -9
  490. package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +0 -18
  491. package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +0 -1
  492. package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +0 -1
  493. package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +0 -1
  494. package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +0 -1
  495. package/src/console/ui-next/dist/assets/store-page-02xOiqIK.js +0 -41
  496. package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +0 -2
  497. package/src/console/ui-next/dist/assets/tree-expand-toggle-BtyhmWb4.js +0 -55
  498. package/src/console/ui-next/dist/assets/units-page-4rHOePuE.js +0 -1
  499. package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +0 -1
  500. package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +0 -1
  501. package/src/console/ui-next/dist/assets/vault-page-DISPgxLM.js +0 -2
  502. package/src/console/ui-next/dist/assets/xyflow-D7n4g6go.js +0 -7
  503. package/src/console/ui-next/dist/assets/xyflow-DZ0Ws1xk.css +0 -1
  504. package/src/docker/ollama-pull.ts +0 -232
  505. package/src/docker/recipes/llama-cpp.ts +0 -298
  506. package/src/docker/recipes/ollama.ts +0 -44
  507. package/src/docker/recipes/sglang.ts +0 -55
  508. package/src/docker/recipes/vllm.ts +0 -44
  509. package/src/drivers/ai-ollama-tools.integration.test.ts +0 -109
  510. package/src/drivers/ai-ollama.integration.test.ts +0 -184
  511. package/src/drivers/ai-ollama.ts +0 -389
  512. package/src/drivers/ollama.ts +0 -14
@@ -4,11 +4,17 @@
4
4
 
5
5
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
6
  import { dirname, join } from "node:path";
7
- import { OLLAMA_IMAGE } from "../../docker/recipes/index.ts";
7
+ import { getAiProviderEntry } from "../../elements/ai/providers.ts";
8
+ import { CLOUD_PROVIDERS } from "./catalog.ts";
8
9
 
9
10
  /** Choices applied to the project. */
10
11
  export type AiSetupApplyInput = {
11
- readonly driver: "ollama" | "anthropic" | "openai-compatible" | "mock";
12
+ readonly driver: "anthropic" | "openai-compatible" | "mock";
13
+ /**
14
+ * Name written to `ai.model({ provider })` — registry id (`openrouter`,
15
+ * `openai`, …), or `openai-compatible` / `local` for self-hosted.
16
+ */
17
+ readonly provider?: string;
12
18
  readonly baseUrl?: string;
13
19
  readonly chatModel?: string;
14
20
  readonly visionModel?: string | null;
@@ -16,7 +22,7 @@ export type AiSetupApplyInput = {
16
22
  readonly apiKeyEnv?: string;
17
23
  /** When set with {@link apiKeyEnv}, writes the token into `.env.local`. */
18
24
  readonly apiKey?: string;
19
- /** Optional `images.ai` pin (llama.cpp / Ollama / vLLM / SGLang). */
25
+ /** Optional `images.ai` pin (legacy; cloud / custom / lmstudio leave unset). */
20
26
  readonly image?: string;
21
27
  };
22
28
 
@@ -56,8 +62,9 @@ export function applyAiSetup(
56
62
  config = upsertAiDrivers(config, input.driver);
57
63
  if (input.image) {
58
64
  config = upsertImage(config, "ai", input.image);
59
- } else if (input.driver === "ollama") {
60
- config = upsertImage(config, "ai", OLLAMA_IMAGE);
65
+ } else {
66
+ // Cloud / host-side providers must not leave a leftover `images.ai` pin.
67
+ config = removeImage(config, "ai");
61
68
  }
62
69
  writeFileSync(configPath, config, "utf8");
63
70
  }
@@ -92,6 +99,9 @@ export function applyAiSetup(
92
99
  writeFileSync(envPath, env.endsWith("\n") ? env : `${env}\n`, "utf8");
93
100
 
94
101
  const aiTsPath = writeAiModels(cwd, input);
102
+ if (input.apiKeyEnv) {
103
+ ensureAiApiKeyVaultSecret(cwd, input.apiKeyEnv);
104
+ }
95
105
 
96
106
  return { configPath, envPath, aiTsPath };
97
107
  }
@@ -143,6 +153,19 @@ export function upsertImage(source: string, key: string, image: string): string
143
153
  return source.replace(imagesRe, `images: {${body}\n${line}\n }`);
144
154
  }
145
155
 
156
+ /**
157
+ * Drop one flat image role pin (e.g. clear `images.ai` for cloud AI).
158
+ *
159
+ * @param source - Config source
160
+ * @param key - Image role (`ai`, `pgdog`, … — not nested `store.*`)
161
+ */
162
+ export function removeImage(source: string, key: string): string {
163
+ const keyLit = key.includes(".") ? `"${key}"` : key;
164
+ const lineRe = new RegExp(`\\n[ \\t]*${keyLit}\\s*:\\s*"[^"]*",?`);
165
+ if (!lineRe.test(source)) return source;
166
+ return source.replace(lineRe, "");
167
+ }
168
+
146
169
  /** Options for {@link upsertEnv}. */
147
170
  export type UpsertEnvOptions = {
148
171
  /**
@@ -181,34 +204,40 @@ export function upsertEnv(
181
204
  */
182
205
  export function renderAiTs(input: AiSetupApplyInput): string {
183
206
  const chat = input.chatModel ?? "default";
184
- const provider =
185
- input.driver === "ollama"
186
- ? "ollama"
187
- : input.driver === "anthropic"
188
- ? "anthropic"
189
- : input.driver === "mock"
190
- ? "mock"
191
- : "openai-compatible";
207
+ const provider = resolveDeclProvider(input);
208
+ const apiKeyEnv = input.apiKeyEnv;
209
+ const registryCloud = isRegistryCloudProvider(provider);
210
+ const nativeAnthropic = input.driver === "anthropic";
211
+ const localPrimary = isSelfHostedLocal(input);
192
212
 
193
- const lines = [
194
- `import { ai } from "okengine";`,
195
- ``,
196
- `/** Cloud OpenAI-compatible binding (OpenAI / Groq / OpenRouter / …). */`,
213
+ const smartLines = [
197
214
  `export const smart = ai.model("smart", {`,
198
215
  ` provider: "${provider}",`,
216
+ ...(nativeAnthropic ? [` driverId: "anthropic",`] : []),
199
217
  ` model: process.env.OKE_AI_CLOUD_MODEL ?? process.env.OKE_AI_MODEL ?? "${chat}",`,
200
- ` baseUrl: process.env.OPENAI_BASE_URL?.trim() || "https://api.openai.com/v1",`,
201
- ` ...(process.env.OPENAI_API_KEY?.trim()`,
202
- ` ? { apiKey: process.env.OPENAI_API_KEY.trim() }`,
203
- ` : {}),`,
218
+ ...smartBaseUrlLines(input, { registryCloud, localPrimary }),
219
+ ...apiKeySpreadLines(apiKeyEnv),
204
220
  `});`,
205
- ``,
206
- `/** Local inference binding (docker llama.cpp / Ollama via \`OKE_AI_URL\`). */`,
221
+ ];
222
+
223
+ const localProvider = "openai-compatible";
224
+ const localDefaultModel = localPrimary ? chat : "local-model";
225
+ const localLines = [
207
226
  `export const local = ai.model("local", {`,
208
- ` provider: "${provider === "ollama" ? "ollama" : "openai-compatible"}",`,
209
- ` model: process.env.OKE_AI_LOCAL_MODEL ?? "${chat}",`,
227
+ ` provider: "${localProvider}",`,
228
+ ` model: process.env.OKE_AI_LOCAL_MODEL ?? "${localDefaultModel}",`,
210
229
  ` ...(process.env.OKE_AI_URL?.trim() ? { baseUrl: process.env.OKE_AI_URL.trim() } : {}),`,
211
230
  `});`,
231
+ ];
232
+
233
+ const lines = [
234
+ `import { ai } from "okengine";`,
235
+ ``,
236
+ `/** Primary model binding (${provider}). */`,
237
+ ...smartLines,
238
+ ``,
239
+ `/** Local OpenAI-compatible binding (via \`OKE_AI_URL\`). */`,
240
+ ...localLines,
212
241
  ``,
213
242
  `/** Advanced Notes summarize — used by \`notes.summarize\` via \`fx.ask\`. */`,
214
243
  `export const summarizeNote = smart.prompt("summarize-note", {`,
@@ -222,7 +251,9 @@ export function renderAiTs(input: AiSetupApplyInput): string {
222
251
  ``,
223
252
  `export const vision = ai.model("vision", {`,
224
253
  ` provider: "${provider}",`,
254
+ ...(nativeAnthropic ? [` driverId: "anthropic",`] : []),
225
255
  ` model: process.env.OKE_AI_VISION_MODEL ?? "${input.visionModel}",`,
256
+ ...apiKeySpreadLines(apiKeyEnv),
226
257
  `});`,
227
258
  );
228
259
  }
@@ -233,6 +264,7 @@ export function renderAiTs(input: AiSetupApplyInput): string {
233
264
  `export const embedModel = ai.model("embed", {`,
234
265
  ` provider: "${provider}",`,
235
266
  ` model: process.env.OKE_AI_EMBED_MODEL ?? "${input.embedModel}",`,
267
+ ...apiKeySpreadLines(apiKeyEnv),
236
268
  `});`,
237
269
  ``,
238
270
  `export const docsEmbed = ai.embed("docs", { model: embedModel });`,
@@ -243,6 +275,163 @@ export function renderAiTs(input: AiSetupApplyInput): string {
243
275
  return `${lines.join("\n")}\n`;
244
276
  }
245
277
 
278
+ /**
279
+ * Provider string for generated `ai.model` declarations.
280
+ *
281
+ * @param input - Setup choices
282
+ */
283
+ export function resolveDeclProvider(input: AiSetupApplyInput): string {
284
+ if (input.provider !== undefined && input.provider.length > 0) return input.provider;
285
+ if (input.driver === "anthropic") return "anthropic";
286
+ if (input.driver === "mock") return "mock";
287
+ return "openai-compatible";
288
+ }
289
+
290
+ function isRegistryCloudProvider(provider: string): boolean {
291
+ return getAiProviderEntry(provider) !== undefined;
292
+ }
293
+
294
+ function isSelfHostedLocal(input: AiSetupApplyInput): boolean {
295
+ return input.image !== undefined;
296
+ }
297
+
298
+ function smartBaseUrlLines(
299
+ input: AiSetupApplyInput,
300
+ flags: { readonly registryCloud: boolean; readonly localPrimary: boolean },
301
+ ): string[] {
302
+ if (input.driver === "anthropic") {
303
+ return [];
304
+ }
305
+ if (flags.registryCloud) {
306
+ // Registry auto-resolves; optional env override for proxies/mirrors.
307
+ return [
308
+ ` ...(process.env.OPENAI_BASE_URL?.trim() ? { baseUrl: process.env.OPENAI_BASE_URL.trim() } : {}),`,
309
+ ];
310
+ }
311
+ if (flags.localPrimary) {
312
+ return [
313
+ ` ...(process.env.OKE_AI_URL?.trim() ? { baseUrl: process.env.OKE_AI_URL.trim() } : {}),`,
314
+ ];
315
+ }
316
+ // custom / lmstudio — prefer OPENAI_BASE_URL, fall back to setup-time URL when present
317
+ if (input.baseUrl) {
318
+ return [` baseUrl: process.env.OPENAI_BASE_URL?.trim() || ${JSON.stringify(input.baseUrl)},`];
319
+ }
320
+ return [
321
+ ` ...(process.env.OPENAI_BASE_URL?.trim() ? { baseUrl: process.env.OPENAI_BASE_URL.trim() } : {}),`,
322
+ ];
323
+ }
324
+
325
+ function apiKeySpreadLines(apiKeyEnv: string | undefined): string[] {
326
+ if (!apiKeyEnv) return [];
327
+ return [
328
+ ` ...(process.env.${apiKeyEnv}?.trim()`,
329
+ ` ? { apiKey: process.env.${apiKeyEnv}.trim() }`,
330
+ ` : {}),`,
331
+ ];
332
+ }
333
+
334
+ /**
335
+ * `OPENROUTER_API_KEY` → `openrouterApiKey`.
336
+ *
337
+ * @param envName - Env / vault contract name
338
+ */
339
+ export function envNameToCamelBinding(envName: string): string {
340
+ const parts = envName
341
+ .toLowerCase()
342
+ .split(/_+/g)
343
+ .filter((p) => p.length > 0);
344
+ if (parts.length === 0) return "apiKey";
345
+ return parts
346
+ .map((part, i) => (i === 0 ? part : `${part[0]!.toUpperCase()}${part.slice(1)}`))
347
+ .join("");
348
+ }
349
+
350
+ /**
351
+ * Human description for a provider API key env.
352
+ *
353
+ * @param envName - Env / vault contract name
354
+ */
355
+ export function apiKeyEnvDescription(envName: string): string {
356
+ const meta = CLOUD_PROVIDERS.find((p) => p.apiKeyEnv === envName);
357
+ if (meta) return `${meta.label} API key`;
358
+ const stem = envName.replace(/_API_KEY$/i, "").replace(/_/g, " ");
359
+ return `${stem} API key`;
360
+ }
361
+
362
+ /**
363
+ * Render a `vault.secret` block for an AI provider key (no `dev:` stub so
364
+ * missing values become Vault boot gaps on `oke dev`).
365
+ *
366
+ * @param apiKeyEnv - Env / contract name
367
+ */
368
+ export function renderAiApiKeyVaultSecret(apiKeyEnv: string): string {
369
+ const binding = envNameToCamelBinding(apiKeyEnv);
370
+ const description = apiKeyEnvDescription(apiKeyEnv);
371
+ return `/** ${description} — no \`dev:\` stub so first \`oke dev\` asks via Vault gaps. */
372
+ export const ${binding} = vault.secret(${JSON.stringify(apiKeyEnv)}, {
373
+ description: ${JSON.stringify(description)},
374
+ rotate: "90d",
375
+ });
376
+ `;
377
+ }
378
+
379
+ /**
380
+ * True when `source` already declares `vault.secret("ENV")` (or `vault("ENV")`).
381
+ *
382
+ * @param source - TypeScript source
383
+ * @param apiKeyEnv - Contract name
384
+ */
385
+ export function hasAiApiKeyVaultSecret(source: string, apiKeyEnv: string): boolean {
386
+ const lit = apiKeyEnv.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
387
+ return new RegExp(`\\bvault(?:\\.secret)?\\s*\\(\\s*["']${lit}["']`).test(source);
388
+ }
389
+
390
+ /**
391
+ * Merge a vault.secret contract for the AI API key into core / vault sources.
392
+ *
393
+ * @param existing - Current module source
394
+ * @param apiKeyEnv - Env / contract name
395
+ */
396
+ export function mergeAiApiKeyVaultSecret(existing: string, apiKeyEnv: string): string {
397
+ if (hasAiApiKeyVaultSecret(existing, apiKeyEnv)) return existing;
398
+ let next = ensureNamedOkengineImport(existing, "vault");
399
+ const block = renderAiApiKeyVaultSecret(apiKeyEnv);
400
+ const vaultHeading = /(\/\/ --- Vault[^\n]*\n)/;
401
+ if (vaultHeading.test(next)) {
402
+ return next.replace(vaultHeading, `$1\n${block}`);
403
+ }
404
+ const aiHeading = /(\/\/ --- AI[^\n]*\n)/;
405
+ if (aiHeading.test(next)) {
406
+ return next.replace(aiHeading, `${block}\n$1`);
407
+ }
408
+ return `${next.trimEnd()}\n\n${block}`;
409
+ }
410
+
411
+ /**
412
+ * Declare `vault.secret(apiKeyEnv)` in the project so missing keys surface as
413
+ * Vault gaps (and Console Vault) — never silently optional.
414
+ *
415
+ * Prefers `src/core/vault.ts` when present, else merges into `src/core.ts`.
416
+ *
417
+ * @param cwd - Project root
418
+ * @param apiKeyEnv - Env / contract name
419
+ */
420
+ export function ensureAiApiKeyVaultSecret(cwd: string, apiKeyEnv: string): void {
421
+ const coreVaultPath = join(cwd, "src", "core", "vault.ts");
422
+ if (existsSync(coreVaultPath)) {
423
+ const prev = readFileSync(coreVaultPath, "utf8");
424
+ const next = mergeAiApiKeyVaultSecret(prev, apiKeyEnv);
425
+ if (next !== prev) writeFileSync(coreVaultPath, next, "utf8");
426
+ return;
427
+ }
428
+ const coreTsPath = join(cwd, "src", "core.ts");
429
+ if (!existsSync(coreTsPath)) return;
430
+ const prev = readFileSync(coreTsPath, "utf8");
431
+ const next = mergeAiApiKeyVaultSecret(prev, apiKeyEnv);
432
+ if (next !== prev) writeFileSync(coreTsPath, next, "utf8");
433
+ }
434
+
246
435
  /**
247
436
  * Write AI model declarations into `src/core/ai.ts` when that split exists
248
437
  * (so a thin `src/core.ts` barrel stays a re-export), else `src/core.ts`,
@@ -260,14 +449,7 @@ function writeAiModels(cwd: string, input: AiSetupApplyInput): string {
260
449
 
261
450
  if (existsSync(coreAiPath)) {
262
451
  const existing = readFileSync(coreAiPath, "utf8");
263
- if (hasAiModels(existing)) {
264
- const withPrompt = ensureSummarizeNotePrompt(existing);
265
- if (withPrompt !== existing) {
266
- writeFileSync(coreAiPath, withPrompt, "utf8");
267
- }
268
- } else {
269
- writeFileSync(coreAiPath, mergeAiIntoCore(existing, rendered), "utf8");
270
- }
452
+ writeFileSync(coreAiPath, resolveAiCoreSource(existing, rendered), "utf8");
271
453
  ensureCoreBarrelExportsAi(cwd);
272
454
  ensureCoreImported(cwd);
273
455
  return coreAiPath;
@@ -285,14 +467,7 @@ function writeAiModels(cwd: string, input: AiSetupApplyInput): string {
285
467
  mkdirSync(dirname(coreTsPath), { recursive: true });
286
468
  if (existsSync(coreTsPath)) {
287
469
  const existing = readFileSync(coreTsPath, "utf8");
288
- if (hasAiModels(existing)) {
289
- const withPrompt = ensureSummarizeNotePrompt(existing);
290
- if (withPrompt !== existing) {
291
- writeFileSync(coreTsPath, withPrompt, "utf8");
292
- }
293
- return coreTsPath;
294
- }
295
- writeFileSync(coreTsPath, mergeAiIntoCore(existing, rendered), "utf8");
470
+ writeFileSync(coreTsPath, resolveAiCoreSource(existing, rendered), "utf8");
296
471
  } else {
297
472
  writeFileSync(coreTsPath, rendered, "utf8");
298
473
  }
@@ -300,17 +475,82 @@ function writeAiModels(cwd: string, input: AiSetupApplyInput): string {
300
475
  return coreTsPath;
301
476
  }
302
477
 
478
+ /**
479
+ * Decide whether to merge a full AI module, repair a broken stub, or only
480
+ * backfill `local` / `summarizeNote` on an already-complete core.
481
+ *
482
+ * @param existing - Current core / AI sidecar source
483
+ * @param rendered - Output of {@link renderAiTs}
484
+ */
485
+ export function resolveAiCoreSource(existing: string, rendered: string): string {
486
+ if (isIncompleteAiSetup(existing)) {
487
+ return mergeAiIntoCore(stripIncompleteAiExports(existing), rendered);
488
+ }
489
+ if (hasAiModels(existing)) {
490
+ return ensureSummarizeNotePrompt(existing);
491
+ }
492
+ return mergeAiIntoCore(existing, rendered);
493
+ }
494
+
303
495
  /**
304
496
  * @param source - Existing TypeScript
305
497
  */
306
498
  function hasAiModels(source: string): boolean {
499
+ // Require real exports — template comments like `// ai.model("smart", …)`
500
+ // must not look like an already-configured core.
307
501
  return (
308
- /\bai\.model\s*\(/.test(source) ||
502
+ /\bexport\s+const\s+(?:smart|local|vision|embedModel)\s*=\s*ai\.model\s*\(/.test(source) ||
309
503
  /from\s+["']\.\/(?:core\/)?ai["']/.test(source) ||
310
504
  /import\s+["']\.\/(?:core\/)?ai["']/.test(source)
311
505
  );
312
506
  }
313
507
 
508
+ /**
509
+ * True when prior AI setup left unusable stubs (e.g. `local` + `summarizeNote`
510
+ * without `smart` / without an `ai` import — the old comment-as-configured bug).
511
+ *
512
+ * @param source - Existing TypeScript
513
+ */
514
+ export function isIncompleteAiSetup(source: string): boolean {
515
+ const hasAiImport = /import\s*\{[^}]*\bai\b[^}]*\}\s*from\s*["']okengine["']/.test(source);
516
+ const hasSmart = /\bexport\s+const\s+smart\s*=\s*ai\.model\s*\(/.test(source);
517
+ const hasLocal = /\bexport\s+const\s+local\s*=\s*ai\.model\s*\(/.test(source);
518
+ const hasSummarize = /\bexport\s+const\s+summarizeNote\s*=/.test(source);
519
+ const hasAnyModel =
520
+ /\bexport\s+const\s+(?:smart|local|vision|embedModel)\s*=\s*ai\.model\s*\(/.test(source);
521
+ if (hasAnyModel && !hasAiImport) return true;
522
+ if ((hasLocal || hasSummarize) && !hasSmart) return true;
523
+ return false;
524
+ }
525
+
526
+ /**
527
+ * Remove partial AI exports so {@link mergeAiIntoCore} can rewrite a full set.
528
+ *
529
+ * @param source - Existing TypeScript
530
+ */
531
+ export function stripIncompleteAiExports(source: string): string {
532
+ let next = source;
533
+ next = next.replace(
534
+ /(?:\/\*\*[^*]*\*+(?:[^/*][^*]*\*+)*\/\s*)?export\s+const\s+local\s*=\s*ai\.model\s*\(\s*["']local["']\s*,\s*\{[\s\S]*?\}\s*\)\s*;\s*/g,
535
+ "",
536
+ );
537
+ next = next.replace(
538
+ /(?:\/\*\*[^*]*\*+(?:[^/*][^*]*\*+)*\/\s*)?export\s+const\s+summarizeNote\s*=\s*smart\.prompt\s*\([\s\S]*?\}\s*\)\s*;\s*/g,
539
+ "",
540
+ );
541
+ if (!/\bexport\s+const\s+smart\s*=\s*ai\.model\s*\(/.test(next)) {
542
+ next = next.replace(
543
+ /(?:\/\*\*[^*]*\*+(?:[^/*][^*]*\*+)*\/\s*)?export\s+const\s+(?:vision|embedModel)\s*=\s*ai\.model\s*\([\s\S]*?\}\s*\)\s*;\s*/g,
544
+ "",
545
+ );
546
+ next = next.replace(
547
+ /(?:\/\*\*[^*]*\*+(?:[^/*][^*]*\*+)*\/\s*)?export\s+const\s+docsEmbed\s*=\s*ai\.embed\s*\([\s\S]*?\}\s*\)\s*;\s*/g,
548
+ "",
549
+ );
550
+ }
551
+ return next.replace(/\n{3,}/g, "\n\n");
552
+ }
553
+
314
554
  /**
315
555
  * Append the advanced Notes `summarize-note` prompt when a `smart` model
316
556
  * exists but the prompt was never declared (common after older `--ai` runs).
@@ -319,24 +559,26 @@ function hasAiModels(source: string): boolean {
319
559
  */
320
560
  export function ensureSummarizeNotePrompt(source: string): string {
321
561
  let next = source;
322
- if (
323
- !/\bai\.model\s*\(\s*["']local["']/.test(next) &&
324
- /\bai\.model\s*\(\s*["']smart["']/.test(next)
325
- ) {
562
+ const hasSmartExport = /\bexport\s+const\s+smart\s*=/.test(next);
563
+ const hasLocalExport = /\bexport\s+const\s+local\s*=/.test(next);
564
+ if (!hasLocalExport && hasSmartExport) {
326
565
  next = `${next.trimEnd()}
327
566
 
328
- /** Local inference binding (docker llama.cpp / Ollama via \`OKE_AI_URL\`). */
567
+ /** Local OpenAI-compatible binding (via \`OKE_AI_URL\`). */
329
568
  export const local = ai.model("local", {
330
569
  provider: "openai-compatible",
331
- model: process.env.OKE_AI_LOCAL_MODEL ?? "granite3.3:2b",
570
+ model: process.env.OKE_AI_LOCAL_MODEL ?? "local-model",
332
571
  ...(process.env.OKE_AI_URL?.trim() ? { baseUrl: process.env.OKE_AI_URL.trim() } : {}),
333
572
  });
334
573
  `;
335
574
  }
336
- if (/summarize-note/.test(next) || /summarizeNote/.test(next)) {
337
- return next;
575
+ const hasSummarizeExport =
576
+ /\bexport\s+const\s+summarizeNote\s*=/.test(next) ||
577
+ /\.prompt\s*\(\s*["']summarize-note["']/.test(next);
578
+ if (hasSummarizeExport) {
579
+ return next === source ? next : ensureNamedOkengineImport(next, "ai");
338
580
  }
339
- if (!/\bai\.model\s*\(\s*["']smart["']/.test(next)) {
581
+ if (!hasSmartExport) {
340
582
  return next;
341
583
  }
342
584
  const prompt = `
@@ -346,7 +588,7 @@ export const summarizeNote = smart.prompt("summarize-note", {
346
588
  timeout: "30s",
347
589
  });
348
590
  `;
349
- return `${next.trimEnd()}\n${prompt}\n`;
591
+ return ensureNamedOkengineImport(`${next.trimEnd()}\n${prompt}\n`, "ai");
350
592
  }
351
593
 
352
594
  /**