okengine 0.22.0 → 0.23.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 (262) hide show
  1. package/AGENTS.md +7 -0
  2. package/manifest.v1.schema.json +44 -0
  3. package/package.json +32 -19
  4. package/site/content/docs/ai/mcp.mdx +13 -9
  5. package/site/content/docs/ai/skills.mdx +1 -1
  6. package/site/content/docs/elements/ai/agents.mdx +61 -13
  7. package/site/content/docs/elements/ai/decide.mdx +536 -0
  8. package/site/content/docs/elements/ai/events.mdx +373 -0
  9. package/site/content/docs/elements/ai/index.mdx +9 -3
  10. package/site/content/docs/elements/ai/meta.json +1 -1
  11. package/site/content/docs/elements/ai/prompts.mdx +4 -2
  12. package/site/content/docs/elements/gate/auth.mdx +4 -1
  13. package/site/content/docs/elements/store/index.mdx +1 -1
  14. package/site/content/docs/elements/store/kv.mdx +5 -5
  15. package/site/content/docs/recipes/dragonfly.mdx +5 -5
  16. package/site/content/docs/recipes/index.mdx +6 -6
  17. package/site/content/docs/recipes/postgres.mdx +12 -8
  18. package/site/content/docs/recipes/redis.mdx +4 -4
  19. package/site/content/docs/reference/cli.mdx +2 -1
  20. package/site/content/docs/reference/configuration.mdx +9 -7
  21. package/site/content/docs/reference/fx.mdx +7 -6
  22. package/src/auth/api-key-sql.test.ts +44 -0
  23. package/src/auth/api-key-sql.ts +1 -0
  24. package/src/auth/api-keys.ts +30 -12
  25. package/src/auth/config.ts +6 -2
  26. package/src/auth/gate-auth.test.ts +20 -0
  27. package/src/cli/agents-md.test.ts +25 -0
  28. package/src/cli/ask-vault-gaps.test.ts +38 -1
  29. package/src/cli/ask-vault-gaps.ts +35 -2
  30. package/src/cli/decide.ts +174 -0
  31. package/src/cli/decision-lock-watch.test.ts +45 -0
  32. package/src/cli/decision-lock-watch.ts +51 -0
  33. package/src/cli/dev-app-runner.ts +34 -8
  34. package/src/cli/dev-auth-secret.test.ts +49 -0
  35. package/src/cli/dev-auth-secret.ts +49 -0
  36. package/src/cli/dev-hot.test.ts +41 -0
  37. package/src/cli/dev-hot.ts +48 -0
  38. package/src/cli/dev.test.ts +1 -1
  39. package/src/cli/dev.ts +24 -6
  40. package/src/cli/eval.ts +142 -1
  41. package/src/cli/index.ts +5 -0
  42. package/src/cli/load-config.images.test.ts +2 -2
  43. package/src/cli/load-config.ts +1 -1
  44. package/src/cli/mcp-from-console.ts +48 -0
  45. package/src/cli/registry.ts +35 -1
  46. package/src/client/agent.ts +307 -0
  47. package/src/client/transport.test.ts +20 -0
  48. package/src/client/transport.ts +2 -1
  49. package/src/client/types.ts +1 -1
  50. package/src/client-react/index.ts +7 -1
  51. package/src/client-react/use-agent-run.test.ts +271 -0
  52. package/src/client-react/use-agent-run.ts +193 -0
  53. package/src/compiler/ai-approval.test.ts +36 -0
  54. package/src/compiler/ai-repair.test.ts +22 -0
  55. package/src/compiler/decisions.extract.test.ts +164 -0
  56. package/src/compiler/effects-infer.ts +27 -1
  57. package/src/compiler/extract.ts +268 -12
  58. package/src/compiler/response.ts +35 -2
  59. package/src/config/index.ts +21 -3
  60. package/src/console/server/ai-runs-flows.ts +269 -0
  61. package/src/console/server/ai-runs.test.ts +323 -0
  62. package/src/console/server/ai-runs.ts +562 -0
  63. package/src/console/server/ai.ts +12 -1
  64. package/src/console/server/app.ts +10 -1
  65. package/src/console/server/decisions-flows.ts +157 -0
  66. package/src/console/server/decisions.test.ts +66 -0
  67. package/src/console/server/decisions.ts +229 -0
  68. package/src/console/server/flows.ts +50 -2
  69. package/src/console/server/invoke-user-flow.ts +9 -0
  70. package/src/console/server/runs-ingest.ts +16 -0
  71. package/src/console/server/serve.ts +5 -0
  72. package/src/console/server/state.ts +25 -1
  73. package/src/console/server/store-stats.test.ts +47 -0
  74. package/src/console/server/store-stats.ts +92 -18
  75. package/src/console/ui-next/dist/assets/access-page-DDKkhT9v.js +4 -0
  76. package/src/console/ui-next/dist/assets/{agent-disclosure-Bkohc_8X.js → agent-disclosure-62Xts8Xi.js} +1 -1
  77. package/src/console/ui-next/dist/assets/{cache-glyph-rL0lHxat.js → cache-glyph-DyeoKHKA.js} +1 -1
  78. package/src/console/ui-next/dist/assets/{call-pii-button-Deo4PP18.js → call-pii-button-DvfpLXsU.js} +1 -1
  79. package/src/console/ui-next/dist/assets/{collapsible-CoJ6amHf.js → collapsible-BY03SeCg.js} +1 -1
  80. package/src/console/ui-next/dist/assets/confirm-sheet-DZfuJXP4.js +1 -0
  81. package/src/console/ui-next/dist/assets/copy-inline-button-BTjPVjRT.js +1 -0
  82. package/src/console/ui-next/dist/assets/decisions-page-CgkKlA56.js +1 -0
  83. package/src/console/ui-next/dist/assets/detail-header-Dk5LipF6.js +1 -0
  84. package/src/console/ui-next/dist/assets/dropdown-menu-CRGrj5JU.js +1 -0
  85. package/src/console/ui-next/dist/assets/duration-tone-Y6HLjyJ5.js +9 -0
  86. package/src/console/ui-next/dist/assets/element-icons-C6t8Vlml.js +1 -0
  87. package/src/console/ui-next/dist/assets/explorer-chrome-Dslh2yE8.js +1 -0
  88. package/src/console/ui-next/dist/assets/explorer-empty-CIVg7Jgb.js +1 -0
  89. package/src/console/ui-next/dist/assets/flows-page-DLPfNy-_.js +1 -0
  90. package/src/console/ui-next/dist/assets/{highlighted-json-CPYBzDh0.js → highlighted-json-D7nNzpgB.js} +1 -1
  91. package/src/console/ui-next/dist/assets/{http-method-DTPevKmM.js → http-method-DdL19zzh.js} +1 -1
  92. package/src/console/ui-next/dist/assets/index-BlrN49Hs.css +2 -0
  93. package/src/console/ui-next/dist/assets/index-C0lc_s9d.js +58 -0
  94. package/src/console/ui-next/dist/assets/observability-page-CRlcyLCC.js +8 -0
  95. package/src/console/ui-next/dist/assets/{replica-lag-BM27lXld.js → replica-lag-Bb3FWUu9.js} +7 -7
  96. package/src/console/ui-next/dist/assets/request-meta-DYBMWhX8.js +1 -0
  97. package/src/console/ui-next/dist/assets/shortcut-keys-DTE1sjTC.js +1 -0
  98. package/src/console/ui-next/dist/assets/store-page-CcE-SXC8.js +41 -0
  99. package/src/console/ui-next/dist/assets/trace-detail-sheet-CKMSEQ4s.js +2 -0
  100. package/src/console/ui-next/dist/assets/tree-expand-toggle-QOfk0M0H.js +55 -0
  101. package/src/console/ui-next/dist/assets/units-page-DKC1CDDd.js +1 -0
  102. package/src/console/ui-next/dist/assets/use-vault-list-BAyQG8Re.js +1 -0
  103. package/src/console/ui-next/dist/assets/vault-page-B54v4Yit.js +2 -0
  104. package/src/console/ui-next/dist/index.html +4 -3
  105. package/src/console/ui-next/seed-parked-approval.ts +126 -0
  106. package/src/console/ui-next/src/client.ts +224 -1
  107. package/src/console/ui-next/src/components/explorer/detail-header.tsx +9 -0
  108. package/src/console/ui-next/src/components/explorer/explorer-start-toggle.tsx +5 -1
  109. package/src/console/ui-next/src/components/ui/sheet-form.tsx +21 -12
  110. package/src/console/ui-next/src/features/flows/decisions/decisions-page.tsx +216 -0
  111. package/src/console/ui-next/src/features/flows/decisions/resolve.test.ts +48 -0
  112. package/src/console/ui-next/src/features/flows/decisions/resolve.ts +111 -0
  113. package/src/console/ui-next/src/features/flows/flows-page.tsx +10 -0
  114. package/src/console/ui-next/src/features/flows/graph/element-map.ts +1 -0
  115. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +6 -0
  116. package/src/console/ui-next/src/features/flows/traces/effect-summary.test.ts +24 -0
  117. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
  118. package/src/console/ui-next/src/features/flows/traces/http-status.ts +58 -0
  119. package/src/console/ui-next/src/features/flows/traces/request-client-card.tsx +355 -0
  120. package/src/console/ui-next/src/features/flows/traces/request-client.test.ts +114 -0
  121. package/src/console/ui-next/src/features/flows/traces/request-client.ts +423 -0
  122. package/src/console/ui-next/src/features/flows/traces/request-input-view.test.ts +52 -6
  123. package/src/console/ui-next/src/features/flows/traces/request-input-view.ts +85 -19
  124. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +505 -80
  125. package/src/console/ui-next/src/features/flows/traces/trace-lanes.test.ts +61 -0
  126. package/src/console/ui-next/src/features/flows/traces/trace-lanes.ts +78 -0
  127. package/src/console/ui-next/src/features/flows/traces/trace-nav.test.ts +48 -0
  128. package/src/console/ui-next/src/features/flows/traces/trace-nav.ts +57 -0
  129. package/src/console/ui-next/src/features/flows/traces/trace-request-section.tsx +275 -96
  130. package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +16 -2
  131. package/src/console/ui-next/src/features/observability/detail/ai-runs.tsx +369 -0
  132. package/src/console/ui-next/src/features/observability/observability-page.tsx +31 -1
  133. package/src/console/ui-next/src/features/observability/state/observability-selection.ts +18 -3
  134. package/src/console/ui-next/src/features/store/performance/kv-performance-panel.tsx +12 -1
  135. package/src/console/ui-next/src/features/store/performance/performance-panel.tsx +14 -1
  136. package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +1 -1
  137. package/src/console/ui-next/src/features/units/detail/flow-contract-panel.tsx +6 -2
  138. package/src/console/ui-next/src/features/units/explorer/units-tree.tsx +73 -18
  139. package/src/console/ui-next/src/features/units/lib/unit-tree.test.ts +21 -0
  140. package/src/console/ui-next/src/features/units/lib/unit-tree.ts +82 -0
  141. package/src/console/ui-next/src/features/units/units-page.tsx +25 -7
  142. package/src/console/ui-next/src/router.tsx +17 -0
  143. package/src/console/ui-next/ui-next-seed-runs.ts +23 -0
  144. package/src/console/ui-next/vite-console-kernel-plugin.ts +43 -3
  145. package/src/console/ui-next/vite.config.ts +9 -0
  146. package/src/docker/recipes/dragonfly.ts +3 -2
  147. package/src/docker/recipes/redis.ts +1 -1
  148. package/src/drivers/ai-anthropic.ts +212 -21
  149. package/src/drivers/ai-mock.ts +34 -1
  150. package/src/drivers/ai-openai-compatible.ts +67 -26
  151. package/src/drivers/ai-preconnect.ts +18 -0
  152. package/src/drivers/ai-stream.test.ts +62 -0
  153. package/src/drivers/ai-types.ts +20 -0
  154. package/src/drivers/journal-postgres.ts +289 -2
  155. package/src/drivers/memory-ddl.test.ts +18 -0
  156. package/src/drivers/memory.ts +21 -1
  157. package/src/drivers/pg-rls.test.ts +31 -0
  158. package/src/drivers/pg-rls.ts +15 -0
  159. package/src/drivers/postgres.test.ts +9 -0
  160. package/src/drivers/postgres.ts +49 -11
  161. package/src/elements/ai/agent-event-slot.ts +27 -0
  162. package/src/elements/ai/approval-http.ts +207 -0
  163. package/src/elements/ai/approval.test.ts +741 -0
  164. package/src/elements/ai/approval.ts +228 -0
  165. package/src/elements/ai/decisions/bind.ts +163 -0
  166. package/src/elements/ai/decisions/certificate.test.ts +291 -0
  167. package/src/elements/ai/decisions/certificate.ts +377 -0
  168. package/src/elements/ai/decisions/certify.ts +314 -0
  169. package/src/elements/ai/decisions/decide.test.ts +953 -0
  170. package/src/elements/ai/decisions/e2e.test.ts +399 -0
  171. package/src/elements/ai/decisions/export.test.ts +120 -0
  172. package/src/elements/ai/decisions/export.ts +146 -0
  173. package/src/elements/ai/decisions/fixtures/openrouter-request.json +24 -0
  174. package/src/elements/ai/decisions/fixtures/openrouter-response.json +22 -0
  175. package/src/elements/ai/decisions/fixtures/typesafe-request.json +19 -0
  176. package/src/elements/ai/decisions/fixtures/typesafe-response.json +13 -0
  177. package/src/elements/ai/decisions/http.test.ts +154 -0
  178. package/src/elements/ai/decisions/http.ts +240 -0
  179. package/src/elements/ai/decisions/labels.test.ts +282 -0
  180. package/src/elements/ai/decisions/labels.ts +257 -0
  181. package/src/elements/ai/decisions/openrouter.live.test.ts +136 -0
  182. package/src/elements/ai/decisions/openrouter.ts +36 -0
  183. package/src/elements/ai/decisions/provider.ts +102 -0
  184. package/src/elements/ai/decisions/typesafe.ts +36 -0
  185. package/src/elements/ai/declare.ts +252 -1
  186. package/src/elements/ai/events.test.ts +379 -0
  187. package/src/elements/ai/events.ts +138 -0
  188. package/src/elements/ai/run-events.test.ts +461 -0
  189. package/src/elements/ai/run-events.ts +522 -0
  190. package/src/elements/ai/runtime.ts +1186 -105
  191. package/src/elements/ai/stream-turn.ts +214 -0
  192. package/src/elements/ai/stream.live.test.ts +76 -0
  193. package/src/elements/ai/subagent.test.ts +136 -0
  194. package/src/elements/ai.test.ts +497 -3
  195. package/src/elements/ai.ts +16 -1
  196. package/src/elements/clock/durable.ts +27 -1
  197. package/src/elements/store/sql-session.test.ts +51 -0
  198. package/src/elements/store/sql-session.ts +111 -1
  199. package/src/i18n/catalogs/ar.ts +5 -0
  200. package/src/i18n/catalogs/en.ts +5 -0
  201. package/src/index.ts +2 -0
  202. package/src/kernel/agent-event-store.ts +448 -0
  203. package/src/kernel/app.ts +103 -1
  204. package/src/kernel/boot-bind/ai.ts +3 -0
  205. package/src/kernel/boot-bind/store.test.ts +26 -0
  206. package/src/kernel/boot-bind/store.ts +82 -6
  207. package/src/kernel/boot.ts +15 -4
  208. package/src/kernel/builtin-errors.ts +2 -0
  209. package/src/kernel/capability.ts +3 -0
  210. package/src/kernel/decision-label-store.ts +441 -0
  211. package/src/kernel/effects.test.ts +2 -1
  212. package/src/kernel/effects.ts +13 -1
  213. package/src/kernel/element-registries.ts +3 -0
  214. package/src/kernel/errors-text.ts +4 -0
  215. package/src/kernel/errors.ts +2 -0
  216. package/src/kernel/flow.test.ts +2 -3
  217. package/src/kernel/fx-decide.ts +761 -0
  218. package/src/kernel/fx.test.ts +12 -5
  219. package/src/kernel/fx.ts +312 -29
  220. package/src/kernel/http-frame.test.ts +94 -0
  221. package/src/kernel/http-frame.ts +203 -0
  222. package/src/kernel/journal.ts +104 -0
  223. package/src/kernel/json-result.ts +10 -0
  224. package/src/kernel/pipeline.test.ts +45 -0
  225. package/src/kernel/sse-id.ts +32 -0
  226. package/src/manifest/diff.ts +1 -0
  227. package/src/manifest/types.ts +29 -2
  228. package/src/mcp/ai-tools.test.ts +202 -0
  229. package/src/mcp/authorization.ts +66 -0
  230. package/src/mcp/session.ts +3 -0
  231. package/src/mcp/tools.ts +168 -0
  232. package/src/release/http-graph.test.ts +2 -0
  233. package/src/release/http-graph.ts +5 -0
  234. package/src/release/measure.ts +39 -14
  235. package/src/runs/collect.ts +4 -1
  236. package/src/runs/parquet.test.ts +27 -1
  237. package/src/runs/parquet.ts +19 -0
  238. package/src/runs/types.ts +31 -0
  239. package/src/runtime/json-code-block.test.ts +106 -47
  240. package/src/runtime/json-code-block.ts +1209 -46
  241. package/src/term.test.ts +13 -0
  242. package/src/term.ts +81 -49
  243. package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +0 -1
  244. package/src/console/ui-next/dist/assets/access-page-Ze6ckjdd.js +0 -4
  245. package/src/console/ui-next/dist/assets/copy-inline-button-CWG5_ZOh.js +0 -1
  246. package/src/console/ui-next/dist/assets/detail-header-b_ZzIsmU.js +0 -1
  247. package/src/console/ui-next/dist/assets/dropdown-menu-nwXEO1Ac.js +0 -1
  248. package/src/console/ui-next/dist/assets/duration-tone-BbQ_8z50.js +0 -9
  249. package/src/console/ui-next/dist/assets/element-icons-CwyLpVXz.js +0 -1
  250. package/src/console/ui-next/dist/assets/explorer-empty-C8sSCqYT.js +0 -1
  251. package/src/console/ui-next/dist/assets/flows-page-D2E7BtKK.js +0 -1
  252. package/src/console/ui-next/dist/assets/index-VxoEz295.css +0 -2
  253. package/src/console/ui-next/dist/assets/index-vTuwmeQz.js +0 -58
  254. package/src/console/ui-next/dist/assets/observability-page-SJTSzHFJ.js +0 -4
  255. package/src/console/ui-next/dist/assets/request-meta-BjFT7DNl.js +0 -1
  256. package/src/console/ui-next/dist/assets/shortcut-keys-CUNegEV4.js +0 -1
  257. package/src/console/ui-next/dist/assets/store-page-Qj9ThRE-.js +0 -41
  258. package/src/console/ui-next/dist/assets/trace-detail-sheet-Cmitq3da.js +0 -2
  259. package/src/console/ui-next/dist/assets/tree-expand-toggle-CbDIB-7x.js +0 -55
  260. package/src/console/ui-next/dist/assets/units-page-_PuYqFty.js +0 -1
  261. package/src/console/ui-next/dist/assets/use-vault-list-Cs45Czkt.js +0 -1
  262. package/src/console/ui-next/dist/assets/vault-page-BOMr0go9.js +0 -2
