okengine 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/AGENTS.md +7 -0
  2. package/manifest.v1.schema.json +44 -0
  3. package/package.json +32 -19
  4. package/site/content/docs/ai/mcp.mdx +13 -9
  5. package/site/content/docs/ai/skills.mdx +1 -1
  6. package/site/content/docs/elements/ai/agents.mdx +61 -13
  7. package/site/content/docs/elements/ai/decide.mdx +536 -0
  8. package/site/content/docs/elements/ai/events.mdx +373 -0
  9. package/site/content/docs/elements/ai/index.mdx +9 -3
  10. package/site/content/docs/elements/ai/meta.json +1 -1
  11. package/site/content/docs/elements/ai/prompts.mdx +4 -2
  12. package/site/content/docs/elements/gate/auth.mdx +4 -1
  13. package/site/content/docs/elements/store/index.mdx +1 -1
  14. package/site/content/docs/elements/store/kv.mdx +5 -5
  15. package/site/content/docs/recipes/dragonfly.mdx +5 -5
  16. package/site/content/docs/recipes/index.mdx +6 -6
  17. package/site/content/docs/recipes/postgres.mdx +12 -8
  18. package/site/content/docs/recipes/redis.mdx +4 -4
  19. package/site/content/docs/reference/cli.mdx +2 -1
  20. package/site/content/docs/reference/configuration.mdx +9 -7
  21. package/site/content/docs/reference/fx.mdx +7 -6
  22. package/src/auth/api-key-sql.test.ts +44 -0
  23. package/src/auth/api-key-sql.ts +1 -0
  24. package/src/auth/api-keys.ts +30 -12
  25. package/src/auth/config.ts +6 -2
  26. package/src/auth/gate-auth.test.ts +20 -0
  27. package/src/cli/agents-md.test.ts +25 -0
  28. package/src/cli/ask-vault-gaps.test.ts +38 -1
  29. package/src/cli/ask-vault-gaps.ts +35 -2
  30. package/src/cli/decide.ts +174 -0
  31. package/src/cli/decision-lock-watch.test.ts +45 -0
  32. package/src/cli/decision-lock-watch.ts +51 -0
  33. package/src/cli/dev-app-runner.ts +34 -8
  34. package/src/cli/dev-auth-secret.test.ts +49 -0
  35. package/src/cli/dev-auth-secret.ts +49 -0
  36. package/src/cli/dev-hot.test.ts +41 -0
  37. package/src/cli/dev-hot.ts +48 -0
  38. package/src/cli/dev.test.ts +1 -1
  39. package/src/cli/dev.ts +24 -6
  40. package/src/cli/eval.ts +142 -1
  41. package/src/cli/index.ts +5 -0
  42. package/src/cli/load-config.images.test.ts +2 -2
  43. package/src/cli/load-config.ts +1 -1
  44. package/src/cli/mcp-from-console.ts +48 -0
  45. package/src/cli/registry.ts +35 -1
  46. package/src/client/agent.ts +307 -0
  47. package/src/client/transport.test.ts +20 -0
  48. package/src/client/transport.ts +2 -1
  49. package/src/client/types.ts +1 -1
  50. package/src/client-react/index.ts +7 -1
  51. package/src/client-react/use-agent-run.test.ts +271 -0
  52. package/src/client-react/use-agent-run.ts +193 -0
  53. package/src/compiler/ai-approval.test.ts +36 -0
  54. package/src/compiler/ai-repair.test.ts +22 -0
  55. package/src/compiler/decisions.extract.test.ts +164 -0
  56. package/src/compiler/effects-infer.ts +27 -1
  57. package/src/compiler/extract.ts +268 -12
  58. package/src/compiler/response.ts +35 -2
  59. package/src/config/index.ts +21 -3
  60. package/src/console/server/ai-runs-flows.ts +269 -0
  61. package/src/console/server/ai-runs.test.ts +323 -0
  62. package/src/console/server/ai-runs.ts +562 -0
  63. package/src/console/server/ai.ts +12 -1
  64. package/src/console/server/app.ts +10 -1
  65. package/src/console/server/decisions-flows.ts +157 -0
  66. package/src/console/server/decisions.test.ts +66 -0
  67. package/src/console/server/decisions.ts +229 -0
  68. package/src/console/server/flows.ts +50 -2
  69. package/src/console/server/invoke-user-flow.ts +9 -0
  70. package/src/console/server/runs-ingest.ts +16 -0
  71. package/src/console/server/serve.ts +5 -0
  72. package/src/console/server/state.ts +25 -1
  73. package/src/console/server/store-stats.test.ts +47 -0
  74. package/src/console/server/store-stats.ts +92 -18
  75. package/src/console/ui-next/dist/assets/access-page-DDKkhT9v.js +4 -0
  76. package/src/console/ui-next/dist/assets/{agent-disclosure-Bkohc_8X.js → agent-disclosure-62Xts8Xi.js} +1 -1
  77. package/src/console/ui-next/dist/assets/{cache-glyph-rL0lHxat.js → cache-glyph-DyeoKHKA.js} +1 -1
  78. package/src/console/ui-next/dist/assets/{call-pii-button-Deo4PP18.js → call-pii-button-DvfpLXsU.js} +1 -1
  79. package/src/console/ui-next/dist/assets/{collapsible-CoJ6amHf.js → collapsible-BY03SeCg.js} +1 -1
  80. package/src/console/ui-next/dist/assets/confirm-sheet-DZfuJXP4.js +1 -0
  81. package/src/console/ui-next/dist/assets/copy-inline-button-BTjPVjRT.js +1 -0
  82. package/src/console/ui-next/dist/assets/decisions-page-CgkKlA56.js +1 -0
  83. package/src/console/ui-next/dist/assets/detail-header-Dk5LipF6.js +1 -0
  84. package/src/console/ui-next/dist/assets/dropdown-menu-CRGrj5JU.js +1 -0
  85. package/src/console/ui-next/dist/assets/duration-tone-Y6HLjyJ5.js +9 -0
  86. package/src/console/ui-next/dist/assets/element-icons-C6t8Vlml.js +1 -0
  87. package/src/console/ui-next/dist/assets/explorer-chrome-Dslh2yE8.js +1 -0
  88. package/src/console/ui-next/dist/assets/explorer-empty-CIVg7Jgb.js +1 -0
  89. package/src/console/ui-next/dist/assets/flows-page-DLPfNy-_.js +1 -0
  90. package/src/console/ui-next/dist/assets/{highlighted-json-CPYBzDh0.js → highlighted-json-D7nNzpgB.js} +1 -1
  91. package/src/console/ui-next/dist/assets/{http-method-DTPevKmM.js → http-method-DdL19zzh.js} +1 -1
  92. package/src/console/ui-next/dist/assets/index-BlrN49Hs.css +2 -0
  93. package/src/console/ui-next/dist/assets/index-C0lc_s9d.js +58 -0
  94. package/src/console/ui-next/dist/assets/observability-page-CRlcyLCC.js +8 -0
  95. package/src/console/ui-next/dist/assets/{replica-lag-BM27lXld.js → replica-lag-Bb3FWUu9.js} +7 -7
  96. package/src/console/ui-next/dist/assets/request-meta-DYBMWhX8.js +1 -0
  97. package/src/console/ui-next/dist/assets/shortcut-keys-DTE1sjTC.js +1 -0
  98. package/src/console/ui-next/dist/assets/store-page-CcE-SXC8.js +41 -0
  99. package/src/console/ui-next/dist/assets/trace-detail-sheet-CKMSEQ4s.js +2 -0
  100. package/src/console/ui-next/dist/assets/tree-expand-toggle-QOfk0M0H.js +55 -0
  101. package/src/console/ui-next/dist/assets/units-page-DKC1CDDd.js +1 -0
  102. package/src/console/ui-next/dist/assets/use-vault-list-BAyQG8Re.js +1 -0
  103. package/src/console/ui-next/dist/assets/vault-page-B54v4Yit.js +2 -0
  104. package/src/console/ui-next/dist/index.html +4 -3
  105. package/src/console/ui-next/seed-parked-approval.ts +126 -0
  106. package/src/console/ui-next/src/client.ts +224 -1
  107. package/src/console/ui-next/src/components/explorer/detail-header.tsx +9 -0
  108. package/src/console/ui-next/src/components/explorer/explorer-start-toggle.tsx +5 -1
  109. package/src/console/ui-next/src/components/ui/sheet-form.tsx +21 -12
  110. package/src/console/ui-next/src/features/flows/decisions/decisions-page.tsx +216 -0
  111. package/src/console/ui-next/src/features/flows/decisions/resolve.test.ts +48 -0
  112. package/src/console/ui-next/src/features/flows/decisions/resolve.ts +111 -0
  113. package/src/console/ui-next/src/features/flows/flows-page.tsx +10 -0
  114. package/src/console/ui-next/src/features/flows/graph/element-map.ts +1 -0
  115. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +6 -0
  116. package/src/console/ui-next/src/features/flows/traces/effect-summary.test.ts +24 -0
  117. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
  118. package/src/console/ui-next/src/features/flows/traces/http-status.ts +58 -0
  119. package/src/console/ui-next/src/features/flows/traces/request-client-card.tsx +355 -0
  120. package/src/console/ui-next/src/features/flows/traces/request-client.test.ts +114 -0
  121. package/src/console/ui-next/src/features/flows/traces/request-client.ts +423 -0
  122. package/src/console/ui-next/src/features/flows/traces/request-input-view.test.ts +52 -6
  123. package/src/console/ui-next/src/features/flows/traces/request-input-view.ts +85 -19
  124. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +505 -80
  125. package/src/console/ui-next/src/features/flows/traces/trace-lanes.test.ts +61 -0
  126. package/src/console/ui-next/src/features/flows/traces/trace-lanes.ts +78 -0
  127. package/src/console/ui-next/src/features/flows/traces/trace-nav.test.ts +48 -0
  128. package/src/console/ui-next/src/features/flows/traces/trace-nav.ts +57 -0
  129. package/src/console/ui-next/src/features/flows/traces/trace-request-section.tsx +275 -96
  130. package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +16 -2
  131. package/src/console/ui-next/src/features/observability/detail/ai-runs.tsx +369 -0
  132. package/src/console/ui-next/src/features/observability/observability-page.tsx +31 -1
  133. package/src/console/ui-next/src/features/observability/state/observability-selection.ts +18 -3
  134. package/src/console/ui-next/src/features/store/performance/kv-performance-panel.tsx +12 -1
  135. package/src/console/ui-next/src/features/store/performance/performance-panel.tsx +14 -1
  136. package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +1 -1
  137. package/src/console/ui-next/src/features/units/detail/flow-contract-panel.tsx +6 -2
  138. package/src/console/ui-next/src/features/units/explorer/units-tree.tsx +73 -18
  139. package/src/console/ui-next/src/features/units/lib/unit-tree.test.ts +21 -0
  140. package/src/console/ui-next/src/features/units/lib/unit-tree.ts +82 -0
  141. package/src/console/ui-next/src/features/units/units-page.tsx +25 -7
  142. package/src/console/ui-next/src/router.tsx +17 -0
  143. package/src/console/ui-next/ui-next-seed-runs.ts +23 -0
  144. package/src/console/ui-next/vite-console-kernel-plugin.ts +43 -3
  145. package/src/console/ui-next/vite.config.ts +9 -0
  146. package/src/docker/recipes/dragonfly.ts +3 -2
  147. package/src/docker/recipes/redis.ts +1 -1
  148. package/src/drivers/ai-anthropic.ts +212 -21
  149. package/src/drivers/ai-mock.ts +34 -1
  150. package/src/drivers/ai-openai-compatible.ts +67 -26
  151. package/src/drivers/ai-preconnect.ts +18 -0
  152. package/src/drivers/ai-stream.test.ts +62 -0
  153. package/src/drivers/ai-types.ts +20 -0
  154. package/src/drivers/journal-postgres.ts +289 -2
  155. package/src/drivers/memory-ddl.test.ts +18 -0
  156. package/src/drivers/memory.ts +21 -1
  157. package/src/drivers/pg-rls.test.ts +31 -0
  158. package/src/drivers/pg-rls.ts +15 -0
  159. package/src/drivers/postgres.test.ts +9 -0
  160. package/src/drivers/postgres.ts +49 -11
  161. package/src/elements/ai/agent-event-slot.ts +27 -0
  162. package/src/elements/ai/approval-http.ts +207 -0
  163. package/src/elements/ai/approval.test.ts +741 -0
  164. package/src/elements/ai/approval.ts +228 -0
  165. package/src/elements/ai/decisions/bind.ts +163 -0
  166. package/src/elements/ai/decisions/certificate.test.ts +291 -0
  167. package/src/elements/ai/decisions/certificate.ts +377 -0
  168. package/src/elements/ai/decisions/certify.ts +314 -0
  169. package/src/elements/ai/decisions/decide.test.ts +953 -0
  170. package/src/elements/ai/decisions/e2e.test.ts +399 -0
  171. package/src/elements/ai/decisions/export.test.ts +120 -0
  172. package/src/elements/ai/decisions/export.ts +146 -0
  173. package/src/elements/ai/decisions/fixtures/openrouter-request.json +24 -0
  174. package/src/elements/ai/decisions/fixtures/openrouter-response.json +22 -0
  175. package/src/elements/ai/decisions/fixtures/typesafe-request.json +19 -0
  176. package/src/elements/ai/decisions/fixtures/typesafe-response.json +13 -0
  177. package/src/elements/ai/decisions/http.test.ts +154 -0
  178. package/src/elements/ai/decisions/http.ts +240 -0
  179. package/src/elements/ai/decisions/labels.test.ts +282 -0
  180. package/src/elements/ai/decisions/labels.ts +257 -0
  181. package/src/elements/ai/decisions/openrouter.live.test.ts +136 -0
  182. package/src/elements/ai/decisions/openrouter.ts +36 -0
  183. package/src/elements/ai/decisions/provider.ts +102 -0
  184. package/src/elements/ai/decisions/typesafe.ts +36 -0
  185. package/src/elements/ai/declare.ts +252 -1
  186. package/src/elements/ai/events.test.ts +379 -0
  187. package/src/elements/ai/events.ts +138 -0
  188. package/src/elements/ai/run-events.test.ts +461 -0
  189. package/src/elements/ai/run-events.ts +522 -0
  190. package/src/elements/ai/runtime.ts +1186 -105
  191. package/src/elements/ai/stream-turn.ts +214 -0
  192. package/src/elements/ai/stream.live.test.ts +76 -0
  193. package/src/elements/ai/subagent.test.ts +136 -0
  194. package/src/elements/ai.test.ts +497 -3
  195. package/src/elements/ai.ts +16 -1
  196. package/src/elements/clock/durable.ts +27 -1
  197. package/src/elements/store/sql-session.test.ts +51 -0
  198. package/src/elements/store/sql-session.ts +111 -1
  199. package/src/i18n/catalogs/ar.ts +5 -0
  200. package/src/i18n/catalogs/en.ts +5 -0
  201. package/src/index.ts +2 -0
  202. package/src/kernel/agent-event-store.ts +448 -0
  203. package/src/kernel/app.ts +103 -1
  204. package/src/kernel/boot-bind/ai.ts +3 -0
  205. package/src/kernel/boot-bind/store.test.ts +26 -0
  206. package/src/kernel/boot-bind/store.ts +82 -6
  207. package/src/kernel/boot.ts +15 -4
  208. package/src/kernel/builtin-errors.ts +2 -0
  209. package/src/kernel/capability.ts +3 -0
  210. package/src/kernel/decision-label-store.ts +441 -0
  211. package/src/kernel/effects.test.ts +2 -1
  212. package/src/kernel/effects.ts +13 -1
  213. package/src/kernel/element-registries.ts +3 -0
  214. package/src/kernel/errors-text.ts +4 -0
  215. package/src/kernel/errors.ts +2 -0
  216. package/src/kernel/flow.test.ts +2 -3
  217. package/src/kernel/fx-decide.ts +761 -0
  218. package/src/kernel/fx.test.ts +12 -5
  219. package/src/kernel/fx.ts +312 -29
  220. package/src/kernel/http-frame.test.ts +94 -0
  221. package/src/kernel/http-frame.ts +203 -0
  222. package/src/kernel/journal.ts +104 -0
  223. package/src/kernel/json-result.ts +10 -0
  224. package/src/kernel/pipeline.test.ts +45 -0
  225. package/src/kernel/sse-id.ts +32 -0
  226. package/src/manifest/diff.ts +1 -0
  227. package/src/manifest/types.ts +29 -2
  228. package/src/mcp/ai-tools.test.ts +202 -0
  229. package/src/mcp/authorization.ts +66 -0
  230. package/src/mcp/session.ts +3 -0
  231. package/src/mcp/tools.ts +168 -0
  232. package/src/release/http-graph.test.ts +2 -0
  233. package/src/release/http-graph.ts +5 -0
  234. package/src/release/measure.ts +39 -14
  235. package/src/runs/collect.ts +4 -1
  236. package/src/runs/parquet.test.ts +27 -1
  237. package/src/runs/parquet.ts +19 -0
  238. package/src/runs/types.ts +31 -0
  239. package/src/runtime/json-code-block.test.ts +106 -47
  240. package/src/runtime/json-code-block.ts +1209 -46
  241. package/src/term.test.ts +13 -0
  242. package/src/term.ts +81 -49
  243. package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +0 -1
  244. package/src/console/ui-next/dist/assets/access-page-Ze6ckjdd.js +0 -4
  245. package/src/console/ui-next/dist/assets/copy-inline-button-CWG5_ZOh.js +0 -1
  246. package/src/console/ui-next/dist/assets/detail-header-b_ZzIsmU.js +0 -1
  247. package/src/console/ui-next/dist/assets/dropdown-menu-nwXEO1Ac.js +0 -1
  248. package/src/console/ui-next/dist/assets/duration-tone-BbQ_8z50.js +0 -9
  249. package/src/console/ui-next/dist/assets/element-icons-CwyLpVXz.js +0 -1
  250. package/src/console/ui-next/dist/assets/explorer-empty-C8sSCqYT.js +0 -1
  251. package/src/console/ui-next/dist/assets/flows-page-D2E7BtKK.js +0 -1
  252. package/src/console/ui-next/dist/assets/index-VxoEz295.css +0 -2
  253. package/src/console/ui-next/dist/assets/index-vTuwmeQz.js +0 -58
  254. package/src/console/ui-next/dist/assets/observability-page-SJTSzHFJ.js +0 -4
  255. package/src/console/ui-next/dist/assets/request-meta-BjFT7DNl.js +0 -1
  256. package/src/console/ui-next/dist/assets/shortcut-keys-CUNegEV4.js +0 -1
  257. package/src/console/ui-next/dist/assets/store-page-Qj9ThRE-.js +0 -41
  258. package/src/console/ui-next/dist/assets/trace-detail-sheet-Cmitq3da.js +0 -2
  259. package/src/console/ui-next/dist/assets/tree-expand-toggle-CbDIB-7x.js +0 -55
  260. package/src/console/ui-next/dist/assets/units-page-_PuYqFty.js +0 -1
  261. package/src/console/ui-next/dist/assets/use-vault-list-Cs45Czkt.js +0 -1
  262. package/src/console/ui-next/dist/assets/vault-page-BOMr0go9.js +0 -2
