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
package/src/cli/dev.ts CHANGED
@@ -31,8 +31,8 @@ import {
31
31
  createAnchoredBoard,
32
32
  createBootProgress,
33
33
  devStatusFromAiPhase,
34
- formatAppReadyLine,
35
34
  formatBootWarn,
35
+ formatBoundSurfaces,
36
36
  formatClaimNote,
37
37
  formatCliChrome,
38
38
  formatDevBanner,
@@ -83,7 +83,7 @@ export interface DevSurfaceHandle {
83
83
 
84
84
  /** App surface returned by {@link DevOptions.startApp}. */
85
85
  export interface DevAppHandle {
86
- readonly stop: () => void;
86
+ readonly stop: () => void | Promise<void>;
87
87
  /** Bound listen port (set when serve chooses an ephemeral port). */
88
88
  readonly port?: number;
89
89
  /** Base URL of the app server. */
@@ -181,12 +181,9 @@ export interface DevOptions {
181
181
  /**
182
182
  * Injectable `.adopt()` barrel regeneration (tests). Default: real
183
183
  * `generateAdoptBarrel` + atomic write to `<cwd>/src/flows/generated.ts`
184
- * (`generated.ts.tmp` → rename). Runs once per `oke dev` session, before
185
- * the entry is resolved/imported a real file on disk, so `oke dev`'s
186
- * runtime `import()` and `oke build`'s `Bun.build()` resolve it
187
- * identically (unlike a virtual-module Bun plugin, which does not — see
188
- * `compiler/generate-adopt.ts`). Atomic so a concurrent import during
189
- * `bun --hot` never sees a torn mid-write file.
184
+ * (`generated.ts.tmp` → rename). Runs at session start and again on
185
+ * `src/flows/**` changes except `generated.ts` itself. Write-if-changed
186
+ * so the `src/` watcher + bun `--hot` do not loop.
190
187
  *
191
188
  * @param cwd - Project root
192
189
  */
@@ -481,6 +478,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
481
478
  files: readonly string[];
482
479
  cwd: string;
483
480
  env: Record<string, string>;
481
+ aiServiceName?: string;
484
482
  } | null = null;
485
483
  let stackSqlDriver = "postgres";
486
484
  let stackKvDriver = "redis";
@@ -658,6 +656,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
658
656
  files: composeFiles,
659
657
  cwd: dockerOut,
660
658
  env: { ...stackEnv! },
659
+ aiServiceName: derived.specs.find((s) => s.role === "ai")?.serviceName,
661
660
  };
662
661
 
663
662
  // Stream compose health until infra is up (AI may stay pending — model load).
@@ -942,7 +941,9 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
942
941
  return { code: 0, plan };
943
942
  }
944
943
 
944
+ let boardPaused = false;
945
945
  const repaintBoard = (aiStatus?: DevStatus): void => {
946
+ if (boardPaused) return;
946
947
  bootBoard.paint(paintBootBoard(aiStatus ?? heroAiStatus));
947
948
  };
948
949
 
@@ -1026,15 +1027,6 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1026
1027
  `oke db push (dev · ${result.dialect}) ${result.code === 0 ? "ok" : "failed"}`,
1027
1028
  ),
1028
1029
  );
1029
- if (result.code === 0) {
1030
- const { maybeAskSeed } = await import("./ask-seed.ts");
1031
- await maybeAskSeed({
1032
- cwd,
1033
- env: configEnv,
1034
- write,
1035
- stdinIsTTY: options.stdinIsTTY ?? process.stdin.isTTY,
1036
- });
1037
- }
1038
1030
  } catch (err) {
1039
1031
  write(await formatDevSchemaSyncFailure(cwd, err, "boot"));
1040
1032
  }
@@ -1323,14 +1315,14 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1323
1315
  write(formatBootWarn(notice));
1324
1316
  }
1325
1317
  bootNotices.length = 0;
1326
- write(formatAppReadyLine(`http://127.0.0.1:${boundAppPort}`));
1327
- write(formatServiceLine("Console", `http://127.0.0.1:${boundConsolePort}`));
1328
- if (mcpServer) {
1329
- write(formatServiceLine("MCP", `http://127.0.0.1:${boundMcpPort}`));
1330
- }
1331
- if (docsMcpServer) {
1332
- write(formatServiceLine("Docs MCP", `http://127.0.0.1:${boundDocsMcpPort}`));
1333
- }
1318
+ write(
1319
+ formatBoundSurfaces({
1320
+ appUrl: `http://127.0.0.1:${boundAppPort}`,
1321
+ consoleUrl: `http://127.0.0.1:${boundConsolePort}`,
1322
+ mcpUrl: mcpServer ? `http://127.0.0.1:${boundMcpPort}` : null,
1323
+ docsMcpUrl: docsMcpServer ? `http://127.0.0.1:${boundDocsMcpPort}` : null,
1324
+ }),
1325
+ );
1334
1326
  write(formatDevLogSeparator());
