okengine 0.22.0 → 0.23.1
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/AGENTS.md +7 -0
- package/manifest.v1.schema.json +44 -0
- package/package.json +32 -19
- package/site/content/docs/ai/mcp.mdx +14 -10
- package/site/content/docs/ai/skills.mdx +1 -1
- package/site/content/docs/elements/ai/agents.mdx +61 -13
- package/site/content/docs/elements/ai/decide.mdx +536 -0
- package/site/content/docs/elements/ai/events.mdx +373 -0
- package/site/content/docs/elements/ai/index.mdx +9 -3
- package/site/content/docs/elements/ai/meta.json +1 -1
- package/site/content/docs/elements/ai/prompts.mdx +4 -2
- package/site/content/docs/elements/channel/index.mdx +4 -4
- package/site/content/docs/elements/channel/receipts.mdx +7 -5
- package/site/content/docs/elements/channel/sms.mdx +2 -1
- package/site/content/docs/elements/flow/index.mdx +22 -18
- package/site/content/docs/elements/gate/auth.mdx +4 -1
- package/site/content/docs/elements/signal/index.mdx +15 -14
- package/site/content/docs/elements/store/index.mdx +1 -1
- package/site/content/docs/elements/store/kv.mdx +5 -5
- package/site/content/docs/plugins/otp.mdx +3 -3
- package/site/content/docs/recipes/dragonfly.mdx +5 -5
- package/site/content/docs/recipes/index.mdx +6 -6
- package/site/content/docs/recipes/postgres.mdx +12 -8
- package/site/content/docs/recipes/redis.mdx +4 -4
- package/site/content/docs/reference/cli.mdx +2 -1
- package/site/content/docs/reference/configuration.mdx +9 -7
- package/site/content/docs/reference/fx.mdx +19 -10
- package/src/auth/api-key-sql.test.ts +44 -0
- package/src/auth/api-key-sql.ts +1 -0
- package/src/auth/api-keys.ts +30 -12
- package/src/auth/config.ts +6 -2
- package/src/auth/gate-auth.test.ts +20 -0
- package/src/auth/tenants.ts +17 -0
- package/src/cli/agents-md.test.ts +25 -0
- package/src/cli/ask-vault-gaps.test.ts +38 -1
- package/src/cli/ask-vault-gaps.ts +35 -2
- package/src/cli/decide.ts +174 -0
- package/src/cli/decision-lock-watch.test.ts +45 -0
- package/src/cli/decision-lock-watch.ts +51 -0
- package/src/cli/dev-app-runner.ts +34 -8
- package/src/cli/dev-auth-secret.test.ts +49 -0
- package/src/cli/dev-auth-secret.ts +49 -0
- package/src/cli/dev-hot.test.ts +41 -0
- package/src/cli/dev-hot.ts +48 -0
- package/src/cli/dev.test.ts +1 -1
- package/src/cli/dev.ts +24 -6
- package/src/cli/doctor-diff.test.ts +29 -1
- package/src/cli/doctor-diff.ts +35 -0
- package/src/cli/eval.ts +142 -1
- package/src/cli/index.ts +5 -0
- package/src/cli/load-config.images.test.ts +2 -2
- package/src/cli/load-config.ts +1 -1
- package/src/cli/manifest-pr-diff.ts +16 -0
- package/src/cli/mcp-from-console.ts +48 -0
- package/src/cli/registry.ts +35 -1
- package/src/client/agent.ts +307 -0
- package/src/client/transport.test.ts +20 -0
- package/src/client/transport.ts +2 -1
- package/src/client/types.ts +1 -1
- package/src/client-react/index.ts +7 -1
- package/src/client-react/use-agent-run.test.ts +271 -0
- package/src/client-react/use-agent-run.ts +193 -0
- package/src/compiler/ai-approval.test.ts +36 -0
- package/src/compiler/ai-repair.test.ts +22 -0
- package/src/compiler/decisions.extract.test.ts +164 -0
- package/src/compiler/effects-fetch.test.ts +77 -0
- package/src/compiler/effects-infer.ts +114 -17
- package/src/compiler/effects-join.test.ts +48 -0
- package/src/compiler/extract.test.ts +28 -0
- package/src/compiler/extract.ts +279 -13
- package/src/compiler/fx-index.ts +58 -4
- package/src/compiler/response.ts +35 -2
- package/src/config/index.ts +21 -3
- package/src/console/index.ts +1 -1
- package/src/console/server/ai-runs-flows.ts +269 -0
- package/src/console/server/ai-runs.test.ts +323 -0
- package/src/console/server/ai-runs.ts +562 -0
- package/src/console/server/ai.ts +12 -1
- package/src/console/server/app.ts +10 -1
- package/src/console/server/decisions-flows.ts +157 -0
- package/src/console/server/decisions.test.ts +66 -0
- package/src/console/server/decisions.ts +229 -0
- package/src/console/server/flows.ts +50 -2
- package/src/console/server/invoke-user-flow.ts +9 -0
- package/src/console/server/runs-ingest.ts +16 -0
- package/src/console/server/serve.ts +5 -0
- package/src/console/server/state.ts +25 -1
- package/src/console/server/store-stats.test.ts +47 -0
- package/src/console/server/store-stats.ts +92 -18
- package/src/console/ui-next/dist/assets/access-page-C_N4qvai.js +4 -0
- package/src/console/ui-next/dist/assets/{agent-disclosure-Bkohc_8X.js → agent-disclosure-Cq1TxniW.js} +1 -1
- package/src/console/ui-next/dist/assets/{cache-glyph-rL0lHxat.js → cache-glyph-BaI4uxZz.js} +1 -1
- package/src/console/ui-next/dist/assets/{call-pii-button-Deo4PP18.js → call-pii-button-DNyK7IIY.js} +1 -1
- package/src/console/ui-next/dist/assets/{collapsible-CoJ6amHf.js → collapsible-CRhBJMZR.js} +1 -1
- package/src/console/ui-next/dist/assets/confirm-sheet-DZfuJXP4.js +1 -0
- package/src/console/ui-next/dist/assets/copy-inline-button-BTjPVjRT.js +1 -0
- package/src/console/ui-next/dist/assets/decisions-page-D9twj8Xy.js +1 -0
- package/src/console/ui-next/dist/assets/detail-header-Dk5LipF6.js +1 -0
- package/src/console/ui-next/dist/assets/dropdown-menu-CRGrj5JU.js +1 -0
- package/src/console/ui-next/dist/assets/duration-tone-CsHWlVPU.js +9 -0
- package/src/console/ui-next/dist/assets/element-icons-C6t8Vlml.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-chrome-Dslh2yE8.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-empty-CIVg7Jgb.js +1 -0
- package/src/console/ui-next/dist/assets/flows-page-ZaTJAhR9.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-CPYBzDh0.js → highlighted-json-DQ-EMAfz.js} +1 -1
- package/src/console/ui-next/dist/assets/{http-method-DTPevKmM.js → http-method-Cn_Fl79s.js} +1 -1
- package/src/console/ui-next/dist/assets/index-BlrN49Hs.css +2 -0
- package/src/console/ui-next/dist/assets/index-CmRFeWdT.js +58 -0
- package/src/console/ui-next/dist/assets/observability-page-Cn6R_dXz.js +8 -0
- package/src/console/ui-next/dist/assets/{replica-lag-BM27lXld.js → replica-lag-Bx6EZhSe.js} +7 -7
- package/src/console/ui-next/dist/assets/request-meta-DmdiUrfh.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-DTE1sjTC.js +1 -0
- package/src/console/ui-next/dist/assets/store-page-8P6vGXnn.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-BfGvpFSv.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-CWdUHbc0.js +55 -0
- package/src/console/ui-next/dist/assets/units-page-CZOFZzay.js +1 -0
- package/src/console/ui-next/dist/assets/use-vault-list-BAyQG8Re.js +1 -0
- package/src/console/ui-next/dist/assets/vault-page-ki_ishmX.js +2 -0
- package/src/console/ui-next/dist/index.html +4 -3
- package/src/console/ui-next/seed-parked-approval.ts +126 -0
- package/src/console/ui-next/src/client.ts +224 -1
- package/src/console/ui-next/src/components/explorer/detail-header.tsx +9 -0
- package/src/console/ui-next/src/components/explorer/explorer-start-toggle.tsx +5 -1
- package/src/console/ui-next/src/components/ui/sheet-form.tsx +21 -12
- package/src/console/ui-next/src/features/flows/decisions/decisions-page.tsx +216 -0
- package/src/console/ui-next/src/features/flows/decisions/resolve.test.ts +48 -0
- package/src/console/ui-next/src/features/flows/decisions/resolve.ts +111 -0
- package/src/console/ui-next/src/features/flows/flows-page.tsx +10 -0
- package/src/console/ui-next/src/features/flows/graph/element-map.ts +1 -0
- package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +6 -0
- package/src/console/ui-next/src/features/flows/traces/effect-summary.test.ts +24 -0
- package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
- package/src/console/ui-next/src/features/flows/traces/http-status.ts +58 -0
- package/src/console/ui-next/src/features/flows/traces/request-client-card.tsx +355 -0
- package/src/console/ui-next/src/features/flows/traces/request-client.test.ts +114 -0
- package/src/console/ui-next/src/features/flows/traces/request-client.ts +423 -0
- package/src/console/ui-next/src/features/flows/traces/request-input-view.test.ts +52 -6
- package/src/console/ui-next/src/features/flows/traces/request-input-view.ts +85 -19
- package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +505 -80
- package/src/console/ui-next/src/features/flows/traces/trace-lanes.test.ts +61 -0
- package/src/console/ui-next/src/features/flows/traces/trace-lanes.ts +78 -0
- package/src/console/ui-next/src/features/flows/traces/trace-nav.test.ts +48 -0
- package/src/console/ui-next/src/features/flows/traces/trace-nav.ts +57 -0
- package/src/console/ui-next/src/features/flows/traces/trace-request-section.tsx +275 -96
- package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +16 -2
- package/src/console/ui-next/src/features/observability/detail/ai-runs.tsx +369 -0
- package/src/console/ui-next/src/features/observability/observability-page.tsx +31 -1
- package/src/console/ui-next/src/features/observability/state/observability-selection.ts +18 -3
- package/src/console/ui-next/src/features/store/performance/kv-performance-panel.tsx +12 -1
- package/src/console/ui-next/src/features/store/performance/performance-panel.tsx +14 -1
- package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +1 -1
- package/src/console/ui-next/src/features/units/detail/flow-contract-panel.tsx +6 -2
- package/src/console/ui-next/src/features/units/explorer/units-tree.tsx +73 -18
- package/src/console/ui-next/src/features/units/lib/unit-tree.test.ts +21 -0
- package/src/console/ui-next/src/features/units/lib/unit-tree.ts +82 -0
- package/src/console/ui-next/src/features/units/units-page.tsx +25 -7
- package/src/console/ui-next/src/router.tsx +17 -0
- package/src/console/ui-next/ui-next-seed-runs.ts +23 -0
- package/src/console/ui-next/vite-console-kernel-plugin.ts +43 -3
- package/src/console/ui-next/vite.config.ts +9 -0
- package/src/docker/recipes/dragonfly.ts +3 -2
- package/src/docker/recipes/redis.ts +1 -1
- package/src/drivers/ai-anthropic.ts +212 -21
- package/src/drivers/ai-mock.ts +34 -1
- package/src/drivers/ai-openai-compatible.ts +67 -26
- package/src/drivers/ai-preconnect.ts +18 -0
- package/src/drivers/ai-stream.test.ts +62 -0
- package/src/drivers/ai-types.ts +20 -0
- package/src/drivers/journal-postgres.test.ts +22 -1
- package/src/drivers/journal-postgres.ts +511 -13
- package/src/drivers/memory-ddl.test.ts +18 -0
- package/src/drivers/memory.ts +21 -1
- package/src/drivers/pg-rls.test.ts +31 -0
- package/src/drivers/pg-rls.ts +15 -0
- package/src/drivers/postgres.test.ts +9 -0
- package/src/drivers/postgres.ts +49 -11
- package/src/drivers/signal-compete.test.ts +234 -0
- package/src/drivers/signal-postgres.ts +39 -19
- package/src/drivers/signal-redis.ts +167 -11
- package/src/drivers/signal-types.ts +12 -0
- package/src/elements/ai/agent-event-slot.ts +27 -0
- package/src/elements/ai/approval-http.ts +207 -0
- package/src/elements/ai/approval.test.ts +741 -0
- package/src/elements/ai/approval.ts +228 -0
- package/src/elements/ai/decisions/bind.ts +163 -0
- package/src/elements/ai/decisions/certificate.test.ts +291 -0
- package/src/elements/ai/decisions/certificate.ts +377 -0
- package/src/elements/ai/decisions/certify.ts +314 -0
- package/src/elements/ai/decisions/decide.test.ts +953 -0
- package/src/elements/ai/decisions/e2e.test.ts +399 -0
- package/src/elements/ai/decisions/export.test.ts +120 -0
- package/src/elements/ai/decisions/export.ts +146 -0
- package/src/elements/ai/decisions/fixtures/openrouter-request.json +24 -0
- package/src/elements/ai/decisions/fixtures/openrouter-response.json +22 -0
- package/src/elements/ai/decisions/fixtures/typesafe-request.json +19 -0
- package/src/elements/ai/decisions/fixtures/typesafe-response.json +13 -0
- package/src/elements/ai/decisions/http.test.ts +154 -0
- package/src/elements/ai/decisions/http.ts +240 -0
- package/src/elements/ai/decisions/labels.test.ts +282 -0
- package/src/elements/ai/decisions/labels.ts +257 -0
- package/src/elements/ai/decisions/openrouter.live.test.ts +136 -0
- package/src/elements/ai/decisions/openrouter.ts +36 -0
- package/src/elements/ai/decisions/provider.ts +102 -0
- package/src/elements/ai/decisions/typesafe.ts +36 -0
- package/src/elements/ai/declare.ts +252 -1
- package/src/elements/ai/events.test.ts +379 -0
- package/src/elements/ai/events.ts +138 -0
- package/src/elements/ai/mcp-protocol.ts +5 -6
- package/src/elements/ai/run-events.test.ts +461 -0
- package/src/elements/ai/run-events.ts +522 -0
- package/src/elements/ai/runtime.ts +1187 -105
- package/src/elements/ai/stream-turn.ts +214 -0
- package/src/elements/ai/stream.live.test.ts +76 -0
- package/src/elements/ai/subagent.test.ts +136 -0
- package/src/elements/ai.test.ts +497 -3
- package/src/elements/ai.ts +16 -1
- package/src/elements/channel/runtime.ts +11 -0
- package/src/elements/channel/sql-ledger.ts +251 -0
- package/src/elements/clock/durable.ts +27 -1
- package/src/elements/signal/runtime.ts +9 -0
- package/src/elements/signal.test.ts +1 -0
- package/src/elements/store/cache.test.ts +120 -15
- package/src/elements/store/cache.ts +200 -39
- package/src/elements/store/prepare-row.test.ts +6 -2
- package/src/elements/store/sql-session.test.ts +53 -0
- package/src/elements/store/sql-session.ts +206 -10
- package/src/elements/store.ts +1 -0
- package/src/i18n/catalogs/ar.ts +5 -0
- package/src/i18n/catalogs/en.ts +5 -0
- package/src/index.ts +2 -0
- package/src/kernel/agent-event-store.ts +448 -0
- package/src/kernel/app.ts +135 -17
- package/src/kernel/auto-cache.test.ts +228 -0
- package/src/kernel/boot-bind/ai.ts +3 -0
- package/src/kernel/boot-bind/channel.ts +63 -2
- package/src/kernel/boot-bind/honor-config.test.ts +40 -10
- package/src/kernel/boot-bind/signal.ts +61 -25
- package/src/kernel/boot-bind/store.test.ts +26 -0
- package/src/kernel/boot-bind/store.ts +82 -6
- package/src/kernel/boot.ts +31 -6
- package/src/kernel/builtin-errors.ts +2 -0
- package/src/kernel/capability.ts +3 -0
- package/src/kernel/decision-label-store.ts +441 -0
- package/src/kernel/effects.test.ts +2 -1
- package/src/kernel/effects.ts +13 -1
- package/src/kernel/element-registries.ts +3 -0
- package/src/kernel/errors-compiler.ts +11 -0
- package/src/kernel/errors-text.ts +20 -0
- package/src/kernel/errors.ts +10 -0
- package/src/kernel/flow.test.ts +2 -3
- package/src/kernel/flow.ts +33 -3
- package/src/kernel/fx-call-types.test.ts +32 -0
- package/src/kernel/fx-decide.ts +765 -0
- package/src/kernel/fx-sql-handle.ts +46 -7
- package/src/kernel/fx.test.ts +13 -8
- package/src/kernel/fx.ts +339 -45
- package/src/kernel/http-frame.test.ts +94 -0
- package/src/kernel/http-frame.ts +203 -0
- package/src/kernel/idempotency.test.ts +2 -1
- package/src/kernel/journal.test.ts +114 -0
- package/src/kernel/journal.ts +471 -51
- package/src/kernel/json-result.ts +10 -0
- package/src/kernel/pipeline-tenant.ts +5 -1
- package/src/kernel/pipeline.test.ts +45 -0
- package/src/kernel/pipeline.ts +5 -0
- package/src/kernel/signal-tx.ts +28 -0
- package/src/kernel/sse-id.ts +32 -0
- package/src/kernel/store-transaction.test.ts +95 -0
- package/src/manifest/diff.ts +1 -0
- package/src/manifest/types.ts +29 -2
- package/src/mcp/ai-tools.test.ts +202 -0
- package/src/mcp/authorization.ts +66 -0
- package/src/mcp/docs-server.ts +19 -2
- package/src/mcp/mcp.test.ts +83 -0
- package/src/mcp/protocol.ts +8 -2
- package/src/mcp/server.ts +23 -2
- package/src/mcp/session.ts +3 -0
- package/src/mcp/tools.ts +168 -0
- package/src/mcp/versions.ts +60 -0
- package/src/plugins/index.ts +1 -0
- package/src/plugins/mena.ts +56 -0
- package/src/release/http-graph.test.ts +2 -0
- package/src/release/http-graph.ts +5 -0
- package/src/release/measure.ts +39 -14
- package/src/runs/collect.ts +4 -1
- package/src/runs/parquet.test.ts +27 -1
- package/src/runs/parquet.ts +19 -0
- package/src/runs/types.ts +31 -0
- package/src/runtime/json-code-block.test.ts +106 -47
- package/src/runtime/json-code-block.ts +1209 -46
- package/src/term.test.ts +13 -0
- package/src/term.ts +81 -49
- package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +0 -1
- package/src/console/ui-next/dist/assets/access-page-Ze6ckjdd.js +0 -4
- package/src/console/ui-next/dist/assets/copy-inline-button-CWG5_ZOh.js +0 -1
- package/src/console/ui-next/dist/assets/detail-header-b_ZzIsmU.js +0 -1
- package/src/console/ui-next/dist/assets/dropdown-menu-nwXEO1Ac.js +0 -1
- package/src/console/ui-next/dist/assets/duration-tone-BbQ_8z50.js +0 -9
- package/src/console/ui-next/dist/assets/element-icons-CwyLpVXz.js +0 -1
- package/src/console/ui-next/dist/assets/explorer-empty-C8sSCqYT.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-D2E7BtKK.js +0 -1
- package/src/console/ui-next/dist/assets/index-VxoEz295.css +0 -2
- package/src/console/ui-next/dist/assets/index-vTuwmeQz.js +0 -58
- package/src/console/ui-next/dist/assets/observability-page-SJTSzHFJ.js +0 -4
- package/src/console/ui-next/dist/assets/request-meta-BjFT7DNl.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-CUNegEV4.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-Qj9ThRE-.js +0 -41
- package/src/console/ui-next/dist/assets/trace-detail-sheet-Cmitq3da.js +0 -2
- package/src/console/ui-next/dist/assets/tree-expand-toggle-CbDIB-7x.js +0 -55
- package/src/console/ui-next/dist/assets/units-page-_PuYqFty.js +0 -1
- package/src/console/ui-next/dist/assets/use-vault-list-Cs45Czkt.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-BOMr0go9.js +0 -2
package/src/compiler/extract.ts
CHANGED
|
@@ -28,6 +28,7 @@ import { SearchConfigError } from "../elements/store/search-errors.ts";
|
|
|
28
28
|
import type {
|
|
29
29
|
Ai,
|
|
30
30
|
AiAgent,
|
|
31
|
+
AiDecision,
|
|
31
32
|
AiMcpServer,
|
|
32
33
|
AiModel,
|
|
33
34
|
AiPrompt,
|
|
@@ -257,11 +258,18 @@ export async function extractManifest(options: ExtractManifestOptions = {}): Pro
|
|
|
257
258
|
if (Object.keys(gates).length > 0) manifest.gates = gates;
|
|
258
259
|
if (Object.keys(vault).length > 0) manifest.vault = vault;
|
|
259
260
|
if (Object.keys(channels).length > 0) manifest.channels = channels;
|
|
260
|
-
if (
|
|
261
|
+
if (
|
|
262
|
+
scope.ai.models ||
|
|
263
|
+
scope.ai.prompts ||
|
|
264
|
+
scope.ai.agents ||
|
|
265
|
+
scope.ai.decisions ||
|
|
266
|
+
scope.ai.mcpServers
|
|
267
|
+
) {
|
|
261
268
|
manifest.ai = {
|
|
262
269
|
...(scope.ai.models ? { models: sortRecord(scope.ai.models) } : {}),
|
|
263
270
|
...(scope.ai.prompts ? { prompts: sortRecord(scope.ai.prompts) } : {}),
|
|
264
271
|
...(scope.ai.agents ? { agents: sortRecord(scope.ai.agents) } : {}),
|
|
272
|
+
...(scope.ai.decisions ? { decisions: sortRecord(scope.ai.decisions) } : {}),
|
|
265
273
|
...(scope.ai.mcpServers ? { mcpServers: sortRecord(scope.ai.mcpServers) } : {}),
|
|
266
274
|
};
|
|
267
275
|
}
|
|
@@ -1426,7 +1434,11 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
|
|
|
1426
1434
|
}
|
|
1427
1435
|
|
|
1428
1436
|
if (obj === "ai" && prop === "agent") {
|
|
1429
|
-
collectAgent(call, scope);
|
|
1437
|
+
collectAgent(call, program, scope);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
if (obj === "ai" && prop === "decision") {
|
|
1441
|
+
collectDecision(call, program, scope);
|
|
1430
1442
|
}
|
|
1431
1443
|
|
|
1432
1444
|
if (obj === "ai" && prop === "mcpServer") {
|
|
@@ -1494,6 +1506,7 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
|
|
|
1494
1506
|
: {}),
|
|
1495
1507
|
...(via && via.length > 0 ? { via } : {}),
|
|
1496
1508
|
...(timeout !== undefined ? { timeout } : {}),
|
|
1509
|
+
...(numberProp(opts, "repair") === 1 ? { repair: 1 as const } : {}),
|
|
1497
1510
|
};
|
|
1498
1511
|
scope.ai.prompts = scope.ai.prompts ?? {};
|
|
1499
1512
|
scope.ai.prompts[promptName] = prompt;
|
|
@@ -1648,27 +1661,267 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
|
|
|
1648
1661
|
}
|
|
1649
1662
|
}
|
|
1650
1663
|
|
|
1651
|
-
function
|
|
1664
|
+
function agentToolEntry(
|
|
1665
|
+
el: AstNode,
|
|
1666
|
+
scope: ProjectScope,
|
|
1667
|
+
): { name: string; approval: boolean } | undefined {
|
|
1668
|
+
if (el.type === "ObjectExpression") {
|
|
1669
|
+
const nameNode = objectProp(el, "name");
|
|
1670
|
+
const raw =
|
|
1671
|
+
(nameNode ? resolveCallTarget(nameNode, scope.bindings) : undefined) ??
|
|
1672
|
+
stringArg(nameNode) ??
|
|
1673
|
+
identifierName(nameNode);
|
|
1674
|
+
if (!raw) return undefined;
|
|
1675
|
+
const name = scope.flowExports.get(raw) ?? scope.bindings.get(raw)?.ref ?? raw;
|
|
1676
|
+
const approvalNode = objectProp(el, "approval");
|
|
1677
|
+
return { name, approval: approvalNode !== undefined && boolProp(el, "approval") !== false };
|
|
1678
|
+
}
|
|
1679
|
+
const resolved = resolveCallTarget(el, scope.bindings);
|
|
1680
|
+
const raw = resolved ?? identifierName(el) ?? stringArg(el);
|
|
1681
|
+
if (!raw) return undefined;
|
|
1682
|
+
const name = scope.flowExports.get(raw) ?? scope.bindings.get(raw)?.ref ?? raw;
|
|
1683
|
+
return { name, approval: false };
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
function assertDecisionPlacement(flowName: string, flow: Flow, scope: ProjectScope): void {
|
|
1687
|
+
const names = flow.effects?.decides ?? [];
|
|
1688
|
+
if (names.length === 0) return;
|
|
1689
|
+
const http = flow.trigger !== undefined && "http" in flow.trigger;
|
|
1690
|
+
for (const decisionName of names) {
|
|
1691
|
+
const decision = scope.ai.decisions?.[decisionName];
|
|
1692
|
+
if (!decision || decision.mode !== "review") continue;
|
|
1693
|
+
if (http) {
|
|
1694
|
+
throw new Error(
|
|
1695
|
+
`ai.decision("${decisionName}") review cannot run in HTTP flow "${flowName}" — a park answers 204. fx.emit a signal and decide in a consumer.`,
|
|
1696
|
+
);
|
|
1697
|
+
}
|
|
1698
|
+
if (flow.durable !== true) {
|
|
1699
|
+
throw new Error(
|
|
1700
|
+
`ai.decision("${decisionName}"): flow "${flowName}" must set durable: true to review`,
|
|
1701
|
+
);
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
function decisionReviewName(node: AstNode | undefined, scope: ProjectScope): string {
|
|
1707
|
+
const literal = stringArg(node);
|
|
1708
|
+
if (literal) return literal;
|
|
1709
|
+
if (node?.type === "Identifier") {
|
|
1710
|
+
const name = (node as Identifier).name;
|
|
1711
|
+
return scope.gateIds.get(name) ?? name;
|
|
1712
|
+
}
|
|
1713
|
+
if (node?.type === "CallExpression") {
|
|
1714
|
+
const arg = stringArg((node as CallExpression).arguments[0]);
|
|
1715
|
+
if (arg) return arg;
|
|
1716
|
+
}
|
|
1717
|
+
if (node?.type === "MemberExpression") {
|
|
1718
|
+
const member = node as AstNode & { property: AstNode; computed?: boolean };
|
|
1719
|
+
if (member.property.type === "Identifier" && member.computed !== true) {
|
|
1720
|
+
return (member.property as Identifier).name;
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
throw new Error("ai.decision: review gate name could not be resolved");
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
function assertDecisionQuestion(
|
|
1727
|
+
decisionName: string,
|
|
1728
|
+
key: string,
|
|
1729
|
+
value: AstNode | undefined,
|
|
1730
|
+
program: AstNode,
|
|
1731
|
+
): void {
|
|
1732
|
+
const resolved = resolveDecisionExpr(value, program, decisionName, key);
|
|
1733
|
+
if (!resolved || resolved.type !== "CallExpression") {
|
|
1734
|
+
throw new Error(
|
|
1735
|
+
`ai.decision("${decisionName}"): question "${key}" must be ai.choice, ai.score, or ai.boolean`,
|
|
1736
|
+
);
|
|
1737
|
+
}
|
|
1738
|
+
const callee = (resolved as CallExpression).callee;
|
|
1739
|
+
const member = callee as AstNode & { property?: AstNode };
|
|
1740
|
+
const fn =
|
|
1741
|
+
callee.type === "MemberExpression" && member.property?.type === "Identifier"
|
|
1742
|
+
? (member.property as Identifier).name
|
|
1743
|
+
: undefined;
|
|
1744
|
+
const args = (resolved as CallExpression).arguments;
|
|
1745
|
+
if (fn === "choice") {
|
|
1746
|
+
const options = args[1];
|
|
1747
|
+
if (!options || options.type !== "ObjectExpression") {
|
|
1748
|
+
throw new Error(
|
|
1749
|
+
`ai.decision("${decisionName}"): choice "${key}" options must be an object literal`,
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1752
|
+
const keys = objectProperties(options)
|
|
1753
|
+
.map((prop) => propKey(prop))
|
|
1754
|
+
.filter((name): name is string => typeof name === "string");
|
|
1755
|
+
if (keys.includes("none_of_these")) {
|
|
1756
|
+
throw new Error(`ai.decision("${decisionName}"): choice "${key}" must not set none_of_these`);
|
|
1757
|
+
}
|
|
1758
|
+
if (keys.length > 254) {
|
|
1759
|
+
throw new Error(
|
|
1760
|
+
`ai.decision("${decisionName}"): choice "${key}" has ${keys.length} options; max 254`,
|
|
1761
|
+
);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
if (fn === "score") {
|
|
1765
|
+
const levels = resolveScoreLevels(args[1], program, decisionName, key);
|
|
1766
|
+
const count = ((levels as AstNode & { elements?: unknown[] }).elements ?? []).length;
|
|
1767
|
+
if (count < 2 || count > 10) {
|
|
1768
|
+
throw new Error(`ai.decision("${decisionName}"): score "${key}" needs 2–10 levels`);
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
function resolveDecisionExpr(
|
|
1774
|
+
value: AstNode | undefined,
|
|
1775
|
+
program: AstNode,
|
|
1776
|
+
decisionName: string,
|
|
1777
|
+
key: string,
|
|
1778
|
+
): AstNode | undefined {
|
|
1779
|
+
if (!value) return undefined;
|
|
1780
|
+
if (value.type !== "Identifier") return value;
|
|
1781
|
+
const name = (value as Identifier).name;
|
|
1782
|
+
const init = sameFileConstInit(name, program);
|
|
1783
|
+
if (!init) {
|
|
1784
|
+
throw new Error(
|
|
1785
|
+
`ai.decision("${decisionName}"): question "${key}" is "${name}", which is not a same-file const`,
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1788
|
+
return init;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
function resolveScoreLevels(
|
|
1792
|
+
levels: AstNode | undefined,
|
|
1793
|
+
program: AstNode,
|
|
1794
|
+
decisionName: string,
|
|
1795
|
+
key: string,
|
|
1796
|
+
): AstNode {
|
|
1797
|
+
const resolved =
|
|
1798
|
+
levels?.type === "Identifier"
|
|
1799
|
+
? sameFileConstInit((levels as Identifier).name, program)
|
|
1800
|
+
: levels;
|
|
1801
|
+
if (!resolved || resolved.type !== "ArrayExpression") {
|
|
1802
|
+
const label = levels?.type === "Identifier" ? (levels as Identifier).name : "the levels";
|
|
1803
|
+
throw new Error(
|
|
1804
|
+
`ai.decision("${decisionName}"): score "${key}" levels (${label}) must be an array literal or a same-file const`,
|
|
1805
|
+
);
|
|
1806
|
+
}
|
|
1807
|
+
return resolved;
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
function sameFileConstInit(name: string, program: AstNode): AstNode | undefined {
|
|
1811
|
+
let found: AstNode | undefined;
|
|
1812
|
+
walk(program, (node) => {
|
|
1813
|
+
if (found || node.type !== "VariableDeclarator") return;
|
|
1814
|
+
const decl = node as AstNode & { id?: AstNode; init?: AstNode };
|
|
1815
|
+
if (decl.id?.type === "Identifier" && (decl.id as Identifier).name === name) {
|
|
1816
|
+
found = decl.init;
|
|
1817
|
+
}
|
|
1818
|
+
});
|
|
1819
|
+
return found;
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
function collectDecision(call: CallExpression, program: AstNode, scope: ProjectScope): void {
|
|
1823
|
+
const decisionName = stringArg(call.arguments[0]);
|
|
1824
|
+
const opts = objectArg(call.arguments[1]);
|
|
1825
|
+
if (!decisionName || !opts) return;
|
|
1826
|
+
const ask = objectProp(opts, "ask");
|
|
1827
|
+
const questionProps = ask?.type === "ObjectExpression" ? objectProperties(ask) : [];
|
|
1828
|
+
const questionNames = questionProps
|
|
1829
|
+
.map((prop) => propKey(prop))
|
|
1830
|
+
.filter((name): name is string => typeof name === "string" && name.length > 0);
|
|
1831
|
+
if (questionNames.length === 0) {
|
|
1832
|
+
throw new Error(`ai.decision("${decisionName}"): ask is empty`);
|
|
1833
|
+
}
|
|
1834
|
+
for (const prop of questionProps) {
|
|
1835
|
+
const key = propKey(prop);
|
|
1836
|
+
if (!key) continue;
|
|
1837
|
+
if (key === "meta" || key === "$") {
|
|
1838
|
+
throw new Error(`ai.decision("${decisionName}"): question id "${key}" is reserved`);
|
|
1839
|
+
}
|
|
1840
|
+
assertDecisionQuestion(
|
|
1841
|
+
decisionName,
|
|
1842
|
+
key,
|
|
1843
|
+
(prop as AstNode & { value?: AstNode }).value,
|
|
1844
|
+
program,
|
|
1845
|
+
);
|
|
1846
|
+
}
|
|
1847
|
+
if (scope.ai.decisions?.[decisionName]) {
|
|
1848
|
+
throw new Error(`ai.decision("${decisionName}"): duplicate decision name`);
|
|
1849
|
+
}
|
|
1850
|
+
const hasReview = objectProp(opts, "review") !== undefined;
|
|
1851
|
+
const uncertain = stringProp(opts, "onUncertain");
|
|
1852
|
+
const abstain = uncertain === "abstain";
|
|
1853
|
+
if (hasReview === abstain) {
|
|
1854
|
+
throw new Error(
|
|
1855
|
+
`ai.decision("${decisionName}"): declare exactly one of review or onUncertain: "abstain"`,
|
|
1856
|
+
);
|
|
1857
|
+
}
|
|
1858
|
+
const autonomyNode = objectProp(opts, "autonomy");
|
|
1859
|
+
let autonomy: AiDecision["autonomy"];
|
|
1860
|
+
if (autonomyNode?.type === "ObjectExpression") {
|
|
1861
|
+
if (objectProp(autonomyNode, "audit") === undefined) {
|
|
1862
|
+
throw new Error(`ai.decision("${decisionName}"): autonomy requires audit`);
|
|
1863
|
+
}
|
|
1864
|
+
const maxError = numberProp(autonomyNode, "maxError");
|
|
1865
|
+
const audit = numberProp(autonomyNode, "audit");
|
|
1866
|
+
const risk = numberProp(autonomyNode, "risk");
|
|
1867
|
+
if (maxError === undefined || audit === undefined) {
|
|
1868
|
+
throw new Error(`ai.decision("${decisionName}"): autonomy requires maxError and audit`);
|
|
1869
|
+
}
|
|
1870
|
+
autonomy = { maxError, audit, ...(risk !== undefined ? { risk } : {}) };
|
|
1871
|
+
}
|
|
1872
|
+
const evals = stringProp(opts, "evals");
|
|
1873
|
+
const decision: AiDecision = {
|
|
1874
|
+
mode: abstain ? "abstain" : "review",
|
|
1875
|
+
questions: questionNames,
|
|
1876
|
+
...(hasReview ? { review: decisionReviewName(objectProp(opts, "review"), scope) } : {}),
|
|
1877
|
+
...(stringProp(opts, "model") ? { model: stringProp(opts, "model") } : {}),
|
|
1878
|
+
driverId: stringProp(opts, "driverId") === "typesafe" ? "typesafe" : "openrouter",
|
|
1879
|
+
...(evals !== undefined ? { evals } : {}),
|
|
1880
|
+
...(autonomy !== undefined ? { autonomy } : {}),
|
|
1881
|
+
};
|
|
1882
|
+
scope.ai.decisions = scope.ai.decisions ?? {};
|
|
1883
|
+
scope.ai.decisions[decisionName] = decision;
|
|
1884
|
+
const bindingName = enclosingConstName(call, program);
|
|
1885
|
+
if (bindingName) {
|
|
1886
|
+
scope.bindings.set(bindingName, { kind: "decision", ref: decisionName });
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
function collectAgent(call: CallExpression, program: AstNode, scope: ProjectScope): void {
|
|
1652
1891
|
const agentName = stringArg(call.arguments[0]);
|
|
1653
1892
|
const opts = objectArg(call.arguments[1]);
|
|
1654
1893
|
if (!agentName || !opts) return;
|
|
1655
1894
|
const toolsArr = arrayProp(opts, "tools");
|
|
1656
|
-
const
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1895
|
+
const entries = (toolsArr ?? [])
|
|
1896
|
+
.map((el) => agentToolEntry(el, scope))
|
|
1897
|
+
.filter((entry): entry is { name: string; approval: boolean } => entry !== undefined);
|
|
1898
|
+
const tools = entries.map((entry) => entry.name);
|
|
1899
|
+
const agentCalls = tools.filter(
|
|
1900
|
+
(name) => scope.bindings.get(name)?.kind === "agent" || scope.ai.agents?.[name],
|
|
1901
|
+
);
|
|
1902
|
+
const approvals = Object.fromEntries(
|
|
1903
|
+
entries.filter((entry) => entry.approval).map((entry) => [entry.name, true as const]),
|
|
1904
|
+
);
|
|
1664
1905
|
const agent: AiAgent = {
|
|
1665
|
-
...(tools
|
|
1906
|
+
...(tools.length > 0 ? { tools } : {}),
|
|
1907
|
+
...(Object.keys(approvals).length > 0 ? { approvals } : {}),
|
|
1666
1908
|
...(numberProp(opts, "maxSteps") !== undefined
|
|
1667
1909
|
? { maxSteps: numberProp(opts, "maxSteps") }
|
|
1668
1910
|
: {}),
|
|
1911
|
+
...(numberProp(opts, "maxDepth") !== undefined
|
|
1912
|
+
? { maxDepth: numberProp(opts, "maxDepth") }
|
|
1913
|
+
: {}),
|
|
1669
1914
|
};
|
|
1670
1915
|
scope.ai.agents = scope.ai.agents ?? {};
|
|
1671
1916
|
scope.ai.agents[agentName] = agent;
|
|
1917
|
+
const bindingName = enclosingConstName(call, program);
|
|
1918
|
+
if (bindingName) {
|
|
1919
|
+
scope.bindings.set(bindingName, {
|
|
1920
|
+
kind: "agent",
|
|
1921
|
+
ref: agentName,
|
|
1922
|
+
...(agentCalls.length > 0 ? { agentCalls } : {}),
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1672
1925
|
}
|
|
1673
1926
|
|
|
1674
1927
|
function collectMcpServer(call: CallExpression, program: AstNode, scope: ProjectScope): void {
|
|
@@ -1941,6 +2194,7 @@ function registerFlow(args: {
|
|
|
1941
2194
|
flowName: name,
|
|
1942
2195
|
file: args.file.path,
|
|
1943
2196
|
resolveCallee: (callee, file) => args.scope.fxIndex.resolve(file, callee),
|
|
2197
|
+
resolveFlowRef: (localName, file) => args.scope.fxIndex.flowRef(file, localName),
|
|
1944
2198
|
})
|
|
1945
2199
|
: {
|
|
1946
2200
|
effects: {} as Effects,
|
|
@@ -1949,6 +2203,7 @@ function registerFlow(args: {
|
|
|
1949
2203
|
cacheIneligible: false,
|
|
1950
2204
|
nondeterministic: false,
|
|
1951
2205
|
readsUserId: false,
|
|
2206
|
+
bareIrreversible: [] as ("fetch" | "send")[],
|
|
1952
2207
|
};
|
|
1953
2208
|
|
|
1954
2209
|
let effects: Effects | undefined;
|
|
@@ -2009,7 +2264,16 @@ function registerFlow(args: {
|
|
|
2009
2264
|
const line = lineAt(args.file.source, args.flowCall.start ?? 0);
|
|
2010
2265
|
flow.source = `${args.file.path}:${line}`;
|
|
2011
2266
|
|
|
2012
|
-
if (boolProp(opts, "durable"))
|
|
2267
|
+
if (boolProp(opts, "durable")) {
|
|
2268
|
+
flow.durable = true;
|
|
2269
|
+
const bare = inferred.bareIrreversible[0];
|
|
2270
|
+
if (bare !== undefined) {
|
|
2271
|
+
throw new Error(
|
|
2272
|
+
`OKE1901: durable flow "${name}" calls fx.${bare} outside fx.step. Wrap fx.fetch and fx.send in fx.step.`,
|
|
2273
|
+
);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
assertDecisionPlacement(name, flow, args.scope);
|
|
2013
2277
|
if (typeof liveFromTrigger === "string") {
|
|
2014
2278
|
flow.live = liveFromTrigger;
|
|
2015
2279
|
// Manual live surface — `.live(tableBinding)` on the trigger. Synthesize
|
|
@@ -2908,6 +3172,7 @@ function parseEffectsObject(node: AstNode | undefined): Effects | undefined {
|
|
|
2908
3172
|
const secrets = stringArrayProp(node, "secrets");
|
|
2909
3173
|
const calls = stringArrayProp(node, "calls");
|
|
2910
3174
|
const fetches = stringArrayProp(node, "fetches");
|
|
3175
|
+
const decides = stringArrayProp(node, "decides");
|
|
2911
3176
|
if (reads) effects.reads = reads as Effects["reads"];
|
|
2912
3177
|
if (writes) effects.writes = writes as Effects["writes"];
|
|
2913
3178
|
if (emits) effects.emits = emits;
|
|
@@ -2917,6 +3182,7 @@ function parseEffectsObject(node: AstNode | undefined): Effects | undefined {
|
|
|
2917
3182
|
if (secrets) effects.secrets = secrets;
|
|
2918
3183
|
if (calls) effects.calls = calls;
|
|
2919
3184
|
if (fetches) effects.fetches = fetches;
|
|
3185
|
+
if (decides) effects.decides = decides;
|
|
2920
3186
|
return effects;
|
|
2921
3187
|
}
|
|
2922
3188
|
|
package/src/compiler/fx-index.ts
CHANGED
|
@@ -26,6 +26,14 @@ export interface FxIndex {
|
|
|
26
26
|
* @param callee - Call callee node
|
|
27
27
|
*/
|
|
28
28
|
resolve(file: string, callee: AstNode): FxCalleeResolution;
|
|
29
|
+
/**
|
|
30
|
+
* Flow name behind an imported handle (`import { create as createTask }`).
|
|
31
|
+
* Absent when `localName` is not a named import of a `flow()` / `on()` export.
|
|
32
|
+
*
|
|
33
|
+
* @param file - File that contains the `fx.call`
|
|
34
|
+
* @param localName - Local binding (`createTask`)
|
|
35
|
+
*/
|
|
36
|
+
flowRef(file: string, localName: string): string | undefined;
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
interface NamedImport {
|
|
@@ -61,6 +69,8 @@ interface FileFx {
|
|
|
61
69
|
readonly functions: Map<string, AstNode>;
|
|
62
70
|
readonly imports: Map<string, ImportBinding>;
|
|
63
71
|
readonly exports: Map<string, LocalExport | Reexport>;
|
|
72
|
+
/** Local binding → `flow("name")` / `on(…, flow("name"))` literal. */
|
|
73
|
+
readonly flowNames: Map<string, string>;
|
|
64
74
|
}
|
|
65
75
|
|
|
66
76
|
const INTRINSIC_SOURCES: Readonly<
|
|
@@ -176,7 +186,29 @@ export function buildFxIndex(
|
|
|
176
186
|
return resolveExport(target, imported.imported, seen);
|
|
177
187
|
}
|
|
178
188
|
|
|
189
|
+
function flowExportName(file: string, exportName: string, seen: Set<string>): string | undefined {
|
|
190
|
+
const key = `${file}#flow:${exportName}`;
|
|
191
|
+
if (seen.has(key)) return undefined;
|
|
192
|
+
seen.add(key);
|
|
193
|
+
const indexed = byFile.get(file);
|
|
194
|
+
if (!indexed) return undefined;
|
|
195
|
+
const exported = indexed.exports.get(exportName);
|
|
196
|
+
if (!exported) return indexed.flowNames.get(exportName);
|
|
197
|
+
if (exported.kind === "local") return indexed.flowNames.get(exported.binding);
|
|
198
|
+
const target = resolveModule(file, exported.from);
|
|
199
|
+
if (!target) return undefined;
|
|
200
|
+
return flowExportName(target, exported.imported, seen);
|
|
201
|
+
}
|
|
202
|
+
|
|
179
203
|
return {
|
|
204
|
+
flowRef(file, localName) {
|
|
205
|
+
const from = normalizePosix(file);
|
|
206
|
+
const imported = byFile.get(from)?.imports.get(localName);
|
|
207
|
+
if (!imported || imported.kind !== "named") return undefined;
|
|
208
|
+
const target = resolveModule(from, imported.from);
|
|
209
|
+
if (!target) return undefined;
|
|
210
|
+
return flowExportName(target, imported.imported, new Set());
|
|
211
|
+
},
|
|
180
212
|
resolve(file, callee) {
|
|
181
213
|
const from = normalizePosix(file);
|
|
182
214
|
const seen = new Set<string>();
|
|
@@ -212,9 +244,10 @@ function indexFile(program: AstNode): FileFx {
|
|
|
212
244
|
const functions = new Map<string, AstNode>();
|
|
213
245
|
const imports = new Map<string, ImportBinding>();
|
|
214
246
|
const exports = new Map<string, LocalExport | Reexport>();
|
|
247
|
+
const flowNames = new Map<string, string>();
|
|
215
248
|
const body = (program as AstNode & { body?: AstNode[] }).body ?? [];
|
|
216
|
-
for (const stmt of body) indexStmt(stmt, functions, imports, exports);
|
|
217
|
-
return { functions, imports, exports };
|
|
249
|
+
for (const stmt of body) indexStmt(stmt, functions, imports, exports, flowNames);
|
|
250
|
+
return { functions, imports, exports, flowNames };
|
|
218
251
|
}
|
|
219
252
|
|
|
220
253
|
function indexStmt(
|
|
@@ -222,6 +255,7 @@ function indexStmt(
|
|
|
222
255
|
functions: Map<string, AstNode>,
|
|
223
256
|
imports: Map<string, ImportBinding>,
|
|
224
257
|
exports: Map<string, LocalExport | Reexport>,
|
|
258
|
+
flowNames: Map<string, string>,
|
|
225
259
|
): void {
|
|
226
260
|
if (stmt.type === "ImportDeclaration") {
|
|
227
261
|
indexImport(stmt, imports);
|
|
@@ -232,7 +266,7 @@ function indexStmt(
|
|
|
232
266
|
return;
|
|
233
267
|
}
|
|
234
268
|
if (stmt.type === "VariableDeclaration") {
|
|
235
|
-
rememberVars(stmt, functions, exports, false);
|
|
269
|
+
rememberVars(stmt, functions, exports, flowNames, false);
|
|
236
270
|
return;
|
|
237
271
|
}
|
|
238
272
|
if (stmt.type === "ExportDefaultDeclaration") {
|
|
@@ -247,7 +281,7 @@ function indexStmt(
|
|
|
247
281
|
const decl = (stmt as AstNode & { declaration?: AstNode | null }).declaration;
|
|
248
282
|
const source = stringArg((stmt as AstNode & { source?: AstNode }).source);
|
|
249
283
|
if (decl?.type === "FunctionDeclaration") rememberFunction(decl, functions, exports, true);
|
|
250
|
-
if (decl?.type === "VariableDeclaration") rememberVars(decl, functions, exports, true);
|
|
284
|
+
if (decl?.type === "VariableDeclaration") rememberVars(decl, functions, exports, flowNames, true);
|
|
251
285
|
const specifiers = (stmt as AstNode & { specifiers?: AstNode[] }).specifiers ?? [];
|
|
252
286
|
for (const spec of specifiers) {
|
|
253
287
|
const local = exportedLocalName(spec);
|
|
@@ -309,6 +343,7 @@ function rememberVars(
|
|
|
309
343
|
decl: AstNode,
|
|
310
344
|
functions: Map<string, AstNode>,
|
|
311
345
|
exports: Map<string, LocalExport | Reexport>,
|
|
346
|
+
flowNames: Map<string, string>,
|
|
312
347
|
exportIt: boolean,
|
|
313
348
|
): void {
|
|
314
349
|
const declarations = (decl as AstNode & { declarations?: AstNode[] }).declarations ?? [];
|
|
@@ -317,10 +352,29 @@ function rememberVars(
|
|
|
317
352
|
if (!name) continue;
|
|
318
353
|
const init = (item as AstNode & { init?: AstNode | null }).init;
|
|
319
354
|
if (init && isFunctionNode(unwrapValue(init))) functions.set(name, unwrapValue(init));
|
|
355
|
+
const flowName = init ? flowLiteralName(unwrapValue(init)) : undefined;
|
|
356
|
+
if (flowName) flowNames.set(name, flowName);
|
|
320
357
|
if (exportIt) exports.set(name, { kind: "local", binding: name });
|
|
321
358
|
}
|
|
322
359
|
}
|
|
323
360
|
|
|
361
|
+
/**
|
|
362
|
+
* `flow("name")` / `call("name")` / `on(trigger, flow("name"))` literal.
|
|
363
|
+
*
|
|
364
|
+
* @param node - Initializer
|
|
365
|
+
*/
|
|
366
|
+
function flowLiteralName(node: AstNode): string | undefined {
|
|
367
|
+
if (node.type !== "CallExpression") return undefined;
|
|
368
|
+
const call = node as AstNode & { callee: AstNode; arguments?: AstNode[] };
|
|
369
|
+
const callee = identifierName(call.callee);
|
|
370
|
+
if (callee === "flow" || callee === "call") return stringArg(call.arguments?.[0]);
|
|
371
|
+
if (callee === "on") {
|
|
372
|
+
const body = call.arguments?.[1];
|
|
373
|
+
return body ? flowLiteralName(unwrapValue(body)) : undefined;
|
|
374
|
+
}
|
|
375
|
+
return undefined;
|
|
376
|
+
}
|
|
377
|
+
|
|
324
378
|
function exportedLocalName(spec: AstNode): string | undefined {
|
|
325
379
|
return (
|
|
326
380
|
identifierName((spec as AstNode & { local?: AstNode }).local) ??
|
package/src/compiler/response.ts
CHANGED
|
@@ -70,6 +70,11 @@ export function encodeFailure(failure: FlowFailure): Response {
|
|
|
70
70
|
});
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/** SSE id reader. Kept off the edge bundle until a stream chunk is encoded. */
|
|
74
|
+
function loadSseId(): typeof import("../kernel/sse-id.ts") {
|
|
75
|
+
return lazyRequire(`${import.meta.dir}/../kernel`, ["sse", "id"].join("-"));
|
|
76
|
+
}
|
|
77
|
+
|
|
73
78
|
function loadFxLiveStream(): typeof import("../kernel/fx-live-stream.ts") {
|
|
74
79
|
return lazyRequire(`${import.meta.dir}/../kernel`, ["fx", "live", "stream"].join("-"));
|
|
75
80
|
}
|
|
@@ -133,6 +138,24 @@ export async function encodeExecuteResult(result: {
|
|
|
133
138
|
return encodeSuccess(result.output);
|
|
134
139
|
}
|
|
135
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Durable park thrown from a streamed agent after the interrupt frame.
|
|
143
|
+
*
|
|
144
|
+
* Duck-typed so this encoder does not import the journal module.
|
|
145
|
+
*
|
|
146
|
+
* @param err - Value thrown by the chunk iterator
|
|
147
|
+
*/
|
|
148
|
+
function isStreamPark(err: unknown): err is { readonly wakeAt: number; readonly label: string } {
|
|
149
|
+
return (
|
|
150
|
+
err instanceof Error &&
|
|
151
|
+
err.name === "JournalSuspend" &&
|
|
152
|
+
"wakeAt" in err &&
|
|
153
|
+
typeof err.wakeAt === "number" &&
|
|
154
|
+
"label" in err &&
|
|
155
|
+
typeof err.label === "string"
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
136
159
|
function encodeSseStream(carrier: JsonStreamResult): Response {
|
|
137
160
|
const encoder = new TextEncoder();
|
|
138
161
|
let finalized = false;
|
|
@@ -147,8 +170,12 @@ function encodeSseStream(carrier: JsonStreamResult): Response {
|
|
|
147
170
|
for await (const chunk of carrier.chunks) {
|
|
148
171
|
const frame = loadFxJson().isSseFrame(chunk)
|
|
149
172
|
? chunk
|
|
150
|
-
: { data: chunk as unknown, id:
|
|
173
|
+
: { data: chunk as unknown, id: loadSseId().readSseId(chunk) };
|
|
151
174
|
const lines: string[] = [];
|
|
175
|
+
if ("comment" in frame && frame.comment && frame.data === undefined) {
|
|
176
|
+
controller.enqueue(encoder.encode(`: ${frame.comment}\n\n`));
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
152
179
|
if (frame.id !== undefined) lines.push(`id: ${frame.id}`);
|
|
153
180
|
lines.push(`data: ${JSON.stringify(frame.data)}`);
|
|
154
181
|
controller.enqueue(encoder.encode(`${lines.join("\n")}\n\n`));
|
|
@@ -156,7 +183,13 @@ function encodeSseStream(carrier: JsonStreamResult): Response {
|
|
|
156
183
|
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
|
157
184
|
controller.close();
|
|
158
185
|
} catch (err) {
|
|
159
|
-
|
|
186
|
+
if (isStreamPark(err)) {
|
|
187
|
+
carrier.parked = { wakeAt: err.wakeAt, label: err.label };
|
|
188
|
+
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
|
189
|
+
controller.close();
|
|
190
|
+
} else {
|
|
191
|
+
controller.error(err);
|
|
192
|
+
}
|
|
160
193
|
} finally {
|
|
161
194
|
await finish();
|
|
162
195
|
}
|
package/src/config/index.ts
CHANGED
|
@@ -508,12 +508,30 @@ export function resolveDriverId(
|
|
|
508
508
|
env: ConfigEnv,
|
|
509
509
|
defaults?: EnvDriverMap,
|
|
510
510
|
): string | undefined {
|
|
511
|
+
return driverRefId(resolveDriverRef(map, env, defaults));
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Resolve the rich driver ref for an env (`pool`, `replicas`, `url`).
|
|
516
|
+
*
|
|
517
|
+
* Same merge rules as {@link resolveDriverId}. A string pin has no pool or
|
|
518
|
+
* replicas. With `defaults`, an unset env key keeps that driver's default
|
|
519
|
+
* pin rather than a sibling environment.
|
|
520
|
+
*
|
|
521
|
+
* @param map - Env → driver map (or bare ref)
|
|
522
|
+
* @param env - Active environment
|
|
523
|
+
* @param defaults - Real default map for this specific driver (per-key merge)
|
|
524
|
+
*/
|
|
525
|
+
export function resolveDriverRef(
|
|
526
|
+
map: EnvDriverInput | undefined,
|
|
527
|
+
env: ConfigEnv,
|
|
528
|
+
defaults?: EnvDriverMap,
|
|
529
|
+
): DriverRef | undefined {
|
|
511
530
|
const normalized = normalizeEnvDriverMap(map);
|
|
512
531
|
if (defaults) {
|
|
513
532
|
const merged = mergeEnvDriverMap(normalized, defaults);
|
|
514
|
-
return
|
|
533
|
+
return merged[env];
|
|
515
534
|
}
|
|
516
535
|
if (!normalized) return undefined;
|
|
517
|
-
|
|
518
|
-
return driverRefId(ref);
|
|
536
|
+
return normalized[env] ?? normalized.dev ?? normalized.prod ?? normalized.test;
|
|
519
537
|
}
|
package/src/console/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Console — operator plane UI + server (port 6533).
|
|
2
|
+
* Console — operator plane UI + server (port 6533). Public import: `okengine/console`.
|
|
3
3
|
*
|
|
4
4
|
* Built on `createClient<ConsoleApp>`. Every action is a real flow through `fx`;
|
|
5
5
|
* the audit log is the trace (docs/spec/console.md).
|