okengine 0.21.1 → 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 (342) hide show
  1. package/AGENTS.md +11 -4
  2. package/LICENSE +202 -0
  3. package/NOTICE +2 -0
  4. package/README.md +15 -9
  5. package/manifest.v1.schema.json +54 -0
  6. package/package.json +53 -38
  7. package/site/content/docs/ai/mcp.mdx +13 -9
  8. package/site/content/docs/ai/skills.mdx +2 -2
  9. package/site/content/docs/client/calling.mdx +27 -19
  10. package/site/content/docs/client/index.mdx +1 -1
  11. package/site/content/docs/elements/ai/agents.mdx +61 -13
  12. package/site/content/docs/elements/ai/decide.mdx +536 -0
  13. package/site/content/docs/elements/ai/events.mdx +373 -0
  14. package/site/content/docs/elements/ai/index.mdx +9 -3
  15. package/site/content/docs/elements/ai/meta.json +1 -1
  16. package/site/content/docs/elements/ai/prompts.mdx +4 -2
  17. package/site/content/docs/elements/channel/email.mdx +1 -1
  18. package/site/content/docs/elements/channel/index.mdx +1 -1
  19. package/site/content/docs/elements/flow/http.mdx +4 -4
  20. package/site/content/docs/elements/flow/index.mdx +3 -3
  21. package/site/content/docs/elements/flow/meta.json +1 -1
  22. package/site/content/docs/elements/gate/auth.mdx +4 -1
  23. package/site/content/docs/elements/gate/tenancy.mdx +1 -1
  24. package/site/content/docs/elements/store/index.mdx +1 -1
  25. package/site/content/docs/elements/store/kv.mdx +5 -5
  26. package/site/content/docs/elements/vault/rotation.mdx +15 -0
  27. package/site/content/docs/plugins/otp.mdx +31 -31
  28. package/site/content/docs/recipes/dragonfly.mdx +5 -5
  29. package/site/content/docs/recipes/index.mdx +6 -6
  30. package/site/content/docs/recipes/mailpit.mdx +1 -1
  31. package/site/content/docs/recipes/meilisearch.mdx +1 -1
  32. package/site/content/docs/recipes/pgdog.mdx +1 -1
  33. package/site/content/docs/recipes/postgres.mdx +12 -8
  34. package/site/content/docs/recipes/redis.mdx +4 -4
  35. package/site/content/docs/reference/cli.mdx +2 -1
  36. package/site/content/docs/reference/configuration.mdx +11 -9
  37. package/site/content/docs/reference/errors.mdx +12 -2
  38. package/site/content/docs/reference/fx.mdx +35 -21
  39. package/site/content/docs/reference/idempotency.mdx +192 -0
  40. package/site/content/docs/reference/index.mdx +6 -1
  41. package/site/content/docs/reference/meta.json +2 -1
  42. package/site/content/docs/understand/meta.json +1 -1
  43. package/site/content/docs/{elements/flow → understand}/routing.mdx +11 -10
  44. package/site/content/docs/understand/the-architecture.mdx +14 -12
  45. package/site/content/docs/understand/try-it.mdx +3 -3
  46. package/src/auth/api-key-sql.test.ts +44 -0
  47. package/src/auth/api-key-sql.ts +1 -0
  48. package/src/auth/api-keys.ts +30 -12
  49. package/src/auth/config.ts +6 -2
  50. package/src/auth/gate-auth.test.ts +20 -0
  51. package/src/cli/agents-md.test.ts +25 -0
  52. package/src/cli/ask-vault-gaps.test.ts +38 -1
  53. package/src/cli/ask-vault-gaps.ts +35 -2
  54. package/src/cli/competitor-mention-removal.test.ts +3 -3
  55. package/src/cli/decide.ts +174 -0
  56. package/src/cli/decision-lock-watch.test.ts +45 -0
  57. package/src/cli/decision-lock-watch.ts +51 -0
  58. package/src/cli/dev-app-runner.ts +34 -8
  59. package/src/cli/dev-auth-secret.test.ts +49 -0
  60. package/src/cli/dev-auth-secret.ts +49 -0
  61. package/src/cli/dev-hot.test.ts +41 -0
  62. package/src/cli/dev-hot.ts +48 -0
  63. package/src/cli/dev.test.ts +1 -1
  64. package/src/cli/dev.ts +24 -6
  65. package/src/cli/docker-cli.test.ts +1 -1
  66. package/src/cli/eval.ts +142 -1
  67. package/src/cli/index.ts +14 -1
  68. package/src/cli/load-config.images.test.ts +12 -12
  69. package/src/cli/load-config.ts +2 -2
  70. package/src/cli/mcp-from-console.ts +48 -0
  71. package/src/cli/registry.ts +35 -1
  72. package/src/cli/vault-cmd.test.ts +23 -0
  73. package/src/cli/vault-cmd.ts +7 -0
  74. package/src/client/agent.ts +307 -0
  75. package/src/client/create.ts +18 -1
  76. package/src/client/transport.test.ts +245 -0
  77. package/src/client/transport.ts +133 -19
  78. package/src/client/types.ts +29 -3
  79. package/src/client-react/index.ts +7 -1
  80. package/src/client-react/use-agent-run.test.ts +271 -0
  81. package/src/client-react/use-agent-run.ts +193 -0
  82. package/src/compiler/ai-approval.test.ts +36 -0
  83. package/src/compiler/ai-repair.test.ts +22 -0
  84. package/src/compiler/decisions.extract.test.ts +164 -0
  85. package/src/compiler/effects-infer.ts +753 -17
  86. package/src/compiler/extract.test.ts +37 -0
  87. package/src/compiler/extract.ts +381 -24
  88. package/src/compiler/fixtures/skyport.expected.json +24 -0
  89. package/src/compiler/fx-follow.test.ts +164 -0
  90. package/src/compiler/fx-index.ts +399 -0
  91. package/src/compiler/response.ts +35 -2
  92. package/src/config/index.ts +21 -3
  93. package/src/console/server/ai-runs-flows.ts +269 -0
  94. package/src/console/server/ai-runs.test.ts +323 -0
  95. package/src/console/server/ai-runs.ts +562 -0
  96. package/src/console/server/ai.ts +12 -1
  97. package/src/console/server/app.ts +10 -1
  98. package/src/console/server/decisions-flows.ts +157 -0
  99. package/src/console/server/decisions.test.ts +66 -0
  100. package/src/console/server/decisions.ts +229 -0
  101. package/src/console/server/flows-invoke.test.ts +2 -2
  102. package/src/console/server/flows.ts +50 -2
  103. package/src/console/server/invoke-user-flow.ts +9 -0
  104. package/src/console/server/runs-ingest.ts +16 -0
  105. package/src/console/server/serve.ts +5 -0
  106. package/src/console/server/state.ts +25 -1
  107. package/src/console/server/store-stats.test.ts +47 -0
  108. package/src/console/server/store-stats.ts +92 -18
  109. package/src/console/ui-next/dist/assets/access-page-DDKkhT9v.js +4 -0
  110. package/src/console/ui-next/dist/assets/agent-disclosure-62Xts8Xi.js +1 -0
  111. package/src/console/ui-next/dist/assets/{cache-glyph-92uM5MO7.js → cache-glyph-DyeoKHKA.js} +1 -1
  112. package/src/console/ui-next/dist/assets/{call-pii-button-DtGVPtcs.js → call-pii-button-DvfpLXsU.js} +1 -1
  113. package/src/console/ui-next/dist/assets/{collapsible-DN7l6zmC.js → collapsible-BY03SeCg.js} +1 -1
  114. package/src/console/ui-next/dist/assets/confirm-sheet-DZfuJXP4.js +1 -0
  115. package/src/console/ui-next/dist/assets/copy-inline-button-BTjPVjRT.js +1 -0
  116. package/src/console/ui-next/dist/assets/decisions-page-CgkKlA56.js +1 -0
  117. package/src/console/ui-next/dist/assets/detail-header-Dk5LipF6.js +1 -0
  118. package/src/console/ui-next/dist/assets/dropdown-menu-CRGrj5JU.js +1 -0
  119. package/src/console/ui-next/dist/assets/duration-tone-Y6HLjyJ5.js +9 -0
  120. package/src/console/ui-next/dist/assets/element-icons-C6t8Vlml.js +1 -0
  121. package/src/console/ui-next/dist/assets/explorer-chrome-Dslh2yE8.js +1 -0
  122. package/src/console/ui-next/dist/assets/explorer-empty-CIVg7Jgb.js +1 -0
  123. package/src/console/ui-next/dist/assets/flows-page-DLPfNy-_.js +1 -0
  124. package/src/console/ui-next/dist/assets/{highlighted-json-BlAEVgNW.js → highlighted-json-D7nNzpgB.js} +1 -1
  125. package/src/console/ui-next/dist/assets/{http-method-BDf7OAHv.js → http-method-DdL19zzh.js} +1 -1
  126. package/src/console/ui-next/dist/assets/index-BlrN49Hs.css +2 -0
  127. package/src/console/ui-next/dist/assets/index-C0lc_s9d.js +58 -0
  128. package/src/console/ui-next/dist/assets/observability-page-CRlcyLCC.js +8 -0
  129. package/src/console/ui-next/dist/assets/{replica-lag-B3GLNVfF.js → replica-lag-Bb3FWUu9.js} +7 -7
  130. package/src/console/ui-next/dist/assets/request-meta-DYBMWhX8.js +1 -0
  131. package/src/console/ui-next/dist/assets/shortcut-keys-DTE1sjTC.js +1 -0
  132. package/src/console/ui-next/dist/assets/store-page-CcE-SXC8.js +41 -0
  133. package/src/console/ui-next/dist/assets/trace-detail-sheet-CKMSEQ4s.js +2 -0
  134. package/src/console/ui-next/dist/assets/tree-expand-toggle-QOfk0M0H.js +55 -0
  135. package/src/console/ui-next/dist/assets/units-page-DKC1CDDd.js +1 -0
  136. package/src/console/ui-next/dist/assets/use-vault-list-BAyQG8Re.js +1 -0
  137. package/src/console/ui-next/dist/assets/vault-page-B54v4Yit.js +2 -0
  138. package/src/console/ui-next/dist/index.html +4 -3
  139. package/src/console/ui-next/seed-parked-approval.ts +126 -0
  140. package/src/console/ui-next/src/client.ts +224 -1
  141. package/src/console/ui-next/src/components/explorer/detail-header.tsx +9 -0
  142. package/src/console/ui-next/src/components/explorer/explorer-start-toggle.tsx +5 -1
  143. package/src/console/ui-next/src/components/ui/sheet-form.tsx +21 -12
  144. package/src/console/ui-next/src/features/flows/decisions/decisions-page.tsx +216 -0
  145. package/src/console/ui-next/src/features/flows/decisions/resolve.test.ts +48 -0
  146. package/src/console/ui-next/src/features/flows/decisions/resolve.ts +111 -0
  147. package/src/console/ui-next/src/features/flows/flows-page.tsx +10 -0
  148. package/src/console/ui-next/src/features/flows/graph/element-map.ts +1 -0
  149. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +6 -0
  150. package/src/console/ui-next/src/features/flows/traces/effect-summary.test.ts +24 -0
  151. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
  152. package/src/console/ui-next/src/features/flows/traces/http-status.ts +58 -0
  153. package/src/console/ui-next/src/features/flows/traces/request-client-card.tsx +355 -0
  154. package/src/console/ui-next/src/features/flows/traces/request-client.test.ts +114 -0
  155. package/src/console/ui-next/src/features/flows/traces/request-client.ts +423 -0
  156. package/src/console/ui-next/src/features/flows/traces/request-input-view.test.ts +52 -6
  157. package/src/console/ui-next/src/features/flows/traces/request-input-view.ts +85 -19
  158. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +505 -80
  159. package/src/console/ui-next/src/features/flows/traces/trace-lanes.test.ts +61 -0
  160. package/src/console/ui-next/src/features/flows/traces/trace-lanes.ts +78 -0
  161. package/src/console/ui-next/src/features/flows/traces/trace-nav.test.ts +48 -0
  162. package/src/console/ui-next/src/features/flows/traces/trace-nav.ts +57 -0
  163. package/src/console/ui-next/src/features/flows/traces/trace-request-section.tsx +275 -96
  164. package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +16 -2
  165. package/src/console/ui-next/src/features/observability/detail/ai-runs.tsx +369 -0
  166. package/src/console/ui-next/src/features/observability/observability-page.tsx +31 -1
  167. package/src/console/ui-next/src/features/observability/state/observability-selection.ts +18 -3
  168. package/src/console/ui-next/src/features/store/performance/kv-performance-panel.tsx +12 -1
  169. package/src/console/ui-next/src/features/store/performance/performance-panel.tsx +14 -1
  170. package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +1 -1
  171. package/src/console/ui-next/src/features/units/detail/flow-contract-panel.tsx +15 -2
  172. package/src/console/ui-next/src/features/units/explorer/units-tree.tsx +73 -18
  173. package/src/console/ui-next/src/features/units/lib/unit-tree.test.ts +21 -0
  174. package/src/console/ui-next/src/features/units/lib/unit-tree.ts +82 -0
  175. package/src/console/ui-next/src/features/units/units-page.tsx +25 -7
  176. package/src/console/ui-next/src/router.tsx +17 -0
  177. package/src/console/ui-next/ui-next-seed-runs.ts +23 -0
  178. package/src/console/ui-next/vite-console-kernel-plugin.ts +43 -3
  179. package/src/console/ui-next/vite.config.ts +9 -0
  180. package/src/docker/docker.test.ts +13 -13
  181. package/src/docker/images-config.test.ts +12 -12
  182. package/src/docker/recipes/dragonfly.ts +3 -2
  183. package/src/docker/recipes/redis.ts +1 -1
  184. package/src/docker/stack-id.test.ts +1 -1
  185. package/src/drivers/ai-anthropic.ts +212 -21
  186. package/src/drivers/ai-mock.ts +34 -1
  187. package/src/drivers/ai-openai-compatible.ts +67 -26
  188. package/src/drivers/ai-preconnect.ts +18 -0
  189. package/src/drivers/ai-stream.test.ts +62 -0
  190. package/src/drivers/ai-types.ts +20 -0
  191. package/src/drivers/journal-postgres.ts +455 -3
  192. package/src/drivers/meilisearch.ts +2 -0
  193. package/src/drivers/memory-ddl.test.ts +18 -0
  194. package/src/drivers/memory.ts +21 -1
  195. package/src/drivers/pg-rls.test.ts +31 -0
  196. package/src/drivers/pg-rls.ts +15 -0
  197. package/src/drivers/postgres.test.ts +9 -0
  198. package/src/drivers/postgres.ts +49 -11
  199. package/src/drivers/vault-builtin.test.ts +10 -0
  200. package/src/drivers/vault-builtin.ts +13 -1
  201. package/src/drivers/vault-remote-bag.ts +5 -1
  202. package/src/drivers/vault-types.ts +6 -1
  203. package/src/elements/ai/agent-event-slot.ts +27 -0
  204. package/src/elements/ai/approval-http.ts +207 -0
  205. package/src/elements/ai/approval.test.ts +741 -0
  206. package/src/elements/ai/approval.ts +228 -0
  207. package/src/elements/ai/decisions/bind.ts +163 -0
  208. package/src/elements/ai/decisions/certificate.test.ts +291 -0
  209. package/src/elements/ai/decisions/certificate.ts +377 -0
  210. package/src/elements/ai/decisions/certify.ts +314 -0
  211. package/src/elements/ai/decisions/decide.test.ts +953 -0
  212. package/src/elements/ai/decisions/e2e.test.ts +399 -0
  213. package/src/elements/ai/decisions/export.test.ts +120 -0
  214. package/src/elements/ai/decisions/export.ts +146 -0
  215. package/src/elements/ai/decisions/fixtures/openrouter-request.json +24 -0
  216. package/src/elements/ai/decisions/fixtures/openrouter-response.json +22 -0
  217. package/src/elements/ai/decisions/fixtures/typesafe-request.json +19 -0
  218. package/src/elements/ai/decisions/fixtures/typesafe-response.json +13 -0
  219. package/src/elements/ai/decisions/http.test.ts +154 -0
  220. package/src/elements/ai/decisions/http.ts +240 -0
  221. package/src/elements/ai/decisions/labels.test.ts +282 -0
  222. package/src/elements/ai/decisions/labels.ts +257 -0
  223. package/src/elements/ai/decisions/openrouter.live.test.ts +136 -0
  224. package/src/elements/ai/decisions/openrouter.ts +36 -0
  225. package/src/elements/ai/decisions/provider.ts +102 -0
  226. package/src/elements/ai/decisions/typesafe.ts +36 -0
  227. package/src/elements/ai/declare.ts +252 -1
  228. package/src/elements/ai/events.test.ts +379 -0
  229. package/src/elements/ai/events.ts +138 -0
  230. package/src/elements/ai/run-events.test.ts +461 -0
  231. package/src/elements/ai/run-events.ts +522 -0
  232. package/src/elements/ai/runtime.ts +1186 -105
  233. package/src/elements/ai/stream-turn.ts +214 -0
  234. package/src/elements/ai/stream.live.test.ts +76 -0
  235. package/src/elements/ai/subagent.test.ts +136 -0
  236. package/src/elements/ai.test.ts +497 -3
  237. package/src/elements/ai.ts +16 -1
  238. package/src/elements/clock/durable.ts +27 -1
  239. package/src/elements/store/emit-drizzle.ts +2 -2
  240. package/src/elements/store/live-default.test.ts +2 -0
  241. package/src/elements/store/live-http.test.ts +1 -0
  242. package/src/elements/store/resource-list-docs.test.ts +2 -2
  243. package/src/elements/store/resource.test.ts +3 -3
  244. package/src/elements/store/schema-decl.test.ts +1 -0
  245. package/src/elements/store/sql-condition.ts +24 -7
  246. package/src/elements/store/sql-session.test.ts +51 -0
  247. package/src/elements/store/sql-session.ts +111 -1
  248. package/src/elements/store/upsert-app.test.ts +1 -1
  249. package/src/elements/vault/audit.test.ts +137 -0
  250. package/src/elements/vault/audit.ts +106 -13
  251. package/src/elements/vault/boot-chain.ts +9 -1
  252. package/src/elements/vault/builtin-adapter.ts +33 -3
  253. package/src/i18n/catalogs/ar.ts +9 -0
  254. package/src/i18n/catalogs/en.ts +9 -0
  255. package/src/index.ts +2 -0
  256. package/src/kernel/abort-scope.ts +33 -2
  257. package/src/kernel/agent-event-store.ts +448 -0
  258. package/src/kernel/app.ts +294 -19
  259. package/src/kernel/auto-cache.test.ts +6 -6
  260. package/src/kernel/boot-bind/ai.ts +3 -0
  261. package/src/kernel/boot-bind/store.test.ts +26 -0
  262. package/src/kernel/boot-bind/store.ts +82 -6
  263. package/src/kernel/boot-bind/vault.ts +1 -0
  264. package/src/kernel/boot.ts +34 -5
  265. package/src/kernel/builtin-errors.ts +10 -0
  266. package/src/kernel/capability.ts +3 -0
  267. package/src/kernel/client-descriptor.ts +1 -0
  268. package/src/kernel/concurrency.test.ts +33 -0
  269. package/src/kernel/decision-label-store.ts +441 -0
  270. package/src/kernel/effects-stamping.test.ts +2 -2
  271. package/src/kernel/effects.test.ts +2 -1
  272. package/src/kernel/effects.ts +13 -1
  273. package/src/kernel/element-registries.ts +3 -0
  274. package/src/kernel/errors-compiler.ts +20 -0
  275. package/src/kernel/errors-text.ts +4 -0
  276. package/src/kernel/errors.ts +2 -0
  277. package/src/kernel/external-effects.test.ts +36 -0
  278. package/src/kernel/flow.test.ts +2 -3
  279. package/src/kernel/flow.ts +24 -0
  280. package/src/kernel/fx-decide.ts +761 -0
  281. package/src/kernel/fx-fetch.ts +5 -1
  282. package/src/kernel/fx.test.ts +12 -5
  283. package/src/kernel/fx.ts +316 -29
  284. package/src/kernel/http-frame.test.ts +94 -0
  285. package/src/kernel/http-frame.ts +203 -0
  286. package/src/kernel/idempotency-store.ts +578 -0
  287. package/src/kernel/idempotency.test.ts +556 -0
  288. package/src/kernel/idempotency.ts +304 -0
  289. package/src/kernel/index.ts +1 -0
  290. package/src/kernel/journal.ts +160 -0
  291. package/src/kernel/json-result.ts +10 -0
  292. package/src/kernel/pipeline.test.ts +45 -0
  293. package/src/kernel/sse-id.ts +32 -0
  294. package/src/manifest/diff.ts +1 -0
  295. package/src/manifest/types.ts +37 -2
  296. package/src/mcp/ai-tools.test.ts +202 -0
  297. package/src/mcp/authorization.ts +66 -0
  298. package/src/mcp/session.ts +3 -0
  299. package/src/mcp/tools.ts +168 -0
  300. package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
  301. package/src/plugins/otp.test.ts +65 -1
  302. package/src/plugins/otp.ts +59 -7
  303. package/src/release/absolute-regression.test.ts +63 -0
  304. package/src/release/http-graph.test.ts +27 -0
  305. package/src/release/http-graph.ts +95 -0
  306. package/src/release/index.ts +3 -0
  307. package/src/release/limits.ts +17 -2
  308. package/src/release/measure.ts +144 -28
  309. package/src/runs/collect.ts +4 -1
  310. package/src/runs/parquet.test.ts +27 -1
  311. package/src/runs/parquet.ts +19 -0
  312. package/src/runs/types.ts +31 -0
  313. package/src/runtime/json-code-block.test.ts +106 -47
  314. package/src/runtime/json-code-block.ts +1209 -46
  315. package/src/term.test.ts +13 -0
  316. package/src/term.ts +81 -49
  317. package/src/test/create-test-app.test.ts +3 -1
  318. package/src/test/create-test-app.ts +40 -0
  319. package/src/test/live-signals.test.ts +1 -0
  320. package/src/test/provisions.integration.test.ts +1 -0
  321. package/src/test/tenant-isolation.test.ts +1 -0
  322. package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +0 -1
  323. package/src/console/ui-next/dist/assets/access-page-Bgt9bq2r.js +0 -4
  324. package/src/console/ui-next/dist/assets/agent-disclosure-B43CXZZR.js +0 -1
  325. package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +0 -1
  326. package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +0 -1
  327. package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +0 -1
  328. package/src/console/ui-next/dist/assets/duration-tone-BmIR9FV8.js +0 -9
  329. package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +0 -1
  330. package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +0 -1
  331. package/src/console/ui-next/dist/assets/flows-page-BMHs-IzK.js +0 -1
  332. package/src/console/ui-next/dist/assets/index-BKpaes3n.js +0 -63
  333. package/src/console/ui-next/dist/assets/index-VxoEz295.css +0 -2
  334. package/src/console/ui-next/dist/assets/observability-page-WnVLI-0j.js +0 -4
  335. package/src/console/ui-next/dist/assets/request-meta-DMbnAe3f.js +0 -1
  336. package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +0 -1
  337. package/src/console/ui-next/dist/assets/store-page-KvFDingJ.js +0 -41
  338. package/src/console/ui-next/dist/assets/trace-detail-sheet-Htk8Cm9t.js +0 -2
  339. package/src/console/ui-next/dist/assets/tree-expand-toggle-CFMPWX4f.js +0 -55
  340. package/src/console/ui-next/dist/assets/units-page-C4NdNuxP.js +0 -1
  341. package/src/console/ui-next/dist/assets/use-vault-list-CT4-gajj.js +0 -1
  342. package/src/console/ui-next/dist/assets/vault-page-CBYl0LW_.js +0 -2
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Operator routes for the decisions list and the review queue.
3
+ */
4
+
5
+ import { z } from "zod";
6
+ import { journalLeaseBusyResponse } from "../../elements/ai/approval.ts";
7
+ import { resolveDecisionReview } from "../../kernel/fx-decide.ts";
8
+ import { fail, flow, http, type Binding } from "../../kernel/index.ts";
9
+ import { bindHttp } from "./bind.ts";
10
+ import type { ConsoleState } from "./state.ts";
11
+ import { loadDecisionQueue, projectDecisionList, candidateMetrics } from "./decisions.ts";
12
+
13
+ const AuthFailed = z.object({});
14
+
15
+ const DecisionListOut = z.object({
16
+ decisions: z.array(
17
+ z.object({
18
+ name: z.string(),
19
+ state: z.enum(["learning", "candidate", "certified", "suspended"]),
20
+ mode: z.enum(["review", "abstain"]),
21
+ model: z.string().optional(),
22
+ metrics: z.record(z.string(), z.number()).optional(),
23
+ promote: z.string().optional(),
24
+ }),
25
+ ),
26
+ suspended: z.array(z.string()),
27
+ failures: z.array(
28
+ z.object({
29
+ decision: z.string(),
30
+ question: z.string(),
31
+ message: z.string(),
32
+ at: z.number(),
33
+ }),
34
+ ),
35
+ });
36
+
37
+ const DecisionQueueOut = z.object({
38
+ rows: z.array(
39
+ z.object({
40
+ id: z.string(),
41
+ decision: z.string(),
42
+ requestedAt: z.number(),
43
+ ageMs: z.number(),
44
+ labelOnly: z.boolean(),
45
+ status: z.enum(["pending", "reviewed"]),
46
+ questions: z.array(
47
+ z.object({
48
+ id: z.string(),
49
+ kind: z.enum(["boolean", "choice", "score"]),
50
+ options: z.array(z.string()).optional(),
51
+ levels: z.array(z.string()).optional(),
52
+ }),
53
+ ),
54
+ }),
55
+ ),
56
+ });
57
+
58
+ const DecisionResolveIn = z.object({
59
+ id: z.string().min(1),
60
+ values: z.record(z.string(), z.unknown()),
61
+ labelOnly: z.boolean().optional(),
62
+ });
63
+
64
+ const DecisionResolveOut = z.object({ ok: z.literal(true) });
65
+
66
+ /**
67
+ * List, queue, and resolve bindings.
68
+ *
69
+ * @param state - Console state
70
+ */
71
+ export function decisionConsoleBindings(state: ConsoleState): Binding[] {
72
+ const list = flow("console.decisions.list", {
73
+ plane: "operator",
74
+ do: async (_input, fx) => {
75
+ if (!fx.operator.id) return fail("AuthFailed", {});
76
+ const { decisionDriftNames, getDecisionLock } =
77
+ await import("../../elements/ai/decisions/certificate.ts");
78
+ const { decisionLabelWriteFailures, listDecisionCandidates, loadDecisionCandidate } =
79
+ await import("../../elements/ai/decisions/labels.ts");
80
+ const names = await listDecisionCandidates();
81
+ const fitted: Record<string, Record<string, number>> = {};
82
+ for (const name of names) {
83
+ const metrics = candidateMetrics(await loadDecisionCandidate(name));
84
+ if (metrics) fitted[name] = metrics;
85
+ }
86
+ return {
87
+ decisions: projectDecisionList(state.manifest, getDecisionLock(), new Set(names), fitted),
88
+ suspended: decisionDriftNames(),
89
+ failures: decisionLabelWriteFailures(),
90
+ };
91
+ },
92
+ });
93
+ const queue = flow("console.decisions.queue", {
94
+ plane: "operator",
95
+ do: async (_input, fx) => {
96
+ if (!fx.operator.id) return fail("AuthFailed", {});
97
+ return { rows: await loadDecisionQueue(state.journalStore, Date.now()) };
98
+ },
99
+ });
100
+ const resolve = flow("console.decisions.resolve", {
101
+ plane: "operator",
102
+ do: async (input: { id: string; values: Record<string, unknown>; labelOnly?: boolean }, fx) => {
103
+ const reviewer = fx.operator.id;
104
+ if (!reviewer) return fail("Unauthorized", {});
105
+ const store = state.journalStore;
106
+ if (!store) return fx.fail.notFound();
107
+ const result = await resolveDecisionReview(
108
+ store,
109
+ input.id,
110
+ {
111
+ values: input.values,
112
+ reviewer,
113
+ tenantId: fx.tenant.id,
114
+ plane: "operator",
115
+ auth: {
116
+ userId: fx.auth.userId,
117
+ scopes: fx.auth.scopes,
118
+ verified: fx.auth.verified,
119
+ },
120
+ },
121
+ Date.now,
122
+ input.labelOnly === true,
123
+ );
124
+ if (result.ok) return { ok: true as const };
125
+ if (result.status === 409 && result.reason === "lease") {
126
+ return journalLeaseBusyResponse(result.retryAfterSeconds);
127
+ }
128
+ if (result.status === 422) {
129
+ return fail("ValidationError", {
130
+ issues: [{ message: "review values do not match the open questions", path: ["values"] }],
131
+ });
132
+ }
133
+ if (result.status === 503) return fx.fail.serviceUnavailable();
134
+ if (result.status === 403) return fx.fail.forbidden();
135
+ if (result.status === 409) return fx.fail.conflict();
136
+ return fx.fail.notFound();
137
+ },
138
+ });
139
+ return [
140
+ bindHttp(
141
+ http.get("/console/decisions", { out: DecisionListOut, errors: { AuthFailed } }),
142
+ list,
143
+ ),
144
+ bindHttp(
145
+ http.get("/console/decisions/queue", { out: DecisionQueueOut, errors: { AuthFailed } }),
146
+ queue,
147
+ ),
148
+ bindHttp(
149
+ http.post("/console/decisions/resolve", {
150
+ in: DecisionResolveIn,
151
+ out: DecisionResolveOut,
152
+ errors: { AuthFailed },
153
+ }),
154
+ resolve,
155
+ ),
156
+ ];
157
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Decision list state and pending age.
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import type { JournalRun } from "../../kernel/journal.ts";
7
+ import { projectDecisionList, projectDecisionQueue } from "./decisions.ts";
8
+ import {
9
+ setDecisionDrift,
10
+ setDecisionLock,
11
+ resetDecisionCertificates,
12
+ } from "../../elements/ai/decisions/certificate.ts";
13
+ import type { Manifest } from "../../manifest/types.ts";
14
+
15
+ const manifest = {
16
+ ai: {
17
+ decisions: {
18
+ triage: { mode: "review" as const, questions: ["team"] },
19
+ route: { mode: "review" as const, questions: ["team"] },
20
+ },
21
+ },
22
+ } as unknown as Manifest;
23
+
24
+ describe("decision console projection", () => {
25
+ test("lockfile and drift set the list state", () => {
26
+ resetDecisionCertificates();
27
+ expect(projectDecisionList(manifest)[0]?.state).toBe("learning");
28
+ setDecisionLock({
29
+ decisions: {
30
+ triage: { model: "typesafe/jev-1.13.0", questions: {} },
31
+ },
32
+ });
33
+ expect(projectDecisionList(manifest).find((row) => row.name === "triage")?.state).toBe(
34
+ "certified",
35
+ );
36
+ setDecisionDrift("triage", true);
37
+ const rows = projectDecisionList(manifest, undefined, new Set(["route"]), {
38
+ route: { labels: 12, errors: 0 },
39
+ });
40
+ expect(rows.find((row) => row.name === "triage")?.state).toBe("suspended");
41
+ const route = rows.find((row) => row.name === "route");
42
+ expect(route?.state).toBe("candidate");
43
+ expect(route?.metrics).toEqual({ labels: 12, errors: 0 });
44
+ expect(route?.promote).toBe("oke decide promote route");
45
+ resetDecisionCertificates();
46
+ });
47
+
48
+ test("queue age is now minus requestedAt", () => {
49
+ const run = {
50
+ id: "run",
51
+ flow: "run",
52
+ status: "sleeping",
53
+ entries: [
54
+ {
55
+ kind: "step" as const,
56
+ name: "ai-decision:abc",
57
+ at: 1_000,
58
+ value: { status: "pending", requestedAt: 1_000 },
59
+ },
60
+ ],
61
+ } as JournalRun;
62
+ const [row] = projectDecisionQueue([run], 61_000);
63
+ expect(row?.ageMs).toBe(60_000);
64
+ expect(row?.labelOnly).toBe(false);
65
+ });
66
+ });
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Console projection for decisions: lockfile state and the review queue.
3
+ */
4
+
5
+ import type { JournalRun, JournalStore } from "../../kernel/journal.ts";
6
+ import type { Manifest } from "../../manifest/types.ts";
7
+ import { aiDecisionRegistry } from "../../kernel/element-registries.ts";
8
+ import {
9
+ decisionDriftNames,
10
+ decisionDriftSuspended,
11
+ getDecisionLock,
12
+ type DecisionCandidate,
13
+ type DecisionLockfile,
14
+ } from "../../elements/ai/decisions/certificate.ts";
15
+
16
+ /** List state for one decision. */
17
+ export type DecisionListState = "learning" | "candidate" | "certified" | "suspended";
18
+
19
+ /** One decision row. */
20
+ export interface DecisionListRow {
21
+ readonly name: string;
22
+ readonly state: DecisionListState;
23
+ readonly mode: "review" | "abstain";
24
+ readonly model?: string;
25
+ /** Candidate fit, when one is stored and the decision is not certified. */
26
+ readonly metrics?: Readonly<Record<string, number>>;
27
+ /** Command that writes this decision's candidate into the lockfile. */
28
+ readonly promote?: string;
29
+ }
30
+
31
+ /** One question the resolve form can answer. */
32
+ export interface DecisionFormQuestion {
33
+ readonly id: string;
34
+ readonly kind: "boolean" | "choice" | "score";
35
+ readonly options?: readonly string[];
36
+ readonly levels?: readonly string[];
37
+ }
38
+
39
+ /** One parked or audit review. */
40
+ export interface DecisionQueueRow {
41
+ readonly id: string;
42
+ readonly decision: string;
43
+ readonly requestedAt: number;
44
+ readonly ageMs: number;
45
+ readonly labelOnly: boolean;
46
+ readonly status: "pending" | "reviewed";
47
+ readonly questions: readonly DecisionFormQuestion[];
48
+ }
49
+
50
+ /**
51
+ * List state from the lockfile, the candidate, and the app drift flag.
52
+ *
53
+ * @param manifest - App manifest
54
+ * @param lock - Lockfile, when one is loaded
55
+ */
56
+ export function projectDecisionList(
57
+ manifest: Manifest | null | undefined,
58
+ lock: DecisionLockfile | undefined = getDecisionLock(),
59
+ candidates: ReadonlySet<string> = new Set(),
60
+ metrics: Readonly<Record<string, Readonly<Record<string, number>>>> = {},
61
+ ): DecisionListRow[] {
62
+ const decisions = manifest?.ai?.decisions ?? {};
63
+ return Object.entries(decisions).map(([name, decision]) => {
64
+ const certified = lock?.decisions[name] !== undefined;
65
+ const candidate = candidates.has(name);
66
+ const suspended = decisionDriftSuspended(name);
67
+ const state: DecisionListState = suspended
68
+ ? "suspended"
69
+ : certified
70
+ ? "certified"
71
+ : candidate
72
+ ? "candidate"
73
+ : "learning";
74
+ const fitted = metrics[name];
75
+ return {
76
+ name,
77
+ state,
78
+ mode: decision.mode,
79
+ ...(decision.model !== undefined ? { model: decision.model } : {}),
80
+ ...(state === "candidate" && fitted ? { metrics: fitted } : {}),
81
+ ...(state === "candidate" ? { promote: `oke decide promote ${name}` } : {}),
82
+ };
83
+ });
84
+ }
85
+
86
+ /**
87
+ * First slice metrics on a stored candidate.
88
+ *
89
+ * @param entry - Candidate body
90
+ */
91
+ export function candidateMetrics(
92
+ entry: DecisionCandidate | undefined,
93
+ ): Record<string, number> | undefined {
94
+ if (!entry) return undefined;
95
+ for (const slices of Object.values(entry.questions)) {
96
+ for (const slice of Object.values(slices)) {
97
+ if (slice.metrics) return { ...slice.metrics };
98
+ }
99
+ }
100
+ return undefined;
101
+ }
102
+
103
+ /**
104
+ * Pending and reviewed decision steps, newest age first.
105
+ *
106
+ * @param runs - Journal runs
107
+ * @param now - Clock
108
+ */
109
+ export function projectDecisionQueue(
110
+ runs: readonly JournalRun[],
111
+ now: number,
112
+ tenantId?: string | null,
113
+ ): DecisionQueueRow[] {
114
+ const rows: DecisionQueueRow[] = [];
115
+ for (const run of runs) {
116
+ for (const entry of run.entries) {
117
+ if (entry.kind !== "step") continue;
118
+ const labelOnly = entry.name.startsWith("ai-decision-label:");
119
+ const parked = entry.name.startsWith("ai-decision:");
120
+ if (!labelOnly && !parked) continue;
121
+ const id = entry.name.slice(labelOnly ? "ai-decision-label:".length : "ai-decision:".length);
122
+ const value = entry.value as {
123
+ status?: "pending" | "reviewed";
124
+ requestedAt?: number;
125
+ tenant?: string | null;
126
+ open?: readonly string[];
127
+ };
128
+ if (tenantId !== undefined && (value.tenant ?? null) !== tenantId) continue;
129
+ const requestedAt = value.requestedAt ?? entry.at;
130
+ rows.push({
131
+ id,
132
+ decision: decisionNameFromId(id),
133
+ requestedAt,
134
+ ageMs: Math.max(0, now - requestedAt),
135
+ labelOnly,
136
+ status: value.status ?? "pending",
137
+ questions: formQuestions(decisionNameFromId(id), value.open),
138
+ });
139
+ }
140
+ }
141
+ return rows.sort((a, b) => b.ageMs - a.ageMs);
142
+ }
143
+
144
+ /** One decision on the read-only MCP list. */
145
+ export interface McpDecisionRow {
146
+ readonly name: string;
147
+ readonly state: DecisionListState;
148
+ readonly pending: number;
149
+ readonly metrics?: Readonly<Record<string, number>>;
150
+ readonly drift: boolean;
151
+ }
152
+
153
+ /**
154
+ * Decision rows for `oke.decisions.list`: lockfile state, pending reviews,
155
+ * certificate or candidate metrics, and drift.
156
+ *
157
+ * @param manifest - App manifest
158
+ * @param pendingByName - Pending review counts
159
+ * @param metrics - Candidate or certificate metrics
160
+ */
161
+ export function projectMcpDecisions(
162
+ manifest: Manifest | null | undefined,
163
+ pendingByName: Readonly<Record<string, number>> = {},
164
+ metrics: Readonly<Record<string, Readonly<Record<string, number>>>> = {},
165
+ ): McpDecisionRow[] {
166
+ const drifted = new Set(decisionDriftNames());
167
+ return projectDecisionList(
168
+ manifest,
169
+ getDecisionLock(),
170
+ new Set(Object.keys(metrics)),
171
+ metrics,
172
+ ).map((row) => ({
173
+ name: row.name,
174
+ state: row.state,
175
+ pending: pendingByName[row.name] ?? 0,
176
+ ...((row.metrics ?? metrics[row.name]) ? { metrics: row.metrics ?? metrics[row.name] } : {}),
177
+ drift: drifted.has(row.name) || row.state === "suspended",
178
+ }));
179
+ }
180
+
181
+ /**
182
+ * Queue from a journal store.
183
+ *
184
+ * @param store - Durable journal, when the console has one
185
+ * @param now - Clock
186
+ */
187
+ export async function loadDecisionQueue(
188
+ store: JournalStore | null | undefined,
189
+ now: number,
190
+ tenantId?: string | null,
191
+ ): Promise<DecisionQueueRow[]> {
192
+ if (!store) return [];
193
+ return projectDecisionQueue(await store.list(), now, tenantId);
194
+ }
195
+
196
+ function decisionNameFromId(id: string): string {
197
+ try {
198
+ const decoded = Buffer.from(id, "base64url").toString("utf8");
199
+ const first = decoded.indexOf(".");
200
+ const second = first >= 0 ? decoded.indexOf(".", first + 1) : -1;
201
+ if (second > first) return decoded.slice(second + 1);
202
+ return first > 0 ? decoded.slice(first + 1) : id;
203
+ } catch {
204
+ return id;
205
+ }
206
+ }
207
+
208
+ function formQuestions(name: string, open: readonly string[] | undefined): DecisionFormQuestion[] {
209
+ const decl = aiDecisionRegistry.find((item) => item.name === name);
210
+ if (!decl) return [];
211
+ const ids = open && open.length > 0 ? open : Object.keys(decl.ask);
212
+ const questions: DecisionFormQuestion[] = [];
213
+ for (const id of ids) {
214
+ const question = decl.ask[id];
215
+ if (!question) continue;
216
+ if (question.kind === "choice") {
217
+ questions.push({
218
+ id,
219
+ kind: "choice",
220
+ options: [...Object.keys(question.options), "none_of_these"],
221
+ });
222
+ } else if (question.kind === "score") {
223
+ questions.push({ id, kind: "score", levels: [...question.levels] });
224
+ } else {
225
+ questions.push({ id, kind: "boolean" });
226
+ }
227
+ }
228
+ return questions;
229
+ }
@@ -622,7 +622,7 @@ describe("console flows invoke", () => {
622
622
  config: { drivers: { store: { sql: { test: "pglite" } } } },
623
623
  startScheduler: false,
624
624
  });
625
- const harness = await createTestApp(host);
625
+ const harness = await createTestApp(host, { capability: "open" });
626
626
  const seedFlow = host.bindings.find((b) => b.flow.name === "views.seed")?.flow;
627
627
  expect(seedFlow).toBeDefined();
628
628
  const seeded = await host.execute(
@@ -728,7 +728,7 @@ describe("console flows invoke", () => {
728
728
  config: { drivers: { store: { sql: { test: "pglite" } } } },
729
729
  startScheduler: false,
730
730
  });
731
- const harness = await createTestApp(host);
731
+ const harness = await createTestApp(host, { capability: "open" });
732
732
  const manifest = {
733
733
  ...FLOWS_TEST_MANIFEST,
734
734
  flows: {
@@ -16,6 +16,8 @@ import {
16
16
  } from "../../auth/index.ts";
17
17
  import { DryRunWriteIsolationError } from "../../kernel/dry-run.ts";
18
18
  import { fail, flow, http, type AnyFlowDef, type Binding } from "../../kernel/index.ts";
19
+ import { redactHttpFrame } from "../../kernel/http-frame.ts";
20
+ import { lazyRequire } from "../../kernel/lazy-require.ts";
19
21
  import type { Flow as ManifestFlow, ResourceRef } from "../../manifest/types.ts";
20
22
  import { eventHasIrreversible, runReplay } from "../../cli/replay.ts";
21
23
  import { createRunsRuntime } from "../../runs/runtime.ts";
@@ -44,6 +46,7 @@ import {
44
46
  STORE_SQL_LOCKS_POLL_MS,
45
47
  STORE_SQL_STATS_PII_GAP,
46
48
  StoreSqlStatsError,
49
+ engineErrorText,
47
50
  } from "./store-stats.ts";
48
51
  import {
49
52
  STORE_KV_STATS_SERVER_WIDE_GAP,
@@ -92,7 +95,18 @@ const ManifestOut = z.object({
92
95
  });
93
96
 
94
97
  const EffectEntryOut = z.object({
95
- kind: z.enum(["read", "write", "emit", "send", "ask", "embed", "secret", "call", "fetch"]),
98
+ kind: z.enum([
99
+ "read",
100
+ "write",
101
+ "emit",
102
+ "send",
103
+ "ask",
104
+ "embed",
105
+ "secret",
106
+ "call",
107
+ "fetch",
108
+ "decide",
109
+ ]),
96
110
  resource: z.string(),
97
111
  timestamp: z.number(),
98
112
  duration: z.number(),
@@ -104,6 +118,7 @@ const EffectEntryOut = z.object({
104
118
  kind: z.enum(["third-party", "infrastructure"]).optional(),
105
119
  })
106
120
  .optional(),
121
+ agentRunId: z.string().optional(),
107
122
  });
108
123
 
109
124
  const LogLineOut = z.object({
@@ -150,6 +165,26 @@ const RunsListOut = z.object({
150
165
  input: z.unknown().nullable(),
151
166
  /** Flow return value snapshot — null when absent (failures / legacy). */
152
167
  output: z.unknown().nullable(),
168
+ /**
169
+ * HTTP wire frame — null for non-HTTP runs and rows recorded before
170
+ * the frame existed.
171
+ */
172
+ http: z
173
+ .object({
174
+ request: z.object({
175
+ method: z.string(),
176
+ path: z.string(),
177
+ query: z.record(z.string(), z.string()),
178
+ headers: z.record(z.string(), z.string()),
179
+ }),
180
+ response: z
181
+ .object({
182
+ status: z.number(),
183
+ headers: z.record(z.string(), z.string()),
184
+ })
185
+ .optional(),
186
+ })
187
+ .nullable(),
153
188
  }),
154
189
  ),
155
190
  });
@@ -891,6 +926,8 @@ const AiListOut = z.object({
891
926
  agent: z.string(),
892
927
  message: z.string(),
893
928
  ok: z.boolean(),
929
+ stopReason: z.enum(["completed", "max_steps", "budget", "denied", "aborted", "error"]),
930
+ error: z.string().optional(),
894
931
  steps: z.number(),
895
932
  cost: z.number(),
896
933
  at: z.number(),
@@ -910,6 +947,7 @@ const AiListOut = z.object({
910
947
  "secret",
911
948
  "call",
912
949
  "fetch",
950
+ "decide",
913
951
  ]),
914
952
  resource: z.string(),
915
953
  }),
@@ -2356,6 +2394,14 @@ export function createConsoleBindings(state: ConsoleState): {
2356
2394
  }),
2357
2395
  channelSendTest,
2358
2396
  ),
2397
+ ...lazyRequire<typeof import("./decisions-flows.ts")>(
2398
+ import.meta.dir,
2399
+ ["decisions", "flows"].join("-"),
2400
+ ).decisionConsoleBindings(state),
2401
+ ...lazyRequire<typeof import("./ai-runs-flows.ts")>(
2402
+ import.meta.dir,
2403
+ ["ai", "runs", "flows"].join("-"),
2404
+ ).agentConsoleBindings(state),
2359
2405
  ];
2360
2406
 
2361
2407
  return {
@@ -2657,6 +2703,7 @@ export function projectRun(r: WideEvent, piiFields: ReadonlySet<string> = new Se
2657
2703
  duration: e.duration,
2658
2704
  reversibility: e.reversibility,
2659
2705
  ...(e.external !== undefined ? { external: e.external } : {}),
2706
+ ...(e.agentRunId !== undefined ? { agentRunId: e.agentRunId } : {}),
2660
2707
  })),