1335
1327
  chromeReady = true;
1336
1328
  };
@@ -1358,6 +1350,10 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1358
1350
  const watchFs: DevWatchFn =
1359
1351
  options.watchFs ?? ((path, watchOptions, listener) => watch(path, watchOptions, listener));
1360
1352
  const watcher = watchFs(resolve(cwd, "src"), { recursive: true }, (_event, filename) => {
1353
+ const rel = (filename?.toString() ?? "").replace(/\\/g, "/");
1354
+ if (isFlowsTreeWatchPath(rel)) {
1355
+ void syncAdoptBarrel(cwd);
1356
+ }
1361
1357
  void regen(appUrl);
1362
1358
  // Only live-extract when the host did not pin a Manifest (tests).
1363
1359
  if (options.manifest === undefined) {
@@ -1388,7 +1384,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1388
1384
  const host = attachedHost;
1389
1385
  attachedHost = null;
1390
1386
  void host?.stop();
1391
- app.stop();
1387
+ void Promise.resolve(app.stop());
1392
1388
  consoleServer.stop();
1393
1389
  mcpServer?.stop();
1394
1390
  docsMcpServer?.stop();
@@ -1489,6 +1485,25 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1489
1485
  await syncComposeBoard();
1490
1486
  paintReadyChrome();
1491
1487
  };
1488
+ const waitComposeReady = async (): Promise<void> => {
1489
+ const { watchComposeHealth } = await import("../docker/compose-health.ts");
1490
+ const aiServiceName = started.aiServiceName;
1491
+ liveComposeHealth = await watchComposeHealth({
1492
+ files: started.files,
1493
+ cwd: started.cwd,
1494
+ env: started.env,
1495
+ run: composeHealthRun,
1496
+ timeoutMs: 20_000,
1497
+ isDone: (map) => {
1498
+ if (map.size === 0) return true;
1499
+ return [...map.entries()].every(
1500
+ ([name, s]) => name === aiServiceName || s === "ready" || s === "error",
1501
+ );
1502
+ },
1503
+ });
1504
+ if (liveComposeHealth.get("ai") === "error") heroAiStatus = "error";
1505
+ };
1506
+
1492
1507
  const composeAction = async (action: DevComposeControlAction): Promise<void> => {
1493
1508
  const files = started.files;
1494
1509
  const finalArgs =
@@ -1510,6 +1525,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1510
1525
  const detail = [stdout.trim(), stderr.trim()].filter(Boolean).join("\n");
1511
1526
  throw new Error(`docker compose ${action} exited ${code}` + (detail ? `\n${detail}` : ""));
1512
1527
  }
1528
+ if (action === "up") await waitComposeReady();
1513
1529
  await syncComposeBoard();
1514
1530
  };
1515
1531
 
@@ -1517,6 +1533,25 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1517
1533
  const { launchDevLiveControls } = await import("./tui/DevLive.tsx");
1518
1534
  await launchDevLiveControls({
1519
1535
  onRefresh: () => refreshChrome(),
1536
+ onSeed: async () => {
1537
+ const { runSlashCli } = await import("./tui/slash-run.ts");
1538
+ boardPaused = true;
1539
+ const lines: string[] = [];
1540
+ try {
1541
+ const code = await runSlashCli(cwd, ["db", "seed", "--force"], (line) => {
1542
+ lines.push(line);
1543
+ write(line);
1544
+ });
1545
+ if (code === 0) return;
1546
+ const last = [...lines]
1547
+ .reverse()
1548
+ .map((line) => line.trim())
1549
+ .find((line) => line.length > 0 && !line.startsWith("✗") && !line.startsWith("$"));
1550
+ throw new Error(last || "oke db seed failed");
1551
+ } finally {
1552
+ boardPaused = false;
1553
+ }
1554
+ },
1520
1555
  onComposeUp: () => composeAction("up"),
1521
1556
  onComposeStop: () => composeAction("stop"),
1522
1557
  onQuit: () => {
@@ -1589,6 +1624,20 @@ function isSchemaDeclDefinitionError(err: unknown, declarePath: string): boolean
1589
1624
  return declarePath.length > 0 && blob.includes(declarePath);
1590
1625
  }
1591
1626
 
1627
+ /**
1628
+ * True when a `src/` watcher event is a flows tree change that should
1629
+ * regenerate `generated.ts` (never the generated file itself).
1630
+ *
1631
+ * @param rel - Watcher filename (POSIX or Windows)
1632
+ */
1633
+ export function isFlowsTreeWatchPath(rel: string): boolean {
1634
+ const posix = rel.replace(/\\/g, "/");
1635
+ if (!posix.includes("flows/")) return false;
1636
+ const base = posix.split("/").pop() ?? posix;
1637
+ if (base === "generated.ts" || base === "generated.ts.tmp") return false;
1638
+ return true;
1639
+ }
1640
+
1592
1641
  /**
1593
1642
  * Frame a schema-sync failure for the `oke dev` loop: warn loud for developer
1594
1643
  * bugs, soft-skip only known environmental gaps. Never crashes the session.
@@ -1652,6 +1701,29 @@ async function tryLoadProjectManifest(cwd: string): Promise<Manifest | null | un
1652
1701
  return extracted;
1653
1702
  }
1654
1703
 
1704
+ /**
1705
+ * SIGKILL a pid and every child (`bun --hot` leaves a worker).
1706
+ *
1707
+ * @param pid - Root pid
1708
+ */
1709
+ function killProcessTree(pid: number): void {
1710
+ const listed = Bun.spawnSync(["pgrep", "-P", String(pid)], {
1711
+ stdout: "pipe",
1712
+ stderr: "pipe",
1713
+ });
1714
+ if (listed.exitCode === 0) {
1715
+ for (const line of listed.stdout.toString().split("\n")) {
1716
+ const child = Number(line.trim());
1717
+ if (Number.isInteger(child) && child > 0) killProcessTree(child);
1718
+ }
1719
+ }
1720
+ try {
1721
+ process.kill(pid, "SIGKILL");
1722
+ } catch {
1723
+ // Already gone.
1724
+ }
1725
+ }
1726
+
1655
1727
  /**
1656
1728
  * Default app boot: `bun --hot` on the shipped runner (socket-preserving).
1657
1729
  *
@@ -1687,17 +1759,18 @@ async function startAppHot(
1687
1759
  });
1688
1760
 
1689
1761
  let stopped = false;
1690
- const stop = () => {
1691
- if (stopped) return;
1762
+ const stop = (): Promise<void> => {
1763
+ if (stopped) return proc.exited.then(() => {});
1692
1764
  stopped = true;
1693
1765
  try {
1694
1766
  // SIGKILL so `bun --hot` cannot linger and hold the project directory
1695
1767
  // open (create-oke afterEach `rmSync` otherwise races the child exit).
1696
- proc.kill("SIGKILL");
1768
+ killProcessTree(proc.pid);
1697
1769
  } catch {
1698
1770
  // Inconsequential: process already exited — kill is idempotent best-effort.
1699
1771
  }
1700
1772
  void unlink(readyPath).catch(() => {});
1773
+ return proc.exited.then(() => {});
1701
1774
  };
1702
1775
 
1703
1776
  try {
@@ -24,7 +24,8 @@ export default defineConfig({
24
24
  dialect: "postgresql",
25
25
  schema: "./src/db/schema.drizzle.ts",
26
26
  out: "./src/db/migrations",
27
- // Core runtime tables (oke_crons, …) are created by drivers not domain schema.
27
+ // Domain tables live in public. Schema \`oke\` (RLS) and \`oke_console\` are engine-owned.
28
+ schemaFilter: ["public"],
28
29
  tablesFilter: ["!oke_*"],
29
30
  dbCredentials: {
30
31
  url: process.env.DATABASE_URL ?? process.env.OKE_STORE_SQL_URL!,
@@ -13,6 +13,7 @@ import { TUI_BRAND, TUI_HINT, TUI_MUTED, TUI_WARN } from "./theme.ts";
13
13
  /** Props for {@link DevLiveControls}. */
14
14
  export type DevLiveControlsProps = {
15
15
  readonly onRefresh: () => void | Promise<void>;
16
+ readonly onSeed: () => void | Promise<void>;
16
17
  readonly onComposeUp: () => void | Promise<void>;
17
18
  readonly onComposeStop: () => void | Promise<void>;
18
19
  readonly onQuit: () => void;
@@ -49,6 +50,14 @@ export function DevLiveControls(props: DevLiveControlsProps): ReactElement {
49
50
  .finally(() => setBusy(false));
50
51
  return;
51
52
  }
53
+ if (ctrl === "s") {
54
+ setBusy(true);
55
+ void Promise.resolve(props.onSeed())
56
+ .then(() => setStatus("seeded"))
57
+ .catch((err: unknown) => setStatus(err instanceof Error ? err.message : String(err)))
58
+ .finally(() => setBusy(false));
59
+ return;
60
+ }
52
61
  if (ctrl === "u" || ctrl === "x") {
53
62
  setBusy(true);
54
63
  const run = ctrl === "u" ? props.onComposeUp : props.onComposeStop;
@@ -66,13 +75,13 @@ export function DevLiveControls(props: DevLiveControlsProps): ReactElement {
66
75
  oke dev
67
76
  </Text>
68
77
  <Text color={TUI_MUTED}> · controls · </Text>
69
- <Text color={TUI_HINT}>? help · r refresh · q quit · u up · x stop</Text>
78
+ <Text color={TUI_HINT}>? help · r refresh · s seed · q quit · u up · x stop</Text>
70
79
  {busy ? <Text color={TUI_WARN}> · working…</Text> : null}
71
80
  {status ? <Text color={TUI_MUTED}>{` · ${status}`}</Text> : null}
72
81
  </Text>
73
82
  {help ? (
74
83
  <Text color={TUI_MUTED}>
75
- r clear/refresh board · q quit · u compose up · x compose stop
84
+ r clear/refresh board · s oke db seed · q quit · u compose up · x compose stop
76
85
  </Text>
77
86
  ) : null}
78
87
  </Box>
@@ -104,6 +113,7 @@ export async function launchDevLiveControls(props: DevLiveControlsProps): Promis
104
113
 
105
114
  const bound: DevLiveControlsProps = {
106
115
  onRefresh: () => afterStdout(props.onRefresh),
116
+ onSeed: () => afterStdout(props.onSeed),
107
117
  onComposeUp: () => afterStdout(props.onComposeUp),
108
118
  onComposeStop: () => afterStdout(props.onComposeStop),
109
119
  onQuit: props.onQuit,
@@ -2,10 +2,11 @@ import { describe, expect, test } from "bun:test";
2
2
  import { mapDevControlInput } from "./keys.ts";
3
3
 
4
4
  describe("mapDevControlInput", () => {
5
- test("maps ? r q u x and aliases", () => {
5
+ test("maps ? r s q u x and aliases", () => {
6
6
  expect(mapDevControlInput("?", {})).toBe("?");
7
7
  expect(mapDevControlInput("h", {})).toBe("?");
8
8
  expect(mapDevControlInput("r", {})).toBe("r");
9
+ expect(mapDevControlInput("s", {})).toBe("s");
9
10
  expect(mapDevControlInput("q", {})).toBe("q");
10
11
  expect(mapDevControlInput("u", {})).toBe("u");
11
12
  expect(mapDevControlInput("x", {})).toBe("x");
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Shared keyboard mapping for Ink `useInput` and pure tests.
3
- * Parity with legacy `parseDevControlKey` (`? r q u x`).
3
+ * Parity with legacy `parseDevControlKey` (`? r s q u x`).
4
4
  */
5
5
 
6
6
  /** Dev control action from a keypress. */
7
- export type DevControlKey = "?" | "r" | "q" | "u" | "x";
7
+ export type DevControlKey = "?" | "r" | "s" | "q" | "u" | "x";
8
8
 
9
9
  /**
10
10
  * Map Ink input (+ ctrl) to a control key.
@@ -20,6 +20,7 @@ export function mapDevControlInput(
20
20
  if (input === "?" || input === "h" || input === "H") return "?";
21
21
  if (input === "q" || input === "Q") return "q";
22
22
  if (input === "r" || input === "R") return "r";
23
+ if (input === "s" || input === "S") return "s";
23
24
  if (input === "u" || input === "U") return "u";
24
25
  if (input === "x" || input === "X") return "x";
25
26
  return null;
@@ -129,7 +129,7 @@ describe("typed error narrowing end-to-end", () => {
129
129
  const FlightFull = z.object({ seatsLeft: z.number() });
130
130
 
131
131
  on(
132
- http.post("/bookings").gate.public,
132
+ http.post("/bookings").public(),
133
133
  flow("bookings.create", {
134
134
  in: Booking,
135
135
  out: z.object({ id: z.string() }),
@@ -12,6 +12,7 @@ import type {
12
12
  ResourceRef,
13
13
  SecretRef,
14
14
  SignalRef,
15
+ SignalResourceRef,
15
16
  TemplateRef,
16
17
  FlowRef,
17
18
  } from "../manifest/types.ts";
@@ -56,6 +57,8 @@ export interface InferBinding {
56
57
  | "flow"
57
58
  | "embed"
58
59
  | "table"
60
+ | "mcp-server"
61
+ | "mcp-tool"
59
62
  | "unknown";
60
63
  /** Resolved resource / name. */
61
64
  readonly ref: string;
@@ -142,7 +145,7 @@ const TABLE_ARG_METHODS = new Set([
142
145
  * @param options - Handler AST, bindings, and annotation flag
143
146
  */
144
147
  export function inferEffects(options: InferEffectsOptions): InferredEffects {
145
- const reads = new Set<ResourceRef>();
148
+ const reads = new Set<ResourceRef | SignalResourceRef>();
146
149
  const writes = new Set<ResourceRef>();
147
150
  const emits = new Set<SignalRef>();
148
151
  const sends = new Set<TemplateRef>();
@@ -173,6 +176,12 @@ export function inferEffects(options: InferEffectsOptions): InferredEffects {
173
176
  continue;
174
177
  }
175
178
 
179
+ if (chain.rootMethod === "deadLetters" && call === chain.rootCall) {
180
+ const ref = resolveNamed(call.arguments[0], options.bindings, "signal");
181
+ if (ref) reads.add(`signal:${ref}`);
182
+ continue;
183
+ }
184
+
176
185
  if (chain.rootMethod === "send" && call === chain.rootCall) {
177
186
  const ref = resolveNamed(call.arguments[0], options.bindings, "template");
178
187
  if (ref) sends.add(ref);
@@ -203,7 +212,7 @@ export function inferEffects(options: InferEffectsOptions): InferredEffects {
203
212
  }
204
213
 
205
214
  if (chain.rootMethod === "call" && call === chain.rootCall) {
206
- const ref = resolveNamed(call.arguments[0], options.bindings, "flow");
215
+ const ref = resolveCallTarget(call.arguments[0], options.bindings);
207
216
  if (ref) calls.add(ref);
208
217
  continue;
209
218
  }
@@ -541,12 +550,58 @@ function toolsFromAskOptions(
541
550
  );
542
551
  const out: FlowRef[] = [];
543
552
  for (const el of els) {
544
- const ref = resolveNamed(el, bindings, "flow");
553
+ const ref = resolveCallTarget(el, bindings);
545
554
  if (ref) out.push(ref as FlowRef);
546
555
  }
547
556
  return out;
548
557
  }
549
558
 
559
+ /**
560
+ * Resolve `fx.call` / ask-tools: flow name, `mcp:` ref, or `server.tool("x")`.
561
+ *
562
+ * @param node - Argument AST
563
+ * @param bindings - Known bindings
564
+ */
565
+ export function resolveCallTarget(
566
+ node: AstNode | undefined,
567
+ bindings: ReadonlyMap<string, InferBinding>,
568
+ ): string | undefined {
569
+ const mcp = resolveMcpToolExpr(node, bindings);
570
+ if (mcp) return mcp;
571
+ return resolveNamed(node, bindings, "flow");
572
+ }
573
+
574
+ /**
575
+ * Resolve `server.tool("name")` or a bound `mcp-tool` identifier.
576
+ *
577
+ * @param node - AST
578
+ * @param bindings - Known bindings
579
+ */
580
+ export function resolveMcpToolExpr(
581
+ node: AstNode | undefined,
582
+ bindings: ReadonlyMap<string, InferBinding>,
583
+ ): string | undefined {
584
+ if (!node) return undefined;
585
+ if (node.type === "CallExpression") {
586
+ const callee = (node as CallExpression).callee;
587
+ if (callee.type === "MemberExpression") {
588
+ const member = callee as AstNode & { object: AstNode; property: AstNode };
589
+ const obj = identifierName(member.object);
590
+ const prop = identifierName(member.property);
591
+ const tool = stringArg((node as CallExpression).arguments[0]);
592
+ if (obj && prop === "tool" && tool) {
593
+ const server = bindings.get(obj);
594
+ if (server?.kind === "mcp-server") {
595
+ return `mcp:${server.ref}/${tool}`;
596
+ }
597
+ }
598
+ }
599
+ }
600
+ const binding = resolveBinding(node, bindings);
601
+ if (binding?.kind === "mcp-tool") return binding.ref;
602
+ return undefined;
603
+ }
604
+
550
605
  /**
551
606
  * String literal argument.
552
607
  *