@@ -0,0 +1,562 @@
1
+ /**
2
+ * Console projection for agent runs and the tool-approval queue.
3
+ *
4
+ * Numbers come from the agent ledger, the follow log, and the journal.
5
+ * The UI must not recompute them.
6
+ */
7
+
8
+ import type { AgentEventStore } from "../../kernel/agent-event-store.ts";
9
+ import type { JournalRun, JournalStore } from "../../kernel/journal.ts";
10
+ import {
11
+ approvalStepName,
12
+ parseApprovalId,
13
+ type AgentApprovalRecord,
14
+ } from "../../elements/ai/approval.ts";
15
+ import type { AgUiEvent } from "../../elements/ai/events.ts";
16
+ import type { AgentRunHeader } from "../../elements/ai/run-events.ts";
17
+ import type {
18
+ AgentDenial,
19
+ AgentRunRecord,
20
+ AgentStopReason,
21
+ AgentToolEffect,
22
+ AiJournalEntry,
23
+ AiRuntime,
24
+ } from "../../elements/ai.ts";
25
+
26
+ /** Run lifecycle shown on the AI list. */
27
+ export type ConsoleAgentRunStatus = "running" | "finished" | "error" | "interrupted";
28
+
29
+ /** One tool line on a run. */
30
+ export interface ConsoleAgentTrailStep {
31
+ readonly tool: string;
32
+ readonly status: "ok" | "denied" | "pending";
33
+ readonly effects: readonly AgentToolEffect[];
34
+ readonly denial: AgentDenial | null;
35
+ readonly approver?: string;
36
+ readonly at: number;
37
+ }
38
+
39
+ /** One schema-repair follow-up that landed inside the run window. */
40
+ export interface ConsoleAgentRepair {
41
+ readonly prompt: string;
42
+ readonly at: number;
43
+ readonly attempts: number;
44
+ }
45
+
46
+ /** Agent run row for the list and the subagent tree. */
47
+ export interface ConsoleAgentRunRow {
48
+ readonly id: string;
49
+ readonly agent: string;
50
+ readonly status: ConsoleAgentRunStatus;
51
+ readonly stopReason?: AgentStopReason;
52
+ readonly error?: string;
53
+ readonly steps: number;
54
+ readonly cost: number;
55
+ readonly inputTokens?: number;
56
+ readonly outputTokens?: number;
57
+ readonly threadId?: string;
58
+ readonly startedAt: number;
59
+ readonly finishedAt?: number;
60
+ readonly parentRunId?: string;
61
+ readonly tenant: string | null;
62
+ }
63
+
64
+ /** Run detail: trail, repairs, and direct children. */
65
+ export interface ConsoleAgentRunDetail extends ConsoleAgentRunRow {
66
+ readonly message: string;
67
+ readonly trail: readonly ConsoleAgentTrailStep[];
68
+ readonly denials: readonly AgentDenial[];
69
+ readonly repairs: readonly ConsoleAgentRepair[];
70
+ readonly children: readonly ConsoleAgentRunRow[];
71
+ readonly events: readonly { readonly seq: number; readonly event: AgUiEvent }[];
72
+ }
73
+
74
+ /** One pending tool approval. */
75
+ export interface ConsoleApprovalRow {
76
+ readonly id: string;
77
+ readonly agent: string;
78
+ readonly tool: string;
79
+ readonly args: unknown;
80
+ readonly requestedAt: number;
81
+ readonly ageMs: number;
82
+ readonly gate: string;
83
+ readonly tenant: string | null;
84
+ readonly runId: string;
85
+ }
86
+
87
+ /** Sources for {@link loadConsoleAgentRuns}. */
88
+ export interface LoadAgentRunsInput {
89
+ readonly runtime: AiRuntime | null;
90
+ readonly store: JournalStore | null;
91
+ readonly now: number;
92
+ /** When set, drop rows from every other tenant. */
93
+ readonly tenantId?: string | null;
94
+ }
95
+
96
+ /**
97
+ * List agent runs from the in-memory ledger and the follow log.
98
+ *
99
+ * @param input - Runtime, journal, clock
100
+ */
101
+ export async function loadConsoleAgentRuns(
102
+ input: LoadAgentRunsInput,
103
+ ): Promise<ConsoleAgentRunDetail[]> {
104
+ const events = input.store?.agentEvents;
105
+ const headers = events ? await events.listHeaders() : [];
106
+ const records = new Map<string, AgentRunRecord>();
107
+ for (const run of input.runtime?.agentRuns ?? []) records.set(run.id, run);
108
+ const journal = input.runtime?.journal ?? [];
109
+ const durable = await durableTrail(input.store);
110
+ const details: ConsoleAgentRunDetail[] = [];
111
+ const seen = new Set<string>();
112
+
113
+ for (const header of headers) {
114
+ seen.add(header.runId);
115
+ const record = records.get(header.runId);
116
+ const stored = await readEvents(events, header.runId);
117
+ details.push(detailFrom(header, record, stored, journal, durable, input.tenantId));
118
+ }
119
+ for (const record of records.values()) {
120
+ if (seen.has(record.id)) continue;
121
+ details.push(detailFrom(undefined, record, [], journal, durable, input.tenantId));
122
+ }
123
+
124
+ const visible = details.filter((row) => row.id.length > 0);
125
+ const byParent = new Map<string, ConsoleAgentRunRow[]>();
126
+ for (const row of visible) {
127
+ if (!row.parentRunId) continue;
128
+ const list = byParent.get(row.parentRunId) ?? [];
129
+ list.push(rowWithoutTree(row));
130
+ byParent.set(row.parentRunId, list);
131
+ }
132
+ return visible
133
+ .map((row) => ({ ...row, children: byParent.get(row.id) ?? [] }))
134
+ .sort((a, b) => b.startedAt - a.startedAt || a.id.localeCompare(b.id));
135
+ }
136
+
137
+ /**
138
+ * One run, or undefined when neither ledger has it.
139
+ *
140
+ * @param input - Sources
141
+ * @param runId - Agent run id
142
+ */
143
+ export async function loadConsoleAgentRun(
144
+ input: LoadAgentRunsInput,
145
+ runId: string,
146
+ ): Promise<ConsoleAgentRunDetail | undefined> {
147
+ const runs = await loadConsoleAgentRuns(input);
148
+ return runs.find((row) => row.id === runId);
149
+ }
150
+
151
+ /**
152
+ * Pending tool approvals, newest age first.
153
+ *
154
+ * @param store - Durable journal
155
+ * @param now - Clock
156
+ * @param tenantId - When set, keep only this tenant
157
+ */
158
+ export async function loadApprovalQueue(
159
+ store: JournalStore | null | undefined,
160
+ now: number,
161
+ tenantId?: string | null,
162
+ ): Promise<ConsoleApprovalRow[]> {
163
+ if (!store) return [];
164
+ return projectApprovalQueue(await store.list(), now, tenantId);
165
+ }
166
+
167
+ /**
168
+ * Pending `ai-approval:` steps.
169
+ *
170
+ * @param runs - Journal runs
171
+ * @param now - Clock
172
+ * @param tenantId - When set, keep only this tenant
173
+ */
174
+ export function projectApprovalQueue(
175
+ runs: readonly JournalRun[],
176
+ now: number,
177
+ tenantId?: string | null,
178
+ ): ConsoleApprovalRow[] {
179
+ const rows: ConsoleApprovalRow[] = [];
180
+ for (const run of runs) {
181
+ for (const entry of run.entries) {
182
+ if (entry.kind !== "step" || !entry.name.startsWith("ai-approval:")) continue;
183
+ const id = entry.name.slice("ai-approval:".length);
184
+ const value = entry.value as AgentApprovalRecord;
185
+ if (value.status !== "pending") continue;
186
+ if (tenantId !== undefined && tenantId !== null && (value.tenant ?? null) !== tenantId) {
187
+ continue;
188
+ }
189
+ const parsed = parseApprovalId(id);
190
+ const requestedAt = value.requestedAt ?? entry.at;
191
+ rows.push({
192
+ id,
193
+ agent: value.agent ?? run.flow,
194
+ tool: value.tool,
195
+ args: value.args,
196
+ requestedAt,
197
+ ageMs: Math.max(0, now - requestedAt),
198
+ gate: value.gate,
199
+ tenant: value.tenant ?? null,
200
+ runId: parsed?.runId ?? run.id,
201
+ });
202
+ }
203
+ }
204
+ return rows.sort((a, b) => b.ageMs - a.ageMs || a.id.localeCompare(b.id));
205
+ }
206
+
207
+ /**
208
+ * Step name helper re-exported for tests that seed a journal row.
209
+ *
210
+ * @param id - Approval id
211
+ */
212
+ export function approvalStep(id: string): string {
213
+ return approvalStepName(id);
214
+ }
215
+
216
+ function rowWithoutTree(row: ConsoleAgentRunDetail): ConsoleAgentRunRow {
217
+ return {
218
+ id: row.id,
219
+ agent: row.agent,
220
+ status: row.status,
221
+ ...(row.stopReason !== undefined ? { stopReason: row.stopReason } : {}),
222
+ ...(row.error !== undefined ? { error: row.error } : {}),
223
+ steps: row.steps,
224
+ cost: row.cost,
225
+ ...(row.inputTokens !== undefined ? { inputTokens: row.inputTokens } : {}),
226
+ ...(row.outputTokens !== undefined ? { outputTokens: row.outputTokens } : {}),
227
+ ...(row.threadId !== undefined ? { threadId: row.threadId } : {}),
228
+ startedAt: row.startedAt,
229
+ ...(row.finishedAt !== undefined ? { finishedAt: row.finishedAt } : {}),
230
+ ...(row.parentRunId !== undefined ? { parentRunId: row.parentRunId } : {}),
231
+ tenant: row.tenant,
232
+ };
233
+ }
234
+
235
+ async function readEvents(
236
+ events: AgentEventStore | undefined,
237
+ runId: string,
238
+ ): Promise<readonly { readonly seq: number; readonly event: AgUiEvent }[]> {
239
+ if (!events) return [];
240
+ const rows = await events.readAfter(runId, 0);
241
+ return rows.map((row) => ({ seq: row.seq, event: row.event as AgUiEvent }));
242
+ }
243
+
244
+ /** One journal approval, addressed by its opaque id and by the tool call. */
245
+ interface DurableApproval {
246
+ readonly id: string;
247
+ /** Journal run that owns the step. This is the Flow run, not always the agent run. */
248
+ readonly journalRunId: string;
249
+ readonly agent?: string;
250
+ readonly toolCallId: string;
251
+ readonly step: ConsoleAgentTrailStep;
252
+ }
253
+
254
+ /** Agent name and tool lines recovered from journal approval steps. */
255
+ interface DurableTrail {
256
+ readonly agentByRun: ReadonlyMap<string, string>;
257
+ readonly stepsByRun: ReadonlyMap<string, readonly ConsoleAgentTrailStep[]>;
258
+ readonly byApprovalId: ReadonlyMap<string, DurableApproval>;
259
+ }
260
+
261
+ /**
262
+ * Approval steps are the durable trail when the in-memory ledger is on another runtime.
263
+ *
264
+ * @param store - Shared journal
265
+ */
266
+ async function durableTrail(store: JournalStore | null): Promise<DurableTrail> {
267
+ const agentByRun = new Map<string, string>();
268
+ const stepsByRun = new Map<string, ConsoleAgentTrailStep[]>();
269
+ const byApprovalId = new Map<string, DurableApproval>();
270
+ if (!store) return { agentByRun, stepsByRun, byApprovalId };
271
+ for (const run of await store.list()) {
272
+ for (const entry of run.entries) {
273
+ if (entry.kind !== "step" || !entry.name.startsWith("ai-approval:")) continue;
274
+ const id = entry.name.slice("ai-approval:".length);
275
+ const parsed = parseApprovalId(id);
276
+ if (!parsed) continue;
277
+ const value = entry.value as AgentApprovalRecord;
278
+ if (value.agent) agentByRun.set(parsed.runId, value.agent);
279
+ const denied = value.status === "denied";
280
+ const step: ConsoleAgentTrailStep = {
281
+ tool: value.tool,
282
+ status: denied ? "denied" : value.status === "approved" ? "ok" : "pending",
283
+ effects: [],
284
+ denial: denied
285
+ ? {
286
+ agent: value.agent ?? "",
287
+ tool: value.tool,
288
+ gate: value.gate,
289
+ reason: value.reason ?? "",
290
+ at: entry.at,
291
+ }
292
+ : null,
293
+ ...(value.approver !== undefined ? { approver: value.approver } : {}),
294
+ at: value.requestedAt ?? entry.at,
295
+ };
296
+ const list = stepsByRun.get(parsed.runId) ?? [];
297
+ list.push(step);
298
+ stepsByRun.set(parsed.runId, list);
299
+ byApprovalId.set(id, {
300
+ id,
301
+ journalRunId: parsed.runId,
302
+ ...(value.agent !== undefined ? { agent: value.agent } : {}),
303
+ toolCallId: parsed.toolCallId,
304
+ step,
305
+ });
306
+ }
307
+ }
308
+ return { agentByRun, stepsByRun, byApprovalId };
309
+ }
310
+
311
+ function detailFrom(
312
+ header: AgentRunHeader | undefined,
313
+ record: AgentRunRecord | undefined,
314
+ events: readonly { readonly seq: number; readonly event: AgUiEvent }[],
315
+ journal: readonly AiJournalEntry[],
316
+ durable: DurableTrail,
317
+ tenantId: string | null | undefined,
318
+ ): ConsoleAgentRunDetail {
319
+ const finished = events.findLast(
320
+ (row) => row.event.type === "RUN_FINISHED" && row.event.outcome?.type !== "interrupt",
321
+ );
322
+ const interrupted = events.some(
323
+ (row) => row.event.type === "RUN_FINISHED" && row.event.outcome?.type === "interrupt",
324
+ );
325
+ const errored = events.findLast((row) => row.event.type === "RUN_ERROR");
326
+ const usage = finished?.event.type === "RUN_FINISHED" ? finished.event.usage?.[0] : undefined;
327
+ const result = finished?.event.type === "RUN_FINISHED" ? finished.event.result : undefined;
328
+ const stopReason = record?.stopReason ?? stopFrom(result?.stopReason);
329
+ const error =
330
+ record?.error ??
331
+ (result && "error" in result && typeof result.error === "string" ? result.error : undefined) ??
332
+ (errored?.event.type === "RUN_ERROR" ? errored.event.message : undefined);
333
+ const startedAt = header?.openedAt ?? record?.at ?? 0;
334
+ const finishedAt = header?.finishedAt ?? record?.finishedAt;
335
+ const tenant = header?.tenant ?? null;
336
+ const status = runStatus({
337
+ stopReason,
338
+ error,
339
+ finishedAt,
340
+ interrupted: interrupted && finished === undefined,
341
+ });
342
+ const inputTokens = record?.inputTokens ?? usage?.inputTokens;
343
+ const outputTokens = record?.outputTokens ?? usage?.outputTokens;
344
+ const id = header?.runId ?? record?.id ?? "";
345
+ const trail = trailFor(id, record, events, durable);
346
+ const parentRunId = header?.parentRunId ?? record?.parentRunId ?? parentFromEvents(events);
347
+ const row: ConsoleAgentRunDetail = {
348
+ id,
349
+ agent:
350
+ header?.agent ??
351
+ record?.agent ??
352
+ agentFromApprovals(id, events, durable) ??
353
+ agentFromEvents(events) ??
354
+ "(unknown)",
355
+ status,
356
+ ...(stopReason !== undefined ? { stopReason } : {}),
357
+ ...(error !== undefined ? { error } : {}),
358
+ steps: record?.steps ?? (countSteps(events) || trail.length),
359
+ cost: record?.cost ?? result?.cost ?? 0,
360
+ ...(inputTokens !== undefined ? { inputTokens } : {}),
361
+ ...(outputTokens !== undefined ? { outputTokens } : {}),
362
+ ...((header?.threadId ?? record?.threadId) !== undefined
363
+ ? { threadId: header?.threadId ?? record?.threadId }
364
+ : {}),
365
+ startedAt,
366
+ ...(finishedAt !== undefined ? { finishedAt } : {}),
367
+ ...(parentRunId !== undefined ? { parentRunId } : {}),
368
+ tenant,
369
+ message: record?.message ?? "",
370
+ trail,
371
+ denials: record?.denials ?? trail.flatMap((step) => (step.denial ? [step.denial] : [])),
372
+ repairs: repairsInWindow(journal, startedAt, finishedAt),
373
+ children: [],
374
+ events,
375
+ };
376
+ if (tenantId !== undefined && tenantId !== null && row.tenant !== tenantId) {
377
+ return { ...row, id: "" };
378
+ }
379
+ return row;
380
+ }
381
+
382
+ function runStatus(input: {
383
+ readonly stopReason?: AgentStopReason;
384
+ readonly error?: string;
385
+ readonly finishedAt?: number;
386
+ readonly interrupted: boolean;
387
+ }): ConsoleAgentRunStatus {
388
+ if (input.stopReason === "error") return "error";
389
+ if (input.error !== undefined && input.finishedAt === undefined && !input.interrupted)
390
+ return "error";
391
+ if (input.interrupted) return "interrupted";
392
+ if (input.finishedAt === undefined && input.stopReason === undefined) return "running";
393
+ return "finished";
394
+ }
395
+
396
+ function stopFrom(value: string | undefined): AgentStopReason | undefined {
397
+ if (
398
+ value === "completed" ||
399
+ value === "max_steps" ||
400
+ value === "budget" ||
401
+ value === "denied" ||
402
+ value === "aborted" ||
403
+ value === "error"
404
+ ) {
405
+ return value;
406
+ }
407
+ return undefined;
408
+ }
409
+
410
+ function parentFromEvents(events: readonly { readonly event: AgUiEvent }[]): string | undefined {
411
+ for (const row of events) {
412
+ if (row.event.type !== "CUSTOM") continue;
413
+ const value = row.event.value;
414
+ if (!value || typeof value !== "object" || !("parentRunId" in value)) continue;
415
+ const parent = (value as { parentRunId?: unknown }).parentRunId;
416
+ if (typeof parent === "string") return parent;
417
+ }
418
+ return undefined;
419
+ }
420
+
421
+ function agentFromEvents(events: readonly { readonly event: AgUiEvent }[]): string | undefined {
422
+ for (const row of events) {
423
+ if (row.event.type === "CUSTOM" && row.event.name === "oke.subagent.started") {
424
+ const value = row.event.value;
425
+ if (value && typeof value === "object" && "agent" in value) {
426
+ const agent = (value as { agent?: unknown }).agent;
427
+ if (typeof agent === "string") return agent;
428
+ }
429
+ }
430
+ }
431
+ return undefined;
432
+ }
433
+
434
+ function countSteps(events: readonly { readonly event: AgUiEvent }[]): number {
435
+ const started = events.filter((row) => row.event.type === "STEP_STARTED").length;
436
+ const finished = events.filter((row) => row.event.type === "STEP_FINISHED").length;
437
+ return Math.max(started, finished);
438
+ }
439
+
440
+ /**
441
+ * Tool lines for one agent run.
442
+ *
443
+ * The in-memory ledger wins. Otherwise the follow log's tool calls are joined
444
+ * to journal approvals by the interrupt id. The approval's journal run id is
445
+ * the Flow run, so it only matches the agent run id when they are the same.
446
+ *
447
+ * @param id - Agent run id
448
+ * @param record - In-memory ledger row
449
+ * @param events - Follow-log rows
450
+ * @param durable - Journal approvals
451
+ */
452
+ function trailFor(
453
+ id: string,
454
+ record: AgentRunRecord | undefined,
455
+ events: readonly { readonly event: AgUiEvent }[],
456
+ durable: DurableTrail,
457
+ ): ConsoleAgentTrailStep[] {
458
+ const recorded = record?.trail ?? [];
459
+ if (recorded.length > 0) {
460
+ return recorded.map((step) => ({
461
+ tool: step.tool,
462
+ status: step.status,
463
+ effects: step.effects,
464
+ denial: step.denial ?? null,
465
+ ...(step.approver !== undefined ? { approver: step.approver } : {}),
466
+ at: step.at,
467
+ }));
468
+ }
469
+ const linked = approvalsForRun(id, events, durable);
470
+ const steps: ConsoleAgentTrailStep[] = [];
471
+ const seen = new Set<string>();
472
+ for (const row of events) {
473
+ if (row.event.type !== "TOOL_CALL_START") continue;
474
+ const toolCallId = row.event.toolCallId;
475
+ seen.add(toolCallId);
476
+ const approval = linked.get(toolCallId);
477
+ const resulted = events.some((stored) => {
478
+ const body = stored.event;
479
+ return body.type === "TOOL_CALL_RESULT" && body.toolCallId === toolCallId;
480
+ });
481
+ if (approval) {
482
+ steps.push(
483
+ resulted && approval.step.status === "pending"
484
+ ? { ...approval.step, status: "ok" }
485
+ : approval.step,
486
+ );
487
+ continue;
488
+ }
489
+ steps.push({
490
+ tool: row.event.toolCallName,
491
+ status: resulted ? "ok" : "pending",
492
+ effects: [],
493
+ denial: null,
494
+ at: 0,
495
+ });
496
+ }
497
+ for (const approval of linked.values()) {
498
+ if (seen.has(approval.toolCallId)) continue;
499
+ steps.push(approval.step);
500
+ }
501
+ if (steps.length > 0) return steps;
502
+ return [...(durable.stepsByRun.get(id) ?? [])];
503
+ }
504
+
505
+ /**
506
+ * Approvals whose interrupt is on this follow log, or whose journal run id is this agent run.
507
+ *
508
+ * @param id - Agent run id
509
+ * @param events - Follow-log rows
510
+ * @param durable - Journal approvals
511
+ */
512
+ function approvalsForRun(
513
+ id: string,
514
+ events: readonly { readonly event: AgUiEvent }[],
515
+ durable: DurableTrail,
516
+ ): Map<string, DurableApproval> {
517
+ const linked = new Map<string, DurableApproval>();
518
+ for (const approval of durable.byApprovalId.values()) {
519
+ if (approval.journalRunId === id) linked.set(approval.toolCallId, approval);
520
+ }
521
+ for (const row of events) {
522
+ if (row.event.type !== "RUN_FINISHED" || row.event.outcome?.type !== "interrupt") continue;
523
+ for (const interrupt of row.event.outcome.interrupts) {
524
+ const approval = durable.byApprovalId.get(interrupt.id);
525
+ if (approval) linked.set(approval.toolCallId, approval);
526
+ }
527
+ }
528
+ return linked;
529
+ }
530
+
531
+ /**
532
+ * Agent name from an approval that belongs to this run.
533
+ *
534
+ * @param id - Agent run id
535
+ * @param events - Follow-log rows
536
+ * @param durable - Journal approvals
537
+ */
538
+ function agentFromApprovals(
539
+ id: string,
540
+ events: readonly { readonly event: AgUiEvent }[],
541
+ durable: DurableTrail,
542
+ ): string | undefined {
543
+ for (const approval of approvalsForRun(id, events, durable).values()) {
544
+ if (approval.agent) return approval.agent;
545
+ }
546
+ return durable.agentByRun.get(id);
547
+ }
548
+
549
+ function repairsInWindow(
550
+ journal: readonly AiJournalEntry[],
551
+ startedAt: number,
552
+ finishedAt: number | undefined,
553
+ ): ConsoleAgentRepair[] {
554
+ const end = finishedAt ?? Number.POSITIVE_INFINITY;
555
+ const repairs: ConsoleAgentRepair[] = [];
556
+ for (const entry of journal) {
557
+ if (entry.attempts.length < 2) continue;
558
+ if (entry.at < startedAt || entry.at > end) continue;
559
+ repairs.push({ prompt: entry.prompt, at: entry.at, attempts: entry.attempts.length });
560
+ }
561
+ return repairs;
562
+ }
@@ -11,6 +11,7 @@ import {
11
11
  createAiRuntime,
12
12
  type AgentDenial,
13
13
  type AgentRunRecord,
14
+ type AgentStopReason,
14
15
  type AgentToolEffect,
15
16
  type AiAskOutcome,
16
17
  type AiFallbackAttempt,
@@ -94,6 +95,8 @@ export interface AgentRunRow {
94
95
  readonly agent: string;
95
96
  readonly message: string;
96
97
  readonly ok: boolean;
98
+ readonly stopReason: AgentStopReason;
99
+ readonly error?: string;
97
100
  readonly steps: number;
98
101
  readonly cost: number;
99
102
  readonly at: number;
@@ -158,7 +161,11 @@ export interface ProjectAiOptions {
158
161
  */
159
162
  export function createManifestAiRuntime(
160
163
  manifest: Manifest | null,
161
- options: { readonly now?: () => number } = {},
164
+ options: {
165
+ readonly now?: () => number;
166
+ readonly journalStore?: import("../../kernel/journal.ts").JournalStore | null;
167
+ readonly eventLog?: import("../../elements/ai/run-events.ts").AgentEventLog;
168
+ } = {},
162
169
  ): AiRuntime {
163
170
  const models = Object.entries(manifest?.ai?.models ?? {}).map(([name, m]) =>
164
171
  ai.model(name, {
@@ -200,6 +207,8 @@ export function createManifestAiRuntime(
200
207
  defaultDriver: mockAiDriver,
201
208
  effectsForFlow: (flowName) => effectsForFlowFromManifest(manifest, flowName),
202
209
  now: options.now,
210
+ ...(options.journalStore ? { journalStore: options.journalStore } : {}),
211
+ ...(options.eventLog ? { eventLog: options.eventLog } : {}),
203
212
  });
204
213
  }
205
214
 
@@ -413,6 +422,8 @@ function projectAgentRuns(runs: readonly AgentRunRecord[]): readonly AgentRunRow
413
422
  agent: r.agent,
414
423
  message: r.message,
415
424
  ok: r.ok,
425
+ stopReason: r.stopReason,
426
+ ...(r.error !== undefined ? { error: r.error } : {}),
416
427
  steps: r.steps,
417
428
  cost: r.cost,
418
429
  at: r.at,
@@ -15,6 +15,7 @@ import {
15
15
  } from "../../runs/index.ts";
16
16
  import { piiFieldNamesFromManifest } from "./runs-pii.ts";
17
17
  import { runConsoleRunsQuery } from "./runs-query.ts";
18
+ import { getAgentEventLog } from "../../elements/ai/agent-event-slot.ts";
18
19
  import { createManifestAiRuntime } from "./ai.ts";
19
20
  import { CONSOLE_GATES } from "./console-gates.ts";
20
21
  import { createConsoleBindings } from "./flows.ts";
@@ -364,7 +365,15 @@ export function bindManifestAiRuntime(state: ConsoleState): void {
364
365
  Object.keys(state.manifest.ai.agents ?? {}).length > 0 ||
365
366
  Object.keys(state.manifest.ai.models ?? {}).length > 0);
366
367
  if (!hasAi) return;
367
- bindAiRuntime(state, createManifestAiRuntime(state.manifest, { now: state.now }));
368
+ const eventLog = getAgentEventLog();
369
+ bindAiRuntime(
370
+ state,
371
+ createManifestAiRuntime(state.manifest, {
372
+ now: state.now,
373
+ ...(state.journalStore ? { journalStore: state.journalStore } : {}),
374
+ ...(eventLog ? { eventLog } : {}),
375
+ }),
376
+ );
368
377
  }
369
378
 
370
379
  /**