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,522 @@
1
+ /**
2
+ * Persisted agent-run event log.
3
+ *
4
+ * The live `fx.run` stream still yields every token. This log coalesces text
5
+ * and tool-argument deltas so a follower can resume without storing one row
6
+ * per token. Structural events are stored as their own rows. The journal
7
+ * driver (memory, file, Postgres) keeps the rows so another instance, or a
8
+ * process restart, resumes from the last seq.
9
+ */
10
+
11
+ import {
12
+ AgentEventDuplicateSeqError,
13
+ type AgentEventStore,
14
+ } from "../../kernel/agent-event-store.ts";
15
+ import type { AgUiEvent } from "./events.ts";
16
+ import { getAgentEventLog } from "./agent-event-slot.ts";
17
+
18
+ export { getAgentEventLog, setAgentEventLog } from "./agent-event-slot.ts";
19
+
20
+ /** Stored rows per run before deltas stop. Structural events still land. */
21
+ export const AGENT_EVENT_CAP = 5_000;
22
+
23
+ /** Default lifetime of a finished run's events. */
24
+ export const AGENT_EVENT_TTL_MS = 24 * 60 * 60 * 1000;
25
+
26
+ /** Unfinished runs older than this are closed with `RUN_ERROR` and removed. */
27
+ export const AGENT_EVENT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
28
+
29
+ /** Name of the one notice stored when deltas stop. */
30
+ export const AGENT_EVENTS_TRUNCATED = "oke.events.truncated";
31
+
32
+ /** Name of the notice stored after a durable append fails twice. */
33
+ export const AGENT_EVENTS_GAP = "oke.events.gap";
34
+
35
+ /** Flush a coalesced delta after this many milliseconds. */
36
+ const FLUSH_MS = 100;
37
+
38
+ /** Flush a coalesced delta after this many characters. */
39
+ const FLUSH_CHARS = 1024;
40
+
41
+ /** One stored event. `seq` is the SSE id. */
42
+ export interface StoredAgentEvent {
43
+ readonly seq: number;
44
+ readonly event: AgUiEvent;
45
+ }
46
+
47
+ /** Who may follow the run. */
48
+ export interface AgentRunHeader {
49
+ readonly runId: string;
50
+ readonly threadId: string;
51
+ /** Agent that opened the run. Absent on logs written before this field. */
52
+ readonly agent?: string;
53
+ /** Parent agent run, when this run is a nested tool. */
54
+ readonly parentRunId?: string;
55
+ readonly tenant: string | null;
56
+ /** Every non-public gate on the calling Flow. */
57
+ readonly gates: readonly string[];
58
+ /** Starting `auth.userId`. Null when the caller was anonymous. */
59
+ readonly userId: string | null;
60
+ /** Starting operator id. Null when the caller was not an operator. */
61
+ readonly operatorId: string | null;
62
+ /** Epoch ms the run was opened. */
63
+ readonly openedAt?: number;
64
+ readonly finishedAt?: number;
65
+ }
66
+
67
+ /** Journal-backed event log for agent runs. */
68
+ export interface AgentEventLog {
69
+ open(header: AgentRunHeader): Promise<void>;
70
+ header(runId: string): Promise<AgentRunHeader | undefined>;
71
+ /**
72
+ * Record one event. Deltas coalesce. Returns the seq once a row is stored,
73
+ * or undefined while a delta is still buffered.
74
+ */
75
+ append(runId: string, event: AgUiEvent, now: number): Promise<number | undefined>;
76
+ /** Force the open delta buffer out. */
77
+ flush(runId: string, now: number): Promise<number | undefined>;
78
+ read(runId: string, afterSeq: number): Promise<readonly StoredAgentEvent[]>;
79
+ /** Delete finished runs older than `ttlMs`. Returns how many runs were removed. */
80
+ sweep(now: number, ttlMs?: number, maxAgeMs?: number): Promise<number>;
81
+ subscribe(runId: string, afterSeq: number, signal?: AbortSignal): AsyncIterable<StoredAgentEvent>;
82
+ /** Live subscribers still attached to one run. */
83
+ listenerCount(runId: string): number;
84
+ }
85
+
86
+ interface RunBucket {
87
+ header: AgentRunHeader;
88
+ rows: StoredAgentEvent[];
89
+ nextSeq: number;
90
+ /** Deltas have already produced the one truncation notice. */
91
+ truncated: boolean;
92
+ /** Store is already at the cap, even when this process has no rows yet. */
93
+ overCap: boolean;
94
+ /** The last durable append failed. The next stored row is {@link AGENT_EVENTS_GAP}. */
95
+ pendingGap: boolean;
96
+ pending?: { event: AgUiEvent; chars: number; since: number };
97
+ timer?: ReturnType<typeof setTimeout>;
98
+ listeners: Set<(row: StoredAgentEvent | undefined) => void>;
99
+ }
100
+
101
+ /** Cap, TTL, and the age after which an unfinished run is closed. */
102
+ export interface AgentEventLogOptions {
103
+ readonly cap?: number;
104
+ readonly ttlMs?: number;
105
+ readonly maxAgeMs?: number;
106
+ /**
107
+ * Claim the right to close one abandoned run.
108
+ * False means another instance holds the journal lease.
109
+ */
110
+ claim?(runId: string): Promise<boolean>;
111
+ }
112
+
113
+ const DELTA_TYPES = new Set(["TEXT_MESSAGE_CONTENT", "TOOL_CALL_ARGS"]);
114
+
115
+ /**
116
+ * Event log. Pass a journal {@link AgentEventStore} so rows survive restart.
117
+ *
118
+ * Appends for one run are serialized. The opener reads `MAX(seq)` before
119
+ * writing. Past {@link AGENT_EVENT_CAP}, deltas stop and one
120
+ * `oke.events.truncated` is stored. Structural events, interrupts, and the
121
+ * terminal frames stay. History a follower can resume from is not trimmed.
122
+ *
123
+ * @param store - Durable rows. Omit for a process-local log.
124
+ * @param options - Cap, TTL, and max age. A number is the TTL.
125
+ */
126
+ export function createMemoryAgentEventLog(
127
+ store?: AgentEventStore,
128
+ options: number | AgentEventLogOptions = AGENT_EVENT_TTL_MS,
129
+ ): AgentEventLog {
130
+ const opts: AgentEventLogOptions = typeof options === "number" ? { ttlMs: options } : options;
131
+ const cap = opts.cap ?? AGENT_EVENT_CAP;
132
+ const defaultTtl = opts.ttlMs ?? AGENT_EVENT_TTL_MS;
133
+ const defaultMaxAge = opts.maxAgeMs ?? AGENT_EVENT_MAX_AGE_MS;
134
+ const runs = new Map<string, RunBucket>();
135
+ const queues = new Map<string, Promise<unknown>>();
136
+
137
+ const bucket = (runId: string): RunBucket | undefined => runs.get(runId);
138
+
139
+ const serialized = <T>(runId: string, fn: () => Promise<T>): Promise<T> => {
140
+ const prev = queues.get(runId) ?? Promise.resolve();
141
+ const job = prev.then(fn, fn);
142
+ queues.set(
143
+ runId,
144
+ job.then(
145
+ () => undefined,
146
+ () => undefined,
147
+ ),
148
+ );
149
+ return job;
150
+ };
151
+
152
+ const terminal = (event: AgUiEvent): boolean =>
153
+ event.type === "RUN_ERROR" ||
154
+ (event.type === "RUN_FINISHED" && event.outcome?.type !== "interrupt");
155
+
156
+ const keepPastCap = (event: AgUiEvent): boolean => {
157
+ if (event.type === "RUN_FINISHED" || event.type === "RUN_ERROR") return true;
158
+ if (
159
+ event.type === "CUSTOM" &&
160
+ (event.name === AGENT_EVENTS_TRUNCATED || event.name === AGENT_EVENTS_GAP)
161
+ ) {
162
+ return true;
163
+ }
164
+ return !DELTA_TYPES.has(event.type);
165
+ };
166
+
167
+ const persist = async (
168
+ runId: string,
169
+ row: StoredAgentEvent,
170
+ ): Promise<{ readonly ok: true; readonly error?: unknown } | { readonly ok: false }> => {
171
+ if (!store) return { ok: true };
172
+ try {
173
+ await store.append(runId, row);
174
+ return { ok: true };
175
+ } catch (err) {
176
+ if (err instanceof AgentEventDuplicateSeqError) throw err;
177
+ try {
178
+ await store.append(runId, row);
179
+ return { ok: true, error: err };
180
+ } catch (again) {
181
+ if (again instanceof AgentEventDuplicateSeqError) throw again;
182
+ return { ok: false };
183
+ }
184
+ }
185
+ };
186
+
187
+ const push = async (run: RunBucket, event: AgUiEvent): Promise<number | undefined> => {
188
+ const gap = event.type === "CUSTOM" && event.name === AGENT_EVENTS_GAP;
189
+ if (run.pendingGap && !gap) {
190
+ run.pendingGap = false;
191
+ const marked = await push(run, { type: "CUSTOM", name: AGENT_EVENTS_GAP, value: {} });
192
+ if (marked === undefined) return undefined;
193
+ }
194
+ const seq = run.nextSeq;
195
+ const row: StoredAgentEvent = { seq, event };
196
+ const stored = await persist(run.header.runId, row);
197
+ if (!stored.ok) {
198
+ run.pendingGap = true;
199
+ return undefined;
200
+ }
201
+ if (gap) run.pendingGap = false;
202
+ run.nextSeq = seq + 1;
203
+ run.rows.push(row);
204
+ for (const listener of run.listeners) listener(row);
205
+ if (stored.error) throw stored.error;
206
+ return seq;
207
+ };
208
+
209
+ const noteTruncation = async (run: RunBucket): Promise<number | undefined> => {
210
+ if (run.truncated) return undefined;
211
+ run.truncated = true;
212
+ return push(run, {
213
+ type: "CUSTOM",
214
+ name: AGENT_EVENTS_TRUNCATED,
215
+ value: { cap },
216
+ });
217
+ };
218
+
219
+ const accept = async (run: RunBucket, event: AgUiEvent): Promise<number | undefined> => {
220
+ const delta = DELTA_TYPES.has(event.type);
221
+ if (delta && !keepPastCap(event) && (run.truncated || run.overCap || run.rows.length >= cap)) {
222
+ if (run.truncated) return undefined;
223
+ return noteTruncation(run);
224
+ }
225
+ if ((run.overCap || run.rows.length >= cap) && !keepPastCap(event)) return undefined;
226
+ return push(run, event);
227
+ };
228
+
229
+ const clearTimer = (run: RunBucket): void => {
230
+ if (!run.timer) return;
231
+ clearTimeout(run.timer);
232
+ run.timer = undefined;
233
+ };
234
+
235
+ const flushPending = async (run: RunBucket): Promise<number | undefined> => {
236
+ clearTimer(run);
237
+ const pending = run.pending;
238
+ run.pending = undefined;
239
+ if (!pending) return undefined;
240
+ return accept(run, pending.event);
241
+ };
242
+
243
+ const armTimer = (run: RunBucket): void => {
244
+ if (run.timer) return;
245
+ run.timer = setTimeout(() => {
246
+ run.timer = undefined;
247
+ void serialized(run.header.runId, () => flushPending(run).then(() => undefined));
248
+ }, FLUSH_MS);
249
+ };
250
+
251
+ const drop = async (runId: string): Promise<void> => {
252
+ const run = runs.get(runId);
253
+ if (run) {
254
+ clearTimer(run);
255
+ runs.delete(runId);
256
+ for (const listener of run.listeners) listener(undefined);
257
+ }
258
+ await store?.remove(runId);
259
+ };
260
+
261
+ return {
262
+ async open(header) {
263
+ await serialized(header.runId, async () => {
264
+ const max = (await store?.maxSeq(header.runId)) ?? 0;
265
+ const marked = (await store?.truncated(header.runId)) ?? false;
266
+ const existing = runs.get(header.runId);
267
+ if (existing) {
268
+ if (store) {
269
+ existing.nextSeq = max + 1;
270
+ if (marked) existing.truncated = true;
271
+ if (marked || max >= cap) existing.overCap = true;
272
+ }
273
+ return;
274
+ }
275
+ if (max > 0) {
276
+ const stored = await store?.readHeader(header.runId);
277
+ runs.set(header.runId, {
278
+ header: (stored ?? header) as AgentRunHeader,
279
+ rows: [],
280
+ nextSeq: max + 1,
281
+ truncated: marked,
282
+ overCap: marked || max >= cap,
283
+ pendingGap: false,
284
+ listeners: new Set(),
285
+ });
286
+ return;
287
+ }
288
+ const opened: AgentRunHeader = { ...header, openedAt: header.openedAt ?? Date.now() };
289
+ runs.set(header.runId, {
290
+ header: opened,
291
+ rows: [],
292
+ nextSeq: 1,
293
+ truncated: false,
294
+ overCap: false,
295
+ pendingGap: false,
296
+ listeners: new Set(),
297
+ });
298
+ await store?.writeHeader(opened);
299
+ });
300
+ },
301
+ async header(runId) {
302
+ const live = bucket(runId)?.header;
303
+ if (live) return live;
304
+ return (await store?.readHeader(runId)) as AgentRunHeader | undefined;
305
+ },
306
+ async append(runId, event, now) {
307
+ return serialized(runId, async () => {
308
+ const run = bucket(runId);
309
+ if (!run) return undefined;
310
+ const structural = !DELTA_TYPES.has(event.type);
311
+ if (structural) {
312
+ await flushPending(run);
313
+ if (terminal(event)) {
314
+ run.header = { ...run.header, finishedAt: now };
315
+ await store?.writeHeader(run.header);
316
+ }
317
+ return accept(run, event);
318
+ }
319
+ const pending = run.pending;
320
+ if (pending && sameDelta(pending.event, event)) {
321
+ pending.event = mergeDelta(pending.event, event);
322
+ pending.chars += deltaSize(event);
323
+ if (pending.chars >= FLUSH_CHARS || now - pending.since >= FLUSH_MS) {
324
+ return flushPending(run);
325
+ }
326
+ armTimer(run);
327
+ return undefined;
328
+ }
329
+ await flushPending(run);
330
+ run.pending = { event, chars: deltaSize(event), since: now };
331
+ if (deltaSize(event) >= FLUSH_CHARS) return flushPending(run);
332
+ armTimer(run);
333
+ return undefined;
334
+ });
335
+ },
336
+ async flush(runId, _now) {
337
+ return serialized(runId, async () => {
338
+ const run = bucket(runId);
339
+ if (!run) return undefined;
340
+ return flushPending(run);
341
+ });
342
+ },
343
+ async read(runId, afterSeq) {
344
+ if (store) {
345
+ const rows = await store.readAfter(runId, afterSeq);
346
+ return rows.map((row) => ({ seq: row.seq, event: row.event as AgUiEvent }));
347
+ }
348
+ const run = bucket(runId);
349
+ if (!run) return [];
350
+ return run.rows.filter((row) => row.seq > afterSeq);
351
+ },
352
+ async sweep(now, ttl = defaultTtl, maxAge = defaultMaxAge) {
353
+ const headers = store
354
+ ? await store.listHeaders()
355
+ : [...runs.values()].map((run) => run.header);
356
+ let removed = 0;
357
+ for (const header of headers) {
358
+ const finished = header.finishedAt;
359
+ if (finished !== undefined && now - finished >= ttl) {
360
+ await drop(header.runId);
361
+ removed++;
362
+ continue;
363
+ }
364
+ const opened = header.openedAt;
365
+ if (finished === undefined && opened !== undefined && now - opened >= maxAge) {
366
+ const claimed = opts.claim
367
+ ? await opts.claim(header.runId)
368
+ : store
369
+ ? await store.claim(header.runId, now)
370
+ : true;
371
+ if (!claimed) continue;
372
+ try {
373
+ if (!runs.has(header.runId)) await this.open(header as AgentRunHeader);
374
+ await this.append(
375
+ header.runId,
376
+ {
377
+ type: "RUN_ERROR",
378
+ message: "agent events: run exceeded max age",
379
+ code: "AgentEventMaxAge",
380
+ },
381
+ now,
382
+ );
383
+ await drop(header.runId);
384
+ removed++;
385
+ } catch (err) {
386
+ if (err instanceof AgentEventDuplicateSeqError) continue;
387
+ throw err;
388
+ }
389
+ }
390
+ }
391
+ return removed;
392
+ },
393
+ subscribe(runId, afterSeq, signal) {
394
+ const run = bucket(runId);
395
+ return {
396
+ async *[Symbol.asyncIterator]() {
397
+ if (!run && !store) return;
398
+ let last = afterSeq;
399
+ if (store) {
400
+ for (const row of await store.readAfter(runId, afterSeq)) {
401
+ last = row.seq;
402
+ yield { seq: row.seq, event: row.event as AgUiEvent };
403
+ }
404
+ } else if (run) {
405
+ for (const row of run.rows) {
406
+ if (row.seq <= afterSeq) continue;
407
+ last = row.seq;
408
+ yield row;
409
+ }
410
+ }
411
+ if (!run) {
412
+ const known = (await store?.listHeaders())?.some((item) => item.runId === runId);
413
+ if (!known && last === afterSeq) return;
414
+ }
415
+ const live = run;
416
+ const queue: StoredAgentEvent[] = [];
417
+ let wake: (() => void) | undefined;
418
+ let done = false;
419
+ const listener = (row: StoredAgentEvent | undefined): void => {
420
+ if (!row) {
421
+ done = true;
422
+ } else if (row.seq > last && !queue.some((queued) => queued.seq === row.seq)) {
423
+ queue.push(row);
424
+ }
425
+ wake?.();
426
+ };
427
+ live?.listeners.add(listener);
428
+ const onAbort = (): void => {
429
+ done = true;
430
+ wake?.();
431
+ };
432
+ signal?.addEventListener("abort", onAbort, { once: true });
433
+ let delay = 200;
434
+ let poll: ReturnType<typeof setTimeout> | undefined;
435
+ const schedule = (): void => {
436
+ if (!store || done) return;
437
+ poll = setTimeout(() => {
438
+ void store.readAfter(runId, last).then((rows) => {
439
+ if (rows.length === 0) delay = Math.min(delay * 2, 2_000);
440
+ else delay = 200;
441
+ for (const row of rows) {
442
+ listener({ seq: row.seq, event: row.event as AgUiEvent });
443
+ }
444
+ const liveHeader = bucket(runId)?.header;
445
+ if (liveHeader?.finishedAt !== undefined && rows.length === 0) {
446
+ done = true;
447
+ wake?.();
448
+ return;
449
+ }
450
+ schedule();
451
+ });
452
+ }, delay);
453
+ };
454
+ schedule();
455
+ try {
456
+ if (signal?.aborted) return;
457
+ for (;;) {
458
+ while (queue.length > 0) {
459
+ const row = queue.shift();
460
+ if (!row) break;
461
+ last = row.seq;
462
+ yield row;
463
+ }
464
+ if (done || signal?.aborted) return;
465
+ if (live?.header.finishedAt !== undefined && queue.length === 0) return;
466
+ await new Promise<void>((resolve) => {
467
+ wake = resolve;
468
+ });
469
+ }
470
+ } finally {
471
+ if (poll) clearTimeout(poll);
472
+ live?.listeners.delete(listener);
473
+ signal?.removeEventListener("abort", onAbort);
474
+ }
475
+ },
476
+ };
477
+ },
478
+ listenerCount(runId) {
479
+ return bucket(runId)?.listeners.size ?? 0;
480
+ },
481
+ };
482
+ }
483
+
484
+ function deltaSize(event: AgUiEvent): number {
485
+ if (event.type === "TEXT_MESSAGE_CONTENT" || event.type === "TOOL_CALL_ARGS") {
486
+ return event.delta.length;
487
+ }
488
+ return 0;
489
+ }
490
+
491
+ function sameDelta(left: AgUiEvent, right: AgUiEvent): boolean {
492
+ if (left.type === "TEXT_MESSAGE_CONTENT" && right.type === "TEXT_MESSAGE_CONTENT") {
493
+ return left.messageId === right.messageId;
494
+ }
495
+ if (left.type === "TOOL_CALL_ARGS" && right.type === "TOOL_CALL_ARGS") {
496
+ return left.toolCallId === right.toolCallId;
497
+ }
498
+ return false;
499
+ }
500
+
501
+ function mergeDelta(left: AgUiEvent, right: AgUiEvent): AgUiEvent {
502
+ if (left.type === "TEXT_MESSAGE_CONTENT" && right.type === "TEXT_MESSAGE_CONTENT") {
503
+ return { ...left, delta: left.delta + right.delta };
504
+ }
505
+ if (left.type === "TOOL_CALL_ARGS" && right.type === "TOOL_CALL_ARGS") {
506
+ return { ...left, delta: left.delta + right.delta };
507
+ }
508
+ return right;
509
+ }
510
+
511
+ /**
512
+ * Sweep the installed log. The scheduler calls this on every tick.
513
+ *
514
+ * @param now - Clock
515
+ * @param ttlMs - Lifetime of a finished run. Defaults to {@link AGENT_EVENT_TTL_MS}.
516
+ */
517
+ export async function sweepInstalledAgentEvents(
518
+ now = Date.now(),
519
+ ttlMs = AGENT_EVENT_TTL_MS,
520
+ ): Promise<number> {
521
+ return (await getAgentEventLog()?.sweep(now, ttlMs)) ?? 0;
522
+ }