experimental-ash 0.34.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/docs/public/auth-and-route-protection.md +9 -0
  3. package/dist/docs/public/sandbox.md +42 -19
  4. package/dist/docs/public/session-context.md +1 -1
  5. package/dist/src/compiled/.vendor-stamp.json +2 -2
  6. package/dist/src/compiled/@vercel/sandbox/index.d.ts +11 -2
  7. package/dist/src/compiled/@vercel/sandbox/index.js +3 -3
  8. package/dist/src/compiled/@vercel/sandbox/package.json +1 -1
  9. package/dist/src/compiled/_chunks/node/{auth-ZhCJAHxl.js → auth-CVVvWjaK.js} +1 -1
  10. package/dist/src/compiled/_chunks/node/{version-D4IYmfaS.js → version-nR4RSpFw.js} +1 -1
  11. package/dist/src/execution/sandbox/bindings/vercel.d.ts +1 -1
  12. package/dist/src/execution/sandbox/session.js +1 -1
  13. package/dist/src/internal/application/package.js +1 -1
  14. package/dist/src/internal/logging.js +1 -1
  15. package/dist/src/packages/ash-scaffold/src/channels.js +1 -1
  16. package/dist/src/packages/ash-scaffold/src/web-template.js +2 -2
  17. package/dist/src/public/channels/auth.d.ts +22 -11
  18. package/dist/src/public/channels/auth.js +1 -1
  19. package/dist/src/public/definitions/sandbox.d.ts +1 -1
  20. package/dist/src/public/sandbox/index.d.ts +1 -1
  21. package/dist/src/public/sandbox/vercel-sandbox.d.ts +4 -4
  22. package/dist/src/runtime/governance/auth/oidc.js +1 -1
  23. package/dist/src/runtime/governance/auth/token-claims.d.ts +2 -0
  24. package/dist/src/runtime/governance/auth/token-claims.js +1 -1
  25. package/dist/src/runtime/governance/auth/types.d.ts +6 -0
  26. package/dist/src/shared/sandbox-session.d.ts +0 -17
  27. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # experimental-ash
2
2
 
3
+ ## 0.35.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 24aa0cd: `localDev()` now also grants the local-dev session when running under `vercel dev` (`VERCEL=1` and `VERCEL_ENV=development`), not just loopback requests, so the local Vercel dev server can reach the agent without an OIDC token. `ASH_LOG_LEVEL` now defaults to `info` in every environment (previously `debug` outside production), making `debug` opt-in so dev output is no longer flooded with best-effort lines.
8
+ - a59d91c: Remove deprecated `SandboxSession.runCommand` alias and `SandboxRunCommandOptions` type. Use `sandbox.run({ command })` and `SandboxRunOptions` instead.
9
+ - 083c20a: `vercelOidc()` now accepts Vercel OIDC tokens with `external_sub` as user principals when the token matches the configured Vercel project and deployment environment. The resulting session auth uses `external_sub` as the subject, prefers `external_iss` / `connector_id` as the issuer, and exposes string OIDC profile claims such as `name`, `picture`, and `email` as attributes.
10
+
11
+ ### Patch Changes
12
+
13
+ - 321bae2: Upgrade `@vercel/sandbox` from 2.0.0 to 2.0.1. Sessions are now persistent by default and auto-resume on `Sandbox.get()`. Vendored types updated with `resume`, `onResume`, and `readFile`.
14
+
3
15
  ## 0.34.0
4
16
 
5
17
  ### Minor Changes
@@ -156,6 +156,15 @@ Use this for the common Vercel deployment path. Verifies a bearer JWT against th
156
156
  issuer; tokens minted for the current `VERCEL_PROJECT_ID` are always accepted (so internal
157
157
  subagent / runtime callers authenticate without configuration).
158
158
 
