okengine 0.13.0 → 0.15.2

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 (277) hide show
  1. package/manifest.v1.schema.json +7 -1
  2. package/package.json +4 -5
  3. package/site/content/docs/ai/mcp.mdx +27 -2
  4. package/site/content/docs/elements/ai.mdx +59 -11
  5. package/site/content/docs/elements/channel.mdx +1 -0
  6. package/site/content/docs/elements/clock.mdx +30 -12
  7. package/site/content/docs/elements/flow.mdx +9 -3
  8. package/site/content/docs/elements/gate.mdx +25 -22
  9. package/site/content/docs/elements/signal.mdx +71 -25
  10. package/site/content/docs/elements/store.mdx +87 -28
  11. package/site/content/docs/elements/vault.mdx +1 -0
  12. package/site/content/docs/get-started/basic-usage.mdx +22 -13
  13. package/site/content/docs/get-started/index.mdx +5 -0
  14. package/site/content/docs/get-started/installation.mdx +2 -2
  15. package/site/content/docs/get-started/introduction.mdx +11 -3
  16. package/site/content/docs/get-started/meta.json +1 -1
  17. package/site/content/docs/get-started/project-structure.mdx +932 -0
  18. package/site/content/docs/reference/cli.md +13 -10
  19. package/site/content/docs/reference/client.mdx +25 -21
  20. package/site/content/docs/reference/fx.mdx +27 -22
  21. package/site/content/docs/reference/security.md +1 -1
  22. package/src/auth/bindings.ts +14 -5
  23. package/src/auth/cross-plane.ts +10 -3
  24. package/src/cli/ai-setup/ai-setup.test.ts +62 -0
  25. package/src/cli/ai-setup/apply.ts +32 -1
  26. package/src/cli/ai-setup/recommend.test.ts +25 -0
  27. package/src/cli/ai-setup/recommend.ts +8 -3
  28. package/src/cli/attach-host-console.test.ts +3 -2
  29. package/src/cli/attach-host-console.ts +3 -0
  30. package/src/cli/build.test.ts +4 -2
  31. package/src/cli/db-seed.ts +48 -15
  32. package/src/cli/db.test.ts +104 -0
  33. package/src/cli/db.ts +122 -10
  34. package/src/cli/dev-controls.test.ts +20 -0
  35. package/src/cli/dev-controls.ts +13 -0
  36. package/src/cli/dev.test.ts +15 -4
  37. package/src/cli/dev.ts +102 -29
  38. package/src/cli/ensure-drizzle-config.ts +2 -1
  39. package/src/cli/tui/DevLive.tsx +12 -2
  40. package/src/cli/tui/keys.test.ts +2 -1
  41. package/src/cli/tui/keys.ts +3 -2
  42. package/src/compiler/aot.test.ts +1 -1
  43. package/src/compiler/effects-infer.ts +58 -3
  44. package/src/compiler/extract.test.ts +458 -29
  45. package/src/compiler/extract.ts +387 -69
  46. package/src/compiler/flow-path.test.ts +96 -0
  47. package/src/compiler/flow-path.ts +173 -0
  48. package/src/compiler/generate-adopt.test.ts +112 -3
  49. package/src/compiler/generate-adopt.ts +280 -26
  50. package/src/compiler/response.ts +45 -1
  51. package/src/compiler/schema-from-ast.ts +609 -0
  52. package/src/console/server/ai.ts +5 -2
  53. package/src/console/server/bind.ts +2 -2
  54. package/src/console/server/console.test.ts +3 -0
  55. package/src/console/server/flows.ts +121 -40
  56. package/src/console/server/invoke-user-flow.test.ts +3 -1
  57. package/src/console/server/invoke-user-flow.ts +17 -2
  58. package/src/console/server/iso-at.test.ts +9 -0
  59. package/src/console/server/iso-at.ts +11 -0
  60. package/src/console/server/security-headers.ts +6 -3
  61. package/src/console/server/serve.ts +17 -2
  62. package/src/console/server/spa-proxy.test.ts +1 -0
  63. package/src/console/server/sql-catalog.ts +147 -7
  64. package/src/console/server/sql-rls.test.ts +16 -0
  65. package/src/console/server/state.ts +8 -0
  66. package/src/console/server/store.test.ts +7 -4
  67. package/src/console/server/store.ts +142 -58
  68. package/src/console/ui-next/dist/assets/agent-disclosure-C0X1fbWF.js +1 -0
  69. package/src/console/ui-next/dist/assets/cache-glyph-F1FI122b.js +1 -0
  70. package/src/console/ui-next/dist/assets/{call-pii-button-bqkxMrJH.js → call-pii-button-DEDCl_j3.js} +1 -1
  71. package/src/console/ui-next/dist/assets/collapsible-LPqGvfoz.js +1 -0
  72. package/src/console/ui-next/dist/assets/{confirm-sheet-DDCRmG62.js → confirm-sheet-3ptDzXbz.js} +1 -1
  73. package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
  74. package/src/console/ui-next/dist/assets/{explorer-empty-HjTnVQoR.js → explorer-empty-BvYrygyO.js} +1 -1
  75. package/src/console/ui-next/dist/assets/flows-page-Dluo1Bd7.js +1 -0
  76. package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-CS_O8L-r.js} +1 -1
  77. package/src/console/ui-next/dist/assets/http-method--sWDdXSB.js +1 -0
  78. package/src/console/ui-next/dist/assets/index-Ca3HZMVq.js +66 -0
  79. package/src/console/ui-next/dist/assets/index-UtTDRQpS.css +2 -0
  80. package/src/console/ui-next/dist/assets/{link-DF7SZ9Ek.js → link-COeyggt-.js} +1 -1
  81. package/src/console/ui-next/dist/assets/observability-page-HK9-BO8z.js +4 -0
  82. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
  83. package/src/console/ui-next/dist/assets/replica-lag-DBIFFf7d.js +18 -0
  84. package/src/console/ui-next/dist/assets/sheet-form-Dnwa5oOR.js +1 -0
  85. package/src/console/ui-next/dist/assets/shortcut-keys-CoqwEIi0.js +1 -0
  86. package/src/console/ui-next/dist/assets/skeleton-BHcfDYcb.js +1 -0
  87. package/src/console/ui-next/dist/assets/store-page-BSohH3wM.js +41 -0
  88. package/src/console/ui-next/dist/assets/trace-detail-sheet-CFIGRnnA.js +2 -0
  89. package/src/console/ui-next/dist/assets/tree-expand-toggle-BgLl34w7.js +54 -0
  90. package/src/console/ui-next/dist/assets/units-page-B0cFE76A.js +1 -0
  91. package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-BLbZhzyF.js} +1 -1
  92. package/src/console/ui-next/dist/assets/vault-page-B1SbYe10.js +2 -0
  93. package/src/console/ui-next/dist/index.html +6 -6
  94. package/src/console/ui-next/seed-invoke-host.ts +2 -0
  95. package/src/console/ui-next/src/client.ts +35 -7
  96. package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
  97. package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
  98. package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
  99. package/src/console/ui-next/src/features/flows/graph/build-flow-graph.test.ts +18 -0
  100. package/src/console/ui-next/src/features/flows/graph/build-flow-graph.ts +14 -1
  101. package/src/console/ui-next/src/features/flows/graph/neighborhood.test.ts +17 -0
  102. package/src/console/ui-next/src/features/flows/graph/neighborhood.ts +16 -3
  103. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +3 -1
  104. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
  105. package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
  106. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +9 -3
  107. package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +29 -2
  108. package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
  109. package/src/console/ui-next/src/features/observability/lib/ask-count.test.ts +25 -0
  110. package/src/console/ui-next/src/features/observability/lib/ask-count.ts +4 -1
  111. package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
  112. package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
  113. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
  114. package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
  115. package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
  116. package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
  117. package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
  118. package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
  119. package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
  120. package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
  121. package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
  122. package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
  123. package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
  124. package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
  125. package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
  126. package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
  127. package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
  128. package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
  129. package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
  130. package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
  131. package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
  132. package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
  133. package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
  134. package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
  135. package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
  136. package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
  137. package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +12 -4
  138. package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
  139. package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
  140. package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
  141. package/src/docker/compose-health.test.ts +27 -0
  142. package/src/docker/compose-health.ts +19 -3
  143. package/src/docker/docker.test.ts +4 -1
  144. package/src/docker/dockerfile.ts +1 -1
  145. package/src/docker/recipes/pgdog.ts +2 -0
  146. package/src/drivers/ai-anthropic.ts +5 -0
  147. package/src/drivers/ai-ollama.ts +49 -30
  148. package/src/drivers/ai-openai-compatible.ts +57 -46
  149. package/src/drivers/ai-providers.test.ts +3 -0
  150. package/src/drivers/bun-native-completeness.test.ts +7 -9
  151. package/src/drivers/clock-postgres.ts +13 -9
  152. package/src/drivers/instances-postgres.ts +4 -6
  153. package/src/drivers/journal-postgres.ts +13 -9
  154. package/src/drivers/pg-rls.test.ts +87 -0
  155. package/src/drivers/pg-rls.ts +233 -7
  156. package/src/drivers/pglite.ts +26 -3
  157. package/src/drivers/postgres.test.ts +23 -0
  158. package/src/drivers/postgres.ts +133 -9
  159. package/src/drivers/redis.ts +11 -4
  160. package/src/drivers/signal-redis.ts +24 -14
  161. package/src/drivers/signal-types.ts +2 -1
  162. package/src/drivers/types.ts +10 -0
  163. package/src/elements/ai/declare.ts +109 -0
  164. package/src/elements/ai/errors.test.ts +5 -1
  165. package/src/elements/ai/errors.ts +30 -2
  166. package/src/elements/ai/eval.ts +4 -6
  167. package/src/elements/ai/mcp-client.test.ts +206 -0
  168. package/src/elements/ai/mcp-client.ts +362 -0
  169. package/src/elements/ai/mcp-http.ts +159 -0
  170. package/src/elements/ai/mcp-mock.ts +134 -0
  171. package/src/elements/ai/mcp-protocol.ts +234 -0
  172. package/src/elements/ai/mcp-stdio.test.ts +50 -0
  173. package/src/elements/ai/mcp-stdio.ts +212 -0
  174. package/src/elements/ai/mcp-transport.ts +70 -0
  175. package/src/elements/ai/runtime.ts +159 -29
  176. package/src/elements/ai.test.ts +139 -0
  177. package/src/elements/ai.ts +16 -0
  178. package/src/elements/clock/declare.ts +26 -1
  179. package/src/elements/clock/health.test.ts +43 -0
  180. package/src/elements/clock/runtime.ts +55 -2
  181. package/src/elements/clock/schedule.ts +71 -142
  182. package/src/elements/clock.test.ts +9 -40
  183. package/src/elements/clock.ts +6 -1
  184. package/src/elements/gate/boot.ts +5 -5
  185. package/src/elements/gate/declare.ts +36 -4
  186. package/src/elements/gate/flatten.ts +1 -1
  187. package/src/elements/gate/runtime.ts +1 -1
  188. package/src/elements/index.ts +2 -0
  189. package/src/elements/signal/declare.ts +2 -1
  190. package/src/elements/signal/runtime.ts +16 -1
  191. package/src/elements/signal.ts +1 -0
  192. package/src/elements/store/cache.test.ts +2 -0
  193. package/src/elements/store/cache.ts +3 -3
  194. package/src/elements/store/declare.ts +9 -6
  195. package/src/elements/store/emit-drizzle.ts +44 -8
  196. package/src/elements/store/resource.test.ts +1 -1
  197. package/src/elements/store/rls-identity.test.ts +95 -0
  198. package/src/elements/store/rls-identity.ts +116 -0
  199. package/src/elements/store/runtime.ts +4 -0
  200. package/src/elements/store/schema-decl.test.ts +37 -0
  201. package/src/elements/store/schema-decl.ts +161 -0
  202. package/src/elements/store/seed.ts +1 -1
  203. package/src/elements/store/sql-condition.test.ts +12 -0
  204. package/src/elements/store/sql-condition.ts +26 -2
  205. package/src/elements/store/sql-rls-isolation.test.ts +165 -0
  206. package/src/elements/store/sql-rls-stamp.test.ts +117 -0
  207. package/src/elements/store/sql-session.ts +81 -3
  208. package/src/elements/store/upsert-app.test.ts +1 -1
  209. package/src/elements/store.ts +14 -0
  210. package/src/elements/vault/builtin-adapter.ts +17 -0
  211. package/src/full.ts +2 -0
  212. package/src/http.ts +20 -3
  213. package/src/index.ts +14 -1
  214. package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
  215. package/src/kernel/adopt-routes.ts +30 -2
  216. package/src/kernel/app.ts +227 -77
  217. package/src/kernel/auto-cache.test.ts +8 -8
  218. package/src/kernel/auto-registry.test.ts +54 -9
  219. package/src/kernel/boot-bind/ai.ts +24 -0
  220. package/src/kernel/boot-bind/clock.ts +2 -0
  221. package/src/kernel/boot.test.ts +4 -4
  222. package/src/kernel/boot.ts +5 -3
  223. package/src/kernel/effects-stamping.test.ts +3 -3
  224. package/src/kernel/element-registries.ts +15 -5
  225. package/src/kernel/errors.ts +24 -0
  226. package/src/kernel/flow-units.ts +64 -0
  227. package/src/kernel/flow.ts +29 -17
  228. package/src/kernel/fx-dead-letters.test.ts +77 -0
  229. package/src/kernel/fx.test.ts +22 -0
  230. package/src/kernel/fx.ts +135 -6
  231. package/src/kernel/horizontal-child.ts +4 -4
  232. package/src/kernel/http-path-pending.ts +20 -0
  233. package/src/kernel/http-resource.ts +4 -0
  234. package/src/kernel/http-stream.test.ts +174 -0
  235. package/src/kernel/index.ts +2 -0
  236. package/src/kernel/pipeline.test.ts +2 -2
  237. package/src/kernel/plugin-elements.test.ts +1 -1
  238. package/src/kernel/plugin-needs.test.ts +1 -1
  239. package/src/kernel/ready.test.ts +2 -2
  240. package/src/kernel/resource-mount.test.ts +14 -0
  241. package/src/kernel/run-duration.test.ts +2 -2
  242. package/src/kernel/stamp-http.test.ts +79 -0
  243. package/src/kernel/stamp-http.ts +75 -0
  244. package/src/kernel/triggers.ts +52 -27
  245. package/src/manifest/mcp-ref.ts +88 -0
  246. package/src/manifest/types.ts +41 -2
  247. package/src/manifest/validate.test.ts +20 -0
  248. package/src/mcp/docs-server.ts +1 -1
  249. package/src/mcp/server.ts +1 -1
  250. package/src/plugins/compression.test.ts +21 -0
  251. package/src/plugins/compression.ts +1 -0
  252. package/src/release/measure.ts +2 -2
  253. package/src/runtime/bun.ts +41 -4
  254. package/src/runtime/json-code-block.test.ts +2 -2
  255. package/src/runtime/serve.test.ts +4 -4
  256. package/src/term.test.ts +52 -4
  257. package/src/term.ts +125 -17
  258. package/src/test/reset-element-registries.ts +10 -3
  259. package/src/cli/ask-seed.test.ts +0 -96
  260. package/src/cli/ask-seed.ts +0 -82
  261. package/src/console/ui-next/dist/assets/cache-glyph-CLPBqZeb.js +0 -1
  262. package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
  263. package/src/console/ui-next/dist/assets/flows-page-Dg8CTE29.js +0 -1
  264. package/src/console/ui-next/dist/assets/http-method-BJ92Z_ke.js +0 -1
  265. package/src/console/ui-next/dist/assets/index-Bp-R7jtM.js +0 -66
  266. package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
  267. package/src/console/ui-next/dist/assets/observability-page-DAnpEaq1.js +0 -4
  268. package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
  269. package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
  270. package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
  271. package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
  272. package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
  273. package/src/console/ui-next/dist/assets/skeleton-CL_X0GCj.js +0 -1
  274. package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
  275. package/src/console/ui-next/dist/assets/trace-detail-sheet-D16lWQMt.js +0 -2
  276. package/src/console/ui-next/dist/assets/units-page-BtQ0bqMe.js +0 -1
  277. package/src/console/ui-next/dist/assets/vault-page-Ca-MvcmJ.js +0 -2
