History

feat(cloudflare): hibernatable MCP Durable Object, old impl removed (#1178)

* feat(cloudflare): serve MCP sessions with Agents * feat(execution): add paused execution introspection API Expose pausedExecutionCount and hasPausedExecutions on ExecutionEngine so Cloudflare MCP session DOs can distinguish idle cleanup from paused continuations. * feat(cloudflare): extend MCP session lease while paused continuations exist Add shared alarm policy so idle sessions still destroy after 5 minutes, but sessions with in-memory paused executions reschedule a 10-minute lease instead of tearing down the runtime. Preserves the legacy unaddressable-alarm guard. * fix(cloud): forward paused execution introspection * fix(cloudflare): cap paused-session lease at one approval window (greptile) The extend_paused_lease branch rescheduled the alarm unconditionally, so a session whose paused execution was never resumed (browser tab closed, client disconnect) would extend the lease forever and keep the Durable Object alive and billable indefinitely. Bound the lease to a single approval window: once idle exceeds SESSION_TIMEOUT_MS + PAUSED_EXECUTION_LEASE_MS (15 min), the session is torn down regardless of outstanding paused work. By that point the browser approval wait (tool-server, 10 min) has already timed out, so the paused execution is no longer resumable. idleMs grows across extensions because activity is not marked during the wait, so decideSessionAlarm stays pure. Also add the missing coverage: idle_within_timeout wins regardless of paused work, and the new cap forces destroy_idle_session. * chore: update lockfile * fix(mcp): settle approval pauses under hibernation Add host-owned paused execution hooks so Cloudflare MCP sessions can hold a bounded pending-approval lease, keep active POST requests alive until their response drains, and release hibernation blockers on resume or timeout. Also bound the approval wait window and resolve OpenAPI-backed annotations with point lookups so large specs do not reset the Durable Object before returning a pause envelope. This is the upstream port of the core runtime pieces from aryaSaatvik/executor#56. The execution-history payload compaction from that PR is omitted because this upstream branch does not contain that plugin. * refactor(mcp): align pause lifecycle telemetry Remove MCP host console logging wrappers and rely on existing debug logging plus span annotations for pause lifecycle state. Keep Cloudflare Durable Object logs focused on exceptional boundary events by dropping happy-path active POST and pending approval lease messages. * fix(mcp): guard approval timeout races Handle malformed active POST metadata without failing the Agent connection. Avoid overwriting a stored browser approval on timeout, and only resume timed-out executions directly when no MCP waiter is already registered. * refactor(cloudflare): remove legacy MCP session DO and finish cloud migration The hibernatable Agent-based MCP Durable Object now fully replaces the old bespoke session DO. This removes the superseded implementation instead of leaving it alongside the new one: - delete the old McpSessionDOBase + its worker transport and request-id queue (nothing extends it anymore; both hosts' concrete McpSessionDO already extend McpAgentSessionDOBase) - finish the cloud migration: cloud only kept the legacy McpServingRoutes session-store dispatch to publish its OAuth discovery docs. Make mcp.sessions optional and add a discovery-only McpDiscoveryRoutes layer so a host whose /mcp transport lives outside the envelope (the Agent bridge) can serve just the discovery docs. Cloud now wires only the auth seam. - drop the now-dead cloud session-store, seams, response-peek, and reporter wiring plus the stale package exports Net: the old impl and the half-migrated cloud envelope path are gone. Co-authored-by: Saatvik Arya <aryasaatvik@gmail.com> * style: oxfmt two react files left unformatted by the merge * fix: regenerate bun.lock to match merged manifests (frozen install was failing) * fix: reconcile bun.lock against main pins plus branch deps (frozen install passes) ---------

Rhys Sullivan committed 4 months ago

Commit 5d3648d1 parent 9e01ee141 files · +1866 −2499Browse files

Unified · Split

41 files
  1. apps/cloud/package.json +0 −1
  2. apps/cloud/src/api/protected.test.ts +2 −0
  3. apps/cloud/src/app.ts +6 −3
  4. apps/cloud/src/engine/execution-usage.ts +2 −0
  5. apps/cloud/src/mcp/agent-handler.ts +163 −0
  6. apps/cloud/src/mcp/index.ts +9 −15
  7. apps/cloud/src/mcp/mount.ts +14 −76
  8. apps/cloud/src/mcp/reporter.ts +0 −24
  9. apps/cloud/src/mcp/session-durable-object.ts +8 −7
  10. apps/cloud/src/mcp/session-store.ts +0 −35
  11. apps/cloud/src/mcp/worker-transport.test.ts +0 −196
  12. apps/cloud/src/server.ts +15 −0
  13. apps/host-cloudflare/src/app.ts +12 −15
  14. apps/host-cloudflare/src/mcp/agent-handler.ts +153 −0
  15. apps/host-cloudflare/src/mcp/index.ts +4 −64
  16. apps/host-cloudflare/src/mcp/session-durable-object.ts +9 −8
  17. apps/host-cloudflare/src/mcp/session-store.ts +0 −43
  18. apps/host-cloudflare/src/worker.e2e.node.test.ts +69 −4
  19. apps/host-cloudflare/src/worker.ts +18 −10
  20. bun.lock +268 −27
  21. packages/core/api/src/server/executor-app.ts +13 −2
  22. packages/core/execution/src/engine.test.ts +15 −0
  23. packages/core/execution/src/engine.ts +8 −0
  24. packages/core/execution/src/promise.ts +4 −0
  25. packages/hosts/cloudflare/package.json +4 −20
  26. packages/hosts/cloudflare/src/mcp/agent-session-durable-object.ts +788 −0
  27. packages/hosts/cloudflare/src/mcp/request-id-queue.ts +0 −125
  28. packages/hosts/cloudflare/src/mcp/response-peek.ts +0 −263
  29. packages/hosts/cloudflare/src/mcp/seams.ts +0 −31
  30. packages/hosts/cloudflare/src/mcp/session-alarm-policy.test.ts +58 −0
  31. packages/hosts/cloudflare/src/mcp/session-alarm-policy.ts +47 −0
  32. packages/hosts/cloudflare/src/mcp/session-durable-object.ts +0 −924
  33. packages/hosts/cloudflare/src/mcp/session-store.test.ts +0 −226
  34. packages/hosts/cloudflare/src/mcp/session-store.ts +0 −261
  35. packages/hosts/cloudflare/src/mcp/worker-transport.ts +0 −106
  36. packages/hosts/mcp/src/envelope.test.ts +43 −0
  37. packages/hosts/mcp/src/envelope.ts +24 −0
  38. packages/hosts/mcp/src/index.ts +1 −1
  39. packages/hosts/mcp/src/tool-server.test.ts +49 −1
  40. packages/hosts/mcp/src/tool-server.ts +54 −4
  41. packages/plugins/openapi/src/sdk/backing.ts +6 −7
apps/cloud/package.json+0−1
@@ -67,7 +67,6 @@ · 66 unmodified lines
6767 "@tanstack/react-router": "catalog:",
6868 "@tanstack/react-start": "catalog:",
6969 "@workos-inc/node": "^8.11.1",
70− "agents": "^0.10.0",
7170 "autumn-js": "^1.2.8",
7271 "drizzle-orm": "catalog:",
7372 "effect": "catalog:",
apps/cloud/src/api/protected.test.ts+2−0
@@ -17,6 +17,8 @@ const makeBaseEngine = (): ExecutionEngine => · 16 unmodified lines
1717 result: { result: "ok", logs: [] },
1818 }),
1919 getPausedExecution: () => Effect.succeed(null),
20+ pausedExecutionCount: () => Effect.succeed(0),
21+ hasPausedExecutions: () => Effect.succeed(false),
2022 getDescription: Effect.succeed("desc"),
2123 }) as ExecutionEngine;
2224
apps/cloud/src/app.ts+6−3
@@ -12,7 +12,7 @@ import { workosAccountMiddleware } from "./account/account-api"; · 11 unmodified lines
1212 import { ApiKeyService } from "./auth/api-keys";
1313 import { cloudIdentityFailureStrategy, workosIdentityLayer } from "./auth/workos-auth-provider";
1414 import { DbService } from "./db/db";
15−import { cloudMcpAuth, cloudMcpReporter, cloudMcpSessions } from "./mcp";
15+import { cloudMcpAuth } from "./mcp";
1616 import { McpSessionDO } from "./mcp/session-durable-object";
1717 import { ErrorCaptureLive } from "./observability";
1818 import { AutumnService } from "./extensions/billing/service";
@@ -89,10 +89,13 @@ const { appLayer, toWebHandler, mcpExport } = ExecutorApp.make({ · 70 unmodified lines
8989 // Billing-as-extension #1: the usage-metering decorator (reads AutumnService).
9090 decorator: CloudMeteringEngineDecorator,
9191 },
92+ // No `sessions` seam: `server.ts` intercepts `/mcp` transport for the
93+ // hibernatable Agent bridge, so the app envelope mounts only cloud's OAuth
94+ // discovery docs (via the `auth` seam). No `reporter` either: it only wrapped
95+ // the envelope's `/mcp` transport route, which cloud no longer serves here
96+ // (the Agent DO captures its own defects via Sentry in `server.ts`).
9297 mcp: {
9398 auth: cloudMcpAuth,
94− sessions: cloudMcpSessions,
95− reporter: cloudMcpReporter,
9699 },
97100 plugins: { provider: CloudPluginsProvider, config: CloudHostConfig },
98101 errorCapture: ErrorCaptureLive,
apps/cloud/src/engine/execution-usage.ts+2−0
@@ -19,5 +19,7 @@ export const withExecutionUsageTracking = <E extends Cause.YieldableError>( · 18 unmodified lines
1919 // resume doesn't count as usage
2020 resume: (executionId, response) => engine.resume(executionId, response),
2121 getPausedExecution: (executionId) => engine.getPausedExecution(executionId),
22+ pausedExecutionCount: () => engine.pausedExecutionCount(),
23+ hasPausedExecutions: () => engine.hasPausedExecutions(),
2224 getDescription: engine.getDescription,
2325 });
apps/cloud/src/mcp/agent-handler.ts+163−0
@@ -0,0 +1,163 @@
1+import { Effect, Predicate } from "effect";
2+
3+import {
4+ McpAuthProvider,
5+ jsonRpcErrorBody,
6+ defaultMcpResource,
7+ type AuthOutcome,
8+ type McpResource,
9+} from "@executor-js/host-mcp";
10+import {
11+ currentPropagationHeaders,
12+ readElicitationMode,
13+ withVerifiedIdentityHeaders,
14+} from "@executor-js/cloudflare/mcp/do-headers";
15+import type { McpSessionProps } from "@executor-js/cloudflare/mcp/agent-durable-object";
16+
17+import { cloudMcpAuth } from "./auth-provider";
18+import { McpSessionDO } from "./session-durable-object";
19+
20+interface McpAgentSessionStub {
21+ readonly validateMcpSessionOwner: (identity: {
22+ readonly accountId: string;
23+ readonly organizationId: string;
24+ }) => Promise<"ok" | "not_found" | "forbidden">;
25+ readonly _cf_scheduleDestroy: () => Promise<void>;
26+}
27+
28+const corsPreflightResponse = (): Response =>
29+ new Response(null, {
30+ status: 204,
31+ headers: {
32+ "access-control-allow-origin": "*",
33+ "access-control-allow-methods": "GET, POST, DELETE, OPTIONS",
34+ "access-control-allow-headers":
35+ "content-type, authorization, mcp-session-id, accept, mcp-protocol-version",
36+ "access-control-expose-headers": "mcp-session-id, WWW-Authenticate",
37+ },
38+ });
39+
40+const jsonRpcResponse = (
41+ status: number,
42+ code: number,
43+ message: string,
44+ challenge?: string,
45+): Response =>
46+ challenge === undefined
47+ ? jsonRpcErrorBody(status, code, message)
48+ : jsonRpcErrorBody(status, code, message, { challenge });
49+
50+const renderAuthError = (
51+ auth: McpAuthProvider["Service"],
52+ request: Request,
53+ outcome: Exclude<AuthOutcome, { readonly _tag: "Authenticated" }>,
54+): Response => {
55+ if (Predicate.isTagged(outcome, "Unauthorized")) {
56+ return jsonRpcResponse(
57+ 401,
58+ -32001,
59+ "Unauthorized",
60+ outcome.challenge ?? `Bearer resource_metadata="${auth.resourceMetadataUrl(request)}"`,
61+ );
62+ }
63+ if (Predicate.isTagged(outcome, "Forbidden")) {
64+ return jsonRpcResponse(403, outcome.code ?? -32001, outcome.message);
65+ }
66+ return jsonRpcResponse(503, -32001, outcome.message);
67+};
68+
69+const sessionStub = (env: Env, sessionId: string): McpAgentSessionStub =>
70+ // oxlint-disable-next-line executor/no-double-cast -- boundary: Workers types expose only DurableObjectStub, but RPC methods are generated from the bound DO class.
71+ env.MCP_SESSION.get(
72+ env.MCP_SESSION.idFromName(`streamable-http:${sessionId}`),
73+ ) as unknown as McpAgentSessionStub;
74+
75+const authenticate = (request: Request) =>
76+ Effect.gen(function* () {
77+ const auth = yield* McpAuthProvider;
78+ const outcome = yield* auth.authenticate(request);
79+ return { auth, outcome };
80+ }).pipe(Effect.provide(cloudMcpAuth));
81+
82+// The MCP resource the request targets. `server.ts` routes both the bare `/mcp`
83+// and `/mcp/toolkits/<slug>` to this handler (`prepareMcpOrgScope` strips the org
84+// selector but keeps the toolkit segment), so a session minted on a toolkit path
85+// scopes its tool catalog to that toolkit.
86+const resourceFromPath = (request: Request): McpResource => {
87+ const segments = new URL(request.url).pathname.split("/").filter((s) => s.length > 0);
88+ if (segments.length === 3 && segments[0] === "mcp" && segments[1] === "toolkits" && segments[2]) {
89+ return { kind: "toolkit", slug: segments[2] };
90+ }
91+ return defaultMcpResource;
92+};
93+
94+const propsForPrincipal = (
95+ request: Request,
96+ principal: Extract<AuthOutcome, { readonly _tag: "Authenticated" }>["principal"],
97+ resource: McpResource,
98+): Effect.Effect<McpSessionProps> =>
99+ Effect.gen(function* () {
100+ const propagation = yield* currentPropagationHeaders(request);
101+ return {
102+ session: {
103+ organizationId: principal.organizationId,
104+ userId: principal.accountId,
105+ elicitationMode: readElicitationMode(request),
106+ resource,
107+ webOrigin: new URL(request.url).origin,
108+ },
109+ propagation,
110+ };
111+ });
112+
113+export const makeCloudMcpAgentHandler = () => {
114+ const serve = McpSessionDO.serve("/mcp", {
115+ binding: "MCP_SESSION",
116+ transport: "streamable-http",
117+ });
118+
119+ return async (request: Request, env: Env, ctx: ExecutionContext): Promise<Response> => {
120+ if (request.method === "OPTIONS") return corsPreflightResponse();
121+ const sessionId = request.headers.get("mcp-session-id");
122+
123+ const { auth, outcome } = await Effect.runPromise(authenticate(request));
124+ if (!Predicate.isTagged(outcome, "Authenticated")) {
125+ if (Predicate.isTagged(outcome, "Forbidden") && sessionId) {
126+ await Effect.runPromise(
127+ Effect.ignore(Effect.tryPromise(() => sessionStub(env, sessionId)._cf_scheduleDestroy())),
128+ );
129+ }
130+ return renderAuthError(auth, request, outcome);
131+ }
132+
133+ if (!sessionId && request.method === "DELETE") {
134+ return new Response(null, { status: 204, headers: { "access-control-allow-origin": "*" } });
135+ }
136+
137+ if (sessionId) {
138+ const owner = await sessionStub(env, sessionId).validateMcpSessionOwner({
139+ accountId: outcome.principal.accountId,
140+ organizationId: outcome.principal.organizationId,
141+ });
142+ if (owner === "not_found") {
143+ return jsonRpcResponse(404, -32001, "Session not found");
144+ }
145+ if (owner === "forbidden") {
146+ return jsonRpcResponse(403, -32003, "MCP session does not belong to the current bearer");
147+ }
148+ }
149+
150+ const resource = resourceFromPath(request);
151+ const props = await Effect.runPromise(propsForPrincipal(request, outcome.principal, resource));
152+ (ctx as ExecutionContext & { props?: McpSessionProps }).props = props;
153+ const forwarded = withVerifiedIdentityHeaders(
154+ request,
155+ {
156+ accountId: outcome.principal.accountId,
157+ organizationId: outcome.principal.organizationId,
158+ },
159+ resource,
160+ );
161+ return serve.fetch(forwarded, env, ctx);
162+ };
163+};
apps/cloud/src/mcp/index.ts+9−15
@@ -1,19 +1,15 @@
11 // ---------------------------------------------------------------------------
2−// Cloud MCP — the three provider seams behind the shared host-mcp envelope,
3−// named to match the app composition root's `mcp: { auth, sessions, reporter }`:
2+// Cloud MCP — the auth seam behind the shared host-mcp envelope, slotted into
3+// the app composition root's `mcp: { auth }`:
44 //
5−// - auth -> cloudMcpAuth (WorkOS JWT + API-key + org-liveness + the
6−// two OAuth discovery docs)
7−// - sessions -> cloudMcpSessions (the Durable-Object session dispatch)
8−// - reporter -> cloudMcpReporter (forwards request-orchestration defects to
9−// Sentry + the dev console)
5+// - auth -> cloudMcpAuth (WorkOS JWT + API-key + org-liveness + the two OAuth
6+// discovery docs)
107 //
11−// These three are what `app.ts`'s `ExecutorApp.make` slots into its `mcp`
12−// providers; the unified app handler serves /mcp from the app layer (like
13−// self-host), so start.ts no longer hand-mounts MCP. The MCP-path predicate +
14−// test-worker envelope builder live in `./mount` (`classifyMcpPath` /
15−// `makeMcpWebHandler`), imported directly there. The MCP session Durable Object
16−// class itself stays a platform-side export (server.ts) and imports its
8+// `server.ts` intercepts `/mcp` transport for the hibernatable Agent bridge, so
9+// the app envelope mounts only cloud's OAuth discovery docs (no `sessions` or
10+// `reporter` seam). The MCP-path predicate lives in `./mount` (`classifyMcpPath`
11+// / `prepareMcpOrgScope`), imported directly there. The MCP session Durable
12+// Object class itself stays a platform-side export (server.ts) and imports its
1713 // siblings directly, NOT this barrel, to keep the DO bundle react-start-free.
1814 // ---------------------------------------------------------------------------
1915
@@ -21,5 +17,3 @@ · 1 unmodified line
2117 // its `McpAuth`/`McpOrganizationAuth` seams provided internally), shaped as the
2218 // `Layer<McpAuthProvider, never, IdentityProvider>` `ExecutorApp.make` expects.
2319 export { cloudMcpAuth } from "./auth-provider";
24−export { cloudMcpSessionStoreLayer as cloudMcpSessions } from "./session-store";
25−export { cloudMcpReporter } from "./reporter";
apps/cloud/src/mcp/mount.ts+14−76
@@ -1,43 +1,20 @@
11 // ---------------------------------------------------------------------------
2−// Cloud MCP front — test-worker helpers for the shared, provider-neutral
3−// host-mcp serving envelope (@executor-js/host-mcp) behind cloud's two seams.
2+// Cloud MCP front — the MCP-path predicate + org-scope rewrite shared by
3+// `server.ts`'s request dispatch.
44 // ---------------------------------------------------------------------------
55 //
6−// PRODUCTION serves /mcp through `app.ts`'s unified `ExecutorApp.make` handler
7−// (the same `McpServingRoutes` envelope provided `cloudMcpAuth` +
8−// `cloudMcpSessions`), dispatched by start.ts alongside /api. This module is the
9−// TEST-WORKER counterpart: it exposes the two pieces `test-worker.ts` needs to
10−// build the identical envelope with swapped auth seams —
11−// - `makeMcpWebHandler` — bind `McpServingRoutes` to a web handler over a
12−// given auth provider + seam requirements + telemetry runtime, mirroring the
13−// self-host mount (`HttpRouter.toWebHandler`).
6+// PRODUCTION serves /mcp through `server.ts`'s hibernatable Agent bridge.
7+// Discovery docs flow through `app.ts`'s unified `ExecutorApp.make` handler
8+// (the `auth` seam's discovery routes). This module exposes:
149 // - `classifyMcpPath` — the "is this an MCP path?" predicate (`/mcp` + the
15−// two discovery docs) that start.ts's dispatch and the test worker share.
16−//
17−// Cloud's two envelope seams:
18−// - McpAuthProvider -> cloudMcpAuthProviderLayer (WorkOS JWT + API key +
19−// per-request org-liveness + the two OAuth discovery docs)
20−// - McpSessionStore -> cloudMcpSessionStoreLayer (Durable-Object dispatch)
21−//
22−// Streaming passthrough — the DO returns a `Response` whose body is a
23−// `ReadableStream` (SSE). The envelope wraps it with `HttpServerResponse.raw`,
24−// which passes the `Response` body through unchanged.
10+// two discovery docs, plus their org-scoped variants).
11+// - `prepareMcpOrgScope` — rewrite an org-scoped MCP request to the bare path
12+// the handler routes, carrying the URL-pinned org in an internal header.
2513 // ---------------------------------------------------------------------------
2614
27−import { HttpRouter, HttpServer } from "effect/unstable/http";
28−import { Layer } from "effect";
29−
3015 import { isValidOrgSlug } from "@executor-js/api";
31−import { McpServingRoutes } from "@executor-js/host-mcp";
3216
33−import {
34− McpAuth,
35− McpOrganizationAuth,
36− MCP_ORGANIZATION_HEADER,
37− PROTECTED_RESOURCE_METADATA_PATH,
38−} from "./auth";
39−import { cloudMcpReporter } from "./reporter";
40−import { cloudMcpSessionStoreLayer } from "./session-store";
17+import { MCP_ORGANIZATION_HEADER, PROTECTED_RESOURCE_METADATA_PATH } from "./auth";
4118
4219 const MCP_PATH = "/mcp";
4320 const AUTHORIZATION_SERVER_METADATA_PATH = "/.well-known/oauth-authorization-server";
@@ -152,47 +129,8 @@ export const prepareMcpOrgScope = (request: Request): Request => { · 108 unmodified lines
152129 return rewritten;
153130 };
154131
155−/**
156− * Build the envelope web handler from the shared `McpServingRoutes` Layer,
157− * provided cloud's two seams. Mirrors the self-host mount (apps/host-selfhost
158− * api.ts): `HttpRouter.provideRequest` clears the route handlers' per-request
159− * seam requirements, the build-time `Layer.provide(McpAuthProviderLive)`
160− * satisfies the `HttpRouter.use` callback's read of `discoveryRoutes`, and
161− * `HttpServer.layerServices` supplies the platform services for the web
162− * handler binding.
163− *
164− * `seamsRequirements` resolves the McpAuth + McpOrganizationAuth tags the
165− * provider reads; `runtime` (the WebSdk telemetry layer) is provided to the
166− * WHOLE router so every route-handler span lands on cloud's tracer — the same
167− * tracer the old `mcpApp` was provided.
168− *
169− * Exported so the test worker can build the same handler with test seam Layers.
170− */
171−export const makeMcpWebHandler = <SeamsError = never>(options: {
172− readonly authProvider: Layer.Layer<
173− import("@executor-js/host-mcp").McpAuthProvider,
174− never,
175− McpAuth | McpOrganizationAuth
176− >;
177− readonly seamsRequirements: Layer.Layer<McpAuth | McpOrganizationAuth, SeamsError>;
178− readonly runtime: Layer.Layer<never>;
179−}): ((request: Request) => Promise<Response>) => {
180− const McpAuthProviderLive = options.authProvider.pipe(Layer.provide(options.seamsRequirements));
181− const McpSeams = Layer.mergeAll(McpAuthProviderLive, cloudMcpSessionStoreLayer, cloudMcpReporter);
182− const McpRouteLive = McpServingRoutes.pipe(
183− HttpRouter.provideRequest(McpSeams),
184− Layer.provide(McpAuthProviderLive),
185− );
186− return HttpRouter.toWebHandler(
187− McpRouteLive.pipe(
188− Layer.provideMerge(Layer.mergeAll(options.runtime, HttpServer.layerServices)),
189− ),
190− ).handler;
191−};
192−
193−// Production no longer mounts /mcp here — `app.ts`'s unified `ExecutorApp.make`
194−// handler serves it (the same `McpServingRoutes` envelope + cloud seams as
195−// `cloudMcpAuth`/`cloudMcpSessions`), dispatched by start.ts alongside /api.
196−// `classifyMcpPath` + `makeMcpWebHandler` remain because the workerd/miniflare
197−// test worker (`test-worker.ts`) builds the same envelope with swapped auth
198−// seams and classifies MCP paths with the identical predicate.
132+// Production no longer mounts the /mcp transport here. `server.ts` intercepts MCP
133+// transport requests for the hibernatable Agent bridge, while `ExecutorApp.make`
134+// serves the OAuth discovery docs through the `auth` seam's discovery routes.
135+// `classifyMcpPath` + `prepareMcpOrgScope` remain because `server.ts`'s request
136+// dispatch uses them to recognize and normalize MCP paths.
apps/cloud/src/mcp/reporter.ts+0−24
@@ -1,24 +0,0 @@
1−// ---------------------------------------------------------------------------
2−// Cloud MCP error reporter seam — `cloudMcpReporter`.
3−//
4−// Forwards a request-orchestration defect the shared host-mcp envelope is about
5−// to render as a JSON-RPC 500 to Sentry (`captureCause`) and the dev console,
6−// preserving the OLD `mcpApp`'s top-level
7−// `console.error('[mcp] request failed', …)` + `captureCause` behavior that the
8−// shared envelope would otherwise swallow (it returns a `Response`).
9−// ---------------------------------------------------------------------------
10−
11−import { Cause, Effect, Layer } from "effect";
12−
13−import { McpErrorReporter } from "@executor-js/host-mcp";
14−
15−import { captureCause } from "../observability";
16−
17−export const cloudMcpReporter: Layer.Layer<McpErrorReporter> = Layer.succeed(McpErrorReporter)({
18− report: (cause) =>
19− Effect.sync(() => {
20− // oxlint-disable-next-line no-console -- boundary: preserve the old mcpApp top-level request-failure log
21− console.error("[mcp] request failed:", Cause.pretty(cause));
22− captureCause(cause);
23− }),
24−});
apps/cloud/src/mcp/session-durable-object.ts+8−7
@@ -1,8 +1,8 @@
11 // ---------------------------------------------------------------------------
22 // Cloud MCP Session Durable Object — the cloud binding of the shared
3−// `McpSessionDOBase` (@executor-js/cloudflare). All session lifecycle (cold
4−// restore, the inactivity alarm, owner validation, transport upgrade, the
5−// browser-approval store, the per-request span bridge) lives in the base; cloud
3+// `McpAgentSessionDOBase` (@executor-js/cloudflare). Hibernatable transport
4+// serving, cold restore, the inactivity alarm, owner validation, browser
5+// approval storage, and the per-request span bridge live in the base. Cloud
66 // supplies ONLY its injected dependencies:
77 // - openSessionDb → a long-lived postgres.js handle
88 // - resolveSessionMeta → WorkOS/UserStore organization resolution
@@ -24,12 +24,12 @@ import postgres, { type Sql } from "postgres"; · 15 unmodified lines
2424 import { createExecutorMcpServer } from "@executor-js/host-mcp/tool-server";
2525 import { buildResumeApprovalUrl } from "@executor-js/host-mcp/browser-approval";
2626 import {
27− McpSessionDOBase,
27+ McpAgentSessionDOBase,
2828 type BuiltMcpServer,
2929 type IncomingTraceHeaders,
3030 type McpSessionInit,
3131 type SessionMeta,
32−} from "@executor-js/cloudflare/mcp/durable-object";
32+} from "@executor-js/cloudflare/mcp/agent-durable-object";
3333 import { buildExecuteDescription } from "@executor-js/execution";
3434
3535 // The DO meters executions just like the HTTP `/api/*` plane: it builds its
@@ -67,7 +67,7 @@ export type { · 31 unmodified lines
6767 McpSessionResumeApprovalResult,
6868 McpSessionInit,
6969 IncomingTraceHeaders,
70−} from "@executor-js/cloudflare/mcp/durable-object";
70+} from "@executor-js/cloudflare/mcp/agent-durable-object";
7171
7272 // ---------------------------------------------------------------------------
7373 // Cloud DB handle — one postgres.js client per session runtime
@@ -157,7 +157,7 @@ const makeSessionServices = (dbHandle: CloudSessionDbHandle) => { · 83 unmodified lines
157157 // Durable Object
158158 // ---------------------------------------------------------------------------
159159
160−export class McpSessionDO extends McpSessionDOBase<CloudSessionDbHandle> {
160+export class McpSessionDO extends McpAgentSessionDOBase<Env, CloudSessionDbHandle> {
161161 protected override openSessionDb(): CloudSessionDbHandle {
162162 return makeDbHandle({
163163 idleTimeout: LONG_LIVED_DB_IDLE_TIMEOUT_SECONDS,
@@ -221,6 +221,7 @@ export class McpSessionDO extends McpSessionDOBase<CloudSessionDbHandle> { · 57 unmodified lines
221221 parentSpan: () => self.currentParentSpan(),
222222 debug: env.EXECUTOR_MCP_DEBUG === "true",
223223 browserApprovalStore: self.browserApprovalStore,
224+ pausedExecutionHooks: self.pausedExecutionHooks,
224225 elicitationMode:
225226 sessionElicitationMode === "browser"
226227 ? {
apps/cloud/src/mcp/session-store.ts+0−35
@@ -1,35 +0,0 @@
1−// ---------------------------------------------------------------------------
2−// Cloud McpSessionStore — the shared Durable-Object dispatcher
3−// (@executor-js/cloudflare) over cloud's `env.MCP_SESSION` namespace. Cloud
4−// supplies only the stub accessors + the Sentry capture for internal errors;
5−// all dispatch/identity/trace/peek logic is in the shared package, identical to
6−// host-cloudflare.
7−// ---------------------------------------------------------------------------
8−
9−import * as Sentry from "@sentry/cloudflare";
10−import { env } from "cloudflare:workers";
11−import { Data } from "effect";
12−
13−import {
14− makeDurableObjectMcpSessionStore,
15− type McpSessionDOStub,
16−} from "@executor-js/cloudflare/mcp/session-store";
17−
18−// Cloud's Sentry capture for a JSON-RPC internal (-32603) error the response
19−// peeker surfaces — injected into the shared store.
20−class McpInternalJsonRpcError extends Data.TaggedError("McpInternalJsonRpcError")<{
21− readonly message: string;
22−}> {}
23−
24−// The DO RPC stub structurally satisfies `McpSessionDOStub` (init/handleRequest/
25−// clearSession), but `@cloudflare/workers-types` types it as a generic
26−// `DurableObjectStub`. Narrow at this one boundary via an `unknown` hop — a
27−// single cast, so no double-cast through the worker-types stub type.
28−const toSessionStub = (stub: unknown): McpSessionDOStub => stub as McpSessionDOStub;
29−
30−export const cloudMcpSessionStoreLayer = makeDurableObjectMcpSessionStore({
31− getStub: (sessionId) =>
32− toSessionStub(env.MCP_SESSION.get(env.MCP_SESSION.idFromString(sessionId))),
33− newStub: () => toSessionStub(env.MCP_SESSION.get(env.MCP_SESSION.newUniqueId())),
34− onInternalError: (message) => Sentry.captureException(new McpInternalJsonRpcError({ message })),
35−});
apps/cloud/src/mcp/worker-transport.test.ts+0−196
@@ -1,196 +0,0 @@
1−import { describe, expect, it } from "@effect/vitest";
2−
3−import {
4− JsonRpcRequestIdQueue,
5− PREVIOUS_REQUEST_TIMEOUT_MS,
6−} from "@executor-js/cloudflare/mcp/request-id-queue";
7−
8−const jsonRpcRequest = (body: unknown): Request =>
9− new Request("https://example.invalid/mcp", {
10− method: "POST",
11− headers: { "content-type": "application/json" },
12− body: JSON.stringify(body),
13− });
14−
15−describe("JsonRpcRequestIdQueue", () => {
16− it("serialises requests with the same id", async () => {
17− const queue = new JsonRpcRequestIdQueue();
18− const order: string[] = [];
19−
20− let releaseFirst!: () => void;
21− const firstStarted = new Promise<void>((resolve) => {
22− const firstRunning = new Promise<void>((release) => {
23− releaseFirst = release;
24− });
25− queue.run(jsonRpcRequest({ jsonrpc: "2.0", id: 1, method: "tools/call" }), async () => {
26− order.push("first:start");
27− resolve();
28− await firstRunning;
29− order.push("first:end");
30− });
31− });
32−
33− await firstStarted;
34−
35− const secondDone = queue.run(
36− jsonRpcRequest({ jsonrpc: "2.0", id: 1, method: "tools/call" }),
37− async () => {
38− order.push("second");
39− },
40− );
41−
42− // Second must wait for first.
43− await new Promise((r) => setTimeout(r, 20));
44− expect(order).toEqual(["first:start"]);
45−
46− releaseFirst();
47− await secondDone;
48− expect(order).toEqual(["first:start", "first:end", "second"]);
49− });
50−
51− it("does not block requests with different ids", async () => {
52− const queue = new JsonRpcRequestIdQueue();
53− let release!: () => void;
54− const hung = new Promise<void>((resolve) => {
55− release = resolve;
56− });
57−
58− queue.run(jsonRpcRequest({ jsonrpc: "2.0", id: 1, method: "tools/call" }), () => hung);
59−
60− const otherDone = await Promise.race([
61− queue
62− .run(jsonRpcRequest({ jsonrpc: "2.0", id: 2, method: "tools/call" }), async () => "done")
63− .then((v) => ({ kind: "settled" as const, v })),
64− new Promise<{ kind: "blocked" }>((r) => setTimeout(() => r({ kind: "blocked" }), 200)),
65− ]);
66−
67− expect(otherDone.kind).toBe("settled");
68− release();
69− });
70−
71− it("does not let a waiting resume block an unrelated same-id tool call", async () => {
72− const queue = new JsonRpcRequestIdQueue();
73− let releaseResume!: () => void;
74− const hungResume = new Promise<void>((resolve) => {
75− releaseResume = resolve;
76− });
77−
78− const resumeStarted = new Promise<void>((resolve) => {
79− queue.run(
80− jsonRpcRequest({
81− jsonrpc: "2.0",
82− id: 1,
83− method: "tools/call",
84− params: { name: "resume", arguments: { executionId: "exec_1" } },
85− }),
86− async () => {
87− resolve();
88− await hungResume;
89− },
90− );
91− });
92− await resumeStarted;
93−
94− const executeDone = await Promise.race([
95− queue
96− .run(
97− jsonRpcRequest({
98− jsonrpc: "2.0",
99− id: 1,
100− method: "tools/call",
101− params: { name: "execute", arguments: { code: "return 1" } },
102− }),
103− async () => "done",
104− )
105− .then((v) => ({ kind: "settled" as const, v })),
106− new Promise<{ kind: "blocked" }>((r) => setTimeout(() => r({ kind: "blocked" }), 200)),
107− ]);
108−
109− expect(executeDone.kind).toBe("settled");
110− releaseResume();
111− });
112−
113− it("serialises same-id resume calls for the same execution", async () => {
114− const queue = new JsonRpcRequestIdQueue();
115− const order: string[] = [];
116−
117− let releaseFirst!: () => void;
118− const firstStarted = new Promise<void>((resolve) => {
119− const firstRunning = new Promise<void>((release) => {
120− releaseFirst = release;
121− });
122− queue.run(
123− jsonRpcRequest({
124− jsonrpc: "2.0",
125− id: 1,
126− method: "tools/call",
127− params: { name: "resume", arguments: { executionId: "exec_1" } },
128− }),
129− async () => {
130− order.push("first:start");
131− resolve();
132− await firstRunning;
133− order.push("first:end");
134− },
135− );
136− });
137− await firstStarted;
138−
139− const secondDone = queue.run(
140− jsonRpcRequest({
141− jsonrpc: "2.0",
142− id: 1,
143− method: "tools/call",
144− params: { name: "resume", arguments: { executionId: "exec_1" } },
145− }),
146− async () => {
147− order.push("second");
148− },
149− );
150−
151− await new Promise((r) => setTimeout(r, 20));
152− expect(order).toEqual(["first:start"]);
153−
154− releaseFirst();
155− await secondDone;
156− expect(order).toEqual(["first:start", "first:end", "second"]);
157− });
158−
159− it("regression: caps wait on a hung previous request and dispatches anyway", async () => {
160− // Override the timeout for fast CI — the production default is
161− // PREVIOUS_REQUEST_TIMEOUT_MS (60s) which we cap test-side to 100ms.
162− // Same behaviour, same code path; only the wall-clock budget changes.
163− const queue = new JsonRpcRequestIdQueue({ previousTimeoutMs: 100 });
164− const order: string[] = [];
165−
166− // Kick off a request and never release it — the poisoned-queue
167− // shape that used to cascade for the full upstream 180s timeout.
168− const firstStarted = new Promise<void>((resolve) => {
169− queue.run(jsonRpcRequest({ jsonrpc: "2.0", id: 1, method: "tools/call" }), async () => {
170− order.push("first:start");
171− resolve();
172− await new Promise(() => undefined); // hang forever
173− });
174− });
175− await firstStarted;
176−
177− const result = await queue.run(
178− jsonRpcRequest({ jsonrpc: "2.0", id: 1, method: "tools/call" }),
179− async () => {
180− order.push("second");
181− return "ok";
182− },
183− );
184−
185− expect(result).toBe("ok");
186− expect(order).toEqual(["first:start", "second"]);
187− });
188−
189− it("exposes a sane production timeout", () => {
190− // Sanity guard: must stay below the 180s upstream timeout that
191− // Claude / Cowork enforce, but be long enough to outlast a normal
192− // dynamic-worker execution under load.
193− expect(PREVIOUS_REQUEST_TIMEOUT_MS).toBeGreaterThan(10_000);
194− expect(PREVIOUS_REQUEST_TIMEOUT_MS).toBeLessThan(180_000);
195− });
196−});
apps/cloud/src/server.ts+15−0
@@ -10,6 +10,8 @@ import * as Sentry from "@sentry/cloudflare"; · 9 unmodified lines
1010 import handler from "@tanstack/react-start/server-entry";
1111
1212 import { isAppOwnedPath } from "./app-paths";
13+import { makeCloudMcpAgentHandler } from "./mcp/agent-handler";
14+import { classifyMcpPath, prepareMcpOrgScope } from "./mcp/mount";
1315 import { McpSessionDO as McpSessionDOBase } from "./mcp/session-durable-object";
1416 import { browserTracesResponse } from "./observability/browser-traces";
1517 import { flushTracerProvider, installTracerProvider } from "./observability/telemetry";
@@ -77,6 +79,7 @@ const fetchHandler = handler.fetch as ( · 61 unmodified lines
7779 ) => Response | Promise<Response>;
7880
7981 const tracer = trace.getTracer("executor-cloud-worker");
82+const mcpAgentHandler = makeCloudMcpAgentHandler();
8083
8184 const cloudflareHandler: ExportedHandler<Env> = {
8285 fetch: async (request, env, ctx) => {
@@ -89,6 +92,18 @@ const cloudflareHandler: ExportedHandler<Env> = { · 6 unmodified lines
8992 return fetchHandler(request, env, ctx);
9093 }
9194 const url = new URL(request.url);
95+ const mcpRoute = classifyMcpPath(url.pathname);
96+ if (mcpRoute?.kind === "mcp") {
97+ // The Cloudflare Agents MCP bridge needs the platform ExecutionContext
98+ // to pass authenticated session props into the hibernatable DO.
99+ // Discovery docs still flow through the app-level MCP envelope.
100+ // oxlint-disable-next-line executor/no-try-catch-or-throw -- adapter boundary; keep trace export alive after the Agents bridge resolves or rejects
101+ try {
102+ return await mcpAgentHandler(prepareMcpOrgScope(request), env, ctx);
103+ } finally {
104+ ctx.waitUntil(flushTracerProvider());
105+ }
106+ }
92107 // Effect-served paths bring their own http.server span (with traceparent
93108 // join) — opening one here too would duplicate it. See the header note.
94109 if (isAppOwnedPath(url.pathname)) {
apps/host-cloudflare/src/app.ts+12−15
@@ -14,15 +14,16 @@ import { · 13 unmodified lines
1414 } from "./execution";
1515 import { ErrorCaptureLive } from "./observability";
1616 import { cloudflareAccountMiddleware } from "./account/account-provider";
17−import { makeCloudflareMcpSeams } from "./mcp";
17+import { makeCloudflareApprovalHandler } from "./mcp";
18+import { makeCloudflareMcpAgentHandler } from "./mcp/agent-handler";
1819 import { preloadQuickJs } from "./quickjs";
1920
2021 // ===========================================================================
21−// The Cloudflare host, as ONE `ExecutorApp.make` call — the 4th app alongside
22+// The Cloudflare host, as ONE `ExecutorApp.make` call: the 4th app alongside
2223 // cloud / self-host / local, differing only by the injected Layers.
2324 //
2425 // The whole scenario in 60 seconds: Cloudflare Access is the identity (validate
25−// the Cf-Access-Jwt-Assertion JWT — no Better Auth, no WorkOS, no app login),
26+// the Cf-Access-Jwt-Assertion JWT, no Better Auth, no WorkOS, no app login),
2627 // D1 is the SQLite store (same FumaDB assembly as self-host), QuickJS is the
2728 // in-process code substrate, no billing, single-tenant. `diff` against
2829 // host-selfhost/src/app.ts is three injected Layers: identity, db, plugins/config.
@@ -37,23 +38,22 @@ export const makeCloudflareApp = async (env: CloudflareEnv) => { · 8 unmodified lines
3738 const plugins = makeCloudflarePlugins(config.secretKey);
3839
3940 // Load the Workers-compatible (WASM-inlined) QuickJS variant before any
40− // executor is built — the default variant can't fetch its .wasm on Workers.
41+ // executor is built, the default variant cannot fetch its .wasm on Workers.
4142 await preloadQuickJs();
4243
43− // Open + idempotently bring up the D1 schema once (the long-lived handle the
44− // per-request scoped executor reads through the DbProvider seam).
44+ // Open and idempotently bring up the D1 schema once. This is the long-lived
45+ // handle the per-request scoped executor reads through the DbProvider seam.
4546 const dbHandle = await createD1ExecutorDb(env.DB, env.BLOBS);
4647 const identityLayer = cloudflareAccessIdentityLayer(config);
47− // MCP runs through the `MCP_SESSION` Durable Object (cross-isolate sessions);
48− // each session DO opens its own D1 handle, so it takes `env`, not `dbHandle`.
49− const mcp = makeCloudflareMcpSeams(config, env);
48+ const mcpAgentHandler = makeCloudflareMcpAgentHandler(config);
49+ const approvalHandler = makeCloudflareApprovalHandler(config, env);
5050
5151 const { appLayer, toWebHandler } = ExecutorApp.make({
5252 plugins,
5353 providers: {
5454 identity: identityLayer,
5555 db: dbProviderLayer(Effect.succeed(dbHandle)),
56− engine: { codeExecutor: CloudflareCodeExecutorProvider }, // decorator defaults to no-op
56+ engine: { codeExecutor: CloudflareCodeExecutorProvider },
5757 plugins: {
5858 provider: makeCloudflarePluginsProvider(config),
5959 config: makeCloudflareHostConfig(config),
@@ -63,21 +63,18 @@ export const makeCloudflareApp = async (env: CloudflareEnv) => { · 3 unmodified lines
6363 // auth context; `me` reflects the Access principal. Members/keys are
6464 // Access-managed, so the rest of the surface is stubbed.
6565 account: cloudflareAccountMiddleware(config),
66− // The MCP serving envelope: Access-JWT auth + the shared in-process session
67− // store over the QuickJS engine.
68− mcp: { auth: mcp.auth, sessions: mcp.sessions, reporter: mcp.reporter },
6966 },
7067 extensions: {
7168 routes: [
7269 // Browser approval of paused MCP executions: the console resume page
7370 // reads paused detail (GET) and records the decision (POST .../resume),
7471 // Access-gated, routed to the owning session's Durable Object.
75− HttpRouter.add("*", "/api/mcp-sessions/*", HttpEffect.fromWebHandler(mcp.approvalHandler)),
72+ HttpRouter.add("*", "/api/mcp-sessions/*", HttpEffect.fromWebHandler(approvalHandler)),
7673 ],
7774 },
7875 config: { mountPrefix: "/api", failure: textFailureStrategy },
7976 boot: identityLayer,
8077 });
8178
82− return { appLayer, toWebHandler };
79+ return { appLayer, toWebHandler, mcpAgentHandler };
8380 };
apps/host-cloudflare/src/mcp/agent-handler.ts+153−0
@@ -0,0 +1,153 @@
1+import { Effect, Predicate } from "effect";
2+
3+import {
4+ McpAuthProvider,
5+ jsonRpcErrorBody,
6+ defaultMcpResource,
7+ type AuthOutcome,
8+ type Principal,
9+} from "@executor-js/host-mcp";
10+import {
11+ currentPropagationHeaders,
12+ readElicitationMode,
13+ withVerifiedIdentityHeaders,
14+} from "@executor-js/cloudflare/mcp/do-headers";
15+import type { McpSessionProps } from "@executor-js/cloudflare/mcp/agent-durable-object";
16+
17+import type { CloudflareConfig, CloudflareEnv } from "../config";
18+import { cloudflareAccessMcpAuth } from "./auth";
19+import { McpSessionDO } from "./session-durable-object";
20+
21+interface McpAgentSessionStub {
22+ readonly validateMcpSessionOwner: (identity: {
23+ readonly accountId: string;
24+ readonly organizationId: string;
25+ }) => Promise<"ok" | "not_found" | "forbidden">;
26+ readonly _cf_scheduleDestroy: () => Promise<void>;
27+}
28+
29+const corsPreflightResponse = (): Response =>
30+ new Response(null, {
31+ status: 204,
32+ headers: {
33+ "access-control-allow-origin": "*",
34+ "access-control-allow-methods": "GET, POST, DELETE, OPTIONS",
35+ "access-control-allow-headers":
36+ "content-type, authorization, mcp-session-id, accept, mcp-protocol-version",
37+ "access-control-expose-headers": "mcp-session-id, WWW-Authenticate",
38+ },
39+ });
40+
41+const jsonRpcResponse = (
42+ status: number,
43+ code: number,
44+ message: string,
45+ challenge?: string,
46+): Response =>
47+ challenge === undefined
48+ ? jsonRpcErrorBody(status, code, message)
49+ : jsonRpcErrorBody(status, code, message, { challenge });
50+
51+const renderAuthError = (
52+ auth: McpAuthProvider["Service"],
53+ request: Request,
54+ outcome: Exclude<AuthOutcome, { readonly _tag: "Authenticated" }>,
55+): Response => {
56+ if (Predicate.isTagged(outcome, "Unauthorized")) {
57+ return jsonRpcResponse(
58+ 401,
59+ -32001,
60+ "Unauthorized",
61+ outcome.challenge ?? `Bearer resource_metadata="${auth.resourceMetadataUrl(request)}"`,
62+ );
63+ }
64+ if (Predicate.isTagged(outcome, "Forbidden")) {
65+ return jsonRpcResponse(403, outcome.code ?? -32001, outcome.message);
66+ }
67+ return jsonRpcResponse(503, -32001, outcome.message);
68+};
69+
70+const sessionStub = (env: CloudflareEnv, sessionId: string): McpAgentSessionStub =>
71+ // oxlint-disable-next-line executor/no-double-cast -- boundary: Workers types expose only DurableObjectStub, but RPC methods are generated from the bound DO class.
72+ env.MCP_SESSION.get(
73+ env.MCP_SESSION.idFromName(`streamable-http:${sessionId}`),
74+ ) as unknown as McpAgentSessionStub;
75+
76+const authenticate = (request: Request, config: CloudflareConfig) =>
77+ Effect.gen(function* () {
78+ const auth = yield* McpAuthProvider;
79+ const outcome = yield* auth.authenticate(request);
80+ return { auth, outcome };
81+ }).pipe(Effect.provide(cloudflareAccessMcpAuth(config)));
82+
83+const propsForPrincipal = (
84+ request: Request,
85+ principal: Principal,
86+): Effect.Effect<McpSessionProps> =>
87+ Effect.gen(function* () {
88+ const propagation = yield* currentPropagationHeaders(request);
89+ return {
90+ session: {
91+ organizationId: principal.organizationId,
92+ userId: principal.accountId,
93+ elicitationMode: readElicitationMode(request),
94+ // host-cloudflare only routes the bare `/mcp` endpoint to the Agent
95+ // bridge (see worker.ts), so the session always serves the default
96+ // resource.
97+ resource: defaultMcpResource,
98+ webOrigin: new URL(request.url).origin,
99+ },
100+ propagation,
101+ };
102+ });
103+
104+export const makeCloudflareMcpAgentHandler = (config: CloudflareConfig) => {
105+ const serve = McpSessionDO.serve("/mcp", {
106+ binding: "MCP_SESSION",
107+ transport: "streamable-http",
108+ });
109+
110+ return async (request: Request, env: CloudflareEnv, ctx: ExecutionContext): Promise<Response> => {
111+ if (request.method === "OPTIONS") return corsPreflightResponse();
112+ const sessionId = request.headers.get("mcp-session-id");
113+
114+ const { auth, outcome } = await Effect.runPromise(authenticate(request, config));
115+ if (!Predicate.isTagged(outcome, "Authenticated")) {
116+ if (Predicate.isTagged(outcome, "Forbidden") && sessionId) {
117+ await Effect.runPromise(
118+ Effect.ignore(Effect.tryPromise(() => sessionStub(env, sessionId)._cf_scheduleDestroy())),
119+ );
120+ }
121+ return renderAuthError(auth, request, outcome);
122+ }
123+
124+ if (!sessionId && request.method === "DELETE") {
125+ return new Response(null, { status: 204, headers: { "access-control-allow-origin": "*" } });
126+ }
127+
128+ if (sessionId) {
129+ const owner = await sessionStub(env, sessionId).validateMcpSessionOwner({
130+ accountId: outcome.principal.accountId,
131+ organizationId: outcome.principal.organizationId,
132+ });
133+ if (owner === "not_found") {
134+ return jsonRpcResponse(404, -32001, "Session not found");
135+ }
136+ if (owner === "forbidden") {
137+ return jsonRpcResponse(403, -32003, "MCP session does not belong to the current bearer");
138+ }
139+ }
140+
141+ const props = await Effect.runPromise(propsForPrincipal(request, outcome.principal));
142+ (ctx as ExecutionContext & { props?: McpSessionProps }).props = props;
143+ const forwarded = withVerifiedIdentityHeaders(
144+ request,
145+ {
146+ accountId: outcome.principal.accountId,
147+ organizationId: outcome.principal.organizationId,
148+ },
149+ defaultMcpResource,
150+ );
151+ return serve.fetch(forwarded, env, ctx);
152+ };
153+};
apps/host-cloudflare/src/mcp/index.ts+4−64
@@ -1,58 +1,19 @@
1−import { Effect, type Layer } from "effect";
1+import { Effect } from "effect";
22
3−import type { McpAuthProvider, McpErrorReporter, McpSessionStore } from "@executor-js/host-mcp";
43 import { decodeResumeResponse } from "@executor-js/host-mcp/browser-approval";
54 import type {
65 McpApprovalOwner,
76 McpSessionApprovalResult,
87 McpSessionResumeApprovalResult,
9−} from "@executor-js/cloudflare/mcp/durable-object";
8+} from "@executor-js/cloudflare/mcp/agent-durable-object";
109 import type { ResumeResponse } from "@executor-js/execution";
1110
1211 import type { CloudflareConfig, CloudflareEnv } from "../config";
1312 import { makeAccessVerifier } from "../auth/cloudflare-access";
14−import { cloudflareAccessMcpAuth } from "./auth";
15−import { cloudflareMcpReporter, makeCloudflareMcpSessionStore } from "./session-store";
1613
1714 export { cloudflareAccessMcpAuth } from "./auth";
18−export { cloudflareMcpReporter, makeCloudflareMcpSessionStore } from "./session-store";
1915 export { McpSessionDO } from "./session-durable-object";
2016
21−// ---------------------------------------------------------------------------
22−// The Cloudflare MCP serving seams, fed to `ExecutorApp.make`'s `mcp` group.
23−//
24−// `ExecutorApp.make` mounts the shared, provider-neutral MCP serving envelope
25−// (@executor-js/host-mcp) at the top-level `/mcp`, outside the API's execution
26−// middleware. The Cloudflare host provides the two envelope seams plus the
27−// error-reporter override:
28−// - McpAuthProvider -> `cloudflareAccessMcpAuth`: validate the Access JWT
29−// (same identity as the API gate); no MCP OAuth.
30−// - McpSessionStore -> the shared Durable-Object dispatcher over the host's
31−// `MCP_SESSION` namespace (cross-isolate, same as cloud).
32−// - McpErrorReporter -> `cloudflareMcpReporter`: route 500 defects through the
33−// host's console capture.
34−// ---------------------------------------------------------------------------
35−
36−export interface CloudflareMcpSeams {
37− /** Validate the Access JWT to an MCP `AuthOutcome`; declares no discovery routes. */
38− readonly auth: Layer.Layer<McpAuthProvider>;
39− /** The Durable-Object session store seam (dispatch + lifetime). */
40− readonly sessions: Layer.Layer<McpSessionStore>;
41− /** Route 500 defects through the host's console `ErrorCapture`. */
42− readonly reporter: Layer.Layer<McpErrorReporter>;
43− /**
44− * The browser-approval HTTP handler, mounted by the app at
45− * `/api/mcp-sessions/*`: an Access-gated web handler that reads paused-execution
46− * detail (GET) and records the human's decision (POST `/resume`) for the console
47− * resume page, routing each to the owning session's Durable Object RPCs.
48− */
49− readonly approvalHandler: (request: Request) => Promise<Response>;
50−}
51−
52−// The MCP session Durable Object exposes the approval RPCs (the base class
53−// implements them); `@cloudflare/workers-types` types the stub generically, so
54−// narrow at this one boundary via a single `unknown`-param hop (same shape the
55−// session-store seam uses for the dispatch stub).
5617 const toApprovalStub = (stub: unknown): McpApprovalStub => stub as McpApprovalStub;
5718
5819 interface McpApprovalStub {
@@ -73,19 +34,13 @@ const RESUME_PATH = /^\/api\/mcp-sessions\/([^/?#]+)\/executions\/([^/?#]+)\/res · 14 unmodified lines
7334 const jsonResponse = (value: unknown, status: number): Response =>
7435 new Response(JSON.stringify(value), { status, headers: { "content-type": "application/json" } });
7536
76−/**
77− * Resolve the request to its Access principal (dev-auth → the fixed dev admin),
78− * then route the browser-approval call to the owning session's Durable Object —
79− * the same RPCs cloud serves through its HttpApi. The DO validates that the
80− * principal owns the session before reading or resuming.
81− */
82−const makeCloudflareApprovalHandler = (
37+export const makeCloudflareApprovalHandler = (
8338 config: CloudflareConfig,
8439 env: CloudflareEnv,
8540 ): ((request: Request) => Promise<Response>) => {
8641 const { verify } = makeAccessVerifier(config);
8742 const stubFor = (sessionId: string): McpApprovalStub =>
88− toApprovalStub(env.MCP_SESSION.get(env.MCP_SESSION.idFromString(sessionId)));
43+ toApprovalStub(env.MCP_SESSION.get(env.MCP_SESSION.idFromName(`streamable-http:${sessionId}`)));
8944
9045 return async (request) => {
9146 const principal = await Effect.runPromise(verify(request));
@@ -136,18 +91,3 @@ const makeCloudflareApprovalHandler = ( · 44 unmodified lines
13691 return jsonResponse({ error: "Not found" }, 404);
13792 };
13893 };
139−
140−/**
141− * Build the Cloudflare MCP serving seams over the host's `MCP_SESSION` Durable
142− * Object namespace. No per-session DB handle is threaded here — each session DO
143− * opens its own D1 handle in its own isolate.
144− */
145−export const makeCloudflareMcpSeams = (
146− config: CloudflareConfig,
147− env: CloudflareEnv,
148−): CloudflareMcpSeams => ({
149− auth: cloudflareAccessMcpAuth(config),
150− sessions: makeCloudflareMcpSessionStore(env),
151− reporter: cloudflareMcpReporter,
152− approvalHandler: makeCloudflareApprovalHandler(config, env),
153−});
apps/host-cloudflare/src/mcp/session-durable-object.ts+9−8
@@ -4,11 +4,11 @@ import { createExecutorMcpServer } from "@executor-js/host-mcp/tool-server"; · 3 unmodified lines
44 import { buildResumeApprovalUrl } from "@executor-js/host-mcp/browser-approval";
55 import type { ExecutorDbHandle } from "@executor-js/api/server";
66 import {
7− McpSessionDOBase,
7+ McpAgentSessionDOBase,
88 type BuiltMcpServer,
99 type McpSessionInit,
1010 type SessionMeta,
11−} from "@executor-js/cloudflare/mcp/durable-object";
11+} from "@executor-js/cloudflare/mcp/agent-durable-object";
1212
1313 import { loadConfig, type CloudflareConfig, type CloudflareEnv } from "../config";
1414 import { createD1ExecutorDb } from "../db/d1";
@@ -17,7 +17,7 @@ import { preloadQuickJs } from "../quickjs"; · 2 unmodified lines
1717
1818 // ---------------------------------------------------------------------------
1919 // Cloudflare (self-host) MCP Session Durable Object — the host-cloudflare
20−// binding of the shared `McpSessionDOBase` (@executor-js/cloudflare). Identical
20+// binding of the shared `McpAgentSessionDOBase` (@executor-js/cloudflare). Identical
2121 // base to cloud; the ONLY differences are the injected dependencies:
2222 // - openSessionDb → a long-lived D1 `ExecutorDbHandle` (same FumaDB
2323 // assembly the HTTP path uses), adapted to the base's
@@ -35,14 +35,14 @@ import { preloadQuickJs } from "../quickjs"; · 11 unmodified lines
3535 // own lifecycle (the binding is the connection), so `end` is `close` — a no-op.
3636 type CfSessionDbHandle = ExecutorDbHandle & { readonly end: () => Promise<void> };
3737
38−export class McpSessionDO extends McpSessionDOBase<CfSessionDbHandle> {
38+export class McpSessionDO extends McpAgentSessionDOBase<CloudflareEnv, CfSessionDbHandle> {
3939 private readonly cfEnv: CloudflareEnv;
4040 private readonly cfConfig: CloudflareConfig;
4141
42− // `ctx`'s type is taken from the base constructor so it tracks whichever
43− // `@cloudflare/workers-types` the shared package resolves (avoids a
44− // cross-version `DurableObjectState` mismatch at the `super` call).
45− constructor(ctx: ConstructorParameters<typeof McpSessionDOBase>[0], env: CloudflareEnv) {
42+ constructor(
43+ ctx: ConstructorParameters<typeof McpAgentSessionDOBase<CloudflareEnv, CfSessionDbHandle>>[0],
44+ env: CloudflareEnv,
45+ ) {
4646 super(ctx, env);
4747 this.cfEnv = env;
4848 this.cfConfig = loadConfig(env);
@@ -90,6 +90,7 @@ export class McpSessionDO extends McpSessionDOBase<CfSessionDbHandle> { · 41 unmodified lines
9090 const mcpServer = yield* createExecutorMcpServer({
9191 engine,
9292 browserApprovalStore: self.browserApprovalStore,
93+ pausedExecutionHooks: self.pausedExecutionHooks,
9394 elicitationMode:
9495 elicitationMode === "browser"
9596 ? {
apps/host-cloudflare/src/mcp/session-store.ts+0−43
@@ -1,43 +0,0 @@
1−import { Layer } from "effect";
2−
3−import { makeConsoleMcpErrorReporter } from "@executor-js/api/server";
4−import type { McpErrorReporter, McpSessionStore } from "@executor-js/host-mcp";
5−import {
6− makeDurableObjectMcpSessionStore,
7− type McpSessionDOStub,
8−} from "@executor-js/cloudflare/mcp/session-store";
9−
10−import type { CloudflareEnv } from "../config";
11−import { ErrorCaptureLive } from "../observability";
12−
13−// ---------------------------------------------------------------------------
14−// Cloudflare McpSessionStore wiring — the SAME shared Durable-Object dispatcher
15−// as cloud (@executor-js/cloudflare), over host-cloudflare's `MCP_SESSION`
16−// namespace. The dispatch/identity/trace/peek logic all lives in the shared
17−// package; the host supplies ONLY its DO stub accessors (the session id IS the
18−// DO id, so every follow-up request routes back to the same isolate).
19−//
20−// This replaces the in-process store: an in-memory session map is invisible to
21−// the next Worker isolate, so `tools/list` after `initialize` failed in
22−// production ("Not connected"). The DO holds the session in one addressable
23−// isolate, fixing that across the board.
24−// ---------------------------------------------------------------------------
25−
26−// The DO RPC stub structurally satisfies `McpSessionDOStub` (init/handleRequest/
27−// clearSession), but `@cloudflare/workers-types` types it as a generic
28−// `DurableObjectStub`. Narrow at this one boundary via an `unknown` hop — a
29−// single cast, so no double-cast through the worker-types stub type.
30−const toSessionStub = (stub: unknown): McpSessionDOStub => stub as McpSessionDOStub;
31−
32−/** Build the DO-backed MCP session store over the host's `MCP_SESSION` namespace. */
33−export const makeCloudflareMcpSessionStore = (env: CloudflareEnv): Layer.Layer<McpSessionStore> =>
34− makeDurableObjectMcpSessionStore({
35− getStub: (sessionId) =>
36− toSessionStub(env.MCP_SESSION.get(env.MCP_SESSION.idFromString(sessionId))),
37− newStub: () => toSessionStub(env.MCP_SESSION.get(env.MCP_SESSION.newUniqueId())),
38− // host-cf has no Sentry; a 500-defect surfaces through the reporter seam below.
39− });
40−
41−/** Route 500-defects through the host's console `ErrorCapture`. */
42−export const cloudflareMcpReporter: Layer.Layer<McpErrorReporter> =
43− makeConsoleMcpErrorReporter(ErrorCaptureLive);
apps/host-cloudflare/src/worker.e2e.node.test.ts+69−4
@@ -4,6 +4,7 @@ import { resolve } from "node:path"; · 3 unmodified lines
44 import { fileURLToPath } from "node:url";
55
66 import { afterAll, beforeAll, describe, expect, it } from "@effect/vitest";
7+import { Schema } from "effect";
78 import { unstable_dev, type Unstable_DevWorker } from "wrangler";
89
910 // ---------------------------------------------------------------------------
@@ -29,6 +30,28 @@ const SPEC = JSON.stringify({ · 19 unmodified lines
2930 },
3031 });
3132
33+const decodeUnknownJson = Schema.decodeUnknownSync(Schema.fromJsonString(Schema.Unknown));
34+
35+interface JsonReadableResponse {
36+ readonly headers: { get(name: string): string | null };
37+ json(): Promise<unknown>;
38+ text(): Promise<string>;
39+}
40+
41+const readMcpJson = async <A>(response: JsonReadableResponse): Promise<A> => {
42+ const contentType = response.headers.get("content-type") ?? "";
43+ if (contentType.includes("application/json")) {
44+ return (await response.json()) as A;
45+ }
46+ const text = await response.text();
47+ const data = text
48+ .split("\n")
49+ .find((line) => line.startsWith("data: "))
50+ ?.slice("data: ".length);
51+ expect(data).toBeTruthy();
52+ return decodeUnknownJson(data!) as A;
53+};
54+
3255 describe("cloudflare host e2e (workerd/miniflare)", () => {
3356 let worker: Unstable_DevWorker;
3457
@@ -209,13 +232,55 @@ describe("cloudflare host e2e (workerd/miniflare)", () => { · 174 unmodified lines
209232 method: "tools/list",
210233 });
211234 expect(list.status).toBe(200);
212− const listed = (await list.json()) as {
235+ const listed = await readMcpJson<{
213236 result?: { tools?: ReadonlyArray<{ name: string }> };
214− };
237+ }>(list);
215238 const toolNames = listed.result?.tools?.map((t) => t.name) ?? [];
216239 expect(toolNames).toContain("execute");
217240 }, 60_000);
218241
242+ it("serves streamable HTTP GET only for initialized sessions", async () => {
243+ const missing = await worker.fetch("/mcp", {
244+ method: "GET",
245+ headers: { accept: "text/event-stream" },
246+ });
247+ expect(missing.status).toBe(400);
248+ await missing.text();
249+
250+ const init = await worker.fetch("/mcp", {
251+ method: "POST",
252+ headers: {
253+ "content-type": "application/json",
254+ accept: "application/json, text/event-stream",
255+ },
256+ body: JSON.stringify({
257+ jsonrpc: "2.0",
258+ id: 1,
259+ method: "initialize",
260+ params: {
261+ protocolVersion: "2025-03-26",
262+ capabilities: {},
263+ clientInfo: { name: "test", version: "1" },
264+ },
265+ }),
266+ });
267+ expect(init.status).toBe(200);
268+ const sessionId = init.headers.get("mcp-session-id");
269+ expect(sessionId).toBeTruthy();
270+ await init.text();
271+
272+ const stream = await worker.fetch("/mcp", {
273+ method: "GET",
274+ headers: {
275+ accept: "text/event-stream",
276+ "mcp-session-id": sessionId!,
277+ },
278+ });
279+ expect(stream.status).toBe(200);
280+ expect(stream.headers.get("content-type")).toContain("text/event-stream");
281+ await stream.body?.cancel();
282+ }, 60_000);
283+
219284 it("invokes the execute tool over MCP (initialize → tools/call → QuickJS)", async () => {
220285 const accept = "application/json, text/event-stream";
221286 const rpc = (sessionId: string | null, body: unknown) =>
@@ -255,9 +320,9 @@ describe("cloudflare host e2e (workerd/miniflare)", () => { · 33 unmodified lines
255320 params: { name: "execute", arguments: { code: "export default 6 * 7" } },
256321 });
257322 expect(call.status).toBe(200);
258− const result = (await call.json()) as {
323+ const result = await readMcpJson<{
259324 result?: { structuredContent?: { result?: number } };
260− };
325+ }>(call);
261326 expect(result.result?.structuredContent?.result).toBe(42);
262327 }, 60_000);
263328 });
apps/host-cloudflare/src/worker.ts+18−10
@@ -6,25 +6,33 @@ import type { CloudflareEnv } from "./config"; · 5 unmodified lines
66 export { McpSessionDO } from "./mcp";
77
88 // ---------------------------------------------------------------------------
9−// The Worker fetch entry. `ExecutorApp.make`'s `toWebHandler()` produces a
10−// `(Request) => Promise<Response>` — exactly a Worker handler — so the entry is
11−// thin: build the app ONCE per isolate (memoized; the build runs the D1 schema
12−// bring-up), then forward every request to its handler. `env` (the D1 binding +
13−// Access vars) arrives with the request and is captured at build time.
9+// The Worker fetch entry. Most requests go to `ExecutorApp.make`'s Effect web
10+// handler. `/mcp` stays at this edge boundary because `McpAgent.serve()` needs
11+// the Cloudflare `ExecutionContext` to pass authenticated session props into the
12+// hibernatable Durable Object bridge.
1413 // ---------------------------------------------------------------------------
1514
16−let handlerPromise: Promise<(request: Request) => Promise<Response>> | null = null;
15+let handlerPromise: Promise<{
16+ readonly app: (request: Request) => Promise<Response>;
17+ readonly mcp: (request: Request, env: CloudflareEnv, ctx: ExecutionContext) => Promise<Response>;
18+}> | null = null;
1719
18−const resolveHandler = (env: CloudflareEnv): Promise<(request: Request) => Promise<Response>> => {
20+const resolveHandler = (env: CloudflareEnv) => {
1921 if (!handlerPromise) {
20− handlerPromise = makeCloudflareApp(env).then(({ toWebHandler }) => toWebHandler().handler);
22+ handlerPromise = makeCloudflareApp(env).then(({ toWebHandler, mcpAgentHandler }) => ({
23+ app: toWebHandler().handler,
24+ mcp: mcpAgentHandler,
25+ }));
2126 }
2227 return handlerPromise;
2328 };
2429
2530 export default {
26− fetch: async (request: Request, env: CloudflareEnv): Promise<Response> => {
31+ fetch: async (request: Request, env: CloudflareEnv, ctx: ExecutionContext): Promise<Response> => {
2732 const serve = await resolveHandler(env);
28− return serve(request);
33+ if (new URL(request.url).pathname === "/mcp") {
34+ return serve.mcp(request, env, ctx);
35+ }
36+ return serve.app(request);
2937 },
3038 };
bun.lock+268−27
@@ -91,7 +91,6 @@ · 90 unmodified lines
9191 "@tanstack/react-router": "catalog:",
9292 "@tanstack/react-start": "catalog:",
9393 "@workos-inc/node": "^8.11.1",
94− "agents": "^0.10.0",
9594 "autumn-js": "^1.2.8",
9695 "drizzle-orm": "catalog:",
9796 "effect": "catalog:",
@@ -655,7 +654,7 @@ · 557 unmodified lines
655654 "@executor-js/host-mcp": "workspace:*",
656655 "@executor-js/sdk": "workspace:*",
657656 "@modelcontextprotocol/sdk": "^1.29.0",
658− "agents": "^0.10.0",
657+ "agents": "^0.16.2",
659658 "effect": "catalog:",
660659 },
661660 "devDependencies": {
@@ -1286,8 +1285,6 @@ · 624 unmodified lines
12861285
12871286 "@antfu/ni": ["@antfu/ni@0.23.2", "", { "bin": { "na": "bin/na.mjs", "ni": "bin/ni.mjs", "nr": "bin/nr.mjs", "nu": "bin/nu.mjs", "nci": "bin/nci.mjs", "nlx": "bin/nlx.mjs", "nun": "bin/nun.mjs" } }, "sha512-FSEVWXvwroExDXUu8qV6Wqp2X3D1nJ0Li4LFymCyvCVrm7I3lNfG0zZWSWvGU1RE7891eTnFTyh31L3igOwNKQ=="],
12881287
1289− "@apidevtools/json-schema-ref-parser": ["@apidevtools/json-schema-ref-parser@11.9.3", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0" } }, "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ=="],
1290−
12911288 "@astrojs/cloudflare": ["@astrojs/cloudflare@13.1.10", "", { "dependencies": { "@astrojs/internal-helpers": "0.8.0", "@astrojs/underscore-redirects": "1.0.3", "@cloudflare/vite-plugin": "^1.25.6", "piccolore": "^0.1.3", "tinyglobby": "^0.2.15", "vite": "^7.3.1" }, "peerDependencies": { "astro": "^6.0.0", "wrangler": "^4.61.1" } }, "sha512-Ogl8p4MifPMHbpHKJL78eqEL+I3wbHrYmo83P/FkKZQ9VzzKi2A1RjnbaiiPAwrA8xQOqIUo4zlN7+Mse0aJAQ=="],
12921289
12931290 "@astrojs/compiler": ["@astrojs/compiler@3.0.1", "", {}, "sha512-z97oYbdebO5aoWzuJ/8q5hLK232+17KcLZ7cJ8BCWk6+qNzVxn/gftC0KzMBUTD8WAaBkPpNSQK6PXLnNrZ0CA=="],
@@ -1392,27 +1389,27 @@ · 98 unmodified lines
13921389
13931390 "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
13941391
1395− "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="],
1392+ "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw=="],
13961393
13971394 "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
13981395
1399− "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow=="],
1396+ "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/traverse": "^7.29.7", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg=="],
14001397
14011398 "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
14021399
1403− "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="],
1400+ "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg=="],
14041401
14051402 "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="],
14061403
14071404 "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="],
14081405
1409− "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="],
1406+ "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong=="],
14101407
14111408 "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="],
14121409
1413− "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.28.6", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg=="],
1410+ "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.29.7", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ=="],
14141411
1415− "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="],
1412+ "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ=="],
14161413
14171414 "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
14181415
@@ -1422,11 +1419,11 @@ · 3 unmodified lines
14221419
14231420 "@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="],
14241421
1425− "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
1422+ "@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="],
14261423
1427− "@babel/plugin-proposal-decorators": ["@babel/plugin-proposal-decorators@7.29.0", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-syntax-decorators": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA=="],
1424+ "@babel/plugin-proposal-decorators": ["@babel/plugin-proposal-decorators@7.29.7", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-syntax-decorators": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-EtU0Hi3GvrTqD56xKmZvV/uCXK2ZbwVNPNLAquVItcAZpUhkXwWlo3Fmj0c2LxgSf2I8IDULeAepwNP1OefLXg=="],
14281425
1429− "@babel/plugin-syntax-decorators": ["@babel/plugin-syntax-decorators@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA=="],
1426+ "@babel/plugin-syntax-decorators": ["@babel/plugin-syntax-decorators@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg=="],
14301427
14311428 "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="],
14321429
@@ -1472,6 +1469,8 @@ · 39 unmodified lines
14721469
14731470 "@better-fetch/fetch": ["@better-fetch/fetch@1.1.21", "", {}, "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A=="],
14741471
1472+ "@borewit/text-codec": ["@borewit/text-codec@0.2.2", "", {}, "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ=="],
1473+
14751474 "@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="],
14761475
14771476 "@capsizecss/unpack": ["@capsizecss/unpack@4.0.0", "", { "dependencies": { "fontkitten": "^1.0.0" } }, "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA=="],
@@ -1534,6 +1533,8 @@ · 56 unmodified lines
15341533
15351534 "@clerk/shared": ["@clerk/shared@4.22.0", "", { "dependencies": { "@tanstack/query-core": "^5.100.6", "dequal": "2.0.3", "glob-to-regexp": "0.4.1", "js-cookie": "3.0.7" }, "peerDependencies": { "react": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0", "react-dom": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-GZ56kzUB2UBb8MCF+Eo8WIey+W4RP1tAkyOL+geiHxrQxJlUcgD+xalY2YPJLH8WVFwtOnfIl8KPEo0M0e/DRg=="],
15361535
1536+ "@cloudflare/codemode": ["@cloudflare/codemode@0.4.2", "", { "dependencies": { "@types/json-schema": "^7.0.15", "acorn": "^8.17.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.0", "@tanstack/ai": ">=0.8.0 <1.0.0", "ai": "^6.0.0", "zod": "^4.0.0" }, "optionalPeers": ["@modelcontextprotocol/sdk", "@tanstack/ai", "ai", "zod"] }, "sha512-6sLMZDRY2USbXirrI4tjmGSMYAYM+E/PJIaDQLLbMdvQjk1z1TmJNSLomrZwErO1zFPXvGX2kaqkkxvixkfV2w=="],
1537+
15371538 "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.5.0", "", {}, "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg=="],
15381539
15391540 "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.16.1", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": ">1.20260305.0 <2.0.0-0" }, "optionalPeers": ["workerd"] }, "sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw=="],
@@ -2022,8 +2023,6 @@ · 482 unmodified lines
20222023
20232024 "@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="],
20242025
2025− "@jsdevtools/ono": ["@jsdevtools/ono@7.1.3", "", {}, "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="],
2026−
20272026 "@kitlangton/terminal-control": ["@kitlangton/terminal-control@0.3.0", "", { "optionalDependencies": { "@kitlangton/terminal-control-darwin-arm64": "0.3.0", "@kitlangton/terminal-control-darwin-x64": "0.3.0", "@kitlangton/terminal-control-linux-arm64-gnu": "0.3.0", "@kitlangton/terminal-control-linux-x64-gnu": "0.3.0" }, "peerDependencies": { "vitest": ">=3.0.0" }, "optionalPeers": ["vitest"] }, "sha512-YlOx9ztDInHBETgxWNRd6nq/YzpMlw1YEJ1OIrhdeWTqrQdJHIYThWGiWda9GxmQAThWNULkKR17as+QDgyKdQ=="],
20282027
20292028 "@kitlangton/terminal-control-darwin-arm64": ["@kitlangton/terminal-control-darwin-arm64@0.3.0", "", { "os": "darwin", "cpu": "arm64", "bin": { "termctrl": "bin/termctrl" } }, "sha512-HydPv5if5pAjwHNbA7TnZAJYKfq9AiZITaJuFyF1HsUMedzin3qzv9WRVBqIiGsQ/FiR1i1kaGhyLzaVpZR35w=="],
@@ -2092,8 +2091,12 @@ · 62 unmodified lines
20922091
20932092 "@mishieck/ink-titled-box": ["@mishieck/ink-titled-box@0.3.0", "", { "peerDependencies": { "ink": "^6.0.0", "react": "^19.1.0", "typescript": "^5" } }, "sha512-ugzVH9hixp3hwKfQ8On/qnsrdAxS3y9rTu/aGOFed4zVUvtZyGZNIR4rxAwXult8HKI4vJEh0OM8wib9NPrwUg=="],
20942093
2094+ "@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="],
2095+
20952096 "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="],
20962097
2098+ "@mongodb-js/zstd": ["@mongodb-js/zstd@7.0.0", "", { "dependencies": { "node-addon-api": "^8.5.0", "prebuild-install": "^7.1.3" } }, "sha512-mQ2s0pYYiav+tzCDR05Zptem8Ey2v8s11lri5RKGhTtL4COVCvVCk5vtyRYNT+9L8qSfyOqqefF9UtnW8mC5jA=="],
2099+
20972100 "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="],
20982101
20992102 "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="],
@@ -2140,6 +2143,8 @@ · 40 unmodified lines
21402143
21412144 "@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="],
21422145
2146+ "@nodable/entities": ["@nodable/entities@2.2.0", "", {}, "sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg=="],
2147+
21432148 "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
21442149
21452150 "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
@@ -2984,6 +2989,8 @@ · 838 unmodified lines
29842989
29852990 "@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.162.0", "", {}, "sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA=="],
29862991
2992+ "@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="],
2993+
29872994 "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
29882995
29892996 "@turbo/darwin-64": ["@turbo/darwin-64@2.9.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-X/56SnVXIQZBLKwniGTwEQTGmtE5brSACnKMBWpY3YafuxVYefrC2acamfjgxP7BG5w3I+6jf0UrLoSzgPcSJg=="],
@@ -3104,8 +3111,6 @@ · 114 unmodified lines
31043111
31053112 "@types/keyv": ["@types/keyv@3.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg=="],
31063113
3107− "@types/lodash": ["@types/lodash@4.17.24", "", {}, "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ=="],
3108−
31093114 "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
31103115
31113116 "@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="],
@@ -3232,7 +3237,7 @@ · 120 unmodified lines
32323237
32333238 "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
32343239
3235− "agents": ["agents@0.10.2", "", { "dependencies": { "@babel/plugin-proposal-decorators": "^7.29.0", "@cfworker/json-schema": "^4.1.1", "@modelcontextprotocol/sdk": "1.29.0", "@rolldown/plugin-babel": "^0.2.3", "cron-schedule": "^6.0.0", "json-schema": "^0.4.0", "json-schema-to-typescript": "^15.0.4", "mimetext": "^3.0.28", "nanoid": "^5.1.7", "partyserver": "^0.4.1", "partysocket": "1.1.16", "picomatch": "^4.0.4", "yargs": "^18.0.0" }, "peerDependencies": { "@ai-sdk/openai": "^3.0.0", "@ai-sdk/react": "^3.0.0", "@cloudflare/ai-chat": "^0.4.2", "@cloudflare/codemode": "^0.3.4", "@x402/core": "^2.0.0", "@x402/evm": "^2.0.0", "ai": "^6.0.0", "react": "^19.0.0", "viem": ">=2.0.0", "vite": ">=6.0.0 <9.0.0", "zod": "^4.0.0" }, "optionalPeers": ["@ai-sdk/openai", "@ai-sdk/react", "@cloudflare/ai-chat", "@cloudflare/codemode", "@x402/core", "@x402/evm", "viem", "vite"], "bin": { "agents": "dist/cli/index.js" } }, "sha512-zltFKKENDClZgIJB7aRneI8tRpnCbMbP4RGPh4t8OgqKHomxUNZJEqMrjcgwP/0FX1W9GGrrLJVo9Ez2ibHVvA=="],
3240+ "agents": ["agents@0.16.2", "", { "dependencies": { "@babel/plugin-proposal-decorators": "^7.29.7", "@cfworker/json-schema": "^4.1.1", "@cloudflare/codemode": "^0.4.1", "@modelcontextprotocol/sdk": "1.29.0", "@rolldown/plugin-babel": "^0.2.3", "cron-schedule": "^6.0.0", "esbuild": "^0.28.1", "just-bash": "^3.0.1", "mimetext": "^3.0.28", "nanoid": "^5.1.11", "partyserver": "^0.5.8", "partysocket": "1.2.0", "yaml": "^2.9.0", "yargs": "^18.0.0" }, "peerDependencies": { "@cloudflare/ai-chat": ">=0.8.5 <1.0.0", "@tanstack/ai": ">=0.10.2 <1.0.0", "@x402/core": "^2.0.0", "@x402/evm": "^2.0.0", "ai": "^6.0.0", "chat": "^4.29.0", "react": "^19.0.0", "vite": ">=6.0.0 <9.0.0", "zod": "^4.0.0" }, "optionalPeers": ["@cloudflare/ai-chat", "@tanstack/ai", "@x402/core", "@x402/evm", "chat", "vite"], "bin": { "agents": "dist/cli/index.js" } }, "sha512-pSWRw4l5j14cGsCPLDhXGjEfv4RX/kUHdgYsqCohd3AfutPNUEyGhbqfNzg8HklfQdZLpjJj+X7vsRAect5/4Q=="],
32363241
32373242 "ahooks": ["ahooks@3.9.7", "", { "dependencies": { "@babel/runtime": "^7.21.0", "@types/js-cookie": "^3.0.6", "dayjs": "^1.9.1", "intersection-observer": "^0.12.0", "js-cookie": "^3.0.5", "lodash": "^4.17.21", "react-fast-compare": "^3.2.2", "resize-observer-polyfill": "^1.5.1", "screenfull": "^5.0.0", "tslib": "^2.4.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-S0lvzhbdlhK36RFBkGv+RbOM/dbbweym+BIHM/bwwuWVSVN5TuVErHPMWo4w0t1NDYg5KPp2iEf7Y7E5LASYiw=="],
32383243
@@ -3264,6 +3269,8 @@ · 25 unmodified lines
32643269
32653270 "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
32663271
3272+ "anynum": ["anynum@1.0.1", "", {}, "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A=="],
3273+
32673274 "app-builder-bin": ["app-builder-bin@5.0.0-alpha.12", "", {}, "sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w=="],
32683275
32693276 "app-builder-lib": ["app-builder-lib@26.8.1", "", { "dependencies": { "@develar/schema-utils": "~2.6.5", "@electron/asar": "3.4.1", "@electron/fuses": "^1.8.0", "@electron/get": "^3.0.0", "@electron/notarize": "2.5.0", "@electron/osx-sign": "1.3.3", "@electron/rebuild": "^4.0.3", "@electron/universal": "2.0.3", "@malept/flatpak-bundler": "^0.4.0", "@types/fs-extra": "9.0.13", "async-exit-hook": "^2.0.1", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chromium-pickle-js": "^0.2.0", "ci-info": "4.3.1", "debug": "^4.3.4", "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", "ejs": "^3.1.8", "electron-publish": "26.8.1", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", "isbinaryfile": "^5.0.0", "jiti": "^2.4.2", "js-yaml": "^4.1.0", "json5": "^2.2.3", "lazy-val": "^1.0.5", "minimatch": "^10.0.3", "plist": "3.1.0", "proper-lockfile": "^4.1.2", "resedit": "^1.7.0", "semver": "~7.7.3", "tar": "^7.5.7", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0", "which": "^5.0.0" }, "peerDependencies": { "dmg-builder": "26.8.1", "electron-builder-squirrel-windows": "26.8.1" } }, "sha512-p0Im/Dx5C4tmz8QEE1Yn4MkuPC8PrnlRneMhWJj7BBXQfNTJUshM/bp3lusdEsDbvvfJZpXWnYesgSLvwtM2Zw=="],
@@ -3944,6 +3951,10 @@ · 674 unmodified lines
39443951
39453952 "fast-wrap-ansi": ["fast-wrap-ansi@0.2.0", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w=="],
39463953
3954+ "fast-xml-builder": ["fast-xml-builder@1.2.0", "", { "dependencies": { "path-expression-matcher": "^1.5.0", "xml-naming": "^0.1.0" } }, "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q=="],
3955+
3956+ "fast-xml-parser": ["fast-xml-parser@5.9.3", "", { "dependencies": { "@nodable/entities": "^2.2.0", "fast-xml-builder": "^1.2.0", "is-unsafe": "^1.0.1", "path-expression-matcher": "^1.5.0", "strnum": "^2.4.1", "xml-naming": "^0.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-brCNCeScma/kqa54J4PIDriSSSLssRkuYaUCpvHJulGc3HGI/xxKUCTDcYkAdqJsyb//ydpbxecjC3hB9+tb/g=="],
3957+
39473958 "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
39483959
39493960 "fd-package-json": ["fd-package-json@2.0.0", "", { "dependencies": { "walk-up-path": "^4.0.0" } }, "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ=="],
@@ -3960,7 +3971,7 @@ · 10 unmodified lines
39603971
39613972 "file-selector": ["file-selector@0.5.0", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-s8KNnmIDTBoD0p9uJ9uD0XY38SCeBOtj0UMXyQSLg1Ypfrfj8+dAvwsLjYQkQ2GjhVtp2HrnF5cJzMhBjfD8HA=="],
39623973
3963− "file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="],
3974+ "file-type": ["file-type@21.3.4", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g=="],
39643975
39653976 "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
39663977
@@ -4296,6 +4307,8 @@ · 329 unmodified lines
42964307
42974308 "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
42984309
4310+ "is-unsafe": ["is-unsafe@1.0.1", "", {}, "sha512-CLK2+VdgERgD96EYm5lUQssZYlRg2tkZnbsxZoacmSiRxiFJ4Nk4SzjCl+Ur+v3kXIY9dTIdb3IH22y1mZ56LA=="],
4311+
42994312 "is-wayland": ["is-wayland@0.1.0", "", {}, "sha512-QkbMsWkIfkrzOPxenwye0h56iAXirZYHG9eHVPb22fO9y+wPbaX/CHacOWBa/I++4ohTcByimhM1/nyCsH8KNA=="],
43004313
43014314 "is-windows": ["is-windows@1.0.2", "", {}, "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA=="],
@@ -4350,8 +4363,6 @@ · 48 unmodified lines
43504363
43514364 "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
43524365
4353− "json-schema-to-typescript": ["json-schema-to-typescript@15.0.4", "", { "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.5.5", "@types/json-schema": "^7.0.15", "@types/lodash": "^4.17.7", "is-glob": "^4.0.3", "js-yaml": "^4.1.0", "lodash": "^4.17.21", "minimist": "^1.2.8", "prettier": "^3.2.5", "tinyglobby": "^0.2.9" }, "bin": { "json2ts": "dist/src/cli.js" } }, "sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ=="],
4354−
43554366 "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
43564367
43574368 "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
@@ -4374,6 +4385,8 @@ · 16 unmodified lines
43744385
43754386 "jsprim": ["jsprim@1.4.2", "", { "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", "json-schema": "0.4.0", "verror": "1.10.0" } }, "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw=="],
43764387
4388+ "just-bash": ["just-bash@3.0.2", "", { "dependencies": { "diff": "^8.0.2", "fast-xml-parser": "^5.7.3", "file-type": "^21.2.0", "ini": "^6.0.0", "minimatch": "^10.1.1", "modern-tar": "^0.7.3", "papaparse": "^5.5.3", "quickjs-emscripten": "^0.32.0", "re2js": "^1.2.1", "seek-bzip": "^2.0.0", "smol-toml": "^1.6.0", "sprintf-js": "^1.1.3", "sql.js": "^1.13.0", "turndown": "^7.2.2", "yaml": "^2.8.2" }, "optionalDependencies": { "@mongodb-js/zstd": "^7.0.0", "node-liblzma": "^2.0.3" }, "bin": { "just-bash": "dist/bin/just-bash.js", "just-bash-shell": "dist/bin/shell/shell.js" } }, "sha512-uY8LMD2NpADp1HlYUcZSw9ffuq0tRhW76sg8xkeo+ZRDMu7mbekWbKbYDWquazVVi7pqpGZlic/liMFktQKy6w=="],
4389+
43774390 "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
43784391
43794392 "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
@@ -4674,6 +4687,8 @@ · 294 unmodified lines
46744687
46754688 "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="],
46764689
4690+ "modern-tar": ["modern-tar@0.7.6", "", {}, "sha512-sweCIVXzx1aIGTCdzcMlSZt1h8k5Tmk08VNAuRk3IU28XamGiOH5ypi11g6De2CH7PhYqSSnGy2A/EFhbWnVKg=="],
4691+
46774692 "module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="],
46784693
46794694 "monaco-editor": ["monaco-editor@0.55.1", "", { "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" } }, "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A=="],
@@ -4702,7 +4717,7 @@ · 22 unmodified lines
47024717
47034718 "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
47044719
4705− "nanoid": ["nanoid@5.1.9", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-ZUvP7KeBLe3OZ1ypw6dI/TzYJuvHP77IM4Ry73waSQTLn8/g8rpdjfyVAh7t1/+FjBtG4lCP42MEbDxOsRpBMw=="],
4720+ "nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="],
47064721
47074722 "nanostores": ["nanostores@1.3.0", "", {}, "sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA=="],
47084723
@@ -4716,7 +4731,7 @@ · 7 unmodified lines
47164731
47174732 "node-abi": ["node-abi@3.89.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA=="],
47184733
4719− "node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="],
4734+ "node-addon-api": ["node-addon-api@8.9.0", "", {}, "sha512-ekZMeaaIzSQTSpr7X2X3iJM7lTzgnx8ahAG9pJfT/7+14mlEM8ZYQ9cgCDvSSRbReFK0oHli3WrZdCiRsgAT9Q=="],
47204735
47214736 "node-api-version": ["node-api-version@0.2.1", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q=="],
47224737
@@ -4730,8 +4745,12 @@ · 7 unmodified lines
47304745
47314746 "node-gyp": ["node-gyp@12.3.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "tar": "^7.5.4", "tinyglobby": "^0.2.12", "undici": "^6.25.0", "which": "^6.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg=="],
47324747
4748+ "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="],
4749+
47334750 "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="],
47344751
4752+ "node-liblzma": ["node-liblzma@2.2.0", "", { "dependencies": { "node-addon-api": "^8.5.0", "node-gyp-build": "^4.8.4" }, "bin": { "nxz": "lib/cli/nxz.js" } }, "sha512-s0KzNOWwOJJgPG6wxg6cKohnAl9Wk/oW1KrQaVzJBjQwVcUGPQCzpR46Ximygjqj/3KhOrtJXnYMp/xYAXp75g=="],
4753+
47354754 "node-mock-http": ["node-mock-http@1.0.4", "", {}, "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ=="],
47364755
47374756 "node-releases": ["node-releases@2.0.37", "", {}, "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg=="],
@@ -4830,6 +4849,8 @@ · 92 unmodified lines
48304849
48314850 "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
48324851
4852+ "papaparse": ["papaparse@5.5.4", "", {}, "sha512-SwzWD9gl/ElwYLCI0nUja1mFJzjq2D8ziShfNBa7zCHzkOozeOGDwHWQ+tvCzEZcewecWZ5U7kUopDnG+DFYEQ=="],
4853+
48334854 "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
48344855
48354856 "parse-bmfont-ascii": ["parse-bmfont-ascii@1.0.6", "", {}, "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA=="],
@@ -4854,9 +4875,9 @@ · 18 unmodified lines
48544875
48554876 "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
48564877
4857− "partyserver": ["partyserver@0.4.1", "", { "dependencies": { "nanoid": "^5.1.6" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20240729.0" } }, "sha512-StSs0oY8RmTxjGNil7VbCG4gnTN+4rYX20fiUIItAxTPpr/5rPDZT6PIvMROkk9M1Gn7GzE1wuQXwhxceaGhXA=="],
4878+ "partyserver": ["partyserver@0.5.8", "", { "dependencies": { "nanoid": "^5.1.9" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260424.1" } }, "sha512-htgSwiBcBu9zIYLrsxBAOvdkjukHvncbTk0nDrJgfruvZ08rxtEN1Ab4T7j9osykP80Bq3zA2oWFd3ngc4Z9uw=="],
48584879
4859− "partysocket": ["partysocket@1.1.16", "", { "dependencies": { "event-target-polyfill": "^0.0.4" }, "peerDependencies": { "react": ">=17" }, "optionalPeers": ["react"] }, "sha512-d7xFv+ZC7x0p/DAHWJ5FhxQhimIx+ucyZY+kxL0cKddLBmK9c4p2tEA/L+dOOrWm6EYrRwrBjKQV0uSzOY9x1w=="],
4880+ "partysocket": ["partysocket@1.2.0", "", { "dependencies": { "event-target-polyfill": "^0.0.4" }, "peerDependencies": { "react": ">=17" }, "optionalPeers": ["react"] }, "sha512-xgXql4N0b3umN263lHkrZMvvtYC906h4YjY8l63LNcF0x1bfJmQtZLQGqNQWzFHAxLns/6h6/rjdLW4EdYqQwA=="],
48604881
48614882 "patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="],
48624883
@@ -4864,6 +4885,8 @@ · 1 unmodified line
48644885
48654886 "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
48664887
4888+ "path-expression-matcher": ["path-expression-matcher@1.6.1", "", {}, "sha512-h7bxdzhHk8Knyc4Tj+jMaa7fEEoUJy7p1qtbVgkYg1Uhpe5Np5VuGXCRZnkZvU+Q42M1vStt0ifa3ueykRJPmQ=="],
4889+
48674890 "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
48684891
48694892 "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
@@ -5050,6 +5073,8 @@ · 180 unmodified lines
50505073
50515074 "re-resizable": ["re-resizable@6.11.2", "", { "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-2xI2P3OHs5qw7K0Ud1aLILK6MQxW50TcO+DetD9eIV58j84TqYeHoZcL9H4GXFXXIh7afhH8mv5iUCXII7OW7A=="],
50525075
5076+ "re2js": ["re2js@1.4.0", "", {}, "sha512-KTOIcZTSOpOxbu3i0+T6mFQ6tkxXKlTxfcMFs1trQbsMnG84qNq+DjXr8Afu+FEFjvF1NNlldpC7roPyazFI8g=="],
5077+
50535078 "react": ["react@19.2.5", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="],
50545079
50555080 "react-avatar-editor": ["react-avatar-editor@15.1.0", "", { "peerDependencies": { "react": "^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Zto7u9l6Wd5LPPtjeFJ+7uwoT4bs01OSgkN2kxD18lWl8IiZ0GY3nWCbKPx4qIU7Au1vENsMJm19rfVWHHayaQ=="],
@@ -5268,6 +5293,8 @@ · 212 unmodified lines
52685293
52695294 "scroll-into-view-if-needed": ["scroll-into-view-if-needed@3.1.0", "", { "dependencies": { "compute-scroll-into-view": "^3.0.2" } }, "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ=="],
52705295
5296+ "seek-bzip": ["seek-bzip@2.0.0", "", { "dependencies": { "commander": "^6.0.0" }, "bin": { "seek-bunzip": "bin/seek-bunzip", "seek-table": "bin/seek-bzip-table" } }, "sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg=="],
5297+
52715298 "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
52725299
52735300 "semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="],
@@ -5364,6 +5391,8 @@ · 90 unmodified lines
53645391
53655392 "sql-highlight": ["sql-highlight@6.1.0", "", {}, "sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA=="],
53665393
5394+ "sql.js": ["sql.js@1.14.1", "", {}, "sha512-gcj8zBWU5cFsi9WUP+4bFNXAyF1iRpA3LLyS/DP5xlrNzGmPIizUeBggKa8DbDwdqaKwUcTEnChtd2grWo/x/A=="],
5395+
53675396 "srvx": ["srvx@0.11.15", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-iXsux0UcOjdvs0LCMa2Ws3WwcDUozA3JN3BquNXkaFPP7TpRqgunKdEgoZ/uwb1J6xaYHfxtz9Twlh6yzwM6Tg=="],
53685397
53695398 "sshpk": ["sshpk@1.18.0", "", { "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", "bcrypt-pbkdf": "^1.0.0", "dashdash": "^1.12.0", "ecc-jsbn": "~0.1.1", "getpass": "^0.1.1", "jsbn": "~0.1.0", "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" }, "bin": { "sshpk-conv": "bin/sshpk-conv", "sshpk-sign": "bin/sshpk-sign", "sshpk-verify": "bin/sshpk-verify" } }, "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ=="],
@@ -5410,7 +5439,9 @@ · 40 unmodified lines
54105439
54115440 "stripe": ["stripe@22.3.0", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-ypO6xjVrMWs9SmIMeHr8naCx3dAQ0clxMdUTxn7Ejd7hmY9meBGfE+N4pVHkf9sUNebAHp6uJo6mV3GxDIc2cA=="],
54125441
5413− "strtok3": ["strtok3@6.3.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" } }, "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw=="],
5442+ "strnum": ["strnum@2.4.1", "", { "dependencies": { "anynum": "^1.0.1" } }, "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg=="],
5443+
5444+ "strtok3": ["strtok3@10.3.5", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA=="],
54145445
54155446 "stubborn-fs": ["stubborn-fs@2.0.0", "", { "dependencies": { "stubborn-utils": "^1.0.1" } }, "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA=="],
54165447
@@ -5518,7 +5549,7 @@ · 101 unmodified lines
55185549
55195550 "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
55205551
5521− "token-types": ["token-types@4.2.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ=="],
5552+ "token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="],
55225553
55235554 "toml": ["toml@4.1.1", "", {}, "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw=="],
55245555
@@ -5552,6 +5583,8 @@ · 27 unmodified lines
55525583
55535584 "turbo": ["turbo@2.9.6", "", { "optionalDependencies": { "@turbo/darwin-64": "2.9.6", "@turbo/darwin-arm64": "2.9.6", "@turbo/linux-64": "2.9.6", "@turbo/linux-arm64": "2.9.6", "@turbo/windows-64": "2.9.6", "@turbo/windows-arm64": "2.9.6" }, "bin": { "turbo": "bin/turbo" } }, "sha512-+v2QJey7ZUeUiuigkU+uFfklvNUyPI2VO2vBpMYJA+a1hKFLFiKtUYlRHdb3P9CrAvMzi0upbjI4WT+zKtqkBg=="],
55545585
5586+ "turndown": ["turndown@7.2.4", "", { "dependencies": { "@mixmark-io/domino": "^2.2.0" } }, "sha512-I8yFsfRzmzK0WV1pNNOA4A7y4RDfFxPRxb3t+e3ui14qSGOxGtiSP6GjeX+Y6CHb7HYaFj7ECUD7VE5kQMZWGQ=="],
5587+
55555588 "tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="],
55565589
55575590 "twitter-api-v2": ["twitter-api-v2@1.29.0", "", {}, "sha512-v473q5bwme4N+DWSg6qY+JCvfg1nSJRWwui3HUALafxfqCvVkKiYmS/5x/pVeJwTmyeBxexMbzHwnzrH4h6oYQ=="],
@@ -5734,6 +5767,8 @@ · 176 unmodified lines
57345767
57355768 "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
57365769
5770+ "xml-naming": ["xml-naming@0.1.0", "", {}, "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw=="],
5771+
57375772 "xml-parse-from-string": ["xml-parse-from-string@1.0.1", "", {}, "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g=="],
57385773
57395774 "xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="],
@@ -5796,14 +5831,44 @@ · 56 unmodified lines
57965831
57975832 "@azure/msal-browser/@azure/msal-common": ["@azure/msal-common@16.4.1", "", {}, "sha512-Bl8f+w37xkXsYh7QRkAKCFGYtWMYuOVO7Lv+BxILrvGz3HbIEF22Pt0ugyj0QPOl6NLrHcnNUQ9yeew98P/5iw=="],
57985833
5834+ "@babel/core/@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
5835+
57995836 "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
58005837
5838+ "@babel/generator/@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
5839+
5840+ "@babel/helper-annotate-as-pure/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
5841+
58015842 "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
58025843
58035844 "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
58045845
5846+ "@babel/helper-create-class-features-plugin/@babel/traverse": ["@babel/traverse@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/types": "^7.29.7", "debug": "^4.3.1" } }, "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw=="],
5847+
58055848 "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
58065849
5850+ "@babel/helper-member-expression-to-functions/@babel/traverse": ["@babel/traverse@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/types": "^7.29.7", "debug": "^4.3.1" } }, "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw=="],
5851+
5852+ "@babel/helper-member-expression-to-functions/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
5853+
5854+ "@babel/helper-optimise-call-expression/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
5855+
5856+ "@babel/helper-replace-supers/@babel/traverse": ["@babel/traverse@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/types": "^7.29.7", "debug": "^4.3.1" } }, "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw=="],
5857+
5858+ "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse": ["@babel/traverse@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/types": "^7.29.7", "debug": "^4.3.1" } }, "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw=="],
5859+
5860+ "@babel/helper-skip-transparent-expression-wrappers/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
5861+
5862+ "@babel/parser/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
5863+
5864+ "@babel/plugin-proposal-decorators/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
5865+
5866+ "@babel/plugin-syntax-decorators/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
5867+
5868+ "@babel/template/@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
5869+
5870+ "@babel/traverse/@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
5871+
58075872 "@better-auth/core/jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="],
58085873
58095874 "@changesets/apply-release-plan/prettier": ["prettier@2.8.8", "", { "bin": { "prettier": "bin-prettier.js" } }, "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q=="],
@@ -5814,6 +5879,8 @@ · 4 unmodified lines
58145879
58155880 "@clerk/shared/js-cookie": ["js-cookie@3.0.7", "", {}, "sha512-z/wZZgDrkNV1eA0ULjM/F9/50Ya8fbzgKneSpoPsXSGd0KnpdtHfOZWK+GcwLk+EZbS4F9RBhU+K2RgzuDaItw=="],
58165881
5882+ "@cloudflare/codemode/acorn": ["acorn@8.17.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="],
5883+
58175884 "@cloudflare/vite-plugin/@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.16.0", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": "1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0" }, "optionalPeers": ["workerd"] }, "sha512-8ovsRpwzPoEqPUzoErAYVv8l3FMZNeBVQfJTvtzP4AgLSRGZISRfuChFxHWUQd3n6cnrwkuTGxT+2cGo8EsyYg=="],
58185885
58195886 "@cloudflare/vite-plugin/miniflare": ["miniflare@4.20260415.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.24.8", "workerd": "1.20260415.1", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-JoExRWN4YBI2luA5BoSMFEgi8rQWXUGzo3mtE+58VXCLV3jj/Xnk5Yeqs/IXWz8Es5GJIaq6BtsixDvAxXSIng=="],
@@ -5916,6 +5983,8 @@ · 96 unmodified lines
59165983
59175984 "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
59185985
5986+ "@jimp/core/file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="],
5987+
59195988 "@jimp/core/mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
59205989
59215990 "@jimp/plugin-blit/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
@@ -6246,6 +6315,8 @@ · 324 unmodified lines
62466315
62476316 "@tanstack/router-plugin/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
62486317
6318+ "@tanstack/router-utils/@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
6319+
62496320 "@tanstack/start-plugin-core/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
62506321
62516322 "@tanstack/start-plugin-core/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.40", "", {}, "sha512-s3GeJKSQOwBlzdUrj4ISjJj5SfSh+aqn0wjOar4Bx95iV1ETI7F6S/5hLcfAxZ9kXDcyrAkxPlqmd1ZITttf+w=="],
@@ -6254,6 +6325,10 @@ · 2 unmodified lines
62546325
62556326 "@tanstack/start-plugin-core/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
62566327
6328+ "@types/babel__core/@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
6329+
6330+ "@types/babel__template/@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
6331+
62576332 "@types/better-sqlite3/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
62586333
62596334 "@types/cacheable-request/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
@@ -6280,6 +6355,10 @@ · 20 unmodified lines
62806355
62816356 "@vercel/sdk/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
62826357
6358+ "agents/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
6359+
6360+ "agents/yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
6361+
62836362 "agents/yargs": ["yargs@18.0.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^7.2.0", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="],
62846363
62856364 "ai/@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
@@ -6314,6 +6393,8 @@ · 28 unmodified lines
63146393
63156394 "atmn/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
63166395
6396+ "babel-dead-code-elimination/@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
6397+
63176398 "basic-auth/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
63186399
63196400 "better-auth/jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="],
@@ -6434,6 +6515,8 @@ · 114 unmodified lines
64346515
64356516 "iconv-corefoundation/cli-truncate": ["cli-truncate@2.1.0", "", { "dependencies": { "slice-ansi": "^3.0.0", "string-width": "^4.2.0" } }, "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg=="],
64366517
6518+ "iconv-corefoundation/node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="],
6519+
64376520 "image-q/@types/node": ["@types/node@16.9.1", "", {}, "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g=="],
64386521
64396522 "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
@@ -6456,6 +6539,10 @@ · 16 unmodified lines
64566539
64576540 "jsprim/verror": ["verror@1.10.0", "", { "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw=="],
64586541
6542+ "just-bash/quickjs-emscripten": ["quickjs-emscripten@0.32.0", "", { "dependencies": { "@jitl/quickjs-wasmfile-debug-asyncify": "0.32.0", "@jitl/quickjs-wasmfile-debug-sync": "0.32.0", "@jitl/quickjs-wasmfile-release-asyncify": "0.32.0", "@jitl/quickjs-wasmfile-release-sync": "0.32.0", "quickjs-emscripten-core": "0.32.0" } }, "sha512-So0Sqw869y/S2oE3Nuc0uT3Dhqgvsj8FSrwBdsuTosVsG8ME5/OcudU1GxsrIFdFABgy17GHnTVO9TYV/bLQcA=="],
6543+
6544+ "just-bash/yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
6545+
64596546 "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
64606547
64616548 "kayvee/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="],
@@ -6464,6 +6551,8 @@ · 2 unmodified lines
64646551
64656552 "libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="],
64666553
6554+ "magicast/@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
6555+
64676556 "matcher/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
64686557
64696558 "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
@@ -6570,6 +6659,8 @@ · 100 unmodified lines
65706659
65716660 "run-jxa/type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="],
65726661
6662+ "seek-bzip/commander": ["commander@6.2.1", "", {}, "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA=="],
6663+
65736664 "serialize-error/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
65746665
65756666 "set-value/is-extendable": ["is-extendable@0.1.1", "", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="],
@@ -6644,8 +6735,64 @@ · 68 unmodified lines
66446735
66456736 "@azure/identity/@azure/msal-node/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
66466737
6738+ "@babel/helper-annotate-as-pure/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
6739+
6740+ "@babel/helper-annotate-as-pure/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
6741+
66476742 "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
66486743
6744+ "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="],
6745+
6746+ "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/generator": ["@babel/generator@7.29.7", "", { "dependencies": { "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ=="],
6747+
6748+ "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="],
6749+
6750+ "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="],
6751+
6752+ "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
6753+
6754+ "@babel/helper-member-expression-to-functions/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="],
6755+
6756+ "@babel/helper-member-expression-to-functions/@babel/traverse/@babel/generator": ["@babel/generator@7.29.7", "", { "dependencies": { "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ=="],
6757+
6758+ "@babel/helper-member-expression-to-functions/@babel/traverse/@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="],
6759+
6760+ "@babel/helper-member-expression-to-functions/@babel/traverse/@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="],
6761+
6762+ "@babel/helper-member-expression-to-functions/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
6763+
6764+ "@babel/helper-member-expression-to-functions/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
6765+
6766+ "@babel/helper-optimise-call-expression/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
6767+
6768+ "@babel/helper-optimise-call-expression/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
6769+
6770+ "@babel/helper-replace-supers/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="],
6771+
6772+ "@babel/helper-replace-supers/@babel/traverse/@babel/generator": ["@babel/generator@7.29.7", "", { "dependencies": { "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ=="],
6773+
6774+ "@babel/helper-replace-supers/@babel/traverse/@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="],
6775+
6776+ "@babel/helper-replace-supers/@babel/traverse/@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="],
6777+
6778+ "@babel/helper-replace-supers/@babel/traverse/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
6779+
6780+ "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="],
6781+
6782+ "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/generator": ["@babel/generator@7.29.7", "", { "dependencies": { "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ=="],
6783+
6784+ "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="],
6785+
6786+ "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="],
6787+
6788+ "@babel/helper-skip-transparent-expression-wrappers/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
6789+
6790+ "@babel/helper-skip-transparent-expression-wrappers/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
6791+
6792+ "@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
6793+
6794+ "@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
6795+
66496796 "@cloudflare/vite-plugin/miniflare/undici": ["undici@7.24.8", "", {}, "sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ=="],
66506797
66516798 "@cloudflare/vite-plugin/miniflare/workerd": ["workerd@1.20260415.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260415.1", "@cloudflare/workerd-darwin-arm64": "1.20260415.1", "@cloudflare/workerd-linux-64": "1.20260415.1", "@cloudflare/workerd-linux-arm64": "1.20260415.1", "@cloudflare/workerd-windows-64": "1.20260415.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-phyPjRnx+mQDfkhN9ENPioL1L0SdhYs4S0YmJK/xF9Oga+ykNfdSy1MHnsOj8yqnOV96zcVQMx32dJ0r3pq0jQ=="],
@@ -6846,6 +6993,10 @@ · 194 unmodified lines
68466993
68476994 "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
68486995
6996+ "@jimp/core/file-type/strtok3": ["strtok3@6.3.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" } }, "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw=="],
6997+
6998+ "@jimp/core/file-type/token-types": ["token-types@4.2.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ=="],
6999+
68497000 "@libsql/kysely-libsql/@libsql/client/@libsql/core": ["@libsql/core@0.8.1", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-u6nrj6HZMTPsgJ9EBhLzO2uhqhlHQJQmVHV+0yFLvfGf3oSP8w7TjZCNUgu1G8jHISx6KFi7bmcrdXW9lRt++A=="],
68507001
68517002 "@libsql/kysely-libsql/@libsql/client/@libsql/hrana-client": ["@libsql/hrana-client@0.6.2", "", { "dependencies": { "@libsql/isomorphic-fetch": "^0.2.1", "@libsql/isomorphic-ws": "^0.1.5", "js-base64": "^3.7.5", "node-fetch": "^3.3.2" } }, "sha512-MWxgD7mXLNf9FXXiM0bc90wCjZSpErWKr5mGza7ERy2FJNNMXd7JIOv+DepBA1FQTIfI8TFO4/QDYgaQC0goNw=="],
@@ -6940,6 +7091,8 @@ · 88 unmodified lines
69407091
69417092 "@tanstack/router-generator/@tanstack/router-core/seroval-plugins": ["seroval-plugins@1.5.4", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw=="],
69427093
7094+ "@tanstack/router-generator/@tanstack/router-utils/@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
7095+
69437096 "@tanstack/start-plugin-core/@tanstack/router-generator/@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.161.7", "", { "bin": { "intent": "bin/intent.js" } }, "sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ=="],
69447097
69457098 "@types/better-sqlite3/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
@@ -6966,6 +7119,58 @@ · 20 unmodified lines
69667119
69677120 "@types/yauzl/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
69687121
7122+ "agents/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
7123+
7124+ "agents/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="],
7125+
7126+ "agents/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="],
7127+
7128+ "agents/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="],
7129+
7130+ "agents/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="],
7131+
7132+ "agents/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="],
7133+
7134+ "agents/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="],
7135+
7136+ "agents/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="],
7137+
7138+ "agents/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="],
7139+
7140+ "agents/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="],
7141+
7142+ "agents/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="],
7143+
7144+ "agents/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="],
7145+
7146+ "agents/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="],
7147+
7148+ "agents/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="],
7149+
7150+ "agents/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="],
7151+
7152+ "agents/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="],
7153+
7154+ "agents/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="],
7155+
7156+ "agents/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="],
7157+
7158+ "agents/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="],
7159+
7160+ "agents/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="],
7161+
7162+ "agents/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="],
7163+
7164+ "agents/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="],
7165+
7166+ "agents/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="],
7167+
7168+ "agents/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="],
7169+
7170+ "agents/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="],
7171+
7172+ "agents/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
7173+
69697174 "agents/yargs/cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="],
69707175
69717176 "agents/yargs/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
@@ -7178,6 +7383,16 @@ · 206 unmodified lines
71787383
71797384 "jsprim/verror/extsprintf": ["extsprintf@1.4.1", "", {}, "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA=="],
71807385
7386+ "just-bash/quickjs-emscripten/@jitl/quickjs-wasmfile-debug-asyncify": ["@jitl/quickjs-wasmfile-debug-asyncify@0.32.0", "", { "dependencies": { "@jitl/quickjs-ffi-types": "0.32.0" } }, "sha512-EX8zbXwGqCgAE764M+qvkHtyXDi/FUoMBea0JnES7vCM3P7a2+EOZOjGv85wtZ2sJhI1oJ+nekmqpOODFDY+hw=="],
7387+
7388+ "just-bash/quickjs-emscripten/@jitl/quickjs-wasmfile-debug-sync": ["@jitl/quickjs-wasmfile-debug-sync@0.32.0", "", { "dependencies": { "@jitl/quickjs-ffi-types": "0.32.0" } }, "sha512-LeYWrPGC1uNCTBWvibo3ZLJj0CSVNYUXvJpXMCmuQ5Sap2cCACc3uvGvYV4homHHBAzfw5akoTqMMS4YFRtw+Q=="],
7389+
7390+ "just-bash/quickjs-emscripten/@jitl/quickjs-wasmfile-release-asyncify": ["@jitl/quickjs-wasmfile-release-asyncify@0.32.0", "", { "dependencies": { "@jitl/quickjs-ffi-types": "0.32.0" } }, "sha512-3oSwPfja12ICz4aIblB58cuY8JlEq5Txt8Cut4VLo+LH47QN+mzCnSgnbB03hWzg1LBcc+VyyI9UOag7a1NF+Q=="],
7391+
7392+ "just-bash/quickjs-emscripten/@jitl/quickjs-wasmfile-release-sync": ["@jitl/quickjs-wasmfile-release-sync@0.32.0", "", { "dependencies": { "@jitl/quickjs-ffi-types": "0.32.0" } }, "sha512-BKNDI/TPBfGlLNGYpLrhcDGXmIk4xHm4MRAisOBnOzpXVn9HZWsfmMAc9WMBrAHjvvds6HOikKeaOBKdPdpVrg=="],
7393+
7394+ "just-bash/quickjs-emscripten/quickjs-emscripten-core": ["quickjs-emscripten-core@0.32.0", "", { "dependencies": { "@jitl/quickjs-ffi-types": "0.32.0" } }, "sha512-QFnPfjFey8EqknSrSxe1hZrf1/8z7/6s1QzGOmKo6++02r7QRRX7ZoyNaZh7JuVjWsVW87KnQrbZqnHkOAzUyg=="],
7395+
71817396 "kayvee/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
71827397
71837398 "mimetext/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
@@ -7306,6 +7521,22 @@ · 122 unmodified lines
73067521
73077522 "yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
73087523
7524+ "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
7525+
7526+ "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
7527+
7528+ "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
7529+
7530+ "@babel/helper-member-expression-to-functions/@babel/traverse/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
7531+
7532+ "@babel/helper-replace-supers/@babel/traverse/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
7533+
7534+ "@babel/helper-replace-supers/@babel/traverse/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
7535+
7536+ "@babel/helper-replace-supers/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
7537+
7538+ "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
7539+
73097540 "@cloudflare/vite-plugin/miniflare/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260415.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-dsxaKsQm3LnPGNPEdsRv09QN3Y4DqCw7kX5j6noKqbAtro2jTr95sVlYM1jUxZ5FkOl1f7SXgaKKB9t5H5Nkbg=="],
73107541
73117542 "@cloudflare/vite-plugin/miniflare/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260415.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-+JgSgVA49KyKteHRA1SnonE4Zn5Ei5zdAp5FQMxFmXI8qulZw4Hl7safXxRyK4i9sTO8gl7TFOKO5Q64VPvSDQ=="],
@@ -7468,6 +7699,16 @@ · 156 unmodified lines
74687699
74697700 "ink-confirm-input/ink-text-input/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
74707701
7702+ "just-bash/quickjs-emscripten/@jitl/quickjs-wasmfile-debug-asyncify/@jitl/quickjs-ffi-types": ["@jitl/quickjs-ffi-types@0.32.0", "", {}, "sha512-v9T+GQpmk43VDJ7d72sf0Nexhk+ArvtUihW27dy7lqAl0zBObFKtSBBIm5RBjwIhE8VwsPPm9PNuvPvNqLWUEg=="],
7703+
7704+ "just-bash/quickjs-emscripten/@jitl/quickjs-wasmfile-debug-sync/@jitl/quickjs-ffi-types": ["@jitl/quickjs-ffi-types@0.32.0", "", {}, "sha512-v9T+GQpmk43VDJ7d72sf0Nexhk+ArvtUihW27dy7lqAl0zBObFKtSBBIm5RBjwIhE8VwsPPm9PNuvPvNqLWUEg=="],
7705+
7706+ "just-bash/quickjs-emscripten/@jitl/quickjs-wasmfile-release-asyncify/@jitl/quickjs-ffi-types": ["@jitl/quickjs-ffi-types@0.32.0", "", {}, "sha512-v9T+GQpmk43VDJ7d72sf0Nexhk+ArvtUihW27dy7lqAl0zBObFKtSBBIm5RBjwIhE8VwsPPm9PNuvPvNqLWUEg=="],
7707+
7708+ "just-bash/quickjs-emscripten/@jitl/quickjs-wasmfile-release-sync/@jitl/quickjs-ffi-types": ["@jitl/quickjs-ffi-types@0.32.0", "", {}, "sha512-v9T+GQpmk43VDJ7d72sf0Nexhk+ArvtUihW27dy7lqAl0zBObFKtSBBIm5RBjwIhE8VwsPPm9PNuvPvNqLWUEg=="],
7709+
7710+ "just-bash/quickjs-emscripten/quickjs-emscripten-core/@jitl/quickjs-ffi-types": ["@jitl/quickjs-ffi-types@0.32.0", "", {}, "sha512-v9T+GQpmk43VDJ7d72sf0Nexhk+ArvtUihW27dy7lqAl0zBObFKtSBBIm5RBjwIhE8VwsPPm9PNuvPvNqLWUEg=="],
7711+
74717712 "kayvee/js-yaml/argparse/sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
74727713
74737714 "ora/cli-cursor/restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
packages/core/api/src/server/executor-app.ts+13−2
@@ -49,6 +49,7 @@ import type { PluginsProvider } from "./scoped-executor"; · 48 unmodified lines
4949 import { requestScopedMiddleware } from "./request-scoped";
5050 import {
5151 McpServingRoutes,
52+ McpDiscoveryRoutes,
5253 McpErrorReporterNoop,
5354 type McpAuthProvider,
5455 type McpErrorReporter,
@@ -128,8 +129,12 @@ export interface EngineProviders<REngine = never> { · 73 unmodified lines
128129 export interface McpProviders<RMcpAuth = never> {
129130 /** Resolve a request to an MCP `AuthOutcome` + declare the discovery routes. */
130131 readonly auth: Layer.Layer<McpAuthProvider, never, RMcpAuth>;
131− /** Owns the entire serving-session lifecycle (in-process Map vs DO). */
132− readonly sessions: Layer.Layer<McpSessionStore>;
132+ /**
133+ * Owns the entire serving-session lifecycle (in-process Map vs DO). Optional:
134+ * a host that serves `/mcp` transport outside this envelope (the Cloudflare
135+ * Agent bridge) omits it, and only the discovery routes are mounted.
136+ */
137+ readonly sessions?: Layer.Layer<McpSessionStore>;
133138 /** Forward an orchestration defect to the host's capture; default no-op. */
134139 readonly reporter?: Layer.Layer<McpErrorReporter>;
135140 }
@@ -609,6 +614,12 @@ const buildMcpRoutes = <RMcpAuth>( · 473 unmodified lines
609614 const mcpAuthLive = (mcp.auth as Layer.Layer<McpAuthProvider, never, IdentityProvider>).pipe(
610615 Layer.provide(identity),
611616 );
617+ // No session store: the host serves `/mcp` transport elsewhere (the Cloudflare
618+ // Agent bridge), so mount only the auth-declared discovery routes. The discovery
619+ // handlers are captured from the auth seam at build, so no per-request seams.
620+ if (!mcp.sessions) {
621+ return McpDiscoveryRoutes.pipe(Layer.provide(mcpAuthLive));
622+ }
612623 const mcpSeams = Layer.mergeAll(mcpAuthLive, mcp.sessions, mcp.reporter ?? McpErrorReporterNoop);
613624 return McpServingRoutes.pipe(HttpRouter.provideRequest(mcpSeams), Layer.provide(mcpAuthLive));
614625 };
packages/core/execution/src/engine.test.ts+15−0
@@ -85,3 +85,18 @@ describe("executeWithPause failure propagation", () => { · 84 unmodified lines
8585 }),
8686 );
8787 });
88+
89+describe("pausedExecutionCount", () => {
90+ it.effect("starts at zero", () =>
91+ Effect.gen(function* () {
92+ const executor = yield* makeExecutor();
93+ const engine = createExecutionEngine({
94+ executor,
95+ codeExecutor: succeedingExecutor,
96+ });
97+
98+ expect(yield* engine.pausedExecutionCount()).toBe(0);
99+ expect(yield* engine.hasPausedExecutions()).toBe(false);
100+ }),
101+ );
102+});
packages/core/execution/src/engine.ts+8−0
@@ -401,6 +401,12 @@ export type ExecutionEngine<E extends Cause.YieldableError = CodeExecutionError> · 400 unmodified lines
401401 */
402402 readonly getPausedExecution: (executionId: string) => Effect.Effect<PausedExecution | null>;
403403
404+ /** Count of executions currently paused awaiting resume. */
405+ readonly pausedExecutionCount: () => Effect.Effect<number>;
406+
407+ /** Whether any executions are paused awaiting resume. */
408+ readonly hasPausedExecutions: () => Effect.Effect<boolean>;
409+
404410 /**
405411 * Get the dynamic tool description (workflow + namespaces).
406412 */
@@ -633,6 +639,8 @@ export const createExecutionEngine = <E extends Cause.YieldableError = CodeExecu · 226 unmodified lines
633639 resume: resumeExecution,
634640 getPausedExecution: (executionId) =>
635641 Effect.sync(() => pausedExecutions.get(executionId) ?? null),
642+ pausedExecutionCount: () => Effect.sync(() => pausedExecutions.size),
643+ hasPausedExecutions: () => Effect.sync(() => pausedExecutions.size > 0),
636644 getDescription: buildExecuteDescription(executor),
637645 };
638646 };
packages/core/execution/src/promise.ts+4−0
@@ -50,6 +50,8 @@ export type ExecutionEngine = { · 49 unmodified lines
5050 response: ResumeResponse,
5151 ) => Promise<ExecutionResult | null>;
5252 readonly getPausedExecution: (executionId: string) => Promise<PausedExecution | null>;
53+ readonly pausedExecutionCount: () => Promise<number>;
54+ readonly hasPausedExecutions: () => Promise<boolean>;
5355 readonly getDescription: () => Promise<string>;
5456 };
5557
@@ -155,6 +157,8 @@ export const toPromiseExecutionEngine = <E extends Cause.YieldableError>( · 99 unmodified lines
155157 executeWithPause: (code, options) => Effect.runPromise(engine.executeWithPause(code, options)),
156158 resume: (executionId, response) => Effect.runPromise(engine.resume(executionId, response)),
157159 getPausedExecution: (executionId) => Effect.runPromise(engine.getPausedExecution(executionId)),
160+ pausedExecutionCount: () => Effect.runPromise(engine.pausedExecutionCount()),
161+ hasPausedExecutions: () => Effect.runPromise(engine.hasPausedExecutions()),
158162 getDescription: () => Effect.runPromise(engine.getDescription),
159163 });
160164
packages/hosts/cloudflare/package.json+4−20
@@ -8,29 +8,13 @@ · 7 unmodified lines
88 "types": "./src/blob-store.ts",
99 "default": "./src/blob-store.ts"
1010 },
11− "./mcp/worker-transport": {
12− "types": "./src/mcp/worker-transport.ts",
13− "default": "./src/mcp/worker-transport.ts"
14− },
1511 "./mcp/do-headers": {
1612 "types": "./src/mcp/do-headers.ts",
1713 "default": "./src/mcp/do-headers.ts"
1814 },
19− "./mcp/response-peek": {
20− "types": "./src/mcp/response-peek.ts",
21− "default": "./src/mcp/response-peek.ts"
22− },
23− "./mcp/session-store": {
24− "types": "./src/mcp/session-store.ts",
25− "default": "./src/mcp/session-store.ts"
26− },
27− "./mcp/durable-object": {
28− "types": "./src/mcp/session-durable-object.ts",
29− "default": "./src/mcp/session-durable-object.ts"
30− },
31− "./mcp/request-id-queue": {
32− "types": "./src/mcp/request-id-queue.ts",
33− "default": "./src/mcp/request-id-queue.ts"
15+ "./mcp/agent-durable-object": {
16+ "types": "./src/mcp/agent-session-durable-object.ts",
17+ "default": "./src/mcp/agent-session-durable-object.ts"
3418 }
3519 },
3620 "scripts": {
@@ -44,7 +28,7 @@ · 7 unmodified lines
4428 "@executor-js/host-mcp": "workspace:*",
4529 "@executor-js/sdk": "workspace:*",
4630 "@modelcontextprotocol/sdk": "^1.29.0",
47− "agents": "^0.10.0",
31+ "agents": "^0.16.2",
4832 "effect": "catalog:"
4933 },
5034 "devDependencies": {
packages/hosts/cloudflare/src/mcp/agent-session-durable-object.ts+788−0
@@ -0,0 +1,788 @@
1+import { Cause, Deferred, Effect, Exit, Option, Schema } from "effect";
2+import type * as Tracer from "effect/Tracer";
3+import type { Connection, ConnectionContext } from "agents";
4+import { McpAgent } from "agents/mcp";
5+import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6+
7+import { RequestOrgSlug, RequestWebOrigin } from "@executor-js/api/server";
8+import {
9+ formatPausedExecution,
10+ type ExecutionEngine,
11+ type ResumeResponse,
12+} from "@executor-js/execution";
13+import {
14+ PAUSED_APPROVAL_TIMEOUT_MS,
15+ type PausedExecutionHooks,
16+} from "@executor-js/host-mcp/tool-server";
17+import type { McpResource } from "@executor-js/host-mcp";
18+
19+import type { IncomingPropagationHeaders, McpElicitationMode } from "./do-headers";
20+import {
21+ SESSION_TIMEOUT_MS,
22+ decideSessionAlarm,
23+ pausedLeaseExtensionLog,
24+} from "./session-alarm-policy";
25+
26+export type IncomingTraceHeaders = IncomingPropagationHeaders;
27+
28+export interface McpSessionInit {
29+ readonly organizationId: string;
30+ readonly userId: string;
31+ readonly elicitationMode: McpElicitationMode;
32+ /** The MCP resource the session was minted against (`/mcp` default vs a
33+ * `/mcp/toolkits/<slug>` toolkit), so the tool catalog is scoped to it. */
34+ readonly resource: McpResource;
35+ readonly webOrigin?: string;
36+}
37+
38+export interface McpSessionProps extends Record<string, unknown> {
39+ readonly session: McpSessionInit;
40+ readonly propagation?: IncomingTraceHeaders;
41+}
42+
43+export type McpApprovalOwner = {
44+ readonly accountId: string;
45+ readonly organizationId: string;
46+};
47+
48+type McpSessionApprovalErrorResult =
49+ | { readonly status: "not_found" }
50+ | { readonly status: "forbidden" };
51+
52+type PendingApprovalLease = {
53+ readonly disposeKeepAlive: () => void;
54+ timeout: ReturnType<typeof setTimeout> | null;
55+ expiring: boolean;
56+};
57+
58+export type McpSessionApprovalResult =
59+ | {
60+ readonly status: "ok";
61+ readonly text: string;
62+ readonly structured: Record<string, unknown>;
63+ }
64+ | McpSessionApprovalErrorResult;
65+
66+export type McpSessionResumeApprovalResult =
67+ | {
68+ readonly status: "ok";
69+ readonly executionStatus: "completed" | "paused";
70+ readonly text: string;
71+ readonly structured: Record<string, unknown>;
72+ readonly isError?: boolean;
73+ }
74+ | McpSessionApprovalErrorResult;
75+
76+export interface SessionDbHandle {
77+ readonly end: () => Promise<void> | void;
78+}
79+
80+export interface SessionMeta {
81+ readonly organizationId: string;
82+ readonly organizationName: string;
83+ /** The org's URL slug, when the host's `resolveSessionMeta` carried one.
84+ * Pins browser-handoff URLs to the right org's console. */
85+ readonly organizationSlug?: string;
86+ readonly userId: string;
87+ readonly elicitationMode?: "browser" | "model" | "native";
88+ /** The MCP resource the session serves (carried from {@link McpSessionInit});
89+ * `buildMcpServer` scopes the tool catalog to it. */
90+ readonly resource: McpResource;
91+ readonly webOrigin?: string;
92+}
93+
94+export interface BuiltMcpServer {
95+ readonly mcpServer: McpServer;
96+ readonly engine: ExecutionEngine<Cause.YieldableError>;
97+}
98+
99+export interface BrowserApprovalStore {
100+ readonly takeResponse: (executionId: string) => Effect.Effect<ResumeResponse | null>;
101+ readonly waitForResponse: (executionId: string) => Effect.Effect<ResumeResponse | null>;
102+}
103+
104+const SESSION_META_KEY = "session-meta";
105+const LAST_ACTIVITY_KEY = "last-activity-ms";
106+const PARTYSERVER_NAME_KEY = "__ps_name";
107+const MCP_HTTP_METHOD_HEADER = "cf-mcp-method";
108+const MCP_MESSAGE_HEADER = "cf-mcp-message";
109+const ACTIVE_POST_RESPONSE_WAIT_POLL_MS = 25;
110+const ACTIVE_POST_RESPONSE_WAIT_MAX_MS = PAUSED_APPROVAL_TIMEOUT_MS + 30_000;
111+const approvalResponseKey = (executionId: string) => `approval-response:${executionId}`;
112+
113+type JsonRpcRequestId = string | number;
114+const JsonRpcRequestWithId = Schema.Struct({
115+ id: Schema.Union([Schema.String, Schema.Number]),
116+ method: Schema.String,
117+});
118+const JsonRpcPostPayload = Schema.fromJsonString(Schema.Unknown);
119+const decodeJsonRpcPostPayload = Schema.decodeUnknownOption(JsonRpcPostPayload);
120+const decodeJsonRpcRequestWithId = Schema.decodeUnknownOption(JsonRpcRequestWithId);
121+
122+const resumeApprovalResult = (
123+ executionId: string,
124+ response: ResumeResponse,
125+): Extract<McpSessionResumeApprovalResult, { readonly status: "ok" }> => {
126+ const textByAction = {
127+ accept: "I've approved it",
128+ decline: "I've denied it",
129+ cancel: "I've canceled it",
130+ } satisfies Record<ResumeResponse["action"], string>;
131+ const statusByAction = {
132+ accept: "approved",
133+ decline: "denied",
134+ cancel: "canceled",
135+ } satisfies Record<ResumeResponse["action"], string>;
136+
137+ return {
138+ status: "ok",
139+ executionStatus: "completed",
140+ text: textByAction[response.action],
141+ structured: { status: statusByAction[response.action], executionId },
142+ isError: false,
143+ };
144+};
145+
146+const isSessionProps = (props: unknown): props is McpSessionProps =>
147+ typeof props === "object" &&
148+ props !== null &&
149+ "session" in props &&
150+ typeof (props as { readonly session?: unknown }).session === "object" &&
151+ (props as { readonly session?: unknown }).session !== null;
152+
153+const sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));
154+
155+const readActivePostRequestIds = (request: Request): readonly JsonRpcRequestId[] => {
156+ if (request.headers.get(MCP_HTTP_METHOD_HEADER) !== "POST") return [];
157+ const encoded = request.headers.get(MCP_MESSAGE_HEADER);
158+ if (!encoded) return [];
159+ const decoded = Effect.runSyncExit(
160+ Effect.try({
161+ try: () => atob(encoded),
162+ catch: () => "invalid_base64" as const,
163+ }),
164+ );
165+ if (Exit.isFailure(decoded)) {
166+ console.warn(
167+ JSON.stringify({
168+ event: "mcp_active_post_response_wait_parse_failed",
169+ reason: "invalid_base64",
170+ }),
171+ );
172+ return [];
173+ }
174+ const parsed = decodeJsonRpcPostPayload(decoded.value);
175+ if (Option.isNone(parsed)) {
176+ console.warn(
177+ JSON.stringify({
178+ event: "mcp_active_post_response_wait_parse_failed",
179+ reason: "invalid_json",
180+ }),
181+ );
182+ return [];
183+ }
184+ const messages = Array.isArray(parsed.value) ? parsed.value : [parsed.value];
185+ const requestIds: JsonRpcRequestId[] = [];
186+ for (const message of messages) {
187+ const decoded = decodeJsonRpcRequestWithId(message);
188+ if (Option.isSome(decoded)) requestIds.push(decoded.value.id);
189+ }
190+ return requestIds;
191+};
192+
193+export abstract class McpAgentSessionDOBase<
194+ Env extends Cloudflare.Env = Cloudflare.Env,
195+ TDbHandle extends SessionDbHandle = SessionDbHandle,
196+> extends McpAgent<Env, unknown, McpSessionProps> {
197+ server!: McpServer;
198+ private engine: ExecutionEngine<Cause.YieldableError> | null = null;
199+ private dbHandle: TDbHandle | null = null;
200+ private sessionMeta: SessionMeta | null = null;
201+ private initialized = false;
202+ private lastActivityMs = 0;
203+ private approvalResponses = new Map<string, ResumeResponse>();
204+ private approvalWaiters = new Map<string, Deferred.Deferred<ResumeResponse>>();
205+ private pendingApprovalLeases = new Map<string, PendingApprovalLease>();
206+
207+ protected abstract openSessionDb(): TDbHandle | Promise<TDbHandle>;
208+
209+ protected abstract resolveSessionMeta(token: McpSessionInit): Effect.Effect<SessionMeta>;
210+
211+ protected abstract buildMcpServer(
212+ sessionMeta: SessionMeta,
213+ dbHandle: TDbHandle,
214+ ): Effect.Effect<BuiltMcpServer>;
215+
216+ protected withTelemetry<A, E>(
217+ effect: Effect.Effect<A, E>,
218+ _incoming?: IncomingTraceHeaders,
219+ ): Effect.Effect<A, E> {
220+ return effect;
221+ }
222+
223+ protected captureCause(_cause: Cause.Cause<unknown>): void {}
224+
225+ protected flushTelemetry(): Promise<void> {
226+ return Promise.resolve();
227+ }
228+
229+ protected get sessionId(): string {
230+ return this.getSessionId();
231+ }
232+
233+ protected currentParentSpan(): Tracer.AnySpan | undefined {
234+ return undefined;
235+ }
236+
237+ protected readonly browserApprovalStore: BrowserApprovalStore = {
238+ takeResponse: (executionId) => this.takeApprovalResponse(executionId),
239+ waitForResponse: (executionId) => this.waitForApprovalResponse(executionId),
240+ };
241+
242+ protected readonly pausedExecutionHooks: PausedExecutionHooks = {
243+ onExecutionPaused: (executionId) =>
244+ Effect.sync(() => {
245+ this.queuePendingApprovalLeaseStart(executionId);
246+ }),
247+ onResumeStarted: (executionId) => this.beginPendingApprovalResume(executionId),
248+ onResumeSettled: (executionId) => this.finishPendingApprovalResume(executionId),
249+ };
250+
251+ override async onConnect(conn: Connection, context: ConnectionContext): Promise<void> {
252+ const requestIds = readActivePostRequestIds(context.request);
253+ if (requestIds.length === 0) {
254+ await super.onConnect(conn, context);
255+ return;
256+ }
257+
258+ await this.keepAliveWhile(async () => {
259+ await super.onConnect(conn, context);
260+ await this.waitForActivePostResponseDrain(conn.id);
261+ });
262+ }
263+
264+ private openSessionDbHandle(): Effect.Effect<TDbHandle> {
265+ return Effect.promise(() => Promise.resolve(this.openSessionDb()));
266+ }
267+
268+ private async waitForActivePostResponseDrain(streamId: string): Promise<void> {
269+ const startedAt = Date.now();
270+ for (;;) {
271+ const requestIds = await this.getStreamRequestIds(streamId);
272+ if (!requestIds || requestIds.length === 0) return;
273+ if (Date.now() - startedAt >= ACTIVE_POST_RESPONSE_WAIT_MAX_MS) {
274+ console.warn(
275+ JSON.stringify({
276+ event: "mcp_active_post_response_wait_timeout",
277+ sessionId: this.sessionId,
278+ streamId,
279+ requestIds,
280+ elapsedMs: Date.now() - startedAt,
281+ }),
282+ );
283+ return;
284+ }
285+ await sleep(ACTIVE_POST_RESPONSE_WAIT_POLL_MS);
286+ }
287+ }
288+
289+ private loadSessionMeta(): Effect.Effect<SessionMeta | null> {
290+ return Effect.promise(async () => {
291+ if (this.sessionMeta) return this.sessionMeta;
292+ const stored = await this.ctx.storage.get<SessionMeta>(SESSION_META_KEY);
293+ this.sessionMeta = stored ?? null;
294+ return this.sessionMeta;
295+ }).pipe(Effect.withSpan("mcp.session.load_meta"));
296+ }
297+
298+ private async saveSessionMeta(sessionMeta: SessionMeta): Promise<void> {
299+ this.sessionMeta = sessionMeta;
300+ await this.ctx.storage.put(SESSION_META_KEY, sessionMeta);
301+ }
302+
303+ private async markActivity(now = Date.now()): Promise<void> {
304+ this.lastActivityMs = now;
305+ await Promise.all([
306+ this.ctx.storage.put(LAST_ACTIVITY_KEY, now),
307+ this.ctx.storage.setAlarm(now + SESSION_TIMEOUT_MS),
308+ ]);
309+ }
310+
311+ private async loadLastActivity(): Promise<number> {
312+ if (this.lastActivityMs > 0) return this.lastActivityMs;
313+ const stored = await this.ctx.storage.get<number>(LAST_ACTIVITY_KEY);
314+ this.lastActivityMs = stored ?? 0;
315+ return this.lastActivityMs;
316+ }
317+
318+ private async hasPartyServerName(): Promise<boolean> {
319+ if (this.ctx.id.name) return true;
320+ const stored = await this.ctx.storage.get<string>(PARTYSERVER_NAME_KEY);
321+ return !!stored;
322+ }
323+
324+ private async cleanupUnaddressableSessionAlarm(): Promise<void> {
325+ await Effect.runPromise(this.closeRuntime());
326+ await Effect.runPromise(
327+ Effect.all([
328+ Effect.ignore(Effect.tryPromise(() => this.ctx.storage.deleteAlarm())),
329+ Effect.ignore(Effect.tryPromise(() => this.ctx.storage.delete(LAST_ACTIVITY_KEY))),
330+ ]),
331+ );
332+ }
333+
334+ private resolveAndStoreSessionMeta(token: McpSessionInit) {
335+ const self = this;
336+ return Effect.gen(function* () {
337+ const resolved = yield* self.resolveSessionMeta(token);
338+ const sessionMeta: SessionMeta = {
339+ ...resolved,
340+ ...(token.webOrigin ? { webOrigin: token.webOrigin } : {}),
341+ };
342+ yield* Effect.promise(() => self.saveSessionMeta(sessionMeta)).pipe(
343+ Effect.withSpan("mcp.session.save_meta"),
344+ );
345+ return sessionMeta;
346+ }).pipe(Effect.withSpan("mcp.session.resolve_and_store_meta"));
347+ }
348+
349+ private recordCauseOnSpan(cause: Cause.Cause<unknown>): Effect.Effect<void> {
350+ const errors = Cause.prettyErrors(cause);
351+ if (errors.length === 0) return Effect.void;
352+ const first = errors[0];
353+ return Effect.annotateCurrentSpan({
354+ "exception.type": first?.name ?? "Error",
355+ "exception.message": first?.message ?? "unknown",
356+ "exception.stacktrace": Cause.pretty(cause),
357+ });
358+ }
359+
360+ private withSpanFlush<A, E>(effect: Effect.Effect<A, E>): Effect.Effect<A, E> {
361+ const self = this;
362+ return effect.pipe(Effect.ensuring(Effect.promise(() => self.flushTelemetry())));
363+ }
364+
365+ private buildRuntime(sessionMeta: SessionMeta, dbHandle: TDbHandle) {
366+ const built = sessionMeta.organizationSlug
367+ ? this.buildMcpServer(sessionMeta, dbHandle).pipe(
368+ Effect.provideService(RequestOrgSlug, { slug: sessionMeta.organizationSlug }),
369+ )
370+ : this.buildMcpServer(sessionMeta, dbHandle);
371+ return sessionMeta.webOrigin
372+ ? built.pipe(Effect.provideService(RequestWebOrigin, { origin: sessionMeta.webOrigin }))
373+ : built;
374+ }
375+
376+ private closeRuntime(): Effect.Effect<void> {
377+ const self = this;
378+ return Effect.gen(function* () {
379+ yield* self.releaseAllPendingApprovalLeases();
380+ if (self.server) {
381+ const server = self.server;
382+ yield* Effect.promise(() => server.close()).pipe(Effect.ignore);
383+ }
384+ self.engine = null;
385+ if (self.dbHandle) {
386+ const dbHandle = self.dbHandle;
387+ self.dbHandle = null;
388+ yield* Effect.promise(() => Promise.resolve(dbHandle.end())).pipe(Effect.ignore);
389+ }
390+ self.initialized = false;
391+ });
392+ }
393+
394+ private ensureRuntimeForApproval(): Effect.Effect<boolean> {
395+ const self = this;
396+ return Effect.gen(function* () {
397+ if (self.initialized && self.engine) return true;
398+
399+ const sessionMeta = yield* self.loadSessionMeta();
400+ if (!sessionMeta) return false;
401+
402+ yield* self.closeRuntime();
403+ const dbHandle = yield* self.openSessionDbHandle();
404+ const { mcpServer, engine } = yield* self.buildRuntime(sessionMeta, dbHandle);
405+ self.dbHandle = dbHandle;
406+ self.server = mcpServer;
407+ self.engine = engine;
408+ self.initialized = true;
409+ yield* Effect.promise(() => self.markActivity()).pipe(
410+ Effect.withSpan("McpSessionDO.markActivity"),
411+ );
412+ return true;
413+ }).pipe(Effect.withSpan("McpSessionDO.ensure_runtime_for_approval"));
414+ }
415+
416+ async init(): Promise<void> {
417+ if (this.initialized) return;
418+ const props = isSessionProps(this.props) ? this.props : null;
419+ if (!props) {
420+ // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: McpAgent.init is a Promise-only framework hook and props are required before any Effect runtime exists.
421+ throw new Error("MCP session props are required");
422+ }
423+ const self = this;
424+ const program = Effect.gen(function* () {
425+ const sessionMeta = yield* self.resolveAndStoreSessionMeta(props.session);
426+ const dbHandle = yield* self.openSessionDbHandle();
427+ const { mcpServer, engine } = yield* self.buildRuntime(sessionMeta, dbHandle);
428+ self.dbHandle = dbHandle;
429+ self.server = mcpServer;
430+ self.engine = engine;
431+ self.initialized = true;
432+ yield* Effect.promise(() => self.markActivity()).pipe(
433+ Effect.withSpan("McpSessionDO.markActivity"),
434+ );
435+ }).pipe(
436+ Effect.tapCause((cause) =>
437+ Effect.gen(function* () {
438+ console.error("[mcp-session] init failed:", Cause.pretty(cause));
439+ self.captureCause(cause);
440+ yield* self.recordCauseOnSpan(cause);
441+ }),
442+ ),
443+ Effect.catchCause((cause) =>
444+ Effect.gen(function* () {
445+ yield* Effect.promise(() => self.cleanup());
446+ return yield* Effect.failCause(cause);
447+ }),
448+ ),
449+ Effect.withSpan("McpSessionDO.init", {
450+ attributes: {
451+ "mcp.auth.organization_id": props?.session.organizationId ?? "",
452+ },
453+ }),
454+ );
455+ const traced = this.withTelemetry(program, props?.propagation);
456+ return Effect.runPromise(
457+ traced.pipe(
458+ // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: Durable Object init method can only reject its Promise
459+ Effect.orDie,
460+ (effect) => self.withSpanFlush(effect),
461+ ),
462+ );
463+ }
464+
465+ async validateMcpSessionOwner(
466+ identity: McpApprovalOwner,
467+ ): Promise<"ok" | "not_found" | "forbidden"> {
468+ const self = this;
469+ return Effect.runPromise(
470+ Effect.gen(function* () {
471+ const sessionMeta = yield* self.loadSessionMeta();
472+ if (!sessionMeta) return "not_found" as const;
473+ yield* Effect.promise(() => self.markActivity()).pipe(
474+ Effect.withSpan("McpSessionDO.markActivity"),
475+ );
476+ return identity.accountId === sessionMeta.userId &&
477+ identity.organizationId === sessionMeta.organizationId
478+ ? ("ok" as const)
479+ : ("forbidden" as const);
480+ }).pipe(
481+ Effect.withSpan("McpSessionDO.validateMcpSessionOwner"),
482+ // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: DO RPC exposes Promise results
483+ Effect.orDie,
484+ ),
485+ );
486+ }
487+
488+ async getPausedExecutionForApproval(
489+ executionId: string,
490+ identity: McpApprovalOwner,
491+ incoming?: IncomingTraceHeaders,
492+ ): Promise<McpSessionApprovalResult> {
493+ const self = this;
494+ return Effect.runPromise(
495+ Effect.gen(function* () {
496+ const owner = yield* self.validateApprovalIdentity(identity);
497+ if (owner !== "ok") return { status: owner } as const;
498+
499+ const restored = yield* self.ensureRuntimeForApproval();
500+ if (!restored || !self.engine) return { status: "not_found" } as const;
501+
502+ const paused = yield* self.engine.getPausedExecution(executionId);
503+ if (!paused) return { status: "not_found" } as const;
504+
505+ const formatted = formatPausedExecution(paused);
506+ return {
507+ status: "ok" as const,
508+ text: formatted.text,
509+ structured: formatted.structured,
510+ };
511+ }).pipe(
512+ Effect.withSpan("McpSessionDO.getPausedExecutionForApproval", {
513+ attributes: { "mcp.execution.id": executionId },
514+ }),
515+ (eff) => this.withTelemetry(eff, incoming),
516+ // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: DO RPC exposes Promise results
517+ Effect.orDie,
518+ (eff) => self.withSpanFlush(eff),
519+ ),
520+ );
521+ }
522+
523+ async resumeExecutionForApproval(
524+ executionId: string,
525+ identity: McpApprovalOwner,
526+ response: ResumeResponse,
527+ incoming?: IncomingTraceHeaders,
528+ ): Promise<McpSessionResumeApprovalResult> {
529+ const self = this;
530+ return Effect.runPromise(
531+ Effect.gen(function* () {
532+ const owner = yield* self.validateApprovalIdentity(identity);
533+ if (owner !== "ok") return { status: owner } as const;
534+
535+ const restored = yield* self.ensureRuntimeForApproval();
536+ if (!restored || !self.engine) return { status: "not_found" } as const;
537+
538+ const paused = yield* self.engine.getPausedExecution(executionId);
539+ if (!paused) return { status: "not_found" } as const;
540+
541+ yield* self.recordApprovalResponse(executionId, response);
542+ return resumeApprovalResult(executionId, response);
543+ }).pipe(
544+ Effect.withSpan("McpSessionDO.resumeExecutionForApproval", {
545+ attributes: { "mcp.execution.id": executionId },
546+ }),
547+ (eff) => this.withTelemetry(eff, incoming),
548+ // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: DO RPC exposes Promise results
549+ Effect.orDie,
550+ (eff) => self.withSpanFlush(eff),
551+ ),
552+ );
553+ }
554+
555+ override async destroy(): Promise<void> {
556+ await this.cleanup();
557+ await super.destroy();
558+ }
559+
560+ private async pausedExecutionCount(): Promise<number> {
561+ if (!this.engine) return 0;
562+ return Effect.runPromise(this.engine.pausedExecutionCount());
563+ }
564+
565+ override async alarm(): Promise<void> {
566+ if (!(await this.hasPartyServerName())) {
567+ await this.cleanupUnaddressableSessionAlarm();
568+ return;
569+ }
570+ const lastActivityMs = await this.loadLastActivity();
571+ const idleMs = lastActivityMs > 0 ? Date.now() - lastActivityMs : 0;
572+ const pausedExecutionCount = await this.pausedExecutionCount();
573+ const decision = decideSessionAlarm({ idleMs, pausedExecutionCount });
574+
575+ if (decision.kind === "idle_within_timeout") {
576+ await super.alarm();
577+ return;
578+ }
579+
580+ if (decision.kind === "extend_paused_lease") {
581+ console.info(
582+ JSON.stringify(
583+ pausedLeaseExtensionLog({
584+ sessionId: this.sessionId,
585+ pausedExecutionCount,
586+ idleMs,
587+ leaseMs: decision.leaseMs,
588+ }),
589+ ),
590+ );
591+ await this.ctx.storage.setAlarm(Date.now() + decision.leaseMs);
592+ return;
593+ }
594+
595+ await this.destroy();
596+ }
597+
598+ private validateApprovalIdentity(
599+ identity: McpApprovalOwner,
600+ ): Effect.Effect<"ok" | "not_found" | "forbidden"> {
601+ const self = this;
602+ return Effect.gen(function* () {
603+ const sessionMeta = yield* self.loadSessionMeta();
604+ if (!sessionMeta) return "not_found" as const;
605+ return identity.accountId === sessionMeta.userId &&
606+ identity.organizationId === sessionMeta.organizationId
607+ ? ("ok" as const)
608+ : ("forbidden" as const);
609+ }).pipe(Effect.withSpan("mcp.session.validate_approval_identity"));
610+ }
611+
612+ private startPendingApprovalLease(executionId: string): Effect.Effect<void> {
613+ const self = this;
614+ return Effect.gen(function* () {
615+ if (self.pendingApprovalLeases.has(executionId)) return;
616+
617+ yield* Effect.promise(() => self.markActivity()).pipe(
618+ Effect.withSpan("McpSessionDO.markActivity"),
619+ );
620+ const disposeKeepAlive = yield* Effect.promise(() => self.keepAlive());
621+ const timeout = setTimeout(() => {
622+ self.queuePendingApprovalLeaseExpiration(executionId);
623+ }, PAUSED_APPROVAL_TIMEOUT_MS);
624+ self.pendingApprovalLeases.set(executionId, { disposeKeepAlive, timeout, expiring: false });
625+ }).pipe(
626+ Effect.withSpan("McpSessionDO.pending_approval_lease.start", {
627+ attributes: { "mcp.execution.id": executionId },
628+ }),
629+ Effect.tapCause((cause) =>
630+ Effect.sync(() => {
631+ console.error("[mcp-session] pending approval lease start failed:", Cause.pretty(cause));
632+ self.captureCause(cause);
633+ }),
634+ ),
635+ Effect.ignore,
636+ );
637+ }
638+
639+ private queuePendingApprovalLeaseStart(executionId: string): void {
640+ this.ctx.waitUntil(Effect.runPromise(this.startPendingApprovalLease(executionId)));
641+ }
642+
643+ private queuePendingApprovalLeaseExpiration(executionId: string): void {
644+ this.ctx.waitUntil(
645+ Effect.runPromise(
646+ this.expirePendingApproval(executionId).pipe(
647+ Effect.tapCause((cause) =>
648+ Effect.sync(() => {
649+ console.error(
650+ "[mcp-session] pending approval lease expiration failed:",
651+ Cause.pretty(cause),
652+ );
653+ this.captureCause(cause);
654+ }),
655+ ),
656+ Effect.ignore,
657+ ),
658+ ),
659+ );
660+ }
661+
662+ private beginPendingApprovalResume(executionId: string): Effect.Effect<void> {
663+ return Effect.sync(() => {
664+ const lease = this.pendingApprovalLeases.get(executionId);
665+ if (!lease || lease.expiring) return;
666+ if (lease.timeout) clearTimeout(lease.timeout);
667+ lease.timeout = null;
668+ }).pipe(
669+ Effect.withSpan("McpSessionDO.pending_approval_lease.begin_resume", {
670+ attributes: { "mcp.execution.id": executionId },
671+ }),
672+ );
673+ }
674+
675+ private finishPendingApprovalResume(executionId: string): Effect.Effect<void> {
676+ return this.releasePendingApprovalLease(executionId).pipe(
677+ Effect.withSpan("McpSessionDO.pending_approval_lease.finish", {
678+ attributes: { "mcp.execution.id": executionId },
679+ }),
680+ );
681+ }
682+
683+ private releasePendingApprovalLease(executionId: string): Effect.Effect<void> {
684+ return Effect.sync(() => {
685+ const lease = this.pendingApprovalLeases.get(executionId);
686+ if (!lease) return;
687+ if (lease.timeout) clearTimeout(lease.timeout);
688+ this.pendingApprovalLeases.delete(executionId);
689+ lease.disposeKeepAlive();
690+ });
691+ }
692+
693+ private releaseAllPendingApprovalLeases(): Effect.Effect<void> {
694+ return Effect.sync(() => {
695+ for (const executionId of this.pendingApprovalLeases.keys()) {
696+ const lease = this.pendingApprovalLeases.get(executionId);
697+ if (!lease) continue;
698+ if (lease.timeout) clearTimeout(lease.timeout);
699+ lease.disposeKeepAlive();
700+ }
701+ this.pendingApprovalLeases.clear();
702+ });
703+ }
704+
705+ private recordApprovalResponse(
706+ executionId: string,
707+ response: ResumeResponse,
708+ ): Effect.Effect<void> {
709+ const self = this;
710+ return Effect.gen(function* () {
711+ self.approvalResponses.set(executionId, response);
712+ yield* Effect.promise(() => self.ctx.storage.put(approvalResponseKey(executionId), response));
713+ const waiter = self.approvalWaiters.get(executionId);
714+ if (waiter) yield* Deferred.succeed(waiter, response);
715+ });
716+ }
717+
718+ private expirePendingApproval(executionId: string): Effect.Effect<void> {
719+ const self = this;
720+ return Effect.gen(function* () {
721+ const lease = self.pendingApprovalLeases.get(executionId);
722+ if (!lease || lease.expiring) return;
723+ lease.expiring = true;
724+ if (lease.timeout) clearTimeout(lease.timeout);
725+ lease.timeout = null;
726+ if (self.approvalResponses.has(executionId)) return;
727+
728+ const response = {
729+ action: "decline",
730+ content: { reason: "approval_timeout" },
731+ } satisfies ResumeResponse;
732+ yield* Effect.sync(() => {
733+ console.info(JSON.stringify({ event: "mcp_pending_approval_lease_expire", executionId }));
734+ });
735+ yield* self.recordApprovalResponse(executionId, response);
736+ if (self.engine && !self.approvalWaiters.has(executionId)) {
737+ yield* self.engine.resume(executionId, response).pipe(Effect.ignore);
738+ }
739+ }).pipe(
740+ Effect.ensuring(self.releasePendingApprovalLease(executionId)),
741+ Effect.withSpan("McpSessionDO.pending_approval_lease.expire", {
742+ attributes: { "mcp.execution.id": executionId },
743+ }),
744+ );
745+ }
746+
747+ private takeApprovalResponse(executionId: string): Effect.Effect<ResumeResponse | null> {
748+ const self = this;
749+ return Effect.promise(async () => {
750+ const memoryResponse = self.approvalResponses.get(executionId);
751+ if (memoryResponse) {
752+ self.approvalResponses.delete(executionId);
753+ await self.ctx.storage.delete(approvalResponseKey(executionId));
754+ return memoryResponse;
755+ }
756+ const stored = await self.ctx.storage.get<ResumeResponse>(approvalResponseKey(executionId));
757+ if (!stored) return null;
758+ await self.ctx.storage.delete(approvalResponseKey(executionId));
759+ return stored;
760+ });
761+ }
762+
763+ private waitForApprovalResponse(executionId: string): Effect.Effect<ResumeResponse | null> {
764+ const self = this;
765+ return Effect.gen(function* () {
766+ const existing = yield* self.takeApprovalResponse(executionId);
767+ if (existing) return existing;
768+
769+ const waiter =
770+ self.approvalWaiters.get(executionId) ?? (yield* Deferred.make<ResumeResponse>());
771+ self.approvalWaiters.set(executionId, waiter);
772+ yield* Deferred.await(waiter).pipe(
773+ Effect.ensuring(
774+ Effect.sync(() => {
775+ if (self.approvalWaiters.get(executionId) === waiter) {
776+ self.approvalWaiters.delete(executionId);
777+ }
778+ }),
779+ ),
780+ );
781+ return yield* self.takeApprovalResponse(executionId);
782+ });
783+ }
784+
785+ private async cleanup(): Promise<void> {
786+ await Effect.runPromise(this.closeRuntime());
787+ }
788+}
packages/hosts/cloudflare/src/mcp/request-id-queue.ts+0−125
@@ -1,125 +0,0 @@
1−// The same-id JSON-RPC request queue used by the MCP worker transport.
2−// Pure logic with no Cloudflare/agents dependency, in its own module so it
3−// is unit-testable outside workerd.
4−import { Effect, Exit, Match, Option, Predicate } from "effect";
5−
6−type JsonRpcLike = {
7− readonly id?: unknown;
8− readonly method?: unknown;
9− readonly params?: unknown;
10−};
11−
12−type ToolCallParamsLike = {
13− readonly name?: unknown;
14− readonly arguments?: unknown;
15−};
16−
17−type ResumeArgumentsLike = {
18− readonly executionId?: unknown;
19−};
20−
21−const jsonRpcRequestIdKey = (id: unknown): string | null =>
22− Match.value(id).pipe(
23− Match.whenOr(
24− Match.string,
25− Match.number,
26− Match.boolean,
27− (value) => `${typeof value}:${String(value)}`,
28− ),
29− Match.option,
30− Option.getOrNull,
31− );
32−
33−const jsonRpcRequestQueueKey = (message: JsonRpcLike): string | null => {
34− const idKey = jsonRpcRequestIdKey(message.id);
35− if (!idKey) return null;
36−
37− if (message.method !== "tools/call") return idKey;
38− if (!message.params || typeof message.params !== "object") return idKey;
39−
40− const params = message.params as ToolCallParamsLike;
41− if (params.name !== "resume") return idKey;
42− if (!params.arguments || typeof params.arguments !== "object") return idKey;
43−
44− const args = params.arguments as ResumeArgumentsLike;
45− if (typeof args.executionId !== "string" || args.executionId.length === 0) return idKey;
46−
47− return `${idKey}:tools/call:resume:${args.executionId}`;
48−};
49−
50−const extractJsonRpcRequestQueueKeys = async (request: Request): Promise<ReadonlyArray<string>> => {
51− if (request.method !== "POST") return [];
52− const contentType = request.headers.get("content-type") ?? "";
53− if (!contentType.includes("application/json")) return [];
54−
55− const parsed = await Effect.runPromiseExit(Effect.tryPromise(() => request.clone().json()));
56− if (Exit.isFailure(parsed)) {
57− return [];
58− }
59− const messages = Array.isArray(parsed.value) ? parsed.value : [parsed.value];
60− return messages.flatMap((message) => {
61− if (!message || typeof message !== "object") return [];
62− const rpc = message as JsonRpcLike;
63− if (typeof rpc.method !== "string") return [];
64− const key = jsonRpcRequestQueueKey(rpc);
65− return key ? [key] : [];
66− });
67−};
68−
69−// Hard ceiling on how long a same-id JSON-RPC request will wait for an
70−// earlier in-flight one to finish. Stays well under the 180s upstream
71−// client timeout that Claude / Cowork enforce, so a poisoned queue slot
72−// can't block the next request long enough for the client to give up.
73−// If a previous request hasn't released within the budget, we proceed
74−// anyway — at worst the MCP SDK rejects the second reply for a duplicate
75−// id, which is recoverable; a perma-stuck queue is not.
76−export const PREVIOUS_REQUEST_TIMEOUT_MS = 60_000;
77−
78−export class JsonRpcRequestIdQueue {
79− private readonly inFlight = new Map<string, Promise<void>>();
80− private readonly previousTimeoutMs: number;
81−
82− constructor(options: { readonly previousTimeoutMs?: number } = {}) {
83− this.previousTimeoutMs = options.previousTimeoutMs ?? PREVIOUS_REQUEST_TIMEOUT_MS;
84− }
85−
86− async run<A>(request: Request, run: () => Promise<A>): Promise<A> {
87− const ids = [...new Set(await extractJsonRpcRequestQueueKeys(request))];
88− if (ids.length === 0) return await run();
89−
90− const previous = ids.map((id) => this.inFlight.get(id)).filter(Predicate.isNotUndefined);
91− let release!: () => void;
92− const current = new Promise<void>((resolve) => {
93− release = resolve;
94− });
95− for (const id of ids) {
96− this.inFlight.set(id, current);
97− }
98−
99− // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: promise queue must release in-flight ids after callback completion
100− try {
101− if (previous.length > 0) {
102− const settled = Promise.all(
103− previous.map((p) => Effect.runPromise(Effect.ignore(Effect.tryPromise(() => p)))),
104− );
105− const timeout = new Promise<"timeout">((resolve) =>
106− setTimeout(() => resolve("timeout"), this.previousTimeoutMs),
107− );
108− const outcome = await Promise.race([settled.then(() => "settled" as const), timeout]);
109− if (outcome === "timeout") {
110− console.warn(
111− `[mcp-worker-transport] previous in-flight request for ids=${JSON.stringify(ids)} did not release within ${this.previousTimeoutMs}ms; proceeding anyway`,
112− );
113− }
114− }
115− return await run();
116− } finally {
117− for (const id of ids) {
118− if (this.inFlight.get(id) === current) {
119− this.inFlight.delete(id);
120− }
121− }
122− release();
123− }
124− }
125−}
packages/hosts/cloudflare/src/mcp/response-peek.ts+0−263
@@ -1,263 +0,0 @@
1−import { Cause, Data, Effect, Exit, Option, Schema } from "effect";
2−
3−import { jsonRpcErrorBody } from "@executor-js/host-mcp";
4−
5−const DEFAULT_SSE_PEEK_TIMEOUT_MS = 10_000;
6−
7−/** Observe a JSON-RPC internal error (-32603) seen on a peeked response. The
8− * host injects this (cloud: Sentry capture; host-cloudflare: console / omit). */
9−export type OnInternalJsonRpcError = (message: string) => void;
10−
11−class ResponseBodyTimeoutError extends Data.TaggedError("ResponseBodyTimeoutError")<{
12− readonly timeoutMs: number;
13−}> {}
14−
15−class ResponseBodyReadError extends Data.TaggedError("ResponseBodyReadError") {}
16−
17−const ResponseBodyTimeoutErrorData = Schema.Struct({
18− _tag: Schema.Literal("ResponseBodyTimeoutError"),
19− timeoutMs: Schema.Number,
20−});
21−const decodeResponseBodyTimeoutError = Schema.decodeUnknownOption(ResponseBodyTimeoutErrorData);
22−
23−const SandboxOutcomeSchema = Schema.Struct({
24− status: Schema.optional(Schema.String),
25− error: Schema.optional(
26− Schema.Struct({
27− kind: Schema.optional(Schema.String),
28− message: Schema.optional(Schema.String),
29− }),
30− ),
31−});
32−
33−const JsonRpcResponseBodySchema = Schema.Struct({
34− jsonrpc: Schema.optional(Schema.String),
35− error: Schema.optional(
36− Schema.Struct({
37− code: Schema.optional(Schema.Number),
38− message: Schema.optional(Schema.String),
39− }),
40− ),
41− result: Schema.optional(
42− Schema.Struct({
43− isError: Schema.optional(Schema.Boolean),
44− structuredContent: Schema.optional(SandboxOutcomeSchema),
45− }),
46− ),
47−});
48−
49−const decodeJsonRpcResponseBody = Schema.decodeUnknownOption(
50− Schema.fromJsonString(JsonRpcResponseBodySchema),
51−);
52−
53−type JsonRpcResponseBody = typeof JsonRpcResponseBodySchema.Type;
54−
55−const responseBodyShape = (body: string): string => {
56− const trimmed = body.trimStart();
57− if (!trimmed) return "empty";
58− if (trimmed.startsWith("{")) return "json-object";
59− if (trimmed.startsWith("[")) return "json-array";
60− if (trimmed.startsWith("event:") || trimmed.startsWith("data:")) return "sse";
61− if (trimmed.startsWith("<")) return "html-or-xml";
62− return "other";
63−};
64−
65−const parseFirstJsonRpc = (contentType: string, body: string): JsonRpcResponseBody | null => {
66− if (!body) return null;
67− if (contentType.includes("text/event-stream")) {
68− for (const line of body.split(/\r?\n/)) {
69− if (line.startsWith("data:")) {
70− return Option.getOrNull(decodeJsonRpcResponseBody(line.slice(5).trimStart()));
71− }
72− }
73− return null;
74− }
75− if (contentType.includes("application/json")) {
76− return Option.getOrNull(decodeJsonRpcResponseBody(body));
77− }
78− return null;
79−};
80−
81−const jsonRpcResponseAttrs = (payload: JsonRpcResponseBody | null): Record<string, unknown> => {
82− if (!payload || payload.jsonrpc !== "2.0") return {};
83− const attrs: Record<string, unknown> = {};
84− const err = payload.error;
85− if (err && typeof err === "object") {
86− attrs["mcp.rpc.is_error"] = true;
87− if (typeof err.code === "number") attrs["mcp.rpc.error.code"] = err.code;
88− // oxlint-disable-next-line executor/no-unknown-error-message -- boundary: schema-decoded JSON-RPC error message is protocol telemetry
89− const { message } = err;
90− if (typeof message === "string") {
91− attrs["mcp.rpc.error.message"] = message.slice(0, 500);
92− }
93− }
94− if (payload.result?.isError === true) attrs["mcp.tool.result.is_error"] = true;
95− const structured = payload.result?.structuredContent;
96− if (structured && typeof structured.status === "string") {
97− attrs["mcp.tool.sandbox.status"] = structured.status;
98− if (structured.error?.kind) attrs["mcp.tool.sandbox.error.kind"] = structured.error.kind;
99− const message = structured.error?.["message"];
100− if (typeof message === "string") {
101− attrs["mcp.tool.sandbox.error.message"] = message.slice(0, 500);
102− }
103− }
104− return attrs;
105−};
106−
107−const readResponseText = async (response: Response, timeoutMs: number | null): Promise<string> => {
108− if (timeoutMs === null) return await response.text();
109−
110− const reader = response.body?.getReader();
111− if (!reader) return "";
112−
113− const decoder = new TextDecoder();
114− let timeout: ReturnType<typeof setTimeout> | undefined;
115− const timeoutPromise = new Promise<never>((_, reject) => {
116− timeout = setTimeout(() => {
117− // oxlint-disable-next-line executor/no-promise-catch -- boundary: best-effort stream cancellation inside timeout callback
118− void reader.cancel().catch(() => undefined);
119− // oxlint-disable-next-line executor/no-promise-reject -- boundary: Promise.race timeout adapter for Web ReadableStream
120− reject(new ResponseBodyTimeoutError({ timeoutMs }));
121− }, timeoutMs);
122− });
123− const readPromise = (async () => {
124− // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: Web stream reader cleanup must clear host timeout after success or failure
125− try {
126− let text = "";
127− for (;;) {
128− const { done, value } = await reader.read();
129− if (done) return text + decoder.decode();
130− text += decoder.decode(value, { stream: true });
131− }
132− } finally {
133− if (timeout) clearTimeout(timeout);
134− }
135− })();
136−
137− return await Promise.race([readPromise, timeoutPromise]);
138−};
139−
140−const annotateEmptyResponse = (response: Response, contentType: string) =>
141− Effect.annotateCurrentSpan({
142− "mcp.response.status_code": response.status,
143− "mcp.response.content_type": contentType,
144− "mcp.response.body.shape": "empty",
145− "mcp.response.body.length": 0,
146− "mcp.response.jsonrpc.detected": false,
147− });
148−
149−const withoutBodyHeaders = (response: Response) => {
150− const headers = new Headers(response.headers);
151− headers.delete("content-type");
152− headers.delete("content-length");
153− return new Response(null, {
154− status: response.status,
155− statusText: response.statusText,
156− headers,
157− });
158−};
159−
160−const isResponseBodyTimeoutError = (error: unknown) =>
161− Option.isSome(decodeResponseBodyTimeoutError(error));
162−
163−const responsePeekError = (error: unknown): ResponseBodyTimeoutError | ResponseBodyReadError =>
164− Option.match(decodeResponseBodyTimeoutError(error), {
165− onNone: () => new ResponseBodyReadError(),
166− onSome: ({ timeoutMs }) => new ResponseBodyTimeoutError({ timeoutMs }),
167− });
168−
169−const responseReadFailure = (error: unknown) =>
170− Effect.gen(function* () {
171− const timedOut = isResponseBodyTimeoutError(error);
172− yield* Effect.annotateCurrentSpan({
173− "mcp.response.status_code": timedOut ? 504 : 500,
174− "mcp.response.content_type": "application/json",
175− "mcp.response.body.shape": "json-object",
176− "mcp.response.body.length": 0,
177− "mcp.response.jsonrpc.detected": true,
178− "mcp.peek_response.timed_out": timedOut,
179− "mcp.peek_response.error": timedOut ? "ResponseBodyTimeoutError" : "ResponseBodyReadError",
180− });
181− return jsonRpcErrorBody(
182− timedOut ? 504 : 500,
183− -32001,
184− timedOut
185− ? "Timed out waiting for MCP response - please retry"
186− : "Failed to read MCP response",
187− );
188− });
189−
190−const reportInternalJsonRpcError = (
191− payload: JsonRpcResponseBody | null,
192− onInternalError: OnInternalJsonRpcError | undefined,
193−) =>
194− Effect.sync(() => {
195− if (payload?.error?.code !== -32603) return;
196− onInternalError?.(payload.error["message"] ?? "unknown");
197− });
198−
199−export interface PeekAndAnnotateOptions {
200− /** Observe a JSON-RPC -32603 internal error (cloud injects Sentry capture). */
201− readonly onInternalError?: OnInternalJsonRpcError;
202− /** SSE body read timeout (defaults to 10s). */
203− readonly sseTimeoutMs?: number;
204−}
205−
206−export const peekAndAnnotate = (
207− response: Response,
208− options: PeekAndAnnotateOptions = {},
209−): Effect.Effect<Response> =>
210− Effect.gen(function* () {
211− const contentType = response.headers.get("content-type") ?? "";
212− if (response.status === 202) {
213− yield* annotateEmptyResponse(response, contentType);
214− return withoutBodyHeaders(response);
215− }
216− if (!response.body) {
217− yield* annotateEmptyResponse(response, contentType);
218− return response;
219− }
220−
221− const isSseResponse = contentType.includes("text/event-stream");
222− const timeoutMs = isSseResponse ? (options.sseTimeoutMs ?? DEFAULT_SSE_PEEK_TIMEOUT_MS) : null;
223− const textExit = yield* Effect.exit(
224− Effect.tryPromise({
225− try: () => readResponseText(response, timeoutMs),
226− catch: responsePeekError,
227− }).pipe(
228− Effect.withSpan("mcp.peek_response", {
229− attributes: {
230− "http.response.content_type": contentType,
231− "http.response.status_code": response.status,
232− "mcp.peek_response.timeout_ms": timeoutMs ?? 0,
233− },
234− }),
235− ),
236− );
237− if (Exit.isFailure(textExit)) {
238− const error = Option.getOrElse(
239− Cause.findErrorOption(textExit.cause),
240− () => new ResponseBodyReadError(),
241− );
242− return yield* responseReadFailure(error);
243− }
244−
245− const text = textExit.value;
246− const payload = parseFirstJsonRpc(contentType, text);
247− yield* Effect.annotateCurrentSpan({
248− "mcp.response.status_code": response.status,
249− "mcp.response.content_type": contentType,
250− "mcp.response.body.length": text.length,
251− "mcp.response.body.shape": responseBodyShape(text),
252− "mcp.response.jsonrpc.detected": payload?.jsonrpc === "2.0",
253− });
254− const attrs = jsonRpcResponseAttrs(payload);
255− if (Object.keys(attrs).length > 0) yield* Effect.annotateCurrentSpan(attrs);
256− yield* reportInternalJsonRpcError(payload, options.onInternalError);
257−
258− return new Response(text, {
259− status: response.status,
260− statusText: response.statusText,
261− headers: response.headers,
262− });
263− });
packages/hosts/cloudflare/src/mcp/seams.ts+0−31
@@ -1,31 +0,0 @@
1−import type { McpResource } from "@executor-js/host-mcp";
2−
3−import type { IncomingPropagationHeaders, McpElicitationMode } from "./do-headers";
4−
5−// ---------------------------------------------------------------------------
6−// The injection seams shared between the worker-side DO dispatcher and the
7−// DO-side base class. A host (cloud / host-cloudflare) supplies its own DO
8−// namespace + runtime builder; everything else is platform-generic.
9−// ---------------------------------------------------------------------------
10−
11−/** What the worker tells the session DO at creation (owner + elicitation mode). */
12−export interface McpSessionInit {
13− readonly organizationId: string;
14− readonly userId: string;
15− readonly resource: McpResource;
16− readonly elicitationMode: McpElicitationMode;
17− /** Public origin of the create request (`https://host`), so the DO derives a
18− * web base URL zero-config when the host configures no static one. */
19− readonly webOrigin?: string;
20−}
21−
22−/**
23− * The RPC surface the worker calls on a session-DO stub. The DO base class
24− * (McpSessionDOBase) implements these; a host's concrete DO subclass inherits
25− * them. The worker dispatcher only depends on this interface, not the class.
26− */
27−export interface McpSessionDOStub {
28− init(meta: McpSessionInit, propagation: IncomingPropagationHeaders): Promise<void>;
29− handleRequest(request: Request): Promise<Response>;
30− clearSession(propagation: IncomingPropagationHeaders): Promise<void>;
31−}
packages/hosts/cloudflare/src/mcp/session-alarm-policy.test.ts+58−0
@@ -0,0 +1,58 @@
1+import { describe, expect, it } from "@effect/vitest";
2+
3+import {
4+ MAX_PAUSED_SESSION_IDLE_MS,
5+ PAUSED_EXECUTION_LEASE_MS,
6+ SESSION_TIMEOUT_MS,
7+ decideSessionAlarm,
8+} from "./session-alarm-policy";
9+
10+describe("decideSessionAlarm", () => {
11+ it("keeps the session within the idle timeout", () => {
12+ expect(
13+ decideSessionAlarm({
14+ idleMs: SESSION_TIMEOUT_MS - 1,
15+ pausedExecutionCount: 0,
16+ }),
17+ ).toEqual({ kind: "idle_within_timeout" });
18+ });
19+
20+ it("stays within the idle timeout regardless of paused work", () => {
21+ expect(
22+ decideSessionAlarm({
23+ idleMs: SESSION_TIMEOUT_MS - 1,
24+ pausedExecutionCount: 3,
25+ }),
26+ ).toEqual({ kind: "idle_within_timeout" });
27+ });
28+
29+ it("destroys an idle session with no paused work", () => {
30+ expect(
31+ decideSessionAlarm({
32+ idleMs: SESSION_TIMEOUT_MS,
33+ pausedExecutionCount: 0,
34+ }),
35+ ).toEqual({ kind: "destroy_idle_session" });
36+ });
37+
38+ it("extends the lease when paused continuations exist", () => {
39+ expect(
40+ decideSessionAlarm({
41+ idleMs: SESSION_TIMEOUT_MS,
42+ pausedExecutionCount: 1,
43+ }),
44+ ).toEqual({
45+ kind: "extend_paused_lease",
46+ leaseMs: PAUSED_EXECUTION_LEASE_MS,
47+ });
48+ });
49+
50+ it("destroys a paused session once the lease cap elapses", () => {
51+ expect(
52+ decideSessionAlarm({
53+ idleMs: MAX_PAUSED_SESSION_IDLE_MS,
54+ pausedExecutionCount: 1,
55+ }),
56+ ).toEqual({ kind: "destroy_idle_session" });
57+ });
58+});
packages/hosts/cloudflare/src/mcp/session-alarm-policy.ts+47−0
@@ -0,0 +1,47 @@
1+import { PAUSED_APPROVAL_TIMEOUT_MS } from "@executor-js/host-mcp/tool-server";
2+
3+/** Idle timeout for MCP sessions with no paused continuations. */
4+export const SESSION_TIMEOUT_MS = 5 * 60 * 1000;
5+
6+/** Lease extension while paused executions block hibernation (matches browser approval wait). */
7+export const PAUSED_EXECUTION_LEASE_MS = PAUSED_APPROVAL_TIMEOUT_MS;
8+
9+/**
10+ * Hard upper bound on idle time before a paused session is torn down regardless
11+ * of outstanding paused work. The lease grants a single approval window of grace
12+ * past the idle timeout — once it elapses the browser approval wait has already
13+ * timed out (see tool-server `waitForBrowserApprovalResponse`), so the paused
14+ * execution is no longer resumable and the DO must not keep extending forever.
15+ */
16+export const MAX_PAUSED_SESSION_IDLE_MS = SESSION_TIMEOUT_MS + PAUSED_EXECUTION_LEASE_MS;
17+
18+export type SessionAlarmDecision =
19+ | { readonly kind: "idle_within_timeout" }
20+ | { readonly kind: "destroy_idle_session" }
21+ | { readonly kind: "extend_paused_lease"; readonly leaseMs: number };
22+
23+export const decideSessionAlarm = (input: {
24+ readonly idleMs: number;
25+ readonly pausedExecutionCount: number;
26+}): SessionAlarmDecision => {
27+ if (input.idleMs < SESSION_TIMEOUT_MS) {
28+ return { kind: "idle_within_timeout" };
29+ }
30+ if (input.pausedExecutionCount > 0 && input.idleMs < MAX_PAUSED_SESSION_IDLE_MS) {
31+ return { kind: "extend_paused_lease", leaseMs: PAUSED_EXECUTION_LEASE_MS };
32+ }
33+ return { kind: "destroy_idle_session" };
34+};
35+
36+export const pausedLeaseExtensionLog = (input: {
37+ readonly sessionId: string;
38+ readonly pausedExecutionCount: number;
39+ readonly idleMs: number;
40+ readonly leaseMs: number;
41+}): Record<string, unknown> => ({
42+ event: "mcp_session_paused_lease_extension",
43+ sessionId: input.sessionId,
44+ pausedExecutionCount: input.pausedExecutionCount,
45+ idleMs: input.idleMs,
46+ leaseMs: input.leaseMs,
47+});
packages/hosts/cloudflare/src/mcp/session-durable-object.ts+0−924
@@ -1,924 +0,0 @@
1−// ---------------------------------------------------------------------------
2−// Shared MCP Session Durable Object base — holds the MCP server + engine for ONE
3−// session in a single addressable isolate (the DO id IS the mcp-session-id), so
4−// every follow-up request routes back to the same isolate. Owns ALL the
5−// platform-generic lifecycle (cold-restore from ctx.storage, the inactivity
6−// alarm, owner validation, the JSON-response-mode transport upgrade, the
7−// per-request→per-session span bridge, the browser-approval store). A host
8−// supplies only the seams: openSessionDb / resolveSessionMeta / buildMcpServer,
9−// and optionally withTelemetry / captureCause. cloud and host-cloudflare each
10−// become a ~100-line subclass binding their injected dependencies.
11−// ---------------------------------------------------------------------------
12−
13−import { DurableObject } from "cloudflare:workers";
14−import { Cause, Deferred, Effect } from "effect";
15−import type * as Tracer from "effect/Tracer";
16−import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
17−import type { TransportState } from "agents/mcp";
18−
19−import { jsonRpcErrorBody } from "@executor-js/host-mcp";
20−import { formatResumeAcknowledgement } from "@executor-js/host-mcp/browser-approval";
21−import { RequestOrgSlug, RequestWebOrigin } from "@executor-js/api/server";
22−import {
23− formatPausedExecution,
24− type ExecutionEngine,
25− type ResumeResponse,
26−} from "@executor-js/execution";
27−
28−import { makeMcpWorkerTransport, type McpWorkerTransport } from "./worker-transport";
29−import {
30− INTERNAL_ACCOUNT_ID_HEADER,
31− INTERNAL_ORGANIZATION_ID_HEADER,
32− INTERNAL_RESOURCE_KEY_HEADER,
33− type IncomingPropagationHeaders,
34−} from "./do-headers";
35−import { mcpResourceKey } from "@executor-js/host-mcp";
36−import type { McpSessionInit } from "./seams";
37−import type { McpResource } from "@executor-js/host-mcp";
38−
39−// ---------------------------------------------------------------------------
40−// Types
41−// ---------------------------------------------------------------------------
42−
43−export type { McpSessionInit } from "./seams";
44−
45−/** The W3C trace headers the worker forwards to the DO (same shape as the
46− * dispatcher's propagation headers). */
47−export type IncomingTraceHeaders = IncomingPropagationHeaders;
48−
49−export type McpApprovalOwner = {
50− readonly accountId: string;
51− readonly organizationId: string;
52−};
53−
54−type McpSessionApprovalErrorResult =
55− | { readonly status: "not_found" }
56− | { readonly status: "forbidden" };
57−
58−export type McpSessionApprovalResult =
59− | {
60− readonly status: "ok";
61− readonly text: string;
62− readonly structured: Record<string, unknown>;
63− }
64− | McpSessionApprovalErrorResult;
65−
66−export type McpSessionResumeApprovalResult =
67− | {
68− readonly status: "ok";
69− readonly executionStatus: "completed" | "paused";
70− readonly text: string;
71− readonly structured: Record<string, unknown>;
72− readonly isError?: boolean;
73− }
74− | McpSessionApprovalErrorResult;
75−
76−const resumeApprovalResult = (
77− executionId: string,
78− response: ResumeResponse,
79−): Extract<McpSessionResumeApprovalResult, { readonly status: "ok" }> => ({
80− status: "ok",
81− executionStatus: "completed",
82− ...formatResumeAcknowledgement(executionId, response),
83− isError: false,
84−});
85−
86−const HEARTBEAT_MS = 30 * 1000;
87−const SESSION_TIMEOUT_MS = 5 * 60 * 1000;
88−const TRANSPORT_STATE_KEY = "transport";
89−const SESSION_META_KEY = "session-meta";
90−const LAST_ACTIVITY_KEY = "last-activity-ms";
91−const approvalResponseKey = (executionId: string) => `approval-response:${executionId}`;
92−
93−// ---------------------------------------------------------------------------
94−// Helpers
95−// ---------------------------------------------------------------------------
96−
97−// The DO's JSON-RPC error bodies are INNER responses (no CORS): the edge worker
98−// re-wraps them with CORS before they leave the origin, so the canonical
99−// renderer is called with `cors: false` to stay byte-identical to the prior
100−// hand-rolled copy (`content-type: application/json` only).
101−const jsonRpcError = (status: number, code: number, message: string) =>
102− jsonRpcErrorBody(status, code, message, { cors: false });
103−
104−const sessionOwnerMismatch = () =>
105− jsonRpcError(403, -32003, "MCP session does not belong to the current bearer");
106−
107−// ---------------------------------------------------------------------------
108−// Host seams
109−// ---------------------------------------------------------------------------
110−
111−/**
112− * A host's per-session DB handle. The base only disposes it during runtime
113− * teardown; the host's `buildMcpServer` reads its concrete shape (postgres.js
114− * for cloud, the D1 `ExecutorDbHandle` for host-cloudflare).
115− */
116−export interface SessionDbHandle {
117− readonly end: () => Promise<void> | void;
118−}
119−
120−/**
121− * Resolved session identity + elicitation mode — the output of a host's
122− * `resolveSessionMeta`. Persisted to `ctx.storage` so a cold isolate can
123− * re-validate ownership and rebuild the runtime without re-resolving.
124− */
125−export interface SessionMeta {
126− readonly organizationId: string;
127− readonly organizationName: string;
128− readonly resource: McpResource;
129− /** The org's URL slug, when the host's `resolveSessionMeta` carried one.
130− * Pins browser-handoff URLs to the right org's console. */
131− readonly organizationSlug?: string;
132− readonly userId: string;
133− readonly elicitationMode?: "browser" | "model" | "native";
134− /** Public origin captured at session create — used to derive the runtime's
135− * web base URL when the host configures no static one. */
136− readonly webOrigin?: string;
137−}
138−
139−/** What a host's `buildMcpServer` seam returns: the connected MCP server plus
140− * the engine the base drives for paused-execution approval flows. */
141−export interface BuiltMcpServer {
142− readonly mcpServer: McpServer;
143− readonly engine: ExecutionEngine<Cause.YieldableError>;
144−}
145−
146−/** The shared browser-approval store the base wires to its persisted approval
147− * responses; a host hands it to its MCP server when elicitation is "browser". */
148−export interface BrowserApprovalStore {
149− readonly takeResponse: (executionId: string) => Effect.Effect<ResumeResponse | null>;
150− readonly waitForResponse: (executionId: string) => Effect.Effect<ResumeResponse | null>;
151−}
152−
153−// ---------------------------------------------------------------------------
154−// Durable Object base
155−// ---------------------------------------------------------------------------
156−
157−export abstract class McpSessionDOBase<
158− TDbHandle extends SessionDbHandle = SessionDbHandle,
159−> extends DurableObject {
160− private readonly instanceCreatedAt = Date.now();
161− private mcpServer: McpServer | null = null;
162− private transport: McpWorkerTransport | null = null;
163− private engine: ExecutionEngine<Cause.YieldableError> | null = null;
164− private initialized = false;
165− private lastActivityMs = 0;
166− private dbHandle: TDbHandle | null = null;
167− private sessionMeta: SessionMeta | null = null;
168− private transportJsonResponseMode: boolean | null = null;
169− private approvalResponses = new Map<string, ResumeResponse>();
170− private approvalWaiters = new Map<string, Deferred.Deferred<ResumeResponse>>();
171− // Updated at the start of each `handleRequest` so the host-mcp server's
172− // `parentSpan` getter — invoked by the MCP SDK's deferred tool callbacks
173− // after `transport.handleRequest()` has already returned its streaming
174− // Response — can hand back the request-scoped span. The server is
175− // session-scoped (a fresh server-per-request would lose the elicitation
176− // request → reply correlation that the SDK keeps in-memory on the
177− // `Server` instance), so we have to bridge a per-request value through
178− // a per-session reference.
179− private currentRequestSpan: Tracer.AnySpan | null = null;
180−
181− // -------------------------------------------------------------------------
182− // Host seams — the ONLY platform-specific surface. A host subclass binds its
183− // DB driver, organization lookup, and MCP-server/engine construction; cloud
184− // adds telemetry + Sentry by overriding the two optional hooks. Everything
185− // else in this class is platform-generic.
186− // -------------------------------------------------------------------------
187−
188− /** Open the per-session DB handle the runtime holds for this session's
189− * lifetime (postgres.js for cloud, the D1 handle for host-cloudflare). May be
190− * async — host-cloudflare runs an idempotent schema bring-up when it opens. */
191− protected abstract openSessionDb(): TDbHandle | Promise<TDbHandle>;
192−
193− /** Resolve `openSessionDb` (sync or async) into the Effect chain. */
194− private openSessionDbHandle(): Effect.Effect<TDbHandle> {
195− return Effect.promise(() => Promise.resolve(this.openSessionDb()));
196− }
197−
198− /** Resolve + validate the session owner into the meta persisted to storage.
199− * Owns its own short-lived DB/services (it runs once per session create). */
200− protected abstract resolveSessionMeta(token: McpSessionInit): Effect.Effect<SessionMeta>;
201−
202− /** Build the connected MCP server + engine for a resolved session. The host
203− * provides its execution stack + DB layers here; the base owns the transport
204− * and the per-request span / browser-approval wiring exposed below. */
205− protected abstract buildMcpServer(
206− sessionMeta: SessionMeta,
207− dbHandle: TDbHandle,
208− ): Effect.Effect<BuiltMcpServer>;
209−
210− /** Optional telemetry seam: stitch the DO span under the worker's incoming
211− * trace and install the host's tracer. Default is identity (no telemetry). */
212− protected withTelemetry<A, E>(
213− effect: Effect.Effect<A, E>,
214− _incoming?: IncomingTraceHeaders,
215− ): Effect.Effect<A, E> {
216− return effect;
217− }
218−
219− /** Optional error seam: report a fatal request cause (cloud → Sentry). */
220− protected captureCause(_cause: Cause.Cause<unknown>): void {}
221−
222− /** Optional flush seam: force-export buffered spans before the DO RPC
223− * settles. Default is a no-op; cloud overrides it with the tracer
224− * provider's `forceFlush`. Without this, a DO whose Effect dies tears the
225− * isolate down with the SimpleSpanProcessor's in-flight export `fetch`
226− * still pending — so the failing method's OWN spans (and the exception +
227− * stack the Effect tracer records on them) never reach the collector, and
228− * only the worker-side `mcp.do.*` span (which carries just the RPC-boundary
229− * message, no real stack) survives. Flushing in an `ensuring` finalizer —
230− * placed OUTSIDE the span so the span has already ended and the
231− * SimpleSpanProcessor has fired its export — lets the flush await that
232− * export before the RPC rejects. */
233− protected flushTelemetry(): Promise<void> {
234− return Promise.resolve();
235− }
236−
237− /** Mirror an error cause onto the active span as top-level attributes. The
238− * Effect OTel tracer already records the cause as an `exception` span
239− * EVENT (`exception.stacktrace` = the full `Cause.pretty` rendering), but
240− * span events are awkward to query in Axiom and the worker-side spans never
241− * see them. Copying type/message/stack onto plain span attributes makes the
242− * failing frame a one-field APL lookup on the span row itself. */
243− private recordCauseOnSpan(cause: Cause.Cause<unknown>): Effect.Effect<void> {
244− const errors = Cause.prettyErrors(cause);
245− // No `Error` reasons means a pure interruption (or empty cause) — nothing
246− // worth annotating, and we don't want to flag interrupts as failures.
247− if (errors.length === 0) return Effect.void;
248− const first = errors[0];
249− return Effect.annotateCurrentSpan({
250− "exception.type": first?.name ?? "Error",
251− "exception.message": first?.message ?? "unknown",
252− "exception.stacktrace": Cause.pretty(cause),
253− });
254− }
255−
256− /** Flush DO-side spans once the wrapped method (incl. its span) has ended,
257− * whether it succeeded or died. Apply as the OUTERMOST pipe step. */
258− private withSpanFlush<A, E>(effect: Effect.Effect<A, E>): Effect.Effect<A, E> {
259− const self = this;
260− return effect.pipe(Effect.ensuring(Effect.promise(() => self.flushTelemetry())));
261− }
262−
263− /** The session id — equal to this DO's id. */
264− protected get sessionId(): string {
265− return this.ctx.id.toString();
266− }
267−
268− /** The request-scoped span for the host-mcp `parentSpan` getter (read by
269− * deferred MCP SDK callbacks after the request Effect has returned). */
270− protected currentParentSpan(): Tracer.AnySpan | undefined {
271− return this.currentRequestSpan ?? undefined;
272− }
273−
274− /** The browser-approval store wired to this session's persisted responses. */
275− protected readonly browserApprovalStore: BrowserApprovalStore = {
276− takeResponse: (executionId) => this.takeApprovalResponse(executionId),
277− waitForResponse: (executionId) => this.waitForApprovalResponse(executionId),
278− };
279−
280− private makeStorage() {
281− return {
282− get: async (): Promise<TransportState | undefined> => {
283− return await this.ctx.storage.get<TransportState>(TRANSPORT_STATE_KEY);
284− },
285− set: async (state: TransportState): Promise<void> => {
286− await this.ctx.storage.put(TRANSPORT_STATE_KEY, state);
287− },
288− };
289− }
290−
291− private loadSessionMeta(): Effect.Effect<SessionMeta | null> {
292− return Effect.promise(async () => {
293− if (this.sessionMeta) return this.sessionMeta;
294− const stored = await this.ctx.storage.get<SessionMeta>(SESSION_META_KEY);
295− this.sessionMeta = stored ?? null;
296− return this.sessionMeta;
297− }).pipe(Effect.withSpan("mcp.session.load_meta"));
298− }
299−
300− private async saveSessionMeta(sessionMeta: SessionMeta): Promise<void> {
301− this.sessionMeta = sessionMeta;
302− await this.ctx.storage.put(SESSION_META_KEY, sessionMeta);
303− }
304−
305− private async markActivity(now = Date.now()): Promise<void> {
306− this.lastActivityMs = now;
307− await Promise.all([
308− this.ctx.storage.put(LAST_ACTIVITY_KEY, now),
309− this.ctx.storage.setAlarm(now + HEARTBEAT_MS),
310− ]);
311− }
312−
313− private async loadLastActivity(): Promise<number> {
314− if (this.lastActivityMs > 0) return this.lastActivityMs;
315− const stored = await this.ctx.storage.get<number>(LAST_ACTIVITY_KEY);
316− this.lastActivityMs = stored ?? 0;
317− return this.lastActivityMs;
318− }
319−
320− private entryAttrs(methodEnteredAt: number): Record<string, unknown> {
321− const now = Date.now();
322− return {
323− "mcp.do.instance_age_ms": now - this.instanceCreatedAt,
324− "mcp.do.method_entry_delay_ms": now - methodEnteredAt,
325− "mcp.session.session_id": this.ctx.id.toString(),
326− "mcp.session.initialized": this.initialized,
327− "mcp.session.has_transport": !!this.transport,
328− "mcp.session.has_meta_memory": !!this.sessionMeta,
329− };
330− }
331−
332− private clearSessionState(): Effect.Effect<void> {
333− return Effect.promise(async () => {
334− this.sessionMeta = null;
335− this.initialized = false;
336− this.lastActivityMs = 0;
337− this.transportJsonResponseMode = null;
338−
339− await Promise.all([
340− // oxlint-disable-next-line executor/no-promise-catch -- boundary: Durable Object storage cleanup is best-effort after session invalidation
341− this.ctx.storage.delete(TRANSPORT_STATE_KEY).catch(() => false),
342− // oxlint-disable-next-line executor/no-promise-catch -- boundary: Durable Object storage cleanup is best-effort after session invalidation
343− this.ctx.storage.delete(SESSION_META_KEY).catch(() => false),
344− // oxlint-disable-next-line executor/no-promise-catch -- boundary: Durable Object storage cleanup is best-effort after session invalidation
345− this.ctx.storage.delete(LAST_ACTIVITY_KEY).catch(() => false),
346− // oxlint-disable-next-line executor/no-promise-catch -- boundary: Durable Object alarm cleanup is best-effort after session invalidation
347− this.ctx.storage.deleteAlarm().catch(() => undefined),
348− ]);
349− }).pipe(Effect.withSpan("mcp.session.clear_state"));
350− }
351−
352− private createConnectedRuntime(
353− sessionMeta: SessionMeta,
354− options: { readonly dbHandle: TDbHandle; readonly enableJsonResponse?: boolean },
355− ) {
356− const self = this;
357− return Effect.gen(function* () {
358− // The host builds its MCP server + engine (execution stack, DB layers,
359− // elicitation policy); the base owns the worker transport so JSON-response
360− // mode, the session-id generator, and storage stay identical everywhere.
361− // The session's captured origin is provided here so the host's execution
362− // stack derives a web base URL zero-config (a no-op when it configures one).
363− const built = self.buildMcpServer(sessionMeta, options.dbHandle);
364− const withOrigin = sessionMeta.webOrigin
365− ? built.pipe(Effect.provideService(RequestWebOrigin, { origin: sessionMeta.webOrigin }))
366− : built;
367− // Pin browser-handoff URLs to the session's org slug when captured at
368− // create; absent slug leaves the URL bare for client canonicalization.
369− const { mcpServer, engine } = yield* sessionMeta.organizationSlug
370− ? withOrigin.pipe(
371− Effect.provideService(RequestOrgSlug, { slug: sessionMeta.organizationSlug }),
372− )
373− : withOrigin;
374− const transport = yield* makeMcpWorkerTransport({
375− sessionIdGenerator: () => self.sessionId,
376− storage: self.makeStorage(),
377− enableJsonResponse: options.enableJsonResponse,
378− });
379− self.transportJsonResponseMode = options.enableJsonResponse ?? false;
380− yield* transport.connect(mcpServer);
381− return { mcpServer, transport, engine };
382− }).pipe(Effect.withSpan("McpSessionDO.createRuntime"));
383− }
384−
385− private closeRuntime(): Effect.Effect<void> {
386− const self = this;
387− return Effect.gen(function* () {
388− if (self.transport) {
389− yield* self.transport.close();
390− self.transport = null;
391− }
392− if (self.mcpServer) {
393− const mcpServer = self.mcpServer;
394− // oxlint-disable-next-line executor/no-promise-catch -- boundary: MCP SDK close failure is ignored during best-effort runtime teardown
395− yield* Effect.promise(() => mcpServer.close().catch(() => undefined));
396− self.mcpServer = null;
397− }
398− self.engine = null;
399− if (self.dbHandle) {
400− const dbHandle = self.dbHandle;
401− yield* Effect.promise(() => Promise.resolve(dbHandle.end()));
402− self.dbHandle = null;
403− }
404− self.initialized = false;
405− self.transportJsonResponseMode = null;
406− }).pipe(
407− // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: DO cleanup has no typed failure surface
408− Effect.orDie,
409− );
410− }
411−
412− private installRuntime(
413− sessionMeta: SessionMeta,
414− options: {
415− readonly dbHandle: TDbHandle;
416− readonly enableJsonResponse: boolean;
417− },
418− ) {
419− const self = this;
420− return Effect.gen(function* () {
421− const runtime = yield* self.createConnectedRuntime(sessionMeta, options);
422− self.dbHandle = options.dbHandle;
423− self.mcpServer = runtime.mcpServer;
424− self.transport = runtime.transport;
425− self.engine = runtime.engine;
426− self.initialized = true;
427− });
428− }
429−
430− private ensureRuntimeForApproval(): Effect.Effect<boolean> {
431− const self = this;
432− return Effect.gen(function* () {
433− if (self.initialized && self.engine) return true;
434−
435− const sessionMeta = yield* self.loadSessionMeta();
436− if (!sessionMeta) return false;
437−
438− yield* self.closeRuntime();
439− const dbHandle = yield* self.openSessionDbHandle();
440− yield* self.installRuntime(sessionMeta, {
441− dbHandle,
442− enableJsonResponse: true,
443− });
444− yield* Effect.promise(() => self.markActivity()).pipe(
445− Effect.withSpan("McpSessionDO.markActivity"),
446− );
447− return true;
448− }).pipe(
449− Effect.withSpan("McpSessionDO.ensure_runtime_for_approval"),
450− // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: DO RPC has no typed Effect channel
451− Effect.orDie,
452− );
453− }
454−
455− private validateApprovalIdentity(
456− identity: McpApprovalOwner,
457− ): Effect.Effect<"ok" | "not_found" | "forbidden"> {
458− const self = this;
459− return Effect.gen(function* () {
460− const sessionMeta = yield* self.loadSessionMeta();
461− if (!sessionMeta) return "not_found" as const;
462−
463− const matches =
464− identity.accountId === sessionMeta.userId &&
465− identity.organizationId === sessionMeta.organizationId;
466−
467− yield* Effect.annotateCurrentSpan({
468− "mcp.session.owner_match": matches,
469− });
470−
471− return matches ? ("ok" as const) : ("forbidden" as const);
472− }).pipe(Effect.withSpan("mcp.session.validate_approval_identity"));
473− }
474−
475− private restoreRuntimeFromStorage(request: Request): Effect.Effect<"restored" | "missing_meta"> {
476− const self = this;
477− return Effect.gen(function* () {
478− if (self.initialized && self.transport) return "restored" as const;
479−
480− const sessionMeta = yield* self.loadSessionMeta();
481− if (!sessionMeta) {
482− yield* Effect.annotateCurrentSpan({
483− "mcp.session.restore.outcome": "missing_meta",
484− });
485− return "missing_meta" as const;
486− }
487−
488− yield* self.closeRuntime();
489− const dbHandle = yield* self.openSessionDbHandle();
490− yield* self.installRuntime(sessionMeta, {
491− dbHandle,
492− // GET always returns an SSE stream regardless of this option, but the
493− // session-scoped transport is reused by later POSTs. Keep JSON mode on
494− // across cold restores so a GET reconnect cannot poison future POSTs.
495− enableJsonResponse: true,
496− });
497− yield* Effect.promise(() => self.markActivity()).pipe(
498− Effect.withSpan("McpSessionDO.markActivity"),
499− );
500− yield* Effect.annotateCurrentSpan({
501− "mcp.session.restore.outcome": "restored",
502− });
503− return "restored" as const;
504− }).pipe(
505− Effect.withSpan("McpSessionDO.restoreRuntime", {
506− attributes: {
507− "mcp.request.method": request.method,
508− "mcp.request.session_id_present": !!request.headers.get("mcp-session-id"),
509− },
510− }),
511− // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: cold DO restore is re-entered from Promise-only Durable Object method
512− Effect.orDie,
513− );
514− }
515−
516− private ensureJsonResponseTransportForPost(request: Request): Effect.Effect<void> {
517− const self = this;
518− return Effect.gen(function* () {
519− if (request.method !== "POST" || self.transportJsonResponseMode === true) return;
520−
521− const sessionMeta = yield* self.loadSessionMeta();
522− if (!sessionMeta) return;
523−
524− yield* self.closeRuntime();
525− const dbHandle = yield* self.openSessionDbHandle();
526− yield* self.installRuntime(sessionMeta, {
527− dbHandle,
528− enableJsonResponse: true,
529− });
530− yield* Effect.annotateCurrentSpan({
531− "mcp.session.transport_upgraded_json_response": true,
532− });
533− }).pipe(
534− Effect.withSpan("McpSessionDO.ensureJsonResponseTransportForPost"),
535− // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: transport rebuild is internal DO runtime state
536− Effect.orDie,
537− );
538− }
539−
540− private validateSessionOwner(request: Request): Effect.Effect<Response | null> {
541− const self = this;
542− return Effect.gen(function* () {
543− const sessionMeta = yield* self.loadSessionMeta();
544− if (!sessionMeta) return null;
545−
546− const accountId = request.headers.get(INTERNAL_ACCOUNT_ID_HEADER);
547− const organizationId = request.headers.get(INTERNAL_ORGANIZATION_ID_HEADER);
548− const resourceKey = request.headers.get(INTERNAL_RESOURCE_KEY_HEADER);
549− const matches =
550− accountId === sessionMeta.userId &&
551− organizationId === sessionMeta.organizationId &&
552− resourceKey === mcpResourceKey(sessionMeta.resource);
553−
554− yield* Effect.annotateCurrentSpan({
555− "mcp.session.owner_match": matches,
556− });
557−
558− return matches ? null : sessionOwnerMismatch();
559− }).pipe(Effect.withSpan("mcp.session.validate_owner"));
560− }
561−
562− private resolveAndStoreSessionMeta(token: McpSessionInit) {
563− const self = this;
564− return Effect.gen(function* () {
565− const resolved = yield* self.resolveSessionMeta(token);
566− // Carry the create request's origin onto the persisted meta (the host's
567− // resolveSessionMeta is identity-only and doesn't see it), so a cold
568− // isolate rebuilds the runtime with the same web base URL.
569− const sessionMeta: SessionMeta = {
570− ...resolved,
571− resource: token.resource,
572− ...(token.webOrigin ? { webOrigin: token.webOrigin } : {}),
573− };
574− yield* Effect.promise(() => self.saveSessionMeta(sessionMeta)).pipe(
575− Effect.withSpan("mcp.session.save_meta"),
576− );
577− return sessionMeta;
578− }).pipe(Effect.withSpan("mcp.session.resolve_and_store_meta"));
579− }
580−
581− async init(token: McpSessionInit, incoming?: IncomingTraceHeaders): Promise<void> {
582− const methodEnteredAt = Date.now();
583− if (this.initialized) return;
584− const self = this;
585− return Effect.runPromise(
586− Effect.gen(function* () {
587− yield* Effect.annotateCurrentSpan(self.entryAttrs(methodEnteredAt));
588− yield* self.doInit(token);
589− }).pipe(
590− Effect.withSpan("McpSessionDO.init", {
591− attributes: { "mcp.auth.organization_id": token.organizationId },
592− }),
593− (eff) => this.withTelemetry(eff, incoming),
594− // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: Durable Object init method can only reject its Promise
595− Effect.orDie,
596− (eff) => self.withSpanFlush(eff),
597− ),
598− );
599− }
600−
601− private doInit(token: McpSessionInit) {
602− const self = this;
603− // Single Effect chain so every sub-span (resolveSessionMeta,
604− // createRuntime, createScopedExecutor, createExecutorMcpServer,
605− // transport.connect, storage.setAlarm) lands as a child of
606− // `McpSessionDO.init`. The prior implementation called
607− // `Effect.runPromise` nested inside an async function, which orphaned
608− // each sub-span into its own root trace and made init opaque —
609− // dashboard saw one 2.77s span with nothing under it.
610− return Effect.gen(function* () {
611− const sessionMeta = yield* self.resolveAndStoreSessionMeta(token);
612−
613− self.dbHandle = yield* self.openSessionDbHandle();
614− // POST responses go out as JSON so `transport.handleRequest()` awaits
615− // every MCP tool callback before resolving — keeps engine spans inside
616− // the outer `handleRequest` Effect's fiber so `currentRequestSpan` is
617− // still set when the host-mcp `parentSpan` getter reads it. With SSE
618− // POSTs the callback fires after `Effect.ensuring` clears the field
619− // and engine spans orphan into new root traces. GET still streams
620− // (the GET handler doesn't consult `enableJsonResponse`).
621− const runtime = yield* self.createConnectedRuntime(sessionMeta, {
622− dbHandle: self.dbHandle,
623− enableJsonResponse: true,
624− });
625− self.mcpServer = runtime.mcpServer;
626− self.transport = runtime.transport;
627− self.engine = runtime.engine;
628−
629− self.initialized = true;
630− yield* Effect.promise(() => self.markActivity()).pipe(
631− Effect.withSpan("McpSessionDO.markActivity"),
632− );
633− }).pipe(
634− Effect.tapCause((cause) =>
635− Effect.gen(function* () {
636− console.error("[mcp-session] init failed:", Cause.pretty(cause));
637− // Report to the host's error sink (cloud → Sentry). init() runs on
638− // the prototype, which Sentry's auto-wrap doesn't cover, so capture
639− // here explicitly rather than relying on prototype instrumentation.
640− self.captureCause(cause);
641− // Annotate `McpSessionDO.init` (the active span here — `doInit` opens
642− // none of its own) so the surviving, flushed span names the frame.
643− yield* self.recordCauseOnSpan(cause);
644− }),
645− ),
646− Effect.catchCause((cause) =>
647− Effect.gen(function* () {
648− yield* Effect.promise(() => self.cleanup());
649− return yield* Effect.failCause(cause);
650− }),
651− ),
652− // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: doInit is called only from Promise-only Durable Object init
653− Effect.orDie,
654− );
655− }
656−
657− async handleRequest(request: Request): Promise<Response> {
658− const methodEnteredAt = Date.now();
659− // Wrap the dispatch in an Effect span so every DO request — not just
660− // the rare new-session `init()` — shows up in Axiom. Basic attributes
661− // only (method, session-id presence, response status); rich client
662− // fingerprint stays on the edge `mcp.request` span, which shares a
663− // trace_id with this one.
664− const incoming = {
665− traceparent: request.headers.get("traceparent") ?? undefined,
666− tracestate: request.headers.get("tracestate") ?? undefined,
667− baggage: request.headers.get("baggage") ?? undefined,
668− } satisfies IncomingTraceHeaders;
669− const self = this;
670− const program = Effect.gen(function* () {
671− yield* Effect.annotateCurrentSpan(self.entryAttrs(methodEnteredAt));
672− // Capture the request-entry span so the host-mcp `parentSpan` getter
673− // — fired by deferred MCP SDK callbacks after this Effect has already
674− // returned — anchors engine spans under the same trace. Cleared in a
675− // finalizer so a future request that arrives without a fresh span
676− // doesn't accidentally inherit a stale one.
677− const span = yield* Effect.currentSpan;
678− self.currentRequestSpan = span;
679−
680− return yield* self.dispatchRequest(request).pipe(
681− Effect.tap((response) =>
682− Effect.annotateCurrentSpan({
683− "mcp.response.status_code": response.status,
684− "mcp.response.content_type": response.headers.get("content-type") ?? "",
685− "mcp.transport.enable_json_response": self.transportJsonResponseMode ?? false,
686− }),
687− ),
688− Effect.ensuring(
689− Effect.sync(() => {
690− self.currentRequestSpan = null;
691− }),
692− ),
693− );
694− }).pipe(
695− // Cold-restore failures (`restoreRuntimeFromStorage` is `orDie`'d and is
696− // NOT under the handled `dispatchAuthorizedRequest` branch) die straight
697− // through here — annotate the handleRequest span with their stack before
698− // it ends so the flushed span names the frame.
699− Effect.tapCause((cause) => self.recordCauseOnSpan(cause)),
700− Effect.withSpan("McpSessionDO.handleRequest", {
701− attributes: {
702− "mcp.request.method": request.method,
703− "mcp.request.session_id_present": !!request.headers.get("mcp-session-id"),
704− },
705− }),
706− (eff) => this.withTelemetry(eff, incoming),
707− (eff) => self.withSpanFlush(eff),
708− );
709− return Effect.runPromise(program);
710− }
711−
712− async getPausedExecutionForApproval(
713− executionId: string,
714− identity: McpApprovalOwner,
715− incoming?: IncomingTraceHeaders,
716− ): Promise<McpSessionApprovalResult> {
717− const self = this;
718− return Effect.runPromise(
719− Effect.gen(function* () {
720− const owner = yield* self.validateApprovalIdentity(identity);
721− if (owner !== "ok") return { status: owner } as const;
722−
723− const restored = yield* self.ensureRuntimeForApproval();
724− if (!restored || !self.engine) return { status: "not_found" } as const;
725−
726− const paused = yield* self.engine.getPausedExecution(executionId);
727− if (!paused) return { status: "not_found" } as const;
728−
729− const formatted = formatPausedExecution(paused);
730− return {
731− status: "ok" as const,
732− text: formatted.text,
733− structured: formatted.structured,
734− };
735− }).pipe(
736− Effect.withSpan("McpSessionDO.getPausedExecutionForApproval", {
737− attributes: { "mcp.execution.id": executionId },
738− }),
739− (eff) => this.withTelemetry(eff, incoming),
740− // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: DO RPC exposes Promise results
741− Effect.orDie,
742− (eff) => self.withSpanFlush(eff),
743− ),
744− );
745− }
746−
747− private takeApprovalResponse(executionId: string): Effect.Effect<ResumeResponse | null> {
748− const self = this;
749− return Effect.promise(async () => {
750− const memoryResponse = self.approvalResponses.get(executionId);
751− if (memoryResponse) {
752− self.approvalResponses.delete(executionId);
753− await self.ctx.storage.delete(approvalResponseKey(executionId));
754− return memoryResponse;
755− }
756− const stored = await self.ctx.storage.get<ResumeResponse>(approvalResponseKey(executionId));
757− if (!stored) return null;
758− await self.ctx.storage.delete(approvalResponseKey(executionId));
759− return stored;
760− });
761− }
762−
763− private waitForApprovalResponse(executionId: string): Effect.Effect<ResumeResponse | null> {
764− const self = this;
765− return Effect.gen(function* () {
766− const existing = yield* self.takeApprovalResponse(executionId);
767− if (existing) return existing;
768−
769− const waiter =
770− self.approvalWaiters.get(executionId) ?? (yield* Deferred.make<ResumeResponse>());
771− self.approvalWaiters.set(executionId, waiter);
772− yield* Deferred.await(waiter).pipe(
773− Effect.ensuring(
774− Effect.sync(() => {
775− if (self.approvalWaiters.get(executionId) === waiter) {
776− self.approvalWaiters.delete(executionId);
777− }
778− }),
779− ),
780− );
781− return yield* self.takeApprovalResponse(executionId);
782− });
783− }
784−
785− async resumeExecutionForApproval(
786− executionId: string,
787− identity: McpApprovalOwner,
788− response: ResumeResponse,
789− incoming?: IncomingTraceHeaders,
790− ): Promise<McpSessionResumeApprovalResult> {
791− const self = this;
792− return Effect.runPromise(
793− Effect.gen(function* () {
794− const owner = yield* self.validateApprovalIdentity(identity);
795− if (owner !== "ok") return { status: owner } as const;
796−
797− const restored = yield* self.ensureRuntimeForApproval();
798− if (!restored || !self.engine) return { status: "not_found" } as const;
799−
800− const paused = yield* self.engine.getPausedExecution(executionId);
801− if (!paused) return { status: "not_found" } as const;
802−
803− self.approvalResponses.set(executionId, response);
804− yield* Effect.promise(() =>
805− self.ctx.storage.put(approvalResponseKey(executionId), response),
806− );
807− const waiter = self.approvalWaiters.get(executionId);
808− if (waiter) yield* Deferred.succeed(waiter, response);
809− return resumeApprovalResult(executionId, response);
810− }).pipe(
811− Effect.withSpan("McpSessionDO.resumeExecutionForApproval", {
812− attributes: { "mcp.execution.id": executionId },
813− }),
814− (eff) => this.withTelemetry(eff, incoming),
815− // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: DO RPC exposes Promise results
816− Effect.orDie,
817− (eff) => self.withSpanFlush(eff),
818− ),
819− );
820− }
821−
822− private dispatchRequest(request: Request): Effect.Effect<Response> {
823− const self = this;
824− return Effect.gen(function* () {
825− const ownerError = yield* self.validateSessionOwner(request);
826− if (ownerError) return ownerError;
827− return yield* self.dispatchAuthorizedRequest(request);
828− });
829− }
830−
831− private dispatchAuthorizedRequest(request: Request): Effect.Effect<Response> {
832− if (!this.initialized || !this.transport) {
833− if (request.method === "DELETE") {
834− return this.clearSessionState().pipe(
835− Effect.as(new Response(null, { status: 204 })),
836− Effect.withSpan("mcp.session.stale_delete"),
837− );
838− }
839− const self = this;
840− return Effect.gen(function* () {
841− const restored = yield* self.restoreRuntimeFromStorage(request);
842− if (restored === "restored") {
843− return yield* self.dispatchAuthorizedRequest(request);
844− }
845− return jsonRpcError(404, -32001, "Session timed out due to inactivity — please reconnect");
846− });
847− }
848−
849− const self = this;
850− return Effect.gen(function* () {
851− yield* self.ensureJsonResponseTransportForPost(request);
852− const transport = self.transport;
853− if (!transport) {
854− return jsonRpcError(404, -32001, "Session timed out due to inactivity — please reconnect");
855− }
856−
857− yield* Effect.promise(() => self.markActivity()).pipe(
858− Effect.withSpan("McpSessionDO.markActivity"),
859− );
860− const response = yield* transport.handleRequest(request).pipe(
861− Effect.withSpan("McpSessionDO.transport.handleRequest", {
862− attributes: {
863− "mcp.request.method": request.method,
864− "mcp.request.content_type": request.headers.get("content-type") ?? "",
865− "mcp.request.content_length": request.headers.get("content-length") ?? "",
866− },
867− }),
868− );
869− yield* Effect.annotateCurrentSpan({
870− "mcp.response.status_code": response.status,
871− "mcp.response.content_type": response.headers.get("content-type") ?? "",
872− "mcp.transport.enable_json_response": self.transportJsonResponseMode ?? false,
873− });
874− if (request.method === "DELETE") {
875− yield* Effect.promise(() => self.cleanup()).pipe(Effect.withSpan("mcp.session.cleanup"));
876− }
877− return response;
878− }).pipe(
879− Effect.catchCause((cause) =>
880− Effect.gen(function* () {
881− console.error("[mcp-session] handleRequest error:", Cause.pretty(cause));
882− self.captureCause(cause);
883− yield* self.recordCauseOnSpan(cause);
884− return jsonRpcError(500, -32603, "Internal error");
885− }),
886− ),
887− );
888− }
889−
890− override async alarm(): Promise<void> {
891− const program = Effect.promise(() => this.runAlarm()).pipe(
892− Effect.withSpan("McpSessionDO.alarm"),
893− (eff) => this.withTelemetry(eff),
894− (eff) => this.withSpanFlush(eff),
895− );
896− return Effect.runPromise(program);
897− }
898−
899− async clearSession(incoming?: IncomingTraceHeaders): Promise<void> {
900− return Effect.runPromise(
901− Effect.promise(() => this.cleanup()).pipe(
902− Effect.withSpan("McpSessionDO.clearSession"),
903− (eff) => this.withTelemetry(eff, incoming),
904− (eff) => this.withSpanFlush(eff),
905− ),
906− );
907− }
908−
909− private async runAlarm(): Promise<void> {
910− const lastActivityMs = await this.loadLastActivity();
911− const idleMs = Date.now() - lastActivityMs;
912− if (idleMs >= SESSION_TIMEOUT_MS) {
913− await Effect.runPromise(this.closeRuntime());
914− await this.ctx.storage.deleteAlarm();
915− return;
916− }
917− await this.ctx.storage.setAlarm(Date.now() + HEARTBEAT_MS);
918− }
919−
920− private async cleanup(): Promise<void> {
921− await Effect.runPromise(this.closeRuntime());
922− await Effect.runPromise(this.clearSessionState());
923− }
924−}
packages/hosts/cloudflare/src/mcp/session-store.test.ts+0−226
@@ -1,226 +0,0 @@
1−// ---------------------------------------------------------------------------
2−// Regression coverage for the transient DO-relocation retry in the worker-side
3−// session-store dispatcher.
4−//
5−// Cloudflare may relocate a live Durable Object between machines; an in-flight
6−// `init` then throws "cannot access storage because object has moved to a
7−// different machine". Before the retry, that rejection became an unrecoverable
8−// defect the envelope rendered as a -32603 — exactly the prod incident on
9−// 2026-06-15 (one `mcp.do.init` failure a reconnect cleared). These tests inject
10−// that rejection at the `McpSessionDOStub` seam and pin the recovery behavior.
11−// ---------------------------------------------------------------------------
12−
13−import { describe, expect, it } from "@effect/vitest";
14−import { Cause, Effect, Exit } from "effect";
15−
16−import {
17− McpSessionStore,
18− defaultMcpResource,
19− type McpDispatchResult,
20− type Principal,
21−} from "@executor-js/host-mcp";
22−
23−import {
24− DO_RELOCATION_MAX_RETRIES,
25− makeDurableObjectMcpSessionStore,
26− type McpSessionInit,
27− type McpSessionDOStub,
28−} from "./session-store";
29−import { INTERNAL_RESOURCE_KEY_HEADER } from "./do-headers";
30−
31−const RELOCATION_ERROR = "cannot access storage because object has moved to a different machine";
32−
33−const TEST_PRINCIPAL: Principal = {
34− accountId: "user_test",
35− organizationId: "org_test",
36− organizationName: "Test Org",
37− email: "test@example.com",
38− name: "Test",
39− avatarUrl: null,
40− roles: ["user"],
41−};
42−
43−/** A POST with no session id — routes dispatch through the create/init path. */
44−const initializeRequest = (): Request =>
45− new Request("https://mcp.test/mcp", {
46− method: "POST",
47− headers: { authorization: "Bearer x", "content-type": "application/json" },
48− body: JSON.stringify({ jsonrpc: "2.0", id: 0, method: "initialize" }),
49− });
50−
51−/** The JSON-RPC body the faked DO returns once `init` succeeds. */
52−const okResponse = (): Response =>
53− new Response(JSON.stringify({ jsonrpc: "2.0", id: 0, result: {} }), {
54− status: 200,
55− headers: { "content-type": "application/json" },
56− });
57−
58−/** Drive `dispatch` for a create over a store built from one fake stub. */
59−const dispatchCreate = (stub: McpSessionDOStub): Effect.Effect<McpDispatchResult> =>
60− Effect.gen(function* () {
61− const store = yield* McpSessionStore;
62− return yield* store.dispatch({
63− request: initializeRequest(),
64− principal: TEST_PRINCIPAL,
65− resource: defaultMcpResource,
66− sessionId: null,
67− method: "POST",
68− });
69− }).pipe(
70− Effect.provide(makeDurableObjectMcpSessionStore({ newStub: () => stub, getStub: () => stub })),
71− );
72−
73−/** Rendered cause of a failed dispatch (empty for a success) — keeps the
74− * message assertion unconditional, off the `Exit.isFailure` branch. */
75−const failureText = (exit: Exit.Exit<McpDispatchResult>): string =>
76− Exit.isFailure(exit) ? Cause.pretty(exit.cause) : "";
77−
78−describe("makeDurableObjectMcpSessionStore — DO-relocation retry", () => {
79− it.live("passes the requested MCP resource into session init", () =>
80− Effect.gen(function* () {
81− let initMeta: McpSessionInit | undefined;
82− const stub: McpSessionDOStub = {
83− init: (meta) => {
84− initMeta = meta;
85− return Promise.resolve();
86− },
87− handleRequest: () => Promise.resolve(okResponse()),
88− clearSession: () => Promise.resolve(),
89− };
90−
91− const result = yield* Effect.gen(function* () {
92− const store = yield* McpSessionStore;
93− return yield* store.dispatch({
94− request: initializeRequest(),
95− principal: TEST_PRINCIPAL,
96− resource: { kind: "toolkit", slug: "deploy" },
97− sessionId: null,
98− method: "POST",
99− });
100− }).pipe(
101− Effect.provide(
102− makeDurableObjectMcpSessionStore({ newStub: () => stub, getStub: () => stub }),
103− ),
104− );
105−
106− expect(result).toBeInstanceOf(Response);
107− expect(initMeta?.resource, "the DO session is keyed to the requested resource").toEqual({
108− kind: "toolkit",
109− slug: "deploy",
110− });
111− }),
112− );
113−
114− it.live("stamps the requested MCP resource on forwarded session requests", () =>
115− Effect.gen(function* () {
116− let forwardedResourceKey: string | null = null;
117− const stub: McpSessionDOStub = {
118− init: () => Promise.resolve(),
119− handleRequest: (request) => {
120− forwardedResourceKey = request.headers.get(INTERNAL_RESOURCE_KEY_HEADER);
121− return Promise.resolve(okResponse());
122− },
123− clearSession: () => Promise.resolve(),
124− };
125−
126− const result = yield* Effect.gen(function* () {
127− const store = yield* McpSessionStore;
128− return yield* store.dispatch({
129− request: initializeRequest(),
130− principal: TEST_PRINCIPAL,
131− resource: { kind: "toolkit", slug: "deploy" },
132− sessionId: "existing-session",
133− method: "POST",
134− });
135− }).pipe(
136− Effect.provide(
137− makeDurableObjectMcpSessionStore({ newStub: () => stub, getStub: () => stub }),
138− ),
139− );
140−
141− expect(result).toBeInstanceOf(Response);
142− expect(
143− forwardedResourceKey,
144− "the DO can reject a session id reused on another resource",
145− ).toBe("toolkit:deploy");
146− }),
147− );
148−
149− it.live("retries mcp.do.init past a relocation, then returns the DO response", () =>
150− Effect.gen(function* () {
151− let initCalls = 0;
152− let handleCalls = 0;
153− const stub: McpSessionDOStub = {
154− init: () => {
155− initCalls += 1;
156− if (initCalls === 1) {
157− // oxlint-disable-next-line executor/no-promise-reject, executor/no-error-constructor -- boundary: fake DO stub rejects to simulate a Cloudflare relocation throw
158− return Promise.reject(new Error(RELOCATION_ERROR));
159− }
160− return Promise.resolve();
161− },
162− handleRequest: () => {
163− handleCalls += 1;
164− return Promise.resolve(okResponse());
165− },
166− clearSession: () => Promise.resolve(),
167− };
168−
169− const result = yield* dispatchCreate(stub);
170−
171− expect(initCalls, "init retried once after the relocation").toBe(2);
172− expect(handleCalls, "handleRequest runs once, after init recovers").toBe(1);
173− expect(result).toBeInstanceOf(Response);
174− expect((result as Response).status).toBe(200);
175− }),
176− );
177−
178− it.live("gives up after the retry budget when relocation never clears", () =>
179− Effect.gen(function* () {
180− let initCalls = 0;
181− let handleCalls = 0;
182− const stub: McpSessionDOStub = {
183− init: () => {
184− initCalls += 1;
185− // oxlint-disable-next-line executor/no-promise-reject, executor/no-error-constructor -- boundary: fake DO stub rejects to simulate a Cloudflare relocation throw
186− return Promise.reject(new Error(RELOCATION_ERROR));
187− },
188− handleRequest: () => {
189− handleCalls += 1;
190− return Promise.resolve(okResponse());
191− },
192− clearSession: () => Promise.resolve(),
193− };
194−
195− const exit = yield* Effect.exit(dispatchCreate(stub));
196−
197− expect(Exit.isFailure(exit), "exhausted retries surface as a defect").toBe(true);
198− expect(initCalls, "one initial attempt plus the full retry budget").toBe(
199− 1 + DO_RELOCATION_MAX_RETRIES,
200− );
201− expect(handleCalls, "handleRequest is never reached when init never succeeds").toBe(0);
202− expect(failureText(exit), "the relocation cause is preserved").toContain(RELOCATION_ERROR);
203− }),
204− );
205−
206− it.live("does not retry a non-relocation failure — surfaces it immediately", () =>
207− Effect.gen(function* () {
208− let initCalls = 0;
209− const stub: McpSessionDOStub = {
210− init: () => {
211− initCalls += 1;
212− // oxlint-disable-next-line executor/no-promise-reject, executor/no-error-constructor -- boundary: fake DO stub rejects to simulate a non-transient init failure
213− return Promise.reject(new Error("schema spread bug — not transient"));
214− },
215− handleRequest: () => Promise.resolve(okResponse()),
216− clearSession: () => Promise.resolve(),
217− };
218−
219− const exit = yield* Effect.exit(dispatchCreate(stub));
220−
221− expect(Exit.isFailure(exit), "a non-transient rejection still dies").toBe(true);
222− expect(initCalls, "non-relocation errors are not retried").toBe(1);
223− expect(failureText(exit), "the original failure is preserved").toContain("schema spread bug");
224− }),
225− );
226−});
packages/hosts/cloudflare/src/mcp/session-store.ts+0−261
@@ -1,261 +0,0 @@
1−// ---------------------------------------------------------------------------
2−// The Durable-Object-backed McpSessionStore — the cross-isolate variant of the
3−// shared host-mcp session seam. Shared by every Cloudflare host (cloud +
4−// host-cloudflare); a host supplies ONLY its DO namespace accessors (newStub
5−// for create, getStub for forward, addressed by the session-id == DO-id) and an
6−// optional internal-error reporter. Everything else — identity-header stamping,
7−// W3C trace propagation, response peeking, the verbatim DO error passthrough —
8−// is platform-generic.
9−//
10−// `dispatch` owns the worker-isolate orchestration:
11−// - sessionId null + POST initialize -> newStub() -> init(meta) + handleRequest
12−// - sessionId present -> getStub(id) -> handleRequest (the DO id routes back to
13−// the same isolate, which is the whole point — sessions survive across the
14−// worker's stateless isolates).
15−//
16−// IMPORTANT: the DO `Response` is returned VERBATIM (incl. its 403 -32003 /
17−// 404 -32001 error bodies) — the envelope's "forbidden"/"not-found" discriminants
18−// would emit different message bytes. The envelope short-circuits bare GET (400)
19−// and DELETE (204) before dispatch, so dispatch only sees create or forward.
20−// ---------------------------------------------------------------------------
21−
22−import { Data, Effect, Layer, Schedule } from "effect";
23−
24−import {
25− McpSessionStore,
26− type McpDispatchInput,
27− type McpDispatchResult,
28−} from "@executor-js/host-mcp";
29−
30−import {
31− currentPropagationHeaders,
32− readElicitationMode,
33− withMcpResponseHeaders,
34− withPropagationHeaders,
35− withVerifiedIdentityHeaders,
36− type VerifiedTokenHeaders,
37−} from "./do-headers";
38−import { peekAndAnnotate, type OnInternalJsonRpcError } from "./response-peek";
39−import type { McpSessionDOStub } from "./seams";
40−
41−export type { McpSessionDOStub, McpSessionInit } from "./seams";
42−
43−export interface DurableObjectStoreConfig {
44− /** Resolve the stub for an existing session id (the id IS the DO id). */
45− readonly getStub: (sessionId: string) => McpSessionDOStub;
46− /** Mint a fresh session DO stub (a new unique id) for a create. */
47− readonly newStub: () => McpSessionDOStub;
48− /** Observe a JSON-RPC -32603 the peeker surfaces (cloud: Sentry). */
49− readonly onInternalError?: OnInternalJsonRpcError;
50−}
51−
52−// ---------------------------------------------------------------------------
53−// Transient DO-relocation retry.
54−//
55−// Cloudflare may relocate a live Durable Object between machines; an in-flight
56−// stub call against the now-stale instance throws "cannot access storage
57−// because object has moved to a different machine". The runtime throws this
58−// BEFORE the object touched storage, so no work and no writes happened on that
59−// instance — a re-issued stub call resolves the DO's new location and succeeds.
60−// (Observed in prod 2026-06-15: a single `mcp.do.init` failure surfaced to the
61−// client as a -32603 "Internal server error" that a reconnect cleared.)
62−//
63−// We retry ONLY this relocation class, and ONLY `init` — init takes a
64−// structured `meta` object with no request body, so replaying it is safe. The
65−// forward path (`handleRequest`) is deliberately excluded: its `Request` body is
66−// a one-shot stream that cannot be re-sent on a retry without buffering.
67−// ---------------------------------------------------------------------------
68−
69−const RELOCATED_DO_ERROR_FRAGMENT = "moved to a different machine";
70−
71−/** A rejected DO stub call, normalized at the boundary: the original thrown
72− * value in `cause` (so the final `Effect.die` re-raises it verbatim) plus a
73− * pre-computed `relocated` flag so the retry gate never re-inspects `unknown`. */
74−class DoStubError extends Data.TaggedError("DoStubError")<{
75− readonly cause: unknown;
76− readonly relocated: boolean;
77−}> {}
78−
79−/** Normalize a rejected DO stub call, flagging the Cloudflare relocation class —
80− * the only safe-to-retry error, and detectable only via the thrown message. */
81−const toDoStubError = (cause: unknown): DoStubError =>
82− new DoStubError({
83− cause,
84− // oxlint-disable-next-line executor/no-instanceof-error, executor/no-unknown-error-message -- boundary: Cloudflare signals DO relocation only via the thrown Error's message
85− relocated: cause instanceof Error && cause.message.includes(RELOCATED_DO_ERROR_FRAGMENT),
86− });
87−
88−/** Up to 4 attempts total (1 + 3 retries) at ~25/50/100ms jittered backoff. */
89−export const DO_RELOCATION_MAX_RETRIES = 3;
90−const DO_RELOCATION_RETRY_SCHEDULE = Schedule.jittered(Schedule.exponential("25 millis"));
91−
92−/**
93− * Run a replayable DO stub call under one `withSpan`, retrying the DO-relocation
94− * error a bounded number of times. A call that still fails after retries (or any
95− * non-relocation rejection) becomes an unrecoverable defect — exactly the prior
96− * `Effect.promise` semantics, which the envelope's top-level `catchCause` renders
97− * as a -32603 and reports to the error reporter (cloud: Sentry).
98− */
99−const withDoRelocationRetry = <A>(
100− spanName: string,
101− attributes: Record<string, string | number | boolean>,
102− call: () => Promise<A>,
103−): Effect.Effect<A> =>
104− Effect.suspend(() => {
105− let attempts = 0;
106− return Effect.tryPromise({
107− try: () => {
108− attempts += 1;
109− return call();
110− },
111− catch: toDoStubError,
112− }).pipe(
113− // Gate retries on BOTH the relocation class and a hard attempt budget. The
114− // `attempts` counter (bumped in `try`) bounds this directly rather than via
115− // `retry`'s `times`, whose interaction with `while` is unreliable here.
116− Effect.retry({
117− schedule: DO_RELOCATION_RETRY_SCHEDULE,
118− while: (error) => error.relocated && attempts <= DO_RELOCATION_MAX_RETRIES,
119− }),
120− Effect.tap(() =>
121− attempts > 1
122− ? Effect.annotateCurrentSpan({ "mcp.do.relocation_retries": attempts - 1 })
123− : Effect.void,
124− ),
125− // A call that still fails (or any non-relocation rejection) re-raises the
126− // original thrown value as an unrecoverable defect — the prior
127− // `Effect.promise` semantics the envelope renders as a -32603.
128− // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: dispatch's contract is E=never; an exhausted DO RPC re-raises as the defect the envelope renders as -32603
129− Effect.catch((error) => Effect.die(error.cause)),
130− );
131− }).pipe(Effect.withSpan(spanName, { attributes }));
132−
133−/**
134− * Forward a request to an existing session DO. `peek` tees the body for
135− * telemetry on POST/DELETE; GET (SSE) streams through untouched. Returns the DO
136− * `Response` verbatim (incl. its 403 -32003 / 404 -32001 error bodies).
137− */
138−const forwardToExistingSession = (
139− config: DurableObjectStoreConfig,
140− request: Request,
141− sessionId: string,
142− peek: boolean,
143− token: VerifiedTokenHeaders,
144− resource: McpDispatchInput["resource"],
145−): Effect.Effect<Response> =>
146− Effect.gen(function* () {
147− const stub = config.getStub(sessionId);
148− const propagation = yield* currentPropagationHeaders(request);
149− const propagated = withPropagationHeaders(
150− withVerifiedIdentityHeaders(request, token, resource),
151− propagation,
152− );
153− const raw = yield* Effect.promise(() => stub.handleRequest(propagated)).pipe(
154− Effect.withSpan("mcp.do.handle_request", {
155− attributes: {
156− "mcp.request.method": request.method,
157− "mcp.request.session_id_present": true,
158− },
159− }),
160− );
161− const annotated = peek
162− ? yield* peekAndAnnotate(raw, { onInternalError: config.onInternalError })
163− : raw;
164− return withMcpResponseHeaders(annotated);
165− });
166−
167−/** Open a new session DO (POST, no session-id): init then handleRequest. */
168−const createSession = (
169− config: DurableObjectStoreConfig,
170− request: Request,
171− token: VerifiedTokenHeaders,
172− resource: McpDispatchInput["resource"],
173−): Effect.Effect<Response> =>
174− Effect.gen(function* () {
175− const stub = config.newStub();
176− const propagation = yield* currentPropagationHeaders(request);
177− yield* withDoRelocationRetry("mcp.do.init", { "mcp.request.session_id_present": false }, () =>
178− stub.init(
179− {
180− organizationId: token.organizationId,
181− userId: token.accountId,
182− resource,
183− elicitationMode: readElicitationMode(request),
184− // The public origin the client reached us at — lets the DO derive a web
185− // base URL with no static config (we read the real URL, not a spoofable
186− // forwarded host).
187− webOrigin: new URL(request.url).origin,
188− },
189− propagation,
190− ),
191− );
192− const propagated = withPropagationHeaders(
193− withVerifiedIdentityHeaders(request, token, resource),
194− propagation,
195− );
196− const raw = yield* Effect.promise(() => stub.handleRequest(propagated)).pipe(
197− Effect.withSpan("mcp.do.handle_request", {
198− attributes: {
199− "mcp.request.method": request.method,
200− "mcp.request.session_id_present": false,
201− },
202− }),
203− );
204− const annotated = yield* peekAndAnnotate(raw, { onInternalError: config.onInternalError });
205− return withMcpResponseHeaders(annotated);
206− });
207−
208−const clearExistingSession = (
209− config: DurableObjectStoreConfig,
210− sessionId: string,
211− request?: Request,
212−): Effect.Effect<void> =>
213− Effect.gen(function* () {
214− const stub = config.getStub(sessionId);
215− // Disposal carries the active request's trace context (tracestate/baggage)
216− // when the envelope forwards the inbound request (the Forbidden-with-session
217− // teardown); otherwise a synthetic request, with traceparent still linking
218− // the span via the active Effect span.
219− const propagation = yield* currentPropagationHeaders(
220− request ?? new Request("https://mcp.invalid/mcp"),
221− );
222− yield* Effect.promise(() => stub.clearSession(propagation)).pipe(
223− Effect.catchCause(() => Effect.void),
224− Effect.withSpan("mcp.do.clear_session", {
225− attributes: { "mcp.request.session_id_present": true },
226− }),
227− );
228− });
229−
230−/**
231− * Build the `McpSessionStore` seam over a host's DO namespace. Cloud and
232− * host-cloudflare each pass their `getStub`/`newStub` (over `env.MCP_SESSION`);
233− * the dispatch logic is identical.
234− */
235−export const makeDurableObjectMcpSessionStore = (
236− config: DurableObjectStoreConfig,
237−): Layer.Layer<McpSessionStore> =>
238− Layer.succeed(McpSessionStore)({
239− dispatch: ({
240− request,
241− principal,
242− resource,
243− sessionId,
244− }: McpDispatchInput): Effect.Effect<McpDispatchResult> => {
245− const token: VerifiedTokenHeaders = {
246− accountId: principal.accountId,
247− organizationId: principal.organizationId,
248− };
249− return sessionId
250− ? forwardToExistingSession(
251− config,
252− request,
253− sessionId,
254− request.method !== "GET",
255− token,
256− resource,
257− )
258− : createSession(config, request, token, resource);
259− },
260− dispose: (sessionId, request) => clearExistingSession(config, sessionId, request),
261− });
packages/hosts/cloudflare/src/mcp/worker-transport.ts+0−106
@@ -1,106 +0,0 @@
1−import { WorkerTransport, type WorkerTransportOptions } from "agents/mcp";
2−import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3−import { Data, Effect } from "effect";
4−
5−import { JsonRpcRequestIdQueue } from "./request-id-queue.js";
6−
7−export { JsonRpcRequestIdQueue, PREVIOUS_REQUEST_TIMEOUT_MS } from "./request-id-queue.js";
8−
9−export class McpWorkerTransportError extends Data.TaggedError("McpWorkerTransportError")<{
10− readonly cause: unknown;
11−}> {}
12−
13−export type McpWorkerTransport = Readonly<{
14− transport: WorkerTransport;
15− connect: (server: McpServer) => Effect.Effect<void, McpWorkerTransportError>;
16− handleRequest: (request: Request) => Effect.Effect<Response, McpWorkerTransportError>;
17− close: () => Effect.Effect<void>;
18−}>;
19−
20−type HandleRequestResult = {
21− readonly response: Response;
22− readonly replacedStandaloneSse: boolean;
23−};
24−
25−const closeExistingStandaloneSse = (transport: WorkerTransport): boolean => {
26− const streamId =
27− typeof Reflect.get(transport, "standaloneSseStreamId") === "string"
28− ? Reflect.get(transport, "standaloneSseStreamId")
29− : "_GET_stream";
30− const streamMapping = Reflect.get(transport, "streamMapping");
31− if (!(streamMapping instanceof Map)) return false;
32−
33− const stream = streamMapping.get(streamId);
34− if (!stream) return false;
35−
36− if (
37− typeof stream === "object" &&
38− stream !== null &&
39− typeof Reflect.get(stream, "cleanup") === "function"
40− ) {
41− Reflect.get(stream, "cleanup")();
42− }
43− streamMapping.delete(streamId);
44− return true;
45−};
46−
47−const isStandaloneSseGet = (request: Request): boolean =>
48− request.method === "GET" && (request.headers.get("accept") ?? "").includes("text/event-stream");
49−
50−export const makeMcpWorkerTransport = (
51− options: WorkerTransportOptions,
52−): Effect.Effect<McpWorkerTransport> =>
53− Effect.sync(() => {
54− const transport = new WorkerTransport(options);
55− const requestIdQueue = new JsonRpcRequestIdQueue();
56−
57− const use = <A>(name: string, fn: () => Promise<A>) =>
58− Effect.tryPromise({
59− try: fn,
60− catch: (cause) => new McpWorkerTransportError({ cause }),
61− }).pipe(Effect.withSpan(`mcp.worker_transport.${name}`));
62−
63− const handleWithStandaloneSseReplacement = async (
64− request: Request,
65− ): Promise<HandleRequestResult> => {
66− if (!isStandaloneSseGet(request)) {
67− return {
68− response: await transport.handleRequest(request),
69− replacedStandaloneSse: false,
70− };
71− }
72−
73− const initial = await transport.handleRequest(request);
74− if (initial.status !== 409) {
75− return { response: initial, replacedStandaloneSse: false };
76− }
77−
78− const replacedStandaloneSse = closeExistingStandaloneSse(transport);
79− return {
80− response: replacedStandaloneSse ? await transport.handleRequest(request) : initial,
81− replacedStandaloneSse,
82− };
83− };
84−
85− return {
86− transport,
87− connect: (server: McpServer) => use("connect", () => server.connect(transport)),
88− handleRequest: (request: Request) =>
89− Effect.gen(function* () {
90− const result = yield* use("handle_request", () =>
91− requestIdQueue.run(request, () => handleWithStandaloneSseReplacement(request)),
92− );
93− yield* Effect.annotateCurrentSpan({
94− "mcp.transport.replaced_standalone_sse": result.replacedStandaloneSse,
95− });
96− return result.response;
97− }),
98− close: () =>
99− Effect.ignore(
100− Effect.tryPromise({
101− try: () => transport.close(),
102− catch: (cause) => new McpWorkerTransportError({ cause }),
103− }),
104− ).pipe(Effect.withSpan("mcp.worker_transport.close")),
105− } satisfies McpWorkerTransport;
106− }).pipe(Effect.withSpan("mcp.worker_transport.make"));
packages/hosts/mcp/src/envelope.test.ts+43−0
@@ -20,6 +20,7 @@ import { · 19 unmodified lines
2020 McpErrorReporter,
2121 McpErrorReporterNoop,
2222 McpServingRoutes,
23+ McpDiscoveryRoutes,
2324 McpSessionStore,
2425 type McpResource,
2526 type McpDispatchResult,
@@ -195,3 +196,45 @@ it("dispatches toolkit MCP routes with the parsed toolkit resource", async () => · 169 unmodified lines
195196 slug: "deploy",
196197 });
197198 });
199+
200+describe("McpDiscoveryRoutes (discovery-only, no session store)", () => {
201+ // Builds with the auth seam ALONE — no McpSessionStore. This is the cloud
202+ // shape: the Agent bridge serves /mcp transport, the envelope only publishes
203+ // the provider's OAuth discovery docs. If this required McpSessionStore it
204+ // would not compile, so the build itself is part of the assertion.
205+ const discoveryHandler = (): ((request: Request) => Promise<Response>) =>
206+ HttpRouter.toWebHandler(
207+ McpDiscoveryRoutes.pipe(
208+ Layer.provide(AuthProviderLive),
209+ Layer.provideMerge(HttpServer.layerServices),
210+ ),
211+ ).handler;
212+
213+ it("serves the provider discovery document on GET", async () => {
214+ const handler = discoveryHandler();
215+ const response = await handler(new Request(`https://host.test${DISCOVERY_PATH}`));
216+ expect(response.status).toBe(200);
217+ expect((await response.json()) as { ok: boolean }).toEqual({ ok: true });
218+ });
219+
220+ it("answers an OPTIONS preflight on a discovery path with 204 + CORS", async () => {
221+ const handler = discoveryHandler();
222+ const response = await handler(
223+ new Request(`https://host.test${DISCOVERY_PATH}`, { method: "OPTIONS" }),
224+ );
225+ expect(response.status).toBe(204);
226+ expect(response.headers.get("access-control-allow-origin")).toBe("*");
227+ });
228+
229+ it("does NOT mount the /mcp transport route", async () => {
230+ const handler = discoveryHandler();
231+ const response = await handler(
232+ new Request("https://host.test/mcp", {
233+ method: "POST",
234+ headers: { authorization: "Bearer x", "content-type": "application/json" },
235+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list" }),
236+ }),
237+ );
238+ expect(response.status).toBe(404);
239+ });
240+});
packages/hosts/mcp/src/envelope.ts+24−0
@@ -286,3 +286,27 @@ export const McpServingRoutes = HttpRouter.use((router) => · 285 unmodified lines
286286 yield* router.add("*", TOOLKIT_MCP_PATH, toolkitMcpRoute);
287287 }),
288288 );
289+
290+/**
291+ * The discovery-only subset of {@link McpServingRoutes}: the provider-declared
292+ * `/.well-known/oauth-*` GET routes plus their CORS preflight, and NOTHING on
293+ * `/mcp`. Requires only the {@link McpAuthProvider} seam, not {@link McpSessionStore}.
294+ *
295+ * For a host whose `/mcp` transport is served outside this envelope (the
296+ * Cloudflare Agent bridge intercepts `/mcp` before the app handler) but that
297+ * still needs to publish its OAuth metadata docs. Such a host wires the `auth`
298+ * seam without a `sessions` seam.
299+ */
300+export const McpDiscoveryRoutes = HttpRouter.use((router) =>
301+ Effect.gen(function* () {
302+ const auth = yield* McpAuthProvider;
303+ for (const route of auth.discoveryRoutes) {
304+ yield* router.add("GET", route.path, discoveryRoute(route.handler));
305+ yield* router.add(
306+ "OPTIONS",
307+ route.path,
308+ Effect.sync(() => HttpServerResponse.raw(corsPreflightResponse())),
309+ );
310+ }
311+ }),
312+);
packages/hosts/mcp/src/index.ts+1−1
@@ -36,4 +36,4 @@ export { · 35 unmodified lines
3636 type McpResource,
3737 } from "./seams";
3838
39−export { McpServingRoutes, jsonRpcErrorBody } from "./envelope";
39+export { McpServingRoutes, McpDiscoveryRoutes, jsonRpcErrorBody } from "./envelope";
packages/hosts/mcp/src/tool-server.test.ts+49−1
@@ -38,6 +38,8 @@ const makeStubEngine = <E extends Cause.YieldableError = never>(overrides: { · 37 unmodified lines
3838 (() => Effect.succeed({ status: "completed", result: { result: "default" } })),
3939 resume: overrides.resume ?? (() => Effect.succeed(null)),
4040 getPausedExecution: () => Effect.succeed(null),
41+ pausedExecutionCount: () => Effect.succeed(0),
42+ hasPausedExecutions: () => Effect.succeed(false),
4143 getDescription: Effect.succeed(overrides.description ?? "test executor"),
4244 });
4345
@@ -46,7 +48,10 @@ const withClient = async <E extends Cause.YieldableError>( · 2 unmodified lines
4648 engine: ExecutionEngine<E>,
4749 capabilities: ClientCapabilities,
4850 fn: (client: Client) => Promise<void>,
49− config?: Pick<ExecutorMcpServerConfig<E>, "debug" | "elicitationMode" | "browserApprovalStore">,
51+ config?: Pick<
52+ ExecutorMcpServerConfig<E>,
53+ "debug" | "elicitationMode" | "browserApprovalStore" | "pausedExecutionHooks"
54+ >,
5055 ) => {
5156 const mcpServer = await Effect.runPromise(createExecutorMcpServer({ engine, ...config }));
5257 const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
@@ -1119,6 +1124,49 @@ describe("MCP host server — client without elicitation (pause/resume)", () => · 1066 unmodified lines
11191124 );
11201125 });
11211126
1127+ it("reports pause and resume lifecycle boundaries", async () => {
1128+ const events: string[] = [];
1129+ const approval = FormElicitation.make({ message: "Approve", requestedSchema: {} });
1130+ const engine = makeStubEngine({
1131+ executeWithPause: () => Effect.succeed(makePausedResult("exec_1", approval)),
1132+ resume: () => Effect.succeed(makePausedResult("exec_2", approval)),
1133+ });
1134+
1135+ await withClient(
1136+ engine,
1137+ NO_CAPS,
1138+ async (client) => {
1139+ await client.callTool({
1140+ name: "execute",
1141+ arguments: { code: "pause" },
1142+ });
1143+ expect(events).toEqual(["pause:exec_1"]);
1144+
1145+ await client.callTool({
1146+ name: "resume",
1147+ arguments: { executionId: "exec_1", action: "accept", content: "{}" },
1148+ });
1149+ expect(events).toEqual([
1150+ "pause:exec_1",
1151+ "resume-start:exec_1",
1152+ "resume-settle:exec_1",
1153+ "pause:exec_2",
1154+ ]);
1155+ },
1156+ {
1157+ elicitationMode: { mode: "model" },
1158+ pausedExecutionHooks: {
1159+ onExecutionPaused: (executionId) =>
1160+ Effect.sync(() => events.push(`pause:${executionId}`)),
1161+ onResumeStarted: (executionId) =>
1162+ Effect.sync(() => events.push(`resume-start:${executionId}`)),
1163+ onResumeSettled: (executionId) =>
1164+ Effect.sync(() => events.push(`resume-settle:${executionId}`)),
1165+ },
1166+ },
1167+ );
1168+ });
1169+
11221170 it("resume tool passes parsed content to engine", async () => {
11231171 let receivedContent: Record<string, unknown> | undefined;
11241172 const engine = makeStubEngine({
packages/hosts/mcp/src/tool-server.ts+54−4
@@ -1,4 +1,4 @@
1−import { Effect, Match, Option, Schema } from "effect";
1+import { Duration, Effect, Match, Option, Schema } from "effect";
22 import * as Cause from "effect/Cause";
33 import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
44 import { ContentBlockSchema, type ContentBlock } from "@modelcontextprotocol/sdk/types.js";
@@ -91,6 +91,12 @@ type SharedMcpServerConfig = { · 86 unmodified lines
9191 readonly mode: "native";
9292 };
9393 readonly browserApprovalStore?: BrowserApprovalStore;
94+ /**
95+ * Host-owned lifecycle for paused executions. The MCP server reports pause
96+ * boundaries; the host decides whether that means a keepAlive lease, browser
97+ * wait, durable record, or no-op.
98+ */
99+ readonly pausedExecutionHooks?: PausedExecutionHooks;
94100 };
95101
96102 export type ExecutorMcpServerConfig<E extends Cause.YieldableError = Cause.YieldableError> =
@@ -104,6 +110,15 @@ export type BrowserApprovalStore = { · 7 unmodified lines
104110 readonly waitForResponse?: (executionId: string) => Effect.Effect<ResumeResponse | null>;
105111 };
106112
113+export const PAUSED_APPROVAL_TIMEOUT_MS = 4 * 60 * 1000;
114+const BROWSER_APPROVAL_WAIT_TIMEOUT_MS = PAUSED_APPROVAL_TIMEOUT_MS + 1000;
115+
116+export type PausedExecutionHooks = {
117+ readonly onExecutionPaused?: (executionId: string) => Effect.Effect<void>;
118+ readonly onResumeStarted?: (executionId: string) => Effect.Effect<void>;
119+ readonly onResumeSettled?: (executionId: string) => Effect.Effect<void>;
120+};
121+
107122 // ---------------------------------------------------------------------------
108123 // Elicitation bridge
109124 // ---------------------------------------------------------------------------
@@ -544,6 +559,17 @@ export const createExecutorMcpServer = <E extends Cause.YieldableError>( · 434 unmodified lines
544559 ({
545560 mode: "model",
546561 } as const);
562+ const onExecutionPaused = (executionId: string): Effect.Effect<void> =>
563+ config.pausedExecutionHooks?.onExecutionPaused?.(executionId) ?? Effect.void;
564+ const onResumeStarted = (executionId: string): Effect.Effect<void> =>
565+ config.pausedExecutionHooks?.onResumeStarted?.(executionId) ?? Effect.void;
566+ const onResumeSettled = (executionId: string): Effect.Effect<void> =>
567+ config.pausedExecutionHooks?.onResumeSettled?.(executionId) ?? Effect.void;
568+ const resumeWithLifecycle = (executionId: string, response: ResumeResponse) =>
569+ Effect.gen(function* () {
570+ yield* onResumeStarted(executionId);
571+ return yield* engine.resume(executionId, response);
572+ }).pipe(Effect.ensuring(onResumeSettled(executionId)));
547573
548574 const resolveParentSpan = (): Tracer.AnySpan | undefined => {
549575 const ps = config.parentSpan;
@@ -594,6 +620,14 @@ export const createExecutorMcpServer = <E extends Cause.YieldableError>( · 44 unmodified lines
594620 ? pausedInteractionKind(outcome.execution.elicitationContext.request)
595621 : undefined,
596622 });
623+ if (outcome.status === "paused") {
624+ yield* Effect.annotateCurrentSpan({
625+ "mcp.execute.paused": true,
626+ "mcp.execute.paused_execution_id": outcome.execution.id,
627+ "mcp.execute.pause_source": "execute",
628+ });
629+ yield* onExecutionPaused(outcome.execution.id);
630+ }
597631 return outcome.status === "completed"
598632 ? toMcpResult(outcome.result)
599633 : elicitationMode.mode === "browser"
@@ -620,7 +654,7 @@ export const createExecutorMcpServer = <E extends Cause.YieldableError>( · 20 unmodified lines
620654 hasContent: content !== undefined,
621655 clientCapabilities: server.server.getClientCapabilities() ?? null,
622656 });
623− const outcome = yield* engine.resume(executionId, { action, content });
657+ const outcome = yield* resumeWithLifecycle(executionId, { action, content });
624658 if (!outcome) {
625659 debugLog("resume.missing_execution", { executionId });
626660 return missingExecutionResult(executionId);
@@ -634,6 +668,14 @@ export const createExecutorMcpServer = <E extends Cause.YieldableError>( · 7 unmodified lines
634668 ? pausedInteractionKind(outcome.execution.elicitationContext.request)
635669 : undefined,
636670 });
671+ if (outcome.status === "paused") {
672+ yield* Effect.annotateCurrentSpan({
673+ "mcp.execute.paused": true,
674+ "mcp.execute.paused_execution_id": outcome.execution.id,
675+ "mcp.execute.pause_source": "resume",
676+ });
677+ yield* onExecutionPaused(outcome.execution.id);
678+ }
637679 return outcome.status === "completed"
638680 ? toMcpResult(outcome.result)
639681 : toMcpPausedResult(formatPausedExecution(outcome.execution));
@@ -682,7 +724,7 @@ export const createExecutorMcpServer = <E extends Cause.YieldableError>( · 42 unmodified lines
682724
683725 return waitForResponse(executionId).pipe(
684726 Effect.timeoutOrElse({
685− duration: "10 minutes",
727+ duration: Duration.millis(BROWSER_APPROVAL_WAIT_TIMEOUT_MS),
686728 orElse: () => Effect.succeed(null),
687729 }),
688730 );
@@ -693,10 +735,18 @@ export const createExecutorMcpServer = <E extends Cause.YieldableError>( · 4 unmodified lines
693735 const response = yield* waitForBrowserApprovalResponse(executionId);
694736 if (!response) return yield* requireUserResumeApproval(executionId);
695737
696− const outcome = yield* engine.resume(executionId, response);
738+ const outcome = yield* resumeWithLifecycle(executionId, response);
697739 if (!outcome) {
698740 return missingExecutionResult(executionId);
699741 }
742+ if (outcome.status === "paused") {
743+ yield* Effect.annotateCurrentSpan({
744+ "mcp.execute.paused": true,
745+ "mcp.execute.paused_execution_id": outcome.execution.id,
746+ "mcp.execute.pause_source": "browser_resume",
747+ });
748+ yield* onExecutionPaused(outcome.execution.id);
749+ }
700750 return outcome.status === "completed"
701751 ? toMcpResult(outcome.result)
702752 : yield* requireUserResumeApproval(outcome.execution.id);
packages/plugins/openapi/src/sdk/backing.ts+6−7
@@ -651,15 +651,14 @@ export const resolveOpenApiBackedAnnotations = (input: { · 650 unmodified lines
651651 readonly toolRows: readonly { readonly name: string }[];
652652 }) =>
653653 Effect.gen(function* () {
654− const ops = yield* input.ctx.storage.listOperations(String(input.integration));
655− const byName = new Map<string, OperationBinding>();
656− for (const op of ops) byName.set(op.toolName, op.binding);
657654 const out: Record<string, ReturnType<typeof annotationsForOperation>> = {};
658655 for (const row of input.toolRows) {
659− const binding = byName.get(row.name);
660− if (binding) {
661− out[row.name] = annotationsForOperation(binding.method, binding.pathTemplate);
662− }
656+ const operation = yield* input.ctx.storage.getOperation(input.integration, row.name);
657+ if (!operation) continue;
658+ out[row.name] = annotationsForOperation(
659+ operation.binding.method,
660+ operation.binding.pathTemplate,
661+ );
663662 }
664663 return out;
665664 });