Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,9 @@ Execution remains attached to its foreground request. Process crashes can leave

## Known hosted runtime limits

Sandbox identity includes the configured provider connection and authenticated principal. Each process reserves at most 25 leases before provisioning, protects active runs, and expires workspaces 15 minutes after the final run closes. Reported deletion failures retain their lease for retry. Cleanup remains best effort: abrupt process termination loses these records, and the installed Docker, Daytona, and Vercel adapters suppress deletion errors. Guaranteed eventual cleanup would require provider expiry or external reconciliation.
Sandbox identity includes the authenticated principal, provider connection, and configuration revision. Each process reserves at most 25 chat leases before provisioning, protects active runs, and expires workspaces 15 minutes after the final run closes. A separate `SandboxResources` service records each named allocation before contacting its provider, including connection tests. It renews durable ownership once a minute with a 15-minute deadline. Every tool acquisition checks ownership, and expired ownership cannot be revived. The existing Effect `ClusterCron` claims expired allocations in bounded batches and retries failed cleanup after five minutes. Local cleanup keeps a workspace fenced while deletion is still running.

Cleanup records retain encrypted, identity-bound configuration snapshots so provider edits or deletion do not discard the original cleanup credentials. Organization deletion purges its sandboxes before deleting these records. If a process dies before recording a creation outcome, or creation returns a transport failure, cleanup reconciles the reserved name for a day after retirement to catch late results. This can delay Organization deletion. Before retaining a new allocation intent, one provider lookup of its freshly generated name checks access without resuming an existing workspace. This extra request prevents rejected credentials from leaving an unreapable intent. Provider outages or credentials revoked after creation retain failed cleanup records for later recovery. Local patches to the maintained TanStack adapters preserve names, distinguish absence from outages, and delete Vercel sandbox entities rather than only stopping their sessions. Managed sandboxes expose filesystem, process, environment, and port operations. Forks and snapshots remain disabled until their additional resources have durable ownership too.

Sandbox files and command output are buffered before size checks or truncation, and wrapper timeouts may not stop transfers. Sparse XLSX worksheets are padded and rendered synchronously before text truncation, without a document-wide work budget. Both paths can exhaust replica resources.