@@ -37,18 +37,18 @@
37
37
  } catch (e) {}
38
38
  })();
39
39
  </script>
40
- <script type="module" crossorigin src="/assets/index-Bp-R7jtM.js"></script>
40
+ <script type="module" crossorigin src="/assets/index-Ca3HZMVq.js"></script>
41
41
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
42
42
  <link rel="modulepreload" crossorigin href="/assets/react-dom-Bph1y7z7.js">
43
43
  <link rel="modulepreload" crossorigin href="/assets/xyflow-D7n4g6go.js">
44
- <link rel="modulepreload" crossorigin href="/assets/shortcut-keys-DO4IsVqv.js">
45
- <link rel="modulepreload" crossorigin href="/assets/link-DF7SZ9Ek.js">
46
- <link rel="modulepreload" crossorigin href="/assets/skeleton-CL_X0GCj.js">
47
- <link rel="modulepreload" crossorigin href="/assets/react-B1ML8gxg.js">
44
+ <link rel="modulepreload" crossorigin href="/assets/shortcut-keys-CoqwEIi0.js">
45
+ <link rel="modulepreload" crossorigin href="/assets/link-COeyggt-.js">
46
+ <link rel="modulepreload" crossorigin href="/assets/skeleton-BHcfDYcb.js">
47
+ <link rel="modulepreload" crossorigin href="/assets/react-D8E3mtu1.js">
48
48
  <link rel="modulepreload" crossorigin href="/assets/preload-helper-oH4irX4C.js">
