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.
- package/manifest.v1.schema.json +7 -1
- package/package.json +4 -5
- package/site/content/docs/ai/mcp.mdx +27 -2
- package/site/content/docs/elements/ai.mdx +59 -11
- package/site/content/docs/elements/channel.mdx +1 -0
- package/site/content/docs/elements/clock.mdx +30 -12
- package/site/content/docs/elements/flow.mdx +9 -3
- package/site/content/docs/elements/gate.mdx +25 -22
- package/site/content/docs/elements/signal.mdx +71 -25
- package/site/content/docs/elements/store.mdx +87 -28
- package/site/content/docs/elements/vault.mdx +1 -0
- package/site/content/docs/get-started/basic-usage.mdx +22 -13
- package/site/content/docs/get-started/index.mdx +5 -0
- package/site/content/docs/get-started/installation.mdx +2 -2
- package/site/content/docs/get-started/introduction.mdx +11 -3
- package/site/content/docs/get-started/meta.json +1 -1
- package/site/content/docs/get-started/project-structure.mdx +932 -0
- package/site/content/docs/reference/cli.md +13 -10
- package/site/content/docs/reference/client.mdx +25 -21
- package/site/content/docs/reference/fx.mdx +27 -22
- package/site/content/docs/reference/security.md +1 -1
- package/src/auth/bindings.ts +14 -5
- package/src/auth/cross-plane.ts +10 -3
- package/src/cli/ai-setup/ai-setup.test.ts +62 -0
- package/src/cli/ai-setup/apply.ts +32 -1
- package/src/cli/ai-setup/recommend.test.ts +25 -0
- package/src/cli/ai-setup/recommend.ts +8 -3
- package/src/cli/attach-host-console.test.ts +3 -2
- package/src/cli/attach-host-console.ts +3 -0
- package/src/cli/build.test.ts +4 -2
- package/src/cli/db-seed.ts +48 -15
- package/src/cli/db.test.ts +104 -0
- package/src/cli/db.ts +122 -10
- package/src/cli/dev-controls.test.ts +20 -0
- package/src/cli/dev-controls.ts +13 -0
- package/src/cli/dev.test.ts +15 -4
- package/src/cli/dev.ts +102 -29
- package/src/cli/ensure-drizzle-config.ts +2 -1
- package/src/cli/tui/DevLive.tsx +12 -2
- package/src/cli/tui/keys.test.ts +2 -1
- package/src/cli/tui/keys.ts +3 -2
- package/src/compiler/aot.test.ts +1 -1
- package/src/compiler/effects-infer.ts +58 -3
- package/src/compiler/extract.test.ts +458 -29
- package/src/compiler/extract.ts +387 -69
- package/src/compiler/flow-path.test.ts +96 -0
- package/src/compiler/flow-path.ts +173 -0
- package/src/compiler/generate-adopt.test.ts +112 -3
- package/src/compiler/generate-adopt.ts +280 -26
- package/src/compiler/response.ts +45 -1
- package/src/compiler/schema-from-ast.ts +609 -0
- package/src/console/server/ai.ts +5 -2
- package/src/console/server/bind.ts +2 -2
- package/src/console/server/console.test.ts +3 -0
- package/src/console/server/flows.ts +121 -40
- package/src/console/server/invoke-user-flow.test.ts +3 -1
- package/src/console/server/invoke-user-flow.ts +17 -2
- package/src/console/server/iso-at.test.ts +9 -0
- package/src/console/server/iso-at.ts +11 -0
- package/src/console/server/security-headers.ts +6 -3
- package/src/console/server/serve.ts +17 -2
- package/src/console/server/spa-proxy.test.ts +1 -0
- package/src/console/server/sql-catalog.ts +147 -7
- package/src/console/server/sql-rls.test.ts +16 -0
- package/src/console/server/state.ts +8 -0
- package/src/console/server/store.test.ts +7 -4
- package/src/console/server/store.ts +142 -58
- package/src/console/ui-next/dist/assets/agent-disclosure-C0X1fbWF.js +1 -0
- package/src/console/ui-next/dist/assets/cache-glyph-F1FI122b.js +1 -0
- package/src/console/ui-next/dist/assets/{call-pii-button-bqkxMrJH.js → call-pii-button-DEDCl_j3.js} +1 -1
- package/src/console/ui-next/dist/assets/collapsible-LPqGvfoz.js +1 -0
- package/src/console/ui-next/dist/assets/{confirm-sheet-DDCRmG62.js → confirm-sheet-3ptDzXbz.js} +1 -1
- package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
- package/src/console/ui-next/dist/assets/{explorer-empty-HjTnVQoR.js → explorer-empty-BvYrygyO.js} +1 -1
- package/src/console/ui-next/dist/assets/flows-page-Dluo1Bd7.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-CS_O8L-r.js} +1 -1
- package/src/console/ui-next/dist/assets/http-method--sWDdXSB.js +1 -0
- package/src/console/ui-next/dist/assets/index-Ca3HZMVq.js +66 -0
- package/src/console/ui-next/dist/assets/index-UtTDRQpS.css +2 -0
- package/src/console/ui-next/dist/assets/{link-DF7SZ9Ek.js → link-COeyggt-.js} +1 -1
- package/src/console/ui-next/dist/assets/observability-page-HK9-BO8z.js +4 -0
- package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
- package/src/console/ui-next/dist/assets/replica-lag-DBIFFf7d.js +18 -0
- package/src/console/ui-next/dist/assets/sheet-form-Dnwa5oOR.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-CoqwEIi0.js +1 -0
- package/src/console/ui-next/dist/assets/skeleton-BHcfDYcb.js +1 -0
- package/src/console/ui-next/dist/assets/store-page-BSohH3wM.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CFIGRnnA.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-BgLl34w7.js +54 -0
- package/src/console/ui-next/dist/assets/units-page-B0cFE76A.js +1 -0
- package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-BLbZhzyF.js} +1 -1
- package/src/console/ui-next/dist/assets/vault-page-B1SbYe10.js +2 -0
- package/src/console/ui-next/dist/index.html +6 -6
- package/src/console/ui-next/seed-invoke-host.ts +2 -0
- package/src/console/ui-next/src/client.ts +35 -7
- package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
- package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
- package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
- package/src/console/ui-next/src/features/flows/graph/build-flow-graph.test.ts +18 -0
- package/src/console/ui-next/src/features/flows/graph/build-flow-graph.ts +14 -1
- package/src/console/ui-next/src/features/flows/graph/neighborhood.test.ts +17 -0
- package/src/console/ui-next/src/features/flows/graph/neighborhood.ts +16 -3
- package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +3 -1
- package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
- package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
- package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +9 -3
- package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +29 -2
- package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
- package/src/console/ui-next/src/features/observability/lib/ask-count.test.ts +25 -0
- package/src/console/ui-next/src/features/observability/lib/ask-count.ts +4 -1
- package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
- package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
- package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
- package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
- package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
- package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
- package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
- package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
- package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
- package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
- package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
- package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
- package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
- package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
- package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
- package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
- package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
- package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
- package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
- package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
- package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +12 -4
- package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
- package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
- package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
- package/src/docker/compose-health.test.ts +27 -0
- package/src/docker/compose-health.ts +19 -3
- package/src/docker/docker.test.ts +4 -1
- package/src/docker/dockerfile.ts +1 -1
- package/src/docker/recipes/pgdog.ts +2 -0
- package/src/drivers/ai-anthropic.ts +5 -0
- package/src/drivers/ai-ollama.ts +49 -30
- package/src/drivers/ai-openai-compatible.ts +57 -46
- package/src/drivers/ai-providers.test.ts +3 -0
- package/src/drivers/bun-native-completeness.test.ts +7 -9
- package/src/drivers/clock-postgres.ts +13 -9
- package/src/drivers/instances-postgres.ts +4 -6
- package/src/drivers/journal-postgres.ts +13 -9
- package/src/drivers/pg-rls.test.ts +87 -0
- package/src/drivers/pg-rls.ts +233 -7
- package/src/drivers/pglite.ts +26 -3
- package/src/drivers/postgres.test.ts +23 -0
- package/src/drivers/postgres.ts +133 -9
- package/src/drivers/redis.ts +11 -4
- package/src/drivers/signal-redis.ts +24 -14
- package/src/drivers/signal-types.ts +2 -1
- package/src/drivers/types.ts +10 -0
- package/src/elements/ai/declare.ts +109 -0
- package/src/elements/ai/errors.test.ts +5 -1
- package/src/elements/ai/errors.ts +30 -2
- package/src/elements/ai/eval.ts +4 -6
- package/src/elements/ai/mcp-client.test.ts +206 -0
- package/src/elements/ai/mcp-client.ts +362 -0
- package/src/elements/ai/mcp-http.ts +159 -0
- package/src/elements/ai/mcp-mock.ts +134 -0
- package/src/elements/ai/mcp-protocol.ts +234 -0
- package/src/elements/ai/mcp-stdio.test.ts +50 -0
- package/src/elements/ai/mcp-stdio.ts +212 -0
- package/src/elements/ai/mcp-transport.ts +70 -0
- package/src/elements/ai/runtime.ts +159 -29
- package/src/elements/ai.test.ts +139 -0
- package/src/elements/ai.ts +16 -0
- package/src/elements/clock/declare.ts +26 -1
- package/src/elements/clock/health.test.ts +43 -0
- package/src/elements/clock/runtime.ts +55 -2
- package/src/elements/clock/schedule.ts +71 -142
- package/src/elements/clock.test.ts +9 -40
- package/src/elements/clock.ts +6 -1
- package/src/elements/gate/boot.ts +5 -5
- package/src/elements/gate/declare.ts +36 -4
- package/src/elements/gate/flatten.ts +1 -1
- package/src/elements/gate/runtime.ts +1 -1
- package/src/elements/index.ts +2 -0
- package/src/elements/signal/declare.ts +2 -1
- package/src/elements/signal/runtime.ts +16 -1
- package/src/elements/signal.ts +1 -0
- package/src/elements/store/cache.test.ts +2 -0
- package/src/elements/store/cache.ts +3 -3
- package/src/elements/store/declare.ts +9 -6
- package/src/elements/store/emit-drizzle.ts +44 -8
- package/src/elements/store/resource.test.ts +1 -1
- package/src/elements/store/rls-identity.test.ts +95 -0
- package/src/elements/store/rls-identity.ts +116 -0
- package/src/elements/store/runtime.ts +4 -0
- package/src/elements/store/schema-decl.test.ts +37 -0
- package/src/elements/store/schema-decl.ts +161 -0
- package/src/elements/store/seed.ts +1 -1
- package/src/elements/store/sql-condition.test.ts +12 -0
- package/src/elements/store/sql-condition.ts +26 -2
- package/src/elements/store/sql-rls-isolation.test.ts +165 -0
- package/src/elements/store/sql-rls-stamp.test.ts +117 -0
- package/src/elements/store/sql-session.ts +81 -3
- package/src/elements/store/upsert-app.test.ts +1 -1
- package/src/elements/store.ts +14 -0
- package/src/elements/vault/builtin-adapter.ts +17 -0
- package/src/full.ts +2 -0
- package/src/http.ts +20 -3
- package/src/index.ts +14 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
- package/src/kernel/adopt-routes.ts +30 -2
- package/src/kernel/app.ts +227 -77
- package/src/kernel/auto-cache.test.ts +8 -8
- package/src/kernel/auto-registry.test.ts +54 -9
- package/src/kernel/boot-bind/ai.ts +24 -0
- package/src/kernel/boot-bind/clock.ts +2 -0
- package/src/kernel/boot.test.ts +4 -4
- package/src/kernel/boot.ts +5 -3
- package/src/kernel/effects-stamping.test.ts +3 -3
- package/src/kernel/element-registries.ts +15 -5
- package/src/kernel/errors.ts +24 -0
- package/src/kernel/flow-units.ts +64 -0
- package/src/kernel/flow.ts +29 -17
- package/src/kernel/fx-dead-letters.test.ts +77 -0
- package/src/kernel/fx.test.ts +22 -0
- package/src/kernel/fx.ts +135 -6
- package/src/kernel/horizontal-child.ts +4 -4
- package/src/kernel/http-path-pending.ts +20 -0
- package/src/kernel/http-resource.ts +4 -0
- package/src/kernel/http-stream.test.ts +174 -0
- package/src/kernel/index.ts +2 -0
- package/src/kernel/pipeline.test.ts +2 -2
- package/src/kernel/plugin-elements.test.ts +1 -1
- package/src/kernel/plugin-needs.test.ts +1 -1
- package/src/kernel/ready.test.ts +2 -2
- package/src/kernel/resource-mount.test.ts +14 -0
- package/src/kernel/run-duration.test.ts +2 -2
- package/src/kernel/stamp-http.test.ts +79 -0
- package/src/kernel/stamp-http.ts +75 -0
- package/src/kernel/triggers.ts +52 -27
- package/src/manifest/mcp-ref.ts +88 -0
- package/src/manifest/types.ts +41 -2
- package/src/manifest/validate.test.ts +20 -0
- package/src/mcp/docs-server.ts +1 -1
- package/src/mcp/server.ts +1 -1
- package/src/plugins/compression.test.ts +21 -0
- package/src/plugins/compression.ts +1 -0
- package/src/release/measure.ts +2 -2
- package/src/runtime/bun.ts +41 -4
- package/src/runtime/json-code-block.test.ts +2 -2
- package/src/runtime/serve.test.ts +4 -4
- package/src/term.test.ts +52 -4
- package/src/term.ts +125 -17
- package/src/test/reset-element-registries.ts +10 -3
- package/src/cli/ask-seed.test.ts +0 -96
- package/src/cli/ask-seed.ts +0 -82
- package/src/console/ui-next/dist/assets/cache-glyph-CLPBqZeb.js +0 -1
- package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-Dg8CTE29.js +0 -1
- package/src/console/ui-next/dist/assets/http-method-BJ92Z_ke.js +0 -1
- package/src/console/ui-next/dist/assets/index-Bp-R7jtM.js +0 -66
- package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
- package/src/console/ui-next/dist/assets/observability-page-DAnpEaq1.js +0 -4
- package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
- package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
- package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
- package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
- package/src/console/ui-next/dist/assets/skeleton-CL_X0GCj.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
- package/src/console/ui-next/dist/assets/trace-detail-sheet-D16lWQMt.js +0 -2
- package/src/console/ui-next/dist/assets/units-page-BtQ0bqMe.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-Ca-MvcmJ.js +0 -2
|
@@ -17,6 +17,7 @@ oke --help # command list (also what bare `oke` prints whe
|
|
|
17
17
|
|
|
18
18
|
oke dev # watch · hot reload · Console :6533 · app :6530 · MCP :6535 · docs MCP :6536
|
|
19
19
|
# → always Docker Compose under docker/ + host Bun
|
|
20
|
+
# → regenerates flows/generated.ts when the flows tree changes
|
|
20
21
|
# → client types + domain schema push on save
|
|
21
22
|
# → Console UI hot-reloads from source (okengine checkout; no bun run build)
|
|
22
23
|
oke dev --no-db-push # opt out of auto oke db push on schema input save
|
|
@@ -48,7 +49,7 @@ oke schema generate # core + plugin stubs → .oke/schema/oke.ts (-
|
|
|
48
49
|
oke db push # domain schema.ts → live DB (dev; drizzle-kit)
|
|
49
50
|
oke db generate # versioned SQL under drizzle/ (review)
|
|
50
51
|
oke db migrate # apply migrations (explicit; never auto in prod)
|
|
51
|
-
oke db seed # this app's defineSeed (name + essential + env)
|
|
52
|
+
oke db seed # this app's defineSeed (name + essential + env) — standard CLI path
|
|
52
53
|
oke db seed --env prod --force # CI: skip prod confirmation prompt
|
|
53
54
|
oke db studio # drizzle-kit Studio (long-running)
|
|
54
55
|
oke vault set STRIPE_KEY # also: list · import .env · key rotate
|
|
@@ -112,24 +113,26 @@ not spawn a second stack. Non-TTY / CI keeps the classic help listing
|
|
|
112
113
|
`oke dev` always starts Compose (requires a running Docker daemon). Boot chrome
|
|
113
114
|
prints immediately (wordmark + Starting + profile), then streams background work
|
|
114
115
|
(compose up, vault, per-service health, AI probe). Status ● is green ready ·
|
|
115
|
-
yellow pending/loading · red error · dim idle.
|
|
116
|
+
yellow pending/loading · red error · dim idle. After bind, Backend · Console ·
|
|
117
|
+
MCP · Docs MCP each get a box (URL + one-line purpose). Compose health keeps polling
|
|
116
118
|
(`docker compose ps -a`); AI ● tracks model phase while the AI container is up.
|
|
117
119
|
Boot does not wait for the model to become ready. A successful session writes
|
|
118
120
|
`.oke/dev.json` (pid · ports · startedAt) and clears it on stop. Durable local
|
|
119
|
-
markers live in `.oke/state.json`
|
|
120
|
-
prompt for this app's `defineSeed({ name })`) — not in the session lock. Console session signing uses
|
|
121
|
+
markers live in `.oke/state.json` — not in the session lock. Console session signing uses
|
|
121
122
|
`.oke/console.secret` (or `OKE_CONSOLE_SECRET`); that is not a Vault secret.
|
|
122
123
|
|
|
123
124
|
On a TTY, Ink keyboard controls stay active after boot (`useInput` — same
|
|
124
125
|
shortcuts as before). Press `r` to clear the log pane and reprint the latest
|
|
125
126
|
board.
|
|
126
127
|
|
|
127
|
-
| Key
|
|
128
|
-
|
|
|
129
|
-
| `?`
|
|
130
|
-
| `r`
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
128
|
+
| Key | Action |
|
|
129
|
+
| --- | ----------------------------------- |
|
|
130
|
+
| `?` | help |
|
|
131
|
+
| `r` | refresh — clear logs, show latest ● |
|
|
132
|
+
| `s` | `oke db seed` |
|
|
133
|
+
| `q` | quit `oke dev` |
|
|
134
|
+
| `u` | compose up (whole stack) |
|
|
135
|
+
| `x` | compose stop (whole stack) |
|
|
133
136
|
|
|
134
137
|
Compose stays quiet unless it fails. Process-local boot notices print once under
|
|
135
138
|
the hero (not once per process).
|
|
@@ -5,7 +5,7 @@ icon: "MonitorSmartphone"
|
|
|
5
5
|
source: "docs/spec/unified-theory.md"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
`okengine/client` is how a browser, CLI, or another service calls your app's flows.
|
|
8
|
+
`okengine/client` is how a browser, CLI, or another service calls your app's flows. Import the generated barrel, take `typeof app`, and `api.notes.get({ id })` is fully typed — same contracts the server already has, no separate schema project.
|
|
9
9
|
|
|
10
10
|
<Callout title="The one rule">
|
|
11
11
|
Treat every call as a result envelope: `{ data, error }`. Flow failures are values you switch on
|
|
@@ -21,19 +21,22 @@ source: "docs/spec/unified-theory.md"
|
|
|
21
21
|
### Adopt flows and export `App`
|
|
22
22
|
|
|
23
23
|
```typescript title="src/app.ts"
|
|
24
|
-
import
|
|
25
|
-
import
|
|
24
|
+
import "@/core";
|
|
25
|
+
import "@/flows/generated";
|
|
26
|
+
import { oke } from "okengine/http";
|
|
26
27
|
|
|
27
|
-
export const app = oke({ name: "
|
|
28
|
+
export const app = oke({ name: "notes" });
|
|
28
29
|
export type App = typeof app;
|
|
29
30
|
```
|
|
30
31
|
|
|
32
|
+
`.adopt({ notes })` is optional and additive.
|
|
33
|
+
|
|
31
34
|
</Step>
|
|
32
35
|
|
|
33
36
|
<Step>
|
|
34
37
|
### Create the client
|
|
35
38
|
|
|
36
|
-
Same repo — pass the app value so HTTP triggers hit REST (method + path from
|
|
39
|
+
Same repo — pass the app value so HTTP triggers hit REST (method + path from `$routes`):
|
|
37
40
|
|
|
38
41
|
```typescript title="client"
|
|
39
42
|
import { createClient } from "okengine/client";
|
|
@@ -114,7 +117,7 @@ oke client add https://api.example.com --out ./types/oke-client.d.ts
|
|
|
114
117
|
| Incomplete proxy path (`api.notes()` with no flow) | Result error: `Incomplete path: api.notes(…)` |
|
|
115
118
|
|
|
116
119
|
```typescript
|
|
117
|
-
// REST —
|
|
120
|
+
// REST — method/path from the bound HTTP trigger (file tree or explicit)
|
|
118
121
|
await api.notes.get({ id: "n_1" }); // GET /notes/n_1
|
|
119
122
|
|
|
120
123
|
// RPC — untriggered flow named notes.stats
|
|
@@ -210,16 +213,16 @@ core today: cookie jars or plugin `.client()` decorations. Browser apps: also se
|
|
|
210
213
|
outcome by calling a Flow that uses it — or by handling a gate denial on that call.
|
|
211
214
|
</Callout>
|
|
212
215
|
|
|
213
|
-
| Element | On the client | How
|
|
214
|
-
| --------------------------------- | ------------- |
|
|
215
|
-
| [Flow](/docs/elements/flow) | Direct | `api.unit.flow(input)` — the only public surface
|
|
216
|
-
| [Gate](/docs/elements/gate) | Indirect | Bearer via `auth`; denials as `Unauthorized` / `Forbidden` / `RateLimited`
|
|
217
|
-
| [Store](/docs/elements/store) | Via Flows | `fx.store` inside Flows; `store.resource` + `on(http.resource…)` → five Flows
|
|
218
|
-
| [Signal](/docs/elements/signal) | Via Flows | Emit/consume server-side; no subscribe API on `okengine/client` yet
|
|
219
|
-
| [Clock](/docs/elements/clock) | Via Flows | Schedules fire on the server — the client never ticks a clock
|
|
220
|
-
| [Vault](/docs/elements/vault) | Via Flows | Secrets stay server-side; never ship them to the browser package
|
|
221
|
-
| [Channel](/docs/elements/channel) | Via Flows | `fx.send` in a Flow — the client does not send email/SMS/push
|
|
222
|
-
| [AI](/docs/elements/ai) | Via Flows | `fx.ask` / `fx.run` inside a Flow; the client gets that Flow’s `out`
|
|
216
|
+
| Element | On the client | How |
|
|
217
|
+
| --------------------------------- | ------------- | ------------------------------------------------------------------------------------------ |
|
|
218
|
+
| [Flow](/docs/elements/flow) | Direct | `api.unit.flow(input)` — the only public surface |
|
|
219
|
+
| [Gate](/docs/elements/gate) | Indirect | Bearer via `auth`; denials as `Unauthorized` / `Forbidden` / `RateLimited` |
|
|
220
|
+
| [Store](/docs/elements/store) | Via Flows | `fx.store` inside Flows; `store.resource` + `on(http.resource…)` → five Flows on `$routes` |
|
|
221
|
+
| [Signal](/docs/elements/signal) | Via Flows | Emit/consume server-side; no subscribe API on `okengine/client` yet |
|
|
222
|
+
| [Clock](/docs/elements/clock) | Via Flows | Schedules fire on the server — the client never ticks a clock |
|
|
223
|
+
| [Vault](/docs/elements/vault) | Via Flows | Secrets stay server-side; never ship them to the browser package |
|
|
224
|
+
| [Channel](/docs/elements/channel) | Via Flows | `fx.send` in a Flow — the client does not send email/SMS/push |
|
|
225
|
+
| [AI](/docs/elements/ai) | Via Flows | `fx.ask` / `fx.run` inside a Flow; the client gets that Flow’s `out` |
|
|
223
226
|
|
|
224
227
|
### Store resources
|
|
225
228
|
|
|
@@ -227,7 +230,7 @@ Mount a resource, adopt the returned ops, then call the five Flows like any othe
|
|
|
227
230
|
|
|
228
231
|
```typescript
|
|
229
232
|
const notesR = store.resource(db, notes, {/* in, out, list */});
|
|
230
|
-
const mounted = on(http.resource("/notes", notesR.all()).
|
|
233
|
+
const mounted = on(http.resource("/notes", notesR.all()).public().live());
|
|
231
234
|
// .adopt({ notes: mounted }) →
|
|
232
235
|
const page = await api.notes.list({ limit: 20 });
|
|
233
236
|
const more = await page.next();
|
|
@@ -280,9 +283,9 @@ Budget: the `./client` export stays under the measured client-runtime cap (hard
|
|
|
280
283
|
|
|
281
284
|
<Accordion title="api.main.health is not a function / type error">
|
|
282
285
|
|
|
283
|
-
Confirm the
|
|
284
|
-
|
|
285
|
-
`oke dev` after renaming exports.
|
|
286
|
+
Confirm the Flow is `export`ed from a generated unit (`import "@/flows/generated"` then `oke({ name })`), or from a module you still `.adopt({ main })`.
|
|
287
|
+
|
|
288
|
+
Type `createClient` with that `App` (or ambient `Register` after `oke-client.d.ts` regenerates). Restart `oke dev` after renaming exports.
|
|
286
289
|
|
|
287
290
|
</Accordion>
|
|
288
291
|
|
|
@@ -321,7 +324,8 @@ calls `api.auth.refresh({ refreshToken })`. Re-login when rotation fails or no r
|
|
|
321
324
|
|
|
322
325
|
## Learn more
|
|
323
326
|
|
|
324
|
-
- [
|
|
327
|
+
- [Project structure](/docs/get-started/project-structure) — folders are the URL; `$routes` without `.adopt()`
|
|
328
|
+
- [Basic usage](/docs/get-started/basic-usage) — generated barrel → client → test loop
|
|
325
329
|
- [Gate](/docs/elements/gate) — policies, `gate.public`, denials
|
|
326
330
|
- [Store](/docs/elements/store) — `store.resource` and list query language
|
|
327
331
|
- [Flow](/docs/elements/flow) — `in` / `out` / `errors` and `fx.fail`
|
|
@@ -33,9 +33,10 @@ See [Store](/docs/elements/store) for the query-builder surface.
|
|
|
33
33
|
|
|
34
34
|
## Signals
|
|
35
35
|
|
|
36
|
-
| Signature | Records
|
|
37
|
-
| ------------------------------------- |
|
|
38
|
-
| `fx.emit(signal, payload?, { key? })` | `emit`
|
|
36
|
+
| Signature | Records | Notes |
|
|
37
|
+
| ------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
38
|
+
| `fx.emit(signal, payload?, { key? })` | `emit` | Commits the signal outbox when the call resolves; optional `key` serializes `once` per key; stamps producer run id as `parentRunId` for trace chains; throws **OKE1042** (orphan) or **OKE1043** (schema) |
|
|
39
|
+
| `fx.deadLetters(signal)` | `read` `signal:<name>` | Dead-lettered messages for that signal. Payload typed from `SignalDecl<T>`. Page with `fx.json.withQuery`. Cross-signal throws **OKE1001**. |
|
|
39
40
|
|
|
40
41
|
## Runs (observability read)
|
|
41
42
|
|
|
@@ -145,12 +146,12 @@ contact a provider. Channel bodies use `{{field}}` catalogs — not ICU (see [Ch
|
|
|
145
146
|
|
|
146
147
|
## AI
|
|
147
148
|
|
|
148
|
-
| Signature | Records | Returns
|
|
149
|
-
| ----------------------------------------------------- | ------------------------- |
|
|
150
|
-
| `fx.ask(prompt, input?, { via?, tools?, maxSteps? })` | `ask` (+ `call` per tool) | Object validated against the prompt's `out`
|
|
151
|
-
| `fx.run(agent, input?)` | `ask` | Agent result
|
|
152
|
-
| `fx.stream(model, { prompt?, data? })`
|
|
153
|
-
| `fx.search(embed, query, { topK? })` | `read` | Matches from the index/embed
|
|
149
|
+
| Signature | Records | Returns |
|
|
150
|
+
| ----------------------------------------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
151
|
+
| `fx.ask(prompt, input?, { via?, tools?, maxSteps? })` | `ask` (+ `call` per tool) | Object validated against the prompt's `out` |
|
|
152
|
+
| `fx.run(agent, input?)` | `ask` | Agent result |
|
|
153
|
+
| `fx.stream(model, { prompt?, data?, via? })` | `ask` | `AsyncIterable<string>` — real driver stream; cancels via ambient `fx.signal` (HTTP disconnect included) |
|
|
154
|
+
| `fx.search(embed, query, { topK? })` | `read` | Matches from the index/embed |
|
|
154
155
|
|
|
155
156
|
AI calls are nondeterministic: journaling is forced on and auto-cache disabled around them. `tools` are Flow refs — each model tool call goes through `fx.call` (same capability and Runs path).
|
|
156
157
|
|
|
@@ -169,12 +170,15 @@ AI calls are nondeterministic: journaling is forced on and auto-cache disabled a
|
|
|
169
170
|
|
|
170
171
|
## Clock
|
|
171
172
|
|
|
172
|
-
| Signature | Notes
|
|
173
|
-
| --------------------------------- |
|
|
174
|
-
| `fx.clock.now()` | Epoch-ms, injectable — the only legal "now"
|
|
175
|
-
| `fx.clock.
|
|
173
|
+
| Signature | Notes |
|
|
174
|
+
| --------------------------------- | ------------------------------------------------------------------- |
|
|
175
|
+
| `fx.clock.now()` | Epoch-ms, injectable — the only legal "now" |
|
|
176
|
+
| `fx.clock.ago(duration)` | Instant before now (`"30d"` → now − 30 days) |
|
|
177
|
+
| `fx.clock.fromNow(duration)` | Instant after now (`"14d"` → now + 14 days) |
|
|
178
|
+
| `fx.clock.duration(duration)` | Span in ms — offset a stored instant (`createdAt + duration("7d")`) |
|
|
179
|
+
| `fx.clock.sleep(label, duration)` | Durable sleep in `durable` flows; immediate otherwise |
|
|
176
180
|
|
|
177
|
-
Durations: `"200ms"` · `"30s"` · `"2m"` · `"1h"` · `"7d"`.
|
|
181
|
+
Durations: `"200ms"` · `"30s"` · `"2m"` · `"1h"` · `"7d"`. A `"d"` is 86_400_000 ms, not a calendar day. Unknown strings parse as `0`.
|
|
178
182
|
|
|
179
183
|
## Cache
|
|
180
184
|
|
|
@@ -192,15 +196,16 @@ keys. Use `cache: false` to opt out, or `cache: "30s"` for a TTL.
|
|
|
192
196
|
|
|
193
197
|
## Responses
|
|
194
198
|
|
|
195
|
-
| Helper | Status | Body
|
|
196
|
-
| ----------------------------------------- | ------ |
|
|
197
|
-
| `fx.json.ok(value, { meta? })` | 200 | `{ data, meta?, error: null }`
|
|
198
|
-
| `fx.json.create(value)` | 201 | `{ data, error: null }`
|
|
199
|
-
| `fx.json.empty()` | 204 | no body
|
|
200
|
-
| `fx.json.with(page)` / `with(data, meta)` | 200 | `{ data, meta, error: null }` — already-built pager
|
|
201
|
-
| `fx.json.withQuery(rows, input, spec?)` | 200 | In-memory list page — zero-config `q` / auto-eq / PostgREST
|
|
199
|
+
| Helper | Status | Body |
|
|
200
|
+
| ----------------------------------------- | ------ | -------------------------------------------------------------- |
|
|
201
|
+
| `fx.json.ok(value, { meta? })` | 200 | `{ data, meta?, error: null }` |
|
|
202
|
+
| `fx.json.create(value)` | 201 | `{ data, error: null }` |
|
|
203
|
+
| `fx.json.empty()` | 204 | no body |
|
|
204
|
+
| `fx.json.with(page)` / `with(data, meta)` | 200 | `{ data, meta, error: null }` — already-built pager |
|
|
205
|
+
| `fx.json.withQuery(rows, input, spec?)` | 200 | In-memory list page — zero-config `q` / auto-eq / PostgREST |
|
|
206
|
+
| `fx.json.stream(chunks)` | 200 | `text/event-stream` — JSON `data:` frames, then `data: [DONE]` |
|
|
202
207
|
|
|
203
|
-
Returning a plain value instead answers 200 with `{ data: value, error: null }` — the helpers exist for status and `meta` control.
|
|
208
|
+
Returning a plain value instead answers 200 with `{ data: value, error: null }` — the helpers exist for status and `meta` control. Pass `fx.stream(...)` into `fx.json.stream` to reach the HTTP client token-by-token.
|
|
204
209
|
|
|
205
210
|
## Logging, i18n, ids
|
|
206
211
|
|
|
@@ -41,7 +41,7 @@ Our path is concrete: a booking name containing "ignore previous instructions an
|
|
|
41
41
|
#### 10.4 Remaining closures
|
|
42
42
|
|
|
43
43
|
- **`invoke-as` is attenuated** exactly like an API key: an operator cannot assume a scope set they could not grant. Impersonating a real user is development-only. Console `POST /console/flows/invoke` runs the target flow on a bound host under the selected identity’s scopes (`trustedInvoke` is in-process only — never from public HTTP); when the host adapter is unbound, invoke fails closed.
|
|
44
|
-
- **Store SQL browse** masks classified PII by default. `QUERY /console/store/query` with `revealPii: true` returns cleartext for the current page and writes an audited `console.store.query.reveal` log (operator, ref, child). The SQL query console (`POST /console/store/sql`) stays masked unless `revealPii: true`, which writes `console.store.sql.reveal` (operator, ref). Call API (`POST /console/flows/invoke`) masks the same classified field names on the handler response unless `revealPii: true`, which writes `console.flows.invoke.reveal` and opens the host store session with cleartext so columns the handler reads (for example `ownerEmail`) are not already `[redacted]`. `asGate`
|
|
44
|
+
- **Store SQL browse** masks classified PII by default. `QUERY /console/store/query` with `revealPii: true` returns cleartext for the current page and writes an audited `console.store.query.reveal` log (operator, ref, child). The SQL query console (`POST /console/store/sql`) stays masked unless `revealPii: true`, which writes `console.store.sql.reveal` (operator, ref). Call API (`POST /console/flows/invoke`) masks the same classified field names on the handler response unless `revealPii: true`, which writes `console.flows.invoke.reveal` and opens the host store session with cleartext so columns the handler reads (for example `ownerEmail`) are not already `[redacted]`. `{ asGate, asUserId }` on SQL, browse, row edit, and Call API stamps `oke.gate()` / `oke.user()` / `oke.has_scope()` on postgres / pglite (one identity bag; policy-only leaves `oke.user()` empty — never `gate:${name}`); Operator / omit / `bypassGates` still bypasses RLS. The response may include `rls: { gate, userId, applied }`. Catalog DDL stays Operator. Per-cell `POST /console/store/reveal` remains available. The Console auth schema (`sql:oke_console` — `oke_operators`, sessions, roles, keys) is hidden from Store browse unless `OKE_CONSOLE_AUTH_STORE=1`. When listed it is **read-only**; edit / delete / raw SQL are refused so operator-plane rows are never joined to `public`.
|
|
45
45
|
- **Host WideEvent ingest** (`POST /console/runs/ingest`, `oke dev` only) is secret-gated and never echoes the event body. Operators read runs only through `projectRun` / `maskWideEventForConsole` on `GET /console/runs` and `/console/live` — the same PII boundary as Console-local runs.
|
|
46
46
|
- **Store SQL performance** (`QUERY /console/store/sql/stats`, `/locks`, `POST /console/store/sql/advise`) is engine-native query text — not Store browse masking. Statements show `pg_stat_statements` fingerprints (`$n`). Live `pg_stat_activity.query` is collapsed until `revealPii: true`, which writes `console.store.sql.stats.reveal` (operator, ref). Named limitation `StoreSqlStatsQueryTextGap`. This surface does not terminate sessions or call `pg_stat_statements_reset`.
|
|
47
47
|
- **Store KV performance** (`QUERY /console/store/kv/stats`) is Redis-wire `INFO` / `COMMANDSTATS` / `SLOWLOG` / `LATENCY` — not Store browse. INFO is instance-wide (`StoreKvStatsServerWideGap`). SLOWLOG args are keys and values — collapsed until `revealPii: true`, which writes `console.store.kv.stats.reveal` (operator, ref). Named limitation `StoreKvStatsSlowlogArgsGap`. `memory` returns `KvStatsUnsupported`. This surface does not run `MONITOR` and does not invent a hot-key table.
|
package/src/auth/bindings.ts
CHANGED
|
@@ -275,10 +275,13 @@ export function createAuthHttpBindings(
|
|
|
275
275
|
} = { refresh, revoke, me };
|
|
276
276
|
const bindings: Binding[] = [
|
|
277
277
|
bindAuthHttp(
|
|
278
|
-
http
|
|
278
|
+
http
|
|
279
|
+
.post(`${base}/refresh`)
|
|
280
|
+
.public()
|
|
281
|
+
.gate(...(refreshRate ? [refreshRate] : [])),
|
|
279
282
|
refresh,
|
|
280
283
|
),
|
|
281
|
-
bindAuthHttp(http.post(`${base}/revoke`).
|
|
284
|
+
bindAuthHttp(http.post(`${base}/revoke`).public(), revoke),
|
|
282
285
|
bindAuthHttp(http.get(`${base}/me`).gate(AUTH_SESSION_GATE), me),
|
|
283
286
|
];
|
|
284
287
|
|
|
@@ -371,11 +374,17 @@ export function createAuthHttpBindings(
|
|
|
371
374
|
flows.signUpEmail = signUpEmail;
|
|
372
375
|
bindings.push(
|
|
373
376
|
bindAuthHttp(
|
|
374
|
-
http
|
|
377
|
+
http
|
|
378
|
+
.post(`${base}/sign-in/email`)
|
|
379
|
+
.public()
|
|
380
|
+
.gate(...(signInRate ? [signInRate] : [])),
|
|
375
381
|
signInEmail,
|
|
376
382
|
),
|
|
377
383
|
bindAuthHttp(
|
|
378
|
-
http
|
|
384
|
+
http
|
|
385
|
+
.post(`${base}/sign-up/email`)
|
|
386
|
+
.public()
|
|
387
|
+
.gate(...(signUpRate ? [signUpRate] : [])),
|
|
379
388
|
signUpEmail,
|
|
380
389
|
),
|
|
381
390
|
);
|
|
@@ -406,7 +415,7 @@ export function isSessionFresh(
|
|
|
406
415
|
* @param flowDef - Flow
|
|
407
416
|
*/
|
|
408
417
|
export function bindAuthHttp(trigger: HttpTrigger, flowDef: AnyFlowDef): Binding {
|
|
409
|
-
const withPosture = trigger.gates.length > 0 ? trigger : trigger.
|
|
418
|
+
const withPosture = trigger.gates.length > 0 ? trigger : trigger.public();
|
|
410
419
|
const normalized = normalizeTrigger(withPosture);
|
|
411
420
|
const list = flowDef.triggers as Trigger[];
|
|
412
421
|
list.push(normalized);
|
package/src/auth/cross-plane.ts
CHANGED
|
@@ -86,9 +86,16 @@ export function assertCrossPlane(
|
|
|
86
86
|
|
|
87
87
|
function inferFlowFromPath(path: string): string | undefined {
|
|
88
88
|
// …/flows/bookings/create.ts → bookings.create
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
// …/flows/notes/[id]/get.ts → notes.get (never notes.id.get)
|
|
90
|
+
const posix = path.replace(/\\/g, "/");
|
|
91
|
+
const m = /(?:^|\/)flows\/([^/]+)\/(.+)\.(ts|tsx|js)$/.exec(posix);
|
|
92
|
+
if (!m) return undefined;
|
|
93
|
+
const unit = m[1]!;
|
|
94
|
+
const rest = m[2]!;
|
|
95
|
+
const leaf = rest.split("/").pop();
|
|
96
|
+
if (!leaf || leaf === "index" || leaf.startsWith("_")) return undefined;
|
|
97
|
+
if (leaf === "shapes" || leaf === "signals" || leaf.endsWith(".test")) return undefined;
|
|
98
|
+
return `${unit}.${leaf}`;
|
|
92
99
|
}
|
|
93
100
|
|
|
94
101
|
function inferPlaneFromSource(source: string): "user" | "operator" | undefined {
|
|
@@ -167,6 +167,68 @@ export default defineConfig({
|
|
|
167
167
|
rmSync(dir, { recursive: true, force: true });
|
|
168
168
|
}
|
|
169
169
|
});
|
|
170
|
+
|
|
171
|
+
test("applyAiSetup prefers an existing src/core/ai.ts over a thin core.ts barrel", () => {
|
|
172
|
+
const dir = mkdtempSync(join(tmpdir(), "oke-ai-setup-split-"));
|
|
173
|
+
try {
|
|
174
|
+
writeFileSync(
|
|
175
|
+
join(dir, "oke.config.ts"),
|
|
176
|
+
`import { defineConfig } from "okengine/config";
|
|
177
|
+
export default defineConfig({
|
|
178
|
+
drivers: {
|
|
179
|
+
channel: {
|
|
180
|
+
email: {
|
|
181
|
+
dev: "smtp",
|
|
182
|
+
test: "console",
|
|
183
|
+
prod: "smtp",
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
images: {
|
|
188
|
+
store: {
|
|
189
|
+
sql: "postgres:18-alpine",
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
`,
|
|
194
|
+
"utf8",
|
|
195
|
+
);
|
|
196
|
+
mkdirSync(join(dir, "src", "core"), { recursive: true });
|
|
197
|
+
writeFileSync(
|
|
198
|
+
join(dir, "src", "core.ts"),
|
|
199
|
+
`export * from "./core/store.ts";\nexport * from "./core/ai.ts";\n`,
|
|
200
|
+
"utf8",
|
|
201
|
+
);
|
|
202
|
+
writeFileSync(
|
|
203
|
+
join(dir, "src", "core", "store.ts"),
|
|
204
|
+
`import { store } from "okengine";\n\nexport const db = store.sql("app");\n`,
|
|
205
|
+
"utf8",
|
|
206
|
+
);
|
|
207
|
+
writeFileSync(join(dir, "src", "core", "ai.ts"), `import { ai } from "okengine";\n`, "utf8");
|
|
208
|
+
writeFileSync(
|
|
209
|
+
join(dir, "src", "app.ts"),
|
|
210
|
+
`import "@/core";\nexport const app = {};\n`,
|
|
211
|
+
"utf8",
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
applyAiSetup(dir, {
|
|
215
|
+
driver: "ollama",
|
|
216
|
+
baseUrl: "http://127.0.0.1:11434",
|
|
217
|
+
chatModel: "gemma4:e4b",
|
|
218
|
+
visionModel: "qwen3-vl:4b",
|
|
219
|
+
embedModel: "nomic-embed-text",
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const barrel = readFileSync(join(dir, "src", "core.ts"), "utf8");
|
|
223
|
+
expect(barrel).not.toContain("ai.model");
|
|
224
|
+
expect(barrel).toContain('export * from "./core/ai.ts"');
|
|
225
|
+
const aiTs = readFileSync(join(dir, "src", "core", "ai.ts"), "utf8");
|
|
226
|
+
expect(aiTs).toContain("smart");
|
|
227
|
+
expect(aiTs).toContain('ai.model("vision"');
|
|
228
|
+
} finally {
|
|
229
|
+
rmSync(dir, { recursive: true, force: true });
|
|
230
|
+
}
|
|
231
|
+
});
|
|
170
232
|
});
|
|
171
233
|
|
|
172
234
|
describe("parseAiSetupArgs", () => {
|
|
@@ -244,7 +244,9 @@ export function renderAiTs(input: AiSetupApplyInput): string {
|
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
/**
|
|
247
|
-
* Write AI model declarations into `src/core.ts`
|
|
247
|
+
* Write AI model declarations into `src/core/ai.ts` when that split exists
|
|
248
|
+
* (so a thin `src/core.ts` barrel stays a re-export), else `src/core.ts`,
|
|
249
|
+
* else legacy `src/core/index.ts` + sidecar.
|
|
248
250
|
*
|
|
249
251
|
* @param cwd - Project root
|
|
250
252
|
* @param input - Setup choices
|
|
@@ -252,9 +254,25 @@ export function renderAiTs(input: AiSetupApplyInput): string {
|
|
|
252
254
|
*/
|
|
253
255
|
function writeAiModels(cwd: string, input: AiSetupApplyInput): string {
|
|
254
256
|
const rendered = renderAiTs(input);
|
|
257
|
+
const coreAiPath = join(cwd, "src", "core", "ai.ts");
|
|
255
258
|
const coreTsPath = join(cwd, "src", "core.ts");
|
|
256
259
|
const legacyIndex = join(cwd, "src", "core", "index.ts");
|
|
257
260
|
|
|
261
|
+
if (existsSync(coreAiPath)) {
|
|
262
|
+
const existing = readFileSync(coreAiPath, "utf8");
|
|
263
|
+
if (hasAiModels(existing)) {
|
|
264
|
+
const withPrompt = ensureSummarizeNotePrompt(existing);
|
|
265
|
+
if (withPrompt !== existing) {
|
|
266
|
+
writeFileSync(coreAiPath, withPrompt, "utf8");
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
writeFileSync(coreAiPath, mergeAiIntoCore(existing, rendered), "utf8");
|
|
270
|
+
}
|
|
271
|
+
ensureCoreBarrelExportsAi(cwd);
|
|
272
|
+
ensureCoreImported(cwd);
|
|
273
|
+
return coreAiPath;
|
|
274
|
+
}
|
|
275
|
+
|
|
258
276
|
// Folder layout still in the wild — keep writing a sidecar.
|
|
259
277
|
if (!existsSync(coreTsPath) && existsSync(legacyIndex)) {
|
|
260
278
|
const aiTsPath = join(cwd, "src", "core", "ai.ts");
|
|
@@ -364,6 +382,19 @@ export function ensureNamedOkengineImport(source: string, name: string): string
|
|
|
364
382
|
return source.replace(re, `import { ${sorted.join(", ")} } from "okengine";`);
|
|
365
383
|
}
|
|
366
384
|
|
|
385
|
+
/**
|
|
386
|
+
* Keep a `src/core.ts` barrel re-exporting `./core/ai.ts` after a split write.
|
|
387
|
+
*
|
|
388
|
+
* @param cwd - Project root
|
|
389
|
+
*/
|
|
390
|
+
function ensureCoreBarrelExportsAi(cwd: string): void {
|
|
391
|
+
const coreTsPath = join(cwd, "src", "core.ts");
|
|
392
|
+
if (!existsSync(coreTsPath)) return;
|
|
393
|
+
const src = readFileSync(coreTsPath, "utf8");
|
|
394
|
+
if (/from\s+["']\.\/core\/ai/.test(src)) return;
|
|
395
|
+
writeFileSync(coreTsPath, `${src.trimEnd()}\nexport * from "./core/ai.ts";\n`, "utf8");
|
|
396
|
+
}
|
|
397
|
+
|
|
367
398
|
/**
|
|
368
399
|
* Ensure `src/app.ts` loads `@/core` / `./core` so merged AI registers.
|
|
369
400
|
*
|
|
@@ -197,4 +197,29 @@ describe("formatModelRow", () => {
|
|
|
197
197
|
expect(row).toContain("text · code");
|
|
198
198
|
expect(row).not.toContain(" · ≈");
|
|
199
199
|
});
|
|
200
|
+
|
|
201
|
+
test("Arabic and CJK labels keep Caps aligned", () => {
|
|
202
|
+
const header = formatModelTableHeader();
|
|
203
|
+
const capsWidth = Bun.stringWidth(header.slice(0, header.indexOf("Caps")));
|
|
204
|
+
const ar = formatModelRow({
|
|
205
|
+
id: "ar",
|
|
206
|
+
label: "مرحبا بالعالم",
|
|
207
|
+
hint: "",
|
|
208
|
+
role: "chat",
|
|
209
|
+
ramGb: 8,
|
|
210
|
+
tier: "fast",
|
|
211
|
+
modalities: ["text"],
|
|
212
|
+
});
|
|
213
|
+
const cjk = formatModelRow({
|
|
214
|
+
id: "cjk",
|
|
215
|
+
label: "你好世界模型",
|
|
216
|
+
hint: "",
|
|
217
|
+
role: "chat",
|
|
218
|
+
ramGb: 8,
|
|
219
|
+
tier: "fast",
|
|
220
|
+
modalities: ["text"],
|
|
221
|
+
});
|
|
222
|
+
expect(Bun.stringWidth(ar.slice(0, ar.indexOf("text")))).toBe(capsWidth);
|
|
223
|
+
expect(Bun.stringWidth(cjk.slice(0, cjk.indexOf("text")))).toBe(capsWidth);
|
|
224
|
+
});
|
|
200
225
|
});
|
|
@@ -195,9 +195,14 @@ const MODEL_COL_RAM = 7;
|
|
|
195
195
|
* @param width - Fixed width
|
|
196
196
|
*/
|
|
197
197
|
function clipPad(value: string, width: number): string {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
198
|
+
const bunAnsi = Bun as typeof Bun & {
|
|
199
|
+
stringWidth(text: string): number;
|
|
200
|
+
sliceAnsi(text: string, start: number, end: number, omission?: string): string;
|
|
201
|
+
};
|
|
202
|
+
const display = bunAnsi.stringWidth(value);
|
|
203
|
+
if (display === width) return value;
|
|
204
|
+
if (display < width) return value + " ".repeat(width - display);
|
|
205
|
+
return bunAnsi.sliceAnsi(value, 0, width, "…");
|
|
201
206
|
}
|
|
202
207
|
|
|
203
208
|
/**
|
|
@@ -28,7 +28,7 @@ describe("attachHostToConsole", () => {
|
|
|
28
28
|
import { oke, on, flow, http, gate, store } from ${JSON.stringify(OKE)};
|
|
29
29
|
const db = store.sql("db");
|
|
30
30
|
export const ping = on(
|
|
31
|
-
http.get("/ping").
|
|
31
|
+
http.get("/ping").public(),
|
|
32
32
|
flow("main.ping", { do: () => ({ ok: true }) }),
|
|
33
33
|
);
|
|
34
34
|
export const app = oke({
|
|
@@ -69,6 +69,7 @@ export const app = oke({
|
|
|
69
69
|
expect(ingested.some((row) => row.event?.flow === "main.ping")).toBe(true);
|
|
70
70
|
await attached!.stop();
|
|
71
71
|
expect(state.invokeUserFlow).toBeNull();
|
|
72
|
+
expect(state.storeRuntime).toBeNull();
|
|
72
73
|
});
|
|
73
74
|
|
|
74
75
|
test("binds host aiRuntime so Console projection sees a journaled ask", async () => {
|
|
@@ -81,7 +82,7 @@ import { oke, on, flow, http, gate, ai } from ${JSON.stringify(OKE)};
|
|
|
81
82
|
const smart = ai.model("smart", { provider: "mock" });
|
|
82
83
|
smart.prompt("ticket-triage", { version: 2 });
|
|
83
84
|
export const ping = on(
|
|
84
|
-
http.get("/ping").
|
|
85
|
+
http.get("/ping").public(),
|
|
85
86
|
flow("main.ping", {
|
|
86
87
|
effects: { asks: ["ticket-triage"] },
|
|
87
88
|
do: (_input, fx) => fx.ask("ticket-triage", { subject: "hello" }),
|
|
@@ -73,6 +73,9 @@ export async function attachHostToConsole(
|
|
|
73
73
|
return {
|
|
74
74
|
stop: async () => {
|
|
75
75
|
options.state.invokeUserFlow = null;
|
|
76
|
+
options.state.storeRuntime = null;
|
|
77
|
+
options.state.vaultRuntime = null;
|
|
78
|
+
options.state.aiRuntime = null;
|
|
76
79
|
await host.stop();
|
|
77
80
|
},
|
|
78
81
|
};
|
package/src/cli/build.test.ts
CHANGED
|
@@ -32,8 +32,10 @@ describe("oke build — .adopt() barrel regeneration", () => {
|
|
|
32
32
|
expect(code).toBe(0);
|
|
33
33
|
|
|
34
34
|
const after = await readFile(generatedPath, "utf8");
|
|
35
|
-
expect(after).toContain('
|
|
36
|
-
expect(after).toContain('
|
|
35
|
+
expect(after).toContain('import * as main from "./main/index.ts";');
|
|
36
|
+
expect(after).toContain('import * as notes from "./notes/index.ts";');
|
|
37
|
+
expect(after).toContain("export { main }");
|
|
38
|
+
expect(after).toContain("export { notes }");
|
|
37
39
|
} finally {
|
|
38
40
|
await rm(root, { recursive: true, force: true });
|
|
39
41
|
}
|