159
+ It accepts Vercel OIDC bearer tokens after signature, issuer, audience, and time-claim verification.
160
+ Tokens for the current `VERCEL_PROJECT_ID` authenticate as runtime/service callers. Tokens with
161
+ `external_sub` authenticate as user callers when their `project_id` matches `VERCEL_PROJECT_ID` if
162
+ configured, and their `environment` matches `VERCEL_TARGET_ENV` or `VERCEL_ENV` if configured. For
163
+ those user tokens, `external_sub` becomes the session subject, `external_iss` becomes the session
164
+ issuer when present, and `connector_id` is used as the issuer fallback before the Vercel OIDC issuer.
165
+ String OIDC profile claims such as `name`, `picture`, and `email` are exposed in
166
+ `getSession().auth.current.attributes`.
167
+
159
168
  ### `none`
160
169
 
161
170
  Returns a synthetic anonymous `SessionAuthContext`. Use as the final entry in `auth` to accept
@@ -31,7 +31,7 @@ import { defineSandbox } from "experimental-ash/sandbox";
31
31
  export default defineSandbox({
32
32
  async bootstrap({ use }) {
33
33
  const sandbox = await use();
34
- await sandbox.runCommand("apt-get install -y jq");
34
+ await sandbox.run({ command: "apt-get install -y jq" });
35
35
  },
36
36
  });
37
37
  ```
@@ -58,7 +58,7 @@ The public lifecycle surface is intentionally small:
58
58
  sandbox — creation happens at prewarm time and at first-time session-create, both driven by the
59
59
  backend factory's options.
60
60
  - `sandbox.resolvePath(path)` — translate a logical `/workspace/...` path into the live filesystem
61
- - `sandbox.runCommand(command)` — run a shell command inside the sandbox
61
+ - `sandbox.run({ command })` — run a shell command inside the sandbox
62
62
 
63
63
  `defineSandbox` lives on `experimental-ash/sandbox`.
64
64
 
@@ -126,9 +126,9 @@ export default defineTool({
126
126
  inputSchema: z.object({ kind: z.string(), payload: z.string() }),
127
127
  async execute({ kind, payload }) {
128
128
  const sandbox = await getSandbox();
129
- await sandbox.runCommand(
130
- `echo ${JSON.stringify(JSON.stringify({ kind, payload }))} >> /var/lib/analytics/events.log`,
131
- );
129
+ await sandbox.run({
130
+ command: `echo ${JSON.stringify(JSON.stringify({ kind, payload }))} >> /var/lib/analytics/events.log`,
131
+ });
132
132
  return { ok: true };
133
133
  },
134
134
  });
@@ -144,7 +144,7 @@ Important rules:
144
144
  ### Workspace Paths
145
145
 
146
146
  Every backend runs `bash` with `/workspace` as the working directory. `readFile(...)`,
147
- `writeFile(...)`, and `runCommand(...)` all share that single namespace — `/workspace/foo` refers
147
+ `writeFile(...)`, and `run(...)` all share that single namespace — `/workspace/foo` refers
148
148
  to the same file whether the backend is local or Vercel.
149
149
 
150
150
  `sandbox.resolvePath(...)` anchors a sandbox-relative path to `/workspace` and returns the
@@ -158,11 +158,11 @@ const sandbox = await getSandbox();
158
158
  const analysisRoot = sandbox.resolvePath("python-analysis");
159
159
 
160
160
  await sandbox.writeFile("python-analysis/run.py", "print('ok')\n");
161
- await sandbox.runCommand(`python ${JSON.stringify(`${analysisRoot}/run.py`)}`);
161
+ await sandbox.run({ command: `python ${JSON.stringify(`${analysisRoot}/run.py`)}` });
162
162
  ```
163
163
 
164
164
  `readFile(...)` and `writeFile(...)` apply the same anchoring internally, so you only need to
165
- call `resolvePath(...)` explicitly when building paths for `runCommand(...)` or returning them
165
+ call `resolvePath(...)` explicitly when building paths for `run(...)` or returning them
166
166
  from authored helpers.
167
167
 
168
168
  ## Subagents Get Their Own Sandbox