2661
2708
  logs: masked.logs.map((line) => ({
2662
2709
  level: line.level,
@@ -2667,6 +2714,7 @@ export function projectRun(r: WideEvent, piiFields: ReadonlySet<string> = new Se
2667
2714
  dimensions,
2668
2715
  input: masked.input === undefined ? null : masked.input,
2669
2716
  output: masked.output === undefined ? null : masked.output,
2717
+ http: r.http ? redactHttpFrame(r.http) : null,
2670
2718
  };
2671
2719
  }
2672
2720
 
@@ -3489,7 +3537,7 @@ function failStoreSqlStats(err: unknown) {
3489
3537
  return fail("PgStatStatementsUnsupported", { reason: err.message });
3490
3538
  }
3491
3539
  return fail("PgStatStatementsUnsupported", {
3492
- reason: err instanceof Error ? err.message : String(err),
3540
+ reason: engineErrorText(err),
3493
3541
  });
3494
3542
  }
3495
3543
 
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { userPrincipal, type UserPrincipal } from "../../auth/planes.ts";
10
10
  import { assembleInput } from "../../compiler/http-parse.ts";
11
+ import { requestForHttpInvoke } from "../../kernel/http-frame.ts";
11
12
  import type { ExecuteResult, OkeApp } from "../../kernel/app.ts";
12
13
  import { httpStatusForFailure } from "../../kernel/builtin-errors.ts";
13
14
  import { fail, OkeError } from "../../kernel/errors.ts";
@@ -357,9 +358,17 @@ export function bindHostInvokeUserFlow(app: InvokeHostApp): ConsoleInvokeUserFlo
357
358
  });
