okengine 0.23.0 → 0.23.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/package.json +1 -1
  2. package/site/content/docs/ai/mcp.mdx +4 -4
  3. package/site/content/docs/elements/channel/index.mdx +4 -4
  4. package/site/content/docs/elements/channel/receipts.mdx +7 -5
  5. package/site/content/docs/elements/channel/sms.mdx +2 -1
  6. package/site/content/docs/elements/flow/index.mdx +22 -18
  7. package/site/content/docs/elements/signal/broadcast.mdx +4 -2
  8. package/site/content/docs/elements/signal/index.mdx +15 -14
  9. package/site/content/docs/plugins/otp.mdx +3 -3
  10. package/site/content/docs/reference/configuration.mdx +16 -0
  11. package/site/content/docs/reference/errors.mdx +31 -19
  12. package/site/content/docs/reference/fx.mdx +12 -4
  13. package/src/auth/tenants.ts +17 -0
  14. package/src/cli/doctor-diff.test.ts +29 -1
  15. package/src/cli/doctor-diff.ts +35 -0
  16. package/src/cli/manifest-pr-diff.ts +16 -0
  17. package/src/compiler/aot.test.ts +3 -2
  18. package/src/compiler/aot.ts +45 -1
  19. package/src/compiler/dynamic.ts +1 -1
  20. package/src/compiler/effects-fetch.test.ts +77 -0
  21. package/src/compiler/effects-infer.ts +87 -16
  22. package/src/compiler/effects-join.test.ts +48 -0
  23. package/src/compiler/extract.test.ts +28 -0
  24. package/src/compiler/extract.ts +11 -1
  25. package/src/compiler/fx-index.ts +58 -4
  26. package/src/compiler/http-parse.test.ts +101 -0
  27. package/src/compiler/http-parse.ts +188 -9
  28. package/src/compiler/interpret.ts +25 -2
  29. package/src/console/index.ts +1 -1
  30. package/src/console/ui-next/dist/assets/{access-page-DDKkhT9v.js → access-page-zBMhZnFm.js} +1 -1
  31. package/src/console/ui-next/dist/assets/{agent-disclosure-62Xts8Xi.js → agent-disclosure-CBj9f7Ju.js} +1 -1
  32. package/src/console/ui-next/dist/assets/{cache-glyph-DyeoKHKA.js → cache-glyph-D3MY49h3.js} +1 -1
  33. package/src/console/ui-next/dist/assets/{call-pii-button-DvfpLXsU.js → call-pii-button-L9JxdBhp.js} +1 -1
  34. package/src/console/ui-next/dist/assets/{collapsible-BY03SeCg.js → collapsible-BvFaX-Zt.js} +1 -1
  35. package/src/console/ui-next/dist/assets/{decisions-page-CgkKlA56.js → decisions-page-DgB76m-X.js} +1 -1
  36. package/src/console/ui-next/dist/assets/{duration-tone-Y6HLjyJ5.js → duration-tone-l1DSJ2Vz.js} +1 -1
  37. package/src/console/ui-next/dist/assets/{flows-page-DLPfNy-_.js → flows-page-Bb9t1lu6.js} +1 -1
  38. package/src/console/ui-next/dist/assets/{highlighted-json-D7nNzpgB.js → highlighted-json-DMvsi3Ti.js} +1 -1
  39. package/src/console/ui-next/dist/assets/{http-method-DdL19zzh.js → http-method-BwhJBTcQ.js} +1 -1
  40. package/src/console/ui-next/dist/assets/{index-C0lc_s9d.js → index-RhV2jT_7.js} +3 -3
  41. package/src/console/ui-next/dist/assets/{observability-page-CRlcyLCC.js → observability-page-BRfUILBq.js} +1 -1
  42. package/src/console/ui-next/dist/assets/{replica-lag-Bb3FWUu9.js → replica-lag-BsceAzIG.js} +1 -1
  43. package/src/console/ui-next/dist/assets/{request-meta-DYBMWhX8.js → request-meta-C6lTyCXp.js} +1 -1
  44. package/src/console/ui-next/dist/assets/{store-page-CcE-SXC8.js → store-page-YmE806bJ.js} +1 -1
  45. package/src/console/ui-next/dist/assets/{trace-detail-sheet-CKMSEQ4s.js → trace-detail-sheet-Di4irS49.js} +1 -1
  46. package/src/console/ui-next/dist/assets/{tree-expand-toggle-QOfk0M0H.js → tree-expand-toggle-DYeL6Rye.js} +1 -1
  47. package/src/console/ui-next/dist/assets/{units-page-DKC1CDDd.js → units-page-07agasRA.js} +1 -1
  48. package/src/console/ui-next/dist/assets/{vault-page-B54v4Yit.js → vault-page-CHBck4n_.js} +1 -1
  49. package/src/console/ui-next/dist/index.html +1 -1
  50. package/src/console/ui-next/seed-parked-approval.ts +5 -1
  51. package/src/drivers/journal-postgres.test.ts +106 -2
  52. package/src/drivers/journal-postgres.ts +475 -14
  53. package/src/drivers/postgres.test.ts +60 -0
  54. package/src/drivers/postgres.ts +79 -5
  55. package/src/drivers/signal-compete.test.ts +238 -0
  56. package/src/drivers/signal-postgres.test.ts +170 -0
  57. package/src/drivers/signal-postgres.ts +486 -213
  58. package/src/drivers/signal-redis.test.ts +224 -0
  59. package/src/drivers/signal-redis.ts +934 -93
  60. package/src/drivers/signal-types.ts +12 -0
  61. package/src/elements/ai/approval.test.ts +15 -3
  62. package/src/elements/ai/approval.ts +15 -9
  63. package/src/elements/ai/mcp-protocol.ts +5 -6
  64. package/src/elements/ai/runtime.ts +20 -19
  65. package/src/elements/channel/runtime.ts +42 -4
  66. package/src/elements/channel/sql-ledger.test.ts +404 -0
  67. package/src/elements/channel/sql-ledger.ts +395 -0
  68. package/src/elements/clock/chaos-child.ts +12 -2
  69. package/src/elements/clock/durable.ts +11 -0
  70. package/src/elements/signal/runtime.ts +9 -0
  71. package/src/elements/signal.test.ts +1 -0
  72. package/src/elements/store/cache-bus.test.ts +121 -0
  73. package/src/elements/store/cache-bus.ts +278 -0
  74. package/src/elements/store/cache.test.ts +179 -15
  75. package/src/elements/store/cache.ts +357 -51
  76. package/src/elements/store/prepare-row.test.ts +6 -2
  77. package/src/elements/store/runtime.ts +44 -2
  78. package/src/elements/store/sql-errors.test.ts +131 -0
  79. package/src/elements/store/sql-errors.ts +39 -16
  80. package/src/elements/store/sql-nested-tx.test.ts +344 -0
  81. package/src/elements/store/sql-session.test.ts +2 -0
  82. package/src/elements/store/sql-session.ts +275 -9
  83. package/src/elements/store.ts +1 -0
  84. package/src/kernel/app.ts +207 -43
  85. package/src/kernel/auto-cache.test.ts +228 -0
  86. package/src/kernel/auto-registry.test.ts +5 -1
  87. package/src/kernel/boot-bind/channel.ts +63 -2
  88. package/src/kernel/boot-bind/honor-config.test.ts +40 -10
  89. package/src/kernel/boot-bind/signal.ts +72 -25
  90. package/src/kernel/boot-bind/store.ts +16 -1
  91. package/src/kernel/boot.test.ts +2 -2
  92. package/src/kernel/boot.ts +54 -10
  93. package/src/kernel/boundary-contract.ts +7 -0
  94. package/src/kernel/builtin-errors.ts +2 -0
  95. package/src/kernel/call.test.ts +1 -0
  96. package/src/kernel/errors-compiler.ts +11 -0
  97. package/src/kernel/errors-text.ts +16 -0
  98. package/src/kernel/errors.ts +8 -0
  99. package/src/kernel/flow.ts +33 -3
  100. package/src/kernel/fx-call-types.test.ts +32 -0
  101. package/src/kernel/fx-decide.ts +11 -7
  102. package/src/kernel/fx-sql-handle.ts +52 -7
  103. package/src/kernel/fx.test.ts +1 -3
  104. package/src/kernel/fx.ts +27 -16
  105. package/src/kernel/horizontal-child.ts +54 -1
  106. package/src/kernel/horizontal.integration.test.ts +131 -7
  107. package/src/kernel/idempotency.test.ts +4 -4
  108. package/src/kernel/journal-boot.test.ts +83 -6
  109. package/src/kernel/journal.test.ts +158 -0
  110. package/src/kernel/journal.ts +515 -54
  111. package/src/kernel/pipeline-tenant.ts +5 -1
  112. package/src/kernel/pipeline.ts +5 -0
  113. package/src/kernel/signal-tx.ts +28 -0
  114. package/src/kernel/store-transaction.test.ts +95 -0
  115. package/src/mcp/authorization.ts +5 -11
  116. package/src/mcp/confirmation.ts +203 -38
  117. package/src/mcp/docs-server.ts +26 -4
  118. package/src/mcp/index.ts +4 -0
  119. package/src/mcp/mcp.test.ts +404 -33
  120. package/src/mcp/protocol.ts +8 -2
  121. package/src/mcp/server.ts +31 -5
  122. package/src/mcp/session.ts +7 -0
  123. package/src/mcp/tools.ts +68 -19
  124. package/src/mcp/versions.ts +60 -0
  125. package/src/plugins/index.ts +1 -0
  126. package/src/plugins/mena.ts +56 -0
  127. package/src/runtime/bun.ts +5 -0
  128. package/src/runtime/types.ts +5 -0