49
49
  <link rel="modulepreload" crossorigin href="/assets/shortcut-Cgzuv4k1.js">
50
50
  <link rel="stylesheet" crossorigin href="/assets/xyflow-DZ0Ws1xk.css">
51
- <link rel="stylesheet" crossorigin href="/assets/index-D4Ldtj79.css">
51
+ <link rel="stylesheet" crossorigin href="/assets/index-UtTDRQpS.css">
52
52
  </head>
53
53
  <body>
54
54
  <div id="root"></div>
@@ -17,6 +17,7 @@ import type { Manifest } from "../../manifest/types.ts";
17
17
  import {
18
18
  aiAgentRegistry,
19
19
  aiEmbedRegistry,
20
+ aiMcpServerRegistry,
20
21
  aiModelRegistry,
21
22
  aiPromptRegistry,
22
23
  channelTemplateRegistry,
@@ -92,6 +93,7 @@ function clearElementRegistries(): void {
92
93
  aiPromptRegistry.length = 0;
93
94
  aiEmbedRegistry.length = 0;
94
95
  aiAgentRegistry.length = 0;
96
+ aiMcpServerRegistry.length = 0;
95
97
  }
96
98
 
97
99
  const SeedIn = z.record(z.string(), z.unknown()).optional();
@@ -446,7 +446,7 @@ export type TracesReplayResult = {
446
446
  readonly ok: true;
447
447
  readonly rootId: string;
448
448
  readonly dryRun: boolean;
449
- readonly at: number;
449
+ readonly at: string;
450
450
  readonly flow: string;
451
451
  };
452
452
 
@@ -506,6 +506,11 @@ export type FlowsInvokeResult = {
506
506
  readonly flowId: string;
507
507
  readonly asUserId: string;
508
508
  readonly asGate?: string | null;
509
+ readonly rls?: {
510
+ readonly gate: string | null;
511
+ readonly userId: string;
512
+ readonly applied: boolean;
513
+ };
509
514
  readonly trigger: string;
510
515
  readonly response: unknown;
511
516
  /** True when classified PII keys were redacted. */
@@ -674,7 +679,7 @@ export type ClockRunNowResult = {
674
679
  readonly ok: true;
675
680
  readonly name: string;
676
681
  readonly ran: boolean;
677
- readonly at: number;
682
+ readonly at: string;
678
683
  };
679
684
 
680
685
  /**
@@ -721,6 +726,8 @@ export type StoreListChild = {
721
726
  readonly columnDescriptions: Readonly<Record<string, string>>;
722
727
  /** Live RLS when the engine reported `pg_class.relrowsecurity`. */
723
728
  readonly rls?: boolean;
729
+ /** Live + declared RLS policy count when the child is a SQL table. */
730
+ readonly rlsPolicyCount?: number;
724
731
  };
725
732
 
726
733
  /** One store row from `GET /console/store` (matches server `StoreListOut`). */
@@ -772,6 +779,8 @@ export type StoreQueryInput = {
772
779
  readonly topK?: number;
773
780
  /** When true, SQL browse returns PII cleartext (audited). Default masks. */
774
781
  readonly revealPii?: boolean;
782
+ readonly asGate?: string;
783
+ readonly asUserId?: string;
775
784
  };
776
785
 
777
786
  /** Success payload from `QUERY /console/store/query` (matches server `StoreQueryOut`). */
@@ -793,6 +802,11 @@ export type StoreQueryResult = {
793
802
  }>;
794
803
  readonly masked: boolean;
795
804
  readonly routedRole?: "primary" | "replica";
805
+ readonly rls?: {
806
+ readonly gate: string | null;
807
+ readonly userId: string;
808
+ readonly applied: boolean;
809
+ };
796
810
  };