Expand Down
3 changes: 3 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@
},
"packageManager": "bun@1.4.4",
"patchedDependencies": {
"nitro@3.0.260903-beta": "patches/nitro@3.0.260903-beta.patch"
"nitro@3.0.260903-beta": "patches/nitro@3.0.260903-beta.patch",
"@tanstack/ai-sandbox-docker@0.3.2": "patches/@tanstack%2Fai-sandbox-docker@0.3.2.patch",
"@tanstack/ai-sandbox-daytona@0.3.4": "patches/@tanstack%2Fai-sandbox-daytona@0.3.4.patch",
"@tanstack/ai-sandbox-vercel@0.2.5": "patches/@tanstack%2Fai-sandbox-vercel@0.2.5.patch"
},
"trustedDependencies": [
"esbuild",
Expand Down
33 changes: 33 additions & 0 deletions patches/@tanstack%2Fai-sandbox-daytona@0.3.4.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
diff --git a/dist/esm/provider.js b/dist/esm/provider.js
index 1449826665afc6cc02e77ce9191a73641d1292f2..01b5211b672811ff82ebb168dd03da15c4796dd2 100644
--- a/dist/esm/provider.js
+++ b/dist/esm/provider.js
@@ -3,1 +3,1 @@
-import { Daytona, DaytonaConflictError } from "@daytona/sdk";
+import { Daytona, DaytonaConflictError, DaytonaNotFoundError } from "@daytona/sdk";
@@ -118,1 +118,2 @@
- } catch {
+ } catch (error) {
+ if (!(error instanceof DaytonaNotFoundError)) throw error;
@@ -126,1 +127,3 @@
- } catch {}
+ } catch (error) {
+ if (!(error instanceof DaytonaNotFoundError)) throw error;
+ }
diff --git a/src/provider.ts b/src/provider.ts
index 1c13ed38933fc9f678a70b080a7de442972ce9cc..73d0eb16a5693a6e1bbfc212be2a7e06d6f44555 100644
--- a/src/provider.ts
+++ b/src/provider.ts
@@ -2,1 +2,1 @@
-import { Daytona, DaytonaConflictError } from '@daytona/sdk'
+import { Daytona, DaytonaConflictError, DaytonaNotFoundError } from '@daytona/sdk'
@@ -197,2 +197,2 @@
- } catch {
- // Gone / not found.
+ } catch (error) {
+ if (!(error instanceof DaytonaNotFoundError)) throw error
@@ -207,2 +207,2 @@
- } catch {
- // Already deleted / gone.
+ } catch (error) {
+ if (!(error instanceof DaytonaNotFoundError)) throw error
60 changes: 60 additions & 0 deletions patches/@tanstack%2Fai-sandbox-docker@0.3.2.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
diff --git a/dist/esm/provider.js b/dist/esm/provider.js
index 6bc1a9d6355d8e62488c5c1b555133460e96ce19..1984978f2735dab87f1fa207868f0bd581ff3387 100644
--- a/dist/esm/provider.js
+++ b/dist/esm/provider.js
@@ -37,1 +37,1 @@
- async startContainer(image, env) {
+ async startContainer(image, env, name) {
@@ -44,0 +45,1 @@
+ name,
@@ -84,1 +85,1 @@
- return this.startContainer(this.config.image, input.env);
+ return this.startContainer(this.config.image, input.env, input.id);
@@ -91,1 +92,2 @@
- } catch {
+ } catch (error) {
+ if (error.statusCode !== 404) throw error;
@@ -96,1 +98,2 @@
- } catch {
+ } catch (error) {
+ if (error.statusCode !== 404) throw error;
@@ -115,1 +118,3 @@
- } catch {}
+ } catch (error) {
+ if (error.statusCode !== 304 && error.statusCode !== 404) throw error;
+ }
@@ -121,1 +126,3 @@
- } catch {}
+ } catch (error) {
+ if (error.statusCode !== 404) throw error;
+ }
diff --git a/src/provider.ts b/src/provider.ts
index 2eba55337bedb778404a042347c0b7811f91c595..079adaa61fc1c171715dfab499e49a541ee21e87 100644
--- a/src/provider.ts
+++ b/src/provider.ts
@@ -92,0 +93,1 @@
+ name?: string,
@@ -101,0 +103,1 @@
+ name,
@@ -145,1 +147,1 @@
- return this.startContainer(this.config.image, input.env)
+ return this.startContainer(this.config.image, input.env, input.id)
@@ -153,1 +155,2 @@
- } catch {
+ } catch (error) {
+ if ((error as { statusCode?: number }).statusCode !== 404) throw error
@@ -159,1 +162,2 @@
- } catch {
+ } catch (error) {
+ if ((error as { statusCode?: number }).statusCode !== 404) throw error
@@ -181,2 +185,3 @@
- } catch {
- // already stopped / gone
+ } catch (error) {
+ const status = (error as { statusCode?: number }).statusCode
+ if (status !== 304 && status !== 404) throw error
@@ -187,2 +192,2 @@
- } catch {
- // already removed
+ } catch (error) {
+ if ((error as { statusCode?: number }).statusCode !== 404) throw error
37 changes: 37 additions & 0 deletions patches/@tanstack%2Fai-sandbox-vercel@0.2.5.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
diff --git a/dist/esm/provider.js b/dist/esm/provider.js
index dde5f06611d2a38ff477e84cb631588f9d7209c7..0bdc855bedef9c0ea8c22413e18b4ce4cb5fd3c5 100644
--- a/dist/esm/provider.js
+++ b/dist/esm/provider.js
@@ -60,0 +61,2 @@
+ name: input.id,
+ signal: input.signal,
@@ -90,1 +92,2 @@
- } catch {
+ } catch (error) {
+ if (!(error instanceof APIError) || error.response.status !== 404) throw error;
@@ -100,2 +103,4 @@
- })).stop();
- } catch {}
+ })).delete({ signal: input.signal });
+ } catch (error) {
+ if (!(error instanceof APIError) || error.response.status !== 404) throw error;
+ }
diff --git a/src/provider.ts b/src/provider.ts
index fb4be83951bc85f8c61fca0c6a228ce8af759d58..ec4f555373c786b33c251461062c42ae1149aac1 100644
--- a/src/provider.ts
+++ b/src/provider.ts
@@ -121,0 +122,2 @@
+ name: input.id,
+ signal: input.signal,
@@ -164,2 +166,2 @@
- } catch {
- // Gone / not found / expired.
+ } catch (error) {
+ if (!(error instanceof APIError) || error.response.status !== 404) throw error
@@ -177,3 +179,3 @@
- await sandbox.stop()
- } catch {
- // Already stopped / gone.
+ await sandbox.delete({ signal: input.signal })
+ } catch (error) {
+ if (!(error instanceof APIError) || error.response.status !== 404) throw error
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
CREATE TABLE "sandbox_resource" (
"organization_id" uuid,
"id" uuid DEFAULT uuidv7(),
"owner_id" uuid NOT NULL,
"provider_sandbox_id" text NOT NULL,
"configuration" text NOT NULL,
"settled" boolean DEFAULT false NOT NULL,
"retired_at" timestamp with time zone,
"expires_at" timestamp with time zone NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "sandbox_resource_pkey" PRIMARY KEY("organization_id","id")
);
--> statement-breakpoint
CREATE UNIQUE INDEX "sandbox_resource_provider_id_uidx" ON "sandbox_resource" ("organization_id","provider_sandbox_id");--> statement-breakpoint
CREATE INDEX "sandbox_resource_expires_at_idx" ON "sandbox_resource" ("expires_at");--> statement-breakpoint
CREATE INDEX "sandbox_resource_owner_id_idx" ON "sandbox_resource" ("owner_id");--> statement-breakpoint
ALTER TABLE "sandbox_resource" ADD CONSTRAINT "sandbox_resource_organization_id_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "organization"("id") DEFERRABLE INITIALLY IMMEDIATE;
Loading
Loading