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/mcp/mcp.test.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { asData, isDataEnvelope, MCP_DATA_KIND } from "./data.ts";
|
|
|
15
15
|
import { authenticateMcpRequest, mintMcpSession, MCP_AUDIENCE } from "./session.ts";
|
|
16
16
|
import { createMcpServer } from "./server.ts";
|
|
17
17
|
import { createToolRuntime } from "./tools.ts";
|
|
18
|
+
import { MCP_CLIENT_PROTOCOL_VERSION, MCP_PROTOCOL_VERSION } from "./versions.ts";
|
|
18
19
|
|
|
19
20
|
const SECRET = "mcp-test-secret";
|
|
20
21
|
|
|
@@ -314,4 +315,86 @@ describe("MCP HTTP server", () => {
|
|
|
314
315
|
expect(callBody.result.structuredContent.kind).toBe("data");
|
|
315
316
|
expect(callBody.result.structuredContent.content.manifest.app).toBe("skyport");
|
|
316
317
|
});
|
|
318
|
+
|
|
319
|
+
test("initialize advertises 2024-11-05 and rejects a version the server does not implement", async () => {
|
|
320
|
+
const store = createSessionStore();
|
|
321
|
+
const issued = await mintMcpSession({
|
|
322
|
+
store,
|
|
323
|
+
secret: SECRET,
|
|
324
|
+
principalId: "op1",
|
|
325
|
+
scopes: ["console:*"],
|
|
326
|
+
});
|
|
327
|
+
const server = createMcpServer({
|
|
328
|
+
sessions: store,
|
|
329
|
+
secret: SECRET,
|
|
330
|
+
context: {
|
|
331
|
+
getManifest: () => SAMPLE_MANIFEST,
|
|
332
|
+
listRuns: async () => [],
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
const headers = {
|
|
336
|
+
host: "127.0.0.1:6535",
|
|
337
|
+
authorization: `Bearer ${issued.accessToken}`,
|
|
338
|
+
"content-type": "application/json",
|
|
339
|
+
};
|
|
340
|
+
const init = await server.fetch(
|
|
341
|
+
new Request("http://127.0.0.1:6535/mcp", {
|
|
342
|
+
method: "POST",
|
|
343
|
+
headers,
|
|
344
|
+
body: JSON.stringify({
|
|
345
|
+
jsonrpc: "2.0",
|
|
346
|
+
id: 1,
|
|
347
|
+
method: "initialize",
|
|
348
|
+
params: { protocolVersion: MCP_PROTOCOL_VERSION },
|
|
349
|
+
}),
|
|
350
|
+
}),
|
|
351
|
+
);
|
|
352
|
+
expect(init.status).toBe(200);
|
|
353
|
+
const initBody = (await init.json()) as { result: { protocolVersion: string } };
|
|
354
|
+
expect(initBody.result.protocolVersion).toBe("2024-11-05");
|
|
355
|
+
const omitted = await server.fetch(
|
|
356
|
+
new Request("http://127.0.0.1:6535/mcp", {
|
|
357
|
+
method: "POST",
|
|
358
|
+
headers,
|
|
359
|
+
body: JSON.stringify({
|
|
360
|
+
jsonrpc: "2.0",
|
|
361
|
+
id: 2,
|
|
362
|
+
method: "initialize",
|
|
363
|
+
params: {},
|
|
364
|
+
}),
|
|
365
|
+
}),
|
|
366
|
+
);
|
|
367
|
+
const omittedBody = (await omitted.json()) as { result: { protocolVersion: string } };
|
|
368
|
+
expect(omittedBody.result.protocolVersion).toBe("2024-11-05");
|
|
369
|
+
const rejected = await server.fetch(
|
|
370
|
+
new Request("http://127.0.0.1:6535/mcp", {
|
|
371
|
+
method: "POST",
|
|
372
|
+
headers,
|
|
373
|
+
body: JSON.stringify({
|
|
374
|
+
jsonrpc: "2.0",
|
|
375
|
+
id: 3,
|
|
376
|
+
method: "initialize",
|
|
377
|
+
params: { protocolVersion: MCP_CLIENT_PROTOCOL_VERSION },
|
|
378
|
+
}),
|
|
379
|
+
}),
|
|
380
|
+
);
|
|
381
|
+
expect(rejected.status).toBe(400);
|
|
382
|
+
const call = await server.fetch(
|
|
383
|
+
new Request("http://127.0.0.1:6535/mcp", {
|
|
384
|
+
method: "POST",
|
|
385
|
+
headers,
|
|
386
|
+
body: JSON.stringify({
|
|
387
|
+
jsonrpc: "2.0",
|
|
388
|
+
id: 4,
|
|
389
|
+
method: "tools/call",
|
|
390
|
+
params: {
|
|
391
|
+
name: "oke.manifest.get",
|
|
392
|
+
arguments: {},
|
|
393
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
394
|
+
},
|
|
395
|
+
}),
|
|
396
|
+
}),
|
|
397
|
+
);
|
|
398
|
+
expect(call.status).toBe(200);
|
|
399
|
+
});
|
|
317
400
|
});
|
package/src/mcp/protocol.ts
CHANGED
|
@@ -129,8 +129,14 @@ export interface McpInitializeResult {
|
|
|
129
129
|
readonly sessionId: string;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
/**
|
|
133
|
-
export
|
|
132
|
+
/** Highest protocol version the servers implement (`2024-11-05`). */
|
|
133
|
+
export {
|
|
134
|
+
MCP_PROTOCOL_VERSION,
|
|
135
|
+
MCP_SUPPORTED_PROTOCOL_VERSIONS,
|
|
136
|
+
acceptsMcpProtocolVersion,
|
|
137
|
+
negotiateMcpProtocolVersion,
|
|
138
|
+
protocolVersionOf,
|
|
139
|
+
} from "./versions.ts";
|
|
134
140
|
|
|
135
141
|
/**
|
|
136
142
|
* Parse tools/call params.
|
package/src/mcp/server.ts
CHANGED
|
@@ -18,7 +18,9 @@ import { MCP_PORT, type ServerHandle } from "../runtime/types.ts";
|
|
|
18
18
|
import { SessionError, type SessionStore } from "../auth/sessions.ts";
|
|
19
19
|
import { asData } from "./data.ts";
|
|
20
20
|
import {
|
|
21
|
-
|
|
21
|
+
acceptsMcpProtocolVersion,
|
|
22
|
+
negotiateMcpProtocolVersion,
|
|
23
|
+
protocolVersionOf,
|
|
22
24
|
parseJsonRpcRequest,
|
|
23
25
|
parseToolsCallParams,
|
|
24
26
|
rpcError,
|
|
@@ -149,10 +151,20 @@ export function createMcpServer(options: CreateMcpServerOptions): McpServer {
|
|
|
149
151
|
|
|
150
152
|
switch (rpc.method) {
|
|
151
153
|
case "initialize": {
|
|
154
|
+
const requested = protocolVersionOf(rpc.params);
|
|
155
|
+
const protocolVersion = negotiateMcpProtocolVersion(requested);
|
|
156
|
+
if (protocolVersion === undefined) {
|
|
157
|
+
return respond(
|
|
158
|
+
jsonRpcHttp(
|
|
159
|
+
rpcError(id, RpcErrorCode.invalidParams, "unsupported protocol version"),
|
|
160
|
+
400,
|
|
161
|
+
),
|
|
162
|
+
);
|
|
163
|
+
}
|
|
152
164
|
const sessionId = newMcpTransportSessionId();
|
|
153
165
|
transportSessions.add(sessionId);
|
|
154
166
|
const result: McpInitializeResult = {
|
|
155
|
-
protocolVersion
|
|
167
|
+
protocolVersion,
|
|
156
168
|
capabilities: { tools: { listChanged: false } },
|
|
157
169
|
serverInfo: { name: "okengine-mcp", version },
|
|
158
170
|
sessionId,
|
|
@@ -182,6 +194,15 @@ export function createMcpServer(options: CreateMcpServerOptions): McpServer {
|
|
|
182
194
|
);
|
|
183
195
|
}
|
|
184
196
|
case "tools/call": {
|
|
197
|
+
const requested = protocolVersionOf(rpc.params);
|
|
198
|
+
if (requested !== undefined && !acceptsMcpProtocolVersion(requested)) {
|
|
199
|
+
return respond(
|
|
200
|
+
jsonRpcHttp(
|
|
201
|
+
rpcError(id, RpcErrorCode.invalidParams, "unsupported protocol version"),
|
|
202
|
+
400,
|
|
203
|
+
),
|
|
204
|
+
);
|
|
205
|
+
}
|
|
185
206
|
const call = parseToolsCallParams(rpc.params);
|
|
186
207
|
if (!call.ok) {
|
|
187
208
|
return respond(jsonRpcHttp(rpcError(id, RpcErrorCode.invalidParams, call.message), 400));
|
package/src/mcp/session.ts
CHANGED
|
@@ -38,6 +38,8 @@ export interface MintMcpSessionOptions {
|
|
|
38
38
|
readonly secret: string;
|
|
39
39
|
readonly principalId: string;
|
|
40
40
|
readonly scopes: Iterable<string>;
|
|
41
|
+
/** Tenant stamped on the access token. Omitted tokens have no tenant. */
|
|
42
|
+
readonly tenantId?: string | null;
|
|
41
43
|
readonly now?: () => number;
|
|
42
44
|
}
|
|
43
45
|
|
|
@@ -58,6 +60,7 @@ export async function mintMcpSession(options: MintMcpSessionOptions): Promise<Is
|
|
|
58
60
|
id: options.principalId,
|
|
59
61
|
plane: "operator",
|
|
60
62
|
scopes: options.scopes,
|
|
63
|
+
...(options.tenantId !== undefined ? { tenantId: options.tenantId } : {}),
|
|
61
64
|
},
|
|
62
65
|
);
|
|
63
66
|
}
|
package/src/mcp/tools.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* confirmation token (no session-level consent cache).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { lazyRequire } from "../kernel/lazy-require.ts";
|
|
8
9
|
import type { Manifest } from "../manifest/types.ts";
|
|
9
10
|
import type { WideEvent } from "../runs/types.ts";
|
|
10
11
|
import { authorizeToolCall, MCP_TOOL_POLICIES, type McpToolPolicy } from "./authorization.ts";
|
|
@@ -22,6 +23,16 @@ export interface McpContext {
|
|
|
22
23
|
readonly getManifest: () => Manifest | null;
|
|
23
24
|
/** List runs/traces (already operator-scoped by the provider). */
|
|
24
25
|
readonly listRuns: () => Promise<readonly WideEvent[]>;
|
|
26
|
+
/** Agent runs for `oke.ai.runs.*`. Absent providers return an empty list. */
|
|
27
|
+
readonly listAgentRuns?: () => Promise<readonly unknown[]>;
|
|
28
|
+
/** One agent run plus follow events. */
|
|
29
|
+
readonly getAgentRun?: (
|
|
30
|
+
runId: string,
|
|
31
|
+
) => Promise<{ readonly run: unknown; readonly events: readonly unknown[] } | undefined>;
|
|
32
|
+
/** Pending tool approvals. Resolve is not an MCP tool. */
|
|
33
|
+
readonly listApprovals?: () => Promise<readonly unknown[]>;
|
|
34
|
+
/** Decision catalogue: state, pending count, metrics, drift. */
|
|
35
|
+
readonly listDecisions?: (tenant?: string) => Promise<readonly unknown[]>;
|
|
25
36
|
/**
|
|
26
37
|
* Safe invoke adapter. Called only after confirmation. Must NOT receive
|
|
27
38
|
* the caller's Bearer token — capabilities are passed as structured input.
|
|
@@ -144,6 +155,14 @@ export function createToolRuntime(
|
|
|
144
155
|
return tracesList(ctx, args);
|
|
145
156
|
case "oke.traces.get":
|
|
146
157
|
return tracesGet(ctx, args);
|
|
158
|
+
case "oke.ai.runs.list":
|
|
159
|
+
return aiRunsList(ctx, requester, args);
|
|
160
|
+
case "oke.ai.runs.get":
|
|
161
|
+
return aiRunsGet(ctx, requester, args);
|
|
162
|
+
case "oke.ai.approvals.list":
|
|
163
|
+
return aiApprovalsList(ctx, requester, args);
|
|
164
|
+
case "oke.decisions.list":
|
|
165
|
+
return decisionsList(ctx, requester, args);
|
|
147
166
|
case "oke.action.confirm":
|
|
148
167
|
return actionConfirm(confirm, requester, args);
|
|
149
168
|
case "oke.action.invoke":
|
|
@@ -243,6 +262,155 @@ async function tracesGet(ctx: McpContext, args: Record<string, unknown>): Promis
|
|
|
243
262
|
return okData(freezeData({ run: projectRun(run) }), "store-record");
|
|
244
263
|
}
|
|
245
264
|
|
|
265
|
+
function tenantArg(args: Record<string, unknown>): string | undefined {
|
|
266
|
+
return typeof args.tenant === "string" && args.tenant.length > 0 ? args.tenant : undefined;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function sameTenant(row: unknown, tenant: string | undefined): boolean {
|
|
270
|
+
if (!tenant) return true;
|
|
271
|
+
if (!row || typeof row !== "object") return false;
|
|
272
|
+
return (row as { tenant?: unknown }).tenant === tenant;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** `console:*` and `mcp:*` may list every tenant. A named tool scope stays on its token. */
|
|
276
|
+
function operatorTenantScope(scopes: readonly string[]): boolean {
|
|
277
|
+
return scopes.includes("console:*") || scopes.includes("mcp:*");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Tenant a read is allowed to see.
|
|
282
|
+
*
|
|
283
|
+
* The token's `tid` is the default. Naming another tenant, or listing every
|
|
284
|
+
* tenant, requires an operator scope. A non-operator token with no tenant
|
|
285
|
+
* sees nothing.
|
|
286
|
+
*/
|
|
287
|
+
function visibleTenant(
|
|
288
|
+
requester: McpRequester,
|
|
289
|
+
args: Record<string, unknown>,
|
|
290
|
+
): { readonly ok: true; readonly tenant: string | undefined } | { readonly ok: false } {
|
|
291
|
+
const requested = tenantArg(args);
|
|
292
|
+
const own = requester.claims.tid;
|
|
293
|
+
if (operatorTenantScope(requester.scopes)) {
|
|
294
|
+
return { ok: true, tenant: requested };
|
|
295
|
+
}
|
|
296
|
+
if (!own) return { ok: false };
|
|
297
|
+
if (requested !== undefined && requested !== own) return { ok: false };
|
|
298
|
+
return { ok: true, tenant: own };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function loadClassify(): typeof import("../elements/store/classify.ts") {
|
|
302
|
+
return lazyRequire(`${import.meta.dir}/../elements/store`, ["class", "ify"].join(""));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function loadRedacted(): typeof import("../kernel/redacted.ts") {
|
|
306
|
+
return lazyRequire(`${import.meta.dir}/../kernel`, ["redact", "ed"].join(""));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* PII-classified and sensitive column names, plus {@link Redacted} values.
|
|
311
|
+
* Same mask token the Console traces projection uses.
|
|
312
|
+
*/
|
|
313
|
+
function maskAiValue(manifest: Manifest | null | undefined, value: unknown): unknown {
|
|
314
|
+
return maskPiiWalk(loadRedacted().maskRedactedDeep(value), classifiedFieldNames(manifest));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function classifiedFieldNames(manifest: Manifest | null | undefined): ReadonlySet<string> {
|
|
318
|
+
const names = new Set<string>();
|
|
319
|
+
if (!manifest?.stores) return names;
|
|
320
|
+
for (const store of Object.values(manifest.stores)) {
|
|
321
|
+
for (const table of Object.values(store.tables ?? {})) {
|
|
322
|
+
for (const [col, tags] of Object.entries(table.columns ?? {})) {
|
|
323
|
+
if (tags?.pii || tags?.sensitive) loadClassify().addPiiFieldName(names, col);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return names;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function maskPiiWalk(value: unknown, fields: ReadonlySet<string>): unknown {
|
|
331
|
+
if (fields.size === 0 || value === null || value === undefined) return value;
|
|
332
|
+
if (Array.isArray(value)) return value.map((item) => maskPiiWalk(item, fields));
|
|
333
|
+
if (typeof value === "object") {
|
|
334
|
+
const out: Record<string, unknown> = {};
|
|
335
|
+
for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
|
|
336
|
+
out[key] = fields.has(key) ? loadClassify().PII_MASK : maskPiiWalk(child, fields);
|
|
337
|
+
}
|
|
338
|
+
return out;
|
|
339
|
+
}
|
|
340
|
+
return value;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function aiRunsList(
|
|
344
|
+
ctx: McpContext,
|
|
345
|
+
requester: McpRequester,
|
|
346
|
+
args: Record<string, unknown>,
|
|
347
|
+
): Promise<ToolCallResult> {
|
|
348
|
+
const limit =
|
|
349
|
+
typeof args.limit === "number" && Number.isFinite(args.limit)
|
|
350
|
+
? Math.min(200, Math.max(1, Math.floor(args.limit)))
|
|
351
|
+
: 50;
|
|
352
|
+
const scope = visibleTenant(requester, args);
|
|
353
|
+
if (!scope.ok) return okData(freezeData({ runs: [] }), "trace");
|
|
354
|
+
const all = (await ctx.listAgentRuns?.()) ?? [];
|
|
355
|
+
const runs = all
|
|
356
|
+
.filter((row) => sameTenant(row, scope.tenant))
|
|
357
|
+
.slice(0, limit)
|
|
358
|
+
.map((row) => maskAiValue(ctx.getManifest(), row));
|
|
359
|
+
return okData(freezeData({ runs }), "trace");
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async function aiRunsGet(
|
|
363
|
+
ctx: McpContext,
|
|
364
|
+
requester: McpRequester,
|
|
365
|
+
args: Record<string, unknown>,
|
|
366
|
+
): Promise<ToolCallResult> {
|
|
367
|
+
const runId = String(args.runId ?? "");
|
|
368
|
+
const scope = visibleTenant(requester, args);
|
|
369
|
+
const found = scope.ok ? await ctx.getAgentRun?.(runId) : undefined;
|
|
370
|
+
if (!found || !sameTenant(found.run, scope.ok ? scope.tenant : undefined)) {
|
|
371
|
+
return {
|
|
372
|
+
ok: false,
|
|
373
|
+
code: "not-found",
|
|
374
|
+
message: `unknown run: ${runId}`,
|
|
375
|
+
data: asData({ runId }, "error"),
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
return okData(
|
|
379
|
+
freezeData({
|
|
380
|
+
run: maskAiValue(ctx.getManifest(), found.run),
|
|
381
|
+
events: maskAiValue(ctx.getManifest(), found.events),
|
|
382
|
+
}),
|
|
383
|
+
"store-record",
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
async function aiApprovalsList(
|
|
388
|
+
ctx: McpContext,
|
|
389
|
+
requester: McpRequester,
|
|
390
|
+
args: Record<string, unknown>,
|
|
391
|
+
): Promise<ToolCallResult> {
|
|
392
|
+
const scope = visibleTenant(requester, args);
|
|
393
|
+
if (!scope.ok) return okData(freezeData({ rows: [] }), "store-record");
|
|
394
|
+
const all = (await ctx.listApprovals?.()) ?? [];
|
|
395
|
+
const rows = all
|
|
396
|
+
.filter((row) => sameTenant(row, scope.tenant))
|
|
397
|
+
.map((row) => maskAiValue(ctx.getManifest(), row));
|
|
398
|
+
return okData(freezeData({ rows }), "store-record");
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async function decisionsList(
|
|
402
|
+
ctx: McpContext,
|
|
403
|
+
requester: McpRequester,
|
|
404
|
+
args: Record<string, unknown>,
|
|
405
|
+
): Promise<ToolCallResult> {
|
|
406
|
+
const scope = visibleTenant(requester, args);
|
|
407
|
+
if (!scope.ok) return okData(freezeData({ decisions: [] }), "manifest");
|
|
408
|
+
const decisions = ((await ctx.listDecisions?.(scope.tenant)) ?? []).filter((row) =>
|
|
409
|
+
sameTenant(row, scope.tenant),
|
|
410
|
+
);
|
|
411
|
+
return okData(freezeData({ decisions: maskAiValue(ctx.getManifest(), decisions) }), "manifest");
|
|
412
|
+
}
|
|
413
|
+
|
|
246
414
|
function actionConfirm(
|
|
247
415
|
confirm: ReturnType<typeof createConfirmationGate>,
|
|
248
416
|
requester: McpRequester,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP protocol versions.
|
|
3
|
+
*
|
|
4
|
+
* The app and docs servers implement `initialize`, `ping`, `tools/list`, and
|
|
5
|
+
* `tools/call`. That is the 2024-11-05 tools surface, so they advertise
|
|
6
|
+
* {@link MCP_PROTOCOL_VERSION} and reject any other client version.
|
|
7
|
+
*
|
|
8
|
+
* The outbound client is separate. It probes external servers with
|
|
9
|
+
* {@link MCP_CLIENT_PROTOCOL_VERSION} (`_meta` on each request) and falls
|
|
10
|
+
* back to a 2024-11-05 `initialize`. That constant is not the server
|
|
11
|
+
* advertisement.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Versions the app and docs servers implement, newest last. */
|
|
15
|
+
export const MCP_SUPPORTED_PROTOCOL_VERSIONS = ["2024-11-05"] as const;
|
|
16
|
+
|
|
17
|
+
/** Highest protocol version the servers implement and advertise. */
|
|
18
|
+
export const MCP_PROTOCOL_VERSION: (typeof MCP_SUPPORTED_PROTOCOL_VERSIONS)[number] = "2024-11-05";
|
|
19
|
+
|
|
20
|
+
/** Version the outbound MCP client probes first. Not the server advertisement. */
|
|
21
|
+
export const MCP_CLIENT_PROTOCOL_VERSION = "2026-07-28";
|
|
22
|
+
|
|
23
|
+
/** `initialize` dialect the outbound client falls back to. */
|
|
24
|
+
export const MCP_LEGACY_PROTOCOL_VERSION = "2024-11-05";
|
|
25
|
+
|
|
26
|
+
const ACCEPTED: readonly string[] = MCP_SUPPORTED_PROTOCOL_VERSIONS;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Whether a client-offered protocol version is one this server implements.
|
|
30
|
+
*
|
|
31
|
+
* @param version - `initialize` or `tools/call` protocol version
|
|
32
|
+
*/
|
|
33
|
+
export function acceptsMcpProtocolVersion(version: string): boolean {
|
|
34
|
+
return ACCEPTED.includes(version);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Pick the protocol version for an `initialize` result.
|
|
39
|
+
*
|
|
40
|
+
* An omitted client version gets the highest version this server implements.
|
|
41
|
+
* A supported client version is echoed. Anything else has no overlap.
|
|
42
|
+
*
|
|
43
|
+
* @param requested - Client `protocolVersion`, when present
|
|
44
|
+
*/
|
|
45
|
+
export function negotiateMcpProtocolVersion(requested: string | undefined): string | undefined {
|
|
46
|
+
if (requested === undefined) return MCP_PROTOCOL_VERSION;
|
|
47
|
+
if (acceptsMcpProtocolVersion(requested)) return requested;
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Read `params.protocolVersion` when the client sent one.
|
|
53
|
+
*
|
|
54
|
+
* @param params - JSON-RPC params
|
|
55
|
+
*/
|
|
56
|
+
export function protocolVersionOf(params: unknown): string | undefined {
|
|
57
|
+
if (params === null || typeof params !== "object" || Array.isArray(params)) return undefined;
|
|
58
|
+
const version = (params as { protocolVersion?: unknown }).protocolVersion;
|
|
59
|
+
return typeof version === "string" && version.length > 0 ? version : undefined;
|
|
60
|
+
}
|
package/src/plugins/index.ts
CHANGED
|
@@ -24,6 +24,7 @@ export {
|
|
|
24
24
|
type MagicLinkOptions,
|
|
25
25
|
} from "./magic-link.ts";
|
|
26
26
|
export { maintenanceMode, type MaintenanceModeOptions } from "./maintenance-mode.ts";
|
|
27
|
+
export { mena, menaChannels, type MenaChannelOptions } from "./mena.ts";
|
|
27
28
|
export {
|
|
28
29
|
createPhoneStore,
|
|
29
30
|
otp,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MENA channel plugin — Taqnyat, Unifonic, and Msegat behind one plugin.
|
|
3
|
+
*
|
|
4
|
+
* Drivers stay the protocol adapters. `fx.sendOtp`, `fx.verifyOtp`, and
|
|
5
|
+
* `fx.deliverOtp` remain supported Channel methods.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { openMsegatChannel } from "../drivers/channel-msegat.ts";
|
|
9
|
+
import { openTaqnyatChannel } from "../drivers/channel-taqnyat.ts";
|
|
10
|
+
import { openTaqnyatMailChannel } from "../drivers/channel-taqnyat-mail.ts";
|
|
11
|
+
import { openTaqnyatWhatsAppChannel } from "../drivers/channel-taqnyat-whatsapp.ts";
|
|
12
|
+
import { openUnifonicChannel } from "../drivers/channel-unifonic.ts";
|
|
13
|
+
import type { ChannelDriver, ChannelOpenOptions } from "../drivers/channel-types.ts";
|
|
14
|
+
import { plugin, type PluginDef } from "../kernel/plugin.ts";
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
openMsegatChannel,
|
|
18
|
+
openTaqnyatChannel,
|
|
19
|
+
openTaqnyatMailChannel,
|
|
20
|
+
openTaqnyatWhatsAppChannel,
|
|
21
|
+
openUnifonicChannel,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Which MENA providers to open. Omitted providers are skipped. */
|
|
25
|
+
export interface MenaChannelOptions {
|
|
26
|
+
readonly sms?: ChannelOpenOptions;
|
|
27
|
+
readonly unifonic?: ChannelOpenOptions;
|
|
28
|
+
readonly msegat?: ChannelOpenOptions;
|
|
29
|
+
readonly mail?: ChannelOpenOptions;
|
|
30
|
+
readonly whatsapp?: ChannelOpenOptions;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Open the MENA drivers that have options.
|
|
35
|
+
*
|
|
36
|
+
* @param options - Per-provider credentials (`bearerToken` / `sender` / …)
|
|
37
|
+
*/
|
|
38
|
+
export function menaChannels(options: MenaChannelOptions = {}): ChannelDriver[] {
|
|
39
|
+
const drivers: ChannelDriver[] = [];
|
|
40
|
+
if (options.sms) drivers.push(openTaqnyatChannel(options.sms));
|
|
41
|
+
if (options.unifonic) drivers.push(openUnifonicChannel(options.unifonic));
|
|
42
|
+
if (options.msegat) drivers.push(openMsegatChannel(options.msegat));
|
|
43
|
+
if (options.mail) drivers.push(openTaqnyatMailChannel(options.mail));
|
|
44
|
+
if (options.whatsapp) drivers.push(openTaqnyatWhatsAppChannel(options.whatsapp));
|
|
45
|
+
return drivers;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Headline plugin. Attach with `.plug(mena())`. Drivers come from
|
|
50
|
+
* {@link menaChannels}; this registration is the product name.
|
|
51
|
+
*
|
|
52
|
+
* @param options - Unused today; reserved for plugin config
|
|
53
|
+
*/
|
|
54
|
+
export function mena(options: MenaChannelOptions = {}): PluginDef {
|
|
55
|
+
return plugin("mena", { version: "1", config: options });
|
|
56
|
+
}
|
|
@@ -19,6 +19,8 @@ describe("okengine/http static import graph", () => {
|
|
|
19
19
|
expect(forbiddenHttpGraphHits(["src/elements/store/search-runtime.ts"])).toEqual([
|
|
20
20
|
"hybrid-search runtime",
|
|
21
21
|
]);
|
|
22
|
+
expect(forbiddenHttpGraphHits(["src/kernel/fx.ts"])).toEqual(["fx runtime"]);
|
|
23
|
+
expect(forbiddenHttpGraphHits(["src/kernel/fx-decide.ts"])).toEqual([]);
|
|
22
24
|
expect(forbiddenHttpGraphHits(["src/elements/store/search-lsh.ts"])).toEqual([]);
|
|
23
25
|
expect(forbiddenHttpGraphHits(["src/kernel/app.ts"])).toEqual([]);
|
|
24
26
|
});
|
|
@@ -39,6 +39,11 @@ export const HTTP_STATIC_GRAPH_FORBIDDEN: readonly HttpGraphForbidden[] = [
|
|
|
39
39
|
specifier.includes("search-runtime.js") ||
|
|
40
40
|
/(^|\/)search-runtime$/.test(specifier),
|
|
41
41
|
},
|
|
42
|
+
{
|
|
43
|
+
id: "fx runtime",
|
|
44
|
+
test: (specifier) =>
|
|
45
|
+
specifier.endsWith("/fx.ts") || specifier.endsWith("/fx.js") || /(^|\/)fx$/.test(specifier),
|
|
46
|
+
},
|
|
42
47
|
];
|
|
43
48
|
|
|
44
49
|
/** Metafile slice this gate reads. */
|
package/src/release/measure.ts
CHANGED
|
@@ -288,7 +288,7 @@ if (!artifact) { console.error("no output"); process.exit(1); }
|
|
|
288
288
|
const raw = await artifact.arrayBuffer();
|
|
289
289
|
process.stdout.write(String(Bun.gzipSync(new Uint8Array(raw)).byteLength));
|
|
290
290
|
`;
|
|
291
|
-
const proc = Bun.spawn([
|
|
291
|
+
const proc = Bun.spawn([process.execPath, "-e", probe], {
|
|
292
292
|
cwd: ROOT,
|
|
293
293
|
stdout: "pipe",
|
|
294
294
|
stderr: "pipe",
|
|
@@ -316,20 +316,45 @@ process.stdout.write(String(Bun.gzipSync(new Uint8Array(raw)).byteLength));
|
|
|
316
316
|
* first download when a shared chunk loads with it.
|
|
317
317
|
*/
|
|
318
318
|
export async function measureClientGzipBytes(): Promise<number> {
|
|
319
|
-
const entry =
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
319
|
+
const entry = resolve(ROOT, "src/client/budget-entry.ts");
|
|
320
|
+
// `bun test` sets NODE_ENV=test. An in-process Bun.build then inlines that
|
|
321
|
+
// and the gzip drifts from `bun run budgets`. Spawn once, production, so
|
|
322
|
+
// both callers share this number.
|
|
323
|
+
const probe = `
|
|
324
|
+
const result = await Bun.build({
|
|
325
|
+
entrypoints: ${JSON.stringify([entry])},
|
|
326
|
+
minify: true,
|
|
327
|
+
target: "browser",
|
|
328
|
+
format: "esm",
|
|
329
|
+
});
|
|
330
|
+
if (!result.success) {
|
|
331
|
+
console.error(result.logs.map(String).join("\\n"));
|
|
332
|
+
process.exit(1);
|
|
333
|
+
}
|
|
334
|
+
const artifact = result.outputs[0];
|
|
335
|
+
if (!artifact) { console.error("no output"); process.exit(1); }
|
|
336
|
+
const raw = await artifact.arrayBuffer();
|
|
337
|
+
process.stdout.write(String(Bun.gzipSync(new Uint8Array(raw)).byteLength));
|
|
338
|
+
`;
|
|
339
|
+
const proc = Bun.spawn([process.execPath, "-e", probe], {
|
|
340
|
+
cwd: ROOT,
|
|
341
|
+
stdout: "pipe",
|
|
342
|
+
stderr: "pipe",
|
|
343
|
+
env: { ...process.env, NODE_ENV: "production" },
|
|
325
344
|
});
|
|
326
|
-
|
|
327
|
-
|
|
345
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
346
|
+
new Response(proc.stdout).text(),
|
|
347
|
+
new Response(proc.stderr).text(),
|
|
348
|
+
proc.exited,
|
|
349
|
+
]);
|
|
350
|
+
if (exitCode !== 0) {
|
|
351
|
+
throw new Error(`client build failed:\n${stderr || stdout}`);
|
|
328
352
|
}
|
|
329
|
-
const
|
|
330
|
-
if (!
|
|
331
|
-
|
|
332
|
-
|
|
353
|
+
const n = Number(stdout.trim());
|
|
354
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
355
|
+
throw new Error(`client measure produced ${stdout}`);
|
|
356
|
+
}
|
|
357
|
+
return n;
|
|
333
358
|
}
|
|
334
359
|
|
|
335
360
|
/**
|
|
@@ -438,7 +463,7 @@ process.stdout.write(String(ms));
|
|
|
438
463
|
`;
|
|
439
464
|
const samples: number[] = [];
|
|
440
465
|
for (let i = 0; i < 7; i++) {
|
|
441
|
-
const proc = Bun.spawn([
|
|
466
|
+
const proc = Bun.spawn([process.execPath, "-e", probe], {
|
|
442
467
|
cwd: ROOT,
|
|
443
468
|
stdout: "pipe",
|
|
444
469
|
stderr: "pipe",
|
package/src/runs/collect.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type { Trigger } from "../kernel/triggers.ts";
|
|
|
13
13
|
import type { FlowPlane } from "../manifest/types.ts";
|
|
14
14
|
import { cacheDimensionOf, type RunTelemetry } from "../kernel/run-telemetry.ts";
|
|
15
15
|
import { okid } from "../okid.ts";
|
|
16
|
-
import type { RunError, WideEvent } from "./types.ts";
|
|
16
|
+
import type { RunError, RunHttpFrame, WideEvent } from "./types.ts";
|
|
17
17
|
|
|
18
18
|
/** Inputs for {@link collectWideEvent}. */
|
|
19
19
|
export interface CollectWideEventInput {
|
|
@@ -42,6 +42,8 @@ export interface CollectWideEventInput {
|
|
|
42
42
|
readonly input?: unknown;
|
|
43
43
|
/** Flow return value when the run completed with an output. */
|
|
44
44
|
readonly output?: unknown;
|
|
45
|
+
/** HTTP wire frame when the trigger was `http`. */
|
|
46
|
+
readonly http?: RunHttpFrame;
|
|
45
47
|
/** Optional parent run id (causal chain). */
|
|
46
48
|
readonly parentId?: string;
|
|
47
49
|
/** Optional run id (generated when omitted). */
|
|
@@ -123,6 +125,7 @@ export function collectWideEvent(input: CollectWideEventInput): WideEvent {
|
|
|
123
125
|
error,
|
|
124
126
|
...(input.input !== undefined ? { input: input.input } : {}),
|
|
125
127
|
...(input.output !== undefined ? { output: input.output } : {}),
|
|
128
|
+
...(input.http !== undefined ? { http: input.http } : {}),
|
|
126
129
|
effects: [...input.ledger.entries],
|
|
127
130
|
logs: [...input.telemetry.logs],
|
|
128
131
|
durationMs,
|
package/src/runs/parquet.test.ts
CHANGED
|
@@ -12,7 +12,13 @@ import { join } from "node:path";
|
|
|
12
12
|
|
|
13
13
|
import { duckPath, openDuckDB } from "./duckdb.ts";
|
|
14
14
|
import { createRunsRuntime } from "./runtime.ts";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
readParquet,
|
|
17
|
+
rowToWideEvent,
|
|
18
|
+
wideEventToRow,
|
|
19
|
+
writeParquet,
|
|
20
|
+
type ParquetRow,
|
|
21
|
+
} from "./parquet.ts";
|
|
16
22
|
import type { WideEvent } from "./types.ts";
|
|
17
23
|
|
|
18
24
|
const temps: string[] = [];
|
|
@@ -62,6 +68,26 @@ async function writeLegacyParquet(path: string, rows: readonly ParquetRow[]): Pr
|
|
|
62
68
|
}
|
|
63
69
|
}
|
|
64
70
|
|
|
71
|
+
describe("parquet http frame", () => {
|
|
72
|
+
test("round-trips a frame and omits a null column", () => {
|
|
73
|
+
const event = sample("framed", null);
|
|
74
|
+
const framed = {
|
|
75
|
+
...event,
|
|
76
|
+
http: {
|
|
77
|
+
request: {
|
|
78
|
+
method: "GET",
|
|
79
|
+
path: "/tasks",
|
|
80
|
+
query: { limit: "20" },
|
|
81
|
+
headers: { accept: "application/json" },
|
|
82
|
+
},
|
|
83
|
+
response: { status: 200, headers: { "content-type": "application/json" } },
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
expect(rowToWideEvent(wideEventToRow(framed)).http).toEqual(framed.http);
|
|
87
|
+
expect(rowToWideEvent(wideEventToRow(event)).http).toBeUndefined();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
65
91
|
describe("parquet error_code types", () => {
|
|
66
92
|
test("readParquet unions all-null JSON error_code with VARCHAR Unauthorized", async () => {
|
|
67
93
|
const dir = await tmpDir();
|