@@ -236,7 +236,8 @@ export default defineSandbox({
236
236
 
237
237
  Ash ships two built-in backends, exposed as factory functions from `experimental-ash/sandbox`:
238
238
 
239
- - `vercelBackend(opts?)` — runs the sandbox on Vercel Sandbox via `@vercel/sandbox`.
239
+ - `vercelBackend(opts?)` — runs the sandbox on [Vercel Sandbox](https://vercel.com/docs/sandbox) via
240
+ [`@vercel/sandbox`](https://vercel.com/docs/sandbox/sdk-reference).
240
241
  - `localBackend(opts?)` — runs the sandbox locally via `just-bash`. Default for `pnpm ash dev`.
241
242
 
242
243
  Attach a backend via `defineSandbox({ backend })`:
@@ -249,7 +250,7 @@ export default defineSandbox({
249
250
  backend: vercelBackend({ runtime: "node24", resources: { vcpus: 2 } }),
250
251
  async bootstrap({ use }) {
251
252
  const sandbox = await use();
252
- await sandbox.runCommand("git clone https://example.com/repo.git repo");
253
+ await sandbox.run({ command: "git clone https://example.com/repo.git repo" });
253
254
  },
254
255
  async onSession({ use }) {
255
256
  await use({ networkPolicy: "deny-all" });
@@ -282,9 +283,10 @@ The factory `opts` parameter is forwarded to the underlying SDK's `Sandbox.creat
282
283
  every fresh sandbox the framework creates — both the template at prewarm time and the session at
283
284
  first-time session-create.
284
285
 
285
- On resume (when the framework reattaches to an existing persistent session via `Sandbox.get`), no
286
- `Sandbox.create` call happens, so the factory opts are not re-applied. The existing sandbox keeps
287
- whatever configuration it had from its prior creation.
286
+ On resume (when the framework reattaches to an existing
287
+ [persistent](#session-persistence-and-resume) session via `Sandbox.get`), no `Sandbox.create` call
288
+ happens, so the factory opts are not re-applied. The existing sandbox keeps whatever configuration
289
+ it had from its prior creation.
288
290
 
289
291
  Lifecycle hooks remain update-time and override post-create:
290
292
 
@@ -361,9 +363,8 @@ lock down via `onSession`.
361
363
 
362
364
  ### Timeout
363
365
 
364
- The `@vercel/sandbox` SDK shuts down idle VMs after a configurable timeout (default 5 minutes). Ash
365
- raises this default to **30 minutes** so the sandbox survives across workflow step boundaries. Set
366
- a different default on the factory, or override per-session in `onSession`:
366
+ Sandbox VMs shut down after a configurable timeout. Ash defaults to **30 minutes**. Set a different
367
+ value on the factory, or override per-session in `onSession`:
367
368
 
368
369
  ```ts
369
370
  // factory default — applies to every fresh sandbox
@@ -376,7 +377,28 @@ async onSession({ use }) {
376
377
  ```
377
378
 
378
379
  The maximum timeout depends on your Vercel plan: **5 hours** for Pro/Enterprise, **45 minutes** for
379
- Hobby. If a sandbox expires between steps, the next step will fail with a `410 Gone` error.
380
+ Hobby. When the timeout fires, the VM shuts down but because sessions are
381
+ [persistent](#session-persistence-and-resume), the filesystem state is preserved and the sandbox
382
+ resumes automatically on the next request.
383
+
384
+ ### Session Persistence and Resume
385
+
386
+ Sandbox sessions are **persistent by default**: when the VM shuts down (timeout or inactivity), the
387
+ filesystem state is preserved. The next time a message arrives — even days or weeks later — the
388
+ sandbox automatically resumes from that state.
389
+
390
+ This means:
391
+
392
+ - A user can send a message days after the last interaction and pick up where they left off.
393
+ Files created during earlier turns, installed dependencies, and workspace state are all preserved.
394
+ - The resume is transparent to your code — no configuration is required.
395
+ - If a sandbox has been deleted (by cleanup policies or manual deletion), Ash creates a fresh
396
+ session from the prewarmed template snapshot. The user gets a clean sandbox seeded with framework
397
+ defaults, bootstrap output, and workspace files — but any session-specific state from prior turns
398
+ is lost.
399
+
400
+ See the [Vercel Sandbox documentation](https://vercel.com/docs/sandbox) for details on persistence
401
+ behavior and plan-specific retention limits.
380
402
 
381
403
  ### Tags
382
404
 
@@ -386,8 +408,7 @@ Ash attaches Vercel Sandbox tags for runtime attribution:
386
408
  - `channel` — the active channel adapter kind
387
409
  - `sessionId` — the Ash session id
388
410
 
389
- Custom tags can be set on the factory (applied at every fresh `Sandbox.create`) or via
390
- `onSession`'s `use()` (applied via `sandbox.update`):
411
+ Custom tags can be set on the factory or via `onSession`'s `use()`:
391
412
 
392
413
  ```ts
393
414
  backend: vercelBackend({ tags: { team: "infra" } });
@@ -425,3 +446,5 @@ Important behavior:
425
446
  - [Tools](./tools.md)
426
447
  - [Workspace](./workspace.md)
427
448
  - [Session Context](./session-context.md)
449
+ - [Vercel Sandbox](https://vercel.com/docs/sandbox) — platform documentation for Vercel Sandbox
450
+ - [Vercel Sandbox SDK Reference](https://vercel.com/docs/sandbox/sdk-reference) — `@vercel/sandbox` API reference
@@ -65,7 +65,7 @@ Important behavior:
65
65
 
66
66
  ```ts
67
67
  const sandbox = await getSandbox();
68
- const result = await sandbox.runCommand("pnpm test");
68
+ const result = await sandbox.run({ command: "pnpm test" });
69
69
  ```
70
70
 
71
71
  Important behavior:
@@ -20,11 +20,11 @@
20
20
  "@standard-schema/spec": "1.1.0",
21
21
  "turndown": "7.2.4",
22
22
  "@vercel/oidc": "3.4.1",
23
- "@vercel/sandbox": "2.0.0",
23
+ "@vercel/sandbox": "2.0.1",
24
24
  "@workflow/core": "5.0.0-beta.7",
25
25
  "@workflow/errors": "5.0.0-beta.4",
26
26
  "zod": "4.4.3",
27
27
  "zod-validation-error": "5.0.0"
28
28
  },
29
- "scriptHash": "d4ce5a47e0f6620d682bfcba879a1eabaa5c61cc9c9cfe24e3a54c38aaf1edb0"
29
+ "scriptHash": "de60e990328c8664beec98bc5e81625ecdc0cdace1ba8bd8aa267bf9bea29af4"
30
30
  }
@@ -41,6 +41,7 @@ export interface SandboxCreateOptions {
41
41
  env?: Record<string, string> | undefined;
42
42
  name?: string | undefined;
43
43
  networkPolicy?: NetworkPolicy | undefined;
44
+ onResume?: ((sandbox: Sandbox) => Promise<void>) | undefined;
44
45
  persistent?: boolean | undefined;
45
46
  ports?: number[] | undefined;
46
47
  resources?: { vcpus?: number | undefined } | undefined;
@@ -52,6 +53,13 @@ export interface SandboxCreateOptions {
52
53
  timeout?: number | undefined;
53
54
  }
54
55
 
56
+ export interface SandboxGetOptions {
57
+ name: string;
58
+ onResume?: ((sandbox: Sandbox) => Promise<void>) | undefined;
59
+ resume?: boolean | undefined;
60
+ signal?: AbortSignal | undefined;
61
+ }
62
+
55
63
  export interface SandboxRunCommandParams {
56
64
  args?: readonly string[] | undefined;
57
65
  cmd: string;
@@ -95,9 +103,10 @@ export declare class Sandbox {
95
103
  status: string;
96
104
  tags?: Record<string, string> | undefined;
97
105
  static create(options?: SandboxCreateOptions): Promise<Sandbox>;
98
- static get(options: { name: string } | Record<string, unknown>): Promise<Sandbox>;
106
+ static get(options: SandboxGetOptions): Promise<Sandbox>;
99
107
  domain(port: number): string;
100
- readFileToBuffer(input: { path: string }): Promise<Buffer | Uint8Array | null>;
108
+ readFile(file: { path: string }): Promise<ReadableStream<Uint8Array> | null>;
109
+ readFileToBuffer(file: { path: string }): Promise<Buffer | null>;
101
110
  runCommand(input: SandboxRunCommandParams & { detached: true }): Promise<SandboxCommand>;
102
111
  runCommand(input: SandboxRunCommandParams): Promise<SandboxCommandFinished>;
103
112
  snapshot(options?: unknown): Promise<{ snapshotId: string }>;