358
359
 
359
360
  const runId = okid();
361
+ const request =
362
+ trigger.kind === "http"
363
+ ? requestForHttpInvoke(trigger, {
364
+ ...(input.pathParams !== undefined ? { pathParams: input.pathParams } : {}),
365
+ input: assembled,
366
+ })
367
+ : undefined;
360
368
  const result = await app.execute(flowDef, assembled, trigger, {
361
369
  trustedInvoke: true,
362
370
  runId,
371
+ ...(request !== undefined ? { request } : {}),
363
372
  ...(input.bypassGates === true ? { bypassGates: true } : {}),
364
373
  ...(input.revealPii === true ? { revealPii: true } : {}),
365
374
  ...(input.rls ? { rls: input.rls } : {}),
@@ -45,6 +45,22 @@ const WideEventIngestSchema = z
45
45
  .optional(),
46
46
  input: z.unknown().optional(),
47
47
  output: z.unknown().optional(),
48
+ http: z
49
+ .object({
50
+ request: z.object({
51
+ method: z.string(),
52
+ path: z.string(),
53
+ query: z.record(z.string(), z.string()),
54
+ headers: z.record(z.string(), z.string()),
55
+ }),
56
+ response: z
57
+ .object({
58
+ status: z.number(),
59
+ headers: z.record(z.string(), z.string()),
60
+ })
61
+ .optional(),
62
+ })
63
+ .optional(),
48
64
  effects: z.array(z.unknown()),
49
65
  logs: z.array(z.unknown()),
50
66
  durationMs: z.number(),
@@ -42,6 +42,7 @@ export const CONSOLE_SPA_PATHS = [
42
42
  "/",
43
43
  "/overview",
44
44
  "/flows",
45
+ "/flows/decisions",
45
46
  "/store",
46
47
  "/vault",
47
48
  "/observability",
@@ -201,6 +202,10 @@ export async function serveConsole(
201
202
 
202
203
  const authed = withCookieAuth(request);
203
204
 
205
+ if (url.pathname.startsWith("/agent/") && handle.state.forwardAgent) {
206
+ return handle.state.forwardAgent(request);
207
+ }
208
+
204
209
  if (url.pathname.startsWith("/console/")) {
205
210
  const response = await runWithDevSurface("Console", () => handle.app.fetch(authed));
206
211
  const withCookies = await attachSessionCookies(authed, response);