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
67 "@tanstack/react-router": "catalog:",67 "@tanstack/react-router": "catalog:",
68 "@tanstack/react-start": "catalog:",68 "@tanstack/react-start": "catalog:",
69 "@workos-inc/node": "^8.11.1",69 "@workos-inc/node": "^8.11.1",
70 "agents": "^0.10.0",
71 "autumn-js": "^1.2.8",70 "autumn-js": "^1.2.8",
72 "drizzle-orm": "catalog:",71 "drizzle-orm": "catalog:",
73 "effect": "catalog:",72 "effect": "catalog:",
apps/cloud/src/api/protected.test.ts+2−0
@@ -17,6 +17,8 @@ const makeBaseEngine = (): ExecutionEngine => · 16 unmodified lines
17 result: { result: "ok", logs: [] },17 result: { result: "ok", logs: [] },
18 }),18 }),
19 getPausedExecution: () => Effect.succeed(null),19 getPausedExecution: () => Effect.succeed(null),
20 pausedExecutionCount: () => Effect.succeed(0),
21 hasPausedExecutions: () => Effect.succeed(false),
20 getDescription: Effect.succeed("desc"),22 getDescription: Effect.succeed("desc"),
21 }) as ExecutionEngine;23 }) as ExecutionEngine;
22 24
apps/cloud/src/app.ts+6−3
@@ -12,7 +12,7 @@ import { workosAccountMiddleware } from "./account/account-api"; · 11 unmodified lines
12import { ApiKeyService } from "./auth/api-keys";12import { ApiKeyService } from "./auth/api-keys";
13import { cloudIdentityFailureStrategy, workosIdentityLayer } from "./auth/workos-auth-provider";13import { cloudIdentityFailureStrategy, workosIdentityLayer } from "./auth/workos-auth-provider";
14import { DbService } from "./db/db";14import { DbService } from "./db/db";
15import { cloudMcpAuth, cloudMcpReporter, cloudMcpSessions } from "./mcp";15import { cloudMcpAuth } from "./mcp";
16import { McpSessionDO } from "./mcp/session-durable-object";16import { McpSessionDO } from "./mcp/session-durable-object";
17import { ErrorCaptureLive } from "./observability";17import { ErrorCaptureLive } from "./observability";
18import { AutumnService } from "./extensions/billing/service";18import { AutumnService } from "./extensions/billing/service";
@@ -89,10 +89,13 @@ const { appLayer, toWebHandler, mcpExport } = ExecutorApp.make({ · 70 unmodified lines
89 // Billing-as-extension #1: the usage-metering decorator (reads AutumnService).89 // Billing-as-extension #1: the usage-metering decorator (reads AutumnService).
90 decorator: CloudMeteringEngineDecorator,90 decorator: CloudMeteringEngineDecorator,
91 },91 },
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`).
92 mcp: {97 mcp: {
93 auth: cloudMcpAuth,98 auth: cloudMcpAuth,
94 sessions: cloudMcpSessions,
95 reporter: cloudMcpReporter,
96 },99 },
97 plugins: { provider: CloudPluginsProvider, config: CloudHostConfig },100 plugins: { provider: CloudPluginsProvider, config: CloudHostConfig },
98 errorCapture: ErrorCaptureLive,101 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
19 // resume doesn't count as usage19 // resume doesn't count as usage
20 resume: (executionId, response) => engine.resume(executionId, response),20 resume: (executionId, response) => engine.resume(executionId, response),
21 getPausedExecution: (executionId) => engine.getPausedExecution(executionId),21 getPausedExecution: (executionId) => engine.getPausedExecution(executionId),
22 pausedExecutionCount: () => engine.pausedExecutionCount(),
23 hasPausedExecutions: () => engine.hasPausedExecutions(),
22 getDescription: engine.getDescription,24 getDescription: engine.getDescription,
23});25});
apps/cloud/src/mcp/agent-handler.ts+163−0
@@ -0,0 +1,163 @@
1import { Effect, Predicate } from "effect";
2
3import {
4 McpAuthProvider,
5 jsonRpcErrorBody,
6 defaultMcpResource,
7 type AuthOutcome,
8 type McpResource,
9} from "@executor-js/host-mcp";
10import {
11 currentPropagationHeaders,
12 readElicitationMode,
13 withVerifiedIdentityHeaders,
14} from "@executor-js/cloudflare/mcp/do-headers";
15import type { McpSessionProps } from "@executor-js/cloudflare/mcp/agent-durable-object";
16
17import { cloudMcpAuth } from "./auth-provider";
18import { McpSessionDO } from "./session-durable-object";
19
20interface 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
28const 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
40const 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
50const 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
69const 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
75const 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.
86const 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
94const 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
113export 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 @@
1// ---------------------------------------------------------------------------1// ---------------------------------------------------------------------------
2// Cloud MCP — the three provider seams behind the shared host-mcp envelope,2// Cloud MCP — the auth seam behind the shared host-mcp envelope, slotted into
3// named to match the app composition root's `mcp: { auth, sessions, reporter }`:3// the app composition root's `mcp: { auth }`:
4//4//
5// - auth -> cloudMcpAuth (WorkOS JWT + API-key + org-liveness + the5// - auth -> cloudMcpAuth (WorkOS JWT + API-key + org-liveness + the two OAuth
6// two OAuth discovery docs)6// discovery docs)
7// - sessions -> cloudMcpSessions (the Durable-Object session dispatch)
8// - reporter -> cloudMcpReporter (forwards request-orchestration defects to
9// Sentry + the dev console)
10//7//
11// These three are what `app.ts`'s `ExecutorApp.make` slots into its `mcp`8// `server.ts` intercepts `/mcp` transport for the hibernatable Agent bridge, so
12// providers; the unified app handler serves /mcp from the app layer (like9// the app envelope mounts only cloud's OAuth discovery docs (no `sessions` or
13// self-host), so start.ts no longer hand-mounts MCP. The MCP-path predicate +10// `reporter` seam). The MCP-path predicate lives in `./mount` (`classifyMcpPath`
14// test-worker envelope builder live in `./mount` (`classifyMcpPath` /11// / `prepareMcpOrgScope`), imported directly there. The MCP session Durable
15// `makeMcpWebHandler`), imported directly there. The MCP session Durable Object12// Object class itself stays a platform-side export (server.ts) and imports its
16// class itself stays a platform-side export (server.ts) and imports its
17// siblings directly, NOT this barrel, to keep the DO bundle react-start-free.13// siblings directly, NOT this barrel, to keep the DO bundle react-start-free.
18// ---------------------------------------------------------------------------14// ---------------------------------------------------------------------------
19 15
@@ -21,5 +17,3 @@ · 1 unmodified line
21// its `McpAuth`/`McpOrganizationAuth` seams provided internally), shaped as the17// its `McpAuth`/`McpOrganizationAuth` seams provided internally), shaped as the
22// `Layer<McpAuthProvider, never, IdentityProvider>` `ExecutorApp.make` expects.18// `Layer<McpAuthProvider, never, IdentityProvider>` `ExecutorApp.make` expects.
23export { cloudMcpAuth } from "./auth-provider";19export { cloudMcpAuth } from "./auth-provider";
24export { cloudMcpSessionStoreLayer as cloudMcpSessions } from "./session-store";
25export { cloudMcpReporter } from "./reporter";
apps/cloud/src/mcp/mount.ts+14−76
@@ -1,43 +1,20 @@
1// ---------------------------------------------------------------------------1// ---------------------------------------------------------------------------
2// Cloud MCP front — test-worker helpers for the shared, provider-neutral2// Cloud MCP front — the MCP-path predicate + org-scope rewrite shared by
3// host-mcp serving envelope (@executor-js/host-mcp) behind cloud's two seams.3// `server.ts`'s request dispatch.
4// ---------------------------------------------------------------------------4// ---------------------------------------------------------------------------
5//5//
6// PRODUCTION serves /mcp through `app.ts`'s unified `ExecutorApp.make` handler6// PRODUCTION serves /mcp through `server.ts`'s hibernatable Agent bridge.
7// (the same `McpServingRoutes` envelope provided `cloudMcpAuth` +7// Discovery docs flow through `app.ts`'s unified `ExecutorApp.make` handler
8// `cloudMcpSessions`), dispatched by start.ts alongside /api. This module is the8// (the `auth` seam's discovery routes). This module exposes:
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`).
14// - `classifyMcpPath` — the "is this an MCP path?" predicate (`/mcp` + the9// - `classifyMcpPath` — the "is this an MCP path?" predicate (`/mcp` + the
15// two discovery docs) that start.ts's dispatch and the test worker share.10// two discovery docs, plus their org-scoped variants).
16//11// - `prepareMcpOrgScope` — rewrite an org-scoped MCP request to the bare path
17// Cloud's two envelope seams:12// the handler routes, carrying the URL-pinned org in an internal header.
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.
25// ---------------------------------------------------------------------------13// ---------------------------------------------------------------------------
26 14
27import { HttpRouter, HttpServer } from "effect/unstable/http";
28import { Layer } from "effect";
29
30import { isValidOrgSlug } from "@executor-js/api";15import { isValidOrgSlug } from "@executor-js/api";
31import { McpServingRoutes } from "@executor-js/host-mcp";
32 16
33import {17import { MCP_ORGANIZATION_HEADER, PROTECTED_RESOURCE_METADATA_PATH } from "./auth";
34 McpAuth,
35 McpOrganizationAuth,
36 MCP_ORGANIZATION_HEADER,
37 PROTECTED_RESOURCE_METADATA_PATH,
38} from "./auth";
39import { cloudMcpReporter } from "./reporter";
40import { cloudMcpSessionStoreLayer } from "./session-store";
41 18
42const MCP_PATH = "/mcp";19const MCP_PATH = "/mcp";
43const AUTHORIZATION_SERVER_METADATA_PATH = "/.well-known/oauth-authorization-server";20const AUTHORIZATION_SERVER_METADATA_PATH = "/.well-known/oauth-authorization-server";
@@ -152,47 +129,8 @@ export const prepareMcpOrgScope = (request: Request): Request => { · 108 unmodified lines
152 return rewritten;129 return rewritten;
153};130};
154 131
155/**132// Production no longer mounts the /mcp transport here. `server.ts` intercepts MCP
156 * Build the envelope web handler from the shared `McpServingRoutes` Layer,133// transport requests for the hibernatable Agent bridge, while `ExecutorApp.make`
157 * provided cloud's two seams. Mirrors the self-host mount (apps/host-selfhost134// serves the OAuth discovery docs through the `auth` seam's discovery routes.
158 * api.ts): `HttpRouter.provideRequest` clears the route handlers' per-request135// `classifyMcpPath` + `prepareMcpOrgScope` remain because `server.ts`'s request
159 * seam requirements, the build-time `Layer.provide(McpAuthProviderLive)`136// dispatch uses them to recognize and normalize MCP paths.
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 */
171export 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.
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
11import { Cause, Effect, Layer } from "effect";
12
13import { McpErrorReporter } from "@executor-js/host-mcp";
14
15import { captureCause } from "../observability";
16
17export 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 @@
1// ---------------------------------------------------------------------------1// ---------------------------------------------------------------------------
2// Cloud MCP Session Durable Object — the cloud binding of the shared2// Cloud MCP Session Durable Object — the cloud binding of the shared
3// `McpSessionDOBase` (@executor-js/cloudflare). All session lifecycle (cold3// `McpAgentSessionDOBase` (@executor-js/cloudflare). Hibernatable transport
4// restore, the inactivity alarm, owner validation, transport upgrade, the4// serving, cold restore, the inactivity alarm, owner validation, browser
5// browser-approval store, the per-request span bridge) lives in the base; cloud5// approval storage, and the per-request span bridge live in the base. Cloud
6// supplies ONLY its injected dependencies:6// supplies ONLY its injected dependencies:
7// - openSessionDb → a long-lived postgres.js handle7// - openSessionDb → a long-lived postgres.js handle
8// - resolveSessionMeta → WorkOS/UserStore organization resolution8// - resolveSessionMeta → WorkOS/UserStore organization resolution
@@ -24,12 +24,12 @@ import postgres, { type Sql } from "postgres"; · 15 unmodified lines
24import { createExecutorMcpServer } from "@executor-js/host-mcp/tool-server";24import { createExecutorMcpServer } from "@executor-js/host-mcp/tool-server";
25import { buildResumeApprovalUrl } from "@executor-js/host-mcp/browser-approval";25import { buildResumeApprovalUrl } from "@executor-js/host-mcp/browser-approval";
26import {26import {
27 McpSessionDOBase,27 McpAgentSessionDOBase,
28 type BuiltMcpServer,28 type BuiltMcpServer,
29 type IncomingTraceHeaders,29 type IncomingTraceHeaders,
30 type McpSessionInit,30 type McpSessionInit,
31 type SessionMeta,31 type SessionMeta,
32} from "@executor-js/cloudflare/mcp/durable-object";32} from "@executor-js/cloudflare/mcp/agent-durable-object";
33import { buildExecuteDescription } from "@executor-js/execution";33import { buildExecuteDescription } from "@executor-js/execution";
34 34
35// The DO meters executions just like the HTTP `/api/*` plane: it builds its35// The DO meters executions just like the HTTP `/api/*` plane: it builds its
@@ -67,7 +67,7 @@ export type { · 31 unmodified lines
67 McpSessionResumeApprovalResult,67 McpSessionResumeApprovalResult,
68 McpSessionInit,68 McpSessionInit,
69 IncomingTraceHeaders,69 IncomingTraceHeaders,
70} from "@executor-js/cloudflare/mcp/durable-object";70} from "@executor-js/cloudflare/mcp/agent-durable-object";
71 71
72// ---------------------------------------------------------------------------72// ---------------------------------------------------------------------------
73// Cloud DB handle — one postgres.js client per session runtime73// Cloud DB handle — one postgres.js client per session runtime
@@ -157,7 +157,7 @@ const makeSessionServices = (dbHandle: CloudSessionDbHandle) => { · 83 unmodified lines
157// Durable Object157// Durable Object
158// ---------------------------------------------------------------------------158// ---------------------------------------------------------------------------
159 159
160export class McpSessionDO extends McpSessionDOBase<CloudSessionDbHandle> {160export class McpSessionDO extends McpAgentSessionDOBase<Env, CloudSessionDbHandle> {
161 protected override openSessionDb(): CloudSessionDbHandle {161 protected override openSessionDb(): CloudSessionDbHandle {
162 return makeDbHandle({162 return makeDbHandle({
163 idleTimeout: LONG_LIVED_DB_IDLE_TIMEOUT_SECONDS,163 idleTimeout: LONG_LIVED_DB_IDLE_TIMEOUT_SECONDS,
@@ -221,6 +221,7 @@ export class McpSessionDO extends McpSessionDOBase<CloudSessionDbHandle> { · 57 unmodified lines
221 parentSpan: () => self.currentParentSpan(),221 parentSpan: () => self.currentParentSpan(),
222 debug: env.EXECUTOR_MCP_DEBUG === "true",222 debug: env.EXECUTOR_MCP_DEBUG === "true",
223 browserApprovalStore: self.browserApprovalStore,223 browserApprovalStore: self.browserApprovalStore,
224 pausedExecutionHooks: self.pausedExecutionHooks,
224 elicitationMode:225 elicitationMode:
225 sessionElicitationMode === "browser"226 sessionElicitationMode === "browser"
226 ? {227 ? {
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
9import * as Sentry from "@sentry/cloudflare";
10import { env } from "cloudflare:workers";
11import { Data } from "effect";
12
13import {
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.
20class 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.
28const toSessionStub = (stub: unknown): McpSessionDOStub => stub as McpSessionDOStub;
29
30export 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 @@
1import { describe, expect, it } from "@effect/vitest";
2
3import {
4 JsonRpcRequestIdQueue,
5 PREVIOUS_REQUEST_TIMEOUT_MS,
6} from "@executor-js/cloudflare/mcp/request-id-queue";
7
8const 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
15describe("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
10import handler from "@tanstack/react-start/server-entry";10import handler from "@tanstack/react-start/server-entry";
11 11
12import { isAppOwnedPath } from "./app-paths";12import { isAppOwnedPath } from "./app-paths";
13import { makeCloudMcpAgentHandler } from "./mcp/agent-handler";
14import { classifyMcpPath, prepareMcpOrgScope } from "./mcp/mount";
13import { McpSessionDO as McpSessionDOBase } from "./mcp/session-durable-object";15import { McpSessionDO as McpSessionDOBase } from "./mcp/session-durable-object";
14import { browserTracesResponse } from "./observability/browser-traces";16import { browserTracesResponse } from "./observability/browser-traces";
15import { flushTracerProvider, installTracerProvider } from "./observability/telemetry";17import { flushTracerProvider, installTracerProvider } from "./observability/telemetry";
@@ -77,6 +79,7 @@ const fetchHandler = handler.fetch as ( · 61 unmodified lines
77) => Response | Promise<Response>;79) => Response | Promise<Response>;
78 80
79const tracer = trace.getTracer("executor-cloud-worker");81const tracer = trace.getTracer("executor-cloud-worker");
82const mcpAgentHandler = makeCloudMcpAgentHandler();
80 83
81const cloudflareHandler: ExportedHandler<Env> = {84const cloudflareHandler: ExportedHandler<Env> = {
82 fetch: async (request, env, ctx) => {85 fetch: async (request, env, ctx) => {
@@ -89,6 +92,18 @@ const cloudflareHandler: ExportedHandler<Env> = { · 6 unmodified lines
89 return fetchHandler(request, env, ctx);92 return fetchHandler(request, env, ctx);
90 }93 }
91 const url = new URL(request.url);94 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 }
92 // Effect-served paths bring their own http.server span (with traceparent107 // Effect-served paths bring their own http.server span (with traceparent
93 // join) — opening one here too would duplicate it. See the header note.108 // join) — opening one here too would duplicate it. See the header note.
94 if (isAppOwnedPath(url.pathname)) {109 if (isAppOwnedPath(url.pathname)) {
apps/host-cloudflare/src/app.ts+12−15
@@ -14,15 +14,16 @@ import { · 13 unmodified lines
14} from "./execution";14} from "./execution";
15import { ErrorCaptureLive } from "./observability";15import { ErrorCaptureLive } from "./observability";
16import { cloudflareAccountMiddleware } from "./account/account-provider";16import { cloudflareAccountMiddleware } from "./account/account-provider";
17import { makeCloudflareMcpSeams } from "./mcp";17import { makeCloudflareApprovalHandler } from "./mcp";
18import { makeCloudflareMcpAgentHandler } from "./mcp/agent-handler";
18import { preloadQuickJs } from "./quickjs";19import { preloadQuickJs } from "./quickjs";
19 20
20// ===========================================================================21// ===========================================================================
21// The Cloudflare host, as ONE `ExecutorApp.make` call — the 4th app alongside22// The Cloudflare host, as ONE `ExecutorApp.make` call: the 4th app alongside
22// cloud / self-host / local, differing only by the injected Layers.23// cloud / self-host / local, differing only by the injected Layers.
23//24//
24// The whole scenario in 60 seconds: Cloudflare Access is the identity (validate25// 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),
26// D1 is the SQLite store (same FumaDB assembly as self-host), QuickJS is the27// D1 is the SQLite store (same FumaDB assembly as self-host), QuickJS is the
27// in-process code substrate, no billing, single-tenant. `diff` against28// in-process code substrate, no billing, single-tenant. `diff` against
28// host-selfhost/src/app.ts is three injected Layers: identity, db, plugins/config.29// 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
37 const plugins = makeCloudflarePlugins(config.secretKey);38 const plugins = makeCloudflarePlugins(config.secretKey);
38 39
39 // Load the Workers-compatible (WASM-inlined) QuickJS variant before any40 // 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.
41 await preloadQuickJs();42 await preloadQuickJs();
42 43
43 // Open + idempotently bring up the D1 schema once (the long-lived handle the44 // Open and idempotently bring up the D1 schema once. This is the long-lived
44 // per-request scoped executor reads through the DbProvider seam).45 // handle the per-request scoped executor reads through the DbProvider seam.
45 const dbHandle = await createD1ExecutorDb(env.DB, env.BLOBS);46 const dbHandle = await createD1ExecutorDb(env.DB, env.BLOBS);
46 const identityLayer = cloudflareAccessIdentityLayer(config);47 const identityLayer = cloudflareAccessIdentityLayer(config);
47 // MCP runs through the `MCP_SESSION` Durable Object (cross-isolate sessions);48 const mcpAgentHandler = makeCloudflareMcpAgentHandler(config);
48 // each session DO opens its own D1 handle, so it takes `env`, not `dbHandle`.49 const approvalHandler = makeCloudflareApprovalHandler(config, env);
49 const mcp = makeCloudflareMcpSeams(config, env);
50 50
51 const { appLayer, toWebHandler } = ExecutorApp.make({51 const { appLayer, toWebHandler } = ExecutorApp.make({
52 plugins,52 plugins,
53 providers: {53 providers: {
54 identity: identityLayer,54 identity: identityLayer,
55 db: dbProviderLayer(Effect.succeed(dbHandle)),55 db: dbProviderLayer(Effect.succeed(dbHandle)),
56 engine: { codeExecutor: CloudflareCodeExecutorProvider }, // decorator defaults to no-op56 engine: { codeExecutor: CloudflareCodeExecutorProvider },
57 plugins: {57 plugins: {
58 provider: makeCloudflarePluginsProvider(config),58 provider: makeCloudflarePluginsProvider(config),
59 config: makeCloudflareHostConfig(config),59 config: makeCloudflareHostConfig(config),
@@ -63,21 +63,18 @@ export const makeCloudflareApp = async (env: CloudflareEnv) => { · 3 unmodified lines
63 // auth context; `me` reflects the Access principal. Members/keys are63 // auth context; `me` reflects the Access principal. Members/keys are
64 // Access-managed, so the rest of the surface is stubbed.64 // Access-managed, so the rest of the surface is stubbed.
65 account: cloudflareAccountMiddleware(config),65 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 },
69 },66 },
70 extensions: {67 extensions: {
71 routes: [68 routes: [
72 // Browser approval of paused MCP executions: the console resume page69 // Browser approval of paused MCP executions: the console resume page
73 // reads paused detail (GET) and records the decision (POST .../resume),70 // reads paused detail (GET) and records the decision (POST .../resume),
74 // Access-gated, routed to the owning session's Durable Object.71 // 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)),
76 ],73 ],
77 },74 },
78 config: { mountPrefix: "/api", failure: textFailureStrategy },75 config: { mountPrefix: "/api", failure: textFailureStrategy },
79 boot: identityLayer,76 boot: identityLayer,
80 });77 });
81 78
82 return { appLayer, toWebHandler };79 return { appLayer, toWebHandler, mcpAgentHandler };
83};80};
apps/host-cloudflare/src/mcp/agent-handler.ts+153−0
@@ -0,0 +1,153 @@
1import { Effect, Predicate } from "effect";
2
3import {
4 McpAuthProvider,
5 jsonRpcErrorBody,
6 defaultMcpResource,
7 type AuthOutcome,
8 type Principal,
9} from "@executor-js/host-mcp";
10import {
11 currentPropagationHeaders,
12 readElicitationMode,
13 withVerifiedIdentityHeaders,
14} from "@executor-js/cloudflare/mcp/do-headers";
15import type { McpSessionProps } from "@executor-js/cloudflare/mcp/agent-durable-object";
16
17import type { CloudflareConfig, CloudflareEnv } from "../config";
18import { cloudflareAccessMcpAuth } from "./auth";
19import { McpSessionDO } from "./session-durable-object";
20
21interface 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
29const 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
41const 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
51const 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
70const 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
76const 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
83const 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
104export 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 @@
1import { Effect, type Layer } from "effect";1import { Effect } from "effect";
2 2
3import type { McpAuthProvider, McpErrorReporter, McpSessionStore } from "@executor-js/host-mcp";
4import { decodeResumeResponse } from "@executor-js/host-mcp/browser-approval";3import { decodeResumeResponse } from "@executor-js/host-mcp/browser-approval";
5import type {4import type {
6 McpApprovalOwner,5 McpApprovalOwner,
7 McpSessionApprovalResult,6 McpSessionApprovalResult,
8 McpSessionResumeApprovalResult,7 McpSessionResumeApprovalResult,
9} from "@executor-js/cloudflare/mcp/durable-object";8} from "@executor-js/cloudflare/mcp/agent-durable-object";
10import type { ResumeResponse } from "@executor-js/execution";9import type { ResumeResponse } from "@executor-js/execution";
11 10
12import type { CloudflareConfig, CloudflareEnv } from "../config";11import type { CloudflareConfig, CloudflareEnv } from "../config";
13import { makeAccessVerifier } from "../auth/cloudflare-access";12import { makeAccessVerifier } from "../auth/cloudflare-access";
14import { cloudflareAccessMcpAuth } from "./auth";
15import { cloudflareMcpReporter, makeCloudflareMcpSessionStore } from "./session-store";
16 13
17export { cloudflareAccessMcpAuth } from "./auth";14export { cloudflareAccessMcpAuth } from "./auth";
18export { cloudflareMcpReporter, makeCloudflareMcpSessionStore } from "./session-store";
19export { McpSessionDO } from "./session-durable-object";15export { McpSessionDO } from "./session-durable-object";
20 16
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
36export 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).
56const toApprovalStub = (stub: unknown): McpApprovalStub => stub as McpApprovalStub;17const toApprovalStub = (stub: unknown): McpApprovalStub => stub as McpApprovalStub;
57 18
58interface McpApprovalStub {19interface McpApprovalStub {
@@ -73,19 +34,13 @@ const RESUME_PATH = /^\/api\/mcp-sessions\/([^/?#]+)\/executions\/([^/?#]+)\/res · 14 unmodified lines
73const jsonResponse = (value: unknown, status: number): Response =>34const jsonResponse = (value: unknown, status: number): Response =>
74 new Response(JSON.stringify(value), { status, headers: { "content-type": "application/json" } });35 new Response(JSON.stringify(value), { status, headers: { "content-type": "application/json" } });
75 36
76/**37export const makeCloudflareApprovalHandler = (
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 */
82const makeCloudflareApprovalHandler = (
83 config: CloudflareConfig,38 config: CloudflareConfig,
84 env: CloudflareEnv,39 env: CloudflareEnv,
85): ((request: Request) => Promise<Response>) => {40): ((request: Request) => Promise<Response>) => {
86 const { verify } = makeAccessVerifier(config);41 const { verify } = makeAccessVerifier(config);
87 const stubFor = (sessionId: string): McpApprovalStub =>42 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}`)));
89 44
90 return async (request) => {45 return async (request) => {
91 const principal = await Effect.runPromise(verify(request));46 const principal = await Effect.runPromise(verify(request));
@@ -136,18 +91,3 @@ const makeCloudflareApprovalHandler = ( · 44 unmodified lines
136 return jsonResponse({ error: "Not found" }, 404);91 return jsonResponse({ error: "Not found" }, 404);
137 };92 };
138};93};
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 */
145export 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
4import { buildResumeApprovalUrl } from "@executor-js/host-mcp/browser-approval";4import { buildResumeApprovalUrl } from "@executor-js/host-mcp/browser-approval";
5import type { ExecutorDbHandle } from "@executor-js/api/server";5import type { ExecutorDbHandle } from "@executor-js/api/server";
6import {6import {
7 McpSessionDOBase,7 McpAgentSessionDOBase,
8 type BuiltMcpServer,8 type BuiltMcpServer,
9 type McpSessionInit,9 type McpSessionInit,
10 type SessionMeta,10 type SessionMeta,
11} from "@executor-js/cloudflare/mcp/durable-object";11} from "@executor-js/cloudflare/mcp/agent-durable-object";
12 12
13import { loadConfig, type CloudflareConfig, type CloudflareEnv } from "../config";13import { loadConfig, type CloudflareConfig, type CloudflareEnv } from "../config";
14import { createD1ExecutorDb } from "../db/d1";14import { createD1ExecutorDb } from "../db/d1";
@@ -17,7 +17,7 @@ import { preloadQuickJs } from "../quickjs"; · 2 unmodified lines
17 17
18// ---------------------------------------------------------------------------18// ---------------------------------------------------------------------------
19// Cloudflare (self-host) MCP Session Durable Object — the host-cloudflare19// Cloudflare (self-host) MCP Session Durable Object — the host-cloudflare
20// binding of the shared `McpSessionDOBase` (@executor-js/cloudflare). Identical20// binding of the shared `McpAgentSessionDOBase` (@executor-js/cloudflare). Identical
21// base to cloud; the ONLY differences are the injected dependencies:21// base to cloud; the ONLY differences are the injected dependencies:
22// - openSessionDb → a long-lived D1 `ExecutorDbHandle` (same FumaDB22// - openSessionDb → a long-lived D1 `ExecutorDbHandle` (same FumaDB
23// assembly the HTTP path uses), adapted to the base's23// assembly the HTTP path uses), adapted to the base's
@@ -35,14 +35,14 @@ import { preloadQuickJs } from "../quickjs"; · 11 unmodified lines
35// own lifecycle (the binding is the connection), so `end` is `close` — a no-op.35// own lifecycle (the binding is the connection), so `end` is `close` — a no-op.
36type CfSessionDbHandle = ExecutorDbHandle & { readonly end: () => Promise<void> };36type CfSessionDbHandle = ExecutorDbHandle & { readonly end: () => Promise<void> };
37 37
38export class McpSessionDO extends McpSessionDOBase<CfSessionDbHandle> {38export class McpSessionDO extends McpAgentSessionDOBase<CloudflareEnv, CfSessionDbHandle> {
39 private readonly cfEnv: CloudflareEnv;39 private readonly cfEnv: CloudflareEnv;
40 private readonly cfConfig: CloudflareConfig;40 private readonly cfConfig: CloudflareConfig;
41 41
42 // `ctx`'s type is taken from the base constructor so it tracks whichever42 constructor(
43 // `@cloudflare/workers-types` the shared package resolves (avoids a43 ctx: ConstructorParameters<typeof McpAgentSessionDOBase<CloudflareEnv, CfSessionDbHandle>>[0],
44 // cross-version `DurableObjectState` mismatch at the `super` call).44 env: CloudflareEnv,
45 constructor(ctx: ConstructorParameters<typeof McpSessionDOBase>[0], env: CloudflareEnv) {45 ) {
46 super(ctx, env);46 super(ctx, env);
47 this.cfEnv = env;47 this.cfEnv = env;
48 this.cfConfig = loadConfig(env);48 this.cfConfig = loadConfig(env);
@@ -90,6 +90,7 @@ export class McpSessionDO extends McpSessionDOBase<CfSessionDbHandle> { · 41 unmodified lines
90 const mcpServer = yield* createExecutorMcpServer({90 const mcpServer = yield* createExecutorMcpServer({
91 engine,91 engine,
92 browserApprovalStore: self.browserApprovalStore,92 browserApprovalStore: self.browserApprovalStore,
93 pausedExecutionHooks: self.pausedExecutionHooks,
93 elicitationMode:94 elicitationMode:
94 elicitationMode === "browser"95 elicitationMode === "browser"
95 ? {96 ? {
apps/host-cloudflare/src/mcp/session-store.ts+0−43
@@ -1,43 +0,0 @@
1import { Layer } from "effect";
2
3import { makeConsoleMcpErrorReporter } from "@executor-js/api/server";
4import type { McpErrorReporter, McpSessionStore } from "@executor-js/host-mcp";
5import {
6 makeDurableObjectMcpSessionStore,
7 type McpSessionDOStub,
8} from "@executor-js/cloudflare/mcp/session-store";
9
10import type { CloudflareEnv } from "../config";
11import { 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.
30const toSessionStub = (stub: unknown): McpSessionDOStub => stub as McpSessionDOStub;
31
32/** Build the DO-backed MCP session store over the host's `MCP_SESSION` namespace. */
33export 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`. */
42export 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
4import { fileURLToPath } from "node:url";4import { fileURLToPath } from "node:url";
5 5
6import { afterAll, beforeAll, describe, expect, it } from "@effect/vitest";6import { afterAll, beforeAll, describe, expect, it } from "@effect/vitest";
7import { Schema } from "effect";
7import { unstable_dev, type Unstable_DevWorker } from "wrangler";8import { unstable_dev, type Unstable_DevWorker } from "wrangler";
8 9
9// ---------------------------------------------------------------------------10// ---------------------------------------------------------------------------
@@ -29,6 +30,28 @@ const SPEC = JSON.stringify({ · 19 unmodified lines
29 },30 },
30});31});
31 32
33const decodeUnknownJson = Schema.decodeUnknownSync(Schema.fromJsonString(Schema.Unknown));
34
35interface JsonReadableResponse {
36 readonly headers: { get(name: string): string | null };
37 json(): Promise<unknown>;
38 text(): Promise<string>;
39}
40
41const 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
32describe("cloudflare host e2e (workerd/miniflare)", () => {55describe("cloudflare host e2e (workerd/miniflare)", () => {
33 let worker: Unstable_DevWorker;56 let worker: Unstable_DevWorker;
34 57
@@ -209,13 +232,55 @@ describe("cloudflare host e2e (workerd/miniflare)", () => { · 174 unmodified lines
209 method: "tools/list",232 method: "tools/list",
210 });233 });
211 expect(list.status).toBe(200);234 expect(list.status).toBe(200);
212 const listed = (await list.json()) as {235 const listed = await readMcpJson<{
213 result?: { tools?: ReadonlyArray<{ name: string }> };236 result?: { tools?: ReadonlyArray<{ name: string }> };
214 };237 }>(list);
215 const toolNames = listed.result?.tools?.map((t) => t.name) ?? [];238 const toolNames = listed.result?.tools?.map((t) => t.name) ?? [];
216 expect(toolNames).toContain("execute");239 expect(toolNames).toContain("execute");
217 }, 60_000);240 }, 60_000);
218 241
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
219 it("invokes the execute tool over MCP (initialize → tools/call → QuickJS)", async () => {284 it("invokes the execute tool over MCP (initialize → tools/call → QuickJS)", async () => {
220 const accept = "application/json, text/event-stream";285 const accept = "application/json, text/event-stream";
221 const rpc = (sessionId: string | null, body: unknown) =>286 const rpc = (sessionId: string | null, body: unknown) =>
@@ -255,9 +320,9 @@ describe("cloudflare host e2e (workerd/miniflare)", () => { · 33 unmodified lines
255 params: { name: "execute", arguments: { code: "export default 6 * 7" } },320 params: { name: "execute", arguments: { code: "export default 6 * 7" } },
256 });321 });
257 expect(call.status).toBe(200);322 expect(call.status).toBe(200);
258 const result = (await call.json()) as {323 const result = await readMcpJson<{
259 result?: { structuredContent?: { result?: number } };324 result?: { structuredContent?: { result?: number } };
260 };325 }>(call);
261 expect(result.result?.structuredContent?.result).toBe(42);326 expect(result.result?.structuredContent?.result).toBe(42);
262 }, 60_000);327 }, 60_000);
263});328});
apps/host-cloudflare/src/worker.ts+18−10
@@ -6,25 +6,33 @@ import type { CloudflareEnv } from "./config"; · 5 unmodified lines
6export { McpSessionDO } from "./mcp";6export { McpSessionDO } from "./mcp";
7 7
8// ---------------------------------------------------------------------------8// ---------------------------------------------------------------------------
9// The Worker fetch entry. `ExecutorApp.make`'s `toWebHandler()` produces a9// The Worker fetch entry. Most requests go to `ExecutorApp.make`'s Effect web
10// `(Request) => Promise<Response>` — exactly a Worker handler — so the entry is10// handler. `/mcp` stays at this edge boundary because `McpAgent.serve()` needs
11// thin: build the app ONCE per isolate (memoized; the build runs the D1 schema11// the Cloudflare `ExecutionContext` to pass authenticated session props into the
12// bring-up), then forward every request to its handler. `env` (the D1 binding +12// hibernatable Durable Object bridge.
13// Access vars) arrives with the request and is captured at build time.
14// ---------------------------------------------------------------------------13// ---------------------------------------------------------------------------
15 14
16let handlerPromise: Promise<(request: Request) => Promise<Response>> | null = null;15let handlerPromise: Promise<{
16 readonly app: (request: Request) => Promise<Response>;
17 readonly mcp: (request: Request, env: CloudflareEnv, ctx: ExecutionContext) => Promise<Response>;
18}> | null = null;
17 19
18const resolveHandler = (env: CloudflareEnv): Promise<(request: Request) => Promise<Response>> => {20const resolveHandler = (env: CloudflareEnv) => {
19 if (!handlerPromise) {21 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 }));
21 }26 }
22 return handlerPromise;27 return handlerPromise;
23};28};
24 29
25export default {30export default {
26 fetch: async (request: Request, env: CloudflareEnv): Promise<Response> => {31 fetch: async (request: Request, env: CloudflareEnv, ctx: ExecutionContext): Promise<Response> => {
27 const serve = await resolveHandler(env);32 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);
29 },37 },
30};38};
bun.lock+268−27
@@ -91,7 +91,6 @@ · 90 unmodified lines
91 "@tanstack/react-router": "catalog:",91 "@tanstack/react-router": "catalog:",
92 "@tanstack/react-start": "catalog:",92 "@tanstack/react-start": "catalog:",
93 "@workos-inc/node": "^8.11.1",93 "@workos-inc/node": "^8.11.1",
94 "agents": "^0.10.0",
95 "autumn-js": "^1.2.8",94 "autumn-js": "^1.2.8",
96 "drizzle-orm": "catalog:",95 "drizzle-orm": "catalog:",
97 "effect": "catalog:",96 "effect": "catalog:",
@@ -655,7 +654,7 @@ · 557 unmodified lines
655 "@executor-js/host-mcp": "workspace:*",654 "@executor-js/host-mcp": "workspace:*",
656 "@executor-js/sdk": "workspace:*",655 "@executor-js/sdk": "workspace:*",
657 "@modelcontextprotocol/sdk": "^1.29.0",656 "@modelcontextprotocol/sdk": "^1.29.0",
658 "agents": "^0.10.0",657 "agents": "^0.16.2",
659 "effect": "catalog:",658 "effect": "catalog:",
660 },659 },
661 "devDependencies": {660 "devDependencies": {
@@ -1286,8 +1285,6 @@ · 624 unmodified lines
1286 1285
1287 "@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=="],1286 "@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=="],
1288 1287
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
1291 "@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=="],1288 "@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=="],
1292 1289
1293 "@astrojs/compiler": ["@astrojs/compiler@3.0.1", "", {}, "sha512-z97oYbdebO5aoWzuJ/8q5hLK232+17KcLZ7cJ8BCWk6+qNzVxn/gftC0KzMBUTD8WAaBkPpNSQK6PXLnNrZ0CA=="],1290 "@astrojs/compiler": ["@astrojs/compiler@3.0.1", "", {}, "sha512-z97oYbdebO5aoWzuJ/8q5hLK232+17KcLZ7cJ8BCWk6+qNzVxn/gftC0KzMBUTD8WAaBkPpNSQK6PXLnNrZ0CA=="],
@@ -1392,27 +1389,27 @@ · 98 unmodified lines
1392 1389
1393 "@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=="],1390 "@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=="],
1394 1391
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=="],
1396 1393
1397 "@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=="],1394 "@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=="],
1398 1395
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=="],
1400 1397
1401 "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],1398 "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
1402 1399
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=="],
1404 1401
1405 "@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=="],1402 "@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=="],
1406 1403
1407 "@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=="],1404 "@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=="],
1408 1405
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=="],
1410 1407
1411 "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="],1408 "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="],
1412 1409
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=="],
1414 1411
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=="],
1416 1413
1417 "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],1414 "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
1418 1415
@@ -1422,11 +1419,11 @@ · 3 unmodified lines
1422 1419
1423 "@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="],1420 "@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="],
1424 1421
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=="],
1426 1423
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=="],
1428 1425
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=="],
1430 1427
1431 "@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=="],1428 "@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=="],
1432 1429
@@ -1472,6 +1469,8 @@ · 39 unmodified lines
1472 1469
1473 "@better-fetch/fetch": ["@better-fetch/fetch@1.1.21", "", {}, "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A=="],1470 "@better-fetch/fetch": ["@better-fetch/fetch@1.1.21", "", {}, "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A=="],
1474 1471
1472 "@borewit/text-codec": ["@borewit/text-codec@0.2.2", "", {}, "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ=="],
1473
1475 "@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="],1474 "@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="],
1476 1475
1477 "@capsizecss/unpack": ["@capsizecss/unpack@4.0.0", "", { "dependencies": { "fontkitten": "^1.0.0" } }, "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA=="],1476 "@capsizecss/unpack": ["@capsizecss/unpack@4.0.0", "", { "dependencies": { "fontkitten": "^1.0.0" } }, "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA=="],
@@ -1534,6 +1533,8 @@ · 56 unmodified lines
1534 1533
1535 "@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=="],1534 "@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=="],
1536 1535
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
1537 "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.5.0", "", {}, "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg=="],1538 "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.5.0", "", {}, "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg=="],
1538 1539
1539 "@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=="],1540 "@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
2022 2023
2023 "@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="],2024 "@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="],
2024 2025
2025 "@jsdevtools/ono": ["@jsdevtools/ono@7.1.3", "", {}, "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="],
2026
2027 "@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=="],2026 "@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=="],
2028 2027
2029 "@kitlangton/terminal-control-darwin-arm64": ["@kitlangton/terminal-control-darwin-arm64@0.3.0", "", { "os": "darwin", "cpu": "arm64", "bin": { "termctrl": "bin/termctrl" } }, "sha512-HydPv5if5pAjwHNbA7TnZAJYKfq9AiZITaJuFyF1HsUMedzin3qzv9WRVBqIiGsQ/FiR1i1kaGhyLzaVpZR35w=="],2028 "@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
2092 2091
2093 "@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=="],2092 "@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=="],
2094 2093
2094 "@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="],
2095
2095 "@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=="],2096 "@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=="],
2096 2097
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
2097 "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="],2100 "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="],
2098 2101
2099 "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="],2102 "@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
2140 2143
2141 "@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="],2144 "@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="],
2142 2145
2146 "@nodable/entities": ["@nodable/entities@2.2.0", "", {}, "sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg=="],
2147
2143 "@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=="],2148 "@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=="],
2144 2149
2145 "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],2150 "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
@@ -2984,6 +2989,8 @@ · 838 unmodified lines
2984 2989
2985 "@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.162.0", "", {}, "sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA=="],2990 "@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.162.0", "", {}, "sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA=="],
2986 2991
2992 "@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="],
2993
2987 "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],2994 "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
2988 2995
2989 "@turbo/darwin-64": ["@turbo/darwin-64@2.9.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-X/56SnVXIQZBLKwniGTwEQTGmtE5brSACnKMBWpY3YafuxVYefrC2acamfjgxP7BG5w3I+6jf0UrLoSzgPcSJg=="],2996 "@turbo/darwin-64": ["@turbo/darwin-64@2.9.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-X/56SnVXIQZBLKwniGTwEQTGmtE5brSACnKMBWpY3YafuxVYefrC2acamfjgxP7BG5w3I+6jf0UrLoSzgPcSJg=="],
@@ -3104,8 +3111,6 @@ · 114 unmodified lines
3104 3111
3105 "@types/keyv": ["@types/keyv@3.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg=="],3112 "@types/keyv": ["@types/keyv@3.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg=="],
3106 3113
3107 "@types/lodash": ["@types/lodash@4.17.24", "", {}, "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ=="],
3108
3109 "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],3114 "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
3110 3115
3111 "@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="],3116 "@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="],
@@ -3232,7 +3237,7 @@ · 120 unmodified lines
3232 3237
3233 "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],3238 "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
3234 3239
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=="],
3236 3241
3237 "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=="],3242 "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=="],
3238 3243
@@ -3264,6 +3269,8 @@ · 25 unmodified lines
3264 3269
3265 "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],3270 "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
3266 3271
3272 "anynum": ["anynum@1.0.1", "", {}, "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A=="],
3273
3267 "app-builder-bin": ["app-builder-bin@5.0.0-alpha.12", "", {}, "sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w=="],3274 "app-builder-bin": ["app-builder-bin@5.0.0-alpha.12", "", {}, "sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w=="],
3268 3275
3269 "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=="],3276 "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
3944 3951
3945 "fast-wrap-ansi": ["fast-wrap-ansi@0.2.0", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w=="],3952 "fast-wrap-ansi": ["fast-wrap-ansi@0.2.0", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w=="],
3946 3953
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
3947 "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],3958 "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
3948 3959
3949 "fd-package-json": ["fd-package-json@2.0.0", "", { "dependencies": { "walk-up-path": "^4.0.0" } }, "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ=="],3960 "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
3960 3971
3961 "file-selector": ["file-selector@0.5.0", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-s8KNnmIDTBoD0p9uJ9uD0XY38SCeBOtj0UMXyQSLg1Ypfrfj8+dAvwsLjYQkQ2GjhVtp2HrnF5cJzMhBjfD8HA=="],3972 "file-selector": ["file-selector@0.5.0", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-s8KNnmIDTBoD0p9uJ9uD0XY38SCeBOtj0UMXyQSLg1Ypfrfj8+dAvwsLjYQkQ2GjhVtp2HrnF5cJzMhBjfD8HA=="],
3962 3973
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=="],
3964 3975
3965 "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],3976 "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
3966 3977
@@ -4296,6 +4307,8 @@ · 329 unmodified lines
4296 4307
4297 "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],4308 "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
4298 4309
4310 "is-unsafe": ["is-unsafe@1.0.1", "", {}, "sha512-CLK2+VdgERgD96EYm5lUQssZYlRg2tkZnbsxZoacmSiRxiFJ4Nk4SzjCl+Ur+v3kXIY9dTIdb3IH22y1mZ56LA=="],
4311
4299 "is-wayland": ["is-wayland@0.1.0", "", {}, "sha512-QkbMsWkIfkrzOPxenwye0h56iAXirZYHG9eHVPb22fO9y+wPbaX/CHacOWBa/I++4ohTcByimhM1/nyCsH8KNA=="],4312 "is-wayland": ["is-wayland@0.1.0", "", {}, "sha512-QkbMsWkIfkrzOPxenwye0h56iAXirZYHG9eHVPb22fO9y+wPbaX/CHacOWBa/I++4ohTcByimhM1/nyCsH8KNA=="],
4300 4313
4301 "is-windows": ["is-windows@1.0.2", "", {}, "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA=="],4314 "is-windows": ["is-windows@1.0.2", "", {}, "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA=="],
@@ -4350,8 +4363,6 @@ · 48 unmodified lines
4350 4363
4351 "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],4364 "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
4352 4365
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
4355 "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],4366 "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
4356 4367
4357 "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],4368 "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
@@ -4374,6 +4385,8 @@ · 16 unmodified lines
4374 4385
4375 "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=="],4386 "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=="],
4376 4387
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
4377 "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=="],4390 "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=="],
4378 4391
4379 "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=="],4392 "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
4674 4687
4675 "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=="],4688 "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=="],
4676 4689
4690 "modern-tar": ["modern-tar@0.7.6", "", {}, "sha512-sweCIVXzx1aIGTCdzcMlSZt1h8k5Tmk08VNAuRk3IU28XamGiOH5ypi11g6De2CH7PhYqSSnGy2A/EFhbWnVKg=="],
4691
4677 "module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="],4692 "module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="],
4678 4693
4679 "monaco-editor": ["monaco-editor@0.55.1", "", { "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" } }, "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A=="],4694 "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
4702 4717
4703 "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=="],4718 "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=="],
4704 4719
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=="],
4706 4721
4707 "nanostores": ["nanostores@1.3.0", "", {}, "sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA=="],4722 "nanostores": ["nanostores@1.3.0", "", {}, "sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA=="],
4708 4723
@@ -4716,7 +4731,7 @@ · 7 unmodified lines
4716 4731
4717 "node-abi": ["node-abi@3.89.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA=="],4732 "node-abi": ["node-abi@3.89.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA=="],
4718 4733
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=="],
4720 4735
4721 "node-api-version": ["node-api-version@0.2.1", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q=="],4736 "node-api-version": ["node-api-version@0.2.1", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q=="],
4722 4737
@@ -4730,8 +4745,12 @@ · 7 unmodified lines
4730 4745
4731 "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=="],4746 "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=="],
4732 4747
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
4733 "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=="],4750 "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=="],
4734 4751
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
4735 "node-mock-http": ["node-mock-http@1.0.4", "", {}, "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ=="],4754 "node-mock-http": ["node-mock-http@1.0.4", "", {}, "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ=="],
4736 4755
4737 "node-releases": ["node-releases@2.0.37", "", {}, "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg=="],4756 "node-releases": ["node-releases@2.0.37", "", {}, "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg=="],
@@ -4830,6 +4849,8 @@ · 92 unmodified lines
4830 4849
4831 "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],4850 "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
4832 4851
4852 "papaparse": ["papaparse@5.5.4", "", {}, "sha512-SwzWD9gl/ElwYLCI0nUja1mFJzjq2D8ziShfNBa7zCHzkOozeOGDwHWQ+tvCzEZcewecWZ5U7kUopDnG+DFYEQ=="],
4853
4833 "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],4854 "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
4834 4855
4835 "parse-bmfont-ascii": ["parse-bmfont-ascii@1.0.6", "", {}, "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA=="],4856 "parse-bmfont-ascii": ["parse-bmfont-ascii@1.0.6", "", {}, "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA=="],
@@ -4854,9 +4875,9 @@ · 18 unmodified lines
4854 4875
4855 "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],4876 "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
4856 4877
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=="],
4858 4879
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=="],
4860 4881
4861 "patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="],4882 "patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="],
4862 4883
@@ -4864,6 +4885,8 @@ · 1 unmodified line
4864 4885
4865 "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],4886 "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
4866 4887
4888 "path-expression-matcher": ["path-expression-matcher@1.6.1", "", {}, "sha512-h7bxdzhHk8Knyc4Tj+jMaa7fEEoUJy7p1qtbVgkYg1Uhpe5Np5VuGXCRZnkZvU+Q42M1vStt0ifa3ueykRJPmQ=="],
4889
4867 "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],4890 "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
4868 4891
4869 "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],4892 "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
@@ -5050,6 +5073,8 @@ · 180 unmodified lines
5050 5073
5051 "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=="],5074 "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=="],
5052 5075
5076 "re2js": ["re2js@1.4.0", "", {}, "sha512-KTOIcZTSOpOxbu3i0+T6mFQ6tkxXKlTxfcMFs1trQbsMnG84qNq+DjXr8Afu+FEFjvF1NNlldpC7roPyazFI8g=="],
5077
5053 "react": ["react@19.2.5", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="],5078 "react": ["react@19.2.5", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="],
5054 5079
5055 "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=="],5080 "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
5268 5293
5269 "scroll-into-view-if-needed": ["scroll-into-view-if-needed@3.1.0", "", { "dependencies": { "compute-scroll-into-view": "^3.0.2" } }, "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ=="],5294 "scroll-into-view-if-needed": ["scroll-into-view-if-needed@3.1.0", "", { "dependencies": { "compute-scroll-into-view": "^3.0.2" } }, "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ=="],
5270 5295
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
5271 "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],5298 "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
5272 5299
5273 "semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="],5300 "semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="],
@@ -5364,6 +5391,8 @@ · 90 unmodified lines
5364 5391
5365 "sql-highlight": ["sql-highlight@6.1.0", "", {}, "sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA=="],5392 "sql-highlight": ["sql-highlight@6.1.0", "", {}, "sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA=="],
5366 5393
5394 "sql.js": ["sql.js@1.14.1", "", {}, "sha512-gcj8zBWU5cFsi9WUP+4bFNXAyF1iRpA3LLyS/DP5xlrNzGmPIizUeBggKa8DbDwdqaKwUcTEnChtd2grWo/x/A=="],
5395
5367 "srvx": ["srvx@0.11.15", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-iXsux0UcOjdvs0LCMa2Ws3WwcDUozA3JN3BquNXkaFPP7TpRqgunKdEgoZ/uwb1J6xaYHfxtz9Twlh6yzwM6Tg=="],5396 "srvx": ["srvx@0.11.15", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-iXsux0UcOjdvs0LCMa2Ws3WwcDUozA3JN3BquNXkaFPP7TpRqgunKdEgoZ/uwb1J6xaYHfxtz9Twlh6yzwM6Tg=="],
5368 5397
5369 "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=="],5398 "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
5410 5439
5411 "stripe": ["stripe@22.3.0", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-ypO6xjVrMWs9SmIMeHr8naCx3dAQ0clxMdUTxn7Ejd7hmY9meBGfE+N4pVHkf9sUNebAHp6uJo6mV3GxDIc2cA=="],5440 "stripe": ["stripe@22.3.0", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-ypO6xjVrMWs9SmIMeHr8naCx3dAQ0clxMdUTxn7Ejd7hmY9meBGfE+N4pVHkf9sUNebAHp6uJo6mV3GxDIc2cA=="],
5412 5441
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=="],
5414 5445
5415 "stubborn-fs": ["stubborn-fs@2.0.0", "", { "dependencies": { "stubborn-utils": "^1.0.1" } }, "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA=="],5446 "stubborn-fs": ["stubborn-fs@2.0.0", "", { "dependencies": { "stubborn-utils": "^1.0.1" } }, "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA=="],
5416 5447
@@ -5518,7 +5549,7 @@ · 101 unmodified lines
5518 5549
5519 "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],5550 "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
5520 5551
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=="],
5522 5553
5523 "toml": ["toml@4.1.1", "", {}, "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw=="],5554 "toml": ["toml@4.1.1", "", {}, "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw=="],
5524 5555
@@ -5552,6 +5583,8 @@ · 27 unmodified lines
5552 5583
5553 "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=="],5584 "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=="],
5554 5585
5586 "turndown": ["turndown@7.2.4", "", { "dependencies": { "@mixmark-io/domino": "^2.2.0" } }, "sha512-I8yFsfRzmzK0WV1pNNOA4A7y4RDfFxPRxb3t+e3ui14qSGOxGtiSP6GjeX+Y6CHb7HYaFj7ECUD7VE5kQMZWGQ=="],
5587
5555 "tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="],5588 "tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="],
5556 5589
5557 "twitter-api-v2": ["twitter-api-v2@1.29.0", "", {}, "sha512-v473q5bwme4N+DWSg6qY+JCvfg1nSJRWwui3HUALafxfqCvVkKiYmS/5x/pVeJwTmyeBxexMbzHwnzrH4h6oYQ=="],5590 "twitter-api-v2": ["twitter-api-v2@1.29.0", "", {}, "sha512-v473q5bwme4N+DWSg6qY+JCvfg1nSJRWwui3HUALafxfqCvVkKiYmS/5x/pVeJwTmyeBxexMbzHwnzrH4h6oYQ=="],
@@ -5734,6 +5767,8 @@ · 176 unmodified lines
5734 5767
5735 "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],5768 "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
5736 5769
5770 "xml-naming": ["xml-naming@0.1.0", "", {}, "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw=="],
5771
5737 "xml-parse-from-string": ["xml-parse-from-string@1.0.1", "", {}, "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g=="],5772 "xml-parse-from-string": ["xml-parse-from-string@1.0.1", "", {}, "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g=="],
5738 5773
5739 "xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="],5774 "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
5796 5831
5797 "@azure/msal-browser/@azure/msal-common": ["@azure/msal-common@16.4.1", "", {}, "sha512-Bl8f+w37xkXsYh7QRkAKCFGYtWMYuOVO7Lv+BxILrvGz3HbIEF22Pt0ugyj0QPOl6NLrHcnNUQ9yeew98P/5iw=="],5832 "@azure/msal-browser/@azure/msal-common": ["@azure/msal-common@16.4.1", "", {}, "sha512-Bl8f+w37xkXsYh7QRkAKCFGYtWMYuOVO7Lv+BxILrvGz3HbIEF22Pt0ugyj0QPOl6NLrHcnNUQ9yeew98P/5iw=="],
5798 5833
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
5799 "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],5836 "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
5800 5837
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
5801 "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],5842 "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
5802 5843
5803 "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],5844 "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
5804 5845
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
5805 "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],5848 "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
5806 5849
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
5807 "@better-auth/core/jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="],5872 "@better-auth/core/jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="],
5808 5873
5809 "@changesets/apply-release-plan/prettier": ["prettier@2.8.8", "", { "bin": { "prettier": "bin-prettier.js" } }, "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q=="],5874 "@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
5814 5879
5815 "@clerk/shared/js-cookie": ["js-cookie@3.0.7", "", {}, "sha512-z/wZZgDrkNV1eA0ULjM/F9/50Ya8fbzgKneSpoPsXSGd0KnpdtHfOZWK+GcwLk+EZbS4F9RBhU+K2RgzuDaItw=="],5880 "@clerk/shared/js-cookie": ["js-cookie@3.0.7", "", {}, "sha512-z/wZZgDrkNV1eA0ULjM/F9/50Ya8fbzgKneSpoPsXSGd0KnpdtHfOZWK+GcwLk+EZbS4F9RBhU+K2RgzuDaItw=="],
5816 5881
5882 "@cloudflare/codemode/acorn": ["acorn@8.17.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="],
5883
5817 "@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=="],5884 "@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=="],
5818 5885
5819 "@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=="],5886 "@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
5916 5983
5917 "@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=="],5984 "@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=="],
5918 5985
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
5919 "@jimp/core/mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],5988 "@jimp/core/mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
5920 5989
5921 "@jimp/plugin-blit/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],5990 "@jimp/plugin-blit/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
@@ -6246,6 +6315,8 @@ · 324 unmodified lines
6246 6315
6247 "@tanstack/router-plugin/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],6316 "@tanstack/router-plugin/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
6248 6317
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
6249 "@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=="],6320 "@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=="],
6250 6321
6251 "@tanstack/start-plugin-core/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.40", "", {}, "sha512-s3GeJKSQOwBlzdUrj4ISjJj5SfSh+aqn0wjOar4Bx95iV1ETI7F6S/5hLcfAxZ9kXDcyrAkxPlqmd1ZITttf+w=="],6322 "@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
6254 6325
6255 "@tanstack/start-plugin-core/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],6326 "@tanstack/start-plugin-core/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
6256 6327
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
6257 "@types/better-sqlite3/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],6332 "@types/better-sqlite3/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
6258 6333
6259 "@types/cacheable-request/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],6334 "@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
6280 6355
6281 "@vercel/sdk/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],6356 "@vercel/sdk/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
6282 6357
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
6283 "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=="],6362 "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=="],
6284 6363
6285 "ai/@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],6364 "ai/@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
@@ -6314,6 +6393,8 @@ · 28 unmodified lines
6314 6393
6315 "atmn/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],6394 "atmn/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
6316 6395
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
6317 "basic-auth/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],6398 "basic-auth/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
6318 6399
6319 "better-auth/jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="],6400 "better-auth/jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="],
@@ -6434,6 +6515,8 @@ · 114 unmodified lines
6434 6515
6435 "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=="],6516 "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=="],
6436 6517
6518 "iconv-corefoundation/node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="],
6519
6437 "image-q/@types/node": ["@types/node@16.9.1", "", {}, "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g=="],6520 "image-q/@types/node": ["@types/node@16.9.1", "", {}, "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g=="],
6438 6521
6439 "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],6522 "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
@@ -6456,6 +6539,10 @@ · 16 unmodified lines
6456 6539
6457 "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=="],6540 "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=="],
6458 6541
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
6459 "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],6546 "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
6460 6547
6461 "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=="],6548 "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
6464 6551
6465 "libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="],6552 "libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="],
6466 6553
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
6467 "matcher/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],6556 "matcher/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
6468 6557
6469 "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],6558 "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
6570 6659
6571 "run-jxa/type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="],6660 "run-jxa/type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="],
6572 6661
6662 "seek-bzip/commander": ["commander@6.2.1", "", {}, "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA=="],
6663
6573 "serialize-error/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],6664 "serialize-error/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
6574 6665
6575 "set-value/is-extendable": ["is-extendable@0.1.1", "", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="],6666 "set-value/is-extendable": ["is-extendable@0.1.1", "", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="],
@@ -6644,8 +6735,64 @@ · 68 unmodified lines
6644 6735
6645 "@azure/identity/@azure/msal-node/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],6736 "@azure/identity/@azure/msal-node/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
6646 6737
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
6647 "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],6742 "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
6648 6743
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
6649 "@cloudflare/vite-plugin/miniflare/undici": ["undici@7.24.8", "", {}, "sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ=="],6796 "@cloudflare/vite-plugin/miniflare/undici": ["undici@7.24.8", "", {}, "sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ=="],
6650 6797
6651 "@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=="],6798 "@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
6846 6993
6847 "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],6994 "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
6848 6995
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
6849 "@libsql/kysely-libsql/@libsql/client/@libsql/core": ["@libsql/core@0.8.1", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-u6nrj6HZMTPsgJ9EBhLzO2uhqhlHQJQmVHV+0yFLvfGf3oSP8w7TjZCNUgu1G8jHISx6KFi7bmcrdXW9lRt++A=="],7000 "@libsql/kysely-libsql/@libsql/client/@libsql/core": ["@libsql/core@0.8.1", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-u6nrj6HZMTPsgJ9EBhLzO2uhqhlHQJQmVHV+0yFLvfGf3oSP8w7TjZCNUgu1G8jHISx6KFi7bmcrdXW9lRt++A=="],
6850 7001
6851 "@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=="],7002 "@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
6940 7091
6941 "@tanstack/router-generator/@tanstack/router-core/seroval-plugins": ["seroval-plugins@1.5.4", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw=="],7092 "@tanstack/router-generator/@tanstack/router-core/seroval-plugins": ["seroval-plugins@1.5.4", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw=="],
6942 7093
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
6943 "@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=="],7096 "@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=="],
6944 7097
6945 "@types/better-sqlite3/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],7098 "@types/better-sqlite3/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
@@ -6966,6 +7119,58 @@ · 20 unmodified lines
6966 7119
6967 "@types/yauzl/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],7120 "@types/yauzl/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
6968 7121
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
6969 "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=="],7174 "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=="],
6970 7175
6971 "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=="],7176 "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
7178 7383
7179 "jsprim/verror/extsprintf": ["extsprintf@1.4.1", "", {}, "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA=="],7384 "jsprim/verror/extsprintf": ["extsprintf@1.4.1", "", {}, "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA=="],
7180 7385
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
7181 "kayvee/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],7396 "kayvee/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
7182 7397
7183 "mimetext/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],7398 "mimetext/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
@@ -7306,6 +7521,22 @@ · 122 unmodified lines
7306 7521
7307 "yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],7522 "yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
7308 7523
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
7309 "@cloudflare/vite-plugin/miniflare/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260415.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-dsxaKsQm3LnPGNPEdsRv09QN3Y4DqCw7kX5j6noKqbAtro2jTr95sVlYM1jUxZ5FkOl1f7SXgaKKB9t5H5Nkbg=="],7540 "@cloudflare/vite-plugin/miniflare/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260415.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-dsxaKsQm3LnPGNPEdsRv09QN3Y4DqCw7kX5j6noKqbAtro2jTr95sVlYM1jUxZ5FkOl1f7SXgaKKB9t5H5Nkbg=="],
7310 7541
7311 "@cloudflare/vite-plugin/miniflare/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260415.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-+JgSgVA49KyKteHRA1SnonE4Zn5Ei5zdAp5FQMxFmXI8qulZw4Hl7safXxRyK4i9sTO8gl7TFOKO5Q64VPvSDQ=="],7542 "@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
7468 7699
7469 "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=="],7700 "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=="],
7470 7701
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
7471 "kayvee/js-yaml/argparse/sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],7712 "kayvee/js-yaml/argparse/sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
7472 7713
7473 "ora/cli-cursor/restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],7714 "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
49import { requestScopedMiddleware } from "./request-scoped";49import { requestScopedMiddleware } from "./request-scoped";
50import {50import {
51 McpServingRoutes,51 McpServingRoutes,
52 McpDiscoveryRoutes,
52 McpErrorReporterNoop,53 McpErrorReporterNoop,
53 type McpAuthProvider,54 type McpAuthProvider,
54 type McpErrorReporter,55 type McpErrorReporter,
@@ -128,8 +129,12 @@ export interface EngineProviders<REngine = never> { · 73 unmodified lines
128export interface McpProviders<RMcpAuth = never> {129export interface McpProviders<RMcpAuth = never> {
129 /** Resolve a request to an MCP `AuthOutcome` + declare the discovery routes. */130 /** Resolve a request to an MCP `AuthOutcome` + declare the discovery routes. */
130 readonly auth: Layer.Layer<McpAuthProvider, never, RMcpAuth>;131 readonly auth: Layer.Layer<McpAuthProvider, never, RMcpAuth>;
131 /** Owns the entire serving-session lifecycle (in-process Map vs DO). */132 /**
132 readonly sessions: Layer.Layer<McpSessionStore>;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>;
133 /** Forward an orchestration defect to the host's capture; default no-op. */138 /** Forward an orchestration defect to the host's capture; default no-op. */
134 readonly reporter?: Layer.Layer<McpErrorReporter>;139 readonly reporter?: Layer.Layer<McpErrorReporter>;
135}140}
@@ -609,6 +614,12 @@ const buildMcpRoutes = <RMcpAuth>( · 473 unmodified lines
609 const mcpAuthLive = (mcp.auth as Layer.Layer<McpAuthProvider, never, IdentityProvider>).pipe(614 const mcpAuthLive = (mcp.auth as Layer.Layer<McpAuthProvider, never, IdentityProvider>).pipe(
610 Layer.provide(identity),615 Layer.provide(identity),
611 );616 );
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 }
612 const mcpSeams = Layer.mergeAll(mcpAuthLive, mcp.sessions, mcp.reporter ?? McpErrorReporterNoop);623 const mcpSeams = Layer.mergeAll(mcpAuthLive, mcp.sessions, mcp.reporter ?? McpErrorReporterNoop);
613 return McpServingRoutes.pipe(HttpRouter.provideRequest(mcpSeams), Layer.provide(mcpAuthLive));624 return McpServingRoutes.pipe(HttpRouter.provideRequest(mcpSeams), Layer.provide(mcpAuthLive));
614};625};
packages/core/execution/src/engine.test.ts+15−0
@@ -85,3 +85,18 @@ describe("executeWithPause failure propagation", () => { · 84 unmodified lines
85 }),85 }),
86 );86 );
87});87});
88
89describe("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
401 */401 */
402 readonly getPausedExecution: (executionId: string) => Effect.Effect<PausedExecution | null>;402 readonly getPausedExecution: (executionId: string) => Effect.Effect<PausedExecution | null>;
403 403
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
404 /**410 /**
405 * Get the dynamic tool description (workflow + namespaces).411 * Get the dynamic tool description (workflow + namespaces).
406 */412 */
@@ -633,6 +639,8 @@ export const createExecutionEngine = <E extends Cause.YieldableError = CodeExecu · 226 unmodified lines
633 resume: resumeExecution,639 resume: resumeExecution,
634 getPausedExecution: (executionId) =>640 getPausedExecution: (executionId) =>
635 Effect.sync(() => pausedExecutions.get(executionId) ?? null),641 Effect.sync(() => pausedExecutions.get(executionId) ?? null),
642 pausedExecutionCount: () => Effect.sync(() => pausedExecutions.size),
643 hasPausedExecutions: () => Effect.sync(() => pausedExecutions.size > 0),
636 getDescription: buildExecuteDescription(executor),644 getDescription: buildExecuteDescription(executor),
637 };645 };
638};646};
packages/core/execution/src/promise.ts+4−0
@@ -50,6 +50,8 @@ export type ExecutionEngine = { · 49 unmodified lines
50 response: ResumeResponse,50 response: ResumeResponse,
51 ) => Promise<ExecutionResult | null>;51 ) => Promise<ExecutionResult | null>;
52 readonly getPausedExecution: (executionId: string) => Promise<PausedExecution | null>;52 readonly getPausedExecution: (executionId: string) => Promise<PausedExecution | null>;
53 readonly pausedExecutionCount: () => Promise<number>;
54 readonly hasPausedExecutions: () => Promise<boolean>;
53 readonly getDescription: () => Promise<string>;55 readonly getDescription: () => Promise<string>;
54};56};
55 57
@@ -155,6 +157,8 @@ export const toPromiseExecutionEngine = <E extends Cause.YieldableError>( · 99 unmodified lines
155 executeWithPause: (code, options) => Effect.runPromise(engine.executeWithPause(code, options)),157 executeWithPause: (code, options) => Effect.runPromise(engine.executeWithPause(code, options)),
156 resume: (executionId, response) => Effect.runPromise(engine.resume(executionId, response)),158 resume: (executionId, response) => Effect.runPromise(engine.resume(executionId, response)),
157 getPausedExecution: (executionId) => Effect.runPromise(engine.getPausedExecution(executionId)),159 getPausedExecution: (executionId) => Effect.runPromise(engine.getPausedExecution(executionId)),
160 pausedExecutionCount: () => Effect.runPromise(engine.pausedExecutionCount()),
161 hasPausedExecutions: () => Effect.runPromise(engine.hasPausedExecutions()),
158 getDescription: () => Effect.runPromise(engine.getDescription),162 getDescription: () => Effect.runPromise(engine.getDescription),
159});163});
160 164
packages/hosts/cloudflare/package.json+4−20
@@ -8,29 +8,13 @@ · 7 unmodified lines
8 "types": "./src/blob-store.ts",8 "types": "./src/blob-store.ts",
9 "default": "./src/blob-store.ts"9 "default": "./src/blob-store.ts"
10 },10 },
11 "./mcp/worker-transport": {
12 "types": "./src/mcp/worker-transport.ts",
13 "default": "./src/mcp/worker-transport.ts"
14 },
15 "./mcp/do-headers": {11 "./mcp/do-headers": {
16 "types": "./src/mcp/do-headers.ts",12 "types": "./src/mcp/do-headers.ts",
17 "default": "./src/mcp/do-headers.ts"13 "default": "./src/mcp/do-headers.ts"
18 },14 },
19 "./mcp/response-peek": {15 "./mcp/agent-durable-object": {
20 "types": "./src/mcp/response-peek.ts",16 "types": "./src/mcp/agent-session-durable-object.ts",
21 "default": "./src/mcp/response-peek.ts"17 "default": "./src/mcp/agent-session-durable-object.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"
34 }18 }
35 },19 },
36 "scripts": {20 "scripts": {
@@ -44,7 +28,7 @@ · 7 unmodified lines
44 "@executor-js/host-mcp": "workspace:*",28 "@executor-js/host-mcp": "workspace:*",
45 "@executor-js/sdk": "workspace:*",29 "@executor-js/sdk": "workspace:*",
46 "@modelcontextprotocol/sdk": "^1.29.0",30 "@modelcontextprotocol/sdk": "^1.29.0",
47 "agents": "^0.10.0",31 "agents": "^0.16.2",
48 "effect": "catalog:"32 "effect": "catalog:"
49 },33 },
50 "devDependencies": {34 "devDependencies": {
packages/hosts/cloudflare/src/mcp/agent-session-durable-object.ts+788−0
@@ -0,0 +1,788 @@
1import { Cause, Deferred, Effect, Exit, Option, Schema } from "effect";
2import type * as Tracer from "effect/Tracer";
3import type { Connection, ConnectionContext } from "agents";
4import { McpAgent } from "agents/mcp";
5import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
7import { RequestOrgSlug, RequestWebOrigin } from "@executor-js/api/server";
8import {
9 formatPausedExecution,
10 type ExecutionEngine,
11 type ResumeResponse,
12} from "@executor-js/execution";
13import {
14 PAUSED_APPROVAL_TIMEOUT_MS,
15 type PausedExecutionHooks,
16} from "@executor-js/host-mcp/tool-server";
17import type { McpResource } from "@executor-js/host-mcp";
18
19import type { IncomingPropagationHeaders, McpElicitationMode } from "./do-headers";
20import {
21 SESSION_TIMEOUT_MS,
22 decideSessionAlarm,
23 pausedLeaseExtensionLog,
24} from "./session-alarm-policy";
25
26export type IncomingTraceHeaders = IncomingPropagationHeaders;
27
28export 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
38export interface McpSessionProps extends Record<string, unknown> {
39 readonly session: McpSessionInit;
40 readonly propagation?: IncomingTraceHeaders;
41}
42
43export type McpApprovalOwner = {
44 readonly accountId: string;
45 readonly organizationId: string;
46};
47
48type McpSessionApprovalErrorResult =
49 | { readonly status: "not_found" }
50 | { readonly status: "forbidden" };
51
52type PendingApprovalLease = {
53 readonly disposeKeepAlive: () => void;
54 timeout: ReturnType<typeof setTimeout> | null;
55 expiring: boolean;
56};
57
58export type McpSessionApprovalResult =
59 | {
60 readonly status: "ok";
61 readonly text: string;
62 readonly structured: Record<string, unknown>;
63 }
64 | McpSessionApprovalErrorResult;
65
66export 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
76export interface SessionDbHandle {
77 readonly end: () => Promise<void> | void;
78}
79
80export 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
94export interface BuiltMcpServer {
95 readonly mcpServer: McpServer;
96 readonly engine: ExecutionEngine<Cause.YieldableError>;
97}
98
99export interface BrowserApprovalStore {
100 readonly takeResponse: (executionId: string) => Effect.Effect<ResumeResponse | null>;
101 readonly waitForResponse: (executionId: string) => Effect.Effect<ResumeResponse | null>;
102}
103
104const SESSION_META_KEY = "session-meta";
105const LAST_ACTIVITY_KEY = "last-activity-ms";
106const PARTYSERVER_NAME_KEY = "__ps_name";
107const MCP_HTTP_METHOD_HEADER = "cf-mcp-method";
108const MCP_MESSAGE_HEADER = "cf-mcp-message";
109const ACTIVE_POST_RESPONSE_WAIT_POLL_MS = 25;
110const ACTIVE_POST_RESPONSE_WAIT_MAX_MS = PAUSED_APPROVAL_TIMEOUT_MS + 30_000;
111const approvalResponseKey = (executionId: string) => `approval-response:${executionId}`;
112
113type JsonRpcRequestId = string | number;
114const JsonRpcRequestWithId = Schema.Struct({
115 id: Schema.Union([Schema.String, Schema.Number]),
116 method: Schema.String,
117});
118const JsonRpcPostPayload = Schema.fromJsonString(Schema.Unknown);
119const decodeJsonRpcPostPayload = Schema.decodeUnknownOption(JsonRpcPostPayload);
120const decodeJsonRpcRequestWithId = Schema.decodeUnknownOption(JsonRpcRequestWithId);
121
122const 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
146const 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
153const sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));
154
155const 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
193export 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.
4import { Effect, Exit, Match, Option, Predicate } from "effect";
5
6type JsonRpcLike = {
7 readonly id?: unknown;
8 readonly method?: unknown;
9 readonly params?: unknown;
10};
11
12type ToolCallParamsLike = {
13 readonly name?: unknown;
14 readonly arguments?: unknown;
15};
16
17type ResumeArgumentsLike = {
18 readonly executionId?: unknown;
19};
20
21const 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
33const 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
50const 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.
76export const PREVIOUS_REQUEST_TIMEOUT_MS = 60_000;
77
78export 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 @@
1import { Cause, Data, Effect, Exit, Option, Schema } from "effect";
2
3import { jsonRpcErrorBody } from "@executor-js/host-mcp";
4
5const 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). */
9export type OnInternalJsonRpcError = (message: string) => void;
10
11class ResponseBodyTimeoutError extends Data.TaggedError("ResponseBodyTimeoutError")<{
12 readonly timeoutMs: number;
13}> {}
14
15class ResponseBodyReadError extends Data.TaggedError("ResponseBodyReadError") {}
16
17const ResponseBodyTimeoutErrorData = Schema.Struct({
18 _tag: Schema.Literal("ResponseBodyTimeoutError"),
19 timeoutMs: Schema.Number,
20});
21const decodeResponseBodyTimeoutError = Schema.decodeUnknownOption(ResponseBodyTimeoutErrorData);
22
23const 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
33const 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
49const decodeJsonRpcResponseBody = Schema.decodeUnknownOption(
50 Schema.fromJsonString(JsonRpcResponseBodySchema),
51);
52
53type JsonRpcResponseBody = typeof JsonRpcResponseBodySchema.Type;
54
55const 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
65const 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
81const 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
107const 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
140const 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
149const 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
160const isResponseBodyTimeoutError = (error: unknown) =>
161 Option.isSome(decodeResponseBodyTimeoutError(error));
162
163const responsePeekError = (error: unknown): ResponseBodyTimeoutError | ResponseBodyReadError =>
164 Option.match(decodeResponseBodyTimeoutError(error), {
165 onNone: () => new ResponseBodyReadError(),
166 onSome: ({ timeoutMs }) => new ResponseBodyTimeoutError({ timeoutMs }),
167 });
168
169const 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
190const 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
199export 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
206export 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 @@
1import type { McpResource } from "@executor-js/host-mcp";
2
3import 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). */
12export 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 */
27export 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 @@
1import { describe, expect, it } from "@effect/vitest";
2
3import {
4 MAX_PAUSED_SESSION_IDLE_MS,
5 PAUSED_EXECUTION_LEASE_MS,
6 SESSION_TIMEOUT_MS,
7 decideSessionAlarm,
8} from "./session-alarm-policy";
9
10describe("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 @@
1import { PAUSED_APPROVAL_TIMEOUT_MS } from "@executor-js/host-mcp/tool-server";
2
3/** Idle timeout for MCP sessions with no paused continuations. */
4export const SESSION_TIMEOUT_MS = 5 * 60 * 1000;
5
6/** Lease extension while paused executions block hibernation (matches browser approval wait). */
7export 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 */
16export const MAX_PAUSED_SESSION_IDLE_MS = SESSION_TIMEOUT_MS + PAUSED_EXECUTION_LEASE_MS;
17
18export type SessionAlarmDecision =
19 | { readonly kind: "idle_within_timeout" }
20 | { readonly kind: "destroy_idle_session" }
21 | { readonly kind: "extend_paused_lease"; readonly leaseMs: number };
22
23export 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
36export 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
13import { DurableObject } from "cloudflare:workers";
14import { Cause, Deferred, Effect } from "effect";
15import type * as Tracer from "effect/Tracer";
16import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
17import type { TransportState } from "agents/mcp";
18
19import { jsonRpcErrorBody } from "@executor-js/host-mcp";
20import { formatResumeAcknowledgement } from "@executor-js/host-mcp/browser-approval";
21import { RequestOrgSlug, RequestWebOrigin } from "@executor-js/api/server";
22import {
23 formatPausedExecution,
24 type ExecutionEngine,
25 type ResumeResponse,
26} from "@executor-js/execution";
27
28import { makeMcpWorkerTransport, type McpWorkerTransport } from "./worker-transport";
29import {
30 INTERNAL_ACCOUNT_ID_HEADER,
31 INTERNAL_ORGANIZATION_ID_HEADER,
32 INTERNAL_RESOURCE_KEY_HEADER,
33 type IncomingPropagationHeaders,
34} from "./do-headers";
35import { mcpResourceKey } from "@executor-js/host-mcp";
36import type { McpSessionInit } from "./seams";
37import type { McpResource } from "@executor-js/host-mcp";
38
39// ---------------------------------------------------------------------------
40// Types
41// ---------------------------------------------------------------------------
42
43export 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). */
47export type IncomingTraceHeaders = IncomingPropagationHeaders;
48
49export type McpApprovalOwner = {
50 readonly accountId: string;
51 readonly organizationId: string;
52};
53
54type McpSessionApprovalErrorResult =
55 | { readonly status: "not_found" }
56 | { readonly status: "forbidden" };
57
58export type McpSessionApprovalResult =
59 | {
60 readonly status: "ok";
61 readonly text: string;
62 readonly structured: Record<string, unknown>;
63 }
64 | McpSessionApprovalErrorResult;
65
66export 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
76const 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
86const HEARTBEAT_MS = 30 * 1000;
87const SESSION_TIMEOUT_MS = 5 * 60 * 1000;
88const TRANSPORT_STATE_KEY = "transport";
89const SESSION_META_KEY = "session-meta";
90const LAST_ACTIVITY_KEY = "last-activity-ms";
91const 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).
101const jsonRpcError = (status: number, code: number, message: string) =>
102 jsonRpcErrorBody(status, code, message, { cors: false });
103
104const 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 */
116export 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 */
125export 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. */
141export 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". */
148export 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
157export 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
13import { describe, expect, it } from "@effect/vitest";
14import { Cause, Effect, Exit } from "effect";
15
16import {
17 McpSessionStore,
18 defaultMcpResource,
19 type McpDispatchResult,
20 type Principal,
21} from "@executor-js/host-mcp";
22
23import {
24 DO_RELOCATION_MAX_RETRIES,
25 makeDurableObjectMcpSessionStore,
26 type McpSessionInit,
27 type McpSessionDOStub,
28} from "./session-store";
29import { INTERNAL_RESOURCE_KEY_HEADER } from "./do-headers";
30
31const RELOCATION_ERROR = "cannot access storage because object has moved to a different machine";
32
33const 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. */
44const 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. */
52const 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. */
59const 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. */
75const failureText = (exit: Exit.Exit<McpDispatchResult>): string =>
76 Exit.isFailure(exit) ? Cause.pretty(exit.cause) : "";
77
78describe("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
22import { Data, Effect, Layer, Schedule } from "effect";
23
24import {
25 McpSessionStore,
26 type McpDispatchInput,
27 type McpDispatchResult,
28} from "@executor-js/host-mcp";
29
30import {
31 currentPropagationHeaders,
32 readElicitationMode,
33 withMcpResponseHeaders,
34 withPropagationHeaders,
35 withVerifiedIdentityHeaders,
36 type VerifiedTokenHeaders,
37} from "./do-headers";
38import { peekAndAnnotate, type OnInternalJsonRpcError } from "./response-peek";
39import type { McpSessionDOStub } from "./seams";
40
41export type { McpSessionDOStub, McpSessionInit } from "./seams";
42
43export 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
69const 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`. */
74class 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. */
81const 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. */
89export const DO_RELOCATION_MAX_RETRIES = 3;
90const 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 */
99const 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 */
138const 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. */
168const 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
208const 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 */
235export 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 @@
1import { WorkerTransport, type WorkerTransportOptions } from "agents/mcp";
2import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3import { Data, Effect } from "effect";
4
5import { JsonRpcRequestIdQueue } from "./request-id-queue.js";
6
7export { JsonRpcRequestIdQueue, PREVIOUS_REQUEST_TIMEOUT_MS } from "./request-id-queue.js";
8
9export class McpWorkerTransportError extends Data.TaggedError("McpWorkerTransportError")<{
10 readonly cause: unknown;
11}> {}
12
13export 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
20type HandleRequestResult = {
21 readonly response: Response;
22 readonly replacedStandaloneSse: boolean;
23};
24
25const 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
47const isStandaloneSseGet = (request: Request): boolean =>
48 request.method === "GET" && (request.headers.get("accept") ?? "").includes("text/event-stream");
49
50export 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
20 McpErrorReporter,20 McpErrorReporter,
21 McpErrorReporterNoop,21 McpErrorReporterNoop,
22 McpServingRoutes,22 McpServingRoutes,
23 McpDiscoveryRoutes,
23 McpSessionStore,24 McpSessionStore,
24 type McpResource,25 type McpResource,
25 type McpDispatchResult,26 type McpDispatchResult,
@@ -195,3 +196,45 @@ it("dispatches toolkit MCP routes with the parsed toolkit resource", async () => · 169 unmodified lines
195 slug: "deploy",196 slug: "deploy",
196 });197 });
197});198});
199
200describe("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
286 yield* router.add("*", TOOLKIT_MCP_PATH, toolkitMcpRoute);286 yield* router.add("*", TOOLKIT_MCP_PATH, toolkitMcpRoute);
287 }),287 }),
288);288);
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 */
300export 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
36 type McpResource,36 type McpResource,
37} from "./seams";37} from "./seams";
38 38
39export { McpServingRoutes, jsonRpcErrorBody } from "./envelope";39export { 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
38 (() => Effect.succeed({ status: "completed", result: { result: "default" } })),38 (() => Effect.succeed({ status: "completed", result: { result: "default" } })),
39 resume: overrides.resume ?? (() => Effect.succeed(null)),39 resume: overrides.resume ?? (() => Effect.succeed(null)),
40 getPausedExecution: () => Effect.succeed(null),40 getPausedExecution: () => Effect.succeed(null),
41 pausedExecutionCount: () => Effect.succeed(0),
42 hasPausedExecutions: () => Effect.succeed(false),
41 getDescription: Effect.succeed(overrides.description ?? "test executor"),43 getDescription: Effect.succeed(overrides.description ?? "test executor"),
42});44});
43 45
@@ -46,7 +48,10 @@ const withClient = async <E extends Cause.YieldableError>( · 2 unmodified lines
46 engine: ExecutionEngine<E>,48 engine: ExecutionEngine<E>,
47 capabilities: ClientCapabilities,49 capabilities: ClientCapabilities,
48 fn: (client: Client) => Promise<void>,50 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 >,
50) => {55) => {
51 const mcpServer = await Effect.runPromise(createExecutorMcpServer({ engine, ...config }));56 const mcpServer = await Effect.runPromise(createExecutorMcpServer({ engine, ...config }));
52 const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();57 const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
@@ -1119,6 +1124,49 @@ describe("MCP host server — client without elicitation (pause/resume)", () => · 1066 unmodified lines
1119 );1124 );
1120 });1125 });
1121 1126
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
1122 it("resume tool passes parsed content to engine", async () => {1170 it("resume tool passes parsed content to engine", async () => {
1123 let receivedContent: Record<string, unknown> | undefined;1171 let receivedContent: Record<string, unknown> | undefined;
1124 const engine = makeStubEngine({1172 const engine = makeStubEngine({
packages/hosts/mcp/src/tool-server.ts+54−4
@@ -1,4 +1,4 @@
1import { Effect, Match, Option, Schema } from "effect";1import { Duration, Effect, Match, Option, Schema } from "effect";
2import * as Cause from "effect/Cause";2import * as Cause from "effect/Cause";
3import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";3import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4import { ContentBlockSchema, type ContentBlock } from "@modelcontextprotocol/sdk/types.js";4import { ContentBlockSchema, type ContentBlock } from "@modelcontextprotocol/sdk/types.js";
@@ -91,6 +91,12 @@ type SharedMcpServerConfig = { · 86 unmodified lines
91 readonly mode: "native";91 readonly mode: "native";
92 };92 };
93 readonly browserApprovalStore?: BrowserApprovalStore;93 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;
94};100};
95 101
96export type ExecutorMcpServerConfig<E extends Cause.YieldableError = Cause.YieldableError> =102export type ExecutorMcpServerConfig<E extends Cause.YieldableError = Cause.YieldableError> =
@@ -104,6 +110,15 @@ export type BrowserApprovalStore = { · 7 unmodified lines
104 readonly waitForResponse?: (executionId: string) => Effect.Effect<ResumeResponse | null>;110 readonly waitForResponse?: (executionId: string) => Effect.Effect<ResumeResponse | null>;
105};111};
106 112
113export const PAUSED_APPROVAL_TIMEOUT_MS = 4 * 60 * 1000;
114const BROWSER_APPROVAL_WAIT_TIMEOUT_MS = PAUSED_APPROVAL_TIMEOUT_MS + 1000;
115
116export 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
107// ---------------------------------------------------------------------------122// ---------------------------------------------------------------------------
108// Elicitation bridge123// Elicitation bridge
109// ---------------------------------------------------------------------------124// ---------------------------------------------------------------------------
@@ -544,6 +559,17 @@ export const createExecutorMcpServer = <E extends Cause.YieldableError>( · 434 unmodified lines
544 ({559 ({
545 mode: "model",560 mode: "model",
546 } as const);561 } 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)));
547 573
548 const resolveParentSpan = (): Tracer.AnySpan | undefined => {574 const resolveParentSpan = (): Tracer.AnySpan | undefined => {
549 const ps = config.parentSpan;575 const ps = config.parentSpan;
@@ -594,6 +620,14 @@ export const createExecutorMcpServer = <E extends Cause.YieldableError>( · 44 unmodified lines
594 ? pausedInteractionKind(outcome.execution.elicitationContext.request)620 ? pausedInteractionKind(outcome.execution.elicitationContext.request)
595 : undefined,621 : undefined,
596 });622 });
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 }
597 return outcome.status === "completed"631 return outcome.status === "completed"
598 ? toMcpResult(outcome.result)632 ? toMcpResult(outcome.result)
599 : elicitationMode.mode === "browser"633 : elicitationMode.mode === "browser"
@@ -620,7 +654,7 @@ export const createExecutorMcpServer = <E extends Cause.YieldableError>( · 20 unmodified lines
620 hasContent: content !== undefined,654 hasContent: content !== undefined,
621 clientCapabilities: server.server.getClientCapabilities() ?? null,655 clientCapabilities: server.server.getClientCapabilities() ?? null,
622 });656 });
623 const outcome = yield* engine.resume(executionId, { action, content });657 const outcome = yield* resumeWithLifecycle(executionId, { action, content });
624 if (!outcome) {658 if (!outcome) {
625 debugLog("resume.missing_execution", { executionId });659 debugLog("resume.missing_execution", { executionId });
626 return missingExecutionResult(executionId);660 return missingExecutionResult(executionId);
@@ -634,6 +668,14 @@ export const createExecutorMcpServer = <E extends Cause.YieldableError>( · 7 unmodified lines
634 ? pausedInteractionKind(outcome.execution.elicitationContext.request)668 ? pausedInteractionKind(outcome.execution.elicitationContext.request)
635 : undefined,669 : undefined,
636 });670 });
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 }
637 return outcome.status === "completed"679 return outcome.status === "completed"
638 ? toMcpResult(outcome.result)680 ? toMcpResult(outcome.result)
639 : toMcpPausedResult(formatPausedExecution(outcome.execution));681 : toMcpPausedResult(formatPausedExecution(outcome.execution));
@@ -682,7 +724,7 @@ export const createExecutorMcpServer = <E extends Cause.YieldableError>( · 42 unmodified lines
682 724
683 return waitForResponse(executionId).pipe(725 return waitForResponse(executionId).pipe(
684 Effect.timeoutOrElse({726 Effect.timeoutOrElse({
685 duration: "10 minutes",727 duration: Duration.millis(BROWSER_APPROVAL_WAIT_TIMEOUT_MS),
686 orElse: () => Effect.succeed(null),728 orElse: () => Effect.succeed(null),
687 }),729 }),
688 );730 );
@@ -693,10 +735,18 @@ export const createExecutorMcpServer = <E extends Cause.YieldableError>( · 4 unmodified lines
693 const response = yield* waitForBrowserApprovalResponse(executionId);735 const response = yield* waitForBrowserApprovalResponse(executionId);
694 if (!response) return yield* requireUserResumeApproval(executionId);736 if (!response) return yield* requireUserResumeApproval(executionId);
695 737
696 const outcome = yield* engine.resume(executionId, response);738 const outcome = yield* resumeWithLifecycle(executionId, response);
697 if (!outcome) {739 if (!outcome) {
698 return missingExecutionResult(executionId);740 return missingExecutionResult(executionId);
699 }741 }
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 }
700 return outcome.status === "completed"750 return outcome.status === "completed"
701 ? toMcpResult(outcome.result)751 ? toMcpResult(outcome.result)
702 : yield* requireUserResumeApproval(outcome.execution.id);752 : 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
651 readonly toolRows: readonly { readonly name: string }[];651 readonly toolRows: readonly { readonly name: string }[];
652}) =>652}) =>
653 Effect.gen(function* () {653 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);
657 const out: Record<string, ReturnType<typeof annotationsForOperation>> = {};654 const out: Record<string, ReturnType<typeof annotationsForOperation>> = {};
658 for (const row of input.toolRows) {655 for (const row of input.toolRows) {
659 const binding = byName.get(row.name);656 const operation = yield* input.ctx.storage.getOperation(input.integration, row.name);
660 if (binding) {657 if (!operation) continue;
661 out[row.name] = annotationsForOperation(binding.method, binding.pathTemplate);658 out[row.name] = annotationsForOperation(
662 }659 operation.binding.method,
660 operation.binding.pathTemplate,
661 );
663 }662 }
664 return out;663 return out;
665 });664 });