package/src/mcp/tools.ts CHANGED
@@ -12,7 +12,9 @@ import { authorizeToolCall, MCP_TOOL_POLICIES, type McpToolPolicy } from "./auth
12
12
  import {
13
13
  createConfirmationGate,
14
14
  MCP_CONFIRM_PHRASE,
15
+ type ConfirmationGate,
15
16
  type ConfirmationGateOptions,
17
+ type ConfirmIssueResult,
16
18
  } from "./confirmation.ts";
17
19
  import { asData, freezeData, type McpDataEnvelope } from "./data.ts";
18
20
  import type { McpRequester } from "./session.ts";
@@ -68,6 +70,9 @@ export type ToolCallResult =
68
70
  /**
69
71
  * Create the tool runtime bound to a context + confirmation gate.
70
72
  *
73
+ * `listTools` receives the requester and omits tools their scopes cannot call.
74
+ * Confirmation is not required to appear in the list; `callTool` still enforces it.
75
+ *
71
76
  * @param ctx - Manifest / runs / action adapters
72
77
  * @param confirmOptions - Confirmation gate options
73
78
  */
@@ -75,7 +80,7 @@ export function createToolRuntime(
75
80
  ctx: McpContext,
76
81
  confirmOptions: ConfirmationGateOptions = {},
77
82
  ): {
78
- readonly listTools: () => readonly McpToolPolicy[];
83
+ readonly listTools: (requester: McpRequester) => readonly McpToolPolicy[];
79
84
  readonly callTool: (
80
85
  requester: McpRequester,
81
86
  name: string,
@@ -87,7 +92,14 @@ export function createToolRuntime(
87
92
  const confirm = createConfirmationGate(confirmOptions);
88
93
 
89
94
  return {
90
- listTools: () => MCP_TOOL_POLICIES,
95
+ listTools(requester) {
96
+ // Same scope check as authorizeToolCall. Listing ignores confirmation;
97
+ // a later call to a write tool still requires a token.
98
+ return MCP_TOOL_POLICIES.filter((policy) => {
99
+ const decision = authorizeToolCall(policy.name, {}, requester.scopes, { confirmed: true });
100
+ return decision.ok;
101
+ });
102
+ },
91
103
  confirmationSize: () => confirm.size(),
92
104
  async callTool(requester, name, args) {
93
105
  const policy = MCP_TOOL_POLICIES.find((p) => p.name === name);
@@ -113,15 +125,42 @@ export function createToolRuntime(
113
125
  }
114
126
 
115
127
  // Write path: consume a fresh per-call confirmation (no session cache).
128
+ // A call with no token is rejected and returns an opaque confirmationId
129
+ // bound to this requester — never the token.
116
130
  if (policy?.mutability === "write") {
117
131
  const token = typeof args.confirmToken === "string" ? args.confirmToken : "";
118
132
  const phrase = typeof args.confirmation === "string" ? args.confirmation : "";
119
133
  const reason = typeof args.reason === "string" ? args.reason : "";
120
134
  const boundArgs = stripConfirmFields(args);
135
+ if (token.length === 0) {
136
+ const opened = confirm.open({
137
+ tool: name,
138
+ args: boundArgs,
139
+ target: {
140
+ principalId: requester.principalId,
141
+ sid: requester.sessionId,
142
+ },
143
+ });
144
+ return {
145
+ ok: false,
146
+ code: "forbidden",
147
+ message: "write tool requires fresh human confirmation (missing)",
148
+ data: asData(
149
+ {
150
+ tool: name,
151
+ confirmationId: opened.confirmationId,
152
+ requiredPhrase: MCP_CONFIRM_PHRASE,
153
+ confirmVia: "oke.action.confirm",
154
+ reason: "missing",
155
+ },
156
+ "error",
157
+ ),
158
+ };
159
+ }
121
160
  const consumed = confirm.consume({
122
161
  tool: name,
123
162
  args: boundArgs,
124
- principalId: requester.principalId,
163
+ consumerSid: requester.sessionId,
125
164
  token,
126
165
  phrase,
127
166
  reason,
@@ -412,28 +451,23 @@ async function decisionsList(
412
451
  }
413
452
 
414
453
  function actionConfirm(
415
- confirm: ReturnType<typeof createConfirmationGate>,
454
+ confirm: ConfirmationGate,
416
455
  requester: McpRequester,
417
456
  args: Record<string, unknown>,
418
457
  ): ToolCallResult {
419
- const tool = String(args.tool ?? "");
420
- const reason = String(args.reason ?? "");
421
- const toolArgs =
422
- args.args !== null && typeof args.args === "object" && !Array.isArray(args.args)
423
- ? (args.args as Record<string, unknown>)
424
- : {};
425
- const issued = confirm.request({
426
- tool,
427
- args: toolArgs,
428
- principalId: requester.principalId,
458
+ const confirmationId = typeof args.confirmationId === "string" ? args.confirmationId : "";
459
+ const reason = typeof args.reason === "string" ? args.reason : "";
460
+ const issued = confirm.issue({
461
+ confirmationId,
429
462
  reason,
463
+ callerSid: requester.sessionId,
430
464
  });
431
- if ("error" in issued) {
465
+ if (!issued.ok) {
432
466
  return {
433
467
  ok: false,
434
- code: "invalid",
435
- message: "reason must be at least 3 characters",
436
- data: asData({ error: issued.error }, "error"),
468
+ code: issued.reason === "reason-short" ? "invalid" : "forbidden",
469
+ message: confirmIssueMessage(issued.reason),
470
+ data: asData({ error: issued.reason }, "error"),
437
471
  };
438
472
  }
439
473
  return okData(
@@ -443,12 +477,27 @@ function actionConfirm(
443
477
  phrase: MCP_CONFIRM_PHRASE,
444
478
  expiresAt: issued.expiresAt,
445
479
  notice:
446
- "Present confirmToken + confirmation phrase on the write call. Tokens are single-use; there is no session consent cache.",
480
+ "Present confirmToken + confirmation phrase on the write call from the session that requested it. Tokens are single-use; the issuer session cannot consume them.",
447
481
  },
448
482
  "action-result",
449
483
  );
450
484
  }
451
485
 
486
+ function confirmIssueMessage(reason: Extract<ConfirmIssueResult, { ok: false }>["reason"]): string {
487
+ switch (reason) {
488
+ case "reason-short":
489
+ return "reason must be at least 3 characters";
490
+ case "same-session":
491
+ return "confirmation must be issued by a different auth session";
492
+ case "unknown":
493
+ return "unknown confirmation";
494
+ case "expired":
495
+ return "confirmation expired";
496
+ case "already-issued":
497
+ return "confirmation already issued";
498
+ }
499
+ }
500
+
452
501
  async function actionInvoke(
453
502
  ctx: McpContext,
454
503
  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
+ }
@@ -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
+ }
@@ -149,6 +149,7 @@ export function serveBunHttp(options: {
149
149
  readonly fetch: (request: Request) => Response | Promise<Response>;
150
150
  readonly routes?: Record<string, MethodHandlers>;
151
151
  readonly id?: string;
152
+ readonly maxRequestBodySize?: number;
152
153
  }): ReturnType<typeof Bun.serve> {
153
154
  const fetch = holdSseIdle(
154
155
  typeof options.fetch === "function"
@@ -158,6 +159,7 @@ export function serveBunHttp(options: {
158
159
  const base = {
159
160
  port: options.port,
160
161
  hostname: options.hostname,
162
+ maxRequestBodySize: options.maxRequestBodySize ?? 1_048_576,
161
163
  fetch,
162
164
  ...(options.id !== undefined ? { id: options.id } : {}),
163
165
  };
@@ -185,6 +187,9 @@ function listenBun(app: FetchApp, options?: ServeOptions): ServerHandle {
185
187
  fetch: fetchHandler,
186
188
  routes,
187
189
  ...(options?.id !== undefined ? { id: options.id } : {}),
190
+ ...(options?.maxRequestBodySize !== undefined
191
+ ? { maxRequestBodySize: options.maxRequestBodySize }
192
+ : {}),
188
193
  });
189
194
 
190
195
  const closeIdle = (
@@ -72,6 +72,11 @@ export interface ServeOptions {
72
72
  * never replaces the mandatory check.
73
73
  */
74
74
  readonly allowedHosts?: readonly string[];
75
+ /**
76
+ * Bun.serve body backstop in bytes. Default 1 MiB. Raise it together with
77
+ * `maxRequestBodySize` when a route accepts a larger body.
78
+ */
79
+ readonly maxRequestBodySize?: number;
75
80
  }
76
81
 
77
82
  /** Handle returned by {@link Runtime.serve}. */