package/src/kernel/app.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  * pipeline, and wires `fx.call` to untriggered (and triggered) flows.
7
7
  */
8
8
 
9
+ import { bindAgentApprovalFlows } from "../elements/ai/approval-http.ts";
9
10
  import { compileRoute } from "../compiler/dynamic.ts";
10
11
  import type { CompiledRoute } from "../compiler/aot.ts";
11
12
  import { encodeExecuteResult, encodeFailure } from "../compiler/response.ts";
@@ -67,6 +68,7 @@ import {
67
68
  import { GATE_PUBLIC_NAME } from "../elements/gate/flatten.ts";
68
69
  import type { JsonCodeAuth } from "../runtime/json-code-block.ts";
69
70
  import { resolveDurationMs } from "./elapsed.ts";
71
+ import { captureHttpFrame } from "./http-frame.ts";
70
72
  import { fail, throwOke, OkeError } from "./errors.ts";
71
73
  import { FLOW_NAME_DUPLICATE, FLOW_UNNAMED } from "./errors-flow-name.ts";
72
74
  import { ONCE_SIGNAL_MULTI_FLOW } from "./errors-once-signal.ts";
@@ -217,6 +219,39 @@ function loadClientDescriptor(): typeof import("./client-descriptor.ts") {
217
219
  return lazyRequire(import.meta.dir, ["client", "descriptor"].join("-"));
218
220
  }
219
221
 
222
+ /** Decision HTTP flows. A static `import()` still lands on every HTTP bundle. */
223
+ function loadDecisionBind(): typeof import("../elements/ai/decisions/bind.ts") {
224
+ return lazyRequire(`${import.meta.dir}/../elements/ai/decisions`, ["bi", "nd"].join(""));
225
+ }
226
+
227
+ /** Decision lockfile. Loaded only when the manifest declares decisions. */
228
+ function loadDecisionCertificate(): typeof import("../elements/ai/decisions/certificate.ts") {
229
+ return lazyRequire(`${import.meta.dir}/../elements/ai/decisions`, ["certificate"].join(""));
230
+ }
231
+
232
+ /** Decision label opener. Loaded only when the manifest declares decisions. */
233
+ function loadDecisionLabels(): typeof import("../elements/ai/decisions/labels.ts") {
234
+ return lazyRequire(`${import.meta.dir}/../elements/ai/decisions`, ["lab", "els"].join(""));
235
+ }
236
+
237
+ /** Drift-flag registration. Loaded only when the manifest declares decisions. */
238
+ function loadDecisionLabelStore(): typeof import("./decision-label-store.ts") {
239
+ return lazyRequire(import.meta.dir, ["decision", "label", "store"].join("-"));
240
+ }
241
+
242
+ /** `fx.decide` gate hook. Loaded only when the manifest declares decisions. */
243
+ function loadFxDecideModule(): typeof import("./fx-decide.ts") {
244
+ return lazyRequire(import.meta.dir, ["fx", "decide"].join("-"));
245
+ }
246
+
247
+ /**
248
+ * Full boot graph (drivers, elements). A static `import()` string is still
249
+ * bundled into every HTTP app; the stem stays computed.
250
+ */
251
+ function loadBoot(): typeof import("./boot.ts") {
252
+ return lazyRequire(import.meta.dir, ["bo", "ot"].join(""));
253
+ }
254
+
220
255
  /** Options for {@link oke}. */
221
256
  export interface OkeOptions {
222
257
  /** Application name (Manifest `app`). */
@@ -1095,6 +1130,8 @@ export function oke(options: OkeOptions): OkeApp {
1095
1130
  // contribute auth-method Bindings before traffic (plan Phase 2).
1096
1131
  const router: Router<Binding> = smart;
1097
1132
 
1133
+ bindAgentApprovalFlows(adoptBinding);
1134
+
1098
1135
  function adoptBinding(b: Binding): void {
1099
1136
  adopted.push(b);
1100
1137
  registerFlow(b.flow);
@@ -1115,6 +1152,24 @@ export function oke(options: OkeOptions): OkeApp {
1115
1152
 
1116
1153
  /** Once-per-app: system `_oke_search_embed_*` CDC bindings from Manifest. */
1117
1154
  let searchEmbedFlowsBound = false;
1155
+ let decisionFlowsBound = false;
1156
+
1157
+ /**
1158
+ * Register the decision aggregate, drift monitor, and candidate route
1159
+ * when any decision declares autonomy.
1160
+ *
1161
+ * @param manifest - Boot manifest
1162
+ */
1163
+ async function ensureDecisionFlows(manifest: BootOptions["manifest"] | undefined): Promise<void> {
1164
+ if (decisionFlowsBound || !manifest) return;
1165
+ const { bindDecisionFlows, manifestHasDecisionAutonomy } = loadDecisionBind();
1166
+ if (!manifestHasDecisionAutonomy(manifest)) {
1167
+ decisionFlowsBound = true;
1168
+ return;
1169
+ }
1170
+ bindDecisionFlows(adoptBinding, manifest);
1171
+ decisionFlowsBound = true;
1172
+ }
1118
1173
  /**
1119
1174
  * Auto-register hybrid-search embed CDC flows when any `.embed()` column
1120
1175
  * exists. Lazy-loads search-bind so edge graphs without search stay lean.
@@ -1339,6 +1394,19 @@ export function oke(options: OkeOptions): OkeApp {
1339
1394
  ...durableResumeFx(),
1340
1395
  ...(gateConfig.auth?.tenant ? (loadAppTenant().w(5, gateConfig.auth) as object) : {}),
1341
1396
  ...(existing?.tenant ? { tenant: { id: existing.tenant } } : {}),
1397
+ callHandler: async (name, callInput) => {
1398
+ const target = flowsByName.get(name);
1399
+ if (!target) return undefined;
1400
+ const inner = await execute(
1401
+ target,
1402
+ callInput,
1403
+ { kind: "internal" } satisfies InternalTrigger,
1404
+ { parentId: runId },
1405
+ );
1406
+ if (inner.failure) return inner.failure;
1407
+ if (inner.ctx.error !== undefined) throw inner.ctx.error;
1408
+ return inner.output;
1409
+ },
1342
1410
  },
1343
1411
  });
1344
1412
  } catch (err) {
@@ -1367,7 +1435,7 @@ export function oke(options: OkeOptions): OkeApp {
1367
1435
  }
1368
1436
 
1369
1437
  async function doBoot(overrides?: Partial<BootOptions>): Promise<BootResult> {
1370
- const { bootApplication, resolveElementNeeds } = await import("./boot.ts");
1438
+ const { bootApplication, resolveElementNeeds } = loadBoot();
1371
1439
  const { assertHttpGatePosture } = await import("../elements/gate/boot.ts");
1372
1440
  const { assertPluginNeeds, buildAvailableNeedTokens } = await import("./plugin-needs.ts");
1373
1441
  const dockerFlag =
@@ -1388,6 +1456,7 @@ export function oke(options: OkeOptions): OkeApp {
1388
1456
  }
1389
1457
 
1390
1458
  const unguardedHttp = overrides?.unguardedHttp ?? gateConfig.unguardedHttp ?? "deny";
1459
+ await ensureDecisionFlows(overrides?.manifest ?? options.manifest);
1391
1460
  // Auth bindings are in `adopted` — missing posture fails with GateBootError.
1392
1461
  assertHttpGatePosture(adopted, { unguardedHttp, env: bootEnv });
1393
1462
 
@@ -1557,6 +1626,26 @@ export function oke(options: OkeOptions): OkeApp {
1557
1626
  // Built-in hybrid search — auto-register durable embed CDC flows when the
1558
1627
  // Manifest declares any `.embed()` column (writer flows stay embed-free).
1559
1628
  await ensureSearchEmbedFlows(overrides?.manifest ?? options.manifest);
1629
+ const decisionManifest = overrides?.manifest ?? options.manifest;
1630
+ const declaredDecisions = decisionManifest?.ai?.decisions;
1631
+ if (declaredDecisions && Object.keys(declaredDecisions).length > 0) {
1632
+ const { loadDecisionLockfile, setDecisionDrift } = loadDecisionCertificate();
1633
+ const { openDecisionLabelStore } = loadDecisionLabels();
1634
+ const { setDeclaredDriftDecisions } = loadDecisionLabelStore();
1635
+ const { setDecisionGateAllow } = loadFxDecideModule();
1636
+ const root = overrides?.rootDir ?? options.rootDir ?? process.env["OKE_ROOT_DIR"];
1637
+ if (!root) {
1638
+ throw new Error(
1639
+ "oke boot: decisions are declared but rootDir and OKE_ROOT_DIR are unset, so the decision lockfile cannot load.",
1640
+ );
1641
+ }
1642
+ await loadDecisionLockfile(root);
1643
+ setDeclaredDriftDecisions(Object.keys(declaredDecisions));
1644
+ if (result.journal && result.journal.store.decisions) {
1645
+ await openDecisionLabelStore(result.journal.store, setDecisionDrift);
1646
+ }
1647
+ if (result.gate) setDecisionGateAllow((names, ctx) => result.gate!.allow(names, ctx));
1648
+ }
1560
1649
  // otp() provider / app mode capability — fail loud at boot, never silent downgrade.
1561
1650
  if (result.channel) {
1562
1651
  const { assertOtpPluginCapability } = await import("../auth/otp-capability.ts");
@@ -2257,6 +2346,14 @@ export function oke(options: OkeOptions): OkeApp {
2257
2346
  : durationMs;
2258
2347
  if (journalSession) {
2259
2348
  inflightRuns.delete(journalSession.runId);
2349
+ const parked = streamOut?.parked;
2350
+ if (parked) {
2351
+ ctx.state.sleeping = {
2352
+ wakeAt: parked.wakeAt,
2353
+ label: parked.label,
2354
+ runId: journalSession.runId,
2355
+ };
2356
+ }
2260
2357
  const sleeping = ctx.state.sleeping as
2261
2358
  | { readonly wakeAt: number; readonly label: string; readonly runId: string }
2262
2359
  | undefined;
@@ -2277,6 +2374,8 @@ export function oke(options: OkeOptions): OkeApp {
2277
2374
  fx,
2278
2375
  error: terminalErr,
2279
2376
  });
2377
+ } else if (parked && sleeping) {
2378
+ await journalSession.commit("sleeping", { wakeAt: sleeping.wakeAt });
2280
2379
  } else if (!sleeping) {
2281
2380
  await journalSession.commit("completed", {
2282
2381
  output: streamOut ? { streamed: true } : result.output,
@@ -2309,6 +2408,9 @@ export function oke(options: OkeOptions): OkeApp {
2309
2408
  ? { output: streamOut ? { streamed: true } : result.output }
2310
2409
  : {}),
2311
2410
  ...(parentId !== undefined ? { parentId } : {}),
2411
+ ...(trigger.kind === "http" && extras?.request
2412
+ ? { http: captureHttpFrame(extras.request, result.response) }
2413
+ : {}),
2312
2414
  },
2313
2415
  archiveCleartext,
2314
2416
  );
@@ -14,6 +14,7 @@ import type { AiDriver, AiOpenOptions } from "../../drivers/ai-types.ts";
14
14
  import { createAiRuntime, type AiRuntime } from "../../elements/ai.ts";
15
15
  import type { GateRuntime } from "../../elements/gate.ts";
16
16
  import type { VaultRuntime } from "../../elements/vault.ts";
17
+ import type { JournalStore } from "../journal.ts";
17
18
  import type { BootOptions } from "../boot.ts";
18
19
 
19
20
  /**
@@ -33,6 +34,7 @@ export function bindAi(
33
34
  env: ConfigEnv = "test",
34
35
  docker = false,
35
36
  vault?: VaultRuntime,
37
+ journalStore?: JournalStore,
36
38
  ): AiRuntime {
37
39
  const id = resolveAiDriverId(options, env, docker);
38
40
  const driver =
@@ -48,6 +50,7 @@ export function bindAi(
48
50
  },
49
51
  gates: options.ai?.gates ?? gate,
50
52
  now,
53
+ ...(journalStore ? { journalStore } : {}),
51
54
  });
52
55
  }
53
56
 
@@ -13,6 +13,7 @@ import {
13
13
  resolveIndexDriverId,
14
14
  resolveKvDriverId,
15
15
  resolveSqlDriverId,
16
+ sqlBindingForDecl,
16
17
  } from "./store.ts";
17
18
 
18
19
  describe("bindStore driver resolution", () => {
@@ -99,6 +100,31 @@ describe("bindStore driver resolution", () => {
99
100
  });
100
101
  });
101
102
 
103
+ describe("sqlBindingForDecl", () => {
104
+ test("pool.max and replica URLs land on the binding; min is ignored", () => {
105
+ const binding = sqlBindingForDecl("db", "postgres://primary/db", {
106
+ driver: "postgres",
107
+ pool: { max: 20, min: 2 },
108
+ replicas: ["postgres://ro-1/db", "postgres://ro-2/db"],
109
+ });
110
+ expect(binding).toEqual({
111
+ name: "db",
112
+ primary: { url: "postgres://primary/db", pool: { max: 20 } },
113
+ replicas: [
114
+ { url: "postgres://ro-1/db", pool: { max: 20 } },
115
+ { url: "postgres://ro-2/db", pool: { max: 20 } },
116
+ ],
117
+ });
118
+ });
119
+
120
+ test("a string pin keeps the URL and omits pool and replicas", () => {
121
+ expect(sqlBindingForDecl("db", "memory://", "memory")).toEqual({
122
+ name: "db",
123
+ primary: { url: "memory://" },
124
+ });
125
+ });
126
+ });
127
+
102
128
  describe("bindStore durable KV routing", () => {
103
129
  const prev = {
104
130
  redis: process.env.REDIS_URL,
@@ -2,7 +2,14 @@
2
2
  * Lazy store binder — loaded only when Store is declared.
3
3
  */
4
4
 
5
- import { resolveDomainDdlMode, resolveDriverId, type ConfigEnv } from "../../config/index.ts";
5
+ import {
6
+ driverRefId,
7
+ resolveDomainDdlMode,
8
+ resolveDriverId,
9
+ resolveDriverRef,
10
+ type ConfigEnv,
11
+ type DriverRef,
12
+ } from "../../config/index.ts";
6
13
  import {
7
14
  STORE_FILES_DEFAULTS,
8
15
  STORE_KV_DEFAULTS,
@@ -18,6 +25,7 @@ import { redisDriver } from "../../drivers/redis.ts";
18
25
  import { s3Driver } from "../../drivers/s3.ts";
19
26
  import type { FilesDriver, IndexDriver, KvDriver, SqlDriver } from "../../drivers/types.ts";
20
27
  import { createStoreRuntime, type StoreRuntime } from "../../elements/store.ts";
28
+ import type { SqlRuntimeBinding } from "../../elements/store/runtime.ts";
21
29
  import type { StoreDecl } from "../../elements/store/declare.ts";
22
30
  import { emitBootWarn } from "../../runtime/boot-warn.ts";
23
31
  import type { BootOptions } from "../boot.ts"; // type-only — no cycle at runtime
@@ -129,17 +137,15 @@ export function bindStore(
129
137
  warnKvMemoryDurable(durableKvDecls.map((d) => d.name));
130
138
  }
131
139
 
132
- const sqlBindings: Record<string, { name: string; primary: { url: string } }> = {};
140
+ const sqlRef = resolveSqlDriverRef(options, env, docker);
141
+ const sqlBindings: Record<string, SqlRuntimeBinding> = {};
133
142
  const kvBindings: Record<string, { url?: string }> = {};
134
143
  const filesBindings: Record<string, { root?: string }> = {};
135
144
  const indexBindings: Record<string, { url?: string; apiKey?: string }> = {};
136
145
 
137
146
  for (const decl of options.stores ?? []) {
138
147
  if (isSqlDecl(decl)) {
139
- sqlBindings[decl.name] = {
140
- name: decl.name,
141
- primary: { url: sqlUrl },
142
- };
148
+ sqlBindings[decl.name] = sqlBindingForDecl(decl.name, sqlUrl, sqlRef);
143
149
  } else if (isKvDecl(decl)) {
144
150
  if (decl.durable === true) {
145
151
  kvBindings[decl.name] = {};
@@ -210,6 +216,76 @@ export function resolveSqlDriverId(options: BootOptions, env: ConfigEnv, docker:
210
216
  return resolveDriverId(options.config?.drivers?.store?.sql, env, STORE_SQL_DEFAULTS)!;
211
217
  }
212
218
 
219
+ /**
220
+ * Rich SQL driver ref for the active env. A docker `OKE_SQL_DRIVER` override
221
+ * that changes the protocol drops `pool` / `replicas` — those belong to the
222
+ * configured driver, not the override.
223
+ *
224
+ * @param options - Boot options
225
+ * @param env - Active environment
226
+ * @param docker - Docker mode
227
+ */
228
+ function resolveSqlDriverRef(
229
+ options: BootOptions,
230
+ env: ConfigEnv,
231
+ docker: boolean,
232
+ ): DriverRef | undefined {
233
+ const configured = resolveDriverRef(options.config?.drivers?.store?.sql, env, STORE_SQL_DEFAULTS);
234
+ const fromEnv = process.env.OKE_SQL_DRIVER?.trim();
235
+ if (docker && fromEnv && fromEnv !== driverRefId(configured)) return fromEnv;
236
+ return configured;
237
+ }
238
+
239
+ /**
240
+ * Primary URL plus documented `pool.max` and read-replica URLs.
241
+ *
242
+ * `pool.min` is ignored — Bun.SQL has no minimum pool size. Unset `max`
243
+ * leaves pool sizing to the postgres driver default of 8.
244
+ *
245
+ * @param name - Store declaration name
246
+ * @param url - Primary connection URL
247
+ * @param ref - Active driver pin (string or rich object)
248
+ */
249
+ export function sqlBindingForDecl(
250
+ name: string,
251
+ url: string,
252
+ ref: DriverRef | undefined,
253
+ ): SqlRuntimeBinding {
254
+ const max = driverPoolMax(ref);
255
+ const pool = max !== undefined ? { pool: { max } } : {};
256
+ const replicas = driverReplicaUrls(ref).map((replicaUrl) => ({
257
+ url: replicaUrl,
258
+ ...pool,
259
+ }));
260
+ return {
261
+ name,
262
+ primary: { url, ...pool },
263
+ ...(replicas.length > 0 ? { replicas } : {}),
264
+ };
265
+ }
266
+
267
+ /**
268
+ * Positive finite `pool.max` from a rich driver ref.
269
+ *
270
+ * @param ref - Driver pin
271
+ */
272
+ function driverPoolMax(ref: DriverRef | undefined): number | undefined {
273
+ if (ref === undefined || typeof ref === "string") return undefined;
274
+ const max = ref.pool?.max;
275
+ if (typeof max !== "number" || !Number.isFinite(max) || max < 1) return undefined;
276
+ return Math.floor(max);
277
+ }
278
+
279
+ /**
280
+ * Replica connection strings from a rich driver ref. Non-strings are dropped.
281
+ *
282
+ * @param ref - Driver pin
283
+ */
284
+ function driverReplicaUrls(ref: DriverRef | undefined): readonly string[] {
285
+ if (ref === undefined || typeof ref === "string" || ref.replicas === undefined) return [];
286
+ return ref.replicas.filter((item): item is string => typeof item === "string" && item.length > 0);
287
+ }
288
+
213
289
  /**
214
290
  * @param options - Boot options
215
291
  * @param env - Active env
@@ -282,9 +282,14 @@ export function resolveElementNeeds(options: BootOptions): ElementNeeds {
282
282
  const considerFlow = (f: AnyFlowDef): void => {
283
283
  const e = f.effects;
284
284
  if (f.durable === true) journal = true;
285
- if ((e?.reads?.length ?? 0) > 0 || (e?.writes?.length ?? 0) > 0) {
286
- store = true;
287
- }
285
+ const touchesStore = [...(e?.reads ?? []), ...(e?.writes ?? [])].some(
286
+ (ref) =>
287
+ ref.startsWith("sql:") ||
288
+ ref.startsWith("kv:") ||
289
+ ref.startsWith("files:") ||
290
+ ref.startsWith("index:"),
291
+ );
292
+ if (touchesStore) store = true;
288
293
  if ((e?.emits?.length ?? 0) > 0) signal = true;
289
294
  if ((e?.secrets?.length ?? 0) > 0) vault = true;
290
295
  if ((e?.sends?.length ?? 0) > 0) channel = true;
@@ -548,6 +553,12 @@ export async function bootApplication(input: BootOptions = {}): Promise<BootResu
548
553
  if (clockRt) void Promise.resolve(clockRt.tick()).catch(ignoreBenignSql);
549
554
  if (journal && durableResume) void Promise.resolve(durableResume()).catch(ignoreBenignSql);
550
555
  if (fleet) void fleet.maybeHeartbeat().catch(ignoreBenignSql);
556
+ const stem = ["run", "events"].join("-");
557
+ void import(`../elements/ai/${stem}.ts`)
558
+ .then((mod: typeof import("../elements/ai/run-events.ts")) =>
559
+ mod.sweepInstalledAgentEvents(),
560
+ )
561
+ .catch(ignoreBenignSql);
551
562
  }, period);
552
563
  schedulerTimer.unref?.();
553
564
  }
@@ -567,7 +578,7 @@ export async function bootApplication(input: BootOptions = {}): Promise<BootResu
567
578
  // 6. AI
568
579
  let ai = pre.ai;
569
580
  if (needs.ai && !ai) {
570
- ai = aiBind!.bindAi(options, gate, now, env, docker, vault);
581
+ ai = aiBind!.bindAi(options, gate, now, env, docker, vault, journal?.store);
571
582
  }
572
583
 
573
584
  // 7. Runs
@@ -25,6 +25,7 @@ export const BUILTIN_ERROR_STATUS = {
25
25
  IdempotencyKeyInvalid: 400,
26
26
  IdempotencyKeyReused: 422,
27
27
  IdempotencyInProgress: 409,
28
+ JournalLeaseBusy: 409,
28
29
  } as const;
29
30
 
30
31
  /** Built-in error code (including `DatabaseError`). */
@@ -94,6 +95,7 @@ export type BuiltinErrorMap = {
94
95
  readonly IdempotencyKeyInvalid: Record<string, never>;
95
96
  readonly IdempotencyKeyReused: Record<string, never>;
96
97
  readonly IdempotencyInProgress: { readonly retryAfter?: number };
98
+ readonly JournalLeaseBusy: { readonly retryAfter?: number };
97
99
  };
98
100
 
99
101
  /**
@@ -21,6 +21,7 @@ const UNDECLARED_KEY: Readonly<Record<EffectKind, keyof typeof OKE_ERRORS>> = {
21
21
  secret: "UNDECLARED_SECRET",
22
22
  call: "UNDECLARED_CALL",
23
23
  fetch: "UNDECLARED_FETCH",
24
+ decide: "UNDECLARED_DECIDE",
24
25
  };
25
26
 
26
27
  /** Maps effect kind → the corresponding `Effects` field. */
@@ -34,6 +35,7 @@ const EFFECTS_FIELD: Readonly<Record<EffectKind, keyof Effects>> = {
34
35
  secret: "secrets",
35
36
  call: "calls",
36
37
  fetch: "fetches",
38
+ decide: "decides",
37
39
  };
38
40
 
39
41
  /**
@@ -91,6 +93,7 @@ export function createCapabilityToken(flow: string, declared?: Effects): Capabil
91
93
  secret: new Set(effects.secrets ?? []),
92
94
  call: new Set(effects.calls ?? []),
93
95
  fetch: new Set(effects.fetches ?? []),
96
+ decide: new Set(effects.decides ?? []),
94
97
  };
95
98
 
96
99
  return {