797
811
 
798
812
  /**
@@ -822,7 +836,7 @@ export type StoreRevealInput = {
822
836
  export type StoreRevealResult = {
823
837
  readonly ok: true;
824
838
  readonly value: unknown;
825
- readonly at: number;
839
+ readonly at: string;
826
840
  };
827
841
 
828
842
  /**
@@ -851,6 +865,8 @@ export type StoreEditInput = {
851
865
  readonly confirmation?: string;
852
866
  readonly reason?: string;
853
867
  readonly commit?: boolean;
868
+ readonly asGate?: string;
869
+ readonly asUserId?: string;
854
870
  };
855
871
 
856
872
  /** Success payload from `POST /console/store/edit` (matches server). */
@@ -863,7 +879,12 @@ export type StoreEditResult = {
863
879
  readonly kind: "send" | "ask";
864
880
  readonly resource: string;
865
881
  }>;
866
- readonly at: number;
882
+ readonly at: string;
883
+ readonly rls?: {
884
+ readonly gate: string | null;
885
+ readonly userId: string;
886
+ readonly applied: boolean;
887
+ };
867
888
  };
868
889
 
869
890
  /**
@@ -907,7 +928,7 @@ export type StoreDeleteInput = {
907
928
  export type StoreDeleteResult = {
908
929
  readonly ok: true;
909
930
  readonly deleted: number;
910
- readonly at: number;
931
+ readonly at: string;
911
932
  };
912
933
 
913
934
  /**
@@ -934,6 +955,7 @@ export type StoreSqlInput = {
934
955
  readonly revealPii?: boolean;
935
956
  /** View rows as this Gate (`oke.gate` on postgres / pglite). */
936
957
  readonly asGate?: string;
958
+ readonly asUserId?: string;
937
959
  };
938
960
 
939
961
  /** Success payload from `POST /console/store/sql` (matches server `StoreSqlOut`). */
@@ -942,7 +964,13 @@ export type StoreSqlResult = {
942
964
  readonly masked: boolean;
943
965
  readonly routedRole: "primary" | "replica";
944
966
  readonly asGate: string | null;
967
+ readonly asUserId: string | null;
945
968
  readonly gateApplied: boolean;
969
+ readonly rls?: {
970
+ readonly gate: string | null;
971
+ readonly userId: string;
972
+ readonly applied: boolean;
973
+ };
946
974
  };
