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
|
@@ -28,6 +28,16 @@ export interface JsonStreamResult {
|
|
|
28
28
|
ready?: () => Promise<void>;
|
|
29
29
|
/** Set by the kernel to commit journal / Runs after the stream settles. */
|
|
30
30
|
finalize?: () => Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Agent result once a streamed `fx.run` settles.
|
|
33
|
+
* Absent for text and live streams.
|
|
34
|
+
*/
|
|
35
|
+
result?: Promise<unknown>;
|
|
36
|
+
/**
|
|
37
|
+
* Set when iteration parked a durable run, after `[DONE]` is queued.
|
|
38
|
+
* Finalize commits the journal as sleeping from this.
|
|
39
|
+
*/
|
|
40
|
+
parked?: { wakeAt: number; label: string };
|
|
31
41
|
}
|
|
32
42
|
|
|
33
43
|
/**
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* including Store-only apps that never enable `gate.auth.tenant`.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { tenantScopesForMember } from "../auth/tenants.ts";
|
|
9
|
+
import { tenantRoleForMember, tenantScopesForMember } from "../auth/tenants.ts";
|
|
10
10
|
import type { FlowFailure } from "./errors.ts";
|
|
11
11
|
import type { PipelineDeps } from "./pipeline.ts";
|
|
12
12
|
import type { InvocationContext } from "./hooks.ts";
|
|
@@ -41,6 +41,10 @@ export function applyPipelineTenant(
|
|
|
41
41
|
for (const scope of tenantScopesForMember(tenant.store, result.id, userId)) {
|
|
42
42
|
deps.principals.auth.scopes.add(scope);
|
|
43
43
|
}
|
|
44
|
+
const role = tenantRoleForMember(tenant.store, result.id, userId);
|
|
45
|
+
deps.principals.cacheRoles = role ? [role] : [];
|
|
46
|
+
} else {
|
|
47
|
+
deps.principals.cacheRoles = [];
|
|
44
48
|
}
|
|
45
49
|
return undefined;
|
|
46
50
|
}
|
|
@@ -14,6 +14,7 @@ import { createClockRuntime } from "../elements/clock.ts";
|
|
|
14
14
|
import { gate } from "../elements/gate.ts";
|
|
15
15
|
import { createRunsRuntime, memoryRunsDriver } from "../runs/index.ts";
|
|
16
16
|
import { oke } from "./app.ts";
|
|
17
|
+
import { HTTP_FRAME_REDACTED } from "./http-frame.ts";
|
|
17
18
|
import { flow, resetFlowSeq } from "./flow.ts";
|
|
18
19
|
import { on, resetBindings } from "./on.ts";
|
|
19
20
|
import { http } from "./triggers.ts";
|
|
@@ -196,6 +197,50 @@ describe("pipeline — Unauthorized for anonymous", () => {
|
|
|
196
197
|
});
|
|
197
198
|
});
|
|
198
199
|
|
|
200
|
+
describe("pipeline — HTTP wire frame", () => {
|
|
201
|
+
test("fetch stores query, headers, and status beside the body", async () => {
|
|
202
|
+
resetBindings();
|
|
203
|
+
resetFlowSeq();
|
|
204
|
+
|
|
205
|
+
on(
|
|
206
|
+
http.get("/tasks", { out: z.object({ ok: z.literal(true) }) }).public(),
|
|
207
|
+
flow("tasks.list", {
|
|
208
|
+
do: () => ({ ok: true as const }),
|
|
209
|
+
}),
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
const runs = createRunsRuntime({ driver: memoryRunsDriver });
|
|
213
|
+
await runs.open();
|
|
214
|
+
const app = oke({
|
|
215
|
+
name: "http-frame",
|
|
216
|
+
gate: { policies: [gate.public] },
|
|
217
|
+
runs,
|
|
218
|
+
env: "test",
|
|
219
|
+
});
|
|
220
|
+
await app.boot({ env: "test", gates: [gate.public], runs });
|
|
221
|
+
|
|
222
|
+
const res = await app.fetch(
|
|
223
|
+
new Request("http://localhost/tasks?limit=20", {
|
|
224
|
+
headers: { accept: "application/json", cookie: "sid=secret" },
|
|
225
|
+
}),
|
|
226
|
+
);
|
|
227
|
+
expect(res.status).toBe(200);
|
|
228
|
+
|
|
229
|
+
const event = (await runs.all()).find((row) => row.flow === "tasks.list");
|
|
230
|
+
expect(event?.input).toBeUndefined();
|
|
231
|
+
expect(event?.http).toMatchObject({
|
|
232
|
+
request: {
|
|
233
|
+
method: "GET",
|
|
234
|
+
path: "/tasks",
|
|
235
|
+
query: { limit: "20" },
|
|
236
|
+
headers: { cookie: HTTP_FRAME_REDACTED, accept: "application/json" },
|
|
237
|
+
},
|
|
238
|
+
response: { status: 200 },
|
|
239
|
+
});
|
|
240
|
+
await app.bootResult?.close();
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
199
244
|
describe("pipeline — evaluated gates on the run", () => {
|
|
200
245
|
test("recorded run contains evaluated chain, not just declared names", async () => {
|
|
201
246
|
resetBindings();
|
package/src/kernel/pipeline.ts
CHANGED
|
@@ -37,6 +37,11 @@ export interface PrincipalBag {
|
|
|
37
37
|
readonly tenant: {
|
|
38
38
|
id: string | null;
|
|
39
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* Membership role names for the tier-1 cache key.
|
|
42
|
+
* Gates do not read this field.
|
|
43
|
+
*/
|
|
44
|
+
cacheRoles?: string[];
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
/** Resolved identity from auth middleware / test harness. */
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ambient signal transaction for `fx.store().transaction`.
|
|
3
|
+
*
|
|
4
|
+
* Emits inside the callback stage on {@link SignalTransaction} and publish
|
|
5
|
+
* only after the SQL transaction commits.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
9
|
+
import type { SignalTransaction } from "../drivers/signal-types.ts";
|
|
10
|
+
|
|
11
|
+
const storage = new AsyncLocalStorage<SignalTransaction>();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The signal transaction wrapping the current async frame, if any.
|
|
15
|
+
*/
|
|
16
|
+
export function currentSignalTransaction(): SignalTransaction | undefined {
|
|
17
|
+
return storage.getStore();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Run `fn` with `tx` as the ambient signal transaction.
|
|
22
|
+
*
|
|
23
|
+
* @param tx - Open signal transaction
|
|
24
|
+
* @param fn - Body
|
|
25
|
+
*/
|
|
26
|
+
export function runInSignalTransaction<T>(tx: SignalTransaction, fn: () => Promise<T>): Promise<T> {
|
|
27
|
+
return storage.run(tx, fn);
|
|
28
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE id carried on a live agent event without changing its JSON payload.
|
|
3
|
+
*
|
|
4
|
+
* The follow log stores a seq. The live `fx.run` frame for that row uses the
|
|
5
|
+
* same seq as `id:` so a client can resume with `Last-Event-ID`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const sseIdBrand: unique symbol = Symbol.for("oke.sse.id");
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Copy `value` and attach an SSE id the encoder writes as `id:`.
|
|
12
|
+
* The id is not enumerable, so it is not part of the JSON payload.
|
|
13
|
+
*
|
|
14
|
+
* @param value - Event object
|
|
15
|
+
* @param id - SSE id (the stored log seq)
|
|
16
|
+
*/
|
|
17
|
+
export function withSseId<T extends object>(value: T, id: string): T {
|
|
18
|
+
const copy: T = { ...value };
|
|
19
|
+
Object.defineProperty(copy, sseIdBrand, { value: id, enumerable: false });
|
|
20
|
+
return copy;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* SSE id attached by {@link withSseId}, if any.
|
|
25
|
+
*
|
|
26
|
+
* @param value - Stream chunk
|
|
27
|
+
*/
|
|
28
|
+
export function readSseId(value: unknown): string | undefined {
|
|
29
|
+
if (typeof value !== "object" || value === null) return undefined;
|
|
30
|
+
const id = (value as { readonly [sseIdBrand]?: unknown })[sseIdBrand];
|
|
31
|
+
return typeof id === "string" ? id : undefined;
|
|
32
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `fx.store().transaction` publishes signal emits only after commit.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import type { SqlConnection } from "../drivers/types.ts";
|
|
7
|
+
import { memorySignalDriver } from "../drivers/signal-memory.ts";
|
|
8
|
+
import { signal } from "../elements/signal.ts";
|
|
9
|
+
import { store } from "../elements/store.ts";
|
|
10
|
+
import { createSqlStoreHandle } from "../elements/store/sql-session.ts";
|
|
11
|
+
import type { StoreRuntime } from "../elements/store/runtime.ts";
|
|
12
|
+
import { createFxContext } from "./fx.ts";
|
|
13
|
+
import type { SignalRuntime } from "../elements/signal/runtime.ts";
|
|
14
|
+
|
|
15
|
+
function fakeSql(): SqlConnection {
|
|
16
|
+
return {
|
|
17
|
+
driverId: "memory",
|
|
18
|
+
role: "primary",
|
|
19
|
+
async query() {
|
|
20
|
+
return [];
|
|
21
|
+
},
|
|
22
|
+
async exec() {
|
|
23
|
+
return { changes: 1 };
|
|
24
|
+
},
|
|
25
|
+
async transaction(fn) {
|
|
26
|
+
return fn(this);
|
|
27
|
+
},
|
|
28
|
+
async close() {},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("fx.store().transaction", () => {
|
|
33
|
+
test("commit publishes the staged emit; rollback drops it", async () => {
|
|
34
|
+
const ping = signal.once("ping");
|
|
35
|
+
const signals = new Map([[ping.name, ping]]);
|
|
36
|
+
const bus = await memorySignalDriver.open({ signals });
|
|
37
|
+
const got: unknown[] = [];
|
|
38
|
+
await bus.subscribe("ping", "c", async (message) => {
|
|
39
|
+
got.push(message.payload);
|
|
40
|
+
});
|
|
41
|
+
const signalRuntime: SignalRuntime = {
|
|
42
|
+
driverId: "memory",
|
|
43
|
+
get bus() {
|
|
44
|
+
return bus;
|
|
45
|
+
},
|
|
46
|
+
declarations: signals,
|
|
47
|
+
register(decl) {
|
|
48
|
+
signals.set(decl.name, decl);
|
|
49
|
+
},
|
|
50
|
+
async start() {
|
|
51
|
+
return bus;
|
|
52
|
+
},
|
|
53
|
+
emit: (name, payload, options) => bus.emit(name, payload, options),
|
|
54
|
+
deadLetters: (name) => bus.deadLetters(name),
|
|
55
|
+
live: (name, opts) => bus.live(name, opts),
|
|
56
|
+
checkLiveResume: (name, afterId) => bus.checkLiveResume(name, afterId),
|
|
57
|
+
async close() {
|
|
58
|
+
await bus.close();
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
const notes = { name: "notes" };
|
|
62
|
+
const storeRuntime = {
|
|
63
|
+
async open() {
|
|
64
|
+
return createSqlStoreHandle("sql:db", {
|
|
65
|
+
connection: fakeSql(),
|
|
66
|
+
classifications: new Map(),
|
|
67
|
+
routedRole: "primary",
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
} as unknown as StoreRuntime;
|
|
71
|
+
const db = store.sql("db");
|
|
72
|
+
const { fx } = createFxContext({
|
|
73
|
+
flow: "notes.write",
|
|
74
|
+
effects: { writes: ["sql:notes", "sql:db"], emits: ["ping"] },
|
|
75
|
+
storeRuntime,
|
|
76
|
+
signalRuntime,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await fx.store(db).transaction(async (tx) => {
|
|
80
|
+
await tx.insert(notes).values({ id: "1" });
|
|
81
|
+
await fx.emit(ping, { id: "1" });
|
|
82
|
+
});
|
|
83
|
+
await bus.drain();
|
|
84
|
+
expect(got).toEqual([{ id: "1" }]);
|
|
85
|
+
|
|
86
|
+
await expect(
|
|
87
|
+
fx.store(db).transaction(async () => {
|
|
88
|
+
await fx.emit(ping, { id: "no" });
|
|
89
|
+
throw new Error("nope");
|
|
90
|
+
}),
|
|
91
|
+
).rejects.toThrow("nope");
|
|
92
|
+
await bus.drain();
|
|
93
|
+
expect(got).toEqual([{ id: "1" }]);
|
|
94
|
+
});
|
|
95
|
+
});
|
package/src/manifest/diff.ts
CHANGED
package/src/manifest/types.ts
CHANGED
|
@@ -58,6 +58,12 @@ export type AuthApiKeysResourceRef = "auth:api-keys";
|
|
|
58
58
|
*/
|
|
59
59
|
export type AuthTenantsResourceRef = "auth:tenants";
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Agent-run journal capability for approval routes.
|
|
63
|
+
* Not a store facet — the journal driver owns the rows.
|
|
64
|
+
*/
|
|
65
|
+
export type JournalRunsResourceRef = "journal:runs";
|
|
66
|
+
|
|
61
67
|
/**
|
|
62
68
|
* Dead-letter / live-stream read capability — not a store facet.
|
|
63
69
|
* Declare on `effects.reads` (never `writes`).
|
|
@@ -101,8 +107,10 @@ export interface Effects {
|
|
|
101
107
|
| AuthApiKeysResourceRef
|
|
102
108
|
| AuthTenantsResourceRef
|
|
103
109
|
>;
|
|
104
|
-
/** Store writes, plus `"auth:api-keys"` / `"auth:tenants"`
|
|
105
|
-
writes?: Array<
|
|
110
|
+
/** Store writes, plus `"auth:api-keys"` / `"auth:tenants"` and `"journal:runs"`. */
|
|
111
|
+
writes?: Array<
|
|
112
|
+
ResourceRef | AuthApiKeysResourceRef | AuthTenantsResourceRef | JournalRunsResourceRef
|
|
113
|
+
>;
|
|
106
114
|
/** Emitted signals. */
|
|
107
115
|
emits?: SignalRef[];
|
|
108
116
|
/** Channel template sends (irreversible). */
|
|
@@ -123,6 +131,8 @@ export interface Effects {
|
|
|
123
131
|
* Hostname only (e.g. `"api.stripe.com"`), never a full URL.
|
|
124
132
|
*/
|
|
125
133
|
fetches?: FetchHostRef[];
|
|
134
|
+
/** Decision refs from `fx.decide` (irreversible, nondeterministic). */
|
|
135
|
+
decides?: string[];
|
|
126
136
|
}
|
|
127
137
|
|
|
128
138
|
/** Embedding model ref for `effects.embeds` / `fx.embed` (model name). */
|
|
@@ -475,6 +485,8 @@ export interface AiPrompt {
|
|
|
475
485
|
version?: number;
|
|
476
486
|
evals?: string;
|
|
477
487
|
budget?: AiBudget;
|
|
488
|
+
/** `1` sends one schema-mismatch follow-up. Default `0`. */
|
|
489
|
+
repair?: 0 | 1;
|
|
478
490
|
/** Ordered recovery chain of logical model names. */
|
|
479
491
|
via?: string[];
|
|
480
492
|
/** Per-command deadline (`"30s"` or milliseconds). */
|
|
@@ -487,7 +499,10 @@ export interface AiPrompt {
|
|
|
487
499
|
/** Bounded agent whose tools are flows. */
|
|
488
500
|
export interface AiAgent {
|
|
489
501
|
tools?: FlowRef[];
|
|
502
|
+
/** Tools that park for human approval. The predicate stays in author code. */
|
|
503
|
+
approvals?: Record<string, true>;
|
|
490
504
|
maxSteps?: number;
|
|
505
|
+
maxDepth?: number;
|
|
491
506
|
model?: string;
|
|
492
507
|
budget?: AiBudget;
|
|
493
508
|
}
|
|
@@ -514,9 +529,21 @@ export interface Ai {
|
|
|
514
529
|
models?: Record<string, AiModel>;
|
|
515
530
|
prompts?: Record<string, AiPrompt>;
|
|
516
531
|
agents?: Record<string, AiAgent>;
|
|
532
|
+
decisions?: Record<string, AiDecision>;
|
|
517
533
|
mcpServers?: Record<string, AiMcpServer>;
|
|
518
534
|
}
|
|
519
535
|
|
|
536
|
+
/** A declared decision. The predicate and locale function stay in author code. */
|
|
537
|
+
export interface AiDecision {
|
|
538
|
+
mode: "review" | "abstain";
|
|
539
|
+
review?: string;
|
|
540
|
+
model?: string;
|
|
541
|
+
driverId?: "openrouter" | "typesafe";
|
|
542
|
+
questions: string[];
|
|
543
|
+
evals?: string;
|
|
544
|
+
autonomy?: { maxError: number; audit: number; risk?: number };
|
|
545
|
+
}
|
|
546
|
+
|
|
520
547
|
/** Per-table metadata on a Manifest {@link Plugin}. */
|
|
521
548
|
export interface PluginTable {
|
|
522
549
|
plane?: string;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only AI and decision MCP tools.
|
|
3
|
+
*
|
|
4
|
+
* Same authorization shape as `oke.traces.*`: one of the declared scopes,
|
|
5
|
+
* no confirmation, and no resolve or promote tool.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, expect, test } from "bun:test";
|
|
9
|
+
import { createSessionStore } from "../auth/sessions.ts";
|
|
10
|
+
import { Redacted } from "../kernel/redacted.ts";
|
|
11
|
+
import type { Manifest } from "../manifest/types.ts";
|
|
12
|
+
import { authorizeToolCall } from "./authorization.ts";
|
|
13
|
+
import { mintMcpSession, authenticateMcpRequest } from "./session.ts";
|
|
14
|
+
import { createToolRuntime, type McpContext } from "./tools.ts";
|
|
15
|
+
|
|
16
|
+
const SECRET = "mcp-ai-secret";
|
|
17
|
+
|
|
18
|
+
const runA = {
|
|
19
|
+
id: "run-a",
|
|
20
|
+
agent: "support",
|
|
21
|
+
tenant: "acme",
|
|
22
|
+
status: "interrupted",
|
|
23
|
+
};
|
|
24
|
+
const runB = {
|
|
25
|
+
id: "run-b",
|
|
26
|
+
agent: "support",
|
|
27
|
+
tenant: "other",
|
|
28
|
+
status: "finished",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
describe("MCP AI and decision tools", () => {
|
|
32
|
+
test("traces scope cannot read agent runs, and there is no resolve tool", () => {
|
|
33
|
+
expect(
|
|
34
|
+
authorizeToolCall("oke.ai.runs.list", {}, ["mcp:traces:read"], { confirmed: true }).ok,
|
|
35
|
+
).toBe(false);
|
|
36
|
+
const resolve = authorizeToolCall("oke.ai.approvals.resolve", {}, ["console:*"]);
|
|
37
|
+
expect(resolve.ok).toBe(false);
|
|
38
|
+
if (resolve.ok) return;
|
|
39
|
+
expect(resolve.reason).toBe("unknown-tool");
|
|
40
|
+
const promote = authorizeToolCall("oke.decisions.promote", {}, ["mcp:*"]);
|
|
41
|
+
expect(promote.ok).toBe(false);
|
|
42
|
+
if (promote.ok) return;
|
|
43
|
+
expect(promote.reason).toBe("unknown-tool");
|
|
44
|
+
expect(
|
|
45
|
+
authorizeToolCall("oke.ai.runs.list", {}, ["console:runs:read"], { confirmed: true }).ok,
|
|
46
|
+
).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("a tenant token sees only its tenant; an operator token sees every tenant", async () => {
|
|
50
|
+
const runtime = createToolRuntime({
|
|
51
|
+
getManifest: () => ({ oke: "1.0", app: "skyport" }) as Manifest,
|
|
52
|
+
listRuns: async () => [],
|
|
53
|
+
listAgentRuns: async () => [runA, runB],
|
|
54
|
+
getAgentRun: async (runId) => {
|
|
55
|
+
const run = [runA, runB].find((row) => row.id === runId);
|
|
56
|
+
if (!run) return undefined;
|
|
57
|
+
return { run, events: [{ seq: 1, event: { type: "RUN_FINISHED" } }] };
|
|
58
|
+
},
|
|
59
|
+
listApprovals: async () => [
|
|
60
|
+
{ id: "a", tenant: "acme", tool: "refund" },
|
|
61
|
+
{ id: "b", tenant: "other", tool: "refund" },
|
|
62
|
+
],
|
|
63
|
+
listDecisions: async () => [
|
|
64
|
+
{ name: "triage", tenant: "acme", state: "learning", pending: 1, drift: false },
|
|
65
|
+
{ name: "other-call", tenant: "other", state: "learning", pending: 0, drift: false },
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
const store = createSessionStore();
|
|
69
|
+
const issued = await mintMcpSession({
|
|
70
|
+
store,
|
|
71
|
+
secret: SECRET,
|
|
72
|
+
principalId: "op",
|
|
73
|
+
scopes: ["mcp:ai:read", "mcp:decisions:read"],
|
|
74
|
+
tenantId: "acme",
|
|
75
|
+
});
|
|
76
|
+
const requester = await authenticateMcpRequest(store, SECRET, issued.accessToken);
|
|
77
|
+
|
|
78
|
+
const listed = await runtime.callTool(requester, "oke.ai.runs.list", {});
|
|
79
|
+
expect(listed.ok).toBe(true);
|
|
80
|
+
if (!listed.ok) return;
|
|
81
|
+
const runs = (listed.data.content as { runs: { id: string }[] }).runs;
|
|
82
|
+
expect(runs.map((row) => row.id)).toEqual(["run-a"]);
|
|
83
|
+
|
|
84
|
+
const foreign = await runtime.callTool(requester, "oke.ai.runs.get", { runId: "run-b" });
|
|
85
|
+
expect(foreign.ok).toBe(false);
|
|
86
|
+
const namedOther = await runtime.callTool(requester, "oke.ai.runs.list", { tenant: "other" });
|
|
87
|
+
expect(namedOther.ok).toBe(true);
|
|
88
|
+
if (!namedOther.ok) return;
|
|
89
|
+
expect((namedOther.data.content as { runs: unknown[] }).runs).toEqual([]);
|
|
90
|
+
expect(foreign.ok).toBe(false);
|
|
91
|
+
|
|
92
|
+
const got = await runtime.callTool(requester, "oke.ai.runs.get", {
|
|
93
|
+
runId: "run-a",
|
|
94
|
+
tenant: "acme",
|
|
95
|
+
});
|
|
96
|
+
expect(got.ok).toBe(true);
|
|
97
|
+
if (!got.ok) return;
|
|
98
|
+
expect((got.data.content as { events: { seq: number }[] }).events[0]?.seq).toBe(1);
|
|
99
|
+
|
|
100
|
+
const approvals = await runtime.callTool(requester, "oke.ai.approvals.list", {
|
|
101
|
+
tenant: "acme",
|
|
102
|
+
});
|
|
103
|
+
expect(approvals.ok).toBe(true);
|
|
104
|
+
if (!approvals.ok) return;
|
|
105
|
+
expect(
|
|
106
|
+
(approvals.data.content as { rows: { id: string }[] }).rows.map((row) => row.id),
|
|
107
|
+
).toEqual(["a"]);
|
|
108
|
+
|
|
109
|
+
const decisions = await runtime.callTool(requester, "oke.decisions.list", {});
|
|
110
|
+
expect(decisions.ok).toBe(true);
|
|
111
|
+
if (!decisions.ok) return;
|
|
112
|
+
expect(
|
|
113
|
+
(decisions.data.content as { decisions: { name: string }[] }).decisions.map(
|
|
114
|
+
(row) => row.name,
|
|
115
|
+
),
|
|
116
|
+
).toEqual(["triage"]);
|
|
117
|
+
|
|
118
|
+
const operatorIssued = await mintMcpSession({
|
|
119
|
+
store,
|
|
120
|
+
secret: SECRET,
|
|
121
|
+
principalId: "root",
|
|
122
|
+
scopes: ["mcp:*"],
|
|
123
|
+
});
|
|
124
|
+
const operator = await authenticateMcpRequest(store, SECRET, operatorIssued.accessToken);
|
|
125
|
+
const allRuns = await runtime.callTool(operator, "oke.ai.runs.list", {});
|
|
126
|
+
expect(allRuns.ok).toBe(true);
|
|
127
|
+
if (!allRuns.ok) return;
|
|
128
|
+
expect((allRuns.data.content as { runs: { id: string }[] }).runs.map((row) => row.id)).toEqual([
|
|
129
|
+
"run-a",
|
|
130
|
+
"run-b",
|
|
131
|
+
]);
|
|
132
|
+
const otherApprovals = await runtime.callTool(operator, "oke.ai.approvals.list", {
|
|
133
|
+
tenant: "other",
|
|
134
|
+
});
|
|
135
|
+
expect(otherApprovals.ok).toBe(true);
|
|
136
|
+
if (!otherApprovals.ok) return;
|
|
137
|
+
expect(
|
|
138
|
+
(otherApprovals.data.content as { rows: { id: string }[] }).rows.map((row) => row.id),
|
|
139
|
+
).toEqual(["b"]);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("every AI tool masks PII fields and redacted secrets", async () => {
|
|
143
|
+
const secret = Redacted.of("sk-live");
|
|
144
|
+
const manifest = {
|
|
145
|
+
oke: "1.0",
|
|
146
|
+
app: "skyport",
|
|
147
|
+
stores: {
|
|
148
|
+
app: {
|
|
149
|
+
tables: {
|
|
150
|
+
people: { columns: { email: { pii: true }, apiKey: { sensitive: true } } },
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
} as unknown as Manifest;
|
|
155
|
+
const ctx: McpContext = {
|
|
156
|
+
getManifest: () => manifest,
|
|
157
|
+
listRuns: async () => [],
|
|
158
|
+
listAgentRuns: async () => [
|
|
159
|
+
{
|
|
160
|
+
id: "run-a",
|
|
161
|
+
tenant: "acme",
|
|
162
|
+
args: { email: "a@b.co", apiKey: secret },
|
|
163
|
+
trail: [{ args: { email: "a@b.co" } }],
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
getAgentRun: async () => ({
|
|
167
|
+
run: { id: "run-a", tenant: "acme", args: { email: "a@b.co", apiKey: secret } },
|
|
168
|
+
events: [{ seq: 1, event: { type: "CUSTOM", value: { email: "a@b.co", apiKey: secret } } }],
|
|
169
|
+
}),
|
|
170
|
+
listApprovals: async () => [
|
|
171
|
+
{ id: "a", tenant: "acme", args: { email: "a@b.co", apiKey: secret } },
|
|
172
|
+
],
|
|
173
|
+
listDecisions: async () => [
|
|
174
|
+
{ name: "triage", tenant: "acme", input: { email: "a@b.co", apiKey: secret } },
|
|
175
|
+
],
|
|
176
|
+
};
|
|
177
|
+
const runtime = createToolRuntime(ctx);
|
|
178
|
+
const store = createSessionStore();
|
|
179
|
+
const issued = await mintMcpSession({
|
|
180
|
+
store,
|
|
181
|
+
secret: SECRET,
|
|
182
|
+
principalId: "op",
|
|
183
|
+
scopes: ["mcp:ai:read", "mcp:decisions:read"],
|
|
184
|
+
tenantId: "acme",
|
|
185
|
+
});
|
|
186
|
+
const requester = await authenticateMcpRequest(store, SECRET, issued.accessToken);
|
|
187
|
+
const blobs = [
|
|
188
|
+
await runtime.callTool(requester, "oke.ai.runs.list", {}),
|
|
189
|
+
await runtime.callTool(requester, "oke.ai.runs.get", { runId: "run-a" }),
|
|
190
|
+
await runtime.callTool(requester, "oke.ai.approvals.list", {}),
|
|
191
|
+
await runtime.callTool(requester, "oke.decisions.list", {}),
|
|
192
|
+
];
|
|
193
|
+
for (const result of blobs) {
|
|
194
|
+
expect(result.ok).toBe(true);
|
|
195
|
+
if (!result.ok) continue;
|
|
196
|
+
const text = JSON.stringify(result.data.content);
|
|
197
|
+
expect(text).not.toContain("a@b.co");
|
|
198
|
+
expect(text).not.toContain("sk-live");
|
|
199
|
+
expect(text).toContain("[redacted]");
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
});
|
package/src/mcp/authorization.ts
CHANGED
|
@@ -133,6 +133,72 @@ export const MCP_TOOL_POLICIES: readonly McpToolPolicy[] = [
|
|
|
133
133
|
},
|
|
134
134
|
params: [{ name: "runId", maxLength: 128 }],
|
|
135
135
|
},
|
|
136
|
+
{
|
|
137
|
+
name: "oke.ai.runs.list",
|
|
138
|
+
scopes: ["mcp:ai:read", "console:runs:read"],
|
|
139
|
+
mutability: "read",
|
|
140
|
+
description: "List agent runs as inert data. Approvals stay in the Console.",
|
|
141
|
+
inputSchema: {
|
|
142
|
+
type: "object",
|
|
143
|
+
properties: {
|
|
144
|
+
limit: { type: "integer", minimum: 1, maximum: 200 },
|
|
145
|
+
tenant: { type: "string" },
|
|
146
|
+
},
|
|
147
|
+
additionalProperties: false,
|
|
148
|
+
},
|
|
149
|
+
params: [
|
|
150
|
+
{ name: "limit", maxLength: 3 },
|
|
151
|
+
{ name: "tenant", maxLength: 128 },
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: "oke.ai.runs.get",
|
|
156
|
+
scopes: ["mcp:ai:read", "console:runs:read"],
|
|
157
|
+
mutability: "read",
|
|
158
|
+
description: "Return one agent run record and its follow events as inert data.",
|
|
159
|
+
inputSchema: {
|
|
160
|
+
type: "object",
|
|
161
|
+
properties: {
|
|
162
|
+
runId: { type: "string", minLength: 1 },
|
|
163
|
+
tenant: { type: "string" },
|
|
164
|
+
},
|
|
165
|
+
required: ["runId"],
|
|
166
|
+
additionalProperties: false,
|
|
167
|
+
},
|
|
168
|
+
params: [
|
|
169
|
+
{ name: "runId", maxLength: 128 },
|
|
170
|
+
{ name: "tenant", maxLength: 128 },
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "oke.ai.approvals.list",
|
|
175
|
+
scopes: ["mcp:ai:read", "console:runs:read"],
|
|
176
|
+
mutability: "read",
|
|
177
|
+
description: "List pending tool approvals as inert data. Resolve stays in the Console.",
|
|
178
|
+
inputSchema: {
|
|
179
|
+
type: "object",
|
|
180
|
+
properties: {
|
|
181
|
+
tenant: { type: "string" },
|
|
182
|
+
},
|
|
183
|
+
additionalProperties: false,
|
|
184
|
+
},
|
|
185
|
+
params: [{ name: "tenant", maxLength: 128 }],
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: "oke.decisions.list",
|
|
189
|
+
scopes: ["mcp:decisions:read", "console:runs:read"],
|
|
190
|
+
mutability: "read",
|
|
191
|
+
description:
|
|
192
|
+
"List decisions with state, pending count, certificate metrics, and drift. Promote stays in the CLI.",
|
|
193
|
+
inputSchema: {
|
|
194
|
+
type: "object",
|
|
195
|
+
properties: {
|
|
196
|
+
tenant: { type: "string" },
|
|
197
|
+
},
|
|
198
|
+
additionalProperties: false,
|
|
199
|
+
},
|
|
200
|
+
params: [{ name: "tenant", maxLength: 128 }],
|
|
201
|
+
},
|
|
136
202
|
{
|
|
137
203
|
name: "oke.action.invoke",
|
|
138
204
|
scopes: ["mcp:action:invoke", "console:flows:invoke"],
|
package/src/mcp/docs-server.ts
CHANGED
|
@@ -16,7 +16,9 @@ import { asData } from "./data.ts";
|
|
|
16
16
|
import { defaultDocsContentDir, loadDocsIndex, type DocsIndex } from "./docs-index.ts";
|
|
17
17
|
import { createDocsToolRuntime } from "./docs-tools.ts";
|
|
18
18
|
import {
|
|
19
|
-
|
|
19
|
+
acceptsMcpProtocolVersion,
|
|
20
|
+
negotiateMcpProtocolVersion,
|
|
21
|
+
protocolVersionOf,
|
|
20
22
|
parseJsonRpcRequest,
|
|
21
23
|
parseToolsCallParams,
|
|
22
24
|
rpcError,
|
|
@@ -94,10 +96,18 @@ export async function createDocsMcpServer(
|
|
|
94
96
|
|
|
95
97
|
switch (rpc.method) {
|
|
96
98
|
case "initialize": {
|
|
99
|
+
const requested = protocolVersionOf(rpc.params);
|
|
100
|
+
const protocolVersion = negotiateMcpProtocolVersion(requested);
|
|
101
|
+
if (protocolVersion === undefined) {
|
|
102
|
+
return jsonRpcHttp(
|
|
103
|
+
rpcError(id, RpcErrorCode.invalidParams, "unsupported protocol version"),
|
|
104
|
+
400,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
97
107
|
const sessionId = newMcpTransportSessionId();
|
|
98
108
|
transportSessions.add(sessionId);
|
|
99
109
|
const result: McpInitializeResult = {
|
|
100
|
-
protocolVersion
|
|
110
|
+
protocolVersion,
|
|
101
111
|
capabilities: { tools: { listChanged: false } },
|
|
102
112
|
serverInfo: { name: "okengine-docs-mcp", version },
|
|
103
113
|
sessionId,
|
|
@@ -124,6 +134,13 @@ export async function createDocsMcpServer(
|
|
|
124
134
|
);
|
|
125
135
|
}
|
|
126
136
|
case "tools/call": {
|
|
137
|
+
const requested = protocolVersionOf(rpc.params);
|
|
138
|
+
if (requested !== undefined && !acceptsMcpProtocolVersion(requested)) {
|
|
139
|
+
return jsonRpcHttp(
|
|
140
|
+
rpcError(id, RpcErrorCode.invalidParams, "unsupported protocol version"),
|
|
141
|
+
400,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
127
144
|
const call = parseToolsCallParams(rpc.params);
|
|
128
145
|
if (!call.ok) {
|
|
129
146
|
return jsonRpcHttp(rpcError(id, RpcErrorCode.invalidParams, call.message), 400);
|