diff --git a/apps/cloud/package.json b/apps/cloud/package.json index 5131ee9575..93720adbed 100644 --- a/apps/cloud/package.json +++ b/apps/cloud/package.json @@ -11,7 +11,7 @@ "db:generate": "drizzle-kit generate", "build": "vite build", "preview": "vite preview", - "deploy": "vite build && wrangler deploy", + "deploy": "op run --env-file=.env.production -- sh -c 'vite build && wrangler deploy'", "cf-typegen": "wrangler types", "typecheck": "tsgo --noEmit", "test": "vitest run", @@ -22,6 +22,7 @@ "@cloudflare/vite-plugin": "^1.31.1", "@effect-atom/atom": "^0.5.0", "@effect-atom/atom-react": "^0.5.0", + "@effect/opentelemetry": "^0.63.0", "@effect/platform": "catalog:", "@executor/api": "workspace:*", "@executor/env": "workspace:*", @@ -36,6 +37,14 @@ "@executor/sdk": "workspace:*", "@executor/storage-postgres": "workspace:*", "@modelcontextprotocol/sdk": "^1.29.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.214.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.214.0", + "@opentelemetry/resources": "^2.6.1", + "@opentelemetry/sdk-logs": "^0.214.0", + "@opentelemetry/sdk-trace-base": "^2.6.1", + "@opentelemetry/semantic-conventions": "^1.40.0", + "@sentry/cloudflare": "^10.48.0", + "@sentry/react": "^10.48.0", "@tanstack/react-router": "catalog:", "@tanstack/react-start": "catalog:", "@workos-inc/node": "^8.11.1", diff --git a/apps/cloud/src/api.ts b/apps/cloud/src/api.ts index 1f92149186..4737774761 100644 --- a/apps/cloud/src/api.ts +++ b/apps/cloud/src/api.ts @@ -3,6 +3,7 @@ // --------------------------------------------------------------------------- import { env } from "cloudflare:workers"; +import * as Sentry from "@sentry/cloudflare"; import { HttpApiBuilder, HttpApiSwagger, @@ -48,6 +49,7 @@ import { DbService } from "./services/db"; import { createOrgExecutor } from "./services/executor"; import { TeamOrgApi } from "./team/compose"; import { TeamHandlers } from "./team/handlers"; +import { TelemetryLive } from "./services/telemetry"; import { server } from "./env"; // --------------------------------------------------------------------------- @@ -73,7 +75,7 @@ const SharedServices = Layer.mergeAll( UserStoreLive, WorkOSAuth.Default, HttpServer.layerContext, -); +).pipe(Layer.provideMerge(TelemetryLive)); const ProtectedCloudApiLive = HttpApiBuilder.api(ProtectedCloudApi).pipe( Layer.provide( Layer.mergeAll( @@ -249,6 +251,7 @@ const handleAutumnRequest = async (request: Request): Promise => { return Effect.runPromise(program.pipe(Effect.provide(SharedServices), Effect.scoped)).catch( (err) => { + Sentry.captureException(err); console.error("[autumn] request failed:", err instanceof Error ? err.stack : err); return Response.json({ error: "Internal server error" }, { status: 500 }); }, @@ -259,9 +262,41 @@ const handleAutumnRequest = async (request: Request): Promise => { // Widget token endpoint — returns a WorkOS widget token for the session user // --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// Sentry tunnel — proxies client events to Sentry to bypass ad blockers +// --------------------------------------------------------------------------- + +const SENTRY_HOST = "o4511196985556992.ingest.us.sentry.io"; +const SENTRY_PROJECT_IDS = new Set(["4511197001416704", "4511197010067456"]); + +const handleSentryTunnel = async (request: Request): Promise => { + try { + const body = await request.text(); + const header = body.split("\n")[0]; + const { dsn } = JSON.parse(header) as { dsn: string }; + const url = new URL(dsn); + const projectId = url.pathname.replace("/", ""); + + if (url.host !== SENTRY_HOST || !SENTRY_PROJECT_IDS.has(projectId)) { + return new Response("Invalid Sentry DSN", { status: 400 }); + } + + return fetch(`https://${SENTRY_HOST}/api/${projectId}/envelope/`, { + method: "POST", + body, + }); + } catch { + return new Response("Invalid request", { status: 400 }); + } +}; + export const handleApiRequest = async (request: Request): Promise => { const pathname = new URL(request.url).pathname; + if (pathname === "/sentry-tunnel" && request.method === "POST") { + return handleSentryTunnel(request); + } + if (isTeamPath(pathname)) { const handler = createTeamHandler(); try { @@ -293,6 +328,30 @@ export const handleApiRequest = async (request: Request): Promise => { const codeExecutor = makeDynamicWorkerExecutor({ loader: env.LOADER }); const handler = yield* buildProtectedHandler(org.id, org.name, codeExecutor); const response = yield* Effect.promise(() => handler.handler(request)); + + // Sanitize error responses — only let declared API errors through, + // replace anything else with a generic message to prevent leaking internals + if (!response.ok) { + const sanitized = yield* Effect.promise(async () => { + try { + const body = await response.clone().json(); + const hasTag = body && typeof body === "object" && "_tag" in body; + if (!hasTag) { + Sentry.captureMessage(`API ${response.status}: ${pathname}`, { level: "error" }); + return Response.json( + { error: "Internal server error" }, + { status: response.status >= 500 ? 500 : response.status }, + ); + } + } catch { + Sentry.captureMessage(`API ${response.status}: ${pathname}`, { level: "error" }); + return Response.json({ error: "Internal server error" }, { status: 500 }); + } + return null; + }); + if (sanitized) return { response: sanitized, orgId: org.id }; + } + return { response, orgId: org.id }; }); @@ -325,10 +384,8 @@ export const handleApiRequest = async (request: Request): Promise => { return result.response; } catch (err) { + Sentry.captureException(err); console.error("[api] request failed:", err instanceof Error ? err.stack : err); - return Response.json( - { error: err instanceof Error ? err.message : "Internal server error" }, - { status: 500 }, - ); + return Response.json({ error: "Internal server error" }, { status: 500 }); } }; diff --git a/apps/cloud/src/env.ts b/apps/cloud/src/env.ts index 196d373ca6..c1063d69f4 100644 --- a/apps/cloud/src/env.ts +++ b/apps/cloud/src/env.ts @@ -12,6 +12,9 @@ const serverShape = { WORKOS_COOKIE_PASSWORD: Env.string("WORKOS_COOKIE_PASSWORD"), VITE_PUBLIC_SITE_URL: Env.stringOr("VITE_PUBLIC_SITE_URL", ""), AUTUMN_SECRET_KEY: Env.stringOr("AUTUMN_SECRET_KEY", ""), + SENTRY_DSN: Env.stringOr("SENTRY_DSN", ""), + AXIOM_TOKEN: Env.stringOr("AXIOM_TOKEN", ""), + AXIOM_DATASET: Env.stringOr("AXIOM_DATASET", "executor-cloud"), }; type SharedEnv = Readonly<{ @@ -27,6 +30,9 @@ type ServerEnv = SharedEnv & WORKOS_COOKIE_PASSWORD: string; VITE_PUBLIC_SITE_URL: string; AUTUMN_SECRET_KEY: string; + SENTRY_DSN: string; + AXIOM_TOKEN: string; + AXIOM_DATASET: string; }>; type WebEnv = Readonly>; diff --git a/apps/cloud/src/mcp-session.ts b/apps/cloud/src/mcp-session.ts index 67f3796ed0..0f42874fb9 100644 --- a/apps/cloud/src/mcp-session.ts +++ b/apps/cloud/src/mcp-session.ts @@ -6,14 +6,19 @@ import { DurableObject, env } from "cloudflare:workers"; import { Data, Effect, Layer } from "effect"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { WorkerTransport, type TransportState } from "agents/mcp"; +import { drizzle } from "drizzle-orm/postgres-js"; +import postgres from "postgres"; import { createExecutorMcpServer } from "@executor/host-mcp"; import { makeDynamicWorkerExecutor } from "@executor/runtime-dynamic-worker"; +import type { DrizzleDb } from "@executor/storage-postgres"; +import * as sharedSchema from "@executor/storage-postgres/schema"; import { UserStoreService } from "./auth/context"; import { server } from "./env"; import { createOrgExecutor } from "./services/executor"; import { DbService } from "./services/db"; +import * as cloudSchema from "./services/schema"; // --------------------------------------------------------------------------- // Types @@ -23,47 +28,19 @@ export type McpSessionInit = { organizationId: string; }; -// Heartbeat interval — keeps the DO alive by re-scheduling an alarm before -// Cloudflare's ~60s idle eviction kicks in. const HEARTBEAT_MS = 30 * 1000; - -// Session timeout — clean up after no requests for this long. -// TODO: Make tier-based — free users get 60s, paid users get 5 minutes. const SESSION_TIMEOUT_MS = 5 * 60 * 1000; // --------------------------------------------------------------------------- -// Session initialization effect +// Errors // --------------------------------------------------------------------------- -const DbLive = DbService.Live; -const UserStoreLive = UserStoreService.Live.pipe(Layer.provide(DbLive)); -const Services = Layer.mergeAll(DbLive, UserStoreLive); - class OrganizationNotFoundError extends Data.TaggedError("OrganizationNotFoundError")<{ readonly organizationId: string; }> {} -const initSession = (organizationId: string) => - Effect.gen(function* () { - const users = yield* UserStoreService; - const org = yield* users.use((store) => store.getOrganization(organizationId)); - - if (!org) { - return yield* new OrganizationNotFoundError({ organizationId }); - } - - const executor = yield* createOrgExecutor(org.id, org.name, server.ENCRYPTION_KEY); - - const codeExecutor = makeDynamicWorkerExecutor({ loader: env.LOADER }); - const mcpServer = yield* Effect.promise(() => - createExecutorMcpServer({ executor, codeExecutor }), - ); - - return mcpServer; - }).pipe(Effect.provide(Services)); - // --------------------------------------------------------------------------- -// JSON-RPC error response helper +// Helpers // --------------------------------------------------------------------------- const jsonRpcError = (status: number, code: number, message: string) => @@ -72,6 +49,32 @@ const jsonRpcError = (status: number, code: number, message: string) => headers: { "content-type": "application/json" }, }); +const combinedSchema = { ...sharedSchema, ...cloudSchema }; + +/** + * Create a long-lived DB connection for the DO lifetime. + * + * Unlike the per-request `DbService.Live` used in `api.ts`, this connection + * stays open across requests within the DO. DOs have a single-threaded + * execution model — there's no cross-request socket reuse issue because + * only one request runs at a time. The connection is closed when the DO + * cleans up (timeout or eviction). + */ +const makeLongLivedDb = (): { db: DrizzleDb; end: () => Promise } => { + const connectionString = env.HYPERDRIVE?.connectionString ?? server.DATABASE_URL; + const sql = postgres(connectionString, { + max: 1, + idle_timeout: 20, + max_lifetime: 300, + connect_timeout: 10, + onnotice: () => undefined, + }); + return { + db: drizzle(sql, { schema: combinedSchema }) as DrizzleDb, + end: () => sql.end({ timeout: 0 }).catch(() => undefined), + }; +}; + // --------------------------------------------------------------------------- // Durable Object // --------------------------------------------------------------------------- @@ -81,6 +84,7 @@ export class McpSessionDO extends DurableObject { private transport: WorkerTransport | null = null; private initialized = false; private lastActivityMs = 0; + private dbHandle: { db: DrizzleDb; end: () => Promise } | null = null; private makeStorage() { return { @@ -96,7 +100,25 @@ export class McpSessionDO extends DurableObject { async init(token: McpSessionInit): Promise { if (this.initialized) return; - this.mcpServer = await Effect.runPromise(initSession(token.organizationId)); + // Create a long-lived DB connection for the DO's lifetime + this.dbHandle = makeLongLivedDb(); + + const DbLive = Layer.succeed(DbService, this.dbHandle.db); + const UserStoreLive = UserStoreService.Live.pipe(Layer.provide(DbLive)); + const Services = Layer.mergeAll(DbLive, UserStoreLive); + + const program = Effect.gen(function* () { + const users = yield* UserStoreService; + const org = yield* users.use((store) => store.getOrganization(token.organizationId)); + if (!org) + return yield* new OrganizationNotFoundError({ organizationId: token.organizationId }); + + const executor = yield* createOrgExecutor(org.id, org.name, server.ENCRYPTION_KEY); + const codeExecutor = makeDynamicWorkerExecutor({ loader: env.LOADER }); + return yield* Effect.promise(() => createExecutorMcpServer({ executor, codeExecutor })); + }).pipe(Effect.provide(Services)); + + this.mcpServer = await Effect.runPromise(program); this.transport = new WorkerTransport({ sessionIdGenerator: () => this.ctx.id.toString(), @@ -143,6 +165,10 @@ export class McpSessionDO extends DurableObject { await this.mcpServer.close().catch(() => undefined); this.mcpServer = null; } + if (this.dbHandle) { + await this.dbHandle.end(); + this.dbHandle = null; + } this.initialized = false; } } diff --git a/apps/cloud/src/routes/__root.tsx b/apps/cloud/src/routes/__root.tsx index e6476ede0f..2a39310f87 100644 --- a/apps/cloud/src/routes/__root.tsx +++ b/apps/cloud/src/routes/__root.tsx @@ -1,4 +1,5 @@ import React from "react"; +import * as Sentry from "@sentry/react"; import { HeadContent, Scripts, createRootRoute } from "@tanstack/react-router"; import { AutumnProvider } from "autumn-js/react"; import { ExecutorProvider } from "@executor/react/api/provider"; @@ -8,6 +9,16 @@ import { LoginPage } from "../web/pages/login"; import { Shell } from "../web/shell"; import appCss from "@executor/react/globals.css?url"; +if (typeof window !== "undefined" && import.meta.env.VITE_PUBLIC_SENTRY_DSN) { + Sentry.init({ + dsn: import.meta.env.VITE_PUBLIC_SENTRY_DSN, + tunnel: "/api/sentry-tunnel", + tracesSampleRate: 0, + replaysSessionSampleRate: 0.1, + replaysOnErrorSampleRate: 1.0, + }); +} + export const Route = createRootRoute({ head: () => ({ meta: [ diff --git a/apps/cloud/src/server.ts b/apps/cloud/src/server.ts index 230f5a6ef3..5987b82af4 100644 --- a/apps/cloud/src/server.ts +++ b/apps/cloud/src/server.ts @@ -1,8 +1,17 @@ +import * as Sentry from "@sentry/cloudflare"; import handler from "@tanstack/react-start/server-entry"; // Export Durable Objects as named exports export { McpSessionDO } from "./mcp-session"; -export default { - fetch: handler.fetch, -}; +export default Sentry.withSentry( + (env: Record) => ({ + dsn: env.SENTRY_DSN, + tracesSampleRate: 0, + enableLogs: true, + sendDefaultPii: true, + }), + { + fetch: handler.fetch, + }, +); diff --git a/apps/cloud/src/services/telemetry.ts b/apps/cloud/src/services/telemetry.ts new file mode 100644 index 0000000000..a5c0b9424d --- /dev/null +++ b/apps/cloud/src/services/telemetry.ts @@ -0,0 +1,35 @@ +// --------------------------------------------------------------------------- +// OpenTelemetry setup — pipes Effect spans + logs to Axiom via OTLP +// --------------------------------------------------------------------------- + +import { Layer } from "effect"; +import { WebSdk, Tracer as OtelTracer } from "@effect/opentelemetry"; +import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base"; +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; +import { server } from "../env"; + +const makeResourceLayer = () => + WebSdk.layer(() => ({ + resource: { + serviceName: "executor-cloud", + serviceVersion: "1.0.0", + }, + spanProcessor: new BatchSpanProcessor( + new OTLPTraceExporter({ + url: "https://api.axiom.co/v1/traces", + headers: { + Authorization: `Bearer ${server.AXIOM_TOKEN}`, + "X-Axiom-Dataset": server.AXIOM_DATASET, + }, + }), + ), + })); + +/** + * Full telemetry layer — provides Effect Tracer backed by OTEL → Axiom. + * All existing `Effect.withSpan` calls automatically become distributed traces. + * No-op when AXIOM_TOKEN is not set. + */ +export const TelemetryLive: Layer.Layer = server.AXIOM_TOKEN + ? OtelTracer.layerGlobal.pipe(Layer.provide(makeResourceLayer())) + : Layer.empty; diff --git a/apps/cloud/wrangler.jsonc b/apps/cloud/wrangler.jsonc index 3968127880..18c0e8f402 100644 --- a/apps/cloud/wrangler.jsonc +++ b/apps/cloud/wrangler.jsonc @@ -4,7 +4,12 @@ "compatibility_date": "2025-04-01", "compatibility_flags": ["nodejs_compat"], "main": "src/server.ts", - "routes": [{ "pattern": "executor.sh", "custom_domain": true }], + "routes": [ + { + "pattern": "executor.sh", + "custom_domain": true, + }, + ], "limits": { "cpu_ms": 300000, }, @@ -44,6 +49,7 @@ }, ], "vars": { + "VITE_PUBLIC_SENTRY_DSN": "https://e20b1bdeea27008092a4dc0ac303e97a@o4511196985556992.ingest.us.sentry.io/4511197010067456", "VITE_PUBLIC_SITE_URL": "https://executor.sh", }, } diff --git a/bun.lock b/bun.lock index ca600cd509..9f891aa18e 100644 --- a/bun.lock +++ b/bun.lock @@ -48,6 +48,7 @@ "@cloudflare/vite-plugin": "^1.31.1", "@effect-atom/atom": "^0.5.0", "@effect-atom/atom-react": "^0.5.0", + "@effect/opentelemetry": "^0.63.0", "@effect/platform": "catalog:", "@executor/api": "workspace:*", "@executor/env": "workspace:*", @@ -62,6 +63,14 @@ "@executor/sdk": "workspace:*", "@executor/storage-postgres": "workspace:*", "@modelcontextprotocol/sdk": "^1.29.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.214.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.214.0", + "@opentelemetry/resources": "^2.6.1", + "@opentelemetry/sdk-logs": "^0.214.0", + "@opentelemetry/sdk-trace-base": "^2.6.1", + "@opentelemetry/semantic-conventions": "^1.40.0", + "@sentry/cloudflare": "^10.48.0", + "@sentry/react": "^10.48.0", "@tanstack/react-router": "catalog:", "@tanstack/react-start": "catalog:", "@workos-inc/node": "^8.11.1", @@ -885,6 +894,8 @@ "@effect/language-service": ["@effect/language-service@0.84.3", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-zpxi6rLCwst/cBQd7ElwDvt36Y6Jvz8v6bCLnNiOL6OXvdLmqjOFWyzWZdMh92vvBQA/aVKhfIAAOP3o4wKt0A=="], + "@effect/opentelemetry": ["@effect/opentelemetry@0.63.0", "", { "peerDependencies": { "@effect/platform": "^0.96.0", "@opentelemetry/api": "^1.9", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": "^2.0.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@opentelemetry/sdk-trace-web": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.33.0", "effect": "^3.21.0" } }, "sha512-2yUG2QWNATi1uKP0kwhaP5eLp+c5NDzAL3EOpIcGLBAC0cbXZrx4n9Qw/QwUKxpuV+pbhrBUPCiByyWAFKfuCw=="], + "@effect/platform": ["@effect/platform@0.96.0", "", { "dependencies": { "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.21.0" } }, "sha512-U7PLhkVzg7zzrgFvyWATOzD6reL87KG/fcdOxgLWBQ/J5CCU6qdPAVG+0o6o+IxcsLoqGwxs+rFxaFzrdtDV1A=="], "@effect/platform-bun": ["@effect/platform-bun@0.89.0", "", { "dependencies": { "@effect/platform-node-shared": "^0.59.0", "multipasta": "^0.2.7" }, "peerDependencies": { "@effect/cluster": "^0.58.0", "@effect/platform": "^0.96.0", "@effect/rpc": "^0.75.0", "@effect/sql": "^0.51.0", "effect": "^3.21.0" } }, "sha512-ReT5f2vujJfffMOBexrgwJd2RLxgfr2G0c1FyCsoflcjdQJ7RZE3cwHDp1M3hAzmG67wWAssMHqLsX6H/n27sQ=="], @@ -1215,6 +1226,32 @@ "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], + "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA=="], + + "@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.6.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-XHzhwRNkBpeP8Fs/qjGrAf9r9PRv67wkJQ/7ZPaBQQ68DYlTBBx5MF9LvPx7mhuXcDessKK2b+DcxqwpgkcivQ=="], + + "@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/exporter-logs-otlp-http": ["@opentelemetry/exporter-logs-otlp-http@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/sdk-logs": "0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-9qv2Tl/Hq6qc5pJCbzFJnzA0uvlb9DgM70yGJPYf3bA5LlLkRCpcn81i4JbcIH4grlQIWY6A+W7YG0LLvS1BAw=="], + + "@opentelemetry/exporter-trace-otlp-http": ["@opentelemetry/exporter-trace-otlp-http@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-kIN8nTBMgV2hXzV/a20BCFilPZdAIMYYJGSgfMMRm/Xa+07y5hRDS2Vm12A/z8Cdu3Sq++ZvJfElokX2rkgGgw=="], + + "@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-transformer": "0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-u1Gdv0/E9wP+apqWf7Wv2npXmgJtxsW2XL0TEv9FZloTZRuMBKmu8cYVXwS4Hm3q/f/3FuCnPTgiwYvIqRSpRg=="], + + "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-logs": "0.214.0", "@opentelemetry/sdk-metrics": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1", "protobufjs": "^7.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-DSaYcuBRh6uozfsWN3R8HsN0yDhCuWP7tOFdkUOVaWD1KVJg8m4qiLUsg/tNhTLS9HUYUcwNpwL2eroLtsZZ/w=="], + + "@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="], + + "@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-zf6acnScjhsaBUU22zXZ/sLWim1dfhUAbGXdMmHmNG3LfBnQ3DKsOCITb2IZwoUsNNMTogqFKBnlIPPftUgGwA=="], + + "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ=="], + + "@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw=="], + + "@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.6.1", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/core": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Hh2i4FwHWRFhnO2Q/p6svMxy8MPsNCG0uuzUY3glqm0rwM0nQvbTO1dXSp9OqQoTKXcQzaz9q1f65fsurmOhNw=="], + + "@opentelemetry/sdk-trace-web": ["@opentelemetry/sdk-trace-web@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-JQevIjBlWGcKBfuwe7tdxGR/75RERsf1OOIvUzPKq86J8qhzkyjnLTTuPNPLRQF1xxEe65W5aI1Uwl6yWUGPQQ=="], + "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], "@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="], @@ -1413,6 +1450,26 @@ "@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="], + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], + + "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], + + "@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="], + + "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="], + + "@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="], + + "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], + + "@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="], + + "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], + + "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], + + "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], + "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], @@ -1637,6 +1694,22 @@ "@secure-exec/v8-linux-x64-gnu": ["@secure-exec/v8-linux-x64-gnu@0.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-Az4s+vUf+78vWtsC7rTn/jQc6WKJafAdt2YpEjB4Gnu+sX+FFTIst1hRV4gJonbRyJdy6SW+OQ6DZatmwczorQ=="], + "@sentry-internal/browser-utils": ["@sentry-internal/browser-utils@10.48.0", "", { "dependencies": { "@sentry/core": "10.48.0" } }, "sha512-SCiTLBXzugFKxev6NoKYBIhQoDk0gUh0AVVVepCBqfCJiWBG01Zvv0R5tCVohr4cWRllkQ8mlBdNQd/I7s9tdA=="], + + "@sentry-internal/feedback": ["@sentry-internal/feedback@10.48.0", "", { "dependencies": { "@sentry/core": "10.48.0" } }, "sha512-tGkEyOM1HDS9qebDphUMEnyk3qq/50AnuTBiFmMJyjNzowylVGmRRk0sr3xkmbVHCDXQCiYnDmSVlJ2x4SDMrQ=="], + + "@sentry-internal/replay": ["@sentry-internal/replay@10.48.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.48.0", "@sentry/core": "10.48.0" } }, "sha512-sevRTePfuk4PNuz9KAKpmTZEomAU0aLXyIhOwA0OnUDdxPhkY8kq5lwDbuxTHv6DQUjUX3YgFbY45VH1JEqHKA=="], + + "@sentry-internal/replay-canvas": ["@sentry-internal/replay-canvas@10.48.0", "", { "dependencies": { "@sentry-internal/replay": "10.48.0", "@sentry/core": "10.48.0" } }, "sha512-9nWuN2z4O+iwbTfuYV5ZmngBgJU/ZxfOo47A5RJP3Nu/kl59aJ1lUhILYOKyeNOIC/JyeERmpIcTxnlPXQzZ3Q=="], + + "@sentry/browser": ["@sentry/browser@10.48.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.48.0", "@sentry-internal/feedback": "10.48.0", "@sentry-internal/replay": "10.48.0", "@sentry-internal/replay-canvas": "10.48.0", "@sentry/core": "10.48.0" } }, "sha512-4jt2zX2ExgFcNe2x+W+/k81fmDUsOrquGtt028CiGuDuma6kEsWBI4JbooT1jhj2T+eeUxe3YGbM23Zhh7Ghhw=="], + + "@sentry/cloudflare": ["@sentry/cloudflare@10.48.0", "", { "dependencies": { "@opentelemetry/api": "^1.9.1", "@sentry/core": "10.48.0" }, "peerDependencies": { "@cloudflare/workers-types": "^4.x" }, "optionalPeers": ["@cloudflare/workers-types"] }, "sha512-i02Ps4/cJjFpbcHLMhNEFXTeVqLB9XpB3+/OFQ9aMFV3yDcxlvHwe0oo7WZf41iroArvpysotLG8Y8NBOU9omA=="], + + "@sentry/core": ["@sentry/core@10.48.0", "", {}, "sha512-h8F+fXVwYC9ro5ZaO8V+v3vqc0awlXHGblEAuVxSGgh4IV/oFX+QVzXeDTTrFOFS6v/Vn5vAyu240eJrJAS6/g=="], + + "@sentry/react": ["@sentry/react@10.48.0", "", { "dependencies": { "@sentry/browser": "10.48.0", "@sentry/core": "10.48.0" }, "peerDependencies": { "react": "^16.14.0 || 17.x || 18.x || 19.x" } }, "sha512-uc93vKjmu6gNns+JAX4qquuxWpAMit0uGPA1TYlMjct9NG1uX3TkDPJAr9Pgd1lOXx8mKqCmj5fK33QeExMpPw=="], + "@shikijs/core": ["@shikijs/core@4.0.2", "", { "dependencies": { "@shikijs/primitive": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw=="], "@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag=="], @@ -2841,6 +2914,8 @@ "log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], "lookpath": ["lookpath@1.2.3", "", { "bin": { "lookpath": "bin/lookpath.js" } }, "sha512-kthRVhf4kH4+HW3anM4UBHxsw/XFESf13euCEldhXr6GpBdmBoa7rDd7WO5G0Mhd4G5XtKTcEy8OR0iRZXpS3Q=="], @@ -3251,6 +3326,8 @@ "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], + "protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], "public-encrypt": ["public-encrypt@4.0.3", "", { "dependencies": { "bn.js": "^4.1.0", "browserify-rsa": "^4.0.0", "create-hash": "^1.1.0", "parse-asn1": "^5.0.0", "randombytes": "^2.0.1", "safe-buffer": "^5.1.2" } }, "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q=="], @@ -3905,6 +3982,8 @@ "@npmcli/agent/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "@opentelemetry/api-logs/@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="], + "@poppinss/colors/kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], "@poppinss/dumper/@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="], @@ -4001,6 +4080,8 @@ "@secure-exec/nodejs/esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], + "@sentry/cloudflare/@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], @@ -4175,6 +4256,8 @@ "proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "protobufjs/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], + "public-encrypt/bn.js": ["bn.js@4.12.3", "", {}, "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g=="], "radix-ui/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], @@ -4555,6 +4638,8 @@ "pkg-dir/find-up/locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + "protobufjs/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], + "read-yaml-file/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], "ripemd160/hash-base/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], diff --git a/packages/core/execution/src/tool-invoker.test.ts b/packages/core/execution/src/tool-invoker.test.ts index b9b752f243..38543c8ea2 100644 --- a/packages/core/execution/src/tool-invoker.test.ts +++ b/packages/core/execution/src/tool-invoker.test.ts @@ -366,52 +366,45 @@ describe("pause/resume with multiple elicitations", () => { // Regression: use separate top-level runPromise calls to match HTTP/CLI // pause/resume, and a single-elicit tool so no later pause can mask a dead // sandbox fiber. - it( - "resume returns across separate runPromise boundaries for a single-elicit tool (HTTP-like)", - async () => { - const executor = await Effect.runPromise(makeElicitingExecutor()); - const engine = createExecutionEngine({ executor }); - - const code = "return await tools.api.singleApproval({});"; - - const outcome1 = await engine.executeWithPause(code); - expect(outcome1.status).toBe("paused"); - const paused1 = outcome1 as Extract; - expect(paused1.execution.elicitationContext.request.message).toBe("Only approval"); - - // `execution.fiber` is on `InternalPausedExecution`; the exported - // `PausedExecution` type doesn't carry it. Cast to read. - const sandboxFiber = ( - paused1.execution as unknown as { - readonly fiber: Fiber.Fiber; - } - ).fiber; - const exitProbe = await Effect.runPromise( - Effect.race( - Fiber.await(sandboxFiber), - Effect.map(Effect.sleep("50 millis"), () => "still-running" as const), - ), - ); - expect(exitProbe).toBe("still-running"); - - const outcome2 = await Promise.race([ - engine.resume(paused1.execution.id, { action: "accept" }), - new Promise((_, reject) => - setTimeout( - () => reject(new Error("resume hung across runPromise boundaries")), - 2000, - ), - ), - ]); - - expect(outcome2).not.toBeNull(); - const resumed = outcome2 as NonNullable; - expect(resumed.status).toBe("completed"); - if (resumed.status === "completed") { - expect(resumed.result.error).toBeUndefined(); - expect(resumed.result.result).toMatchObject({ ok: true }); + it("resume returns across separate runPromise boundaries for a single-elicit tool (HTTP-like)", async () => { + const executor = await Effect.runPromise(makeElicitingExecutor()); + const engine = createExecutionEngine({ executor }); + + const code = "return await tools.api.singleApproval({});"; + + const outcome1 = await engine.executeWithPause(code); + expect(outcome1.status).toBe("paused"); + const paused1 = outcome1 as Extract; + expect(paused1.execution.elicitationContext.request.message).toBe("Only approval"); + + // `execution.fiber` is on `InternalPausedExecution`; the exported + // `PausedExecution` type doesn't carry it. Cast to read. + const sandboxFiber = ( + paused1.execution as unknown as { + readonly fiber: Fiber.Fiber; } - }, - 10000, - ); + ).fiber; + const exitProbe = await Effect.runPromise( + Effect.race( + Fiber.await(sandboxFiber), + Effect.map(Effect.sleep("50 millis"), () => "still-running" as const), + ), + ); + expect(exitProbe).toBe("still-running"); + + const outcome2 = await Promise.race([ + engine.resume(paused1.execution.id, { action: "accept" }), + new Promise((_, reject) => + setTimeout(() => reject(new Error("resume hung across runPromise boundaries")), 2000), + ), + ]); + + expect(outcome2).not.toBeNull(); + const resumed = outcome2 as NonNullable; + expect(resumed.status).toBe("completed"); + if (resumed.status === "completed") { + expect(resumed.result.error).toBeUndefined(); + expect(resumed.result.result).toMatchObject({ ok: true }); + } + }, 10000); }); diff --git a/packages/core/storage-postgres/src/crypto.ts b/packages/core/storage-postgres/src/crypto.ts index 156d6f9d29..868bf84da0 100644 --- a/packages/core/storage-postgres/src/crypto.ts +++ b/packages/core/storage-postgres/src/crypto.ts @@ -1,4 +1,4 @@ -import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto"; +import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto"; const ALGORITHM = "aes-256-gcm"; const IV_LENGTH = 12; @@ -9,7 +9,6 @@ const AUTH_TAG_LENGTH = 16; * Uses SHA-256 to normalize any length input to exactly 32 bytes. */ const deriveKey = (key: string): Buffer => { - const { createHash } = require("node:crypto") as typeof import("node:crypto"); return createHash("sha256").update(key).digest(); };