947
975
 
948
976
  /**
@@ -1267,7 +1295,7 @@ export type VaultWriteResult = {
1267
1295
  readonly ok: true;
1268
1296
  readonly name: string;
1269
1297
  readonly fingerprint: string | null;
1270
- readonly at: number;
1298
+ readonly at: string;
1271
1299
  };
1272
1300
 
1273
1301
  /**
@@ -1331,7 +1359,7 @@ export type VaultRotateMasterResult = {
1331
1359
  readonly kekVersion: number;
1332
1360
  readonly remaining: number;
1333
1361
  readonly masterKey: string | null;
1334
- readonly at: number;
1362
+ readonly at: string;
1335
1363
  };
1336
1364
 
1337
1365
  /**
@@ -61,6 +61,8 @@ export function Table<T>({
61
61
  onInsertColumn,
62
62
  onDeleteColumn,
63
63
  onRowDoubleClick,
64
+ onRowContextMenu,
65
+ onViewportContextMenu,
64
66
  cellRange = null,
65
67
  onCellRangeChange,
66
68
  onCellActivate,
@@ -192,6 +194,13 @@ export function Table<T>({
192
194
  onPointerMove={cellSelect.onScrollerPointerMove}
193
195
  onPointerUp={cellSelect.onPointerUp}
194
196
  onPointerCancel={cellSelect.onPointerUp}
197
+ onContextMenu={(event) => {
198
+ if (!onViewportContextMenu) return;
199
+ const target = event.target;
200
+ if (target instanceof Element && target.closest("[data-slot='table-row']")) return;
201
+ event.preventDefault();
202
+ onViewportContextMenu();
203
+ }}
195
204
  className={cn("overflow-auto", onCellRangeChange ? "select-none" : undefined)}
196
205
  style={{ height }}
197
206
  >
@@ -273,12 +282,19 @@ export function Table<T>({
273
282
  ref={(el) => {
274
283
  rowRefs.current[entry.id] = el;
275
284
  }}
285
+ data-slot="table-row"
276
286
  data-selected={isSelected}
277
287
  style={{ height: rowHeight }}
278
288
  onPointerEnter={
279
289
  hasRowMenu ? () => activateRow(entry.id, vItem.index) : undefined
280
290
  }
281
291
  onPointerLeave={hasRowMenu ? deactivateRow : undefined}
292
+ onContextMenu={(event) => {
293
+ if (!onRowContextMenu) return;
294
+ event.preventDefault();
295
+ event.stopPropagation();
296
+ onRowContextMenu(entry.row, entry.id);
297
+ }}
282
298
  onDoubleClick={(e) => {
283
299
  if (!onRowDoubleClick) return;
284
300
  const target = e.target;
@@ -78,6 +78,10 @@ export interface TableProps<T> {
78
78
  readonly onDeleteColumn?: (columnKey: string, index: number) => void;
79
79
  /** Double-click a body row. */
80
80
  readonly onRowDoubleClick?: (row: T, id: string) => void;
81
+ /** Two-finger / right-click a body row (caller should `preventDefault`). */
82
+ readonly onRowContextMenu?: (row: T, id: string) => void;
83
+ /** Two-finger / right-click empty viewport (not a row). */
84
+ readonly onViewportContextMenu?: () => void;
81
85
  /** Controlled cell range (visible row index × column index). */
82
86
  readonly cellRange?: CellRange | null;
83
87
  /** Enables drag / Shift+click cell-range selection. */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Keyboard key — shadcn Kbd. Tooltip content uses the inverted ink.
2
+ * Keyboard key — shadcn Kbd. Buttons inherit ink; tooltips invert.
3
3
  */
4
4
 
5
5
  import type { ComponentProps, JSX } from "react";
