diff --git a/.env.example b/.env.example index 5ad0165..b304119 100644 --- a/.env.example +++ b/.env.example @@ -202,3 +202,20 @@ COMPUTER_RUNTIME= # that granted the tool, which is where the grant, the policy and the audit row are. Absent, no Bot # may call tools back and it is told so rather than being quietly allowed. AGENT_TOOL_TOKEN= + +# Remote computers on Daytona (https://daytona.io). Set an API key and every Bot gets a cloud +# sandbox of its own -- no local Docker needed for computers. Get a key at https://app.daytona.io +# under Dashboard -> Keys. Requires COMPUTER_TOKEN above, because a Daytona computer is reached +# over a public preview URL and the token is what refuses strangers ("/health" is the only +# unauthenticated route). Mutually exclusive with COMPUTER_SUPERVISOR_URL. AGENT_COMPUTER_URL is +# not needed in this mode. +# +# The first start builds a snapshot from agent-computer/ (a few minutes, streamed to the server +# log); later computers start from it in seconds. Idle computers stop after 15 minutes and wake +# on the next action. +# DAYTONA_API_KEY= +# Only for self-hosted or staging Daytona; the default is the hosted platform. +# DAYTONA_API_URL= +# DAYTONA_TARGET= +# Use a prebuilt snapshot by name instead of building from the local sources. +# DAYTONA_SNAPSHOT= diff --git a/README.md b/README.md index 56e2020..c2eadc2 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ A Bot is any endpoint speaking [AG-UI](https://github.com/ag-ui-protocol/ag-ui), ## Requirements -- Docker, for PostgreSQL, browser computers, the supervisor, and the shipped Bots. +- Docker, for PostgreSQL and the shipped Bots. Bot computers can run locally on Docker or remotely on Daytona (set DAYTONA_API_KEY). - [Bun](https://bun.sh) 1.3+, for the app and API server. - A CopilotKit Intelligence project and license. A free plan is available, and Intelligence can be self-hosted. - A model key. The proof-of-concept Bot uses OpenAI; the LangGraph Bot can use OpenAI, Anthropic, or Google. @@ -124,7 +124,7 @@ A Bot is any endpoint speaking [AG-UI](https://github.com/ag-ui-protocol/ag-ui), ## Features -- **A computer per Bot**: the supervisor gives each Bot its own container, its own `/workspace` volume and its own browser profile. Set `COMPUTER_RUNTIME=runsc` to run them under gVisor where the host supports it. +- **A computer per Bot**: the supervisor gives each Bot its own container, its own `/workspace` volume and its own browser profile. Set `COMPUTER_RUNTIME=runsc` to run them under gVisor where the host supports it. Or set `DAYTONA_API_KEY` to run each computer in a remote Daytona sandbox instead of a local container. - **The gateway is the only way in**: it resolves the target from a server-held snapshot, evaluates the policy, writes the audit row, and only then calls the computer. There is no path that acts without the record existing first. - **CEL policy, fail closed**: rules can inspect `tool.name`, `intent`, `bot.id`, `actor.id`, `page.url`, `page.host`, `element.*`, `key`, `file.*` and `mcp.*`. Deny is evaluated before allow, a missing policy permits nothing, and a broken rule refuses rather than opens. - **Take the wheel**: a Bot that hits a login wall or a 2FA prompt asks for help. Control is handed over in the same panel and recorded as `computer.help_requested`, `computer.control_taken` and `computer.control_released`. While a person is driving, Bot actions are refused rather than queued. @@ -180,6 +180,7 @@ Settings worth knowing: | `COMPUTER_TOKEN` | Secret every Bot computer request must present. `start.sh` sets one. | | `SUPERVISOR_TOKEN` | Secret the supervisor requires. `start.sh` sets one. | | `COMPUTER_SUPERVISOR_URL` | Gives each Bot a computer of its own instead of one shared computer. | +| `DAYTONA_API_KEY` | Runs each Bot's computer in a remote Daytona sandbox instead of local Docker. | | `COMPUTER_RUNTIME` | Set to `runsc` to run computers under gVisor, where the host has it. | | `AGENT_COMPUTER_POLICY` | JSON action policy. Malformed JSON stops server startup. | | `AGENT_COMPUTER_ALLOW_PRIVATE_HOSTS` | Lets a Bot reach this machine's own services. | diff --git a/agent-computer/Dockerfile b/agent-computer/Dockerfile index 594db89..5ca8507 100644 --- a/agent-computer/Dockerfile +++ b/agent-computer/Dockerfile @@ -1,7 +1,7 @@ # The Bot's computer uses Playwright's image so Chromium and its system libraries stay matched. # # The image tag and Playwright dependency must be pinned to the same exact version. Bump both or -# neither. +# neither. Also keep aligned with the Daytona Image recipe in server/src/computer/daytona.ts. FROM mcr.microsoft.com/playwright:v1.62.1-noble # unzip is not in the Playwright image and bun's installer needs it. diff --git a/app/src/routes/_authed/admin/computers.tsx b/app/src/routes/_authed/admin/computers.tsx index 3676ea9..b1e7d96 100644 --- a/app/src/routes/_authed/admin/computers.tsx +++ b/app/src/routes/_authed/admin/computers.tsx @@ -30,7 +30,7 @@ type ComputerProfile = { botId: string; running: boolean; startedAt: string | null; - egress: string | null; + egress?: string | null; }; /** API placeholder id; the list endpoint returns all computers. */ @@ -159,9 +159,11 @@ function ComputersPage() { ? `Browser running since ${new Date(computer.startedAt ?? "").toLocaleTimeString()}` : "No browser running. It starts when the Bot next needs it."} {" ยท "} - {computer.egress - ? `Leaves through ${computer.egress}` - : "Leaves directly"} + {computer.egress === undefined + ? "Egress not reported" + : computer.egress === null + ? "Leaves directly" + : `Leaves through ${computer.egress}`} diff --git a/bun.lock b/bun.lock index b98eb29..fe352de 100644 --- a/bun.lock +++ b/bun.lock @@ -63,6 +63,7 @@ "@better-auth/drizzle-adapter": "^1.6.27", "@copilotkit/runtime": "1.67.1", "@copilotkit/shared": "1.67.1", + "@daytona/sdk": "^0.205.1", "@modelcontextprotocol/sdk": "^1.30.0", "better-auth": "^1.6.27", "cel-js": "^0.8.2", @@ -106,26 +107,64 @@ "@ag-ui/proto": ["@ag-ui/proto@0.0.57", "", { "dependencies": { "@ag-ui/core": "0.0.57", "@bufbuild/protobuf": "^2.2.5", "@protobuf-ts/protoc": "^2.11.1" } }, "sha512-pPENOZt0P6ibH8sCTgq05wLYXi5t3P9B5r/1bWYehXjUxtyOdnukSlWM++SsCIwUXsQdm/b3aBgGjEeTF7RenA=="], - "@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.110", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-rNkamQCeAUOUGr5Npg5pXZyYFH4fS1U6Mbdy3dF/NNBEI3D2Chc/ruRrwNegP0gfpX3cllP3O4jSibGBbWPZ7A=="], + "@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.111", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.46" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-atgBW8jZPr/KuaKX5FvDIHuXBI8VCol6kVeoD4P0657+VXR73QsLogXQVN/Zt5FHtq9WzpdIZseCJiXPqkgwwA=="], - "@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.172", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-FSH+0q6wKNq6/WjWy10m3gbeW8MR6D7Qt380evNQinpfG88mOT2sP3x1U82tBsLlKe9O306LPfgQ3NDXy7HNbA=="], + "@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.177", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.46", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-U579dA3K2UcezpzJHjyw6UhctFtzFRKOSIrAgxr77teluPGP7vj8aLJpZvEEPf+JbUqbvsHRKKYZzip/+NQ89A=="], - "@ai-sdk/google": ["@ai-sdk/google@3.0.108", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kwvYpRNghqt0VRKE7Hx1UWZQCUJJFqUITj24baxy+ApS0Hru0PkBJHD75a36Wc+e6e+wHcKR2MconTeJiBZigA=="], + "@ai-sdk/google": ["@ai-sdk/google@3.0.111", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.46" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XWWju5pVD7Tf30c2VziMfPph3XpN/jDTE5aJhqAPJ2zRmHSSveNYFoosFLOWWFIgQnwGe9AM3bAXR8kHyC2MdA=="], - "@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@3.0.163", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.95", "@ai-sdk/google": "2.0.88", "@ai-sdk/openai-compatible": "1.0.48", "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.32", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-0JuayxQV4TgAkhoNjMX4jPDmGvq3hV24ijE8PO1VlnFoSwMOsm4NmVse0eemM9WJ98SmHMV65Syu72meo+2JJA=="], + "@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@3.0.164", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.95", "@ai-sdk/google": "2.0.89", "@ai-sdk/openai-compatible": "1.0.48", "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.32", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TJ54snZgD5kxWsHmMETkLhoN5333GKp6Me47HkGB01dnLMnrxSD3cSTiEompnZY4tTonxwm1Jl78dUiTKDaKXQ=="], - "@ai-sdk/mcp": ["@ai-sdk/mcp@1.0.70", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45", "pkce-challenge": "^5.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-rgbR+UZQF2DTsYUxgqTFjpFMaqXvQoGo9hS9q27qNpnNu5Pfv0qzUCg41YyED19cv3/LmF4tcpBY0YALsfdMxQ=="], + "@ai-sdk/mcp": ["@ai-sdk/mcp@1.0.71", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.46", "pkce-challenge": "^5.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-woKUwm/yiqxEC+E863tqAjEpiyWCERmy//k9pdNYP8Ta6+PF6Ge2X6p99e72BdBovB+/McUWgbJ4+Fiht+41WQ=="], - "@ai-sdk/openai": ["@ai-sdk/openai@3.0.96", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Pex8vOj1y05j7jtBS39cJJRDjJbMIyCY9+01cSIp1hwEJTKImrFejMgsAazMWXSi/HU+B9ZE6ElftCOwvg4mmQ=="], + "@ai-sdk/openai": ["@ai-sdk/openai@3.0.99", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.46" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-0Y6E5bvR+3eugt5rq2iy39EAAFi4nijXf1kdvcfoMm4erEGMe/6egIpvdvv5ePBU2YsnsxFuLRmMJ76Kv/babQ=="], "@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.48", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.32" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Zl5+5VHId7g344LVSjZvPuFuRVej+u+MB+0ibFVGaXG1qUykyjKu2ptHinK5RjDdviCm+Bs+t3ZLegB39e8VyA=="], "@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="], - "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="], + "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.46", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^6.28.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tEtld97plCFiYevsJuOkGkeuhQndeMWFBVrJS4AjnbD5AqrNSXRCe0p+BZ3Cju/sxDeeZ9ym3q9YUV8fASA7aQ=="], "@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="], + "@aws-sdk/checksums": ["@aws-sdk/checksums@3.1000.28", "", { "dependencies": { "@aws-sdk/core": "^3.977.8", "@aws-sdk/types": "^3.974.4", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-VCpnmyHQ1IH49ni3LXnQj7DPr7rmcJmzYeiCkYdCcfgNtkvOj38cdcL9lapBWoItZWFACJPFJlymqC7/gem3Gw=="], + + "@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.1115.0", "", { "dependencies": { "@aws-sdk/checksums": "^3.1000.28", "@aws-sdk/core": "^3.977.8", "@aws-sdk/credential-provider-node": "^3.972.80", "@aws-sdk/middleware-sdk-s3": "^3.972.74", "@aws-sdk/signature-v4-multi-region": "^3.996.45", "@aws-sdk/types": "^3.974.4", "@smithy/core": "^3.31.1", "@smithy/fetch-http-handler": "^5.6.13", "@smithy/node-http-handler": "^4.9.13", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-oeniaXZCRrKMaffnyjOSxp1xJNsuiku2SxyzVI1Bi1Gycpck/dRTVsloSa5E+nBKz1c5y5eMfbK4GAhGUCCC2Q=="], + + "@aws-sdk/core": ["@aws-sdk/core@3.977.8", "", { "dependencies": { "@aws-sdk/types": "^3.974.4", "@aws-sdk/xml-builder": "^3.972.39", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.31.1", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-7+Kcrkvrk9lM/m7jRhHpT4jCdvzGHsuaSRbF8TdzzkY1mRzp/Ogwf9c7H29k4gGhey0BBWhCWr16+t0J61gwmg=="], + + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.69", "", { "dependencies": { "@aws-sdk/core": "^3.977.8", "@aws-sdk/types": "^3.974.4", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-AreCFzcB4kH2HF9031Ot0jSJr3KXvRg6e8uDeub20JEVdZU3Bv0sTq1plc7VsT3KiqutlzH7l0j50UcCWHUioA=="], + + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.71", "", { "dependencies": { "@aws-sdk/core": "^3.977.8", "@aws-sdk/types": "^3.974.4", "@smithy/core": "^3.31.1", "@smithy/fetch-http-handler": "^5.6.13", "@smithy/node-http-handler": "^4.9.13", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-A8ObcqVmDMnk4F9NozZ7JwmUu9Q4xyBJkmyq1C5U+wNM9ht9J7+EuuyabsLWXZnOoTqFaJuYBYTKf5CTipkEjA=="], + + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.973.14", "", { "dependencies": { "@aws-sdk/core": "^3.977.8", "@aws-sdk/credential-provider-env": "^3.972.69", "@aws-sdk/credential-provider-http": "^3.972.71", "@aws-sdk/credential-provider-login": "^3.972.76", "@aws-sdk/credential-provider-process": "^3.972.69", "@aws-sdk/credential-provider-sso": "^3.973.13", "@aws-sdk/credential-provider-web-identity": "^3.972.75", "@aws-sdk/nested-clients": "^3.997.43", "@aws-sdk/types": "^3.974.4", "@smithy/core": "^3.31.1", "@smithy/credential-provider-imds": "^4.4.16", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-7c+Wti2LsERNWMfm7ySz3/6RPopFW3Nmn7s63Xpcq6R/tRuY5hpvkHA2xVgi5ukJbvok9l0IDtVEvqTtg+X7dw=="], + + "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.76", "", { "dependencies": { "@aws-sdk/core": "^3.977.8", "@aws-sdk/nested-clients": "^3.997.43", "@aws-sdk/types": "^3.974.4", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-LVixwOnEJfrrfKHeZjBA8pIMTZjNDq8ak8VpcoWUuCJDrSnBNU8POJksULMgvN089P0MXtQYH2Zs627/MK1K0g=="], + + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.80", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.69", "@aws-sdk/credential-provider-http": "^3.972.71", "@aws-sdk/credential-provider-ini": "^3.973.14", "@aws-sdk/credential-provider-process": "^3.972.69", "@aws-sdk/credential-provider-sso": "^3.973.13", "@aws-sdk/credential-provider-web-identity": "^3.972.75", "@aws-sdk/types": "^3.974.4", "@smithy/core": "^3.31.1", "@smithy/credential-provider-imds": "^4.4.16", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-bE2qh8ww4iClO1jHsBXdOE8FUgzDbdxbyorNjSCoPSkQd51k3jODItuPZfuwcLHZqDXsH+bI4AMHhqtuyR7mSg=="], + + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.69", "", { "dependencies": { "@aws-sdk/core": "^3.977.8", "@aws-sdk/types": "^3.974.4", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-9kpTNdZTrcqXTfhxM7fgl9Z68ek3Fu5oe3Yf+A/pJGibEqpgZxz2tSY7SinmyCIU2PJ+ygY4FPoBBnLpocMtrQ=="], + + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.973.13", "", { "dependencies": { "@aws-sdk/core": "^3.977.8", "@aws-sdk/nested-clients": "^3.997.43", "@aws-sdk/token-providers": "3.1111.0", "@aws-sdk/types": "^3.974.4", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-Oc81qauMPzUoTnAS2YKpNwY6sY/LUyQTEeaf6yP197WMxkEBQfcKLR1MFpD7+pNTubXnfkH6gwpji+Gc7iyD2Q=="], + + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.75", "", { "dependencies": { "@aws-sdk/core": "^3.977.8", "@aws-sdk/nested-clients": "^3.997.43", "@aws-sdk/types": "^3.974.4", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-YPN6uoGDgjjjeVFZrcOeCJqmB6zpXoeeNgIjqe+DexJaWqdjVfCCe+VAZwli9Z2h8KhFW8oxkO39emQ1tyz/Mw=="], + + "@aws-sdk/lib-storage": ["@aws-sdk/lib-storage@3.1115.0", "", { "dependencies": { "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "buffer": "5.6.0", "events": "3.3.0", "stream-browserify": "3.0.0", "tslib": "^2.6.2" }, "peerDependencies": { "@aws-sdk/client-s3": "^3.1115.0" } }, "sha512-/wyfCZkIYBI6ZgyaPOayAIZxqxx3JeFaqBkWvL9RgqD7PV5c99dXRgkUKmnljAHVXSsR0hxXCYy9XukuBB+rYw=="], + + "@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.972.74", "", { "dependencies": { "@aws-sdk/core": "^3.977.8", "@aws-sdk/signature-v4-multi-region": "^3.996.45", "@aws-sdk/types": "^3.974.4", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-2lzoV2z2QO5KJZYGOCnIZ1WVQgzMECvwuzr1xb034a++8QW4U4eGrmC2u4yg1xvNv4TLL/Uv5DLyuAiw0b9z7Q=="], + + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.43", "", { "dependencies": { "@aws-sdk/core": "^3.977.8", "@aws-sdk/signature-v4-multi-region": "^3.996.45", "@aws-sdk/types": "^3.974.4", "@smithy/core": "^3.31.1", "@smithy/fetch-http-handler": "^5.6.13", "@smithy/node-http-handler": "^4.9.13", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-bit+VpqWNyi3wHxFoTsTliNXimCSL2r2OeDTm7ZrG+YsTZ2D7ofDJ6r/t9PVBn80i6/v0X2h9Tgw6QP2MAKfPw=="], + + "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.45", "", { "dependencies": { "@aws-sdk/types": "^3.974.4", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-bBuyztukzXq6plzFGHAWiQt0QXo+HL8b8lX5cFTzkez/74PtS1c0qPFCIVuHkyoT+miH2qOjAcm1/yoro2ESPA=="], + + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1111.0", "", { "dependencies": { "@aws-sdk/core": "^3.977.8", "@aws-sdk/nested-clients": "^3.997.43", "@aws-sdk/types": "^3.974.4", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-JfljgoVtl+s3Qy21n9a7Z48uCQaOXcN74KJ3TEQfPoB293GrXFSt6HSQJF1sTZ8c/5QedEvd3NjJQMO4u9qa5A=="], + + "@aws-sdk/types": ["@aws-sdk/types@3.974.4", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-dSFDNG00MEz0/xl5gxL62giLd1iYyJsTxZ1I1DOj6lC+bbgLB4TRsYClJg3b62dhXT1uATzsTNXPnC+33EJV3A=="], + + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.39", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-FTti8DS5MMWXNUWiRwXAJeYS+0GHHiMy0+7XOhcwk63ILHmfS2UFy2z/HNpZCSOJJ3P3dnWY6hfYNW3DF0nXUA=="], + + "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="], + "@azure/abort-controller": ["@azure/abort-controller@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-fNAjWnA/nZ2jz31kxR/AqRaUT8ewHBw/WuBIosK0moMy1C9e5ValbDfFdIxJzVOOYaYkV/b2F1S4H/aHiqfVQg=="], "@azure/core-auth": ["@azure/core-auth@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-util": "^1.13.0", "tslib": "^2.6.2" } }, "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg=="], @@ -202,41 +241,41 @@ "@base-ui/utils": ["@base-ui/utils@0.3.2", "", { "dependencies": { "@babel/runtime": "^7.29.2", "@floating-ui/utils": "^0.2.12", "reselect": "^5.2.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-oWy1aq/I2GmYjpl4PhEAhzflF8VPGKgZeq0xAWTbfD5KBWyxcN0ZP2+WHSUm/5Z6lVMBDLReLcoXwSYoRc/zNQ=="], - "@better-auth/core": ["@better-auth/core@1.6.27", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.1", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.4.0", "jose": "^6.1.0", "kysely": "^0.28.5 || ^0.29.0", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types", "@opentelemetry/api"] }, "sha512-A6/mQW4AT2kSHCRZDh9+k8jPUqnCUUCymzBgIroK0l60wLioMtJdcmZiTwrAn6KVUw+4XWw64MgaRn7LOie3wg=="], + "@better-auth/core": ["@better-auth/core@1.7.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.41.1", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.1", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.4.0", "jose": "^6.1.0", "kysely": "^0.28.5 || ^0.29.0", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types", "@opentelemetry/api"] }, "sha512-eZ9lqcnVLMZ3QtUByRo4VZqkB1ESyRddd9NfWjBdDPgh+jcwLScoIUAqhtHLR8zaSUJZah8OLGlkzObyPdUH7A=="], - "@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@1.6.27", "", { "peerDependencies": { "@better-auth/core": "^1.6.27", "@better-auth/utils": "0.4.2", "drizzle-orm": "^0.45.2" }, "optionalPeers": ["drizzle-orm"] }, "sha512-BDJ02ra/fji/ah3aIpxjjNOu/JQVex0UisxS4S0vGZZyiAs+DL24mn3/iovyD5dWB3u3NaGg1n8zpTOntiIXcg=="], + "@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@1.7.1", "", { "peerDependencies": { "@better-auth/core": "^1.7.1", "@better-auth/utils": "0.4.2", "drizzle-orm": "^0.45.2 || >=1.0.0-rc.1 <2.0.0" }, "optionalPeers": ["drizzle-orm"] }, "sha512-qlqNyg5V9bXHSP68/vtlsiZayhR4hgvEGiS/E3SIj8bCpWWFGmyQkxJbQCqpBmC7vT30wE/kNtJMHIgnV3rkiw=="], - "@better-auth/kysely-adapter": ["@better-auth/kysely-adapter@1.6.27", "", { "peerDependencies": { "@better-auth/core": "^1.6.27", "@better-auth/utils": "0.4.2", "kysely": "^0.28.17 || ^0.29.0" }, "optionalPeers": ["kysely"] }, "sha512-aavv7W4+b3QVObYo166baplWvwocCk8ORDxRqQ9ytzVl/waiUpSXAOtDHdXZHFsoVHFVPtEzyEq0Tqr3Qvvfig=="], + "@better-auth/kysely-adapter": ["@better-auth/kysely-adapter@1.7.1", "", { "peerDependencies": { "@better-auth/core": "^1.7.1", "@better-auth/utils": "0.4.2", "kysely": "^0.28.17 || ^0.29.0" }, "optionalPeers": ["kysely"] }, "sha512-yWCpE1cZpMUj37nD6JFDK+GDR8zS37L5WI73il3qbU9TXtWsxUQKc/5c3IHsHizWQsmcQI8uv2pAFKxsRDa+AQ=="], - "@better-auth/memory-adapter": ["@better-auth/memory-adapter@1.6.27", "", { "peerDependencies": { "@better-auth/core": "^1.6.27", "@better-auth/utils": "0.4.2" } }, "sha512-p4NB37MdaVFkxkpLEAKjORgTPhaOAPu1M2zgQXiTJJ3F6Uq3Y1YcCgoz+VxJu0TQfxibCsJD/dZlJMVgf+CF7A=="], + "@better-auth/memory-adapter": ["@better-auth/memory-adapter@1.7.1", "", { "peerDependencies": { "@better-auth/core": "^1.7.1", "@better-auth/utils": "0.4.2" } }, "sha512-6NX1yv88DeqdoG7owYFqKwlrDGaIPhsC52JUGrUgeGVKyOq8a/6hHlHsqG1C2FwT23SHQiKVYCEAG9N6aH5OvQ=="], - "@better-auth/mongo-adapter": ["@better-auth/mongo-adapter@1.6.27", "", { "peerDependencies": { "@better-auth/core": "^1.6.27", "@better-auth/utils": "0.4.2", "mongodb": "^6.0.0 || ^7.0.0" }, "optionalPeers": ["mongodb"] }, "sha512-IOYbJMIjEC//f+JpFlmIQjh6x1R/rGAfdzlojFk6HeAJqbsELuMcI+27dIoesbfHLF/icTrSpZtK986XhJrCfA=="], + "@better-auth/mongo-adapter": ["@better-auth/mongo-adapter@1.7.1", "", { "peerDependencies": { "@better-auth/core": "^1.7.1", "@better-auth/utils": "0.4.2", "mongodb": "^6.0.0 || ^7.0.0" }, "optionalPeers": ["mongodb"] }, "sha512-9ILTcNqhG37QK//qR4UhYLyKzNqq6w6zVTf5KX6xkiTjNcV7Oh1yS31lkIJEVTqRNcy9AoV6FZMW6Bbsm8IMDA=="], - "@better-auth/prisma-adapter": ["@better-auth/prisma-adapter@1.6.27", "", { "peerDependencies": { "@better-auth/core": "^1.6.27", "@better-auth/utils": "0.4.2", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@prisma/client", "prisma"] }, "sha512-v7DXVyaFbkrfLoiFDtcVF7BkEZgFa/DgEGE7XjrAXmMACa5pjDvb7lm8W5X+/qgIbQP04eThhgFQ6EWOsjr8OQ=="], + "@better-auth/prisma-adapter": ["@better-auth/prisma-adapter@1.7.1", "", { "peerDependencies": { "@better-auth/core": "^1.7.1", "@better-auth/utils": "0.4.2", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@prisma/client", "prisma"] }, "sha512-ZiUcafQ85InAofcUjyGgCPjKLfQjXr9SvDmMjuFUW8oEbreA6C6GaFAEA77VuV2doZUQlzvQQ4gCoTmS28W92A=="], - "@better-auth/telemetry": ["@better-auth/telemetry@1.6.27", "", { "peerDependencies": { "@better-auth/core": "^1.6.27", "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.1" } }, "sha512-aYrSiVWQfua8w5YX8X1cM6ca48EdS0t5k+CvYXIsruaNIpR1DXMe1DOD0M5FWAVABnlCf9joyHXbFRSIZSNY/A=="], + "@better-auth/telemetry": ["@better-auth/telemetry@1.7.1", "", { "peerDependencies": { "@better-auth/core": "^1.7.1", "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.1" } }, "sha512-kLKjMfFlTbyt49DGeI9okHAsn0MtBZcMoQYKaEdgR0H3BHzqqyzePcQz/hxAmRgjB4p/6inise3zJwhX0sgXrQ=="], "@better-auth/utils": ["@better-auth/utils@0.4.2", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-AUxrvu+HaaODsUyzDxFgwd/8RZ1yZaYo42LXKSrU2oGgR38pS1ij8nqQKNgtTWoYGpNevNXtCfgTy6loHveW9A=="], "@better-fetch/fetch": ["@better-fetch/fetch@1.3.1", "", {}, "sha512-ABkD1WhyfPZprKRQI3bhATjeiFuNWC9PXhfGWqL+sg/gKrM977oFrYkdb4msM3hgUGonr7KlOsOFT5TU2rht9g=="], - "@biomejs/biome": ["@biomejs/biome@2.5.8", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.5.8", "@biomejs/cli-darwin-x64": "2.5.8", "@biomejs/cli-linux-arm64": "2.5.8", "@biomejs/cli-linux-arm64-musl": "2.5.8", "@biomejs/cli-linux-x64": "2.5.8", "@biomejs/cli-linux-x64-musl": "2.5.8", "@biomejs/cli-win32-arm64": "2.5.8", "@biomejs/cli-win32-x64": "2.5.8" }, "bin": { "biome": "bin/biome" } }, "sha512-aeAeeJB9fSDc7Gq+2GqpQxA0qBj6gj1k2R6L1cYqGePKP/baIq1WX8y6B+D+nRsO5ViQL22K/8IwbqERW0q1nw=="], + "@biomejs/biome": ["@biomejs/biome@2.5.9", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.5.9", "@biomejs/cli-darwin-x64": "2.5.9", "@biomejs/cli-linux-arm64": "2.5.9", "@biomejs/cli-linux-arm64-musl": "2.5.9", "@biomejs/cli-linux-x64": "2.5.9", "@biomejs/cli-linux-x64-musl": "2.5.9", "@biomejs/cli-win32-arm64": "2.5.9", "@biomejs/cli-win32-x64": "2.5.9" }, "bin": { "biome": "bin/biome" } }, "sha512-KkgCvdHB4IhtpHpF564plA9jo6fDOwWGQ/3jvreLzgOtRLEDoPqr7QO9qejNA8jKwDsSkAKr77hqBHnyUbIw4g=="], - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.5.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mk1QON9PHllvvLN5gU3f4rMxeh4syK5p9OvKyWH6/W8ueh04uaC8TUXXByhGufWf/y5mQc03ZLM45zU+cmqMjA=="], + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.5.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-am22pX2aBqznqq1eMyIj/bZ++riF3Lk6ct7cbv+gQK0csFhr+d8O0RkOi2FF2qSgFgANbqNkIZ0/PxlnW2pLFg=="], - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.5.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-bsGwFMBNyHPyiLSsQcZJxdoRrg1V4JL+d7wEsvUBczlP9U9lwM+7mzQHxI4o1mhBsTmdOBbAb6fHU3Z3snN45w=="], + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.5.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-l44KWDHLDvEnD0N/XcrVs7VXb3A18xL7QS3WB0eL93wbmk529ffIG55vleGCqaunpRUjLrdnjK05Qki1dsjylg=="], - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.5.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-XmFiA0WPYFC+uiUDC8WRFzAIH9bo7vwQLav38Uoq4ETC+T/+uBi0TsYGJECkugY3r8USl3jc+Ae2/irAF6F2lQ=="], + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.5.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-ICaK+IYaVZvKbBxX2rwrPT0DdUDMnE9Vm3nQGe+mltQPmUg19pONzkPWGdY4FCsoreDETWDynvdt4ysCbF5gNQ=="], - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.5.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-VcJNbstduTHx83NGAdhp78/JOcP45BZHXL7yNsfI1uGzdUgegAz2s+mSoT7wK6PBNzLoqG0zDOXaz/RQYVtSiw=="], + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.5.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-7ImVPwBLCtkmpR5esd8RHhTqW94f0JLJQum6AneYcy94jRm18TaPPm7slaigGzFhfgt3QiD1Vj52LKmBAnKizA=="], - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.5.8", "", { "os": "linux", "cpu": "x64" }, "sha512-S5wcm9OBDvLHodD4PUaN488hCpco9QD/9ZxuYJiw4euWtr/oQvLR72z2ixItH8Wd5BCm6FZaeb+YNvOoM1xHtQ=="], + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.5.9", "", { "os": "linux", "cpu": "x64" }, "sha512-z22Q/zFYSvbIJfW1CbfZPu4X8PddS6Qd2ORbc6h+aT6EcwAxUF3m6fA4HjNvA3TU4X0dTJRwNPB165ES3PJXzg=="], - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.5.8", "", { "os": "linux", "cpu": "x64" }, "sha512-kKmiyokeISRGq2FLwvr+TzsgBusfxaZ0FZNLcOYOpCK/78tRrEjeEBLvq3xLZMpqbANgJdRPI7vZX8ZL37u9/w=="], + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.5.9", "", { "os": "linux", "cpu": "x64" }, "sha512-RXGaD0o1/pTTguYw1aeDJh9ad6Lfrui0fI7mBderTyGr7WuUJkBIttgLkR3XJyoxOkkgfBDspaUT8wXArTqLZw=="], - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.5.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-nILH0mzm3Hi3iEdd7o7GpB8kBR/mSQwfQG/tyBqyNrY2GFtcgwfV9nV8xLmbtUpMNY/Oi0Ml1XgfR4flOdq+AA=="], + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.5.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-nHK+/HHC+D0ogAHUxomgoSTdjImb6fmNNVTKmf0tyu4eDL1DqPKIHc+i+UL8+b0RnAu8224qo8F2tCVnaT0A3w=="], - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.5.8", "", { "os": "win32", "cpu": "x64" }, "sha512-I2czzXTY61f3nFJxXoMDq80t7MivxDEnCjE+8sDKoFfcKMaoQdkqhIFQ3KyY0XLzeSpUBYeNAXgD+iOV/BU0VA=="], + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.5.9", "", { "os": "win32", "cpu": "x64" }, "sha512-Yiq0H56LjXSSw/hd9YkXgSLQfzyDJzbzU2TezozxyNw+uKWAqOtqGVvBfzKRRDiaFF5avGAhHdWKx7LtDOShUw=="], "@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="], @@ -256,7 +295,7 @@ "@copilotkit/a2ui-renderer": ["@copilotkit/a2ui-renderer@1.67.1", "", { "dependencies": { "@a2ui/web_core": "0.10.4", "clsx": "^2.1.1", "lit": "^3.3.2", "zod": "^3.25.75", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" }, "optionalPeers": ["react", "react-dom"] }, "sha512-/fELhn0gvC+LCurKwdxrF1BRiPbpYUU5UmZjwy1bu1q8brQbXs8UjCcz7oCiCRfhXirzAwEH2LwdPS9ElHJPNw=="], - "@copilotkit/aimock": ["@copilotkit/aimock@1.38.0", "", { "peerDependencies": { "jest": ">=29", "vitest": ">=3" }, "optionalPeers": ["jest", "vitest"], "bin": { "aimock": "dist/aimock-cli.js", "llmock": "dist/cli.js" } }, "sha512-zrGXeSu0/LkHQgrM9Z68uU/k7qpYbnZ25i9djpNlYyDCDCUgKaQPbG1iIYOc+4vuigJmD91ujcK36mBKWz+JVw=="], + "@copilotkit/aimock": ["@copilotkit/aimock@1.39.0", "", { "peerDependencies": { "jest": ">=29", "vitest": ">=3" }, "optionalPeers": ["jest", "vitest"], "bin": { "aimock": "dist/aimock-cli.js", "llmock": "dist/cli.js" } }, "sha512-AWw4vmW2hBchHoggh0G4McWGmGZD6wtXAehL6K5ncWF5lVIjlv++bPmxmRwrpQCi/K4/xK10N9Zp9srJYipEJw=="], "@copilotkit/channels-core": ["@copilotkit/channels-core@0.8.1", "", { "dependencies": { "@ag-ui/client": "0.0.57", "@ag-ui/core": "0.0.57", "@copilotkit/channels-ui": "~0.8.1", "@copilotkit/core": "^1.67.0", "@copilotkit/shared": "^1.67.0", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "vitest": "^4.0.0" }, "optionalPeers": ["vitest"] }, "sha512-FOopmGLUncVCZ7g2s6opvJZoiAFaU49cgu2+T6TGtVfTVZLTaO5OzDNEOafIqh7xMm6TfMWCjr1o1oZ3njKKpw=="], @@ -284,13 +323,21 @@ "@copilotkit/web-inspector": ["@copilotkit/web-inspector@1.67.1", "", { "dependencies": { "@ag-ui/client": "0.0.57", "@copilotkit/core": "1.67.1", "@copilotkit/shared": "1.67.1", "lit": "^3.2.0", "lucide": "^0.525.0", "marked": "^12.0.2" } }, "sha512-DskVsDrUtZPt9Ymh6UxGElUIETBq8Lxa/vtl9eUdvJ+Ply1xUaGKwfNUpIaGiMWzTML4X7diSVdVrmemlnlYjQ=="], + "@daytona/analytics-api-client": ["@daytona/analytics-api-client@0.205.1", "", { "dependencies": { "axios": "^1.6.1" } }, "sha512-/kGNw6RGI7psf5OYqOPTfyN2tDW/206XBMXFQxmD3Ljuor/xk17WrONRah35OJy62lk+BzvCRtnkGBNEL4EjhQ=="], + + "@daytona/api-client": ["@daytona/api-client@0.205.1", "", { "dependencies": { "axios": "^1.6.1" } }, "sha512-Rmeny9+8jm+7mx/0B4FnfddqXSRuQmMTRxlbYaI8ekVidvXFA5YsNExBzzijPZW2piCDfUhlJDt6qKVsrhZe4Q=="], + + "@daytona/sdk": ["@daytona/sdk@0.205.1", "", { "dependencies": { "@aws-sdk/client-s3": "^3.1106.0", "@aws-sdk/lib-storage": "^3.1106.0", "@daytona/analytics-api-client": "0.205.1", "@daytona/api-client": "0.205.1", "@daytona/toolbox-api-client": "0.205.1", "@iarna/toml": "^2.2.5", "@opentelemetry/api": "^1.9.1", "@opentelemetry/exporter-trace-otlp-http": "^0.221.0", "@opentelemetry/instrumentation-http": "^0.221.0", "@opentelemetry/otlp-exporter-base": "0.221.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/sdk-node": "^0.221.0", "@opentelemetry/sdk-trace-base": "^2.10.0", "@opentelemetry/semantic-conventions": "^1.43.0", "axios": "^1.19.0", "busboy": "^1.0.0", "dotenv": "^17.4.2", "expand-tilde": "^2.0.2", "fast-glob": "^3.3.0", "form-data": "^4.0.4", "isomorphic-ws": "^5.0.0", "pathe": "^2.0.3", "shell-quote": "^1.8.2", "socket.io-client": "^4.8.1", "tar": "^7.5.22", "tslib": "2.8.1", "ws": "^8.21.3" } }, "sha512-gSqWNvNXmFJEdmsB+Z/XbWagnt6xkf6AWYtfAmemvY56Ji5oMYQ3Fk6VyeQpArvYCLQTgQy/MifiUXMLlChwYA=="], + + "@daytona/toolbox-api-client": ["@daytona/toolbox-api-client@0.205.1", "", { "dependencies": { "axios": "^1.6.1" } }, "sha512-FmiNbaAdeOiV12No+cRqs7bwklu+q4iA1QW1gA+x235imHaJ5Y8nYOjMV20tOhytGhNm0W23nz7WqyVao6A2pw=="], + "@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.75.1", "", { "dependencies": { "@dotenvx/primitives": "^0.8.0", "commander": "^11.1.0", "conf": "^10.2.0", "dotenv": "^17.2.1", "enquirer": "^2.4.1", "env-paths": "^2.2.1", "execa": "^5.1.1", "fdir": "^6.2.0", "ignore": "^5.3.0", "object-treeify": "1.1.33", "open": "^8.4.2", "picomatch": "^4.0.4", "systeminformation": "^5.22.11", "undici": "^7.11.0", "which": "^4.0.0", "yocto-spinner": "^1.1.0" }, "bin": { "dotenvx": "src/cli/dotenvx.js" } }, "sha512-/BITOC9dmS/edY2zQwZNicQ059O6RKabtQfyEafV0nGtfYRNHYy1DIPiYVcov40+tob9hfmBnbR963dS+EQ1DQ=="], "@dotenvx/primitives": ["@dotenvx/primitives@0.8.0", "", {}, "sha512-VYJy0uhFm9zTJ1TxBaW/pA8bjbOM/OttaNMwZ1RHG4JKyRG7DhSdiqD1ipQoAyoD22olUtxbP78W9xY3Wd11bg=="], "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], - "@envelop/core": ["@envelop/core@5.5.1", "", { "dependencies": { "@envelop/instrumentation": "^1.0.0", "@envelop/types": "^5.2.1", "@whatwg-node/promise-helpers": "^1.2.4", "tslib": "^2.5.0" } }, "sha512-3DQg8sFskDo386TkL5j12jyRAdip/8yzK3x7YGbZBgobZ4aKXrvDU0GppU0SnmrpQnNaiTUsxBs9LKkwQ/eyvw=="], + "@envelop/core": ["@envelop/core@5.6.0", "", { "dependencies": { "@envelop/instrumentation": "^1.0.0", "@envelop/types": "^5.2.1", "@whatwg-node/promise-helpers": "^1.2.4", "tslib": "^2.5.0" } }, "sha512-cD7HNfAzJVw/0Pxneu51UAKzUGLvkctk9rr9DVJ9b7FDe4nSa9kAGMRxx145H6ooELIUMjTd2buk3PuvjJmp/A=="], "@envelop/instrumentation": ["@envelop/instrumentation@1.0.0", "", { "dependencies": { "@whatwg-node/promise-helpers": "^1.2.1", "tslib": "^2.5.0" } }, "sha512-cxgkB66RQB95H3X27jlnxCRNTmPuSTgmBAq6/4n2Dtv4hsk4yz8FadA1ggmd0uZzvKqWD6CR+WFgTjhDqg7eyw=="], @@ -364,32 +411,40 @@ "@fontsource-variable/inter": ["@fontsource-variable/inter@5.3.0", "", {}, "sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA=="], - "@graphql-tools/executor": ["@graphql-tools/executor@1.5.7", "", { "dependencies": { "@graphql-tools/utils": "^11.2.2", "@graphql-typed-document-node/core": "^3.2.0", "@repeaterjs/repeater": "^3.1.0", "@whatwg-node/disposablestack": "^0.0.6", "@whatwg-node/promise-helpers": "^1.0.0", "tslib": "^2.4.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-UcXVClkBml+qyGEsQxfEaAkboqySVGFUd9ivn7pDc9jZSckgF6zL21cNxuRH5ZA2exneV3PTtcc0I0rDOdE0Tg=="], + "@graphql-tools/executor": ["@graphql-tools/executor@2.0.0", "", { "dependencies": { "@graphql-tools/utils": "^12.0.0", "@graphql-typed-document-node/core": "^3.2.0", "@repeaterjs/repeater": "^3.1.0", "@whatwg-node/disposablestack": "^0.0.6", "@whatwg-node/promise-helpers": "^1.0.0", "tslib": "^2.4.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-BjoqO5UfcV3BUqamGtHNm4ITBk040VOmWql/MrUefPo30x6t4v1+engfjgAjWikMsRPLU4ZC6e2QjICLucnOTA=="], "@graphql-tools/merge": ["@graphql-tools/merge@9.2.3", "", { "dependencies": { "@graphql-tools/utils": "^12.0.0", "tslib": "^2.4.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-cKRoXqJGy2zSRBLvotQpkACbXlHAb0yuHLN0l0ypKGCuL3NnF0zofYalkBJqiBxxxpK0lr3s9uowKFHCKi1/ZQ=="], "@graphql-tools/schema": ["@graphql-tools/schema@10.1.0", "", { "dependencies": { "@graphql-tools/merge": "^9.2.3", "@graphql-tools/utils": "^12.0.0", "tslib": "^2.4.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-wao48XQnfY631s3jXoNrhEHvCI8mlKXmIuWrR7F6zAdv92VuSOfHoq9P9KL2EnUMgBUnaStnByOx9Mn6RieWDg=="], - "@graphql-tools/utils": ["@graphql-tools/utils@10.11.0", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "@whatwg-node/promise-helpers": "^1.0.0", "cross-inspect": "1.0.1", "tslib": "^2.4.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-iBFR9GXIs0gCD+yc3hoNswViL1O5josI33dUqiNStFI/MHLCEPduasceAcazRH77YONKNiviHBV8f7OgcT4o2Q=="], + "@graphql-tools/utils": ["@graphql-tools/utils@11.2.2", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "@whatwg-node/promise-helpers": "^1.0.0", "cross-inspect": "1.0.1", "tslib": "^2.4.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-Do2A/t6ayqOma8m7PvpYMsCBswL+NB35BQSng4GWSBqafL2/BnfAZy/NSENPwV721Rm2fG0BHETFC0+FJJZmLw=="], "@graphql-typed-document-node/core": ["@graphql-typed-document-node/core@3.2.0", "", { "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ=="], "@graphql-yoga/logger": ["@graphql-yoga/logger@2.0.1", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-Nv0BoDGLMg9QBKy9cIswQ3/6aKaKjlTh87x3GiBg2Z4RrjyrM48DvOOK0pJh1C1At+b0mUIM67cwZcFTDLN4sA=="], - "@graphql-yoga/plugin-defer-stream": ["@graphql-yoga/plugin-defer-stream@3.21.3", "", { "dependencies": { "@graphql-tools/utils": "^10.11.0" }, "peerDependencies": { "graphql": "^15.2.0 || ^16.0.0", "graphql-yoga": "^5.21.3" } }, "sha512-DnwO7lDWGZMvdv3ps5T15km9I9+pftyfS3XXA9oYJmYjexPJQJQThh7+cEgMeCxOvAZjqJfFWhLyrEdDzfVZtw=="], + "@graphql-yoga/plugin-defer-stream": ["@graphql-yoga/plugin-defer-stream@3.22.0", "", { "dependencies": { "@graphql-tools/utils": "^11.2.0" }, "peerDependencies": { "graphql": "^15.2.0 || ^16.0.0 || ^17.0.0", "graphql-yoga": "^5.22.0" } }, "sha512-7F8/ADgCxRIUgGrG0mWEOI5HB0FYQeUj2YbH88+U05Fe72lrGufKpxOPN3O70jECPukh8jKFtyXfqjl5POPEzA=="], "@graphql-yoga/subscription": ["@graphql-yoga/subscription@5.0.5", "", { "dependencies": { "@graphql-yoga/typed-event-target": "^3.0.2", "@repeaterjs/repeater": "^3.0.4", "@whatwg-node/events": "^0.1.0", "tslib": "^2.8.1" } }, "sha512-oCMWOqFs6QV96/NZRt/ZhTQvzjkGB4YohBOpKM4jH/lDT4qb7Lex/aGCxpi/JD9njw3zBBtMqxbaC22+tFHVvw=="], "@graphql-yoga/typed-event-target": ["@graphql-yoga/typed-event-target@3.0.2", "", { "dependencies": { "@repeaterjs/repeater": "^3.0.4", "tslib": "^2.8.1" } }, "sha512-ZpJxMqB+Qfe3rp6uszCQoag4nSw42icURnBRfFYSOmTgEeOe4rD0vYlbA8spvCu2TlCesNTlEN9BLWtQqLxabA=="], - "@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.11.2", "", { "dependencies": { "@types/node": ">=20.0.0", "happy-dom": "^20.11.2" } }, "sha512-7fkpoXZWzyxaBkzRtlD0wRU5ckxbNT4j6BywzpSYh+SFPsGxEVmNR9KHaMwM2xkHB0pADQLl4Z8DSrztECJ7Ww=="], + "@grpc/grpc-js": ["@grpc/grpc-js@1.14.4", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ=="], + + "@grpc/proto-loader": ["@grpc/proto-loader@0.8.1", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.5", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg=="], + + "@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.11.6", "", { "dependencies": { "@types/node": ">=20.0.0", "happy-dom": "^20.11.6" } }, "sha512-ZQ47qUTeNbGhHkCGExJ1oZhruoxKRaxO44RgFETl3T4c1rRxIBlAOnM3SAH4XHu7Ue2owJXP+jOx1vOuKuxcSg=="], "@hono/node-server": ["@hono/node-server@1.19.17", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ=="], + "@iarna/toml": ["@iarna/toml@2.2.5", "", {}, "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg=="], + "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], "@iconify/utils": ["@iconify/utils@3.1.4", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "import-meta-resolve": "^4.2.0" } }, "sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw=="], + "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], + "@jetbrains/websandbox": ["@jetbrains/websandbox@1.3.1", "", {}, "sha512-YTl3MJXbAkYDyuRhWqlQoo7eY2jaLGRqUkx4LVRvxr0VnK7s5bu0p2KAGOWZIBf6I3pmuuq+JzBDHw1b0fV0/Q=="], "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], @@ -402,13 +457,15 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - "@langchain/core": ["@langchain/core@1.2.7", "", { "dependencies": { "@cfworker/json-schema": "^4.0.2", "@standard-schema/spec": "^1.1.0", "js-tiktoken": "^1.0.12", "langsmith": ">=0.5.0 <1.0.0", "mustache": "^4.2.0", "p-queue": "^6.6.2", "zod": "^3.25.76 || ^4" } }, "sha512-NKEjQimC9IR7YKkfirH9Hq1BIFFKd4tjhWvbtjggS+5mE+bM4ZeFwTIUe9BDIz9zi3hzG4DLbRMi5E8k/PwVTA=="], + "@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="], + + "@langchain/core": ["@langchain/core@1.2.9", "", { "dependencies": { "@cfworker/json-schema": "^4.0.2", "@standard-schema/spec": "^1.1.0", "js-tiktoken": "^1.0.12", "langsmith": ">=0.5.0 <1.0.0", "mustache": "^4.2.0", "p-queue": "^6.6.2", "zod": "^3.25.76 || ^4" } }, "sha512-conzSEj9Zu1AyXJLXsSbgrtxtxinmI1yGqQ5CIJZSoV5rvv+yvQE/vgBnoySpBQ/bl3YPgj2FL/gbDjWykLSfg=="], - "@langchain/langgraph": ["@langchain/langgraph@1.4.9", "", { "dependencies": { "@langchain/langgraph-checkpoint": "^1.1.3", "@langchain/langgraph-sdk": "~1.9.28", "@langchain/protocol": "^0.0.18", "@standard-schema/spec": "1.1.0" }, "peerDependencies": { "@langchain/core": "^1.1.48", "zod": "^3.25.32 || ^4.2.0" } }, "sha512-EvD9rS66Cya09y6rbMgD3Ir8miAkJQFo7FyJOPRPO736Kz3y5TeyeBDOS8ctff/jRc788bPijHx2NVFM79Qqig=="], + "@langchain/langgraph": ["@langchain/langgraph@1.4.12", "", { "dependencies": { "@langchain/langgraph-checkpoint": "^1.1.5", "@langchain/langgraph-sdk": "~1.9.30", "@langchain/protocol": "^0.0.18", "@standard-schema/spec": "1.1.0" }, "peerDependencies": { "@langchain/core": "^1.1.48", "zod": "^3.25.32 || ^4.2.0" } }, "sha512-63iH/igH5Fh5fHqmWp09YYWaDKKB9v4RCmYNJBrnQ224rFRbjebgyYW6o5RCczN5FZxIhQj+xT51rrNmG0zi5A=="], - "@langchain/langgraph-checkpoint": ["@langchain/langgraph-checkpoint@1.1.3", "", { "peerDependencies": { "@langchain/core": "^1.1.48" } }, "sha512-wgzdQNeEsdw1e+4lvlj0tdq/RYR/k1vPin10g0ymGoehZDDgd9nvIllGXSXN4TFgF9sf5qQP/KTkOcLfeseIhA=="], + "@langchain/langgraph-checkpoint": ["@langchain/langgraph-checkpoint@1.1.5", "", { "peerDependencies": { "@langchain/core": "^1.1.48" } }, "sha512-BwDwl5VeTOh6CVuiIPgsUgfK51vTJDMSbFcSCUfjJWsl8/DPdK/mbv+ejxJstkSk/BlSPMP4JfXWcN6jD2ea2Q=="], - "@langchain/langgraph-sdk": ["@langchain/langgraph-sdk@1.9.29", "", { "dependencies": { "@langchain/protocol": "^0.0.18", "@types/json-schema": "^7.0.15", "p-queue": "^9.0.1", "p-retry": "^7.1.1" }, "peerDependencies": { "@langchain/core": "^1.1.48", "react": "^18 || ^19", "react-dom": "^18 || ^19", "svelte": "^4.0.0 || ^5.0.0", "vue": "^3.0.0" }, "optionalPeers": ["react", "react-dom", "svelte", "vue"] }, "sha512-W+ccugM5EzBHvaOieyYxlSbhAy6HWF8Tyn6b/BlTWuFd8xtcnQOz2WuFyofcAc9+aQHE+6yHJfIywGOvjxS+bA=="], + "@langchain/langgraph-sdk": ["@langchain/langgraph-sdk@1.9.30", "", { "dependencies": { "@langchain/protocol": "^0.0.18", "@types/json-schema": "^7.0.15", "p-queue": "^9.0.1", "p-retry": "^7.1.1" }, "peerDependencies": { "@langchain/core": "^1.1.48", "react": "^18 || ^19", "react-dom": "^18 || ^19", "svelte": "^4.0.0 || ^5.0.0", "vue": "^3.0.0" }, "optionalPeers": ["react", "react-dom", "svelte", "vue"] }, "sha512-hPzbhenvvFRn46PHnlkad3E+TuUIfRhNGpekR5cY12D1WKh3Viwfyz9HpzCbfVebChU9Kgk494V9RJTnh1jrPQ=="], "@langchain/protocol": ["@langchain/protocol@0.0.18", "", {}, "sha512-XW1egQtPfsGI41w2AMZNFZrUIwFSQHTjVMZs0OaTpCAvht/QLoaPN8FQcsysMVypOhupG28J29yOorrc70otBQ=="], @@ -424,7 +481,7 @@ "@lukeed/uuid": ["@lukeed/uuid@2.0.1", "", { "dependencies": { "@lukeed/csprng": "^1.1.0" } }, "sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w=="], - "@mermaid-js/parser": ["@mermaid-js/parser@1.2.0", "", { "dependencies": { "@chevrotain/types": "~11.1.2" } }, "sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA=="], + "@mermaid-js/parser": ["@mermaid-js/parser@1.2.1", "", { "dependencies": { "@chevrotain/types": "~11.1.2" } }, "sha512-n12NohV3mrUyUL2o93IgG/ifeW9FTyeJn3zDxkhwa8MJ9Fxg3HQMlA3RiGmD/3UnJvheztkjjQAjA2T4LmUcpw=="], "@microsoft/agents-activity": ["@microsoft/agents-activity@1.7.2", "", { "dependencies": { "zod": "3.25.75" } }, "sha512-aMPZFuyIkdAdKxscyEZ6o6m60rB+JbWE4n02HzP3QOmILWBfddRMxYlK6gFLd86Wpf1hYGvfJBdpx8Av5ZRBIA=="], @@ -448,6 +505,64 @@ "@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="], + "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.221.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-OlanaW1vv7ufTqQ3/fPLI4arGt5ZoM+P8abOMki6uEYnpRazepSWDwDnnw+la7kE26SHVC18//SMccrDvLKOXQ=="], + + "@opentelemetry/configuration": ["@opentelemetry/configuration@0.221.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "yaml": "^2.8.3" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0" } }, "sha512-uE9y56Zdi9Gt/RdxYnVOo3YmFZkKJJMA0gqtBe8wh8gdtF5Asqe+Oh/TWiDtFb1s+31jNY4CWgnfIB1KOITfFA=="], + + "@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.10.0", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-bvyMcgLEkozzSzpEEEo1OMoeQ97bxj6Qs2uN3mPrSdDvObMI1myffD/BPqcLlzZO9//d1SqQA/WPw7Cz2AiqhA=="], + + "@opentelemetry/core": ["@opentelemetry/core@2.10.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-/wNZ8twnEQQA4HoHu22+vcsdru6pWPWxW+7w+FlxT6Id7PE/WIbZmVKkte+PF72e0F2dnImFeHD2syyE1Mw6MQ=="], + + "@opentelemetry/exporter-logs-otlp-grpc": ["@opentelemetry/exporter-logs-otlp-grpc@0.221.0", "", { "dependencies": { "@opentelemetry/otlp-exporter-base": "0.221.0", "@opentelemetry/otlp-grpc-exporter-base": "0.221.0", "@opentelemetry/otlp-transformer": "0.221.0", "@opentelemetry/sdk-logs": "0.221.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-txG1G0IrYSsKKMeiWZfj/i5cQmWB+h+hf3HzPpF3RqZVwp+iQQEIsv8Vtmzy6RWVdHdJZfygmVrBI39YTBvWcw=="], + + "@opentelemetry/exporter-logs-otlp-http": ["@opentelemetry/exporter-logs-otlp-http@0.221.0", "", { "dependencies": { "@opentelemetry/otlp-exporter-base": "0.221.0", "@opentelemetry/otlp-transformer": "0.221.0", "@opentelemetry/sdk-logs": "0.221.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-nKXkr4Tomi6fjYVOf+ytcW3dZAVr4v4Bv5gsT6dr2gvpUPJpKgHB4XbMufMsPotRE3g0XH2GwVVCkN2w6SON+Q=="], + + "@opentelemetry/exporter-logs-otlp-proto": ["@opentelemetry/exporter-logs-otlp-proto@0.221.0", "", { "dependencies": { "@opentelemetry/otlp-exporter-base": "0.221.0", "@opentelemetry/otlp-transformer": "0.221.0", "@opentelemetry/sdk-logs": "0.221.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-AH6EY+47gXFaWYgG3hfeOneGiE9xIZGtDBk+9g0sM8NZWzsQhhmqPbQQXJzS7pyCh5jRRr2nYNXVrkCmoojRvQ=="], + + "@opentelemetry/exporter-metrics-otlp-grpc": ["@opentelemetry/exporter-metrics-otlp-grpc@0.221.0", "", { "dependencies": { "@opentelemetry/exporter-metrics-otlp-http": "0.221.0", "@opentelemetry/otlp-grpc-exporter-base": "0.221.0", "@opentelemetry/otlp-transformer": "0.221.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-KOgCtO15FC6C1T/xOqBcr7EyUs7B+7yomGNb5Y97d3s38rPbCCk5sewkmE2b0/itOkQ/PptX8CLlD+kn2mEtTg=="], + + "@opentelemetry/exporter-metrics-otlp-http": ["@opentelemetry/exporter-metrics-otlp-http@0.221.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/otlp-exporter-base": "0.221.0", "@opentelemetry/otlp-transformer": "0.221.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/sdk-metrics": "2.10.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-sRfCKbOzgy8xZQV2as0RzIZlnCmCseCKZGLfRcrpo2CBngJDr+rPtX0zkG0+oUCV5kfQPUoW3W3C96Ag3Y/Clg=="], + + "@opentelemetry/exporter-metrics-otlp-proto": ["@opentelemetry/exporter-metrics-otlp-proto@0.221.0", "", { "dependencies": { "@opentelemetry/exporter-metrics-otlp-http": "0.221.0", "@opentelemetry/otlp-exporter-base": "0.221.0", "@opentelemetry/otlp-transformer": "0.221.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-YMF4LveY2I3yhw61rn6nmC9FE8U24IZHPeKU1Duc5+sbwjMd8FwZAwba318ImdThCg/HuVQvhm2y6bfgNPnfYg=="], + + "@opentelemetry/exporter-prometheus": ["@opentelemetry/exporter-prometheus@0.221.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/sdk-metrics": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-kW79a20qWESIuAdDrxzg9WKM98twV/NBWBFRAH57ap/+ssZhiCo0hckzKT0zpuwR/gSHrFAQhJL0bYDrnEM34g=="], + + "@opentelemetry/exporter-trace-otlp-grpc": ["@opentelemetry/exporter-trace-otlp-grpc@0.221.0", "", { "dependencies": { "@opentelemetry/otlp-exporter-base": "0.221.0", "@opentelemetry/otlp-grpc-exporter-base": "0.221.0", "@opentelemetry/otlp-transformer": "0.221.0", "@opentelemetry/sdk-trace": "2.10.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-zXminlZedtq9LvOW64CnNkOqk15zV75k8JgtdTuWFge6+jk2m4GmAUm6L2eIiG1o2a2bZxXw2PDrszm+bps0IA=="], + + "@opentelemetry/exporter-trace-otlp-http": ["@opentelemetry/exporter-trace-otlp-http@0.221.0", "", { "dependencies": { "@opentelemetry/otlp-exporter-base": "0.221.0", "@opentelemetry/otlp-transformer": "0.221.0", "@opentelemetry/sdk-trace": "2.10.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-AySXiKoC+meiWm6zdVj5T2LnPDZuatveBby1cMOeQteIWsYXAUxs8Sru13G2pVSPrUXz6vF+og7QVBX6GdC/oQ=="], + + "@opentelemetry/exporter-trace-otlp-proto": ["@opentelemetry/exporter-trace-otlp-proto@0.221.0", "", { "dependencies": { "@opentelemetry/otlp-exporter-base": "0.221.0", "@opentelemetry/otlp-transformer": "0.221.0", "@opentelemetry/sdk-trace": "2.10.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Z9i2T7vgZbWe9rSLYxXVIbeW+XyzUq4rZanW3ZyVNwVDqCsh0EJKUgBWWQ0CZfeuUA+RQPzKgJQHMuWAUnKqXw=="], + + "@opentelemetry/exporter-zipkin": ["@opentelemetry/exporter-zipkin@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/sdk-trace": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-7gsvgf0UDoJ4l9ObrwBmz5G/ZogiPk+lq+g5GpLp24YQF/vPM/BSsnOfcLnfinast5ASUgLo78uSC/ObjlnXgg=="], + + "@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.221.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.221.0", "import-in-the-middle": "^3.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-cCk80Z/iRDf/5gfsKMB4f74LqVA5yKETB/9ojPzVW/6/f70iu89nJvGxsFCxx4XfSohaOofkU19kiYm84AiAlw=="], + + "@opentelemetry/instrumentation-http": ["@opentelemetry/instrumentation-http@0.221.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/instrumentation": "0.221.0", "@opentelemetry/semantic-conventions": "^1.29.0", "forwarded-parse": "2.1.2" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-oIP91CPIANuYr09tGFElPFKAh6JUar+awJf1kBRYlaeo9b0gDwZHEB2zBfFlvdNFHm0wAVutMZODVi5smKT30g=="], + + "@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.221.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/otlp-transformer": "0.221.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-UFPIq80OH3Ns/oPFHRj14d4DTOxUo+MUFU8hUiCq5jTqFhdeJnfVSANHT+xp92409cA+oxzvlZCe6NM1wvCuBA=="], + + "@opentelemetry/otlp-grpc-exporter-base": ["@opentelemetry/otlp-grpc-exporter-base@0.221.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.10.0", "@opentelemetry/otlp-exporter-base": "0.221.0", "@opentelemetry/otlp-transformer": "0.221.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-rQDmNgyiGCTrescjnzH2ntVyUKVIq6I2UjuK8+stT/Xg0ZOT71FVJqwjFdspQl6Yol/Yqsut9bDo+ame8oTmDQ=="], + + "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.221.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.221.0", "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/sdk-logs": "0.221.0", "@opentelemetry/sdk-metrics": "2.10.0", "@opentelemetry/sdk-trace": "2.10.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-lg6lkOU08Az23jVcn/0Els9HP+V8PnR4Km6p0KgpTggS0n/WuhnmY64rSh83Of9iR9nD+dpWr6adlcX8KzAwjg=="], + + "@opentelemetry/propagator-b3": ["@opentelemetry/propagator-b3@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-GnA5B24H+1w8BO21J0q+IWNB0z1v+AGbcquTdIt/dufibhnhgxaA8YKvz0I3akRZhB1jHT+/tlzK+qlAjEDybQ=="], + + "@opentelemetry/propagator-jaeger": ["@opentelemetry/propagator-jaeger@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-yw/IX8DL470dSMZJoE82ScfYGp7JWZ/G8kFJo35ZILUVTB2jFPTOaioN+8s09pH0RHsWNhweVZb+ZnjJJpCChg=="], + + "@opentelemetry/resources": ["@opentelemetry/resources@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-q6MMm2zhggzsHVNbabYwut+a6nbuQQe3URUoxaojM/8K1IBfwwPzvxIjNi2/lI1TFe+fMHMW9MWhrtDLEXEnkA=="], + + "@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.221.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.221.0", "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-FaDcazjyMp7TZZZAsqbo4IkovP0UegoCu0EBkiNt+qCqvUf7FPAsfcrZ3+ZEkKgXZ/jHafop+JoGPDk3A0SmLg=="], + + "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-t6r1VSvXNtSDnPXU1FbZeetJb7yyovHmgu0wRSoftxtE0g2rSNhQZQUy69sRUCL+iioJpX8SN/S6wq6ZtvLySQ=="], + + "@opentelemetry/sdk-node": ["@opentelemetry/sdk-node@0.221.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.221.0", "@opentelemetry/configuration": "0.221.0", "@opentelemetry/context-async-hooks": "2.10.0", "@opentelemetry/core": "2.10.0", "@opentelemetry/exporter-logs-otlp-grpc": "0.221.0", "@opentelemetry/exporter-logs-otlp-http": "0.221.0", "@opentelemetry/exporter-logs-otlp-proto": "0.221.0", "@opentelemetry/exporter-metrics-otlp-grpc": "0.221.0", "@opentelemetry/exporter-metrics-otlp-http": "0.221.0", "@opentelemetry/exporter-metrics-otlp-proto": "0.221.0", "@opentelemetry/exporter-prometheus": "0.221.0", "@opentelemetry/exporter-trace-otlp-grpc": "0.221.0", "@opentelemetry/exporter-trace-otlp-http": "0.221.0", "@opentelemetry/exporter-trace-otlp-proto": "0.221.0", "@opentelemetry/exporter-zipkin": "2.10.0", "@opentelemetry/instrumentation": "0.221.0", "@opentelemetry/otlp-exporter-base": "0.221.0", "@opentelemetry/otlp-grpc-exporter-base": "0.221.0", "@opentelemetry/propagator-b3": "2.10.0", "@opentelemetry/propagator-jaeger": "2.10.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/sdk-logs": "0.221.0", "@opentelemetry/sdk-metrics": "2.10.0", "@opentelemetry/sdk-trace": "2.10.0", "@opentelemetry/sdk-trace-base": "2.10.0", "@opentelemetry/sdk-trace-node": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-UbYuvtBrQQB5Prsh9KOKy4kxzexFxfMs5MkteHeWMoswsEB7kiNhyUVkAOFW/qsEzNHtrkgyghrD2ilZJa+5YA=="], + + "@opentelemetry/sdk-trace": ["@opentelemetry/sdk-trace@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-MfQGq3GRmTh5fM/y+OjaO0vj6+luCB1XO2gfXCalKCfgKw0eHL++sm75DNweC6ohlp+aFvACqeE0fYayqdRaoQ=="], + + "@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/sdk-trace": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-GuYQQT7QD2EeO8lcZLRQzcbOyhqAzL+6WWTKTU9mSUBYBazkEDl+VrQcXQhbB08OWM9anD1aHleVadzulpOaUQ=="], + + "@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.10.0", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.10.0", "@opentelemetry/core": "2.10.0", "@opentelemetry/sdk-trace-base": "2.10.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-GZK/G6oZyBLGlH1pUgeDch7D91KoHd2uotUGIkWCPi9GI5T9X0p4L7nNAMDR1BQjkRYoDqo+ddfVx9t5Uhys+Q=="], + "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.43.0", "", {}, "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg=="], "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="], @@ -456,6 +571,24 @@ "@protobuf-ts/protoc": ["@protobuf-ts/protoc@2.11.1", "", { "bin": { "protoc": "protoc.js" } }, "sha512-mUZJaV0daGO6HUX90o/atzQ6A7bbN2RSuHtdwo8SSF2Qoe3zHwa4IHyCN1evftTeHfLmdz+45qo47sL+5P8nyg=="], + "@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.5", "", {}, "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g=="], + + "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.1", "", {}, "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg=="], + + "@protobufjs/fetch": ["@protobufjs/fetch@1.1.1", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1" } }, "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw=="], + + "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], + + "@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.2", "", {}, "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug=="], + "@radix-ui/primitive": ["@radix-ui/primitive@1.1.7", "", {}, "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q=="], "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.15", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.10" }, "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-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA=="], @@ -518,55 +651,55 @@ "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.62.4", "", { "os": "android", "cpu": "arm" }, "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.62.5", "", { "os": "android", "cpu": "arm" }, "sha512-jfkGfTwhQpsiSckPF8r9bU3pn3vyd72NlWaO+TgEO6WPSDnUhXzrNYCHBMOYj0ACaUgjm6eERLF+XV9a6RstoA=="], - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.62.4", "", { "os": "android", "cpu": "arm64" }, "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A=="], + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.62.5", "", { "os": "android", "cpu": "arm64" }, "sha512-oGVqyQlxnrz9/ty89oHpU857VUHEl5/Xu4R2lS+aivCTrNnSsbiENzTnNaBsjxH0CNWGPhzHArOLFwo+oKXveA=="], - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.62.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw=="], + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.62.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bW7B8xMEq8n99Q3ieEcPRGuphurdZAaFzQc9Efyyw3FL6DZO6pMy9xhdN+kBoD7Sy05xNXSr4OyPPnpkYriS/A=="], - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.62.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A=="], + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.62.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-YSwBS86QeHOGlrxJ1PSOIZSkzRL/JmKeunhc+lV6M1a6En8QuVCD/T/qIA0J4Gd2Y86RIOBYrLcOUtqGh9+/1w=="], - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.62.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ=="], + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.62.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-2fST8lILgl7cKbme/1KDdPCmbXbG+gqoV3bHp19L0ypX/3akYMBVdOunPleRCwonoLnXOZ/0F+Mt/v8POFmfcQ=="], - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.62.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg=="], + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.62.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-cpIxQCP9J+EVad0a6LO1kY3ZGODlk80VlI+2I96B8xMcdHZ4pLVhfQ49JFpYqjPF91FFkQWftf57YlDcTiw9yQ=="], - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.62.4", "", { "os": "linux", "cpu": "arm" }, "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA=="], + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.62.5", "", { "os": "linux", "cpu": "arm" }, "sha512-r9fGh3eFs3e/udWh5ZjXQtxiYK/xoFxQaYR/cELxac/Udkl5Th+IsFm0CX3Kl9hmUH/we7EoMpjJgeQNnE0+IA=="], - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.62.4", "", { "os": "linux", "cpu": "arm" }, "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ=="], + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.62.5", "", { "os": "linux", "cpu": "arm" }, "sha512-xdvFdp7OM6KLJviJT2g/YuRSUjnZgGHk4RNgwIbN7X6cPugOucV60DdHXWzsBVCUdrGb6qSXnJQrrAKMmQuj3Q=="], - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.62.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ=="], + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.62.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-rRqILAndyzHzP7T9NFQrq+4HFWNhqkqkKur7eiBpfLmz01PO0JKx5Vchu3YllE4YXI/Ftgq/szrDWg5GJ0mI8g=="], - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.62.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g=="], + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.62.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-Gf4X3qVMucayUvux6aXXPgXovocSFUC0rrffDuPI/S2nHhNMhjcZxsrAFYCOF350PRreW1XwzFj3CT/3bKsWCw=="], - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.62.4", "", { "os": "linux", "cpu": "none" }, "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA=="], + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.62.5", "", { "os": "linux", "cpu": "none" }, "sha512-+s5qA0TNM0qm8PK/a5gt/1Hpx+NV08uSuCncvhziIlQzT6AEV2fnUQo7eBtFTFO0nA9scauvoR2HusfXmQnO4w=="], - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.62.4", "", { "os": "linux", "cpu": "none" }, "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ=="], + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.62.5", "", { "os": "linux", "cpu": "none" }, "sha512-ybb6QvWwWJCbBWqERpc8K3pYVGIrXlG8MEQ8IIuJY6Y9KdHQxoFoNyfkAOtKn1VHu3KuLidXvwrvGR1mEjeWCw=="], - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.62.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w=="], + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.62.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nZb1DtnOyhCmYvsC8A2CwOkopVg+IS1+fPUa7rMOAXtNw5+lLCLLPqd6XAiNrGtoQKsbvIBOwsHnBH/3wnb4HQ=="], - "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.62.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w=="], + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.62.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-yMbj63Sp89ryrXLWyz+sy+fYD2HpOnMCLGbe4Oa1smclFSUukdtD/BgdiHaAetJNb74URD8U4hM+qG5KVzMEkg=="], - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.62.4", "", { "os": "linux", "cpu": "none" }, "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ=="], + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.62.5", "", { "os": "linux", "cpu": "none" }, "sha512-mhoan3OJw2kYV/e1jtIdmvUZgyBFeA6zGWsOswmR0Tg19TQbowZuR+JMLID6spbbBN7Zee2ejrgmy3+FxGrIdA=="], - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.62.4", "", { "os": "linux", "cpu": "none" }, "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA=="], + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.62.5", "", { "os": "linux", "cpu": "none" }, "sha512-5ZTLmjWbb1VZdjuyhe83K/8QO0/h11midQCBP+X5OYn32ra7eOBoM0ZqtaY4nkgNsYgmdVhMYPoyVPTjUpHf3w=="], - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.62.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA=="], + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.62.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-m53kG+br6PGxOTmgBEM2DHSDs9RVjsyEbUwjJPJGTFm1grWOG8EKJggDCTb60unD4Tjby8fi7/m9XfkEWasVWg=="], - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.62.4", "", { "os": "linux", "cpu": "x64" }, "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw=="], + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.62.5", "", { "os": "linux", "cpu": "x64" }, "sha512-6RHPJR1g/uvdYU8uXBnfq3nlqyZCP82Fr6NHgfGoaIeSh0YEqnX/x6uA9MmJJbnSH7swqX4F+CkGdUF+6doiQA=="], - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.62.4", "", { "os": "linux", "cpu": "x64" }, "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA=="], + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.62.5", "", { "os": "linux", "cpu": "x64" }, "sha512-xs+OXQtEXgpXT0DmA5+U3qnRZHdCST/5HRQxS8wSPZTUZN/EMWeHuSIod32LQklTBZBV9DyfncKBQ8n5V3eFdw=="], - "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.62.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ=="], + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.62.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e7hD+sl3s+mcLQDZ8pbudBVsdG6r5yN4w3LqG2TJ8sQHDpblWj5lrJs/3m01Cvlxbt4x13zu5thLjgypgtkYzw=="], - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.62.4", "", { "os": "none", "cpu": "arm64" }, "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ=="], + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.62.5", "", { "os": "none", "cpu": "arm64" }, "sha512-GiyJaCf+WpMub/17aPcKk27QMl5W6f+KhdPTjlFOn5akH5Wa/DCM9Stdx5cDfmasyKB08MqpVQ1uJE2RkkpbXg=="], - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.62.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw=="], + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.62.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-+OQ8U2DdoEfXl8T4Fb18AjmEwbXMerKDKCL8yCPAYhKCEEKoul7rkbeGCBFCbAlaGaa7pmtRTpkAJM2LE/i5FA=="], - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.62.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ=="], + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.62.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-KanvAZrPKbDBFwrgiU9yEVpQoox9QPV1WZOXX7HudJQY+eSlu82CtWxDU8WtuRRvtN5EGkLczkd6Y6DTcvm9wA=="], - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.62.4", "", { "os": "win32", "cpu": "x64" }, "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw=="], + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.62.5", "", { "os": "win32", "cpu": "x64" }, "sha512-1aC3UEWTtRl3RK3VpDJ/Tqk1XI4SLTmXIthAq6wRWo8XiSXJNd+VprJM4/1P4+i6HIaFEFlVi9sTTziniD2tOQ=="], - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.62.4", "", { "os": "win32", "cpu": "x64" }, "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q=="], + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.62.5", "", { "os": "win32", "cpu": "x64" }, "sha512-/gDJaRs4gl0NPIwqCz+6PkpmhhjRAD2j6P4rSNHBzUkO3naEx2mIU0pRle1vUNRQ7mE/+8OOeXLTv/J56FKiQg=="], "@scarf/scarf": ["@scarf/scarf@1.4.0", "", {}, "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ=="], @@ -608,6 +741,20 @@ "@slack/web-api": ["@slack/web-api@7.19.0", "", { "dependencies": { "@slack/logger": "^4.0.1", "@slack/types": "^2.21.0", "@types/node": ">=18", "@types/retry": "0.12.0", "axios": "^1.16.0", "eventemitter3": "^5.0.1", "form-data": "^4.0.4", "is-electron": "2.2.2", "is-stream": "^2", "p-queue": "^6", "p-retry": "^4", "retry": "^0.13.1" } }, "sha512-ItjyjEZml+LDH8CjcCLRLJHh7VZtevPKExrRN3l5KWyBliyDnGAeoO4Y+K+fFBmRpKLYVPgqWMX4THldv2HVtA=="], + "@smithy/core": ["@smithy/core@3.33.3", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-CsOeKq/9kA3y6VJHt+/+VTCtBaxJ4OTFpgrjIUhPpDIKxBci1k2bJaQASF2h/ELWrulGp+t97DZ0mevfAD8idg=="], + + "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.5.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg=="], + + "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.7.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-nZyWTmSpJEXl6VtWVMBJve/7x12DZu6sIX1z1a+ZMaHlQQRs9Zpu6NbTe/gmxYXVRpkjxyDYpZ5gx2IM6f/Wkw=="], + + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.11.3", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-2jY1tSpERfPfWqyBV2pH+iGFaghVsIJszJNsT7hxtQYhVJpWDyc0LqOWI+nXOxOAHaEfZ4PXXtp1wW1TGpHhkA=="], + + "@smithy/signature-v4": ["@smithy/signature-v4@5.7.3", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-7ImGm+FkHRLcBaRttIAMZ6bzJZWb2cJGoYjq46F2UjycujWzrL9GEN9h4w7eQyXJYnltrUhxbbieBAIRrdqpow=="], + + "@smithy/types": ["@smithy/types@4.17.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-FOKpVZob9MPTn2znRzGrnsMHv7BOsKVw3XiP/cOyYLDVZ9qKp4nifIiSCuUU/fIj5Vu0UOAxCFr+qRAtG0NUkA=="], + + "@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="], + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@tabler/icons": ["@tabler/icons@3.46.0", "", {}, "sha512-f2RYFl3fzPwj5WO82x6en0dmkjefxEfOm16D1ByM6cj/McNiwOkL4VaPUoP9VVIrXAD9WnTSVFr70px703b//A=="], @@ -660,23 +807,23 @@ "@tanstack/react-query": ["@tanstack/react-query@5.101.4", "", { "dependencies": { "@tanstack/query-core": "5.101.4" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA=="], - "@tanstack/react-router": ["@tanstack/react-router@1.170.27", "", { "dependencies": { "@tanstack/history": "1.162.1", "@tanstack/react-store": "^0.9.3", "@tanstack/router-core": "1.171.22", "isbot": "^5.1.22" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-Hxl49xzd8ffWd2ZMigqfXZmpySpixWGvjq5zfh2nK2DbzzDH6IGVh+iUuwarK9MJNcnhWPZ85tOoy6o/OmNlww=="], + "@tanstack/react-router": ["@tanstack/react-router@1.170.31", "", { "dependencies": { "@tanstack/history": "1.162.1", "@tanstack/react-store": "^0.9.3", "@tanstack/router-core": "1.171.26", "isbot": "^5.1.22" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-jqITLcf9Y+es9Wm7fD7XfXFGKk1Fujm+okGqHtr+UhISnQuyLQ8d3frsJlIN0Np1doYoeb5weozSkv72+EZ5bw=="], "@tanstack/react-store": ["@tanstack/react-store@0.11.1", "", { "dependencies": { "@tanstack/store": "0.11.1", "use-sync-external-store": "^1.6.0" }, "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-HaIGKI3YLmjBYIvy5DFDY23oNaYZIsTZfngey07Uh5iLVJgM3bIGCnZeOFOqzjFld9JHWcaHJnasD/bKoGKwJQ=="], - "@tanstack/react-virtual": ["@tanstack/react-virtual@3.14.9", "", { "dependencies": { "@tanstack/virtual-core": "3.17.7" }, "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-qZyr0FZDP8rDC4WBhsryIZmAd9bveJvFGUJJtskWaew6/0dTRS6wZxnR6VQ5bY2KwL3LjerrHqQLk3a0GKcPXQ=="], + "@tanstack/react-virtual": ["@tanstack/react-virtual@3.14.10", "", { "dependencies": { "@tanstack/virtual-core": "3.17.8" }, "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-SRyoUbdFMRHuYXMijV5H4ZarQWpXkj3iANq8OFre+pybeVap8ZJjZ3Nz9bVjx4d8PfobVUQUdKyyyHYk3E+djw=="], - "@tanstack/router-core": ["@tanstack/router-core@1.171.22", "", { "dependencies": { "@tanstack/history": "1.162.1", "cookie-es": "^3.0.0", "seroval": "^1.6.2", "seroval-plugins": "^1.6.2" } }, "sha512-sitsuRkz4qpTjIAV97S5zFCoeGv0OFd6+VWg3ZcIlQbi5R4NIXfz6ogg51n5w6rvTwSODz/lKWb5dl5tvh2bQw=="], + "@tanstack/router-core": ["@tanstack/router-core@1.171.26", "", { "dependencies": { "@tanstack/history": "1.162.1", "cookie-es": "^3.0.0", "seroval": "^1.6.2", "seroval-plugins": "^1.6.2" } }, "sha512-VymmPSs/93szHur/7PBygT6tRbmfcNO1xR46Wn/JVHeBqVduLPCuxBeJEgfVmVq8E4hSklPEh8i6qGXQd6WRqA=="], - "@tanstack/router-generator": ["@tanstack/router-generator@1.167.28", "", { "dependencies": { "@babel/types": "^7.28.5", "@tanstack/router-core": "1.171.22", "@tanstack/router-utils": "1.162.2", "@tanstack/virtual-file-routes": "1.162.0", "jiti": "^2.7.0", "magic-string": "^0.30.21", "prettier": "^3.5.0", "zod": "^4.4.3" } }, "sha512-AxvzdqQoBxrA8hoO1fHYg4cAUVug/xLqQhsGbNG1PelU3RbYRYEtDim7+HbYQCOqJaEuvV8tCvrTPTnT69iIwg=="], + "@tanstack/router-generator": ["@tanstack/router-generator@1.167.32", "", { "dependencies": { "@babel/types": "^7.28.5", "@tanstack/router-core": "1.171.26", "@tanstack/router-utils": "1.162.2", "@tanstack/virtual-file-routes": "1.162.0", "jiti": "^2.7.0", "magic-string": "^0.30.21", "prettier": "^3.5.0", "zod": "^4.4.3" } }, "sha512-KKPWUMUda7JYil+uDQPrX4ecyoXP9J3DesdpavOWpGCavsiAd6cxH6yq18krLGa1j20qmPJc10LJSLOOFia5tQ=="], - "@tanstack/router-plugin": ["@tanstack/router-plugin@1.168.30", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "@tanstack/router-core": "1.171.22", "@tanstack/router-generator": "1.167.28", "@tanstack/router-utils": "1.162.2", "chokidar": "^5.0.0", "unplugin": "^3.0.0", "zod": "^4.4.3" }, "peerDependencies": { "@rsbuild/core": ">=1.0.2 || ^2.0.0", "@tanstack/react-router": "^1.170.26", "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0", "vite-plugin-solid": "^2.11.10 || ^3.0.0-0", "webpack": ">=5.92.0" }, "optionalPeers": ["@rsbuild/core", "@tanstack/react-router", "vite", "vite-plugin-solid", "webpack"] }, "sha512-Z53FeZjSddyn3c+lmUGHOU3bhZPpaFabcynja8/s87CZeyMYS7oNgUMoaYZAhVLk9cAEC/z3fkqISXqktZadDA=="], + "@tanstack/router-plugin": ["@tanstack/router-plugin@1.168.34", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "@tanstack/router-core": "1.171.26", "@tanstack/router-generator": "1.167.32", "@tanstack/router-utils": "1.162.2", "chokidar": "^5.0.0", "unplugin": "^3.0.0", "zod": "^4.4.3" }, "peerDependencies": { "@rsbuild/core": ">=1.0.2 || ^2.0.0", "@tanstack/react-router": "^1.170.31", "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0", "vite-plugin-solid": "^2.11.10 || ^3.0.0-0", "webpack": ">=5.92.0" }, "optionalPeers": ["@rsbuild/core", "@tanstack/react-router", "vite", "vite-plugin-solid", "webpack"] }, "sha512-WsXLQU8tQmoTyoQRB1JI3lGg5wnwo6VB2R8D2RQbtCVlUi3bjreSNgZSJ7Ghw06ZoUyNMO+Csiqf679nNnzN+w=="], "@tanstack/router-utils": ["@tanstack/router-utils@1.162.2", "", { "dependencies": { "@babel/generator": "^7.28.5", "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "ansis": "^4.1.0", "babel-dead-code-elimination": "^1.0.12", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" } }, "sha512-hTWqJtqIFFdvuCl8WXNyrodp2L9zo2G37xKRrcVmVRWpAB2h+U1LuRAfS4tsFTiWOIoE/B+WDVFB8JpoEdw6jQ=="], "@tanstack/store": ["@tanstack/store@0.11.1", "", {}, "sha512-mzTOBhypOuDJAy/D8n2MfUZ1HFkXnmSETviRyhqEC8LUE7/IZQExOTxMANj3KjTofYTkFNpBY67qaVrT41YccA=="], - "@tanstack/virtual-core": ["@tanstack/virtual-core@3.17.7", "", {}, "sha512-bp+v10y65sp2H7WpWfIMyxTNfl8ZVfxFTLRjPIFRryi6FV/J33z4IS53WO4pTk36KlvJ4iLiQz+oaydDC1xbcA=="], + "@tanstack/virtual-core": ["@tanstack/virtual-core@3.17.8", "", {}, "sha512-BfEvehNpOT75r5Ksc5xW6NZuXujTfb7nlSEyVu4XHG3gdxNg1KqXruWbDewXOUaUYIo4oRbSfkjIajz4MAT8tA=="], "@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.162.0", "", {}, "sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA=="], @@ -684,7 +831,7 @@ "@testing-library/react": ["@testing-library/react@16.3.2", "", { "dependencies": { "@babel/runtime": "^7.12.5" }, "peerDependencies": { "@testing-library/dom": "^10.0.0", "@types/react": "^18.0.0 || ^19.0.0", "@types/react-dom": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g=="], - "@testing-library/user-event": ["@testing-library/user-event@14.6.4", "", { "peerDependencies": { "@testing-library/dom": ">=7.21.4" } }, "sha512-QCGwP6QrjypBLwyj5cuyfVamkaIEy/XGY+1VDehbtbQqOggYmTFpFOdWR5mPz14vX8vXLMVjDHlRNBcClyO9ew=="], + "@testing-library/user-event": ["@testing-library/user-event@14.6.5", "", { "peerDependencies": { "@testing-library/dom": ">=7.21.4" } }, "sha512-FhqjldLTpteueBaKflhNFlMT3+PM0O5fiBUivht6b9CZ1eesJyy7+g3Jr7XwJzt/Hip3ZG5hWwK1MX1FuDiE4w=="], "@ts-morph/common": ["@ts-morph/common@0.27.0", "", { "dependencies": { "fast-glob": "^3.3.3", "minimatch": "^10.0.1", "path-browserify": "^1.0.1" } }, "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ=="], @@ -700,7 +847,7 @@ "@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="], - "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], + "@types/bun": ["@types/bun@1.4.0", "", { "dependencies": { "bun-types": "1.4.0" } }, "sha512-K+lZULY23vRgK/CfTjFIV+tyifaNdSMlPh9j+6mQ/cLfpOznLyAuzgV/JQysyECpkBQLVMSyvjlr2fBUSA9wFQ=="], "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], @@ -854,9 +1001,9 @@ "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], - "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], - "ai": ["ai@6.0.253", "", { "dependencies": { "@ai-sdk/gateway": "3.0.172", "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45", "@opentelemetry/api": "^1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-NNwp47xpD4c47NB0ad+MP8HL66f8g6l0JwZ2UdHR6PpBPk9WACEq1k7md3r0GVR6skqAU/Slt5Hk9mJYQwJkvA=="], + "ai": ["ai@6.0.260", "", { "dependencies": { "@ai-sdk/gateway": "3.0.177", "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.46", "@opentelemetry/api": "^1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-vMaV6Mh3+b9+XaSrKA76zx1ku7Z/6kE0VeQ26AsZDoJXpsswreOa/TlVjQxkqwEQgdkRB1NBgrUTdIPq8xUSAQ=="], "ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], @@ -898,9 +1045,9 @@ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.11.13", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.11.16", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-H/bNPUFHewJHyCTdjn1n3Pit5+2GmWT6mmeHImPX+8MA9NA6b67jO4gYmi4jTbCJb2otq34KMZnovndDPqJwhQ=="], - "better-auth": ["better-auth@1.6.27", "", { "dependencies": { "@better-auth/core": "1.6.27", "@better-auth/drizzle-adapter": "1.6.27", "@better-auth/kysely-adapter": "1.6.27", "@better-auth/memory-adapter": "1.6.27", "@better-auth/mongo-adapter": "1.6.27", "@better-auth/prisma-adapter": "1.6.27", "@better-auth/telemetry": "1.6.27", "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.1", "@noble/ciphers": "^2.1.1", "@noble/hashes": "^2.0.1", "better-call": "1.4.0", "defu": "^6.1.4", "jose": "^6.1.3", "kysely": "^0.28.17 || ^0.29.0", "nanostores": "^1.1.1", "zod": "^4.3.6" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": "^0.45.2", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-x3jyxpAiBSsMO/DaXMFSVwM8bTqnYZlCKsZxBV43zL3ZtsGa/CzeUuNKxPT2Lsz7ahTBY90Ku1tibN6nRpVEbw=="], + "better-auth": ["better-auth@1.7.1", "", { "dependencies": { "@better-auth/core": "1.7.1", "@better-auth/drizzle-adapter": "1.7.1", "@better-auth/kysely-adapter": "1.7.1", "@better-auth/memory-adapter": "1.7.1", "@better-auth/mongo-adapter": "1.7.1", "@better-auth/prisma-adapter": "1.7.1", "@better-auth/telemetry": "1.7.1", "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.1", "@noble/ciphers": "^2.2.0", "@noble/hashes": "^2.2.0", "better-call": "1.4.0", "defu": "^6.1.4", "jose": "^6.2.3", "kysely": "^0.28.17 || ^0.29.0", "nanostores": "^1.3.0", "zod": "^4.3.6" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4 || >=1.0.0-beta.1", "drizzle-orm": "^0.45.2 || >=1.0.0-rc.1 <2.0.0", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-g8WlTQijxXWJjPVZfFu1+EJg9cwwHrKDmIkcYMzx8CzYA+tDxl6NI7qQbKkbgw5UtHILsT5VH+RMzFzwnVJqAg=="], "better-call": ["better-call@1.4.0", "", { "dependencies": { "@better-auth/utils": "^0.5.0", "@better-fetch/fetch": "^1.3.1", "rou3": "^0.9.1", "set-cookie-parser": "^3.1.2" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-bBKOT4vv1kZLDgxVePdilk/Jwkn+dtRRsmi3DzHcDP+WnswyVl6dR59l2HEeP/0cB+bDoopASAesWDPIdd/zZA=="], @@ -910,6 +1057,8 @@ "boring-avatars": ["boring-avatars@2.0.4", "", { "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-xhZO/w/6aFmRfkaWohcl2NfyIy87gK5SBbys8kctZeTGF1Apjpv/10pfUuv+YEfVPkESU/h2Y6tt/Dwp+bIZPw=="], + "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="], + "brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], @@ -924,10 +1073,12 @@ "buffer-image-size": ["buffer-image-size@0.6.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ=="], - "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], + "bun-types": ["bun-types@1.4.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-iIKw23BspnQQYd3prITOBxeUsxBHnwzX6YJfGMuNOZzeNcMmVqzIIVGRm1l69ogaPQmb4wB6BN8mA5bE9YuC5Q=="], "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], + "busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="], + "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], @@ -956,6 +1107,10 @@ "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + + "cjs-module-lexer": ["cjs-module-lexer@2.2.1", "", {}, "sha512-Ca8swihM+/4yKecYHY52kgJd300hi2lADU/a1RxNTRe+RJ9jvqQlESpbz9DnG9mowez8qwXHB8qYdIUw9e+F5Q=="], + "clarinet": ["clarinet@0.12.6", "", {}, "sha512-0FR+TrvLbYHLjhzs9oeIbd3yfZmd4u2DzYQEjUTm2dNfh4Y/9RIRWPjsm3aBtrVEpjKI7+lWa4ouqEXoml84mQ=="], "class-transformer": ["class-transformer@0.5.1", "", {}, "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw=="], @@ -968,6 +1123,8 @@ "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], "code-block-writer": ["code-block-writer@13.0.3", "", {}, "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg=="], @@ -1092,11 +1249,11 @@ "dateformat": ["dateformat@4.6.3", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="], - "dayjs": ["dayjs@1.11.21", "", {}, "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA=="], + "dayjs": ["dayjs@1.11.23", "", {}, "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ=="], "debounce-fn": ["debounce-fn@4.0.0", "", { "dependencies": { "mimic-fn": "^3.0.0" } }, "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ=="], - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], @@ -1104,7 +1261,7 @@ "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], - "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], + "default-browser": ["default-browser@5.5.1", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw=="], "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], @@ -1132,7 +1289,7 @@ "dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], - "dompurify": ["dompurify@3.4.13", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ=="], + "dompurify": ["dompurify@3.4.14", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-dVoH9z+MY+C9IilgGCk3YfFqjLi3fChm2OiKJMzh6axrJ5qwxqWaZamgmHrpv22CN/KdbZJuGEGgfQoL00LTdg=="], "dot-prop": ["dot-prop@6.0.1", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA=="], @@ -1150,7 +1307,7 @@ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - "electron-to-chromium": ["electron-to-chromium@1.5.405", "", {}, "sha512-bNglH7lPH5l+yHOes7Zr4VqxhOy4BQ9ZBUX4VdoFgxMpzJk7W1ZoO3Vgd9Pxa9PyjQ76sfm2aKH/nzEcCNRlew=="], + "electron-to-chromium": ["electron-to-chromium@1.5.411", "", {}, "sha512-gglkxzokjHfawpGxq75XdBV2/l3BAPzrsMs70qgaZdTW5rpV1tC4MdgJVP9fN126bODA4ZJQkn1wryEzJyQXIg=="], "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], @@ -1158,6 +1315,10 @@ "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + "engine.io-client": ["engine.io-client@6.6.6", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.21.0", "xmlhttprequest-ssl": "~2.1.1" } }, "sha512-iY6QdftLQ9pyiPoX082bpf/u1UewnOaJrtJIF9T0++QB34lZrj0uP+Q/bj8AlUsAxqhnkTV2BS8SBZSxOmoV5Q=="], + + "engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="], + "enhanced-resolve": ["enhanced-resolve@5.24.5", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A=="], "enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="], @@ -1172,11 +1333,13 @@ "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + "es-module-lexer": ["es-module-lexer@2.3.2", "", {}, "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw=="], + "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="], "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - "es-toolkit": ["es-toolkit@1.50.0", "", {}, "sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w=="], + "es-toolkit": ["es-toolkit@1.51.0", "", {}, "sha512-zC2lQGkM7QX+Gm6iM3+WIdZJzthsEd14LvRNJneSO2hzyz/zNBENR8+YXWo1cKxgPBtV6ksPYHELbcwBRzmdCw=="], "esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="], @@ -1204,6 +1367,8 @@ "execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="], + "expand-tilde": ["expand-tilde@2.0.2", "", { "dependencies": { "homedir-polyfill": "^1.0.1" } }, "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw=="], + "express": ["express@4.22.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.5", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q=="], "express-rate-limit": ["express-rate-limit@8.6.2", "", { "dependencies": { "debug": "^4.4.3", "ip-address": "^10.2.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-YH4ru+eOJxQABscKFfRCy9R7x9QFGdezclVMwwgFFndzS2Xnm0uo6B0ABZsLhcpeptGv2qvuJVWlQr9gQZoC3A=="], @@ -1222,6 +1387,8 @@ "fast-uri": ["fast-uri@3.1.5", "", {}, "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw=="], + "fastdom": ["fastdom@1.0.12", "", { "dependencies": { "strictdom": "^1.0.1" } }, "sha512-LB+xjSTEbjHE1cWsxu+tN2Xqr1kpi+V9aADI7sVM5ZMaXyYGPHULQMzpJMYqOTULK/73pUkWVzzObFRBkPr+hg=="], + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], @@ -1236,7 +1403,7 @@ "find-up": ["find-up@3.0.0", "", { "dependencies": { "locate-path": "^3.0.0" } }, "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg=="], - "follow-redirects": ["follow-redirects@1.16.0", "", { "peerDependencies": { "debug": "*" }, "optionalPeers": ["debug"] }, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], + "follow-redirects": ["follow-redirects@1.16.0", "", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], "form-data": ["form-data@4.0.6", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.4", "mime-types": "^2.1.35" } }, "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ=="], @@ -1244,7 +1411,9 @@ "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], - "framer-motion": ["framer-motion@13.1.0", "", { "dependencies": { "motion-dom": "^13.0.0", "motion-utils": "^13.0.0", "tslib": "^2.4.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-QSZrF0Id3QGuHJ+OL+9PSY9pk86C8ERFalwAGSchzTm65+ZoGH/RM26lmEARLljcHj2lqhv0jZOOks+EI3COOw=="], + "forwarded-parse": ["forwarded-parse@2.1.2", "", {}, "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw=="], + + "framer-motion": ["framer-motion@13.1.1", "", { "dependencies": { "motion-dom": "^13.1.1", "motion-utils": "^13.0.0", "tslib": "^2.4.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-B/xn2TPS4f61cEBLFjiYlQFnBZUW1YVj/LM+C+N4OP8Rs95VLEI2ot/RlfBg111la/EiyECFaJJi/A3FWA8MUA=="], "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], @@ -1262,6 +1431,8 @@ "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + "get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="], "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], @@ -1274,7 +1445,7 @@ "get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], - "get-tsconfig": ["get-tsconfig@4.14.2", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-XpwZALwwl/BaKTAyC6+c5T8y6kCg2jk+XGqOVrKIQmW49pNypYLMRjCUXqa28tQgJlhS2RlzP7sc+Rx7W6qsfw=="], + "get-tsconfig": ["get-tsconfig@4.14.3", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA=="], "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], @@ -1292,11 +1463,11 @@ "graphql-scalars": ["graphql-scalars@1.26.0", "", { "dependencies": { "tslib": "^2.5.0" }, "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-o7BnU5yxAFUWTMKpC5yshB12DjzzoxLgEBO9OpOAcxodWrL3sPldD/fqVQoIfcqPH8OclyQilLtg3/7ZRqQUVQ=="], - "graphql-yoga": ["graphql-yoga@5.21.3", "", { "dependencies": { "@envelop/core": "^5.5.1", "@envelop/instrumentation": "^1.0.0", "@graphql-tools/executor": "^1.5.0", "@graphql-tools/schema": "^10.0.11", "@graphql-tools/utils": "^10.11.0", "@graphql-yoga/logger": "^2.0.1", "@graphql-yoga/subscription": "^5.0.5", "@whatwg-node/fetch": "^0.10.6", "@whatwg-node/promise-helpers": "^1.3.2", "@whatwg-node/server": "^0.11.0", "lru-cache": "^10.0.0", "tslib": "^2.8.1" }, "peerDependencies": { "graphql": "^15.2.0 || ^16.0.0" } }, "sha512-1hJTKZd7k2V0Q2tFMb+rIUSbcovL6uIkkKnW2W9HcldtrC1udukdf5CswKi6SzaI5keG4scxKPEolyZ6U83i/g=="], + "graphql-yoga": ["graphql-yoga@5.22.0", "", { "dependencies": { "@envelop/core": "^5.6.0", "@envelop/instrumentation": "^1.0.0", "@graphql-tools/executor": "^2.0.0", "@graphql-tools/schema": "^10.0.11", "@graphql-tools/utils": "^11.2.0", "@graphql-yoga/logger": "^2.0.1", "@graphql-yoga/subscription": "^5.0.5", "@whatwg-node/fetch": "^0.10.6", "@whatwg-node/promise-helpers": "^1.3.2", "@whatwg-node/server": "^0.11.0", "lru-cache": "^10.0.0", "tslib": "^2.8.1" }, "peerDependencies": { "graphql": "^15.2.0 || ^16.0.0 || ^17.0.0" } }, "sha512-RTMS9WfDJQdYT1VRwDQ34Q9zFvuD3PZXBEDedNdUuoeNsJ6jZBwPo5PhdExzkR+s5iwf8MoiGJOS5Byt/PNAZg=="], "hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="], - "happy-dom": ["happy-dom@20.11.2", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-7MB+bJLkxu3SowAfBJbjW+c55kNz5tkR45gu2qzrxznezhLeN5YIlJbwUgSzlGc+qWoZ8Ykg71H5ezz69xixrw=="], + "happy-dom": ["happy-dom@20.11.6", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-Hldbg8AdAa5a5oDcZpjqnGitp7JB0hqWmfv/8qr+kft4vzSD8BHsbdRfzYvL/0QcbKcURC/yyoygbeDQarPvYg=="], "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], @@ -1338,7 +1509,9 @@ "help-me": ["help-me@5.0.0", "", {}, "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="], - "hono": ["hono@4.13.2", "", {}, "sha512-JydRilDRkYBQMt9qR9U92mXxmbGqsqSn/IKOrh4e7/gEbn+0zSr8igTu0obwJoNGN4sez28DIql7FBHWydoJpA=="], + "homedir-polyfill": ["homedir-polyfill@1.0.3", "", { "dependencies": { "parse-passwd": "^1.0.0" } }, "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA=="], + + "hono": ["hono@4.13.3", "", {}, "sha512-r8AO2mYHoLxSHkgafNeC/BXyb2vWRxD3jem4Ts+ptav8oTG5FIRifAjuJEmZI4bSvvc2ns0GxmIYiZnHqN3mMw=="], "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="], @@ -1348,7 +1521,7 @@ "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], - "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="], @@ -1360,13 +1533,15 @@ "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + "import-in-the-middle": ["import-in-the-middle@3.3.3", "", { "dependencies": { "cjs-module-lexer": "^2.2.0", "es-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" } }, "sha512-AiohS3H80sXO6owEltjGX+glb7qXaDhBoJb9XcQVH4UI207xu/bDLUcadVKp7Qe576reg9yr/PXZjV5qx8gfbA=="], + "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "inline-style-parser": ["inline-style-parser@0.1.1", "", {}, "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q=="], - "internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], + "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], "ip-address": ["ip-address@10.5.0", "", {}, "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g=="], @@ -1388,6 +1563,8 @@ "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], @@ -1420,9 +1597,11 @@ "isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], + "isomorphic-ws": ["isomorphic-ws@5.0.0", "", { "peerDependencies": { "ws": "*" } }, "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw=="], + "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], - "jose": ["jose@6.2.8", "", {}, "sha512-Bsdjwm3Qsd/P0jR+BHDe3LytDfY7WBq2HmCCLIwuVRHMuEC9ae7/R474GIUdF1NgCyZjzVo/A9DOiOBtXq8ZoQ=="], + "jose": ["jose@6.2.9", "", {}, "sha512-XrchZOFZUl/T3vTwRe8XK+cJrGtMF4th1ARnDfwbBXFKThGhlsxEE4Zu03AD/bjJSt/9jT/mxrOCkJWOg77aPA=="], "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], @@ -1464,13 +1643,13 @@ "kysely": ["kysely@0.29.5", "", {}, "sha512-ooa+eSbBNPTo3MycPEuW5jdrxQdQwdtB3LC3h43FiXQbIry5tR0C5lDG7eealK0E4D7XjrnOP5DIUg/LyjRMYQ=="], - "langchain": ["langchain@1.5.8", "", { "dependencies": { "@langchain/langgraph": "^1.4.8", "@langchain/langgraph-checkpoint": "^1.1.3", "langsmith": ">=0.5.0 <1.0.0", "zod": "^3.25.76 || ^4" }, "peerDependencies": { "@langchain/core": "^1.2.7" } }, "sha512-wW7T+E73w9Pu3OovLlSaF6Np50ivCECrNG8Y+BPZmYMUMhzA2Azd8OXCq7hA+kdMkGff39daM4HQZjhBfrHQKg=="], + "langchain": ["langchain@1.5.10", "", { "dependencies": { "@langchain/langgraph": "^1.4.10", "@langchain/langgraph-checkpoint": "^1.1.5", "langsmith": ">=0.5.0 <1.0.0", "zod": "^3.25.76 || ^4" }, "peerDependencies": { "@langchain/core": "^1.2.9" } }, "sha512-JaC12C1qyGn985vvjttr4hr8lfFzWhrXp2M1byZJGmNJ2RiIgqnhiYDuLlG/xHDxhKD3onJ5pCuUif/cbdqPhA=="], - "langsmith": ["langsmith@0.8.10", "", { "dependencies": { "p-queue": "6.6.2" }, "peerDependencies": { "@opentelemetry/api": "*", "@opentelemetry/exporter-trace-otlp-proto": "*", "@opentelemetry/sdk-trace-base": "*", "openai": "*", "ws": ">=7" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/exporter-trace-otlp-proto", "@opentelemetry/sdk-trace-base", "openai", "ws"] }, "sha512-OG07MX2vyWcLyx/RhXPcydZVpuJSmCrcI1PxVmOR7TOoGWnGV68yUIaA85kc9v0gBkyD6cWa2VnBNBZcTN/qcQ=="], + "langsmith": ["langsmith@0.9.0", "", { "dependencies": { "p-queue": "6.6.2" }, "peerDependencies": { "@opentelemetry/api": "*", "@opentelemetry/exporter-trace-otlp-proto": "*", "@opentelemetry/sdk-trace-base": "*", "openai": "*", "ws": ">=7" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/exporter-trace-otlp-proto", "@opentelemetry/sdk-trace-base", "openai", "ws"] }, "sha512-tlg/aG7qezAKY6G3fgADSX7PkRj+JKoF3z7QNkCMsAOvwvuzhiwP9Amn1Z+zAIxuKoWuXQdIjtFN0LVmUC1oUQ=="], "layout-base": ["layout-base@1.0.2", "", {}, "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="], - "libphonenumber-js": ["libphonenumber-js@1.13.10", "", {}, "sha512-xJxrdqvbl2rtn2MaUJrUejz8J7/uZNC0V77oks2LxYrO/+ZtVpRmz+fEQMuu6VusnEB1fmpByiLS1WXecOnAnw=="], + "libphonenumber-js": ["libphonenumber-js@1.13.11", "", {}, "sha512-ETER2kMaIFTI/Nh1a8Gk03dUF/SL0VZqtI+CcVHZxp5WIHYwNS7S+uiYZDYCvLy3lOR4/DAD5jf0h5WkePPpqg=="], "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], @@ -1510,6 +1689,8 @@ "lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], + "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], + "lodash.clonedeep": ["lodash.clonedeep@4.5.0", "", {}, "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="], "lodash.get": ["lodash.get@4.4.2", "", {}, "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="], @@ -1530,6 +1711,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=="], "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], @@ -1594,7 +1777,7 @@ "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], - "mermaid": ["mermaid@11.16.1", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.2", "@iconify/utils": "^3.0.2", "@mermaid-js/parser": "^1.2.0", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", "cytoscape": "^3.33.3", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", "dayjs": "^1.11.20", "dompurify": "^3.3.3", "es-toolkit": "^1.45.1", "katex": "^0.16.45", "khroma": "^2.1.0", "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" } }, "sha512-TQsq6u22fAn3rek5VOubrhKPo1g5hwC3FXUN9hiyupTckcYiGuuKGkNQrKYwGJkXUxZdojwRG46gsSCFZMDp4g=="], + "mermaid": ["mermaid@11.17.0", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.2", "@iconify/utils": "^3.0.2", "@mermaid-js/parser": "^1.2.1", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", "cytoscape": "^3.34.0", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", "dayjs": "^1.11.21", "dompurify": "^3.3.3", "es-toolkit": "^1.45.1", "fastdom": "1.0.12", "katex": "^0.16.47", "khroma": "^2.1.0", "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" } }, "sha512-Jo9N377Wb4MSnHFPTbLi2SxFpsQl4eVHoxnW5U1Md9EazvgMp3s+4ohDxr81YNTgbn5Kj7HJ3yslrSJ52kwpbA=="], "methods": ["methods@1.1.2", "", {}, "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="], @@ -1666,9 +1849,9 @@ "mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], - "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], "mimic-fn": ["mimic-fn@3.1.0", "", {}, "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ=="], @@ -1678,9 +1861,15 @@ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], - "motion": ["motion@13.1.0", "", { "dependencies": { "framer-motion": "^13.1.0", "tslib": "^2.4.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-qtvscq59uCPdWnNW4SdSkrxR+BS/QYsa923bx7ocA+4p+ZGNbbVQwkSnG4aukB81QWjtl3AxX36plxNyZLmHCA=="], + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], + + "module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="], + + "motion": ["motion@13.1.1", "", { "dependencies": { "framer-motion": "^13.1.1", "tslib": "^2.4.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-WNZoK6xiF+kkTqkZ5K7FDDh6A8BG4i5Hc7KXtW8gtTxkpJFds+hIOrDaQGKjQj/AE/i4hJqAaUHEqp/Qo02y6Q=="], - "motion-dom": ["motion-dom@13.0.0", "", { "dependencies": { "motion-utils": "^13.0.0" } }, "sha512-Xk+SJas70uMAUIApg+m3lZDShxI3LBFHq7mFGbBKoRXc2PVPDyAKmzN64Bbzt4CZdP/CItTiJxWtn4TA0v53Ng=="], + "motion-dom": ["motion-dom@13.1.1", "", { "dependencies": { "motion-utils": "^13.0.0" } }, "sha512-XSf8VYWSB6G/0IY3rWVbyLcxWXtAVHkN1PQE2agTaCv3u8RGvbwu56TyyR/MNzBqqNavEBTZzErcxI1TxBrjcA=="], "motion-utils": ["motion-utils@13.0.0", "", {}, "sha512-7DnN7TmbLcYXcG4RVadXIihWlyuM9afoUww8Y5Agg431kGKiuL2/OMyP4mJ5wLz+pvN3t5ySClLOaVXJ+wekRQ=="], @@ -1692,7 +1881,7 @@ "nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="], - "nanostores": ["nanostores@1.4.2", "", {}, "sha512-Wxv8Roefr2nqtiRG0bnaFlpYqpIVtOEeJZHaH+4nGgOK1/7n6OHOuHCb/bhqrNQgZM8fyd0s1PqhdrJc9Ib44g=="], + "nanostores": ["nanostores@1.5.2", "", {}, "sha512-B0UbxzK1s0CN8Xht6r+7iT5+xV8PTaRERR1nATeplRv1Rw5YLWfVAid0hkqY3EceqpG4RjTk8GAwIxQY39Rnwg=="], "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], @@ -1722,9 +1911,9 @@ "oniguruma-to-es": ["oniguruma-to-es@4.3.6", "", { "dependencies": { "oniguruma-parser": "^0.12.2", "regex": "^6.1.0", "regex-recursion": "^6.0.2" } }, "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA=="], - "open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="], + "open": ["open@11.0.1", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.2.0", "wsl-utils": "^1.0.0" } }, "sha512-NzwMUB6C1D0+Kd+9iMS/H4k+Ck3cTX6Ckyfr/gAGlmvSE1LUQZnEZvWBi4PYmMwH/S5SMeTXnE+9uAz8uF+pWw=="], - "openai": ["openai@7.4.0", "", { "peerDependencies": { "@aws-sdk/credential-provider-node": ">=3.972.0 <4", "@smithy/hash-node": ">=4.3.0 <5", "@smithy/signature-v4": ">=5.4.0 <6", "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["@aws-sdk/credential-provider-node", "@smithy/hash-node", "@smithy/signature-v4", "ws", "zod"] }, "sha512-+C9Muit5x8j9R8ej8ZzVgKcrVDtqFqTy9gxFdov0EItLgU68zrJtF9ZeT0cyqJQW9S3PCJkdFgADtRGquRBtew=="], + "openai": ["openai@7.5.0", "", { "peerDependencies": { "@aws-sdk/credential-provider-node": ">=3.972.0 <4", "@smithy/hash-node": ">=4.3.0 <5", "@smithy/signature-v4": ">=5.4.0 <6", "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["@aws-sdk/credential-provider-node", "@smithy/hash-node", "@smithy/signature-v4", "ws", "zod"] }, "sha512-ZbDBz8FSB8Mv8fFYIUvzTFMdV5vl93/octp1MdtK2lfYepSpfv/ewmeugpKz/cwGtFSx+YuUM4NwpZ2P55YiPA=="], "ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="], @@ -1752,6 +1941,8 @@ "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="], + "parse-passwd": ["parse-passwd@1.0.0", "", {}, "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q=="], + "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], @@ -1770,7 +1961,7 @@ "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - "phoenix": ["phoenix@1.8.11", "", {}, "sha512-MbS8y0+8lYNjOYRIorek1GzMFS3nKvycetLlLxB/DZOFRu0yLI0PCvvm8wg+7aEORbmWuioe95+7uoyNKlErZg=="], + "phoenix": ["phoenix@1.8.12", "", {}, "sha512-svUniHb83aGh1XpYWHe4fkbfrGd6xa26TWO/3clkuNplXwgjxh7RI7GOWxf28VAgDWwQ2/yN8PxIa5dqjXOcAA=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], @@ -1798,7 +1989,7 @@ "postgres": ["postgres@3.4.9", "", {}, "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw=="], - "powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], + "powershell-utils": ["powershell-utils@0.2.0", "", {}, "sha512-ZlsFlG7MtSFCoc5xreOvBAozCJ6Pf06opgJjh9ONEv418xpZSAzNjstD36C6+JwOnfSqOW/9uDkqKjezTdxZhw=="], "prettier": ["prettier@3.9.6", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g=="], @@ -1810,7 +2001,7 @@ "process-warning": ["process-warning@5.1.0", "", {}, "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw=="], - "prompt-area": ["prompt-area@0.6.3", "", { "peerDependencies": { "clsx": ">=2", "react": ">=18", "react-dom": ">=18", "tailwind-merge": ">=2" } }, "sha512-/M70Qfui+yd/Z2YOsGloRt69dmMz1HXh+PF3CNZHzHemKHZNWjuPUEd3KAn/xIH+3y5aW+DCSuph39bHF7ACgg=="], + "prompt-area": ["prompt-area@0.6.7", "", { "peerDependencies": { "clsx": ">=2", "react": ">=18", "react-dom": ">=18", "tailwind-merge": ">=2" } }, "sha512-y8rL0ezYWmuWS/5n4wQyvwvL7u5gU78R5psc6/9jrNzdu/I97k9s7jJzmqgHHay9gUxiqajOYa+nIQu88HNTZQ=="], "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], @@ -1818,6 +2009,8 @@ "property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="], + "protobufjs": ["protobufjs@7.6.5", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw=="], + "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=="], "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], @@ -1892,8 +2085,12 @@ "remend": ["remend@1.3.0", "", {}, "sha512-iIhggPkhW3hFImKtB10w0dz4EZbs28mV/dmbcYVonWEJ6UGHHpP+bFZnTh6GNWJONg5m+U56JrL+8IxZRdgWjw=="], + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + "require-in-the-middle": ["require-in-the-middle@8.0.1", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3" } }, "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ=="], + "reselect": ["reselect@5.2.0", "", {}, "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw=="], "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], @@ -1908,7 +2105,7 @@ "robust-predicates": ["robust-predicates@3.0.3", "", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="], - "rollup": ["rollup@4.62.4", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@napi-rs/lzma-linux-x64-gnu": "1.5.1", "@rollup/rollup-android-arm-eabi": "4.62.4", "@rollup/rollup-android-arm64": "4.62.4", "@rollup/rollup-darwin-arm64": "4.62.4", "@rollup/rollup-darwin-x64": "4.62.4", "@rollup/rollup-freebsd-arm64": "4.62.4", "@rollup/rollup-freebsd-x64": "4.62.4", "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", "@rollup/rollup-linux-arm-musleabihf": "4.62.4", "@rollup/rollup-linux-arm64-gnu": "4.62.4", "@rollup/rollup-linux-arm64-musl": "4.62.4", "@rollup/rollup-linux-loong64-gnu": "4.62.4", "@rollup/rollup-linux-loong64-musl": "4.62.4", "@rollup/rollup-linux-ppc64-gnu": "4.62.4", "@rollup/rollup-linux-ppc64-musl": "4.62.4", "@rollup/rollup-linux-riscv64-gnu": "4.62.4", "@rollup/rollup-linux-riscv64-musl": "4.62.4", "@rollup/rollup-linux-s390x-gnu": "4.62.4", "@rollup/rollup-linux-x64-gnu": "4.62.4", "@rollup/rollup-linux-x64-musl": "4.62.4", "@rollup/rollup-openbsd-x64": "4.62.4", "@rollup/rollup-openharmony-arm64": "4.62.4", "@rollup/rollup-win32-arm64-msvc": "4.62.4", "@rollup/rollup-win32-ia32-msvc": "4.62.4", "@rollup/rollup-win32-x64-gnu": "4.62.4", "@rollup/rollup-win32-x64-msvc": "4.62.4", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg=="], + "rollup": ["rollup@4.62.5", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@napi-rs/lzma-linux-x64-gnu": "1.5.1", "@rollup/rollup-android-arm-eabi": "4.62.5", "@rollup/rollup-android-arm64": "4.62.5", "@rollup/rollup-darwin-arm64": "4.62.5", "@rollup/rollup-darwin-x64": "4.62.5", "@rollup/rollup-freebsd-arm64": "4.62.5", "@rollup/rollup-freebsd-x64": "4.62.5", "@rollup/rollup-linux-arm-gnueabihf": "4.62.5", "@rollup/rollup-linux-arm-musleabihf": "4.62.5", "@rollup/rollup-linux-arm64-gnu": "4.62.5", "@rollup/rollup-linux-arm64-musl": "4.62.5", "@rollup/rollup-linux-loong64-gnu": "4.62.5", "@rollup/rollup-linux-loong64-musl": "4.62.5", "@rollup/rollup-linux-ppc64-gnu": "4.62.5", "@rollup/rollup-linux-ppc64-musl": "4.62.5", "@rollup/rollup-linux-riscv64-gnu": "4.62.5", "@rollup/rollup-linux-riscv64-musl": "4.62.5", "@rollup/rollup-linux-s390x-gnu": "4.62.5", "@rollup/rollup-linux-x64-gnu": "4.62.5", "@rollup/rollup-linux-x64-musl": "4.62.5", "@rollup/rollup-openbsd-x64": "4.62.5", "@rollup/rollup-openharmony-arm64": "4.62.5", "@rollup/rollup-win32-arm64-msvc": "4.62.5", "@rollup/rollup-win32-ia32-msvc": "4.62.5", "@rollup/rollup-win32-x64-gnu": "4.62.5", "@rollup/rollup-win32-x64-msvc": "4.62.5", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-/tqMfgP7GPA3PHhCmuiS4vIjrSVhHLgY++i+dhbG462euyAj7FpM4D9uq1X3BgjlqRdpcOrYhcQtfiQLNc8tqw=="], "rou3": ["rou3@0.9.2", "", {}, "sha512-3SOzvaAg8rkHrXtRjpCvCvbyO5to9oOO27Z/XqHEYXfMRVSw/qMIVdmaOk9W2lcRLtR6dlqTjo9hDeJk70QBYQ=="], @@ -1952,12 +2149,14 @@ "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], - "shadcn": ["shadcn@4.17.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "kleur": "^4.1.5", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "socks": "^2.8.8", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "undici": "^7.27.2", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-kphndh72CD7mxLQ/thkbPVgfXpIjzPUllzvTWRJmU3sNGZc/WbOqzJ0FXbBGj6sqNiaGPiOLDQByy8FAtCuVUQ=="], + "shadcn": ["shadcn@4.18.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "kleur": "^4.1.5", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "socks": "^2.8.8", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "undici": "^7.27.2", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-tUFZgkYmfVNQVm3xX7lhSzOvDsp+O14ac5dwgXIr5mIsr79ISueb/Mu+ZtWMz0DH6v77u4eYyvbQ9TTMpSn3aw=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "shell-quote": ["shell-quote@1.10.0", "", {}, "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA=="], + "shiki": ["shiki@3.23.0", "", { "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/engine-javascript": "3.23.0", "@shikijs/engine-oniguruma": "3.23.0", "@shikijs/langs": "3.23.0", "@shikijs/themes": "3.23.0", "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA=="], "side-channel": ["side-channel@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4", "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ=="], @@ -1974,6 +2173,10 @@ "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + "socket.io-client": ["socket.io-client@4.8.3", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" } }, "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g=="], + + "socket.io-parser": ["socket.io-parser@4.2.7", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1" } }, "sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg=="], + "socks": ["socks@2.8.9", "", { "dependencies": { "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" } }, "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw=="], "sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="], @@ -1992,8 +2195,14 @@ "stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="], + "stream-browserify": ["stream-browserify@3.0.0", "", { "dependencies": { "inherits": "~2.0.4", "readable-stream": "^3.5.0" } }, "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA=="], + "streamdown": ["streamdown@2.5.0", "", { "dependencies": { "clsx": "^2.1.1", "hast-util-to-jsx-runtime": "^2.3.6", "html-url-attributes": "^3.0.1", "marked": "^17.0.1", "mermaid": "^11.12.2", "rehype-harden": "^1.1.8", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remend": "1.3.0", "tailwind-merge": "^3.4.0", "unified": "^11.0.5", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-/tTnURfIOxZK/pqJAxsfCvETG/XCJHoWnk3jq9xLcuz6CSpnjjuxSRBTTL4PKGhxiZQf0lqPxGhImdpwcZ2XwA=="], + "streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="], + + "strictdom": ["strictdom@1.0.1", "", {}, "sha512-cEmp9QeXXRmjj/rVp9oyiqcvyocWab/HaoN4+bwFeZ7QzykJD6L3yD4v12K1x0tHpqRqVpJevN3gW7kyM39Bqg=="], + "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=="], "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], @@ -2026,6 +2235,8 @@ "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], + "tar": ["tar@7.5.22", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA=="], + "thread-stream": ["thread-stream@3.2.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw=="], "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], @@ -2150,16 +2361,26 @@ "worker": ["worker@workspace:worker"], + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], "ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="], - "wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="], + "wsl-utils": ["wsl-utils@1.0.0", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA=="], + + "xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.1.2", "", {}, "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="], - "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + "yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + "yocto-spinner": ["yocto-spinner@1.2.2", "", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-DODGl1wJjA/s5pnJFKau9lIYHT81lnhob1i3e1TjxZRxEhWRKl74nTbWE6H5KlkViQQTo/Z29YFdxzTZAMY3ng=="], "yoctocolors": ["yoctocolors@2.2.0", "", {}, "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg=="], @@ -2180,7 +2401,7 @@ "@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.95", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.32" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-V0nIwhyv8f9rD+p6glm0uq30seid8y2CrvNvHCAbnuPm5bPpqVChEB63kixrIDNnogkgI+ZjSTtIbIL2q3QPvQ=="], - "@ai-sdk/google-vertex/@ai-sdk/google": ["@ai-sdk/google@2.0.88", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.32" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-1EgXWI+9wKAvbTyNP2DLuiQ7KZM9bzSzLMXouzxfEC0rvLVSX0tN1emXiEurj8+9kXyElJO7Qm3xWSNhdyVEvA=="], + "@ai-sdk/google-vertex/@ai-sdk/google": ["@ai-sdk/google@2.0.89", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.32" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-zYCrUuiCxLk65iQ3C5AU9qLo0lsyCbYNAS983rDTg/lMVmdsWz8aqrL6K746y7/lRyv+gM1y1HoFCxVsKOSlSw=="], "@ai-sdk/google-vertex/@ai-sdk/provider": ["@ai-sdk/provider@2.0.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-h88OPkavHTiN9tMn2l5awAznGB0lXzjcLhgR1/rvjB2zlLprsNxbM2tt6OJsHUxduLC3klq0/eqaSf6fX5XVww=="], @@ -2190,7 +2411,9 @@ "@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.32", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-izgUo50kamJMwoYCXoFwVccuJeyYp0y1+twf0FfpEz7kdQBloZLZbgLYUOUpannLWrV7xYSJR48BQJIQFbFiAQ=="], - "@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], + "@ai-sdk/provider-utils/undici": ["undici@6.28.0", "", {}, "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA=="], + + "@aws-sdk/lib-storage/buffer": ["buffer@5.6.0", "", { "dependencies": { "base64-js": "^1.0.2", "ieee754": "^1.1.4" } }, "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw=="], "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], @@ -2218,7 +2441,7 @@ "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], - "@graphql-tools/executor/@graphql-tools/utils": ["@graphql-tools/utils@11.2.2", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "@whatwg-node/promise-helpers": "^1.0.0", "cross-inspect": "1.0.1", "tslib": "^2.4.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-Do2A/t6ayqOma8m7PvpYMsCBswL+NB35BQSng4GWSBqafL2/BnfAZy/NSENPwV721Rm2fG0BHETFC0+FJJZmLw=="], + "@graphql-tools/executor/@graphql-tools/utils": ["@graphql-tools/utils@12.0.0", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "@whatwg-node/promise-helpers": "^1.0.0", "cross-inspect": "1.0.1", "tslib": "^2.4.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-aMdIo/l+8j4lhamWAf+MWHr+lOW8zArSBKXspqtKHgt5I2JF9LS55KDLUe/L9AiJOV/O6qJJ60hgYydbnJHbxg=="], "@graphql-tools/merge/@graphql-tools/utils": ["@graphql-tools/utils@12.0.0", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "@whatwg-node/promise-helpers": "^1.0.0", "cross-inspect": "1.0.1", "tslib": "^2.4.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-aMdIo/l+8j4lhamWAf+MWHr+lOW8zArSBKXspqtKHgt5I2JF9LS55KDLUe/L9AiJOV/O6qJJ60hgYydbnJHbxg=="], @@ -2232,8 +2455,6 @@ "@microsoft/agents-hosting/zod": ["zod@3.25.75", "", {}, "sha512-OhpzAmVzabPOL6C3A3gpAifqr9MqihV/Msx3gor2b2kviCgcb+HM9SEOpMWwwNp9MRunWnhtAKUoo0AHhjyPPg=="], - "@modelcontextprotocol/sdk/@hono/node-server": ["@hono/node-server@2.1.0", "", { "peerDependencies": { "hono": "^4" } }, "sha512-XovyyCCnBzW+zKu+z/zq8hwNs4KOR5rEMAOxo2f40Q5xoOI37IMm6MIg2COOUtUApo0i6850MTBKH2u4QLGIqg=="], - "@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], "@segment/analytics-node/jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="], @@ -2272,11 +2493,9 @@ "@types/mdast/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], - "@whatwg-node/node-fetch/@fastify/busboy": ["@fastify/busboy@3.2.1", "", {}, "sha512-tgK4O+57iz5ycYNGXE5ZWj1ES03lD2XnnBYWSbU/3wYZRMQzCUq7Ycds/RdyBZQeL5MU4fxBt6lzbIWf/Bickw=="], - - "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "@typespec/ts-http-runtime/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], - "axios/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + "@whatwg-node/node-fetch/@fastify/busboy": ["@fastify/busboy@3.2.1", "", {}, "sha512-tgK4O+57iz5ycYNGXE5ZWj1ES03lD2XnnBYWSbU/3wYZRMQzCUq7Ycds/RdyBZQeL5MU4fxBt6lzbIWf/Bickw=="], "better-call/@better-auth/utils": ["@better-auth/utils@0.5.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-BL8W4EfIZFwlu0r54m3v1ztjDhu6dDe/amLTm0xybmbZaNgYUqhD3SjpAsnq0q8YD6/ki4iwIgxJNLP/N3TxiA=="], @@ -2286,6 +2505,10 @@ "body-parser/raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="], + "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "conf/ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], "conf/json-schema-typed": ["json-schema-typed@7.0.3", "", {}, "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A=="], @@ -2304,8 +2527,6 @@ "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="], - "dagre-d3-es/lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="], - "dot-prop/is-obj": ["is-obj@2.0.0", "", {}, "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="], "drizzle-kit/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], @@ -2316,7 +2537,7 @@ "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], - "form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "gaxios/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], "gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], @@ -2376,6 +2597,8 @@ "hastscript/property-information": ["property-information@7.2.0", "", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="], + "http-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "is-inside-container/is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], "jsonwebtoken/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], @@ -2466,14 +2689,14 @@ "shiki/@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="], + "stream-browserify/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "strip-ansi/ansi-regex": ["ansi-regex@6.3.0", "", {}, "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ=="], "style-to-js/style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], "type-graphql/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], - "type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - "unified/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], "unified/vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], @@ -2502,8 +2725,16 @@ "vfile-message/unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + "wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "wsl-utils/is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], + "wsl-utils/powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], + + "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "@ag-ui/mcp-middleware/@ag-ui/client/@ag-ui/core": ["@ag-ui/core@0.0.54", "", { "dependencies": { "zod": "^3.22.4" } }, "sha512-Ilx31OvRQaZfU7jSArGqz06JZKOsAt8zWiCPJljyp9zR6Tzl18oyfx8o6FsuGfAktGRe50GI9SCCxNXXysZwtA=="], "@ag-ui/mcp-middleware/@ag-ui/client/@ag-ui/encoder": ["@ag-ui/encoder@0.0.54", "", { "dependencies": { "@ag-ui/core": "0.0.54", "@ag-ui/proto": "0.0.54" } }, "sha512-0dPuE/eAeBRBDj/OOj5AW8SoP1r0dufmoOdrtKgmf+dlbVXKSNkDDHGrrvIWFPxwvPTWhHeN6wnsVUayWpUsGg=="], @@ -2516,6 +2747,8 @@ "@ai-sdk/openai-compatible/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], + "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "@copilotkit/react-core/streamdown/lucide-react": ["lucide-react@0.542.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw=="], "@copilotkit/react-core/streamdown/marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="], @@ -2598,7 +2831,7 @@ "@modelcontextprotocol/sdk/express/merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], - "@modelcontextprotocol/sdk/express/range-parser": ["range-parser@1.3.0", "", {}, "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw=="], + "@modelcontextprotocol/sdk/express/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], "@modelcontextprotocol/sdk/express/send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], @@ -2624,7 +2857,7 @@ "@slack/bolt/express/merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], - "@slack/bolt/express/range-parser": ["range-parser@1.3.0", "", {}, "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw=="], + "@slack/bolt/express/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], "@slack/bolt/express/send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], @@ -2634,16 +2867,18 @@ "@tanstack/react-router/@tanstack/react-store/@tanstack/store": ["@tanstack/store@0.9.3", "", {}, "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw=="], - "accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - - "axios/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + "@typespec/ts-http-runtime/https-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], "body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="], + "d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], + "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="], "drizzle-kit/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], @@ -2702,7 +2937,7 @@ "finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "gaxios/https-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], "hast-util-from-dom/@types/hast/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], @@ -2768,21 +3003,29 @@ "style-to-js/style-to-object/inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], - "type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + "@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.1.0", "", { "dependencies": { "content-type": "^2.1.0" } }, "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg=="], - "@modelcontextprotocol/sdk/express/body-parser/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], + "@modelcontextprotocol/sdk/express/body-parser/content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="], - "@modelcontextprotocol/sdk/express/type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], + "@modelcontextprotocol/sdk/express/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "@modelcontextprotocol/sdk/express/type-is/content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="], "@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.1", "", {}, "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ=="], - "@slack/bolt/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + "@slack/bolt/express/accepts/negotiator": ["negotiator@1.1.0", "", { "dependencies": { "content-type": "^2.1.0" } }, "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg=="], + + "@slack/bolt/express/body-parser/content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="], - "@slack/bolt/express/body-parser/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], + "@slack/bolt/express/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - "@slack/bolt/express/type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], + "@slack/bolt/express/type-is/content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="], "@slack/bolt/express/type-is/media-typer": ["media-typer@1.1.1", "", {}, "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ=="], @@ -2804,6 +3047,10 @@ "react-markdown/remark-rehype/mdast-util-to-hast/unist-util-position": ["unist-util-position@4.0.4", "", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg=="], + "@modelcontextprotocol/sdk/express/accepts/negotiator/content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="], + + "@slack/bolt/express/accepts/negotiator/content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="], + "react-markdown/remark-parse/mdast-util-from-markdown/micromark/micromark-core-commonmark": ["micromark-core-commonmark@1.1.0", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-factory-destination": "^1.0.0", "micromark-factory-label": "^1.0.0", "micromark-factory-space": "^1.0.0", "micromark-factory-title": "^1.0.0", "micromark-factory-whitespace": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-chunked": "^1.0.0", "micromark-util-classify-character": "^1.0.0", "micromark-util-html-tag-name": "^1.0.0", "micromark-util-normalize-identifier": "^1.0.0", "micromark-util-resolve-all": "^1.0.0", "micromark-util-subtokenize": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.1", "uvu": "^0.5.0" } }, "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw=="], "react-markdown/remark-parse/mdast-util-from-markdown/micromark/micromark-factory-space": ["micromark-factory-space@1.1.0", "", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ=="], diff --git a/server/package.json b/server/package.json index e51e784..de96d1c 100644 --- a/server/package.json +++ b/server/package.json @@ -16,6 +16,7 @@ "@better-auth/drizzle-adapter": "^1.6.27", "@copilotkit/runtime": "1.67.1", "@copilotkit/shared": "1.67.1", + "@daytona/sdk": "^0.205.1", "@modelcontextprotocol/sdk": "^1.30.0", "better-auth": "^1.6.27", "cel-js": "^0.8.2", diff --git a/server/src/app.ts b/server/src/app.ts index 35ac76d..c43057c 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -19,7 +19,6 @@ import { createComponentRoutes } from "./components/routes"; import type { SandboxedStore } from "./components/sandboxed"; import { createSandboxedRoutes } from "./components/sandboxed-routes"; import type { ComponentStore } from "./components/store"; -import type { ComputerClient } from "./computer/client"; import type { ComputerGateway } from "./computer/gateway"; import type { PolicyStore } from "./computer/policy-store"; import { createComputerRoutes } from "./computer/routes"; @@ -48,9 +47,7 @@ export function createApp( * scope broke every server test that touches createApp even though none of them use CopilotKit. */ copilotHandler?: HonoApp, - /** Absent when no computer is configured, and the routes are then not mounted at all. */ - computerClient?: ComputerClient, - /** The only path to an acting call: policy decision, then audit row, then the action. */ + /** The single governed computer module: policy, audit trail, transport, and provider lifecycle. */ computerGateway?: ComputerGateway, /** What the gateway enforces, and what an administrator can change while running. */ computerPolicy?: PolicyStore, @@ -291,24 +288,19 @@ export function createApp( if (copilotHandler) { // Mounted at the ROOT with the handler carrying its own basePath. Mounting it at // "/api/copilotkit" as well double-prefixes it: Hono strips the prefix before the handler sees - // the path, so every route lands at /api/copilotkit/api/copilotkit/* and /info 404s. The client + // the path, so every route lands at /api/copilotkit/api/copilotkit/* and /info 404s. The browser // reports that as "Runtime info request failed with status 404" and every run fails before it // starts, with nothing at all in the server log. app.route("/", copilotHandler); } - // The Bot computer. Acting on a page needs the gateway and the policy it enforces, so all - // three arrive together or the routes are not mounted: a computer whose actions were ungoverned is - // not a reduced feature, it is the one shape of this feature that must not exist. - if (computerClient && computerGateway && computerPolicy) { + // The Bot computer. Acting on a page needs the gateway and the policy it enforces, so both arrive + // together or the routes are not mounted. An ungoverned computer is not a reduced feature. It is + // the one shape of this feature that must not exist. + if (computerGateway && computerPolicy) { app.route( "/api/computers", - createComputerRoutes( - computerClient, - computerGateway, - computerPolicy, - requireUser, - ), + createComputerRoutes(computerGateway, computerPolicy, requireUser), ); } diff --git a/server/src/computer/client.ts b/server/src/computer/client.ts index f019ef8..8d0a83c 100644 --- a/server/src/computer/client.ts +++ b/server/src/computer/client.ts @@ -1,60 +1,7 @@ -import type { - ActionResult, - ClickInput, - ComputerProfile, - ComputerStatus, - ControlState, - HumanInput, - HumanInputResult, - KeyInput, - ListFilesInput, - ListFilesResult, - NavigateResult, - ReadFileInput, - ReadFileResult, - ReadResult, - ScreenshotResult, - ScrollInput, - SecretRequest, - SecretResult, - SnapshotResult, - TypeInput, - WriteFileInput, - WriteFileResult, -} from "./schema"; +import type { NavigateResult } from "./schema"; import { checkNavigationTarget } from "./target"; -/** - * How the server talks to a Bot's computer. - * - * The computer has no authentication of its own and trusts whatever reaches it, so this module is - * the boundary: it decides whether a navigation is permitted before the request leaves, and it is - * the only place that knows the computer's address. Nothing downstream of here should be handed a - * raw URL from a model. - */ - -export type ComputerClientOptions = { - /** - * The secret this deployment's computers require. Absent means every call is refused by them, which - * is the correct failure: a computer that answers an unauthenticated caller is the bug. - */ - token?: string; - /** Base URL of the Bot's computer, e.g. http://agent-computer:4100 */ - baseUrl: string; - /** - * Where this Bot's computer is, when each Bot has one of its own. - * - * A supervisor gives every Bot its own container, so the address stops being one fixed URL and becomes - * whatever the supervisor published for that Bot, which also changes when its computer is reset. - * Left unset, `baseUrl` answers for everyone as one shared computer. - */ - resolveBaseUrl?: (botId: string) => Promise; - /** True on a laptop, where browsing the deployment's own services is the point. */ - allowPrivateHosts?: boolean; - timeoutMs?: number; - fetchImpl?: typeof fetch; -}; - +/** The computer did not accept or answer a request. */ export class ComputerUnavailableError extends Error { constructor(reason: string) { super(reason); @@ -62,16 +9,7 @@ export class ComputerUnavailableError extends Error { } } -/** - * The Bot acted on something that is not on the page. - * - * Its own error because it is its own condition, and the one the Bot can fix by taking a fresh - * snapshot. - * - * The message a locator failure carries is a Playwright call log, several lines of `waiting for - * locator('aria-ref=e5')`, which is noise to a model and to a person. It is replaced with the thing - * to do next. - */ +/** The requested element is not on the current page. */ export class ElementNotFoundError extends Error { constructor(reason: string) { super(reason); @@ -79,6 +17,7 @@ export class ElementNotFoundError extends Error { } } +/** The navigation target is not permitted. */ export class NavigationRefusedError extends Error { constructor(reason: string) { super(reason); @@ -86,14 +25,7 @@ export class NavigationRefusedError extends Error { } } -/** - * The file request itself was refused by the computer: outside the workspace, missing, or too large. - * - * Distinct from a policy refusal, which happens in the gateway before the request is ever made. Both - * reach the browser as a 403 but they mean different things: this one says the path is not a thing a - * Bot may name at all, the other says this Bot may not touch an otherwise perfectly valid path. Only - * the second has a rule an administrator can go and edit. - */ +/** The computer refused access to a path outside its workspace. */ export class WorkspaceRefusedError extends Error { constructor(reason: string) { super(reason); @@ -101,14 +33,7 @@ export class WorkspaceRefusedError extends Error { } } -/** - * The request asked for something that is not there, or not usable: no such file, a folder where a - * file was wanted, a write that is too big. - * - * Not a refusal. Nothing declined to let the Bot do this; the thing it named does not fit the request. - * Kept separate from {@link WorkspaceRefusedError} because a Bot's next move differs completely: here - * it should look at what IS there and try again, whereas a refusal is final and should be reported. - */ +/** The workspace request names a path or value that cannot be used. */ export class WorkspaceRequestError extends Error { constructor(reason: string) { super(reason); @@ -116,12 +41,7 @@ export class WorkspaceRequestError extends Error { } } -/** - * The refs the caller is using were taken before the page changed. - * - * Its own type because it is the one failure here that the model can fix without a person: take a new - * snapshot and try again. Collapsed into a generic failure, the Bot apologises to the person instead. - */ +/** The page changed after the caller received its element references. */ export class StaleSnapshotError extends Error { constructor(reason: string) { super(reason); @@ -129,306 +49,159 @@ export class StaleSnapshotError extends Error { } } -export function createComputerClient(options: ComputerClientOptions) { - const doFetch = options.fetchImpl ?? fetch; - /* - * The secret the computer demands. Without it this process is just another caller, which is the - * point: a computer that answers without this token bypasses the policy gateway, audit trail, and - * sign-in boundary. - */ - const token = options.token; - const timeoutMs = options.timeoutMs ?? 45_000; - const base = options.baseUrl.replace(/\/$/, ""); - - /** - * A view of the computer as one Bot. - * - * Which Bot is asking has to reach the computer, or nothing on the far side can be per-Bot: its - * profile, its logins, the proxy its traffic leaves through and who holds its wheel all key off this - * one string. If the id is omitted, every Bot resolves the same fixed default and per-Bot settings - * such as `EGRESS_PROXY_` cannot apply. - * - * A bound view rather than a parameter on twenty methods: the gateway already knows the Bot at the - * point it acts, and threading it through every signature would put the same argument in every call - * site for a value that never changes within a request. - */ - function build(botId?: string) { - async function call( - path: string, - init?: RequestInit, - caller?: AbortSignal, - ): Promise { - // Resolved per call rather than held, because a computer that was reset comes back on a - // different port and a cached address would point at nothing. - // - // Outside the try below on purpose: that catch reports "the computer is not running", which is - // true of a computer that will not answer and misleading about a supervisor that could not be - // reached or refused. Those are different operator-facing problems. - const target = - botId && options.resolveBaseUrl - ? (await options.resolveBaseUrl(botId)).replace(/\/$/, "") - : base; - - // Already stopped before this left: do not dispatch at all. Relying on fetch to reject an - // aborted signal makes "did the click happen" depend on how quickly the runtime notices, and - // the answer to "the person pressed Stop first" should never be a race. - if (caller?.aborted) { - throw new ComputerUnavailableError("The action was stopped."); - } +/** + * Transport options used inside the computer gateway. + * + * This is an internal seam. Application code uses ComputerGateway and does not + * use this interface directly. + */ +export type ComputerTransportOptions = { + token?: string; + allowPrivateHosts?: boolean; + timeoutMs?: number; + fetchImpl?: typeof fetch; +}; - let response: Response; - try { - response = await doFetch(`${target}${path}`, { - ...init, - // The Bot's identity, as a header rather than in the path, so the computer's published routes - // are unchanged and a caller that does not know which Bot it is still works. - headers: { - ...(init?.headers as Record | undefined), - ...(botId ? { "x-openbot-bot-id": botId } : {}), - ...(token ? { "x-openbot-computer-token": token } : {}), - }, - /* - * Both reasons to give up. The timeout protects the server from a computer that - * has stopped answering; `caller` is the person pressing Stop, and it has to reach the - * browser or the click they were stopping still lands. Combined rather than chosen between: - * whichever fires first ends the request. - */ - signal: caller - ? AbortSignal.any([caller, AbortSignal.timeout(timeoutMs)]) - : AbortSignal.timeout(timeoutMs), - }); - } catch (error) { - // Distinguished from a failed page load on purpose: this one means the computer itself is not - // there, which is an operator problem, not something the person asking can fix by rephrasing. - throw new ComputerUnavailableError( - error instanceof Error && error.name === "TimeoutError" - ? "The assistant's computer did not respond in time." - : "The assistant's computer is not running.", - ); - } +/** Internal HTTP interface used only by ComputerGateway. */ +export interface ComputerTransport { + call( + baseUrl: string, + botId: string, + path: string, + init?: RequestInit, + caller?: AbortSignal, + ): Promise; + post( + baseUrl: string, + botId: string, + path: string, + payload: unknown, + caller?: AbortSignal, + ): Promise; + navigate( + baseUrl: string, + botId: string, + url: string, + ): Promise; +} - const body = (await response.json().catch(() => null)) as Record< - string, - unknown - > | null; +/** + * Send authenticated HTTP requests to one located agent-computer process. + * + * Lifecycle and location are deliberately absent. ComputerGateway owns those + * operations through ComputerProvider. + */ +export function createComputerTransport( + options: ComputerTransportOptions, +): ComputerTransport { + const doFetch = options.fetchImpl ?? fetch; + const timeoutMs = options.timeoutMs ?? 45_000; - if (!response.ok) { - const detail = - typeof body?.error === "string" - ? body.error - : `HTTP ${response.status}`; - // A stale ref is fixed by taking a new snapshot, so it is not reported as the computer being - // unavailable. - if (response.status === 409) { - throw new StaleSnapshotError(detail); - } - // These two must not be collapsed: path confinement and ordinary bad requests lead to - // different next actions. - // 403 is the path confinement: a boundary, and the answer will never change. - if (response.status === 403) { - throw new WorkspaceRefusedError(detail); - } - // 400 is an ordinary bad request: no such file, a folder where a file was wanted, too large. A - // different request would succeed, which is exactly what the Bot needs to understand. - if (response.status === 400) { - throw new WorkspaceRequestError(detail); - } - /* - * A locator that never resolved is not an outage. Playwright reports it as a timeout whose - * message is a call log naming the selector, which is how "that button is not there" ended up - * indistinguishable from "the computer is down". - */ - if (/waiting for locator|Timeout .* exceeded/i.test(detail)) { - const ref = detail.match(/aria-ref=([A-Za-z0-9_-]+)/)?.[1]; - throw new ElementNotFoundError( - `${ref ? `Element ${ref} is` : "That element is"} not on the page any more. Take a fresh snapshot and use the refs from it.`, - ); - } - throw new ComputerUnavailableError(detail); - } - return body; + async function call( + baseUrl: string, + botId: string, + path: string, + init?: RequestInit, + caller?: AbortSignal, + ): Promise { + if (caller?.aborted) { + throw new ComputerUnavailableError("The action was stopped."); } - async function post( - path: string, - payload: unknown, - caller?: AbortSignal, - ): Promise { - return call( - path, - { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify(payload), + const target = baseUrl.replace(/\/$/, ""); + let response: Response; + try { + response = await doFetch(`${target}${path}`, { + ...init, + headers: { + ...(init?.headers as Record | undefined), + "x-openbot-bot-id": botId, + ...(options.token + ? { "x-openbot-computer-token": options.token } + : {}), }, - caller, + signal: caller + ? AbortSignal.any([caller, AbortSignal.timeout(timeoutMs)]) + : AbortSignal.timeout(timeoutMs), + }); + } catch (error) { + throw new ComputerUnavailableError( + error instanceof Error && error.name === "TimeoutError" + ? "The assistant's computer did not respond in time." + : "The assistant's computer is not running.", ); } - return { - async status(botId: string): Promise { - try { - await call("/health"); - return { botId, state: "ready" }; - } catch (error) { - return { - botId, - state: "unreachable", - reason: error instanceof Error ? error.message : "Unknown failure.", - }; - } - }, - - /** Open a page. Refuses before the request leaves if the target is not permitted. */ - async navigate(url: string): Promise { - const verdict = checkNavigationTarget(url, { - allowPrivateHosts: options.allowPrivateHosts, - }); - if (!verdict.allowed) { - throw new NavigationRefusedError(verdict.reason); - } - - return (await call("/navigate", { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ url: verdict.url }), - })) as NavigateResult; - }, - - async screenshot(): Promise { - return (await call("/screenshot")) as ScreenshotResult; - }, - - /** The current page as text. No navigation, so no target check applies. */ - async read(): Promise { - return (await call("/read")) as ReadResult; - }, - - async snapshot(): Promise { - return (await call("/snapshot", { method: "POST" })) as SnapshotResult; - }, - - /** - * The acting calls. - * - * Deliberately unguarded here. Unlike `navigate`, which checks its target in this module, these - * carry no policy of their own: the gateway in front of them is the only thing that knows which - * Bot is asking and what the deployment allows, and putting a second half-check here would create - * two places to keep in agreement. Never call these directly from a route. - */ - async click( - input: ClickInput, - caller?: AbortSignal, - ): Promise { - return (await post("/click", input, caller)) as ActionResult; - }, - - async type( - input: TypeInput, - caller?: AbortSignal, - ): Promise { - return (await post("/type", input, caller)) as ActionResult; - }, - - async key(input: KeyInput, caller?: AbortSignal): Promise { - return (await post("/key", input, caller)) as ActionResult; - }, - - async scroll( - input: ScrollInput, - caller?: AbortSignal, - ): Promise { - return (await post("/scroll", input, caller)) as ActionResult; - }, - - /** - * The workspace files. Also unguarded here: the computer confines the path to the workspace, and - * the gateway decides whether this Bot may touch it. Two questions, neither answered in this file. - */ - async readFile(input: ReadFileInput): Promise { - return (await post("/files/read", input)) as ReadFileResult; - }, - - async writeFile(input: WriteFileInput): Promise { - return (await post("/files/write", input)) as WriteFileResult; - }, - - async listFiles(input: ListFilesInput): Promise { - return (await post("/files/list", input)) as ListFilesResult; - }, - - /** Who has the wheel, and whether the Bot is waiting for a person. */ - async control(): Promise { - return (await call("/control")) as ControlState; - }, - - async requestControl(reason: string): Promise { - return (await post("/control/request", { reason })) as ControlState; - }, - - async takeControl(): Promise { - return (await post("/control/take", {})) as ControlState; - }, - - async releaseControl(): Promise { - return (await post("/control/release", {})) as ControlState; - }, - - /** - * A person's own mouse and keyboard, straight through. - * - * Deliberately NOT governed by the policy gateway. The policy exists to constrain what a BOT may - * do; a person taking the wheel is the escape hatch that makes a governed Bot usable at all, and - * a rule that could lock somebody out of their own browser mid-login would be a worse failure - * than anything it prevented. The takeover itself is audited as an event; the keystrokes are not. - */ - /** Ask for a secret. Carries the label and the field, never a value. */ - async requestSecret(input: SecretRequest): Promise { - return (await post("/control/secret", input)) as ControlState; - }, - - /** - * Supply one. The value passes through this call and is kept nowhere: not returned upward, not - * logged here, and not written to the audit trail by the gateway. - */ - /** The computers this process holds, running or not. */ - async computers(): Promise<{ computers: ComputerProfile[] }> { - return (await call("/computers")) as { computers: ComputerProfile[] }; - }, - - /** Stop the browser and keep what it knows. */ - async stopComputer(): Promise<{ stopped: boolean; wasRunning: boolean }> { - return (await post("/computers/stop", {})) as { - stopped: boolean; - wasRunning: boolean; - }; - }, - - /** Delete the profile. Every login the Bot had goes with it. */ - async resetComputer(): Promise<{ reset: boolean; botId: string }> { - return (await post("/computers/reset", {})) as { - reset: boolean; - botId: string; - }; - }, - - async supplySecret(text: string): Promise { - return (await post("/human/secret", { text })) as SecretResult; - }, + const body = (await response.json().catch(() => null)) as Record< + string, + unknown + > | null; + if (!response.ok) { + throwMappedError(response.status, body); + } + return body as T; + } - async humanInput(input: HumanInput): Promise { - const { kind, ...rest } = input; - return (await post(`/human/${kind}`, rest)) as HumanInputResult; - }, + function post( + baseUrl: string, + botId: string, + path: string, + payload: unknown, + caller?: AbortSignal, + ): Promise { + return call( + baseUrl, + botId, + path, + { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(payload), + }, + caller, + ); + } - /** The same computer, addressed as a particular Bot. */ - forBot(id: string) { - return build(id); - }, - }; + async function navigate( + baseUrl: string, + botId: string, + url: string, + ): Promise { + const verdict = checkNavigationTarget(url, { + allowPrivateHosts: options.allowPrivateHosts, + }); + if (!verdict.allowed) { + throw new NavigationRefusedError(verdict.reason); + } + return post(baseUrl, botId, "/navigate", { + url: verdict.url, + }); } - return build(); + return { call, post, navigate }; } -export type ComputerClient = ReturnType; +/** Map agent-computer responses to errors that a caller can act on. */ +function throwMappedError( + status: number, + body: Record | null, +): never { + const detail = + typeof body?.error === "string" ? body.error : `HTTP ${status}`; + if (status === 409) { + throw new StaleSnapshotError(detail); + } + if (status === 403) { + throw new WorkspaceRefusedError(detail); + } + if (status === 400) { + throw new WorkspaceRequestError(detail); + } + if (/waiting for locator|Timeout .* exceeded/i.test(detail)) { + const ref = detail.match(/aria-ref=([A-Za-z0-9_-]+)/)?.[1]; + throw new ElementNotFoundError( + `${ref ? `Element ${ref} is` : "That element is"} not on the page any more. Take a fresh snapshot and use the refs from it.`, + ); + } + throw new ComputerUnavailableError(detail); +} diff --git a/server/src/computer/daytona.ts b/server/src/computer/daytona.ts new file mode 100644 index 0000000..0278aa1 --- /dev/null +++ b/server/src/computer/daytona.ts @@ -0,0 +1,847 @@ +import { createHash } from "node:crypto"; +import { readdirSync, readFileSync } from "node:fs"; +import { join, relative } from "node:path"; +import { + Daytona, + DaytonaConflictError, + DaytonaNotFoundError, + Image, + SandboxState, +} from "@daytona/sdk"; +import { + type ComputerLocation, + type ComputerProvider, + ProviderError, +} from "./provider"; +import type { ComputerStatus } from "./schema"; + +/** + * Remote computers on Daytona for OpenBot. + * + * When DAYTONA_API_KEY is configured, each Bot gets a Daytona cloud sandbox + * instead of a local Docker container. The provider builds and reuses an image + * snapshot, provisions sandboxes on demand, checks health over Daytona preview + * URLs, and maps Bot IDs to active sandboxes. + */ + +export type SandboxHandle = { + id: string; + state?: SandboxState; + labels?: Record; + createdAt?: string; + start(timeout?: number): Promise; + stop(): Promise; + delete(timeout?: number, wait?: boolean): Promise; + refreshActivity(): Promise; + getPreviewLink(port: number): Promise<{ url: string }>; +}; + +export type DaytonaSdk = { + create( + params: { + snapshot: string; + envVars: Record; + labels: Record; + public: boolean; + autoStopInterval: number; + }, + options?: { timeout?: number }, + ): Promise; + get(id: string): Promise; + list(query?: { + labels?: Record; + }): AsyncIterable; + snapshot: { + get(name: string): Promise<{ state: string }>; + create( + params: { name: string; image: unknown }, + options?: { onLogs?: (line: string) => void; timeout?: number }, + ): Promise; + }; +}; + +export type DaytonaProviderOptions = { + apiKey: string; + apiUrl?: string; + target?: string; + snapshot?: string; + computerToken: string; + environment?: Record; + agentComputerDir?: string; + sdk?: DaytonaSdk; + fetchImpl?: typeof fetch; + pollIntervalMs?: number; + healthTimeoutMs?: number; + snapshotTimeoutMs?: number; +}; + +const COMPUTER_PORT = 4100; +const OWNERSHIP_LABEL_KEY = "openbot/computer"; +const OWNERSHIP_LABEL_VALUE = "true"; +const BOT_ID_LABEL_KEY = "openbot/bot-id"; +const TOKEN_HASH_LABEL_KEY = "openbot/computer-token-hash"; +const DEFAULT_AGENT_COMPUTER_DIR = join( + import.meta.dir, + "../../../agent-computer", +); +const DEFAULT_POLL_INTERVAL_MS = 2000; +const HEALTH_POLL_INTERVAL_MS = 500; +const DEFAULT_HEALTH_TIMEOUT_MS = 120_000; +const DEFAULT_SNAPSHOT_TIMEOUT_MS = 15 * 60 * 1000; +const RECIPE_VERSION = "2"; + +// The Playwright image tag and dependency must stay aligned with +// agent-computer/package.json and agent-computer/Dockerfile. Bump both or neither. +function buildRecipe(dir: string): unknown { + return ( + Image.base("mcr.microsoft.com/playwright:v1.62.1-noble") + .runCommands( + "apt-get update && apt-get install -y --no-install-recommends unzip && rm -rf /var/lib/apt/lists/*", + "curl -fsSL https://bun.sh/install | bash", + ) + // Image.env shell-quotes variable references so ${PATH} cannot be used. + .env({ + PATH: "/root/.bun/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + }) + .workdir("/app") + .addLocalFile(join(dir, "package.json"), "/app/package.json") + .runCommands("bun install") + .addLocalDir(join(dir, "src"), "/app/src") + .runCommands("mkdir -p /workspace /profiles") + .env({ + WORKSPACE_DIR: "/workspace", + PROFILES_DIR: "/profiles", + PORT: "4100", + }) + .entrypoint(["bun", "src/index.ts"]) + ); +} + +function getAllFiles(dir: string): string[] { + const entries = readdirSync(dir, { withFileTypes: true }); + const files: string[] = []; + for (const entry of entries) { + const fullPath = join(dir, entry.name); + if (entry.isDirectory()) { + files.push(...getAllFiles(fullPath)); + } else if (entry.isFile()) { + files.push(fullPath); + } + } + return files; +} + +function computeSnapshotName(agentComputerDir: string): string { + const hash = createHash("sha256"); + hash.update(RECIPE_VERSION); + hash.update("\0"); + + const pkgJsonPath = join(agentComputerDir, "package.json"); + const pkgBytes = readFileSync(pkgJsonPath); + hash.update(pkgBytes); + hash.update("\0"); + + const srcDir = join(agentComputerDir, "src"); + const fileList = getAllFiles(srcDir); + const items = fileList.map((fullPath) => { + const rel = relative(srcDir, fullPath).split("\\").join("/"); + return { rel, fullPath }; + }); + items.sort((a, b) => (a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0)); + + for (const item of items) { + const contents = readFileSync(item.fullPath); + hash.update(item.rel); + hash.update("\0"); + hash.update(contents); + hash.update("\0"); + } + + const hex = hash.digest("hex").slice(0, 12); + return `openbot-agent-computer-${hex}`; +} + +function computeTokenHash(token: string): string { + return createHash("sha256").update(token).digest("hex"); +} + +function isNotFoundError(err: unknown): boolean { + if (!err || typeof err !== "object") return false; + if (err instanceof DaytonaNotFoundError) return true; + if ("name" in err && err.name === "DaytonaNotFoundError") { + return true; + } + return false; +} + +function isConflictError(err: unknown): boolean { + if (!err || typeof err !== "object") return false; + if (err instanceof DaytonaConflictError) return true; + if ("name" in err && err.name === "DaytonaConflictError") { + return true; + } + return false; +} + +function toErrorMessage(err: unknown): string { + if (err instanceof Error) { + return err.message; + } + return String(err); +} + +function wrapBotError( + botId: string, + action: string, + err: unknown, +): ProviderError { + if (err instanceof ProviderError) { + return err; + } + return new ProviderError( + `Daytona could not ${action} the computer for ${botId}: ${toErrorMessage(err)}`, + ); +} + +function toComputerStatus(botId: string, state?: SandboxState): ComputerStatus { + if (!state) { + return { + botId, + state: "unreachable", + reason: "Daytona did not report a state for this computer.", + }; + } + switch (state) { + case SandboxState.STARTED: + return { botId, state: "ready" }; + case SandboxState.CREATING: + case SandboxState.RESTORING: + case SandboxState.STARTING: + case SandboxState.PULLING_SNAPSHOT: + case SandboxState.RESUMING: + case SandboxState.PENDING_BUILD: + case SandboxState.BUILDING_SNAPSHOT: + case SandboxState.RESIZING: + case SandboxState.SNAPSHOTTING: + case SandboxState.FORKING: + return { botId, state: "starting" }; + case SandboxState.STOPPED: + case SandboxState.PAUSED: + case SandboxState.ARCHIVED: + case SandboxState.STOPPING: + case SandboxState.PAUSING: + case SandboxState.ARCHIVING: + case SandboxState.DESTROYED: + case SandboxState.DESTROYING: + return { botId, state: "absent" }; + case SandboxState.ERROR: + case SandboxState.BUILD_FAILED: + case SandboxState.UNKNOWN: + case SandboxState.UNKNOWN_DEFAULT_OPEN_API: + return { + botId, + state: "unreachable", + reason: `Daytona reported the computer state "${state}".`, + }; + default: { + const exhaustiveCheck: never = state; + return { + botId, + state: "unreachable", + reason: `Daytona reported the computer state "${String(exhaustiveCheck)}".`, + }; + } + } +} + +function sleep(ms: number): Promise { + const { promise, resolve } = Promise.withResolvers(); + setTimeout(resolve, ms); + return promise; +} + +function withTimeout( + promise: Promise, + timeoutMs: number, + errorMessage = "Operation timed out.", +): Promise { + if (timeoutMs <= 0) { + return Promise.reject(new Error(errorMessage)); + } + let timer: Timer | undefined; + const timeoutPromise = new Promise((_, reject) => { + timer = setTimeout(() => reject(new Error(errorMessage)), timeoutMs); + }); + return Promise.race([promise, timeoutPromise]).finally(() => { + clearTimeout(timer); + }); +} + +type PollDecision = { done: true; value: T } | { done: false }; + +async function pollUntil( + step: () => Promise>, + options: { + timeoutMs: number; + intervalMs: number; + timeoutError: () => Error; + }, +): Promise { + const startTime = Date.now(); + while (Date.now() - startTime < options.timeoutMs) { + const decision = await step(); + if (decision.done) { + return decision.value; + } + const elapsed = Date.now() - startTime; + const remaining = options.timeoutMs - elapsed; + if (remaining <= 0) { + break; + } + await sleep(Math.min(options.intervalMs, remaining)); + } + throw options.timeoutError(); +} + +export function createDaytonaComputerProvider( + options: DaytonaProviderOptions, +): ComputerProvider { + const sdk: DaytonaSdk = + options.sdk ?? + new Daytona({ + apiKey: options.apiKey, + ...(options.apiUrl ? { apiUrl: options.apiUrl } : {}), + ...(options.target ? { target: options.target } : {}), + }); + + const doFetch = options.fetchImpl ?? fetch; + + /** + * In-memory provider state: + * - `known`: maps botId to sandboxId for fast locate lookups. Preview URLs are never cached. + * Entries are cleared on reset, deletion, terminal states, stale token hashes, or 404s. + * - `resetSandboxes`: maps botId to the deleted sandboxId to mask Daytona list eventual-consistency + * staleness where a deleted sandbox temporarily reappears in list() queries. Cleared on fresh creation. + * - `queues`: serializes lifecycle operations (locate, stop, reset) per Bot to prevent race conditions. + * Note: synchronization is process-local only (no cross-instance synchronization). + */ + const known = new Map(); + const resetSandboxes = new Map(); + const queues = new Map>(); + + function runLifecycle(botId: string, op: () => Promise): Promise { + const prev = queues.get(botId) ?? Promise.resolve(); + const next = prev.catch(() => {}).then(op); + queues.set(botId, next); + return next.finally(() => { + if (queues.get(botId) === next) { + queues.delete(botId); + } + }); + } + + let snapshotPromise: Promise | undefined; + + function ensureSnapshot(): Promise { + if (options.snapshot) { + return Promise.resolve(options.snapshot); + } + + if (!snapshotPromise) { + snapshotPromise = (async () => { + const agentComputerDir = + options.agentComputerDir ?? DEFAULT_AGENT_COMPUTER_DIR; + let snapshotName: string; + try { + snapshotName = computeSnapshotName(agentComputerDir); + } catch (err) { + if (err instanceof ProviderError) throw err; + throw new ProviderError( + `Failed to prepare agent-computer sources from "${agentComputerDir}": ${toErrorMessage(err)}. Set DAYTONA_SNAPSHOT to use a prebuilt snapshot.`, + ); + } + + const pollInterval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS; + const snapshotTimeout = + options.snapshotTimeoutMs ?? DEFAULT_SNAPSHOT_TIMEOUT_MS; + + async function pollUntilActive(name: string): Promise { + return await pollUntil( + async () => { + let snap: { state: string }; + try { + snap = await sdk.snapshot.get(name); + } catch (err) { + throw new ProviderError( + `Failed to inspect Daytona snapshot ${name} while waiting for active state: ${toErrorMessage(err)}`, + ); + } + if (snap.state === "active") { + return { done: true, value: name }; + } + if (snap.state === "error" || snap.state === "build_failed") { + throw new ProviderError( + `Daytona snapshot ${name} failed with state "${snap.state}". Delete it in the Daytona dashboard or set DAYTONA_SNAPSHOT to override.`, + ); + } + return { done: false }; + }, + { + timeoutMs: snapshotTimeout, + intervalMs: pollInterval, + timeoutError: () => + new ProviderError( + `Timed out waiting for Daytona snapshot ${name} to become active.`, + ), + }, + ); + } + + try { + const existing = await sdk.snapshot.get(snapshotName); + if (existing.state === "active") { + return snapshotName; + } + if (existing.state === "error" || existing.state === "build_failed") { + throw new ProviderError( + `Daytona snapshot ${snapshotName} failed with state "${existing.state}". Delete it in the Daytona dashboard or set DAYTONA_SNAPSHOT to override.`, + ); + } + return await pollUntilActive(snapshotName); + } catch (err) { + if (isNotFoundError(err)) { + let recipe: unknown; + try { + recipe = buildRecipe(agentComputerDir); + } catch (recipeErr) { + if (recipeErr instanceof ProviderError) throw recipeErr; + throw new ProviderError( + `Failed to build agent-computer recipe from "${agentComputerDir}": ${toErrorMessage(recipeErr)}. Set DAYTONA_SNAPSHOT to use a prebuilt snapshot.`, + ); + } + + try { + await withTimeout( + sdk.snapshot.create( + { name: snapshotName, image: recipe }, + { + onLogs: (line: string) => console.info(`[daytona] ${line}`), + timeout: Math.ceil(snapshotTimeout / 1000), + }, + ), + snapshotTimeout, + `Timed out waiting for Daytona snapshot ${snapshotName} to be created.`, + ); + return snapshotName; + } catch (createErr) { + if (isConflictError(createErr)) { + return await pollUntilActive(snapshotName); + } + if (createErr instanceof ProviderError) { + throw createErr; + } + throw new ProviderError( + `Failed to create Daytona snapshot ${snapshotName}: ${toErrorMessage(createErr)}`, + ); + } + } + if (err instanceof ProviderError) { + throw err; + } + throw new ProviderError( + `Failed to inspect Daytona snapshot ${snapshotName}: ${toErrorMessage(err)}`, + ); + } + })().catch((err) => { + snapshotPromise = undefined; + throw err; + }); + } + + return snapshotPromise; + } + + async function resolveSandbox( + botId: string, + action: string, + includeTerminal = false, + ): Promise { + const deletedSandboxId = resetSandboxes.get(botId); + const knownSandboxId = known.get(botId); + if (knownSandboxId) { + if (deletedSandboxId && knownSandboxId === deletedSandboxId) { + known.delete(botId); + } else { + try { + const sb = await sdk.get(knownSandboxId); + if ( + !includeTerminal && + (sb.state === SandboxState.DESTROYED || + sb.state === SandboxState.DESTROYING) + ) { + known.delete(botId); + return undefined; + } + return sb; + } catch (err) { + if (isNotFoundError(err)) { + known.delete(botId); + return undefined; + } + throw wrapBotError(botId, action, err); + } + } + } + + try { + for await (const sb of sdk.list({ + labels: { + [BOT_ID_LABEL_KEY]: botId, + }, + })) { + if (deletedSandboxId && sb.id === deletedSandboxId) { + continue; + } + if ( + includeTerminal || + (sb.state !== SandboxState.DESTROYED && + sb.state !== SandboxState.DESTROYING) + ) { + return sb; + } + } + } catch (err) { + throw wrapBotError(botId, action, err); + } + + return undefined; + } + + async function createFreshSandbox( + snapshot: string, + botId: string, + ): Promise { + const passthroughEnv: Record = {}; + if (options.environment) { + for (const [key, value] of Object.entries(options.environment)) { + if ( + typeof value === "string" && + (key.startsWith("EGRESS_PROXY") || key === "ACTION_TIMEOUT_MS") + ) { + passthroughEnv[key] = value; + } + } + } + + const envVars: Record = { + COMPUTER_BOT_ID: botId, + COMPUTER_TOKEN: options.computerToken, + ...passthroughEnv, + }; + + const labels: Record = { + [OWNERSHIP_LABEL_KEY]: OWNERSHIP_LABEL_VALUE, + [BOT_ID_LABEL_KEY]: botId, + [TOKEN_HASH_LABEL_KEY]: computeTokenHash(options.computerToken), + }; + + try { + const handle = await sdk.create( + { + snapshot, + envVars, + labels, + public: true, + autoStopInterval: 15, + }, + { timeout: 300 }, + ); + resetSandboxes.delete(botId); + known.set(botId, handle.id); + return handle; + } catch (err) { + throw wrapBotError(botId, "create", err); + } + } + + async function ensureSandboxStarted( + initialHandle: SandboxHandle, + botId: string, + snapshot: string, + ): Promise { + let handle = initialHandle; + if (handle.state === SandboxState.STARTED) { + return handle; + } + + if ( + handle.state === SandboxState.STOPPED || + handle.state === SandboxState.PAUSED || + handle.state === SandboxState.ARCHIVED + ) { + try { + await handle.start(300); + return handle; + } catch (err) { + throw wrapBotError(botId, "start", err); + } + } + + const pollInterval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS; + const maxWaitMs = 300 * 1000; + + return await pollUntil( + async () => { + try { + handle = await sdk.get(handle.id); + } catch (err) { + if (isNotFoundError(err)) { + known.delete(botId); + const fresh = await createFreshSandbox(snapshot, botId); + return { done: true, value: fresh }; + } + throw wrapBotError(botId, "locate", err); + } + + if (handle.state === SandboxState.STARTED) { + return { done: true, value: handle }; + } + if ( + handle.state === SandboxState.STOPPED || + handle.state === SandboxState.PAUSED || + handle.state === SandboxState.ARCHIVED + ) { + try { + await handle.start(300); + } catch (err) { + throw wrapBotError(botId, "start", err); + } + return { done: true, value: handle }; + } + if ( + handle.state === SandboxState.DESTROYED || + handle.state === SandboxState.DESTROYING + ) { + known.delete(botId); + const fresh = await createFreshSandbox(snapshot, botId); + return { done: true, value: fresh }; + } + if ( + handle.state === SandboxState.ERROR || + handle.state === SandboxState.BUILD_FAILED + ) { + throw new ProviderError( + `Daytona sandbox for ${botId} failed with state "${handle.state}".`, + ); + } + return { done: false }; + }, + { + timeoutMs: maxWaitMs, + intervalMs: pollInterval, + timeoutError: () => + new ProviderError( + `Timed out waiting for computer sandbox for ${botId} to start.`, + ), + }, + ); + } + + async function pollHealth(previewUrl: string, botId: string): Promise { + const healthPollInterval = + options.pollIntervalMs ?? HEALTH_POLL_INTERVAL_MS; + const healthTimeout = options.healthTimeoutMs ?? DEFAULT_HEALTH_TIMEOUT_MS; + const healthDeadline = Date.now() + healthTimeout; + const healthUrl = `${previewUrl.replace(/\/$/, "")}/health`; + + while (Date.now() < healthDeadline) { + const remainingMs = Math.max(1, healthDeadline - Date.now()); + try { + const res = await withTimeout( + doFetch(healthUrl, { + headers: { + "X-Daytona-Skip-Preview-Warning": "true", + }, + signal: AbortSignal.timeout(remainingMs), + }), + remainingMs, + ); + if (res.ok) { + return; + } + } catch { + // Health endpoint not reachable yet or timed out; retry + } + const sleepMs = Math.min( + healthPollInterval, + Math.max(0, healthDeadline - Date.now()), + ); + if (sleepMs > 0) { + await sleep(sleepMs); + } + } + + throw new ProviderError( + `The computer for ${botId} started but never answered /health at its preview URL.`, + ); + } + + return { + name: "Daytona", + isolation: "per-bot", + + async locate(botId: string): Promise { + return runLifecycle(botId, async () => { + const snapshot = await ensureSnapshot(); + + let sandboxHandle = await resolveSandbox(botId, "locate"); + const expectedTokenHash = computeTokenHash(options.computerToken); + + if (sandboxHandle) { + const tokenHash = sandboxHandle.labels?.[TOKEN_HASH_LABEL_KEY]; + + if ( + tokenHash !== expectedTokenHash || + sandboxHandle.state === SandboxState.ERROR || + sandboxHandle.state === SandboxState.BUILD_FAILED + ) { + try { + await sandboxHandle.delete(60, true); + } catch { + // Best-effort cleanup of stale or broken sandboxes + } + known.delete(botId); + resetSandboxes.set(botId, sandboxHandle.id); + sandboxHandle = undefined; + } + } + + if (!sandboxHandle) { + sandboxHandle = await createFreshSandbox(snapshot, botId); + } else { + sandboxHandle = await ensureSandboxStarted( + sandboxHandle, + botId, + snapshot, + ); + } + + known.set(botId, sandboxHandle.id); + + let previewUrl: string; + try { + const preview = await sandboxHandle.getPreviewLink(COMPUTER_PORT); + previewUrl = preview.url; + } catch (err) { + throw wrapBotError(botId, "locate", err); + } + + await pollHealth(previewUrl, botId); + + try { + await sandboxHandle.refreshActivity(); + } catch (err) { + throw wrapBotError(botId, "refresh activity on", err); + } + + return previewUrl; + }); + }, + + async status(botId: string): Promise { + const sandboxHandle = await resolveSandbox(botId, "inspect", true); + if (!sandboxHandle) { + return { botId, state: "absent" }; + } + return toComputerStatus(botId, sandboxHandle.state); + }, + + async stop(botId: string): Promise<{ wasRunning: boolean }> { + return runLifecycle(botId, async () => { + const sandboxHandle = await resolveSandbox(botId, "stop"); + if (!sandboxHandle) { + return { wasRunning: false }; + } + if (sandboxHandle.state !== SandboxState.STARTED) { + return { wasRunning: false }; + } + try { + await sandboxHandle.stop(); + return { wasRunning: true }; + } catch (err) { + throw wrapBotError(botId, "stop", err); + } + }); + }, + + async reset(botId: string): Promise<{ cleared: boolean }> { + return runLifecycle(botId, async () => { + const sandboxHandle = await resolveSandbox(botId, "reset"); + known.delete(botId); + if (!sandboxHandle) { + return { cleared: false }; + } + try { + await sandboxHandle.delete(60, true); + } catch (err) { + throw wrapBotError(botId, "reset", err); + } + resetSandboxes.set(botId, sandboxHandle.id); + return { cleared: true }; + }); + }, + + async list(): Promise { + try { + const matching: { botId: string; sb: SandboxHandle }[] = []; + for await (const sb of sdk.list({ + labels: { + [OWNERSHIP_LABEL_KEY]: OWNERSHIP_LABEL_VALUE, + }, + })) { + if ( + sb.state === SandboxState.DESTROYED || + sb.state === SandboxState.DESTROYING + ) { + continue; + } + const botId = sb.labels?.[BOT_ID_LABEL_KEY]; + if (!botId || resetSandboxes.get(botId) === sb.id) { + continue; + } + matching.push({ botId, sb }); + } + + const result = await Promise.all( + matching.map(async ({ botId, sb }) => { + const preview = await sb.getPreviewLink(COMPUTER_PORT); + const status: "running" | "stopped" = + sb.state === SandboxState.STARTED ? "running" : "stopped"; + return { + botId, + status, + url: preview.url, + startedAt: sb.createdAt, + }; + }), + ); + + return result; + } catch (err) { + throw new ProviderError( + `Daytona failed to list computers: ${toErrorMessage(err)}`, + ); + } + }, + + async warm(): Promise { + try { + await ensureSnapshot(); + } catch (err) { + console.error( + `[daytona] Warm failed to build snapshot: ${toErrorMessage(err)}`, + ); + } + }, + }; +} diff --git a/server/src/computer/gateway.ts b/server/src/computer/gateway.ts index b2337be..6c9e384 100644 --- a/server/src/computer/gateway.ts +++ b/server/src/computer/gateway.ts @@ -18,25 +18,45 @@ * The refs are opaque to the caller precisely so that the server holds the mapping. */ import { type AuditStore, recordAuditEvent } from "../audit"; -import type { ComputerClient } from "./client"; +import { createComputerTransport } from "./client"; +export { + ComputerUnavailableError, + ElementNotFoundError, + NavigationRefusedError, + StaleSnapshotError, + WorkspaceRefusedError, + WorkspaceRequestError, +} from "./client"; import { type ActionPolicy, evaluateActionPolicy, type PolicyContext, type PolicyDecision, } from "./policy"; +import type { ComputerProvider } from "./provider"; import type { + ActionResult, ClickInput, + ComputerStatus, + ControlState, + HumanInput, + HumanInputResult, KeyInput, ListFilesInput, + ListFilesResult, + NavigateResult, ReadFileInput, + ReadFileResult, ReadResult, + ScreenshotResult, ScrollInput, SecretRequest, + SecretResult, SnapshotElement, SnapshotResult, TypeInput, WriteFileInput, + WriteFileResult, } from "./schema"; export class ActionRefusedError extends Error { @@ -59,25 +79,106 @@ export type ActionActor = { }; export type ComputerGatewayOptions = { - /** - * The container supervisor, when each Bot has a computer of its own. - * - * Stop and reset prefer it: a computer that is wedged cannot be asked to stop itself, and that is - * exactly the state where a person reaches for the button. Without a supervisor these stay profile - * operations performed by the computer itself, which fits the single-computer deployment where - * nothing else holds the Docker socket. - */ - supervisor?: { - stop(botId: string): Promise; - reset(botId: string): Promise; - list?(): Promise<{ botId: string; status: string; startedAt?: string }[]>; - }; - client: ComputerClient; + provider: ComputerProvider; auditStore: AuditStore; /** Absent denies everything. See evaluateActionPolicy. */ policy: () => ActionPolicy | undefined; + /** True on a laptop, where browsing private network addresses is required. */ + allowPrivateHosts?: boolean; + /** The secret that agent-computer requires on each request. */ + token?: string; + /** An injectable fetch implementation for focused gateway tests. */ + fetchImpl?: typeof fetch; }; +export interface ComputerGateway { + readonly provider: ComputerProvider; + locate(botId: string): Promise; + status(botId: string): Promise; + screenshot(botId: string): Promise; + snapshot(botId: string): Promise; + read(botId: string): Promise; + navigate( + botId: string, + actor: ActionActor, + url: string, + ): Promise; + click( + botId: string, + actor: ActionActor, + input: ClickInput, + signal?: AbortSignal, + ): Promise; + type( + botId: string, + actor: ActionActor, + input: TypeInput, + signal?: AbortSignal, + ): Promise; + key( + botId: string, + actor: ActionActor, + input: KeyInput, + signal?: AbortSignal, + ): Promise; + scroll( + botId: string, + actor: ActionActor, + input: ScrollInput, + ): Promise; + readFile( + botId: string, + actor: ActionActor, + input: ReadFileInput, + ): Promise; + listFiles( + botId: string, + actor: ActionActor, + input: ListFilesInput, + ): Promise; + writeFile( + botId: string, + actor: ActionActor, + input: WriteFileInput, + ): Promise; + control(botId: string): Promise; + requestHelp( + botId: string, + actor: ActionActor, + reason: string, + ): Promise; + takeControl(botId: string, actor: ActionActor): Promise; + releaseControl(botId: string, actor: ActionActor): Promise; + requestSecret( + botId: string, + actor: ActionActor, + input: SecretRequest, + ): Promise; + supplySecret( + botId: string, + actor: ActionActor, + text: string, + ): Promise; + humanInput(botId: string, input: HumanInput): Promise; + computers(): Promise<{ + isolation: "per-bot" | "shared"; + computers: { + botId: string; + running: boolean; + startedAt: string | null; + egress?: string | null; + }[]; + }>; + stopComputer( + botId: string, + actor: ActionActor, + ): Promise<{ wasRunning: boolean }>; + resetComputer( + botId: string, + actor: ActionActor, + ): Promise<{ cleared: boolean }>; +} + /** * The last snapshot the server took, per computer. * @@ -92,23 +193,59 @@ type CachedSnapshot = { url: string; }; -export function createComputerGateway(options: ComputerGatewayOptions) { - const { client, auditStore, supervisor } = options; +export function createComputerGateway( + options: ComputerGatewayOptions, +): ComputerGateway { + const { provider, auditStore } = options; + const transport = createComputerTransport({ + ...(options.token ? { token: options.token } : {}), + ...(options.allowPrivateHosts !== undefined + ? { allowPrivateHosts: options.allowPrivateHosts } + : {}), + ...(options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}), + }); const snapshots = new Map(); - /** - * The computer, addressed as the Bot that is asking. - * - * Every call goes through this. The Bot's browser, its logins and the proxy its traffic leaves - * through are all keyed on this id at the far end, so a call that forgets it lands on the wrong - * computer, because there is always a computer to answer. - */ - const as = (botId: string) => client.forBot(botId); + function locate(botId: string): Promise { + return provider.locate(botId); + } + + async function get( + botId: string, + path: string, + signal?: AbortSignal, + ): Promise { + return transport.call( + await locate(botId), + botId, + path, + undefined, + signal, + ); + } + + async function post( + botId: string, + path: string, + payload: unknown, + signal?: AbortSignal, + ): Promise { + return transport.post(await locate(botId), botId, path, payload, signal); + } /** Read-only, so it passes straight through. Nothing has changed and there is nothing to decide. */ - async function snapshot(computerId: string): Promise { - const result = await as(computerId).snapshot(); - snapshots.set(computerId, { + async function screenshot(botId: string): Promise { + return get(botId, "/screenshot"); + } + + async function snapshot(botId: string): Promise { + const result = await transport.call( + await locate(botId), + botId, + "/snapshot", + { method: "POST" }, + ); + snapshots.set(botId, { snapshotId: result.snapshotId, url: result.url, elements: new Map( @@ -119,7 +256,7 @@ export function createComputerGateway(options: ComputerGatewayOptions) { } async function read(botId: string): Promise { - return as(botId).read(); + return get(botId, "/read"); } /** @@ -130,11 +267,11 @@ export function createComputerGateway(options: ComputerGatewayOptions) { * A deny rule written against a page a Bot has not snapshotted should still refuse it. */ function resolve( - computerId: string, + botId: string, ref: string | undefined, ): SnapshotElement | undefined { if (!ref) return undefined; - return snapshots.get(computerId)?.elements.get(ref); + return snapshots.get(botId)?.elements.get(ref); } /** @@ -145,7 +282,6 @@ export function createComputerGateway(options: ComputerGatewayOptions) { * show that sequence. */ async function govern( - computerId: string, toolName: string, botId: string, actor: ActionActor, @@ -160,8 +296,8 @@ export function createComputerGateway(options: ComputerGatewayOptions) { run: () => Promise, ): Promise { const { ref, filePath } = subject; - const element = resolve(computerId, ref); - const cached = snapshots.get(computerId); + const element = resolve(botId, ref); + const cached = snapshots.get(botId); // For a navigation the relevant page is the one being opened, not the one already loaded. Using // the cached URL would mean `page.host == "..."` could never match the destination, which is the // only thing a rule about navigation would ever want to say. @@ -194,7 +330,6 @@ export function createComputerGateway(options: ComputerGatewayOptions) { toolName, botId, actor, - computerId, element, ref, ...(subject.key ? { key: subject.key } : {}), @@ -202,7 +337,6 @@ export function createComputerGateway(options: ComputerGatewayOptions) { pageUrl, decision, }); - if (!decision.forward) { throw new ActionRefusedError(decision.reason, decision.matched); } @@ -225,7 +359,6 @@ export function createComputerGateway(options: ComputerGatewayOptions) { toolName, botId, actor, - computerId, element, ref, filePath, @@ -244,9 +377,16 @@ export function createComputerGateway(options: ComputerGatewayOptions) { } return { + provider, + locate, + screenshot, snapshot, read, + status(botId: string): Promise { + return provider.status(botId); + }, + /** * Handovers, recorded but not policy-gated. * @@ -256,28 +396,23 @@ export function createComputerGateway(options: ComputerGatewayOptions) { * row and do not ask. What IS recorded is the period: who, when, and why the Bot asked, the fact * an investigator wants is that a human drove this browser between two times. */ - async requestHelp( - computerId: string, - botId: string, - actor: ActionActor, - reason: string, - ) { - const state = await as(botId).requestControl(reason); + async requestHelp(botId: string, actor: ActionActor, reason: string) { + const state = await post(botId, "/control/request", { + reason, + }); await writeControlEvent(auditStore, "computer.help_requested", { botId, actor, - computerId, reason, }); return state; }, - async takeControl(computerId: string, botId: string, actor: ActionActor) { - const state = await as(botId).takeControl(); + async takeControl(botId: string, actor: ActionActor) { + const state = await post(botId, "/control/take", {}); await writeControlEvent(auditStore, "computer.control_taken", { botId, actor, - computerId, // Carried onto the row so the trail says what the person was handed, not merely that they // took over. reason: state.reason, @@ -285,49 +420,31 @@ export function createComputerGateway(options: ComputerGatewayOptions) { return state; }, - async releaseControl( - computerId: string, - botId: string, - actor: ActionActor, - ) { - const state = await as(botId).releaseControl(); + async releaseControl(botId: string, actor: ActionActor) { + const state = await post(botId, "/control/release", {}); await writeControlEvent(auditStore, "computer.control_released", { botId, actor, - computerId, }); return state; }, - control(botId: string) { - return as(botId).control(); + control(botId: string): Promise { + return get(botId, "/control"); }, - /** - * The computers, for the admin surface. A read, so no audit row. - * - * With a supervisor the list is the containers, because that is what a computer is: one per - * Bot, each with its own storage, and the page's Stop and Reset act on those. Asking a single - * computer for its profiles would answer for the one shared browser instead, which is the older - * arrangement and no longer what an administrator is looking at. - */ + /** Return every computer that the configured provider owns. */ async computers() { - if (supervisor?.list) { - const running = await supervisor.list(); - return { - // Said, not inferred. Without a supervisor every Bot shares one browser, which looks - // identical on every screen to each having its own, same cards, same trail, same - // screenshots. A reader has to be told which deployment they are looking at. - isolation: "per-bot" as const, - computers: running.map((computer) => ({ - botId: computer.botId, - running: computer.status === "running", - startedAt: computer.startedAt ?? null, - egress: null, - })), - }; - } - return { isolation: "shared" as const, ...(await client.computers()) }; + const computers = await provider.list(); + return { + isolation: provider.isolation, + computers: computers.map((computer) => ({ + botId: computer.botId, + running: computer.status === "running", + startedAt: computer.startedAt ?? null, + egress: computer.egress, + })), + }; }, /** @@ -338,25 +455,14 @@ export function createComputerGateway(options: ComputerGatewayOptions) { * fact worth having, and a trail that only records effective actions cannot tell you what somebody * tried. */ - async stopComputer(computerId: string, botId: string, actor: ActionActor) { - if (supervisor) { - await supervisor.stop(botId); - await writeControlEvent(auditStore, "computer.stopped", { - botId, - actor, - computerId, - reason: "the container was stopped", - }); - return { wasRunning: true }; - } - const result = await as(botId).stopComputer(); + async stopComputer(botId: string, actor: ActionActor) { + const result = await provider.stop(botId); await writeControlEvent(auditStore, "computer.stopped", { botId, actor, - computerId, reason: result.wasRunning - ? "browser was running" - : "no browser was running", + ? "the computer was stopped" + : "the computer was already stopped", }); return result; }, @@ -367,23 +473,15 @@ export function createComputerGateway(options: ComputerGatewayOptions) { * The most destructive button we have. Every login the Bot had is gone and no undo exists, so the * row is written whatever happens next. */ - async resetComputer(computerId: string, botId: string, actor: ActionActor) { - if (supervisor) { - await supervisor.reset(botId); - await writeControlEvent(auditStore, "computer.reset", { - botId, - actor, - computerId, - reason: "the container and its profile were deleted", - }); - return { cleared: true }; - } - const result = await as(botId).resetComputer(); + async resetComputer(botId: string, actor: ActionActor) { + const result = await provider.reset(botId); + snapshots.delete(botId); await writeControlEvent(auditStore, "computer.reset", { botId, actor, - computerId, - reason: "every saved login on this computer was deleted", + reason: result.cleared + ? "the computer and its saved state were deleted" + : "no saved state was present to delete", }); return result; }, @@ -397,129 +495,104 @@ export function createComputerGateway(options: ComputerGatewayOptions) { * keyboard to the page, and is not on this one. */ async requestSecret( - computerId: string, botId: string, actor: ActionActor, input: SecretRequest, ) { - const state = await as(botId).requestSecret(input); + const state = await post(botId, "/control/secret", input); await writeControlEvent(auditStore, "computer.secret_requested", { botId, actor, - computerId, reason: `${input.label} (into ${input.ref})`, }); return state; }, - async supplySecret( - computerId: string, - botId: string, - actor: ActionActor, - text: string, - ) { - const result = await as(botId).supplySecret(text); + async supplySecret(botId: string, actor: ActionActor, text: string) { + const result = await post(botId, "/human/secret", { text }); await writeControlEvent(auditStore, "computer.secret_supplied", { botId, actor, - computerId, // Length, never content. Enough to show something real was entered. reason: `${result.characters} characters`, }); return result; }, - humanInput( + async humanInput( botId: string, - input: Parameters[0], - ) { - return as(botId).humanInput(input); + input: HumanInput, + ): Promise { + const { kind, ...payload } = input; + return post(botId, `/human/${kind}`, payload); }, /** * Opening a page, through the gateway so it lands in the audit trail. * - * The client still applies its target guard, which is the floor that holds under every policy, - * including one that permits everything. This adds the record and the per-Bot decision on top: a - * refusal by either produces a row, so navigation denials are visible in the audit trail. + * The transport applies its target guard before it sends a request. This is + * the minimum rule that applies even when the action policy permits the URL. */ - navigate( - computerId: string, - botId: string, - actor: ActionActor, - url: string, - ) { + navigate(botId: string, actor: ActionActor, url: string) { return govern( - computerId, "computer_navigate", botId, actor, { targetUrl: url }, - () => as(botId).navigate(url), + async () => transport.navigate(await locate(botId), botId, url), ); }, click( - computerId: string, botId: string, actor: ActionActor, input: ClickInput, signal?: AbortSignal, ) { return govern( - computerId, "computer_click", botId, actor, { ref: input.ref, ...(signal ? { signal } : {}) }, - () => as(botId).click(input, signal), + () => post(botId, "/click", input, signal), ); }, type( - computerId: string, botId: string, actor: ActionActor, input: TypeInput, signal?: AbortSignal, ) { return govern( - computerId, "computer_type", botId, actor, { ref: input.ref, ...(signal ? { signal } : {}) }, - () => as(botId).type(input, signal), + () => post(botId, "/type", input, signal), ); }, key( - computerId: string, botId: string, actor: ActionActor, input: KeyInput, signal?: AbortSignal, ) { return govern( - computerId, "computer_key", botId, actor, // The key is part of the subject, so a rule can tell Enter from a letter. Form submission can // happen through a keypress as well as a click, so the policy context carries the key. { ref: input.ref, key: input.key, ...(signal ? { signal } : {}) }, - () => as(botId).key(input, signal), + () => post(botId, "/key", input, signal), ); }, - scroll( - computerId: string, - botId: string, - actor: ActionActor, - input: ScrollInput, - ) { - return govern(computerId, "computer_scroll", botId, actor, {}, () => - as(botId).scroll(input), + scroll(botId: string, actor: ActionActor, input: ScrollInput) { + return govern("computer_scroll", botId, actor, {}, () => + post(botId, "/scroll", input), ); }, @@ -530,19 +603,13 @@ export function createComputerGateway(options: ComputerGatewayOptions) { * workspace accumulates whatever a Bot has saved across every task it has ever run, so which of * those files it may read back is a real question for a deployment to be able to answer. */ - readFile( - computerId: string, - botId: string, - actor: ActionActor, - input: ReadFileInput, - ) { + readFile(botId: string, actor: ActionActor, input: ReadFileInput) { return govern( - computerId, "computer_read_file", botId, actor, { filePath: input.path }, - () => as(botId).readFile(input), + () => post(botId, "/files/read", input), ); }, @@ -551,35 +618,23 @@ export function createComputerGateway(options: ComputerGatewayOptions) { * every task it has run is worth being able to restrict. A rule denying a folder hides it from the * listing as well as from reads, which is the consistent answer. */ - listFiles( - computerId: string, - botId: string, - actor: ActionActor, - input: ListFilesInput, - ) { + listFiles(botId: string, actor: ActionActor, input: ListFilesInput) { return govern( - computerId, "computer_list_files", botId, actor, { filePath: input.path ?? "." }, - () => as(botId).listFiles(input), + () => post(botId, "/files/list", input), ); }, - writeFile( - computerId: string, - botId: string, - actor: ActionActor, - input: WriteFileInput, - ) { + writeFile(botId: string, actor: ActionActor, input: WriteFileInput) { return govern( - computerId, "computer_write_file", botId, actor, { filePath: input.path }, - () => as(botId).writeFile(input), + () => post(botId, "/files/write", input), ); }, }; @@ -607,8 +662,6 @@ function describeFile(path: string): { }; } -export type ComputerGateway = ReturnType; - /** * One audit row for one decision. * @@ -665,7 +718,6 @@ async function write( toolName: string; botId: string; actor: ActionActor; - computerId: string; element: SnapshotElement | undefined; ref: string | undefined; /** Which key, for a keypress. Recorded because a keypress can act without naming a button. */ @@ -686,7 +738,7 @@ async function write( ? "computer.action_allowed" : "computer.action_refused", targetType: "computer", - targetId: entry.computerId, + targetId: entry.botId, // Only ever a real users row. The audit table has a foreign key to it, so writing the local // development actor's id here makes every action fail on a constraint violation instead of being // recorded. Who it was is in the payload either way. @@ -762,14 +814,13 @@ async function writeControlEvent( entry: { botId: string; actor: ActionActor; - computerId: string; reason?: string; }, ) { await recordAuditEvent(auditStore, { eventType, targetType: "computer", - targetId: entry.computerId, + targetId: entry.botId, ...(entry.actor.userId ? { actorUserId: entry.actor.userId } : {}), payload: { bot: entry.botId, diff --git a/server/src/computer/provider.ts b/server/src/computer/provider.ts new file mode 100644 index 0000000..65854e8 --- /dev/null +++ b/server/src/computer/provider.ts @@ -0,0 +1,237 @@ +import type { ComputerConfig } from "../config"; +import { createDaytonaComputerProvider } from "./daytona"; +import { + createDockerSupervisorProvider, + type SupervisorOptions, +} from "./supervisor"; + +import type { ComputerStatus } from "./schema"; + +/** The address and lifecycle details for one Bot's computer. */ +export type ComputerLocation = { + botId: string; + status: "running" | "stopped"; + url?: string; + startedAt?: string; + egress?: string | null; +}; + +/** A description of how a provider separates one Bot's computer from another. */ +export type IsolationDescription = { + isolation: "off" | "one computer per Bot" | "one shared computer"; + note: string; + warning?: string; +}; + +/** An error from a computer provider. */ +export class ProviderError extends Error { + constructor(message: string) { + super(message); + this.name = "ProviderError"; + } +} + +/** Describe the isolation that this provider (or lack of provider) gives to Bots. */ +export function describeComputerIsolation( + provider?: ComputerProvider, +): IsolationDescription { + if (!provider) { + return { + isolation: "off", + note: "The computer feature is off. No computer provider is configured.", + }; + } + + if (provider.isolation === "per-bot") { + return { + isolation: "one computer per Bot", + note: "Each Bot gets its own isolated computer with its own /workspace and browser profile.", + }; + } + + return { + isolation: "one shared computer", + note: "No supervisor is configured, so every Bot uses the same browser. Sessions, files and logins are shared between them. Set COMPUTER_SUPERVISOR_URL or DAYTONA_API_KEY to give each Bot its own.", + warning: + "Every Bot shares one browser. Set COMPUTER_SUPERVISOR_URL or DAYTONA_API_KEY for a computer each.", + }; +} + +/** + * A backend that gives Bots access to a computer. + * + * Implementations can use a computer for each Bot or one computer for all Bots. + * Callers use this interface and do not need to know which backend is active. + */ +export interface ComputerProvider { + /** The provider name for logs and status output. */ + readonly name: string; + /** How the provider separates computers between Bots. */ + readonly isolation: "per-bot" | "shared"; + /** Return the base address of the computer for this Bot. */ + locate(botId: string): Promise; + /** Return the lifecycle state of the computer for this Bot. */ + status(botId: string): Promise; + /** Stop the computer for this Bot if it exists. */ + stop(botId: string): Promise<{ wasRunning: boolean }>; + /** Remove the computer state for this Bot if it exists. */ + reset(botId: string): Promise<{ cleared: boolean }>; + /** List the computers that this provider owns. */ + list(): Promise; + /** Prepare provider resources before the first computer request. */ + warm?(): Promise; +} + +export type SharedComputerProviderOptions = { + baseUrl: string; + token?: string; + fetchImpl?: typeof fetch; + timeoutMs?: number; +}; +type SharedComputerEntry = { + botId: string; + running?: boolean; + status?: string; + url?: string; + startedAt?: string | null; + egress?: string | null; +}; +/** + * Give every Bot the same computer. + * + * This adapter keeps shared deployments behind the same provider seam as the + * Docker supervisor and Daytona. + */ +export function createSharedComputerProvider( + options: SharedComputerProviderOptions, +): ComputerProvider { + const base = options.baseUrl.replace(/\/$/, ""); + const fetchImpl = options.fetchImpl ?? fetch; + const timeoutMs = options.timeoutMs ?? 45_000; + + function headers(botId?: string): Record { + return { + ...(botId ? { "x-openbot-bot-id": botId } : {}), + ...(options.token ? { "x-openbot-computer-token": options.token } : {}), + }; + } + + async function call( + path: string, + method: "GET" | "POST", + botId?: string, + ): Promise { + let response: Response; + try { + response = await fetchImpl(`${base}${path}`, { + method, + headers: headers(botId), + signal: AbortSignal.timeout(timeoutMs), + }); + } catch (error) { + throw new ProviderError( + `The shared computer at ${base} could not be reached (${error instanceof Error ? error.message : String(error)}).`, + ); + } + + const body = (await response.json().catch(() => null)) as { + error?: string; + } | null; + if (!response.ok) { + throw new ProviderError( + body?.error ?? `The shared computer answered ${response.status}.`, + ); + } + return body; + } + + return { + name: "shared", + isolation: "shared", + + async locate(_botId: string): Promise { + return options.baseUrl; + }, + + async status(botId: string): Promise { + try { + await call("/health", "GET", botId); + return { botId, state: "ready" }; + } catch (error) { + return { + botId, + state: "unreachable", + reason: + error instanceof Error && error.message.length > 0 + ? error.message + : "Unknown failure.", + }; + } + }, + + async stop(botId: string): Promise<{ wasRunning: boolean }> { + const body = (await call("/computers/stop", "POST", botId)) as { + wasRunning?: boolean; + stopped?: boolean; + } | null; + return { + wasRunning: body?.wasRunning ?? body?.stopped ?? false, + }; + }, + + async reset(botId: string): Promise<{ cleared: boolean }> { + const body = (await call("/computers/reset", "POST", botId)) as { + cleared?: boolean; + reset?: boolean; + } | null; + return { + cleared: body?.cleared ?? body?.reset ?? false, + }; + }, + + async list(): Promise { + const body = (await call("/computers", "GET")) as { + computers?: SharedComputerEntry[]; + }; + return (body?.computers ?? []).map((computer) => ({ + botId: computer.botId, + status: + computer.status === "running" || computer.running === true + ? "running" + : "stopped", + url: computer.url ?? base, + ...(computer.startedAt ? { startedAt: computer.startedAt } : {}), + ...(computer.egress !== undefined ? { egress: computer.egress } : {}), + })); + }, + }; +} + +/** Build the one computer provider selected by deployment configuration. */ +export function createComputerProvider( + config: ComputerConfig, +): ComputerProvider { + switch (config.provider) { + case "daytona": + return createDaytonaComputerProvider({ + apiKey: config.apiKey, + computerToken: config.token, + environment: process.env, + ...(config.apiUrl ? { apiUrl: config.apiUrl } : {}), + ...(config.target ? { target: config.target } : {}), + ...(config.snapshot ? { snapshot: config.snapshot } : {}), + }); + case "docker": { + const options: SupervisorOptions = { + baseUrl: config.baseUrl, + ...(config.supervisorToken ? { token: config.supervisorToken } : {}), + }; + return createDockerSupervisorProvider(options); + } + case "shared": + return createSharedComputerProvider({ + baseUrl: config.baseUrl, + ...(config.token ? { token: config.token } : {}), + }); + } +} diff --git a/server/src/computer/routes.ts b/server/src/computer/routes.ts index 3ce1c28..63a1001 100644 --- a/server/src/computer/routes.ts +++ b/server/src/computer/routes.ts @@ -3,18 +3,15 @@ import { Hono } from "hono"; import type { AppVariables } from "../auth/guards"; import { requireAdmin } from "../auth/guards"; import { - type ComputerClient, + type ActionActor, + ActionRefusedError, + type ComputerGateway, ComputerUnavailableError, ElementNotFoundError, NavigationRefusedError, StaleSnapshotError, WorkspaceRefusedError, WorkspaceRequestError, -} from "./client"; -import { - type ActionActor, - ActionRefusedError, - type ComputerGateway, } from "./gateway"; import { type PolicyStore, parseActionPolicy } from "./policy-store"; @@ -25,27 +22,24 @@ import { type PolicyStore, parseActionPolicy } from "./policy-store"; * session guard because `COMPUTER_TOKEN` proves the caller is an internal service, not which user is * asking to drive the browser. * - * Read-only calls go to the client; acting calls go to the gateway. That split is the governance - * boundary: every acting route in this file passes through a policy decision and audit row before it - * reaches the computer. + * Every computer call goes through the gateway. That is the governance seam: each acting route in + * this file passes through a policy decision and audit row before it reaches the computer. */ export function createComputerRoutes( - client: ComputerClient, gateway: ComputerGateway, policyStore: PolicyStore, requireUser: MiddlewareHandler<{ Variables: AppVariables }>, ) { const routes = new Hono<{ Variables: AppVariables }>(); - routes.get("/:botId/status", requireUser, async (context) => - context.json(await client.status(context.req.param("botId"))), - ); + routes.get("/:botId/status", requireUser, async (context) => { + const botId = context.req.param("botId"); + return context.json(await gateway.status(botId)); + }); routes.get("/:botId/screenshot", requireUser, async (context) => { try { - return context.json( - await client.forBot(context.req.param("botId")).screenshot(), - ); + return context.json(await gateway.screenshot(context.req.param("botId"))); } catch (error) { return context.json({ error: describe(error) }, statusFor(error)); } @@ -70,7 +64,6 @@ export function createComputerRoutes( try { return context.json( await gateway.navigate( - context.req.param("botId") ?? "default", context.req.param("botId") ?? "default", { id: context.var.actor.id, @@ -106,14 +99,14 @@ export function createComputerRoutes( /** * The acting routes. * - * Each one hands the gateway the computer id, the Bot, the actor and the input, and does no checking + * Each one hands the gateway the Bot, the actor and the input, and does no checking * of its own beyond the shape of the request. Where a decision gets made is a single place. */ routes.post("/:botId/click", requireUser, (context) => act(context, (botId, actor, body, signal) => { const ref = asRef(body); if (!ref) return badRef; - return gateway.click(botId, botId, actor, ref, signal); + return gateway.click(botId, actor, ref, signal); }), ); @@ -125,7 +118,6 @@ export function createComputerRoutes( return { error: "The text to enter is required." }; } return gateway.type( - botId, botId, actor, { @@ -145,7 +137,6 @@ export function createComputerRoutes( } const ref = asRef(body); return gateway.key( - botId, botId, actor, { @@ -159,7 +150,7 @@ export function createComputerRoutes( routes.post("/:botId/scroll", requireUser, (context) => act(context, (botId, actor, body) => - gateway.scroll(botId, botId, actor, { + gateway.scroll(botId, actor, { ...(typeof body?.deltaY === "number" ? { deltaY: body.deltaY } : {}), }), ), @@ -180,7 +171,6 @@ export function createComputerRoutes( routes.post("/:botId/control/request", requireUser, (context) => act(context, (botId, actor, body) => gateway.requestHelp( - botId, botId, actor, typeof body?.reason === "string" && body.reason.trim() @@ -207,20 +197,20 @@ export function createComputerRoutes( /** Stop the browser, keep the logins. */ routes.post("/:botId/computers/stop", requireUser, (context) => - act(context, (botId, actor) => gateway.stopComputer(botId, botId, actor)), + act(context, (botId, actor) => gateway.stopComputer(botId, actor)), ); /** Delete the profile. Every login goes with it, which is the point and also the danger. */ routes.post("/:botId/computers/reset", requireUser, (context) => - act(context, (botId, actor) => gateway.resetComputer(botId, botId, actor)), + act(context, (botId, actor) => gateway.resetComputer(botId, actor)), ); routes.post("/:botId/control/take", requireUser, (context) => - act(context, (botId, actor) => gateway.takeControl(botId, botId, actor)), + act(context, (botId, actor) => gateway.takeControl(botId, actor)), ); routes.post("/:botId/control/release", requireUser, (context) => - act(context, (botId, actor) => gateway.releaseControl(botId, botId, actor)), + act(context, (botId, actor) => gateway.releaseControl(botId, actor)), ); /** The Bot asking for a value it must not be told. */ @@ -235,7 +225,7 @@ export function createComputerRoutes( if (typeof body?.snapshotId !== "number") { return { error: "The snapshotId the ref came from is required." }; } - return gateway.requestSecret(botId, botId, actor, { + return gateway.requestSecret(botId, actor, { label: typeof body?.label === "string" && body.label.trim() ? body.label.trim() @@ -258,17 +248,16 @@ export function createComputerRoutes( if (typeof body?.text !== "string" || !body.text) { return { error: "A value is required." }; } - return gateway.supplySecret(botId, botId, actor, body.text); + return gateway.supplySecret(botId, actor, body.text); }), ); /** * A person's own mouse and keyboard. * - * Not through the policy gateway, and not audited per keystroke, see the note on `humanInput` in - * client.ts. The takeover is the audited event; what the person typed during it is deliberately - * unrecorded, because the reason a takeover exists is to let them enter the thing nothing else - * should keep. + * Not through the policy decision, and not audited per keystroke. See `ComputerGateway.humanInput`. + * The takeover is the audited event; what the person typed during it is deliberately unrecorded, + * because the reason a takeover exists is to let them enter the thing nothing else should keep. */ routes.post("/:botId/human/:kind", requireUser, async (context) => { const kind = context.req.param("kind"); @@ -299,7 +288,7 @@ export function createComputerRoutes( /** The Bot's files. Through the gateway, like every other acting call. */ routes.post("/:botId/files/list", requireUser, (context) => act(context, (botId, actor, body) => - gateway.listFiles(botId, botId, actor, { + gateway.listFiles(botId, actor, { ...(typeof body?.path === "string" && body.path.trim() ? { path: body.path.trim() } : {}), @@ -312,7 +301,7 @@ export function createComputerRoutes( if (typeof body?.path !== "string" || !body.path.trim()) { return { error: "A file path is required." }; } - return gateway.readFile(botId, botId, actor, { path: body.path.trim() }); + return gateway.readFile(botId, actor, { path: body.path.trim() }); }), ); @@ -324,7 +313,7 @@ export function createComputerRoutes( if (typeof body?.contents !== "string") { return { error: "The contents to write are required." }; } - return gateway.writeFile(botId, botId, actor, { + return gateway.writeFile(botId, actor, { path: body.path.trim(), contents: body.contents, append: body.append === true, diff --git a/server/src/computer/supervisor.ts b/server/src/computer/supervisor.ts index 6212973..2af756c 100644 --- a/server/src/computer/supervisor.ts +++ b/server/src/computer/supervisor.ts @@ -13,9 +13,12 @@ * honest about being one shared computer. */ -export type ComputerLocation = { +import type { ComputerStatus } from "./schema"; +import type { ComputerLocation, ComputerProvider } from "./provider"; + +type SupervisorComputerLocation = { botId: string; - container: string; + container?: string; status: string; port?: number; /** Where to reach it, decided by the supervisor rather than assembled here. */ @@ -39,7 +42,9 @@ export class SupervisorError extends Error { } } -export function createSupervisorClient(options: SupervisorOptions) { +export function createDockerSupervisorProvider( + options: SupervisorOptions, +): ComputerProvider { const doFetch = options.fetchImpl ?? fetch; const base = options.baseUrl.replace(/\/$/, ""); const timeoutMs = options.timeoutMs ?? 120_000; @@ -64,6 +69,9 @@ export function createSupervisorClient(options: SupervisorOptions) { const body = (await response.json().catch(() => null)) as { error?: string; + stopped?: boolean; + reset?: boolean; + computers?: SupervisorComputerLocation[]; } | null; if (!response.ok) { throw new SupervisorError( @@ -73,7 +81,64 @@ export function createSupervisorClient(options: SupervisorOptions) { return body; } + async function listRaw(): Promise { + const body = (await call("/computers", "GET")) as { + computers?: SupervisorComputerLocation[]; + } | null; + return body?.computers ?? []; + } + + async function list(): Promise { + const computers = await listRaw(); + return computers.map((computer) => ({ + botId: computer.botId, + status: + computer.status.toLowerCase() === "running" ? "running" : "stopped", + ...(computer.url + ? { url: computer.url } + : computer.port + ? { url: hostForPort(computer.port) } + : {}), + ...(computer.startedAt ? { startedAt: computer.startedAt } : {}), + })); + } + + function statusFromLocation( + botId: string, + location: SupervisorComputerLocation | undefined, + ): ComputerStatus { + if (!location) return { botId, state: "absent" }; + + const rawStatus = location.status.toLowerCase(); + switch (rawStatus) { + case "running": + return { botId, state: "ready" }; + case "created": + case "restarting": + return { botId, state: "starting" }; + case "paused": + case "removing": + case "exited": + return { botId, state: "absent" }; + case "dead": + return { + botId, + state: "unreachable", + reason: `The computer reported state "${location.status}".`, + }; + default: + return { + botId, + state: "unreachable", + reason: `The computer reported unknown state "${location.status}".`, + }; + } + } + return { + name: "Docker supervisor", + isolation: "per-bot", + /** * The URL of this Bot's computer, starting it if it is not already up. * @@ -84,7 +149,7 @@ export function createSupervisorClient(options: SupervisorOptions) { async locate(botId: string): Promise { const state = (await call( `/computers/${encodeURIComponent(botId)}/ensure`, - )) as ComputerLocation; + )) as SupervisorComputerLocation; // The supervisor says where it is, because only it knows whether these computers sit on a // shared network or answer on a published port. if (state?.url) return state.url; @@ -94,21 +159,39 @@ export function createSupervisorClient(options: SupervisorOptions) { ); }, - async stop(botId: string): Promise { - await call(`/computers/${encodeURIComponent(botId)}/stop`); + async status(botId: string): Promise { + try { + const computers = await listRaw(); + return statusFromLocation( + botId, + computers.find((computer) => computer.botId === botId), + ); + } catch (error) { + return { + botId, + state: "unreachable", + reason: + error instanceof Error && error.message.length > 0 + ? error.message + : "Unknown failure.", + }; + } }, - async reset(botId: string): Promise { - await call(`/computers/${encodeURIComponent(botId)}/reset`); + async stop(botId: string): Promise<{ wasRunning: boolean }> { + const result = (await call( + `/computers/${encodeURIComponent(botId)}/stop`, + )) as { stopped?: boolean } | null; + return { wasRunning: result?.stopped === true }; }, - async list(): Promise { - const body = (await call("/computers", "GET")) as { - computers?: ComputerLocation[]; - }; - return body?.computers ?? []; + async reset(botId: string): Promise<{ cleared: boolean }> { + const result = (await call( + `/computers/${encodeURIComponent(botId)}/reset`, + )) as { reset?: boolean } | null; + return { cleared: result?.reset === true }; }, + + list, }; } - -export type SupervisorClient = ReturnType; diff --git a/server/src/config.ts b/server/src/config.ts index a85e8f0..a81199b 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -21,6 +21,39 @@ export type IntelligenceSettings = { licenseToken: string; }; +export type DaytonaComputerConfig = { + provider: "daytona"; + apiKey: string; + apiUrl?: string; + target?: string; + snapshot?: string; + token: string; + allowPrivateHosts: boolean; + policy?: ActionPolicy; +}; + +export type DockerComputerConfig = { + provider: "docker"; + baseUrl: string; + supervisorToken?: string; + token?: string; + allowPrivateHosts: boolean; + policy?: ActionPolicy; +}; + +export type SharedComputerConfig = { + provider: "shared"; + baseUrl: string; + token?: string; + allowPrivateHosts: boolean; + policy?: ActionPolicy; +}; + +export type ComputerConfig = + | DaytonaComputerConfig + | DockerComputerConfig + | SharedComputerConfig; + export type DeploymentConfig = { databaseUrl: string; keyEncryptionKey: string; @@ -62,27 +95,7 @@ export type DeploymentConfig = { * The Bot computer. Absent means the feature is off and its routes are not mounted, rather than * mounted and failing: a capability that is not configured should be missing, not broken. */ - computer?: { - baseUrl: string; - /** The secret every computer requires of its caller. */ - token?: string; - /** - * The container supervisor, when each Bot is to get a computer of its own. Absent means one - * shared computer at `baseUrl`, which is what a laptop wants and is honest about being one - * machine. - */ - supervisor?: { baseUrl: string; token?: string }; - /** True on a laptop, where browsing the deployment's own services is the point. */ - allowPrivateHosts: boolean; - /** - * What Bots may do on their computers. Absent means the built-in default applies. - * - * A whole policy in one variable rather than a variable per rule, because the rules are an - * ordered pair of lists and splitting them across `AGENT_COMPUTER_DENY_1`-style names makes their - * precedence, which is the only subtle thing about them, impossible to see. - */ - policy?: ActionPolicy; - }; + computer?: ComputerConfig; /** * The secret a Bot presents when it calls a tool back through this server. * @@ -274,36 +287,76 @@ function runtimeCapabilities(environment: Environment): RuntimeCapabilities { }; } -function computerConfig( - environment: Environment, -): DeploymentConfig["computer"] { - const baseUrl = url(environment, "AGENT_COMPUTER_URL"); - if (!baseUrl) { +function computerConfig(environment: Environment): ComputerConfig | undefined { + const daytonaKey = optional(environment, "DAYTONA_API_KEY"); + const supervisorAddress = optional(environment, "COMPUTER_SUPERVISOR_URL"); + const sharedAddress = optional(environment, "AGENT_COMPUTER_URL"); + if (!daytonaKey && !supervisorAddress && !sharedAddress) { return undefined; } - const policy = actionPolicy(environment); + + if (daytonaKey && supervisorAddress) { + throw new Error( + "Set either DAYTONA_API_KEY or COMPUTER_SUPERVISOR_URL, not both. They are two ways of giving each Bot its own computer.", + ); + } + /* * The secret the computers require. Without it every call to a computer is refused, and that is the * intended failure: `agent-computer` drives a browser holding real logins and must not answer * unauthenticated callers that can reach its port. */ const computerToken = optional(environment, "COMPUTER_TOKEN"); + if (daytonaKey && !computerToken) { + throw new Error( + "DAYTONA_API_KEY is set but COMPUTER_TOKEN is not. Daytona computers are reached over a public preview URL, and the token is the only thing that refuses strangers. Generate one: openssl rand -base64 32", + ); + } + + const allowPrivateHosts = + optional(environment, "AGENT_COMPUTER_ALLOW_PRIVATE_HOSTS") === "true"; + const policy = actionPolicy(environment); + + if (daytonaKey && computerToken) { + const apiUrl = url(environment, "DAYTONA_API_URL"); + const target = optional(environment, "DAYTONA_TARGET"); + const snapshot = optional(environment, "DAYTONA_SNAPSHOT"); + return { + provider: "daytona", + apiKey: daytonaKey, + token: computerToken, + allowPrivateHosts, + ...(apiUrl ? { apiUrl } : {}), + ...(target ? { target } : {}), + ...(snapshot ? { snapshot } : {}), + ...(policy ? { policy } : {}), + }; + } + const supervisorUrl = url(environment, "COMPUTER_SUPERVISOR_URL"); - const supervisorToken = optional(environment, "SUPERVISOR_TOKEN"); + if (supervisorUrl) { + const supervisorToken = optional(environment, "SUPERVISOR_TOKEN"); + return { + provider: "docker", + baseUrl: supervisorUrl, + allowPrivateHosts, + ...(supervisorToken ? { supervisorToken } : {}), + ...(computerToken ? { token: computerToken } : {}), + ...(policy ? { policy } : {}), + }; + } + + const baseUrl = url(environment, "AGENT_COMPUTER_URL"); + if (!baseUrl) { + return undefined; + } + return { + provider: "shared", baseUrl, - allowPrivateHosts: - optional(environment, "AGENT_COMPUTER_ALLOW_PRIVATE_HOSTS") === "true", - ...(policy ? { policy } : {}), + allowPrivateHosts, ...(computerToken ? { token: computerToken } : {}), - ...(supervisorUrl - ? { - supervisor: { - baseUrl: supervisorUrl, - ...(supervisorToken ? { token: supervisorToken } : {}), - }, - } - : {}), + ...(policy ? { policy } : {}), }; } diff --git a/server/src/index.ts b/server/src/index.ts index 9831c3a..7d0b7e1 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -18,13 +18,15 @@ import { createThreadIdentity } from "./channels/thread-identity"; import { websocket as channelSocket } from "./channels/socket"; import { createSandboxedStore } from "./components/sandboxed"; import { createComponentStore } from "./components/store"; -import { createComputerClient } from "./computer/client"; import { createComputerGateway } from "./computer/gateway"; import { createPolicyStore, DEFAULT_ACTION_POLICY, } from "./computer/policy-store"; -import { createSupervisorClient } from "./computer/supervisor"; +import { + createComputerProvider, + describeComputerIsolation, +} from "./computer/provider"; import { loadConfig } from "./config"; import { createConnectorAdminService } from "./connectors"; import { @@ -152,21 +154,13 @@ const roleRepository = createRoleRepository(database); const loadAgentsForActor = createRuntimeAgentLoader(database, agentVault); await synchronizeTenantPackage(database, tenantPackage); const auth = config.auth ? createAuth(config, database) : undefined; -// One computer each, when a supervisor is configured to give them out. Without one every Bot shares -// the computer at `baseUrl`, which is what a laptop wants and is honest about being one machine. -const supervisor = config.computer?.supervisor - ? createSupervisorClient(config.computer.supervisor) - : undefined; -const computerClient = config.computer - ? createComputerClient({ - baseUrl: config.computer.baseUrl, - allowPrivateHosts: config.computer.allowPrivateHosts, - ...(config.computer.token ? { token: config.computer.token } : {}), - ...(supervisor - ? { resolveBaseUrl: (botId: string) => supervisor.locate(botId) } - : {}), - }) +const computerProvider = config.computer + ? createComputerProvider(config.computer) : undefined; + +if (computerProvider?.warm) { + void computerProvider.warm(); +} // What Bots may do on their computers. Configuration supplies the deployment's default; an // administrator can change it while running, and a restart returns to the configured one. const policyStore = createPolicyStore( @@ -187,6 +181,15 @@ const policySource = await policyStore.load(); * unavailable, and the row is a note for a reader rather than something the server depends on. */ const bootAuditStore = createAuditStore(database); +const computerGateway = computerProvider + ? createComputerGateway({ + provider: computerProvider, + auditStore: bootAuditStore, + policy: () => policyStore.get(), + allowPrivateHosts: config.computer?.allowPrivateHosts, + token: config.computer?.token, + }) + : undefined; /** * What a Bot can reach beyond its own computer. @@ -227,33 +230,26 @@ void recordAuditEvent(bootAuditStore, { /* * Record whether each Bot has a computer of its own. * - * Without a supervisor every Bot shares the browser at `AGENT_COMPUTER_URL`. That is a fine way to - * run on a laptop, but the shared isolation state must be visible rather than inferred. + * A shared provider is a fine way to run on a laptop, but the shared isolation state must be visible + * rather than inferred. */ +const isolation = describeComputerIsolation(computerProvider); + void recordAuditEvent(bootAuditStore, { eventType: "computer.isolation_loaded", targetType: "computer", - payload: supervisor - ? { - isolation: "one computer per Bot", - note: "Each Bot gets its own container, its own /workspace and its own browser profile.", - } - : { - isolation: "one shared computer", - note: "No supervisor is configured, so every Bot uses the same browser. Sessions, files and logins are shared between them. Set COMPUTER_SUPERVISOR_URL to give each Bot its own.", - }, + payload: { + isolation: isolation.isolation, + note: isolation.note, + }, }).catch(() => undefined); console.info( JSON.stringify({ type: "computer-isolation", - isolation: supervisor ? "one computer per Bot" : "one shared computer", - ...(supervisor - ? {} - : { - warning: - "Every Bot shares one browser. Set COMPUTER_SUPERVISOR_URL for a computer each.", - }), + provider: computerProvider ? computerProvider.name : "none", + isolation: isolation.isolation, + ...(isolation.warning ? { warning: isolation.warning } : {}), }), ); /** @@ -350,19 +346,8 @@ const app = createApp( (actorId) => (botId, runId) => mintRunAssertion({ botId, actorId, runId }, config.keyEncryptionKey), ), - computerClient, // The only path to an acting call. - computerClient - ? createComputerGateway({ - client: computerClient, - auditStore: bootAuditStore, - // Read on every decision rather than captured once, so a rule an administrator adds while the - // server is running applies to the very next action instead of after a restart. - policy: () => policyStore.get(), - // Stop, reset and the listing act on containers when there are containers to act on. - ...(supervisor ? { supervisor } : {}), - }) - : undefined, + computerGateway, policyStore, // Bots as durable objects, and the channels they run in. agentProfileStore, @@ -445,15 +430,18 @@ serve({ if (!actor) { return new Response("Sign in first.", { status: 401 }); } - // Located per Bot when there is a supervisor, and the one shared computer when there is not. + // Located through the configured provider so every stream follows the same isolation rules. let upstream: string; try { - upstream = toStreamUrl( - supervisor - ? await supervisor.locate(streamBotId) - : config.computer.baseUrl, - streamBotId, - ); + const streamBase = computerProvider + ? await computerProvider.locate(streamBotId) + : undefined; + if (!streamBase) { + return new Response("No computer address is configured.", { + status: 503, + }); + } + upstream = toStreamUrl(streamBase, streamBotId); } catch (error) { // Said out loud rather than falling back to another Bot's computer, which is the failure this // whole path exists to prevent. diff --git a/server/tests/agent-routes.test.ts b/server/tests/agent-routes.test.ts index b05774e..68ff108 100644 --- a/server/tests/agent-routes.test.ts +++ b/server/tests/agent-routes.test.ts @@ -551,9 +551,8 @@ describe("agent route composition", () => { api: { getSession: async () => session }, }, { rolesForUser: async () => ["user"] }, - // Positions 4-11: auditReader, credentialService, packageStatusReader, connectorService, - // copilotHandler, computerClient, computerGateway, computerPolicy. - undefined, + // Positions 4-10: auditReader, credentialService, packageStatusReader, connectorService, + // copilotHandler, computerGateway, computerPolicy. undefined, undefined, undefined, diff --git a/server/tests/channel-routes.test.ts b/server/tests/channel-routes.test.ts index baaf7cf..5812027 100644 --- a/server/tests/channel-routes.test.ts +++ b/server/tests/channel-routes.test.ts @@ -305,9 +305,8 @@ describe("channel route composition", () => { api: { getSession: async () => session }, }, { rolesForUser: async () => ["user"] }, - // Positions 4-12, ending at agentProfileStore. the computer gateway and policy store were added - // ahead of these, so the run of placeholders grew with them. - undefined, + // Positions 4-11, ending at agentProfileStore. The computer gateway and policy store come + // before these placeholders. undefined, undefined, undefined, diff --git a/server/tests/computer-client.test.ts b/server/tests/computer-client.test.ts index 462a7b5..aa2ae90 100644 --- a/server/tests/computer-client.test.ts +++ b/server/tests/computer-client.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; import { - createComputerClient, + createComputerTransport, ElementNotFoundError, NavigationRefusedError, } from "../src/computer/client"; @@ -9,12 +9,19 @@ function clientWith( handler: (url: string, init?: RequestInit) => Promise | Response, allowPrivateHosts = false, ) { - return createComputerClient({ - baseUrl: "http://agent-computer:4100", + const transport = createComputerTransport({ allowPrivateHosts, fetchImpl: ((url: string, init?: RequestInit) => Promise.resolve(handler(url, init))) as unknown as typeof fetch, }); + const baseUrl = "http://agent-computer:4100"; + const botId = "bot-1"; + return { + navigate: (url: string) => transport.navigate(baseUrl, botId, url), + screenshot: () => transport.call(baseUrl, botId, "/screenshot"), + click: (input: unknown, signal?: AbortSignal) => + transport.post(baseUrl, botId, "/click", input, signal), + }; } const ok = (body: unknown) => @@ -102,6 +109,15 @@ describe("computer client", () => { "The assistant's computer is not running.", ); + const timedOut = clientWith(() => { + const error = new Error("timed out"); + error.name = "TimeoutError"; + throw error; + }); + await expect(timedOut.navigate("https://example.com")).rejects.toThrow( + "The assistant's computer did not respond in time.", + ); + const badPage = clientWith( () => new Response(JSON.stringify({ error: "net::ERR_NAME_NOT_RESOLVED" }), { @@ -114,18 +130,6 @@ describe("computer client", () => { ); }); - test("status reports unreachable rather than throwing", async () => { - const client = clientWith(() => { - throw new Error("down"); - }); - - await expect(client.status("bot-1")).resolves.toEqual({ - botId: "bot-1", - state: "unreachable", - reason: "The assistant's computer is not running.", - }); - }); - test("screenshot returns the png a transcript can render", async () => { const client = clientWith(() => ok({ @@ -141,19 +145,6 @@ describe("computer client", () => { width: 1280, }); }); - - test("surfaces a timeout as the computer not responding", async () => { - const client = clientWith(() => { - const error = new Error("timed out"); - error.name = "TimeoutError"; - throw error; - }); - - await expect(client.status("bot-1")).resolves.toMatchObject({ - state: "unreachable", - reason: "The assistant's computer did not respond in time.", - }); - }); }); /** diff --git a/server/tests/computer-daytona-fixture.ts b/server/tests/computer-daytona-fixture.ts new file mode 100644 index 0000000..34fe4d9 --- /dev/null +++ b/server/tests/computer-daytona-fixture.ts @@ -0,0 +1,258 @@ +import { createHash } from "node:crypto"; +import { SandboxState } from "@daytona/sdk"; +import { + createDaytonaComputerProvider, + type DaytonaProviderOptions, + type DaytonaSdk, + type SandboxHandle, +} from "../src/computer/daytona"; + +export class DaytonaNotFoundError extends Error { + constructor(message: string) { + super(message); + this.name = "DaytonaNotFoundError"; + } +} + +export type DeleteCall = { + timeout?: number; + wait?: boolean; +}; + +export type FakeSandbox = { + id: string; + state: SandboxState; + labels: Record; + envVars: Record; + public: boolean; + autoStopInterval: number; + createdAt: string; + previewUrl: string; + startCalls: number; + stopCalls: number; + deleteCalls: number; + deleteArgs: DeleteCall[]; + refreshActivityCalls: number; + deleteHandler?: (timeout?: number, wait?: boolean) => void | Promise; + refreshActivityHandler?: () => void | Promise; +}; + +export type CreateParams = { + snapshot: string; + envVars: Record; + labels: Record; + public: boolean; + autoStopInterval: number; + options?: { timeout?: number }; +}; + +export function makeSandbox(options: { + id: string; + botId?: string; + state?: SandboxState | string; + labels?: Record; + envVars?: Record; + public?: boolean; + autoStopInterval?: number; + createdAt?: string; + previewUrl?: string; + deleteHandler?: (timeout?: number, wait?: boolean) => void | Promise; + refreshActivityHandler?: () => void | Promise; +}): FakeSandbox { + const botId = options.botId; + const token = options.envVars?.COMPUTER_TOKEN ?? "tok"; + const tokenHash = createHash("sha256").update(token).digest("hex"); + const defaultLabels = botId + ? { + "openbot/computer": "true", + "openbot/bot-id": botId, + "openbot/computer-token-hash": tokenHash, + } + : { "openbot/computer": "true" }; + const labels = options.labels ?? defaultLabels; + const envVars = + options.envVars ?? + (botId ? { COMPUTER_TOKEN: token, COMPUTER_BOT_ID: botId } : {}); + + return { + id: options.id, + state: (options.state as SandboxState) ?? SandboxState.STARTED, + labels, + envVars, + public: options.public ?? true, + autoStopInterval: options.autoStopInterval ?? 15, + createdAt: options.createdAt ?? "2026-08-20T10:00:00Z", + previewUrl: + options.previewUrl ?? `https://${options.id}.preview.daytona.app`, + startCalls: 0, + stopCalls: 0, + deleteCalls: 0, + deleteArgs: [], + refreshActivityCalls: 0, + ...(options.deleteHandler ? { deleteHandler: options.deleteHandler } : {}), + ...(options.refreshActivityHandler + ? { refreshActivityHandler: options.refreshActivityHandler } + : {}), + }; +} + +export function makeSandboxHandle(sb: FakeSandbox): SandboxHandle { + const handle: SandboxHandle = { + id: sb.id, + get state() { + return sb.state; + }, + set state(val: SandboxState | undefined) { + sb.state = val ?? SandboxState.UNKNOWN; + }, + labels: sb.labels, + createdAt: sb.createdAt, + start: async () => { + sb.startCalls++; + sb.state = SandboxState.STARTED; + }, + stop: async () => { + sb.stopCalls++; + sb.state = SandboxState.STOPPED; + }, + delete: async (timeout?: number, wait?: boolean) => { + sb.deleteCalls++; + sb.deleteArgs.push({ timeout, wait }); + if (sb.deleteHandler) { + await sb.deleteHandler(timeout, wait); + } else { + sb.state = SandboxState.DESTROYED; + } + }, + refreshActivity: async () => { + sb.refreshActivityCalls++; + if (sb.refreshActivityHandler) { + await sb.refreshActivityHandler(); + } + }, + getPreviewLink: async (_port: number) => ({ url: sb.previewUrl }), + }; + return handle; +} + +export type FakeSdk = DaytonaSdk & { + sandboxes: Map; + snapshots: Map; + creates: CreateParams[]; +}; + +export function createFakeSdk(initialSandboxes: FakeSandbox[] = []): FakeSdk { + let idCounter = 1; + const sandboxes = new Map(); + const snapshots = new Map(); + const creates: CreateParams[] = []; + + for (const sb of initialSandboxes) { + sandboxes.set(sb.id, sb); + } + + const sdk: FakeSdk = { + sandboxes, + snapshots, + creates, + create: async (params, options) => { + creates.push({ ...params, options }); + const id = `sb-${idCounter++}`; + const sb: FakeSandbox = { + id, + state: SandboxState.STARTED, + labels: params.labels, + envVars: params.envVars, + public: params.public, + autoStopInterval: params.autoStopInterval, + createdAt: new Date().toISOString(), + previewUrl: `https://${id}.preview.daytona.app`, + startCalls: 0, + stopCalls: 0, + deleteCalls: 0, + deleteArgs: [], + refreshActivityCalls: 0, + }; + sandboxes.set(id, sb); + return makeSandboxHandle(sb); + }, + get: async (id: string) => { + const sb = sandboxes.get(id); + if (!sb) { + throw new DaytonaNotFoundError(`Sandbox ${id} not found`); + } + return makeSandboxHandle(sb); + }, + list: (query?: { labels?: Record }) => { + async function* generator() { + for (const sb of sandboxes.values()) { + if (query?.labels) { + const matches = Object.entries(query.labels).every( + ([k, v]) => sb.labels[k] === v, + ); + if (!matches) continue; + } + yield makeSandboxHandle(sb); + } + } + return generator(); + }, + snapshot: { + get: async (name: string) => { + const snap = snapshots.get(name); + if (!snap) { + throw new DaytonaNotFoundError(`Snapshot ${name} not found`); + } + return snap; + }, + create: async (params, _options) => { + snapshots.set(params.name, { state: "active" }); + return { name: params.name }; + }, + }, + }; + + return sdk; +} + +export function fakeFetch( + handler?: (url: string, init?: RequestInit) => Response | Promise, +): typeof fetch { + return (async (input: string | URL | Request, init?: RequestInit) => { + const url = + typeof input === "string" + ? input + : input instanceof URL + ? input.toString() + : input.url; + if (handler) return handler(url, init); + if (url.endsWith("/health")) { + return new Response(JSON.stringify({ status: "ok" }), { status: 200 }); + } + return new Response("Not Found", { status: 404 }); + }) as unknown as typeof fetch; +} + +export type ClientOverrides = Partial & { + snapshotTimeoutMs?: number; +}; + +export function makeClient( + sdk: FakeSdk = createFakeSdk(), + overrides: ClientOverrides = {}, +) { + const defaultSnapshot = + !overrides.agentComputerDir && overrides.snapshot === undefined + ? { snapshot: "prebuilt" } + : {}; + return createDaytonaComputerProvider({ + apiKey: "test-api-key", + computerToken: "tok", + pollIntervalMs: 1, + healthTimeoutMs: 200, + sdk, + fetchImpl: fakeFetch(), + ...defaultSnapshot, + ...overrides, + } as DaytonaProviderOptions); +} diff --git a/server/tests/computer-daytona-lifecycle.test.ts b/server/tests/computer-daytona-lifecycle.test.ts new file mode 100644 index 0000000..712c9c7 --- /dev/null +++ b/server/tests/computer-daytona-lifecycle.test.ts @@ -0,0 +1,561 @@ +import { describe, expect, test } from "bun:test"; +import { createHash } from "node:crypto"; +import { ProviderError } from "../src/computer/provider"; +import { + createFakeSdk, + fakeFetch, + makeClient, + makeSandbox, +} from "./computer-daytona-fixture"; + +describe("Daytona computer lifecycle supervisor", () => { + test("create carries snapshot, ownership labels including token hash, public:true, autoStopInterval:15, COMPUTER_TOKEN and COMPUTER_BOT_ID then returns preview URL after healthy /health", async () => { + const sdk = createFakeSdk(); + let healthCheckedUrl: string | undefined; + let healthHeaders: HeadersInit | undefined; + + const fetchImpl = fakeFetch((url, init) => { + if (url.endsWith("/health")) { + healthCheckedUrl = url; + healthHeaders = init?.headers; + return new Response("ok", { status: 200 }); + } + return new Response("not found", { status: 404 }); + }); + + const token = "secret-token-123"; + const expectedHash = createHash("sha256").update(token).digest("hex"); + + const client = makeClient(sdk, { + computerToken: token, + fetchImpl, + }); + + const url = await client.locate("sales"); + + expect(sdk.creates).toHaveLength(1); + const createParams = sdk.creates[0]; + expect(createParams.snapshot).toBe("prebuilt"); + expect(createParams.public).toBe(true); + expect(createParams.autoStopInterval).toBe(15); + expect(createParams.labels).toEqual({ + "openbot/computer": "true", + "openbot/bot-id": "sales", + "openbot/computer-token-hash": expectedHash, + }); + expect(createParams.envVars.COMPUTER_TOKEN).toBe(token); + expect(createParams.envVars.COMPUTER_BOT_ID).toBe("sales"); + + expect(url).toBe("https://sb-1.preview.daytona.app"); + expect(healthCheckedUrl).toBe("https://sb-1.preview.daytona.app/health"); + expect( + new Headers(healthHeaders).get("X-Daytona-Skip-Preview-Warning"), + ).toBe("true"); + }); + + test("stopped labeled sandbox is reused and started", async () => { + const existing = makeSandbox({ + id: "sb-stopped-1", + botId: "support", + state: "stopped", + }); + + const sdk = createFakeSdk([existing]); + const client = makeClient(sdk); + + const url = await client.locate("support"); + + expect(url).toBe("https://sb-stopped-1.preview.daytona.app"); + expect(sdk.creates).toHaveLength(0); + expect(existing.startCalls).toBe(1); + expect(existing.state).toBe("started"); + }); + + test("locate on a reusable started sandbox calls refreshActivity exactly once before returning", async () => { + const token = "tok"; + const tokenHash = createHash("sha256").update(token).digest("hex"); + const startedSandbox = makeSandbox({ + id: "sb-started-activity", + botId: "activity-bot", + state: "started", + labels: { + "openbot/computer": "true", + "openbot/bot-id": "activity-bot", + "openbot/computer-token-hash": tokenHash, + }, + }); + + const sdk = createFakeSdk([startedSandbox]); + const client = makeClient(sdk, { computerToken: token }); + + const url = await client.locate("activity-bot"); + + expect(url).toBe("https://sb-started-activity.preview.daytona.app"); + expect(startedSandbox.refreshActivityCalls).toBe(1); + expect(startedSandbox.startCalls).toBe(0); + expect(sdk.creates).toHaveLength(0); + }); + + test("existing sandbox with stale openbot/computer-token-hash is deleted and replaced with correct fingerprint label", async () => { + const currentToken = "rotated-secret-456"; + const expectedHash = createHash("sha256") + .update(currentToken) + .digest("hex"); + + const staleSandbox = makeSandbox({ + id: "sb-stale-token", + botId: "rotate-bot", + labels: { + "openbot/computer": "true", + "openbot/bot-id": "rotate-bot", + "openbot/computer-token-hash": "outdated-sha256-hash", + }, + }); + + const sdk = createFakeSdk([staleSandbox]); + const client = makeClient(sdk, { + computerToken: currentToken, + }); + + const url = await client.locate("rotate-bot"); + + expect(staleSandbox.deleteCalls).toBe(1); + expect(sdk.creates).toHaveLength(1); + const createParams = sdk.creates[0]; + expect(createParams.labels).toEqual({ + "openbot/computer": "true", + "openbot/bot-id": "rotate-bot", + "openbot/computer-token-hash": expectedHash, + }); + expect(createParams.envVars.COMPUTER_TOKEN).toBe(currentToken); + expect(url).toBe("https://sb-1.preview.daytona.app"); + }); + + test("existing sandbox with missing openbot/computer-token-hash label is deleted and replaced", async () => { + const currentToken = "active-token"; + const expectedHash = createHash("sha256") + .update(currentToken) + .digest("hex"); + + const legacySandbox = makeSandbox({ + id: "sb-legacy-token", + botId: "legacy-bot", + labels: { + "openbot/computer": "true", + "openbot/bot-id": "legacy-bot", + }, + }); + + const sdk = createFakeSdk([legacySandbox]); + const client = makeClient(sdk, { + computerToken: currentToken, + }); + + const url = await client.locate("legacy-bot"); + + expect(legacySandbox.deleteCalls).toBe(1); + expect(sdk.creates).toHaveLength(1); + expect(sdk.creates[0].labels["openbot/computer-token-hash"]).toBe( + expectedHash, + ); + expect(url).toBe("https://sb-1.preview.daytona.app"); + }); + + test("concurrent locate calls create once", async () => { + const sdk = createFakeSdk(); + let createsCount = 0; + const { promise: gatePromise, resolve: openGate } = + Promise.withResolvers(); + const origCreate = sdk.create; + sdk.create = async (params, options) => { + createsCount++; + await gatePromise; + return origCreate(params, options); + }; + + const client = makeClient(sdk); + + const firstLocate = client.locate("marketing"); + const secondLocate = client.locate("marketing"); + openGate(); + + const [url1, url2] = await Promise.all([firstLocate, secondLocate]); + + expect(url1).toBe(url2); + expect(createsCount).toBe(1); + }); + + test("concurrent resets for the same Bot serialize, delete at most once, and both resolve", async () => { + let deleteCalls = 0; + const { promise: deleteGate, resolve: releaseDelete } = + Promise.withResolvers(); + + const existing = makeSandbox({ + id: "sb-concurrent-reset", + botId: "concurrent-bot", + deleteHandler: async () => { + deleteCalls++; + if (deleteCalls > 1) { + throw new Error("Daytona conflict: sandbox is already being deleted"); + } + await deleteGate; + existing.state = "destroyed"; + }, + }); + + const sdk = createFakeSdk([existing]); + const client = makeClient(sdk); + + const firstReset = client.reset("concurrent-bot"); + const secondReset = client.reset("concurrent-bot"); + + releaseDelete(); + const [res1, res2] = await Promise.all([firstReset, secondReset]); + + expect(existing.deleteCalls).toBe(1); + expect(res1).toEqual({ cleared: true }); + expect(res2).toEqual({ cleared: false }); + const list = await client.list(); + expect(list.find((b) => b.botId === "concurrent-bot")).toBeUndefined(); + expect(list).toHaveLength(0); + }); + + test("locate started during reset waits for reset completion, never health-polls the old sandbox, and returns a new sandbox", async () => { + const fetchedUrls: string[] = []; + const { promise: resetBlocker, resolve: unblockReset } = + Promise.withResolvers(); + + const oldSandbox = makeSandbox({ + id: "sb-old", + botId: "race-bot", + previewUrl: "https://sb-old.preview.daytona.app", + deleteHandler: async () => { + await resetBlocker; + oldSandbox.state = "destroyed"; + }, + }); + + const sdk = createFakeSdk([oldSandbox]); + const fetchImpl = fakeFetch((url) => { + fetchedUrls.push(url); + if (url === "https://sb-old.preview.daytona.app/health") { + throw new Error("poll attempted against deleted sandbox preview URL"); + } + if (url === "https://sb-1.preview.daytona.app/health") { + return new Response(JSON.stringify({ status: "ok" }), { status: 200 }); + } + return new Response("not found", { status: 404 }); + }); + + const client = makeClient(sdk, { fetchImpl }); + + const resetPromise = client.reset("race-bot"); + const locatePromise = client.locate("race-bot"); + + unblockReset(); + const [resetResult, newUrl] = await Promise.all([ + resetPromise, + locatePromise, + ]); + + expect(resetResult).toEqual({ cleared: true }); + expect(oldSandbox.deleteCalls).toBe(1); + expect(newUrl).toBe("https://sb-1.preview.daytona.app"); + expect(sdk.creates).toHaveLength(1); + expect(fetchedUrls).toEqual(["https://sb-1.preview.daytona.app/health"]); + expect(fetchedUrls).not.toContain( + "https://sb-old.preview.daytona.app/health", + ); + }); + + test("reset deletes and the next locate creates fresh", async () => { + const sdk = createFakeSdk(); + const client = makeClient(sdk); + + const firstUrl = await client.locate("finance"); + expect(sdk.creates).toHaveLength(1); + const firstSandbox = sdk.sandboxes.get("sb-1"); + expect(firstSandbox).toBeDefined(); + if (!firstSandbox) { + throw new Error("firstSandbox must be defined"); + } + + const resetResult = await client.reset("finance"); + expect(resetResult).toEqual({ cleared: true }); + expect(firstSandbox.deleteCalls).toBe(1); + + const secondUrl = await client.locate("finance"); + expect(sdk.creates).toHaveLength(2); + expect(secondUrl).not.toBe(firstUrl); + }); + + test("reset with no existing sandbox returns cleared false", async () => { + const sdk = createFakeSdk(); + const client = makeClient(sdk); + + const result = await client.reset("nonexistent"); + expect(result).toEqual({ cleared: false }); + }); + + test("stop on a started sandbox calls sandbox.stop and returns wasRunning true", async () => { + const runningSandbox = makeSandbox({ + id: "sb-running", + botId: "running-bot", + state: "started", + }); + + const sdk = createFakeSdk([runningSandbox]); + const client = makeClient(sdk); + + const result = await client.stop("running-bot"); + + expect(result).toEqual({ wasRunning: true }); + expect(runningSandbox.stopCalls).toBe(1); + }); + + test("stop on an already stopped sandbox resolves with wasRunning false without calling sandbox.stop", async () => { + const stoppedSandbox = makeSandbox({ + id: "sb-already-stopped", + botId: "already-stopped-bot", + state: "stopped", + }); + + const sdk = createFakeSdk([stoppedSandbox]); + const client = makeClient(sdk); + + const result = await client.stop("already-stopped-bot"); + + expect(result).toEqual({ wasRunning: false }); + expect(stoppedSandbox.stopCalls).toBe(0); + }); + + test("stop with no sandbox returns wasRunning false", async () => { + const sdk = createFakeSdk(); + const client = makeClient(sdk); + + await expect(client.stop("nonexistent")).resolves.toEqual({ + wasRunning: false, + }); + expect(sdk.creates).toHaveLength(0); + }); + + test("list maps openbot/bot-id and skips destroyed", async () => { + const activeBot = makeSandbox({ + id: "sb-active", + botId: "bot-active", + state: "started", + createdAt: "2026-08-20T10:00:00Z", + }); + + const destroyedBot = makeSandbox({ + id: "sb-destroyed", + botId: "bot-destroyed", + state: "destroyed", + createdAt: "2026-08-20T09:00:00Z", + }); + + const stoppedBot = makeSandbox({ + id: "sb-stopped", + botId: "bot-stopped", + state: "stopped", + createdAt: "2026-08-20T11:00:00Z", + }); + + const unrelatedSandbox = makeSandbox({ + id: "sb-unrelated", + labels: { + "some-other-label": "true", + }, + createdAt: "2026-08-20T08:00:00Z", + }); + + const sdk = createFakeSdk([ + activeBot, + destroyedBot, + stoppedBot, + unrelatedSandbox, + ]); + const client = makeClient(sdk); + + const list = await client.list(); + + expect(list).toHaveLength(2); + expect(list).toEqual( + expect.arrayContaining([ + { + botId: "bot-active", + status: "running", + url: "https://sb-active.preview.daytona.app", + startedAt: "2026-08-20T10:00:00Z", + }, + { + botId: "bot-stopped", + status: "stopped", + url: "https://sb-stopped.preview.daytona.app", + startedAt: "2026-08-20T11:00:00Z", + }, + ]), + ); + }); + + test("reset waits for sandbox deletion so list does not return the reset bot", async () => { + const existing = makeSandbox({ + id: "sb-reset-wait-1", + botId: "reset-wait-bot", + deleteHandler: (_timeout, wait) => { + if (wait) { + existing.state = "destroyed"; + } + }, + }); + + const sdk = createFakeSdk([existing]); + const client = makeClient(sdk); + + const result = await client.reset("reset-wait-bot"); + expect(result).toEqual({ cleared: true }); + + expect(existing.deleteCalls).toBe(1); + expect(existing.deleteArgs).toEqual([{ timeout: 60, wait: true }]); + const remaining = await client.list(); + expect( + remaining.find((bot) => bot.botId === "reset-wait-bot"), + ).toBeUndefined(); + }); + + test("reset waits for Daytona list convergence across eventual consistency stale started responses", async () => { + const existing = makeSandbox({ + id: "sb-reset-convergence-1", + botId: "reset-convergence-bot", + }); + + const sdk = createFakeSdk([existing]); + let postDeleteListCalls = 0; + const origList = sdk.list; + + sdk.list = (query?: { labels?: Record }) => { + if (existing.deleteCalls > 0) { + postDeleteListCalls++; + if (postDeleteListCalls === 1) { + async function* staleGenerator() { + yield { + id: existing.id, + state: "started", + labels: existing.labels, + createdAt: existing.createdAt, + start: async () => {}, + stop: async () => {}, + delete: async () => {}, + refreshActivity: async () => {}, + getPreviewLink: async () => ({ url: existing.previewUrl }), + }; + } + return staleGenerator(); + } + async function* emptyGenerator() {} + return emptyGenerator(); + } + return origList(query); + }; + + const client = makeClient(sdk); + + const result = await client.reset("reset-convergence-bot"); + expect(result).toEqual({ cleared: true }); + + const immediate = await client.list(); + expect( + immediate.find((bot) => bot.botId === "reset-convergence-bot"), + ).toBeUndefined(); + + const later = await client.list(); + expect( + later.find((bot) => bot.botId === "reset-convergence-bot"), + ).toBeUndefined(); + }); + + test("failed reset deletion does not tombstone sandbox so retry deletes it and removes it from list", async () => { + let deleteAttempts = 0; + const existing = makeSandbox({ + id: "sb-failed-delete-1", + botId: "retry-delete-bot", + deleteHandler: (_timeout, _wait) => { + deleteAttempts++; + if (deleteAttempts === 1) { + throw new Error("transient deletion error"); + } + existing.state = "destroyed"; + }, + }); + + const sdk = createFakeSdk([existing]); + const client = makeClient(sdk); + + let resetError: unknown; + try { + await client.reset("retry-delete-bot"); + } catch (err) { + resetError = err; + } + + expect(resetError).toBeInstanceOf(ProviderError); + expect((resetError as Error)?.message).toContain("retry-delete-bot"); + expect(existing.deleteCalls).toBe(1); + + const retryResult = await client.reset("retry-delete-bot"); + expect(retryResult).toEqual({ cleared: true }); + + expect(existing.deleteCalls).toBe(2); + const remaining = await client.list(); + expect( + remaining.find((bot) => bot.botId === "retry-delete-bot"), + ).toBeUndefined(); + }); + + test("locate polls stopping sandbox until stopped, calls start(300), and returns preview URL after healthy /health", async () => { + const stoppingSandbox = makeSandbox({ + id: "sb-stopping-1", + botId: "stopping-bot", + state: "stopping", + }); + + const sdk = createFakeSdk([stoppingSandbox]); + let getCalls = 0; + const origGet = sdk.get; + sdk.get = async (id: string) => { + if (id === stoppingSandbox.id && ++getCalls === 1) { + stoppingSandbox.state = "stopped"; + } + return origGet(id); + }; + + const client = makeClient(sdk); + + const url = await client.locate("stopping-bot"); + + expect(url).toBe("https://sb-stopping-1.preview.daytona.app"); + expect(stoppingSandbox.startCalls).toBe(1); + expect(stoppingSandbox.state).toBe("started"); + }); + + test("locate creates a fresh sandbox when a cached sandbox is destroying or not found", async () => { + const sdk = createFakeSdk(); + const client = makeClient(sdk); + + const firstUrl = await client.locate("destroying-bot"); + expect(sdk.creates).toHaveLength(1); + expect(firstUrl).toBe("https://sb-1.preview.daytona.app"); + + const firstSandbox = sdk.sandboxes.get("sb-1"); + expect(firstSandbox).toBeDefined(); + if (!firstSandbox) { + throw new Error("firstSandbox must be defined"); + } + firstSandbox.state = "destroying"; + + const secondUrl = await client.locate("destroying-bot"); + expect(sdk.creates).toHaveLength(2); + expect(secondUrl).toBe("https://sb-2.preview.daytona.app"); + expect(secondUrl).not.toBe(firstUrl); + }); +}); diff --git a/server/tests/computer-daytona.test.ts b/server/tests/computer-daytona.test.ts new file mode 100644 index 0000000..6a032aa --- /dev/null +++ b/server/tests/computer-daytona.test.ts @@ -0,0 +1,458 @@ +import { afterEach, describe, expect, test } from "bun:test"; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { SandboxState } from "@daytona/sdk"; +import { createDaytonaComputerProvider } from "../src/computer/daytona"; +import { + describeComputerIsolation, + ProviderError, +} from "../src/computer/provider"; +import type { ComputerStatus } from "../src/computer/schema"; +import { + createFakeSdk, + fakeFetch, + makeClient, + makeSandbox, +} from "./computer-daytona-fixture"; + +describe("Daytona computer supervisor", () => { + const tempDirs: string[] = []; + + afterEach(() => { + for (const dir of tempDirs) { + try { + rmSync(dir, { recursive: true, force: true }); + } catch { + // cleanup best-effort + } + } + tempDirs.length = 0; + }); + + test("exposes the Daytona per-Bot provider contract", () => { + const provider = createDaytonaComputerProvider({ + apiKey: "test-api-key", + computerToken: "tok", + snapshot: "prebuilt", + sdk: createFakeSdk(), + fetchImpl: fakeFetch(), + }); + + expect(provider.name).toBe("Daytona"); + expect(provider.isolation).toBe("per-bot"); + expect(describeComputerIsolation(provider).isolation).toBe( + "one computer per Bot", + ); + }); + + test("status maps Daytona lifecycle states without starting or creating a sandbox", async () => { + const cases: Array<{ + botId: string; + state: SandboxState; + expected: ComputerStatus; + }> = [ + { + botId: "bot-started", + state: SandboxState.STARTED, + expected: { botId: "bot-started", state: "ready" }, + }, + { + botId: "bot-starting", + state: SandboxState.STARTING, + expected: { botId: "bot-starting", state: "starting" }, + }, + { + botId: "bot-creating", + state: SandboxState.CREATING, + expected: { botId: "bot-creating", state: "starting" }, + }, + { + botId: "bot-restoring", + state: SandboxState.RESTORING, + expected: { botId: "bot-restoring", state: "starting" }, + }, + { + botId: "bot-pulling", + state: SandboxState.PULLING_SNAPSHOT, + expected: { botId: "bot-pulling", state: "starting" }, + }, + { + botId: "bot-resuming", + state: SandboxState.RESUMING, + expected: { botId: "bot-resuming", state: "starting" }, + }, + { + botId: "bot-pending-build", + state: SandboxState.PENDING_BUILD, + expected: { botId: "bot-pending-build", state: "starting" }, + }, + { + botId: "bot-building-snapshot", + state: SandboxState.BUILDING_SNAPSHOT, + expected: { botId: "bot-building-snapshot", state: "starting" }, + }, + { + botId: "bot-resizing", + state: SandboxState.RESIZING, + expected: { botId: "bot-resizing", state: "starting" }, + }, + { + botId: "bot-snapshotting", + state: SandboxState.SNAPSHOTTING, + expected: { botId: "bot-snapshotting", state: "starting" }, + }, + { + botId: "bot-forking", + state: SandboxState.FORKING, + expected: { botId: "bot-forking", state: "starting" }, + }, + { + botId: "bot-stopped", + state: SandboxState.STOPPED, + expected: { botId: "bot-stopped", state: "absent" }, + }, + { + botId: "bot-paused", + state: SandboxState.PAUSED, + expected: { botId: "bot-paused", state: "absent" }, + }, + { + botId: "bot-archived", + state: SandboxState.ARCHIVED, + expected: { botId: "bot-archived", state: "absent" }, + }, + { + botId: "bot-destroyed", + state: SandboxState.DESTROYED, + expected: { botId: "bot-destroyed", state: "absent" }, + }, + { + botId: "bot-destroying", + state: SandboxState.DESTROYING, + expected: { botId: "bot-destroying", state: "absent" }, + }, + { + botId: "bot-archiving", + state: SandboxState.ARCHIVING, + expected: { botId: "bot-archiving", state: "absent" }, + }, + { + botId: "bot-pausing", + state: SandboxState.PAUSING, + expected: { botId: "bot-pausing", state: "absent" }, + }, + { + botId: "bot-stopping", + state: SandboxState.STOPPING, + expected: { botId: "bot-stopping", state: "absent" }, + }, + { + botId: "bot-error", + state: SandboxState.ERROR, + expected: { + botId: "bot-error", + state: "unreachable", + reason: 'Daytona reported the computer state "error".', + }, + }, + { + botId: "bot-build-failed", + state: SandboxState.BUILD_FAILED, + expected: { + botId: "bot-build-failed", + state: "unreachable", + reason: 'Daytona reported the computer state "build_failed".', + }, + }, + { + botId: "bot-unknown", + state: SandboxState.UNKNOWN, + expected: { + botId: "bot-unknown", + state: "unreachable", + reason: 'Daytona reported the computer state "unknown".', + }, + }, + { + botId: "bot-unknown-default-open-api", + state: SandboxState.UNKNOWN_DEFAULT_OPEN_API, + expected: { + botId: "bot-unknown-default-open-api", + state: "unreachable", + reason: 'Daytona reported the computer state "11184809".', + }, + }, + ]; + + const sandboxes = cases.map((c, index) => + makeSandbox({ id: `status-${index}`, botId: c.botId, state: c.state }), + ); + const sdk = createFakeSdk(sandboxes); + const provider = makeClient(sdk); + + for (const c of cases) { + const status = await provider.status(c.botId); + expect(status).toEqual(c.expected); + } + expect(sdk.creates).toHaveLength(0); + expect(sandboxes.every((sandbox) => sandbox.startCalls === 0)).toBe(true); + }); + + test("status reports an unknown Bot as absent without preparing the snapshot", async () => { + const sdk = createFakeSdk(); + sdk.snapshot.get = async () => { + throw new Error("status must not inspect the snapshot"); + }; + const provider = createDaytonaComputerProvider({ + apiKey: "test-api-key", + computerToken: "tok", + sdk, + fetchImpl: fakeFetch(), + }); + + await expect(provider.status("missing-bot")).resolves.toEqual({ + botId: "missing-bot", + state: "absent", + }); + expect(sdk.creates).toHaveLength(0); + }); + + test("SDK failure throws ProviderError naming the Bot", async () => { + const sdk = createFakeSdk(); + sdk.create = async () => { + throw new Error("quota exceeded"); + }; + + const client = makeClient(sdk); + + await expect(client.locate("analytics")).rejects.toThrow(ProviderError); + await expect(client.locate("analytics")).rejects.toThrow(/analytics/); + }); + + test("health timeout throws ProviderError", async () => { + const sdk = createFakeSdk(); + const failingFetch = fakeFetch( + () => new Response("service unavailable", { status: 503 }), + ); + + const client = makeClient(sdk, { + healthTimeoutMs: 50, + fetchImpl: failingFetch, + }); + + await expect(client.locate("unhealthy-bot")).rejects.toThrow(ProviderError); + await expect(client.locate("unhealthy-bot")).rejects.toThrow( + /The computer for unhealthy-bot started but never answered \/health/, + ); + }); + + test("health fetch that never resolves is bounded by healthTimeoutMs and locate rejects with ProviderError", async () => { + const sdk = createFakeSdk(); + const hangingFetch = fakeFetch(() => { + const { promise } = Promise.withResolvers(); + return promise; + }); + + const client = makeClient(sdk, { + healthTimeoutMs: 30, + fetchImpl: hangingFetch, + }); + + let locateError: unknown; + try { + await client.locate("hanging-health-bot"); + } catch (err) { + locateError = err; + } + + expect(locateError).toBeInstanceOf(ProviderError); + expect((locateError as Error)?.message).toContain( + "The computer for hanging-health-bot started but never answered /health at its preview URL.", + ); + }); + + test("snapshot.create that never resolves is bounded by snapshotTimeoutMs and locate rejects with ProviderError", async () => { + const fixtureDir = mkdtempSync( + join(tmpdir(), "openbot-agent-computer-test-"), + ); + tempDirs.push(fixtureDir); + + writeFileSync( + join(fixtureDir, "package.json"), + JSON.stringify({ name: "agent-computer", version: "1.0.0" }), + ); + mkdirSync(join(fixtureDir, "src"), { recursive: true }); + writeFileSync( + join(fixtureDir, "src", "index.ts"), + 'console.log("agent-computer");\n', + ); + + const sdk = createFakeSdk(); + sdk.snapshot.create = () => { + const { promise } = Promise.withResolvers(); + return promise; + }; + + const client = makeClient(sdk, { + agentComputerDir: fixtureDir, + snapshotTimeoutMs: 30, + }); + + let locateError: unknown; + try { + await client.locate("hanging-snapshot-bot"); + } catch (err) { + locateError = err; + } + + expect(locateError).toBeInstanceOf(ProviderError); + }); + + test("missing agentComputerDir package.json or src directory rejects locate with ProviderError naming the directory and advising DAYTONA_SNAPSHOT", async () => { + const missingPkgDir = mkdtempSync( + join(tmpdir(), "openbot-missing-pkg-test-"), + ); + tempDirs.push(missingPkgDir); + mkdirSync(join(missingPkgDir, "src"), { recursive: true }); + writeFileSync( + join(missingPkgDir, "src", "index.ts"), + 'console.log("agent-computer");\n', + ); + + const clientMissingPkg = makeClient(createFakeSdk(), { + agentComputerDir: missingPkgDir, + healthTimeoutMs: 50, + }); + + let pkgError: unknown; + try { + await clientMissingPkg.locate("missing-pkg-bot"); + } catch (err) { + pkgError = err; + } + + expect(pkgError).toBeInstanceOf(ProviderError); + expect((pkgError as Error)?.message).toContain(missingPkgDir); + expect((pkgError as Error)?.message).toContain("DAYTONA_SNAPSHOT"); + + const missingSrcDir = mkdtempSync( + join(tmpdir(), "openbot-missing-src-test-"), + ); + tempDirs.push(missingSrcDir); + writeFileSync( + join(missingSrcDir, "package.json"), + JSON.stringify({ name: "agent-computer", version: "1.0.0" }), + ); + + const clientMissingSrc = makeClient(createFakeSdk(), { + agentComputerDir: missingSrcDir, + healthTimeoutMs: 50, + }); + + let srcError: unknown; + try { + await clientMissingSrc.locate("missing-src-bot"); + } catch (err) { + srcError = err; + } + + expect(srcError).toBeInstanceOf(ProviderError); + expect((srcError as Error)?.message).toContain(missingSrcDir); + expect((srcError as Error)?.message).toContain("DAYTONA_SNAPSHOT"); + }); + + test("snapshot naming is stable for unchanged temp sources and changes when source contents change", async () => { + const fixtureDir = mkdtempSync( + join(tmpdir(), "openbot-agent-computer-test-"), + ); + tempDirs.push(fixtureDir); + + writeFileSync( + join(fixtureDir, "package.json"), + JSON.stringify({ name: "agent-computer", version: "1.0.0" }), + ); + mkdirSync(join(fixtureDir, "src"), { recursive: true }); + writeFileSync( + join(fixtureDir, "src", "index.ts"), + 'console.log("hello world");\n', + ); + + const sdk1 = createFakeSdk(); + const client1 = makeClient(sdk1, { agentComputerDir: fixtureDir }); + + await client1.locate("bot-1"); + expect(sdk1.creates).toHaveLength(1); + const snapshotName1 = sdk1.creates[0].snapshot; + expect(snapshotName1).toMatch(/^openbot-agent-computer-[a-f0-9]{12}$/); + + const sdk2 = createFakeSdk(); + const client2 = makeClient(sdk2, { agentComputerDir: fixtureDir }); + + await client2.locate("bot-2"); + expect(sdk2.creates).toHaveLength(1); + const snapshotName2 = sdk2.creates[0].snapshot; + expect(snapshotName2).toBe(snapshotName1); + + writeFileSync( + join(fixtureDir, "src", "index.ts"), + 'console.log("hello world updated");\n', + ); + + const sdk3 = createFakeSdk(); + const client3 = makeClient(sdk3, { agentComputerDir: fixtureDir }); + + await client3.locate("bot-3"); + expect(sdk3.creates).toHaveLength(1); + const snapshotName3 = sdk3.creates[0].snapshot; + expect(snapshotName3).toMatch(/^openbot-agent-computer-[a-f0-9]{12}$/); + expect(snapshotName3).not.toBe(snapshotName1); + }); + + test("snapshot image recipe configures PATH with bun and standard system binaries without literal variable substitution", async () => { + const fixtureDir = mkdtempSync( + join(tmpdir(), "openbot-agent-computer-test-"), + ); + tempDirs.push(fixtureDir); + + writeFileSync( + join(fixtureDir, "package.json"), + JSON.stringify({ name: "agent-computer", version: "1.0.0" }), + ); + mkdirSync(join(fixtureDir, "src"), { recursive: true }); + writeFileSync( + join(fixtureDir, "src", "index.ts"), + 'console.log("hello world");\n', + ); + + let capturedImage: { dockerfile: string } | undefined; + const sdk = createFakeSdk(); + const originalSnapshotCreate = sdk.snapshot.create; + sdk.snapshot.create = async (params, options) => { + capturedImage = params.image as { dockerfile: string }; + return originalSnapshotCreate(params, options); + }; + + const client = makeClient(sdk, { agentComputerDir: fixtureDir }); + + await client.locate("recipe-test-bot"); + + expect(capturedImage).toBeDefined(); + if (!capturedImage) { + throw new Error("capturedImage must be defined"); + } + const dockerfile = capturedImage.dockerfile; + + const envPathLine = dockerfile + .split("\n") + .find((line) => line.startsWith("ENV PATH=")); + + expect(envPathLine).toBeDefined(); + expect(envPathLine).toContain("/root/.bun/bin"); + expect(envPathLine).toContain("/usr/bin"); + expect(envPathLine).toContain("/bin"); + const dollarPath = "$" + "{PATH}"; + expect(envPathLine).not.toContain(dollarPath); + expect(dockerfile).not.toContain(dollarPath); + }); +}); diff --git a/server/tests/computer-gateway.test.ts b/server/tests/computer-gateway.test.ts index eff68c0..a3346bc 100644 --- a/server/tests/computer-gateway.test.ts +++ b/server/tests/computer-gateway.test.ts @@ -1,12 +1,16 @@ import { describe, expect, test } from "bun:test"; import type { AuditEventInput, AuditStore } from "../src/audit"; -import type { ComputerClient } from "../src/computer/client"; import { ActionRefusedError, createComputerGateway, + WorkspaceRefusedError, } from "../src/computer/gateway"; import type { ActionPolicy } from "../src/computer/policy"; import type { SnapshotResult } from "../src/computer/schema"; +import type { + ComputerLocation, + ComputerProvider, +} from "../src/computer/provider"; /** * What the gateway must guarantee, tested as properties rather than as call sequences. @@ -29,69 +33,136 @@ const SNAPSHOT: SnapshotResult = { ], }; -/** A client that records what reached it, so "did not reach the computer" is checkable. */ -function fakeClient() { +/** A computer that records which HTTP actions reached it. */ +function fakeComputer(options?: { + stopResult?: { wasRunning: boolean }; + resetResult?: { cleared: boolean }; + locations?: ComputerLocation[]; + routes?: Record Response | Promise>; +}) { const calls: string[] = []; - /** Which Bot the gateway addressed the computer as, per call. */ const addressedAs: string[] = []; + const requests: Array<{ url: string; init?: RequestInit }> = []; + const stopResult = options?.stopResult ?? { wasRunning: true }; + const resetResult = options?.resetResult ?? { cleared: true }; + const locations = options?.locations ?? []; const result = (action: string) => ({ action, url: SNAPSHOT.url, elapsedMs: 1, }); - const client = { - snapshot: async () => SNAPSHOT, - read: async () => ({ - url: SNAPSHOT.url, - title: "Order", - text: "", - truncated: false, - }), - click: async () => { - calls.push("click"); - return result("click") as never; - }, - type: async () => { - calls.push("type"); - return result("type") as never; - }, - key: async () => { - calls.push("key"); - return result("key") as never; - }, - scroll: async () => { - calls.push("scroll"); - return result("scroll") as never; - }, - readFile: async () => { - calls.push("readFile"); - return { - path: "notes.md", - text: "kept", - truncated: false, - bytes: 4, - } as never; - }, - writeFile: async () => { - calls.push("writeFile"); - return { path: "notes.md", bytes: 4, appended: false } as never; + const provider: ComputerProvider = { + name: "test", + isolation: "per-bot", + locate: async (botId) => { + addressedAs.push(botId); + return "http://agent-computer:4100"; }, - status: async () => ({ botId: "b", state: "ready" as const }), - navigate: async () => { - calls.push("navigate"); - return { url: "https://example.com/", title: "Example" } as never; + status: async (botId) => ({ botId, state: "ready" }), + stop: async (botId) => { + calls.push(`stop:${botId}`); + return stopResult; }, - screenshot: async () => ({}) as never, - /** - * The per-Bot view. Recorded rather than ignored, so a test can prove the gateway told the - * computer which Bot is asking, the thing every per-Bot behaviour on the far side keys off. - */ - forBot(botId: string) { - addressedAs.push(botId); - return client; + reset: async (botId) => { + calls.push(`reset:${botId}`); + return resetResult; }, - } as unknown as ComputerClient; - return { client, calls, addressedAs }; + list: async () => locations, + }; + const fetchImpl = (async (url: string, init?: RequestInit) => { + requests.push({ url, init }); + const path = new URL(url).pathname; + if (options?.routes && path in options.routes) { + return options.routes[path](init); + } + switch (path) { + case "/snapshot": + return Response.json(SNAPSHOT); + case "/read": + return Response.json({ + url: SNAPSHOT.url, + title: "Order", + text: "", + truncated: false, + }); + case "/screenshot": + calls.push("screenshot"); + return Response.json({ + image: "aGVsbG8=", + mimeType: "image/png", + }); + case "/files/read": + calls.push("readFile"); + return Response.json({ + path: "notes.md", + text: "kept", + truncated: false, + bytes: 4, + }); + case "/files/write": + calls.push("writeFile"); + return Response.json({ + path: "notes.md", + bytes: 4, + appended: false, + }); + case "/files/list": + calls.push("listFiles"); + return Response.json({ + path: "notes", + entries: [], + }); + case "/navigate": + calls.push("navigate"); + return Response.json({ + url: "https://example.com/", + title: "Example", + elapsedMs: 1, + }); + case "/click": + calls.push("click"); + return Response.json(result("click")); + case "/type": + calls.push("type"); + return Response.json(result("type")); + case "/key": + calls.push("key"); + return Response.json(result("key")); + case "/scroll": + calls.push("scroll"); + return Response.json(result("scroll")); + case "/control": + calls.push("control"); + return Response.json({ mode: "bot" }); + case "/control/request": + calls.push("requestHelp"); + return Response.json({ mode: "human", reason: "Sign in" }); + case "/control/take": + calls.push("takeControl"); + return Response.json({ mode: "human" }); + case "/control/release": + calls.push("releaseControl"); + return Response.json({ mode: "bot" }); + case "/control/secret": + calls.push("requestSecret"); + return Response.json({ mode: "secret", ref: "e1" }); + case "/human/secret": + calls.push("supplySecret"); + return Response.json({ supplied: true }); + case "/human/click": + case "/human/move": + case "/human/button": + case "/human/wheel": + calls.push(path.slice(1)); + return Response.json({ ok: true }); + default: + return Response.json( + { error: `Unknown endpoint: ${path}` }, + { status: 404 }, + ); + } + }) as unknown as typeof fetch; + return { provider, fetchImpl, calls, addressedAs, requests }; } function fakeAudit() { @@ -103,23 +174,34 @@ function fakeAudit() { const ACTOR = { id: "dev-local-user" }; const PERMISSIVE: ActionPolicy = { mode: "enforce", deny: [], allow: ["true"] }; -async function gatewayWith(policy: ActionPolicy | undefined) { - const { client, calls } = fakeClient(); +async function gatewayWith( + policy: ActionPolicy | undefined, + options?: { + stopResult?: { wasRunning: boolean }; + resetResult?: { cleared: boolean }; + locations?: ComputerLocation[]; + token?: string; + }, +) { + const { provider, fetchImpl, calls, addressedAs, requests } = + fakeComputer(options); const { store, rows } = fakeAudit(); const gateway = createComputerGateway({ - client, + provider, + fetchImpl, auditStore: store, policy: () => policy, + token: options?.token, }); // Every test acts on refs, so the server must hold a snapshot first, exactly as the real flow does. - await gateway.snapshot("default"); - return { gateway, calls, rows }; + await gateway.snapshot("bot-1"); + return { gateway, calls, rows, addressedAs, requests, provider }; } describe("the computer gateway", () => { test("carries out an allowed action and records it", async () => { const { gateway, calls, rows } = await gatewayWith(PERMISSIVE); - await gateway.click("default", "bot-1", ACTOR, { + await gateway.click("bot-1", ACTOR, { ref: "e9", snapshotId: 7, }); @@ -127,6 +209,7 @@ describe("the computer gateway", () => { expect(calls).toEqual(["click"]); expect(rows).toHaveLength(1); expect(rows[0]?.eventType).toBe("computer.action_allowed"); + expect(rows[0]?.targetId).toBe("bot-1"); }); test("a refused action never reaches the computer", async () => { @@ -136,13 +219,14 @@ describe("the computer gateway", () => { }); await expect( - gateway.click("default", "bot-1", ACTOR, { ref: "e9", snapshotId: 7 }), + gateway.click("bot-1", ACTOR, { ref: "e9", snapshotId: 7 }), ).rejects.toThrow(ActionRefusedError); // The decision happens before the effect. expect(calls).toEqual([]); expect(rows).toHaveLength(1); expect(rows[0]?.eventType).toBe("computer.action_refused"); + expect(rows[0]?.targetId).toBe("bot-1"); }); test("the refusal names the rule, so an operator can find it", async () => { @@ -152,7 +236,7 @@ describe("the computer gateway", () => { }); const error = await gateway - .click("default", "bot-1", ACTOR, { ref: "e9", snapshotId: 7 }) + .click("bot-1", ACTOR, { ref: "e9", snapshotId: 7 }) .catch((caught: unknown) => caught); expect(error).toBeInstanceOf(ActionRefusedError); @@ -170,7 +254,7 @@ describe("the computer gateway", () => { }); await expect( - gateway.click("default", "bot-1", ACTOR, { + gateway.click("bot-1", ACTOR, { ref: "e9", snapshotId: 7, // Not part of the input contract, and must not influence anything even when supplied. @@ -182,7 +266,7 @@ describe("the computer gateway", () => { test("an allowed action reports the element's label for the transcript", async () => { const { gateway } = await gatewayWith(PERMISSIVE); - const result = await gateway.type("default", "bot-1", ACTOR, { + const result = await gateway.type("bot-1", ACTOR, { ref: "e1", snapshotId: 7, text: "Grace Hopper", @@ -192,7 +276,7 @@ describe("the computer gateway", () => { test("the typed text never enters the audit payload", async () => { const { gateway, rows } = await gatewayWith(PERMISSIVE); - await gateway.type("default", "bot-1", ACTOR, { + await gateway.type("bot-1", ACTOR, { ref: "e1", snapshotId: 7, text: "hunter2-not-a-real-password", @@ -213,7 +297,7 @@ describe("the computer gateway", () => { test("an absent policy refuses every action", async () => { const { gateway, calls, rows } = await gatewayWith(undefined); await expect( - gateway.click("default", "bot-1", ACTOR, { ref: "e9", snapshotId: 7 }), + gateway.click("bot-1", ACTOR, { ref: "e9", snapshotId: 7 }), ).rejects.toThrow(ActionRefusedError); expect(calls).toEqual([]); expect(rows[0]?.eventType).toBe("computer.action_refused"); @@ -226,7 +310,7 @@ describe("the computer gateway", () => { allow: ["true"], }); - await gateway.click("default", "bot-1", ACTOR, { + await gateway.click("bot-1", ACTOR, { ref: "e9", snapshotId: 7, }); @@ -242,7 +326,7 @@ describe("the computer gateway", () => { // Writing an id with no `users` row fails the constraint and loses the row entirely, so who it // was is carried in the payload instead. The route decides this; the gateway must honour it. const { gateway, rows } = await gatewayWith(PERMISSIVE); - await gateway.click("default", "bot-1", ACTOR, { + await gateway.click("bot-1", ACTOR, { ref: "e9", snapshotId: 7, }); @@ -257,7 +341,7 @@ describe("the computer gateway", () => { }); await expect( - gateway.readFile("default", "bot-1", ACTOR, { + gateway.readFile("bot-1", ACTOR, { path: "credentials/aws.txt", }), ).rejects.toThrow(ActionRefusedError); @@ -273,10 +357,10 @@ describe("the computer gateway", () => { }); await expect( - gateway.readFile("default", "bot-1", ACTOR, { path: "config/prod.env" }), + gateway.readFile("bot-1", ACTOR, { path: "config/prod.env" }), ).rejects.toThrow(ActionRefusedError); // A different extension in the same folder is untouched by that rule. - await gateway.readFile("default", "bot-1", ACTOR, { + await gateway.readFile("bot-1", ACTOR, { path: "config/prod.json", }); expect(calls).toEqual(["readFile"]); @@ -284,7 +368,7 @@ describe("the computer gateway", () => { test("a permitted write happens and is recorded by path, never by contents", async () => { const { gateway, calls, rows } = await gatewayWith(PERMISSIVE); - await gateway.writeFile("default", "bot-1", ACTOR, { + await gateway.writeFile("bot-1", ACTOR, { path: "notes.md", contents: "the customer's card number is 4111-1111-1111-1111", }); @@ -300,16 +384,20 @@ describe("the computer gateway", () => { test("the computer is told WHICH Bot is asking", async () => { // Every per-Bot behaviour on the computer keys off this id: the profile it opens, the logins it // has, the proxy its traffic leaves through, and who holds its wheel. - const { client, addressedAs } = fakeClient(); + const { provider, fetchImpl, addressedAs } = fakeComputer(); const { store } = fakeAudit(); const gateway = createComputerGateway({ - client, + provider, + fetchImpl, auditStore: store, policy: () => PERMISSIVE, }); await gateway.snapshot("sales-bot"); - await gateway.click("sales-bot", "sales-bot", ACTOR, "e1"); + await gateway.click("sales-bot", ACTOR, { + ref: "e1", + snapshotId: 7, + }); await gateway.read("research-bot"); expect(addressedAs).toContain("sales-bot"); @@ -323,25 +411,28 @@ describe("the computer gateway", () => { test("a permitted action that FAILS gets its own row, not an allowed one", async () => { // A permitted read that later fails path confinement records both the decision and the failed // outcome, so the trail does not imply the Bot received the file. - const { client, calls } = fakeClient(); - const { store, rows } = fakeAudit(); - const failing: ComputerClient = { - ...client, - readFile: async () => { - calls.push("readFile"); - throw new Error("that path is outside your workspace"); + const { provider, fetchImpl, calls } = fakeComputer({ + routes: { + "/files/read": () => { + calls.push("readFile"); + return Response.json( + { error: "that path is outside your workspace" }, + { status: 403 }, + ); + }, }, - forBot: () => failing, - } as unknown as ComputerClient; + }); + const { store, rows } = fakeAudit(); const gateway = createComputerGateway({ - client: failing, + provider, + fetchImpl, auditStore: store, policy: () => PERMISSIVE, }); await expect( - gateway.readFile("default", "bot-1", ACTOR, { path: "../../etc/passwd" }), - ).rejects.toThrow(); + gateway.readFile("bot-1", ACTOR, { path: "../../etc/passwd" }), + ).rejects.toThrow(WorkspaceRefusedError); expect(rows).toHaveLength(2); // The decision, then the outcome. Both are needed: the first says it was permitted, the second @@ -352,11 +443,9 @@ describe("the computer gateway", () => { }); test("opening a page is recorded, with the address it was opening", async () => { - // The target guard refuses a forbidden address inside the - // client and nothing was written, so an attempt on the cloud metadata endpoint left no row while - // ticking a radio button left one. + // The target guard refuses forbidden addresses before the request leaves. const { gateway, calls, rows } = await gatewayWith(PERMISSIVE); - await gateway.navigate("default", "bot-1", ACTOR, "https://example.com/"); + await gateway.navigate("bot-1", ACTOR, "https://example.com/"); expect(calls).toEqual(["navigate"]); expect(rows[0]?.eventType).toBe("computer.action_allowed"); @@ -373,22 +462,17 @@ describe("the computer gateway", () => { }); await expect( - gateway.navigate( - "default", - "bot-1", - ACTOR, - "https://intranet.example.com/hr", - ), + gateway.navigate("bot-1", ACTOR, "https://intranet.example.com/hr"), ).rejects.toThrow(ActionRefusedError); expect(calls).toEqual([]); - await gateway.navigate("default", "bot-1", ACTOR, "https://example.com/"); + await gateway.navigate("bot-1", ACTOR, "https://example.com/"); expect(calls).toEqual(["navigate"]); }); test("an action on an unresolvable ref is still decided and still recorded", async () => { const { gateway, rows } = await gatewayWith(PERMISSIVE); - await gateway.click("default", "bot-1", ACTOR, { + await gateway.click("bot-1", ACTOR, { ref: "e404", snapshotId: 7, }); @@ -396,4 +480,166 @@ describe("the computer gateway", () => { // could not identify what was touched, rather than omitting the field. expect(rows[0]?.payload.element).toBe("not in the current snapshot"); }); + + test("stopComputer returns true when the computer was running and audits with the bot id as target", async () => { + const { gateway, calls, rows } = await gatewayWith(PERMISSIVE, { + stopResult: { wasRunning: true }, + }); + + const result = await gateway.stopComputer("bot-1", ACTOR); + + expect(result).toEqual({ wasRunning: true }); + expect(calls).toContain("stop:bot-1"); + expect(rows).toHaveLength(1); + expect(rows[0]?.eventType).toBe("computer.stopped"); + expect(rows[0]?.targetId).toBe("bot-1"); + expect(rows[0]?.targetType).toBe("computer"); + }); + + test("stopComputer preserves wasRunning=false when the computer was already stopped", async () => { + const { gateway, calls, rows } = await gatewayWith(PERMISSIVE, { + stopResult: { wasRunning: false }, + }); + + const result = await gateway.stopComputer("bot-2", ACTOR); + + expect(result).toEqual({ wasRunning: false }); + expect(calls).toContain("stop:bot-2"); + expect(rows).toHaveLength(1); + expect(rows[0]?.eventType).toBe("computer.stopped"); + expect(rows[0]?.targetId).toBe("bot-2"); + }); + + test("resetComputer returns true when state was cleared and audits with the bot id as target", async () => { + const { gateway, calls, rows } = await gatewayWith(PERMISSIVE, { + resetResult: { cleared: true }, + }); + + const result = await gateway.resetComputer("bot-1", ACTOR); + + expect(result).toEqual({ cleared: true }); + expect(calls).toContain("reset:bot-1"); + expect(rows).toHaveLength(1); + expect(rows[0]?.eventType).toBe("computer.reset"); + expect(rows[0]?.targetId).toBe("bot-1"); + expect(rows[0]?.targetType).toBe("computer"); + }); + + test("resetComputer preserves cleared=false when provider could not clear state and audits the bot id", async () => { + const { gateway, calls, rows } = await gatewayWith(PERMISSIVE, { + resetResult: { cleared: false }, + }); + + const result = await gateway.resetComputer("bot-2", ACTOR); + + expect(result).toEqual({ cleared: false }); + expect(calls).toContain("reset:bot-2"); + expect(rows).toHaveLength(1); + expect(rows[0]?.eventType).toBe("computer.reset"); + expect(rows[0]?.targetId).toBe("bot-2"); + }); + + test("computers maps provider status 'running' and 'stopped' directly and preserves egress distinctions", async () => { + const locations: ComputerLocation[] = [ + { + botId: "bot-proxied", + status: "running", + startedAt: "2026-08-20T12:00:00.000Z", + egress: "198.51.100.42", + }, + { + botId: "bot-direct", + status: "stopped", + startedAt: "2026-08-20T11:00:00.000Z", + egress: null, + }, + { + botId: "bot-unknown-egress", + status: "stopped", + startedAt: "2026-08-20T10:00:00.000Z", + egress: undefined, + }, + ]; + const { gateway } = await gatewayWith(PERMISSIVE, { locations }); + + const result = await gateway.computers(); + + expect(result).toEqual({ + isolation: "per-bot", + computers: [ + { + botId: "bot-proxied", + running: true, + startedAt: "2026-08-20T12:00:00.000Z", + egress: "198.51.100.42", + }, + { + botId: "bot-direct", + running: false, + startedAt: "2026-08-20T11:00:00.000Z", + egress: null, + }, + { + botId: "bot-unknown-egress", + running: false, + startedAt: "2026-08-20T10:00:00.000Z", + egress: undefined, + }, + ], + }); + }); + + test("takes a screenshot through the located computer with its identity and token", async () => { + const { gateway, requests } = await gatewayWith(PERMISSIVE, { + token: "computer-secret", + }); + + const result = await gateway.screenshot("bot-1"); + + expect(result).toEqual({ + image: "aGVsbG8=", + mimeType: "image/png", + }); + const screenshotReq = requests.find((r) => r.url.endsWith("/screenshot")); + expect(screenshotReq).toBeDefined(); + expect(screenshotReq?.url).toBe("http://agent-computer:4100/screenshot"); + expect(screenshotReq?.init?.headers).toMatchObject({ + "x-openbot-bot-id": "bot-1", + "x-openbot-computer-token": "computer-secret", + }); + }); + + test("routes acting, control, file, secret, and human input calls to the correct endpoint paths", async () => { + const { gateway, requests } = await gatewayWith(PERMISSIVE); + + await gateway.key("bot-1", ACTOR, { key: "Tab" }); + await gateway.scroll("bot-1", ACTOR, { deltaY: 400 }); + await gateway.listFiles("bot-1", ACTOR, { path: "notes" }); + await gateway.control("bot-1"); + await gateway.requestHelp("bot-1", ACTOR, "Sign in"); + await gateway.takeControl("bot-1", ACTOR); + await gateway.releaseControl("bot-1", ACTOR); + await gateway.requestSecret("bot-1", ACTOR, { + label: "Password", + ref: "e1", + snapshotId: 7, + }); + await gateway.supplySecret("bot-1", ACTOR, "secret"); + await gateway.humanInput("bot-1", { kind: "click", x: 10, y: 20 }); + + const paths = requests.map(({ url }) => new URL(url).pathname); + expect(paths).toEqual([ + "/snapshot", + "/key", + "/scroll", + "/files/list", + "/control", + "/control/request", + "/control/take", + "/control/release", + "/control/secret", + "/human/secret", + "/human/click", + ]); + }); }); diff --git a/server/tests/computer-provider.test.ts b/server/tests/computer-provider.test.ts new file mode 100644 index 0000000..5bfaa5e --- /dev/null +++ b/server/tests/computer-provider.test.ts @@ -0,0 +1,316 @@ +import { afterEach, describe, expect, test } from "bun:test"; +import type { ComputerConfig } from "../src/config"; +import { + createComputerProvider, + createSharedComputerProvider, + describeComputerIsolation, + ProviderError, +} from "../src/computer/provider"; + +const servers: { stop(closeActiveConnections?: boolean): void }[] = []; + +afterEach(() => { + for (const server of servers.splice(0)) server.stop(true); +}); + +function serve(handler: (request: Request) => Response | Promise) { + const server = Bun.serve({ port: 0, fetch: handler }); + servers.push(server); + return `http://127.0.0.1:${server.port}`; +} + +type FakeAgentComputerHandler = { + health?: (request: Request) => Response | Promise; + computers?: (request: Request) => Response | Promise; + stop?: (request: Request) => Response | Promise; + reset?: (request: Request) => Response | Promise; +}; + +function serveAgentComputer( + handlers: FakeAgentComputerHandler = {}, + options?: { token?: string }, +) { + return serve(async (request) => { + const url = new URL(request.url); + const token = request.headers.get("x-openbot-computer-token"); + + if ( + options?.token && + url.pathname !== "/health" && + token !== options.token + ) { + return Response.json({ error: "Not authorised." }, { status: 401 }); + } + + if (url.pathname === "/health" && request.method === "GET") { + if (handlers.health) return handlers.health(request); + return Response.json({ status: "ok", browser: true }); + } + + if (url.pathname === "/computers" && request.method === "GET") { + if (handlers.computers) return handlers.computers(request); + return Response.json({ computers: [] }); + } + + if (url.pathname === "/computers/stop" && request.method === "POST") { + if (handlers.stop) return handlers.stop(request); + return Response.json({ stopped: true, wasRunning: true }); + } + + if (url.pathname === "/computers/reset" && request.method === "POST") { + if (handlers.reset) return handlers.reset(request); + const botId = request.headers.get("x-openbot-bot-id") ?? "shared"; + return Response.json({ reset: true, botId }); + } + + return Response.json({ error: "Not found." }, { status: 404 }); + }); +} + +describe("computer isolation description", () => { + test("describes the computer feature as off when no provider is configured", () => { + const description = describeComputerIsolation(undefined); + expect(description.isolation).toBe("off"); + expect(description.note.toLowerCase()).toContain("off"); + expect(description.note.toLowerCase()).not.toContain("shared"); + expect(description.note.toLowerCase()).not.toContain("browser"); + }); + + test("describes provider machine isolation when configured", () => { + const provider = createSharedComputerProvider({ + baseUrl: "http://computer:4100/", + }); + + expect(provider.name).toBe("shared"); + expect(provider.isolation).toBe("shared"); + expect(describeComputerIsolation(provider).isolation).toBe( + "one shared computer", + ); + }); +}); + +describe("shared computer provider", () => { + test("locates the shared computer address", async () => { + const provider = createSharedComputerProvider({ + baseUrl: "http://computer:4100/", + }); + expect(await provider.locate("sales")).toBe("http://computer:4100/"); + }); + + test("reports a healthy shared computer as ready", async () => { + const paths: string[] = []; + const baseUrl = serveAgentComputer({ + health: (request) => { + paths.push(new URL(request.url).pathname); + return Response.json({ status: "ok" }); + }, + }); + const provider = createSharedComputerProvider({ baseUrl }); + + expect(await provider.status("sales")).toEqual({ + botId: "sales", + state: "ready", + }); + expect(paths).toEqual(["/health"]); + }); + + test("reports the HTTP failure when the shared computer is not healthy", async () => { + const baseUrl = serveAgentComputer({ + health: () => new Response("not ready", { status: 503 }), + }); + const provider = createSharedComputerProvider({ baseUrl }); + + expect(await provider.status("sales")).toEqual({ + botId: "sales", + state: "unreachable", + reason: "The shared computer answered 503.", + }); + }); + + test("posts /computers/stop with identity and token and returns wasRunning", async () => { + const requests: { + path: string; + method: string; + botId: string | null; + token: string | null; + }[] = []; + const baseUrl = serveAgentComputer( + { + stop: (request) => { + const botId = request.headers.get("x-openbot-bot-id"); + requests.push({ + path: new URL(request.url).pathname, + method: request.method, + botId, + token: request.headers.get("x-openbot-computer-token"), + }); + const wasRunning = botId === "running-bot"; + return Response.json({ stopped: true, wasRunning }); + }, + }, + { token: "computer-secret" }, + ); + const provider = createSharedComputerProvider({ + baseUrl, + token: "computer-secret", + }); + + const runningResult = await provider.stop("running-bot"); + expect(runningResult).toEqual({ wasRunning: true }); + + const idleResult = await provider.stop("idle-bot"); + expect(idleResult).toEqual({ wasRunning: false }); + + expect(requests).toEqual([ + { + path: "/computers/stop", + method: "POST", + botId: "running-bot", + token: "computer-secret", + }, + { + path: "/computers/stop", + method: "POST", + botId: "idle-bot", + token: "computer-secret", + }, + ]); + }); + + test("posts /computers/reset with identity and token and returns cleared", async () => { + const requests: { + path: string; + method: string; + botId: string | null; + token: string | null; + }[] = []; + const baseUrl = serveAgentComputer( + { + reset: (request) => { + const botId = request.headers.get("x-openbot-bot-id"); + requests.push({ + path: new URL(request.url).pathname, + method: request.method, + botId, + token: request.headers.get("x-openbot-computer-token"), + }); + return Response.json({ reset: true, botId }); + }, + }, + { token: "computer-secret" }, + ); + const provider = createSharedComputerProvider({ + baseUrl, + token: "computer-secret", + }); + + const resetResult = await provider.reset("sales"); + expect(resetResult).toEqual({ cleared: true }); + + expect(requests).toEqual([ + { + path: "/computers/reset", + method: "POST", + botId: "sales", + token: "computer-secret", + }, + ]); + }); + + test("maps the shared computer inventory to provider locations preserving egress and status", async () => { + const baseUrl = serveAgentComputer({ + computers: () => + Response.json({ + computers: [ + { + botId: "sales", + running: true, + startedAt: "2026-08-20T12:00:00.000Z", + egress: null, + }, + { + botId: "support", + running: false, + startedAt: null, + egress: "us-east-egress", + }, + { + botId: "analytics", + status: "running", + egress: null, + }, + ], + }), + }); + const provider = createSharedComputerProvider({ baseUrl }); + + expect(await provider.list()).toEqual([ + { + botId: "sales", + status: "running", + url: baseUrl, + startedAt: "2026-08-20T12:00:00.000Z", + egress: null, + }, + { + botId: "support", + status: "stopped", + url: baseUrl, + egress: "us-east-egress", + }, + { + botId: "analytics", + status: "running", + url: baseUrl, + egress: null, + }, + ]); + }); + + test("aborts fetch that never settles with configurable timeoutMs and throws ProviderError", async () => { + const baseUrl = serve(() => new Promise(() => {})); + const provider = createSharedComputerProvider({ + baseUrl, + timeoutMs: 25, + }); + + await expect(provider.stop("sales")).rejects.toThrow(ProviderError); + }); +}); + +describe("computer provider factory", () => { + test("selects the Docker supervisor adapter", () => { + const config: ComputerConfig = { + provider: "docker", + baseUrl: "http://supervisor:4300", + supervisorToken: "supervisor-secret", + token: "computer-secret", + allowPrivateHosts: false, + }; + + expect(createComputerProvider(config).name).toBe("Docker supervisor"); + }); + + test("selects the shared computer adapter", () => { + const config: ComputerConfig = { + provider: "shared", + baseUrl: "http://computer:4100", + token: "computer-secret", + allowPrivateHosts: false, + }; + + expect(createComputerProvider(config).name).toBe("shared"); + }); + + test("selects the Daytona adapter", () => { + const config: ComputerConfig = { + provider: "daytona", + apiKey: "daytona-key", + token: "computer-secret", + allowPrivateHosts: false, + snapshot: "prebuilt", + }; + + expect(createComputerProvider(config).name).toBe("Daytona"); + }); +}); diff --git a/server/tests/computer-routes.test.ts b/server/tests/computer-routes.test.ts new file mode 100644 index 0000000..641abe8 --- /dev/null +++ b/server/tests/computer-routes.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, test } from "bun:test"; +import type { MiddlewareHandler } from "hono"; +import type { AppVariables } from "../src/auth/guards"; +import type { ComputerGateway } from "../src/computer/gateway"; +import type { PolicyStore } from "../src/computer/policy-store"; +import { createComputerRoutes } from "../src/computer/routes"; + +describe("computer routes", () => { + test("gets a screenshot through the governed computer gateway", async () => { + const requestedBotIds: string[] = []; + const gateway = { + screenshot: async (botId: string) => { + requestedBotIds.push(botId); + return { image: "aGVsbG8=", mimeType: "image/png" as const }; + }, + } as unknown as ComputerGateway; + const policyStore = {} as PolicyStore; + const requireUser: MiddlewareHandler<{ Variables: AppVariables }> = async ( + _context, + next, + ) => next(); + const routes = createComputerRoutes(gateway, policyStore, requireUser); + + const response = await routes.request( + "http://openbot.test/bot-17/screenshot", + ); + + expect(response.status).toBe(200); + expect(await response.json()).toEqual({ + image: "aGVsbG8=", + mimeType: "image/png", + }); + expect(requestedBotIds).toEqual(["bot-17"]); + }); +}); diff --git a/server/tests/computer-supervisor.test.ts b/server/tests/computer-supervisor.test.ts index f591a47..919d4cc 100644 --- a/server/tests/computer-supervisor.test.ts +++ b/server/tests/computer-supervisor.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from "bun:test"; +import type { ComputerProvider } from "../src/computer/provider"; import { - createSupervisorClient, + createDockerSupervisorProvider, SupervisorError, } from "../src/computer/supervisor"; @@ -13,8 +14,8 @@ import { * to prevent, and it would look like it was working. */ -function clientWith(handler: (path: string) => Response) { - return createSupervisorClient({ +function clientWith(handler: (path: string) => Response): ComputerProvider { + return createDockerSupervisorProvider({ baseUrl: "http://supervisor:4300", token: "t", fetchImpl: (async (url: string | URL | Request) => @@ -76,7 +77,7 @@ describe("locating a Bot's computer", () => { test("an unreachable supervisor says so, rather than looking like a broken computer", async () => { // These are different problems for whoever has to fix them: one is the supervisor, the other is // the Bot's own container. - const client = createSupervisorClient({ + const client = createDockerSupervisorProvider({ baseUrl: "http://supervisor:4300", fetchImpl: (async () => { throw new Error("connection refused"); @@ -87,7 +88,7 @@ describe("locating a Bot's computer", () => { test("the bot id is escaped into the path", async () => { let seen = ""; - const client = createSupervisorClient({ + const client = createDockerSupervisorProvider({ baseUrl: "http://supervisor:4300", fetchImpl: (async (url: string | URL | Request) => { seen = new URL(String(url)).pathname; @@ -98,3 +99,140 @@ describe("locating a Bot's computer", () => { expect(seen).toBe("/computers/a%2Fb/ensure"); }); }); + +describe("Docker supervisor provider", () => { + test("describes one container and browser profile for each Bot", () => { + const provider = createDockerSupervisorProvider({ + baseUrl: "http://supervisor:4300", + fetchImpl: (async () => + Response.json({ computers: [] })) as unknown as typeof fetch, + }); + + expect(provider.name).toBe("Docker supervisor"); + expect(provider.isolation).toBe("per-bot"); + }); + + test.each([ + ["created", { botId: "bot", state: "starting" }], + ["running", { botId: "bot", state: "ready" }], + ["paused", { botId: "bot", state: "absent" }], + ["restarting", { botId: "bot", state: "starting" }], + ["removing", { botId: "bot", state: "absent" }], + ["exited", { botId: "bot", state: "absent" }], + ["dead", { botId: "bot", state: "unreachable" }], + ] as const)( + "maps Docker container status %s to lifecycle state", + async (dockerStatus, expected) => { + const provider = createDockerSupervisorProvider({ + baseUrl: "http://supervisor:4300", + fetchImpl: (async () => + Response.json({ + computers: [ + { + botId: "bot", + container: "openbot-computer-bot", + status: dockerStatus, + url: "http://openbot-computer-bot:4100", + }, + ], + })) as unknown as typeof fetch, + }); + + const result = await provider.status("bot"); + expect(result).toMatchObject(expected); + }, + ); + + test("reports missing bot as absent", async () => { + const provider = createDockerSupervisorProvider({ + baseUrl: "http://supervisor:4300", + fetchImpl: (async () => + Response.json({ computers: [] })) as unknown as typeof fetch, + }); + + expect(await provider.status("missing-bot")).toEqual({ + botId: "missing-bot", + state: "absent", + }); + }); + + test("lists only the provider location fields with mapped status", async () => { + const provider = createDockerSupervisorProvider({ + baseUrl: "http://supervisor:4300", + fetchImpl: (async () => + Response.json({ + computers: [ + { + botId: "sales", + container: "computer-sales", + status: "running", + port: 49152, + url: "http://computer-sales:4100", + startedAt: "2026-08-20T12:00:00.000Z", + }, + { + botId: "support", + container: "computer-support", + status: "exited", + port: 49153, + url: "http://computer-support:4100", + }, + ], + })) as unknown as typeof fetch, + }); + + expect(await provider.list()).toEqual([ + { + botId: "sales", + status: "running", + url: "http://computer-sales:4100", + startedAt: "2026-08-20T12:00:00.000Z", + }, + { + botId: "support", + status: "stopped", + url: "http://computer-support:4100", + }, + ]); + }); + + test("stop reports whether the container was running", async () => { + const provider = createDockerSupervisorProvider({ + baseUrl: "http://supervisor:4300", + fetchImpl: (async () => + Response.json({ stopped: true })) as unknown as typeof fetch, + }); + + expect(await provider.stop("bot")).toEqual({ wasRunning: true }); + }); + + test("stop reports false when container was not running", async () => { + const provider = createDockerSupervisorProvider({ + baseUrl: "http://supervisor:4300", + fetchImpl: (async () => + Response.json({ stopped: false })) as unknown as typeof fetch, + }); + + expect(await provider.stop("bot")).toEqual({ wasRunning: false }); + }); + + test("reset reports whether container state was cleared", async () => { + const provider = createDockerSupervisorProvider({ + baseUrl: "http://supervisor:4300", + fetchImpl: (async () => + Response.json({ reset: true })) as unknown as typeof fetch, + }); + + expect(await provider.reset("bot")).toEqual({ cleared: true }); + }); + + test("reset reports false when container was not present to clear", async () => { + const provider = createDockerSupervisorProvider({ + baseUrl: "http://supervisor:4300", + fetchImpl: (async () => + Response.json({ reset: false })) as unknown as typeof fetch, + }); + + expect(await provider.reset("bot")).toEqual({ cleared: false }); + }); +}); diff --git a/server/tests/config.test.ts b/server/tests/config.test.ts index 326ad5c..0864ba7 100644 --- a/server/tests/config.test.ts +++ b/server/tests/config.test.ts @@ -186,4 +186,104 @@ describe("deployment configuration", () => { expect(attempt).toThrow("AGENT_STALL_TIMEOUT_MS"); }, ); + + test("configures Daytona as the remote computer provider", () => { + const config = loadConfig({ + ...baseEnvironment, + DAYTONA_API_KEY: "dtn_test_key", + DAYTONA_API_URL: "https://daytona.example.com", + DAYTONA_TARGET: "us", + DAYTONA_SNAPSHOT: "agent-computer", + COMPUTER_TOKEN: "secret-token", + AGENT_COMPUTER_ALLOW_PRIVATE_HOSTS: "true", + }); + + expect(config.computer?.provider).toBe("daytona"); + expect(config.computer).toEqual({ + provider: "daytona", + apiKey: "dtn_test_key", + apiUrl: "https://daytona.example.com", + target: "us", + snapshot: "agent-computer", + token: "secret-token", + allowPrivateHosts: true, + }); + }); + + test("configures Docker as the per-Bot computer provider", () => { + const config = loadConfig({ + ...baseEnvironment, + COMPUTER_SUPERVISOR_URL: "http://localhost:4000", + SUPERVISOR_TOKEN: "supervisor-token", + COMPUTER_TOKEN: "computer-token", + }); + + expect(config.computer?.provider).toBe("docker"); + expect(config.computer).toEqual({ + provider: "docker", + baseUrl: "http://localhost:4000", + supervisorToken: "supervisor-token", + token: "computer-token", + allowPrivateHosts: false, + }); + }); + + test("configures one shared computer", () => { + const config = loadConfig({ + ...baseEnvironment, + AGENT_COMPUTER_URL: "http://localhost:4100", + COMPUTER_TOKEN: "computer-token", + }); + + expect(config.computer?.provider).toBe("shared"); + expect(config.computer).toEqual({ + provider: "shared", + baseUrl: "http://localhost:4100", + token: "computer-token", + allowPrivateHosts: false, + }); + }); + + test("leaves computers off when no provider address is configured", () => { + expect(loadConfig(baseEnvironment).computer).toBeUndefined(); + }); + + test("refuses to configure Daytona computers without COMPUTER_TOKEN", () => { + expect(() => + loadConfig({ + ...baseEnvironment, + DAYTONA_API_KEY: "dtn_test_key", + }), + ).toThrow("COMPUTER_TOKEN"); + }); + + test("refuses to configure both Daytona and Docker computer providers", () => { + const attempt = () => + loadConfig({ + ...baseEnvironment, + DAYTONA_API_KEY: "dtn_test_key", + COMPUTER_TOKEN: "secret-token", + COMPUTER_SUPERVISOR_URL: "http://localhost:4000", + }); + expect(attempt).toThrow("DAYTONA_API_KEY"); + expect(attempt).toThrow("COMPUTER_SUPERVISOR_URL"); + }); + + test.each([ + ["Daytona", "DAYTONA_API_URL", "DAYTONA_API_KEY"], + ["Docker", "COMPUTER_SUPERVISOR_URL", undefined], + ["shared", "AGENT_COMPUTER_URL", undefined], + ] as const)( + "refuses an invalid %s computer provider URL", + (_, urlName, daytonaKeyName) => { + expect(() => + loadConfig({ + ...baseEnvironment, + ...(daytonaKeyName ? { [daytonaKeyName]: "dtn_test_key" } : {}), + ...(daytonaKeyName ? { COMPUTER_TOKEN: "secret-token" } : {}), + [urlName]: "not a URL", + }), + ).toThrow(`${urlName} must be a valid URL`); + }, + ); });