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,461 @@
1
+ /**
2
+ * Coalesced agent event log: cap, truncation, and TTL.
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import { mkdtemp } from "node:fs/promises";
7
+ import { tmpdir } from "node:os";
8
+ import { join } from "node:path";
9
+ import {
10
+ AGENT_EVENT_SWEEP_CLAIM_MS,
11
+ AgentEventDuplicateSeqError,
12
+ createFileAgentEventStore,
13
+ createMemoryAgentEventStore,
14
+ createPostgresAgentEventStore,
15
+ type AgentEventStore,
16
+ } from "../../kernel/agent-event-store.ts";
17
+ import { connectPglite } from "../../drivers/pglite.ts";
18
+ import {
19
+ AGENT_EVENT_CAP,
20
+ AGENT_EVENT_TTL_MS,
21
+ AGENT_EVENTS_GAP,
22
+ createMemoryAgentEventLog,
23
+ setAgentEventLog,
24
+ sweepInstalledAgentEvents,
25
+ } from "./run-events.ts";
26
+
27
+ const header = {
28
+ runId: "r1",
29
+ threadId: "t1",
30
+ tenant: null,
31
+ gates: [] as string[],
32
+ userId: null,
33
+ operatorId: null,
34
+ };
35
+
36
+ describe("agent event log", () => {
37
+ test("the default cap is 5000 rows", () => {
38
+ expect(AGENT_EVENT_CAP).toBe(5_000);
39
+ });
40
+
41
+ test("coalesces text deltas and keeps structural events", async () => {
42
+ const log = createMemoryAgentEventLog();
43
+ await log.open({
44
+ runId: "r1",
45
+ threadId: "t1",
46
+ tenant: null,
47
+ gates: [],
48
+ userId: null,
49
+ operatorId: null,
50
+ });
51
+ await log.append("r1", { type: "TEXT_MESSAGE_START", messageId: "m-1", role: "assistant" }, 0);
52
+ await log.append("r1", { type: "TEXT_MESSAGE_CONTENT", messageId: "m-1", delta: "Hel" }, 0);
53
+ await log.append("r1", { type: "TEXT_MESSAGE_CONTENT", messageId: "m-1", delta: "lo" }, 0);
54
+ await log.append("r1", { type: "TEXT_MESSAGE_END", messageId: "m-1" }, 50);
55
+ const rows = await log.read("r1", 0);
56
+ const content = rows.find((row) => row.event.type === "TEXT_MESSAGE_CONTENT");
57
+ expect(content?.event).toMatchObject({ delta: "Hello" });
58
+ expect(rows.some((row) => row.event.type === "TEXT_MESSAGE_END")).toBe(true);
59
+ });
60
+
61
+ test("at the cap, deltas stop and structural events stay", async () => {
62
+ const log = createMemoryAgentEventLog(undefined, { cap: 3 });
63
+ await log.open({
64
+ runId: "r1",
65
+ threadId: "t1",
66
+ tenant: null,
67
+ gates: [],
68
+ userId: null,
69
+ operatorId: null,
70
+ });
71
+ for (let i = 0; i < 3; i++) {
72
+ await log.append("r1", { type: "STEP_STARTED", stepName: `step-${i}` }, i);
73
+ }
74
+ await log.append("r1", { type: "TEXT_MESSAGE_CONTENT", messageId: "m-1", delta: "x" }, 1_000);
75
+ await log.flush("r1", 1_000);
76
+ await log.append("r1", { type: "TEXT_MESSAGE_CONTENT", messageId: "m-1", delta: "y" }, 1_000);
77
+ await log.append("r1", { type: "STEP_STARTED", stepName: "after" }, 1_001);
78
+ await log.append(
79
+ "r1",
80
+ {
81
+ type: "RUN_FINISHED",
82
+ threadId: "t1",
83
+ runId: "r1",
84
+ outcome: {
85
+ type: "interrupt",
86
+ interrupts: [{ id: "ap-1", reason: "approval" }],
87
+ },
88
+ },
89
+ 1_002,
90
+ );
91
+ await log.append(
92
+ "r1",
93
+ {
94
+ type: "RUN_FINISHED",
95
+ threadId: "t1",
96
+ runId: "r1",
97
+ result: { cost: 0, stopReason: "completed", output: "ok" },
98
+ },
99
+ 1_003,
100
+ );
101
+ const rows = await log.read("r1", 0);
102
+ expect(rows.map((row) => row.seq)).toEqual([1, 2, 3, 4, 5, 6, 7]);
103
+ expect(rows.filter((row) => row.event.type === "CUSTOM")).toHaveLength(1);
104
+ expect(rows.some((row) => row.event.type === "TEXT_MESSAGE_CONTENT")).toBe(false);
105
+ expect(
106
+ rows.some((row) => row.event.type === "STEP_STARTED" && row.event.stepName === "after"),
107
+ ).toBe(true);
108
+ expect(rows.at(-2)?.event.type).toBe("RUN_FINISHED");
109
+ expect(rows.at(-1)?.event.type).toBe("RUN_FINISHED");
110
+ });
111
+
112
+ test("sweep deletes a finished run after the TTL", async () => {
113
+ const log = createMemoryAgentEventLog();
114
+ await log.open({
115
+ runId: "r1",
116
+ threadId: "t1",
117
+ tenant: null,
118
+ gates: [],
119
+ userId: null,
120
+ operatorId: null,
121
+ });
122
+ await log.append(
123
+ "r1",
124
+ {
125
+ type: "RUN_FINISHED",
126
+ threadId: "t1",
127
+ runId: "r1",
128
+ result: { cost: 0, stopReason: "completed" },
129
+ },
130
+ 10,
131
+ );
132
+ expect(await log.sweep(10 + AGENT_EVENT_TTL_MS - 1)).toBe(0);
133
+ expect(await log.header("r1")).toBeDefined();
134
+ expect(await log.sweep(10 + AGENT_EVENT_TTL_MS)).toBe(1);
135
+ expect(await log.header("r1")).toBeUndefined();
136
+ });
137
+
138
+ test("sweepInstalledAgentEvents runs the scheduler hook", async () => {
139
+ const log = createMemoryAgentEventLog();
140
+ setAgentEventLog(log);
141
+ await log.open({
142
+ runId: "r1",
143
+ threadId: "t1",
144
+ tenant: null,
145
+ gates: [],
146
+ userId: null,
147
+ operatorId: null,
148
+ });
149
+ await log.append("r1", { type: "RUN_ERROR", message: "nope", code: "Error" }, 10);
150
+ expect((await log.header("r1"))?.finishedAt).toBe(10);
151
+ expect(await sweepInstalledAgentEvents(10 + AGENT_EVENT_TTL_MS)).toBe(1);
152
+ setAgentEventLog(undefined);
153
+ });
154
+
155
+ test("100 aborted follows leave no listeners", async () => {
156
+ const log = createMemoryAgentEventLog();
157
+ await log.open({
158
+ runId: "r1",
159
+ threadId: "t1",
160
+ tenant: null,
161
+ gates: [],
162
+ userId: null,
163
+ operatorId: null,
164
+ });
165
+ const controllers = Array.from({ length: 100 }, () => new AbortController());
166
+ const iters = controllers.map((controller) =>
167
+ log.subscribe("r1", 0, controller.signal)[Symbol.asyncIterator](),
168
+ );
169
+ for (const controller of controllers) controller.abort();
170
+ await Promise.all(iters.map((iter) => iter.next()));
171
+ expect(log.listenerCount("r1")).toBe(0);
172
+ });
173
+
174
+ test("a restarted log resumes the next seq from the store", async () => {
175
+ const store = createMemoryAgentEventStore();
176
+ const first = createMemoryAgentEventLog(store);
177
+ await first.open({
178
+ runId: "r1",
179
+ threadId: "t1",
180
+ tenant: null,
181
+ gates: [],
182
+ userId: null,
183
+ operatorId: null,
184
+ });
185
+ await first.append("r1", { type: "RUN_STARTED", threadId: "t1", runId: "r1" }, 1);
186
+ const second = createMemoryAgentEventLog(store);
187
+ await second.open({
188
+ runId: "r1",
189
+ threadId: "t1",
190
+ tenant: null,
191
+ gates: [],
192
+ userId: null,
193
+ operatorId: null,
194
+ });
195
+ const seq = await second.append(
196
+ "r1",
197
+ {
198
+ type: "RUN_FINISHED",
199
+ threadId: "t1",
200
+ runId: "r1",
201
+ result: { cost: 0, stopReason: "completed" },
202
+ },
203
+ 2,
204
+ );
205
+ expect(seq).toBe(2);
206
+ expect((await second.read("r1", 0)).map((row) => row.seq)).toEqual([1, 2]);
207
+ });
208
+
209
+ test("sweep reads the store, and an abandoned run is closed then removed", async () => {
210
+ const store = createMemoryAgentEventStore();
211
+ const first = createMemoryAgentEventLog(store);
212
+ await first.open({
213
+ runId: "done",
214
+ threadId: "t1",
215
+ tenant: null,
216
+ gates: [],
217
+ userId: null,
218
+ operatorId: null,
219
+ });
220
+ await first.append(
221
+ "done",
222
+ {
223
+ type: "RUN_FINISHED",
224
+ threadId: "t1",
225
+ runId: "done",
226
+ result: { cost: 0, stopReason: "completed" },
227
+ },
228
+ 10,
229
+ );
230
+ await first.open({
231
+ runId: "stuck",
232
+ threadId: "t1",
233
+ tenant: null,
234
+ gates: [],
235
+ userId: null,
236
+ operatorId: null,
237
+ openedAt: 10,
238
+ });
239
+ const second = createMemoryAgentEventLog(store, { maxAgeMs: 50 });
240
+ expect(second.listenerCount("done")).toBe(0);
241
+ expect(await second.sweep(10 + AGENT_EVENT_TTL_MS, AGENT_EVENT_TTL_MS, 50)).toBe(2);
242
+ expect(await second.header("done")).toBeUndefined();
243
+ expect(await second.header("stuck")).toBeUndefined();
244
+ });
245
+
246
+ test("a duplicate seq is an error", async () => {
247
+ const store = createMemoryAgentEventStore();
248
+ await store.writeHeader(header);
249
+ await store.append("r1", { seq: 1, event: { type: "RUN_STARTED" } });
250
+ await expect(
251
+ store.append("r1", { seq: 1, event: { type: "RUN_STARTED" } }),
252
+ ).rejects.toBeInstanceOf(AgentEventDuplicateSeqError);
253
+ });
254
+
255
+ test("a file store appends JSONL under a write lock", async () => {
256
+ const dir = await mkdtemp(join(tmpdir(), "oke-events-"));
257
+ const store = createFileAgentEventStore(dir);
258
+ const log = createMemoryAgentEventLog(store);
259
+ await log.open(header);
260
+ await Promise.all([
261
+ log.append("r1", { type: "STEP_STARTED", stepName: "a" }, 1),
262
+ log.append("r1", { type: "STEP_STARTED", stepName: "b" }, 2),
263
+ ]);
264
+ const text = await Bun.file(join(dir, `${encodeURIComponent("r1")}.jsonl`)).text();
265
+ const lines = text.trim().split("\n");
266
+ expect(lines[0]).toContain('"kind":"header"');
267
+ expect(lines.filter((line) => line.includes('"kind":"row"'))).toHaveLength(2);
268
+ expect((await log.read("r1", 0)).map((row) => row.seq)).toEqual([1, 2]);
269
+ });
270
+
271
+ test("PGlite stores interleaved emits in call order", async () => {
272
+ const sql = await connectPglite({ url: "memory://" });
273
+ try {
274
+ const store = await createPostgresAgentEventStore(sql);
275
+ const log = createMemoryAgentEventLog(store);
276
+ await log.open(header);
277
+ const names: string[] = [];
278
+ const writes = [];
279
+ for (let i = 0; i < 20; i++) {
280
+ const stepName = i % 2 === 0 ? `a-${i}` : `b-${i}`;
281
+ names.push(stepName);
282
+ writes.push(log.append("r1", { type: "STEP_STARTED", stepName }, i));
283
+ }
284
+ await Promise.all(writes);
285
+ const rows = await log.read("r1", 0);
286
+ expect(
287
+ rows.map((row) => (row.event.type === "STEP_STARTED" ? row.event.stepName : "")),
288
+ ).toEqual(names);
289
+ expect(rows.map((row) => row.seq)).toEqual(names.map((_, index) => index + 1));
290
+ } finally {
291
+ await sql.close();
292
+ }
293
+ });
294
+
295
+ test("resume on another log continues seq without rewriting", async () => {
296
+ const sql = await connectPglite({ url: "memory://" });
297
+ try {
298
+ const store = await createPostgresAgentEventStore(sql);
299
+ const first = createMemoryAgentEventLog(store);
300
+ await first.open(header);
301
+ await first.append("r1", { type: "RUN_STARTED", threadId: "t1", runId: "r1" }, 1);
302
+ await first.append(
303
+ "r1",
304
+ {
305
+ type: "RUN_FINISHED",
306
+ threadId: "t1",
307
+ runId: "r1",
308
+ outcome: { type: "interrupt", interrupts: [{ id: "ap-1", reason: "approval" }] },
309
+ },
310
+ 2,
311
+ );
312
+ const second = createMemoryAgentEventLog(store);
313
+ await second.open(header);
314
+ await second.append("r1", { type: "STEP_STARTED", stepName: "after" }, 3);
315
+ await second.append(
316
+ "r1",
317
+ {
318
+ type: "RUN_FINISHED",
319
+ threadId: "t1",
320
+ runId: "r1",
321
+ result: { cost: 0, stopReason: "completed" },
322
+ },
323
+ 4,
324
+ );
325
+ const rows = await store.read("r1");
326
+ expect(rows?.rows.map((row) => row.seq)).toEqual([1, 2, 3, 4]);
327
+ expect(new Set(rows?.rows.map((row) => row.seq)).size).toBe(4);
328
+ } finally {
329
+ await sql.close();
330
+ }
331
+ });
332
+
333
+ test("park on A, resume on B, resume on A stores each event once", async () => {
334
+ const store = createMemoryAgentEventStore();
335
+ const a = createMemoryAgentEventLog(store);
336
+ const b = createMemoryAgentEventLog(store);
337
+ await a.open(header);
338
+ await a.append("r1", { type: "RUN_STARTED", threadId: "t1", runId: "r1" }, 1);
339
+ await a.append(
340
+ "r1",
341
+ {
342
+ type: "RUN_FINISHED",
343
+ threadId: "t1",
344
+ runId: "r1",
345
+ outcome: { type: "interrupt", interrupts: [{ id: "ap-1", reason: "approval" }] },
346
+ },
347
+ 2,
348
+ );
349
+ await b.open(header);
350
+ await b.append("r1", { type: "STEP_STARTED", stepName: "on-b" }, 3);
351
+ await a.open(header);
352
+ await a.append("r1", { type: "STEP_STARTED", stepName: "on-a" }, 4);
353
+ const rows = await store.read("r1");
354
+ expect(rows?.rows.map((row) => row.seq)).toEqual([1, 2, 3, 4]);
355
+ expect(new Set(rows?.rows.map((row) => row.seq)).size).toBe(4);
356
+ });
357
+
358
+ test("a new log keeps the truncation marker and drops later deltas", async () => {
359
+ const store = createMemoryAgentEventStore();
360
+ const first = createMemoryAgentEventLog(store, { cap: 2 });
361
+ await first.open(header);
362
+ await first.append("r1", { type: "STEP_STARTED", stepName: "a" }, 1);
363
+ await first.append("r1", { type: "STEP_STARTED", stepName: "b" }, 2);
364
+ await first.append("r1", { type: "TEXT_MESSAGE_CONTENT", messageId: "m", delta: "x" }, 1_000);
365
+ await first.flush("r1", 1_000);
366
+ const second = createMemoryAgentEventLog(store, { cap: 2 });
367
+ await second.open(header);
368
+ const seq = await second.append(
369
+ "r1",
370
+ { type: "TEXT_MESSAGE_CONTENT", messageId: "m", delta: "y" },
371
+ 2_000,
372
+ );
373
+ await second.flush("r1", 2_000);
374
+ expect(seq).toBeUndefined();
375
+ const rows = await second.read("r1", 0);
376
+ expect(rows.some((row) => row.event.type === "TEXT_MESSAGE_CONTENT")).toBe(false);
377
+ expect(rows.filter((row) => row.event.type === "CUSTOM")).toHaveLength(1);
378
+ });
379
+
380
+ test("two sweeps close an abandoned run once and a duplicate seq is not thrown", async () => {
381
+ const store = createMemoryAgentEventStore();
382
+ const opener = createMemoryAgentEventLog(store);
383
+ await opener.open({ ...header, openedAt: 1 });
384
+ const a = createMemoryAgentEventLog(store, { maxAgeMs: 10 });
385
+ const b = createMemoryAgentEventLog(store, { maxAgeMs: 10 });
386
+ const removed = await Promise.all([
387
+ a.sweep(100, AGENT_EVENT_TTL_MS, 10),
388
+ b.sweep(100, AGENT_EVENT_TTL_MS, 10),
389
+ ]);
390
+ expect(removed.reduce((sum, count) => sum + count, 0)).toBe(1);
391
+ expect(await store.read("r1")).toBeUndefined();
392
+ await expect(b.sweep(100, AGENT_EVENT_TTL_MS, 10)).resolves.toBe(0);
393
+ });
394
+
395
+ test("a failed store write retries once, then the next stored row is a gap", async () => {
396
+ const inner = createMemoryAgentEventStore();
397
+ let failures = 2;
398
+ const store: AgentEventStore = {
399
+ ...inner,
400
+ async append(runId, row) {
401
+ if (failures > 0) {
402
+ failures -= 1;
403
+ throw new Error("store write failed");
404
+ }
405
+ await inner.append(runId, row);
406
+ },
407
+ };
408
+ const log = createMemoryAgentEventLog(store);
409
+ await log.open(header);
410
+ expect(await log.append("r1", { type: "STEP_STARTED", stepName: "lost" }, 1)).toBeUndefined();
411
+ expect(await inner.maxSeq("r1")).toBe(0);
412
+ await log.append("r1", { type: "STEP_STARTED", stepName: "kept" }, 2);
413
+ const rows = await inner.read("r1");
414
+ expect(rows?.rows.map((row) => row.seq)).toEqual([1, 2]);
415
+ expect(rows?.rows[0]?.event).toMatchObject({ type: "CUSTOM", name: AGENT_EVENTS_GAP });
416
+ expect(rows?.rows[1]?.event).toMatchObject({ type: "STEP_STARTED", stepName: "kept" });
417
+ });
418
+
419
+ test("a sweep claim expires so another instance can reclaim the run", async () => {
420
+ const dir = await mkdtemp(join(tmpdir(), "oke-events-claim-"));
421
+ const sql = await connectPglite({ url: "memory://" });
422
+ const stores: AgentEventStore[] = [
423
+ createMemoryAgentEventStore(),
424
+ createFileAgentEventStore(dir),
425
+ await createPostgresAgentEventStore(sql),
426
+ ];
427
+ try {
428
+ for (const store of stores) {
429
+ await store.writeHeader({ ...header, openedAt: 1 });
430
+ expect(await store.claim("r1", 1_000)).toBe(true);
431
+ expect(await store.claim("r1", 1_000 + AGENT_EVENT_SWEEP_CLAIM_MS - 1)).toBe(false);
432
+ expect(await store.claim("r1", 1_000 + AGENT_EVENT_SWEEP_CLAIM_MS)).toBe(true);
433
+ await store.remove("r1");
434
+ }
435
+ } finally {
436
+ await sql.close();
437
+ }
438
+ });
439
+
440
+ test("truncation matches the stored event type, not a payload string", async () => {
441
+ const sql = await connectPglite({ url: "memory://" });
442
+ try {
443
+ const store = await createPostgresAgentEventStore(sql);
444
+ const log = createMemoryAgentEventLog(store);
445
+ await log.open(header);
446
+ await log.append(
447
+ "r1",
448
+ { type: "CUSTOM", name: "note", value: { text: "oke.events.truncated" } },
449
+ 1,
450
+ );
451
+ expect(await store.truncated("r1")).toBe(false);
452
+ const capped = createMemoryAgentEventLog(store, { cap: 1 });
453
+ await capped.open(header);
454
+ await capped.append("r1", { type: "TEXT_MESSAGE_CONTENT", messageId: "m", delta: "x" }, 2);
455
+ await capped.flush("r1", 2);
456
+ expect(await store.truncated("r1")).toBe(true);
457
+ } finally {
458
+ await sql.close();
459
+ }
460
+ });
461
+ });