@@ -15,7 +15,7 @@ function Kbd({ className, ...props }: ComponentProps<"kbd">): JSX.Element {
15
15
  <kbd
16
16
  data-slot="kbd"
17
17
  className={cn(
18
- "pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-none bg-muted px-1 font-sans text-xs font-medium text-muted-foreground select-none in-data-[slot=tooltip-content]:bg-background/20 in-data-[slot=tooltip-content]:text-background dark:in-data-[slot=tooltip-content]:bg-background/10 [&_svg:not([class*='size-'])]:size-3",
18
+ "pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-none bg-muted px-1 font-sans text-xs font-medium text-muted-foreground select-none in-[.bg-primary]:bg-primary-foreground/20 in-[.bg-primary]:text-primary-foreground in-data-[slot=tooltip-content]:bg-background/20 in-data-[slot=tooltip-content]:text-background dark:in-data-[slot=tooltip-content]:bg-background/10 [&_svg:not([class*='size-'])]:size-3",
19
19
  className,
20
20
  )}
21
21
  {...props}
@@ -150,6 +150,24 @@ describe("buildFlowGraph", () => {
150
150
  expect(callersOfFlow(GRAPH_MANIFEST, "payments.chargeBooking")).toEqual([]);
151
151
  });
152
152
 
153
+ test("mcp: calls become one AI node per server with a call edge", () => {
154
+ const manifest: Manifest = {
155
+ ...GRAPH_MANIFEST,
156
+ flows: {
157
+ "support.triage": {
158
+ effects: { calls: ["mcp:github/create_issue"], asks: ["ticket-triage"] },
159
+ },
160
+ },
161
+ };
162
+ const { nodes, edges } = buildFlowGraph(manifest);
163
+ const byId = new Map(nodes.map((n) => [n.id, n]));
164
+ expect(byId.get("mcp:github")?.data).toMatchObject({ kind: "ai", label: "github" });
165
+ const edge = edges.find((e) => e.id === "e:flow:support.triage->mcp:github");
166
+ expect(edge?.data?.kind).toBe("calls");
167
+ expect(edge?.animated).toBe(true);
168
+ expect(nodes.some((n) => n.id === "flow:mcp:github/create_issue")).toBe(false);
169
+ });
170
+
153
171
  test("unit groups hug content bounds instead of a fixed dead-space box", () => {
154
172
  const unit = nodes.find((n) => n.id === "unit:fulfillment");
155
173
  const flow = nodes.find((n) => n.id === "flow:fulfillment.onOrder");
@@ -10,6 +10,7 @@
10
10
  import dagre from "@dagrejs/dagre";
11
11
  import { MarkerType, type Edge, type Node } from "@xyflow/react";
12
12
  import type { Flow, Manifest } from "../../../../../../manifest/types.ts";
13
+ import { mcpServerNodeId, parseMcpToolRef } from "../../../../../../manifest/mcp-ref.ts";
13
14
  import type { OkeElement } from "@/lib/element-icons.ts";
14
15
  import {
15
16
  EDGE_STROKE,
@@ -270,6 +271,14 @@ export function buildFlowGraph(manifest: Manifest | null | undefined): {
270
271
  edges.push(styledEdge(`e:flow:${flowId}->${ref}`, `flow:${flowId}`, ref, "secrets"));
271
272
  }
272
273
  for (const callee of e.calls ?? []) {
274
+ const mcp = parseMcpToolRef(callee);
275
+ if (mcp) {
276
+ const id = mcpServerNodeId(mcp.server);
277
+ targets.set(id, { id, kind: "ai", label: mcp.server });
278
+ effectCount.set(id, (effectCount.get(id) ?? 0) + 1);
279
+ edges.push(styledEdge(`e:flow:${flowId}->${id}`, `flow:${flowId}`, id, "calls", true));
280
+ continue;
281
+ }
273
282
  edges.push(
274
283
  styledEdge(
275
284
  `e:flow:${flowId}->flow:${callee}`,
@@ -349,7 +358,11 @@ function layoutWithDagre(
349
358
  for (const a of e.asks ?? []) link(`flow:${flowId}`, `ai:${a}`);
350
359
  for (const s of e.sends ?? []) link(`flow:${flowId}`, `channel:${s}`);
351
360
  for (const s of e.secrets ?? []) link(`flow:${flowId}`, `vault:${s}`);
352
- for (const callee of e.calls ?? []) link(`flow:${flowId}`, `flow:${callee}`);
361
+ for (const callee of e.calls ?? []) {
362
+ const mcp = parseMcpToolRef(callee);
363
+ if (mcp) link(`flow:${flowId}`, mcpServerNodeId(mcp.server));
364
+ else link(`flow:${flowId}`, `flow:${callee}`);
365
+ }
353
366
  }
354
367
  }
355
368
 
@@ -52,6 +52,23 @@ describe("sliceManifestForFocus", () => {
52
52
  expect(Object.keys(sliced.flows ?? {})).toEqual(["fulfillment.onOrder"]);
53
53
  });
54
54
 
55
+ test("mcp: callees are not treated as flow ids", () => {
56
+ const manifest = {
57
+ ...FLOWS_TEST_MANIFEST,
58
+ flows: {
59
+ ...FLOWS_TEST_MANIFEST.flows,
60
+ "support.triage": {
61
+ effects: { calls: ["mcp:github/create_issue"] },
62
+ },
63
+ },
64
+ };
65
+ const sliced = sliceManifestForFocus(manifest, { kind: "unit", unit: "support" });
66
+ expect(Object.keys(sliced.flows ?? {})).toEqual(["support.triage"]);
67
+ expect(
68
+ flowTouchesNode(manifest.flows!["support.triage"]!, "support.triage", "mcp:github"),
69
+ ).toBe(true);
70
+ });
71
+
55
72
  test("sliced Manifest lays out a small graph", () => {
56
73
  const sliced = sliceManifestForFocus(FLOWS_TEST_MANIFEST, {
57
74
  kind: "unit",
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import type { Flow, Manifest } from "../../../../../../manifest/types.ts";
7
+ import { mcpServerNodeId, parseMcpToolRef } from "../../../../../../manifest/mcp-ref.ts";
7
8
  import { unitOfFlowId } from "./build-flow-graph.ts";
8
9
 
9
10
  /** Focus that expands the detailed graph (not the bipartite map). */
@@ -29,13 +30,17 @@ export function sliceManifestForFocus(manifest: Manifest, focus: NeighborhoodFoc
29
30
  }
30
31
  const callees: string[] = [];
31
32
  for (const flowId of keep) {
32
- for (const callee of flows[flowId]?.effects?.calls ?? []) callees.push(callee);
33
+ for (const callee of flows[flowId]?.effects?.calls ?? []) {
34
+ if (!parseMcpToolRef(callee)) callees.push(callee);
35
+ }
33
36
  }
34
37
  for (const callee of callees) keep.add(callee);
35
38
  } else if (focus.kind === "flow") {
36
39
  keep.add(focus.flowId);
37
40
  const flow = flows[focus.flowId];
38
- for (const callee of flow?.effects?.calls ?? []) keep.add(callee);
41
+ for (const callee of flow?.effects?.calls ?? []) {
42
+ if (!parseMcpToolRef(callee)) keep.add(callee);
43
+ }
39
44
  for (const [id, other] of Object.entries(flows)) {
40
45
  if (other.effects?.calls?.includes(focus.flowId)) keep.add(id);
41
46
  }
@@ -77,6 +82,14 @@ export function flowTouchesNode(flow: Flow, flowId: string, nodeId: string): boo
77
82
  if (effects.sends?.some((name) => nodeId === `channel:${name}`)) return true;
78
83
  if (effects.asks?.some((name) => nodeId === `ai:${name}`)) return true;
79
84
  if (effects.secrets?.some((name) => nodeId === `vault:${name}`)) return true;
80
- if (effects.calls?.some((callee) => nodeId === `flow:${callee}`)) return true;
85
+ if (
86
+ effects.calls?.some((callee) => {
87
+ const mcp = parseMcpToolRef(callee);
88
+ if (mcp) return nodeId === mcpServerNodeId(mcp.server);
89
+ return nodeId === `flow:${callee}`;
90
+ })
91
+ ) {
92
+ return true;
93
+ }
81
94
  return false;
82
95
  }
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import type { RunEffect } from "@/client.ts";
12
+ import { parseMcpToolRef } from "../../../../../../manifest/mcp-ref.ts";
12
13
  import { ELEMENT_ICONS, type ElementHugeIcon } from "@/lib/element-icons.ts";
13
14
  import { EDGE_STROKE } from "../graph/flow-graph-theme.ts";
14
15
 
@@ -87,7 +88,8 @@ export function effectBarColor(kind: RunEffectKind): string {
87
88
  *
88
89
  * @param kind - Effect kind
89
90
  */
90
- export function effectKindIcon(kind: RunEffectKind): ElementHugeIcon {
91
+ export function effectKindIcon(kind: RunEffectKind, resource?: string): ElementHugeIcon {
92
+ if (resource && parseMcpToolRef(resource)) return ELEMENT_ICONS.ai.icon;
91
93
  switch (kind) {
92
94
  case "read":
93
95
  case "write":
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import type { RunEffect, RunRow } from "@/client.ts";
11
+ import { parseMcpToolRef } from "../../../../../../manifest/mcp-ref.ts";
11
12
  import { formatDuration } from "./format-duration.ts";
12
13
  import { effectKindSummaryLabel, type RunEffectKind } from "./effect-kind.ts";
13
14
 
@@ -164,6 +165,26 @@ export function effectSummaryChips(
164
165
  *
165
166
  * @param effect - Ledger entry
166
167
  */
168
+ /**
169
+ * Units / graph chip label for an `mcp:<server>/<tool>` call.
170
+ *
171
+ * @param ref - Effect resource
172
+ */
173
+ export function mcpCallChipLabel(ref: string): string | null {
174
+ const mcp = parseMcpToolRef(ref);
175
+ if (!mcp) return null;
176
+ return `Call ${mcp.server} → ${mcp.tool}`;
177
+ }
178
+
179
+ /**
180
+ * True when an effect targets an MCP tool (`mcp:…`).
181
+ *
182
+ * @param resource - Effect resource ref
183
+ */
184
+ export function isMcpResource(resource: string): boolean {
185
+ return parseMcpToolRef(resource) !== null;
186
+ }
187
+
167
188
  export function effectEventLabel(effect: Pick<RunEffect, "kind" | "resource">): string {
168
189
  if (isComputedCacheKey(effect.resource)) {
169
190
  return "Cache read";
@@ -171,6 +192,9 @@ export function effectEventLabel(effect: Pick<RunEffect, "kind" | "resource">):
171
192
  if (isSqlResource(effect.resource)) {
172
193
  return effect.kind === "write" ? "DB write" : "DB query";
173
194
  }
195
+ if (effect.kind === "call" && isMcpResource(effect.resource)) {
196
+ return "MCP call";
197
+ }
174
198
  const labels: Record<RunEffectKind, string> = {
175
199
  read: "Read",
176
200
  write: "Write",
@@ -18,6 +18,25 @@ import type { ElementHugeIcon } from "@/lib/element-icons.ts";
18
18
  /** Known HTTP methods from Manifest `HttpTrigger.method`. */
19
19
  export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS" | "QUERY";
20
20
 
21
+ /**
22
+ * Whether the verb carries a JSON request body on the wire.
23
+ *
24
+ * Matches the HTTP compiler (`GET` / `HEAD` / `DELETE` are path-or-query
25
+ * only) and the typed client (`GET` / `HEAD` leftover fields → query).
26
+ *
27
+ * @param method - HTTP method string (case-insensitive) or null
28
+ */
29
+ export function httpMethodHasBody(method: string | null | undefined): boolean {
30
+ switch ((method ?? "").toUpperCase()) {
31
+ case "GET":
32
+ case "HEAD":
33
+ case "DELETE":
34
+ return false;
35
+ default:
36
+ return true;
37
+ }
38
+ }
39
+
21
40
  /**
22
41
  * Units-tree display rank: GET → POST → QUERY → PATCH/PUT → DELETE.
23
42
  *
@@ -504,7 +504,7 @@ export function TraceDetailSheet({
504
504
  )}
505
505
  />
506
506
  <div className="flex items-center gap-2">
507
- <EffectKindGlyph kind={effect.kind} />
507
+ <EffectKindGlyph kind={effect.kind} resource={effect.resource} />
508
508
  <span className="w-[4.5rem] shrink-0 font-medium text-foreground/90">
509
509
  {effectEventLabel(effect)}
510
510
  </span>
@@ -565,11 +565,17 @@ export function TraceDetailSheet({
565
565
  *
566
566
  * @param props - Effect kind
567
567
  */
568
- function EffectKindGlyph({ kind }: { readonly kind: RunEffectKind }): JSX.Element {
568
+ function EffectKindGlyph({
569
+ kind,
570
+ resource,
571
+ }: {
572
+ readonly kind: RunEffectKind;
573
+ readonly resource?: string;
574
+ }): JSX.Element {
569
575
  const color = effectBarColor(kind);
570
576
  return (
571
577
  <span className={EXPLORER_ICON_CLASS} style={{ color }} aria-hidden>
572
- <HugeiconsIcon icon={effectKindIcon(kind)} className="size-3.5" />
578
+ <HugeiconsIcon icon={effectKindIcon(kind, resource)} className="size-3.5" />
573
579
  </span>
574
580
  );
575
581
  }
@@ -2,12 +2,13 @@
2
2
  * Unit tests for effect summary chips and request metadata helpers.
3
3
  */
4
4
 
5
- import { effectSummaryChips, effectEventLabel } from "./effect-summary.ts";
5
+ import { effectSummaryChips, effectEventLabel, mcpCallChipLabel } from "./effect-summary.ts";
6
6
  import { effectBarColor, effectKindIcon, effectKindSummaryLabel } from "./effect-kind.ts";
7
7
  import { ELEMENT_ICONS } from "@/lib/element-icons.ts";
8
8
  import { EDGE_STROKE } from "../graph/flow-graph-theme.ts";
9
9
  import {
10
10
  httpMethodBadgeClass,
11
+ httpMethodHasBody,
11
12
  httpMethodIcon,
12
13
  httpMethodRailClass,
13
14
  httpMethodSortRank,
@@ -110,6 +111,23 @@ describe("effectKindIcon", () => {
110
111
  });
111
112
  });
112
113
 
114
+ describe("httpMethodHasBody", () => {
115
+ test("GET, HEAD, and DELETE are path/query only", () => {
116
+ expect(httpMethodHasBody("GET")).toBe(false);
117
+ expect(httpMethodHasBody("HEAD")).toBe(false);
118
+ expect(httpMethodHasBody("DELETE")).toBe(false);
119
+ expect(httpMethodHasBody("get")).toBe(false);
120
+ });
121
+
122
+ test("POST, PUT, PATCH, QUERY, and OPTIONS carry JSON", () => {
123
+ expect(httpMethodHasBody("POST")).toBe(true);
124
+ expect(httpMethodHasBody("PUT")).toBe(true);
125
+ expect(httpMethodHasBody("PATCH")).toBe(true);
126
+ expect(httpMethodHasBody("QUERY")).toBe(true);
127
+ expect(httpMethodHasBody("OPTIONS")).toBe(true);
128
+ });
129
+ });
130
+
113
131
  describe("httpMethodBadgeClass", () => {
114
132
  test("assigns distinct accents per method", () => {
115
133
  expect(httpMethodBadgeClass("GET")).toContain("emerald");
@@ -305,6 +323,15 @@ describe("effectEventLabel", () => {
305
323
  expect(effectEventLabel({ kind: "read", resource: "sql:bookings" })).toBe("DB query");
306
324
  expect(effectEventLabel({ kind: "write", resource: "sql:bookings" })).toBe("DB write");
307
325
  expect(effectEventLabel({ kind: "emit", resource: "order-placed" })).toBe("Emit");
326
+ expect(effectEventLabel({ kind: "call", resource: "mcp:github/create_issue" })).toBe(
327
+ "MCP call",
328
+ );
329
+ });
330
+
331
+ test("mcp call chips and glyphs", () => {
332
+ expect(mcpCallChipLabel("mcp:github/create_issue")).toBe("Call github → create_issue");
333
+ expect(effectKindIcon("call", "mcp:github/create_issue")).toBe(ELEMENT_ICONS.ai.icon);
334
+ expect(effectKindIcon("call")).toBe(ELEMENT_ICONS.flow.icon);
308
335
  });
309
336
 
310
337
  test("labels computed: cache keys as Cache read, not DB query", () => {
@@ -343,7 +370,7 @@ describe("Sheet Replay path", () => {
343
370
  ok: true as const,
344
371
  rootId: run.id,
345
372
  dryRun: false,
346
- at: 1,
373
+ at: "2026-08-21T09:36:51.525Z",
347
374
  flow: run.flow,
348
375
  },
349
376
  error: null,
@@ -15,6 +15,7 @@ import {
15
15
  EXPLORER_STRIP_TOKEN_IDLE_CLASS,
16
16
  EXPLORER_TOOLBAR_CLASS,
17
17
  } from "@/components/explorer/explorer-chrome.ts";
18
+ import { AgentDisclosure } from "@/components/agents/agent-disclosure.tsx";
18
19
  import { ExplorerSearch } from "@/components/explorer/explorer-search.tsx";
19
20
  import {
20
21
  Empty,
@@ -236,10 +237,16 @@ export function TracesPane({
236
237
  ) : null}
237
238
  </div>
238
239
 
239
- {runs.length > 0 && advancedOpen ? (
240
- <div id="traces-advanced-panel">
240
+ {runs.length > 0 ? (
241
+ <AgentDisclosure
242
+ id="traces-advanced-panel"
243
+ role="region"
244
+ aria-label="Advanced filters"
245
+ open={advancedOpen}
246
+ className="shrink-0"
247
+ >
241
248
  <AdvancedFilters query={filters.advanced} runs={runs} onChange={setAdvanced} />
242
- </div>
249
+ </AgentDisclosure>
243
250
  ) : null}
244
251
 
245
252
  <div className="flex min-h-0 flex-1 flex-col overflow-y-auto">
@@ -44,4 +44,29 @@ describe("askCountInWindow", () => {
44
44
  windowMs: 60_000,
45
45
  });
46
46
  });
47
+
48
+ test("counts MCP call effects next to asks", () => {
49
+ const now = 1_000;
50
+ const runs = [
51
+ monitoringRun({
52
+ id: "a",
53
+ flow: "x",
54
+ startedAt: now - 10,
55
+ effects: [
56
+ {
57
+ kind: "call",
58
+ resource: "mcp:github/create_issue",
59
+ timestamp: now - 10,
60
+ duration: 8,
61
+ reversibility: "irreversible",
62
+ },
63
+ ],
64
+ }),
65
+ ];
66
+ expect(askCountInWindow(runs, now, 60_000)).toEqual({
67
+ kind: "summary",
68
+ asks: 1,
69
+ windowMs: 60_000,
70
+ });
71
+ });
47
72
  });
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import type { RunRow } from "@/client.ts";
6
+ import { parseMcpToolRef } from "../../../../../../manifest/mcp-ref.ts";
6
7
  import { runsInWindow } from "./window-stats.ts";
7
8
 
8
9
  /** Honest empty — no ask effects in the window. */
@@ -36,7 +37,9 @@ export function askCountInWindow(
36
37
  let asks = 0;
37
38
  for (const run of inWindow) {
38
39
  for (const effect of run.effects) {
39
- if (effect.kind === "ask") asks += 1;
40
+ if (effect.kind === "ask" || (effect.kind === "call" && parseMcpToolRef(effect.resource))) {
41
+ asks += 1;
42
+ }
40
43
  }
41
44
  }
42
45
  if (asks === 0) return { kind: "empty" };