@agent-compose/sdk 0.8.1 → 0.8.2

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.
package/dist/index.js CHANGED
@@ -1756,6 +1756,13 @@ class AgentComposeClient {
1756
1756
  const body = await this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/preview/${port}`, { method: "DELETE" });
1757
1757
  return body.closed;
1758
1758
  }
1759
+ holdBackgroundWork(conversationId, input) {
1760
+ return this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/background-work`, { method: "POST", body: { ...input?.minutes !== undefined ? { minutes: input.minutes } : {} } });
1761
+ }
1762
+ async releaseBackgroundWork(conversationId) {
1763
+ const body = await this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/background-work`, { method: "DELETE" });
1764
+ return body.released;
1765
+ }
1759
1766
  forkSession(conversationId, input) {
1760
1767
  return this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/fork`, { method: "POST", body: input?.title ? { title: input.title } : {} });
1761
1768
  }
@@ -2005,7 +2012,7 @@ class AgentComposeClient {
2005
2012
  async createRepoLink(input, opts) {
2006
2013
  const slug = opts?.factorySlug ?? DEFAULT_FACTORY;
2007
2014
  const body = await this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}/repo-links`, { method: "POST", body: input });
2008
- return body.link;
2015
+ return body.links;
2009
2016
  }
2010
2017
  deleteRepoLink(linkId, opts) {
2011
2018
  const slug = opts?.factorySlug ?? DEFAULT_FACTORY;
@@ -2029,9 +2036,17 @@ class AgentComposeClient {
2029
2036
  createApiKey(input) {
2030
2037
  return this.fetch("/api-keys", { method: "POST", body: input });
2031
2038
  }
2032
- async listApiKeys() {
2033
- const body = await this.fetch("/api-keys");
2034
- return body.data;
2039
+ async listApiKeys(opts) {
2040
+ const qs = new URLSearchParams;
2041
+ if (opts?.limit !== undefined)
2042
+ qs.set("limit", String(opts.limit));
2043
+ if (opts?.status !== undefined)
2044
+ qs.set("status", opts.status);
2045
+ if (opts?.cursor !== undefined)
2046
+ qs.set("cursor", opts.cursor);
2047
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
2048
+ const body = await this.fetch(`/api-keys${suffix}`);
2049
+ return { data: body.data, hasMore: body.has_more, nextCursor: body.next_cursor ?? null };
2035
2050
  }
2036
2051
  getUsage(from, to) {
2037
2052
  const qs = `?from=${encodeURIComponent(from.toISOString())}&to=${encodeURIComponent(to.toISOString())}`;
@@ -3783,6 +3798,29 @@ function wrapJsonlCommand(args) {
3783
3798
  function now4() {
3784
3799
  return new Date().toISOString();
3785
3800
  }
3801
+ var TAIL_REATTACH_DELAY_MS = 2000;
3802
+ var LAUNCH_EXEC_TIMEOUT_MS = 30000;
3803
+ var LAUNCH_PID_RECOVERY_ATTEMPTS = 3;
3804
+ var LAUNCH_PID_RECOVERY_DELAY_MS = 1000;
3805
+ var INSTALL_PROBE_TIMEOUT_MS = 30000;
3806
+ function parsePid(raw) {
3807
+ const pid = Number.parseInt(raw.trim().split(`
3808
+ `).pop() ?? "", 10);
3809
+ return Number.isInteger(pid) && pid > 0 ? pid : Number.NaN;
3810
+ }
3811
+ async function recoverLaunchPid(read, pidPath) {
3812
+ for (let attempt = 0;attempt < LAUNCH_PID_RECOVERY_ATTEMPTS; attempt++) {
3813
+ try {
3814
+ const pid = parsePid(await read(pidPath));
3815
+ if (!Number.isNaN(pid))
3816
+ return pid;
3817
+ } catch {}
3818
+ if (attempt < LAUNCH_PID_RECOVERY_ATTEMPTS - 1) {
3819
+ await new Promise((r) => setTimeout(r, LAUNCH_PID_RECOVERY_DELAY_MS));
3820
+ }
3821
+ }
3822
+ return Number.NaN;
3823
+ }
3786
3824
  function shellQuote(value) {
3787
3825
  return `'${value.replace(/'/g, `'\\''`)}'`;
3788
3826
  }
@@ -3868,7 +3906,7 @@ class CliAgentRunner {
3868
3906
  async ensureInstalled() {
3869
3907
  if (this.installed)
3870
3908
  return;
3871
- const probe = await this.sandbox.commands.run(`command -v ${this.spec.bin}`);
3909
+ const probe = await this.sandbox.commands.run(`command -v ${this.spec.bin}`, { timeoutMs: INSTALL_PROBE_TIMEOUT_MS });
3872
3910
  if (probe.exitCode === 0) {
3873
3911
  this.installed = true;
3874
3912
  return;
@@ -4033,51 +4071,153 @@ class CliAgentRunner {
4033
4071
  keepBytes: JSONL_GUARD_KEEP_BYTES
4034
4072
  });
4035
4073
  const lines = new AsyncQueue;
4036
- let buf = "";
4037
- const onStdout = (data) => {
4038
- buf += data;
4039
- let nl;
4040
- while ((nl = buf.indexOf(`
4041
- `)) >= 0) {
4042
- const line = buf.slice(0, nl).trim();
4043
- buf = buf.slice(nl + 1);
4044
- if (line)
4045
- lines.push(line);
4046
- }
4047
- };
4048
- const runOpts = {
4049
- ...this.options.cwd ? { cwd: this.options.cwd } : {},
4050
- onStdout,
4051
- timeoutMs: 0
4052
- };
4053
- let exited = false;
4054
- const settle = (res) => {
4055
- exited = true;
4056
- const tail = buf.trim();
4057
- if (tail)
4058
- lines.push(tail);
4059
- lines.close();
4060
- return res;
4061
- };
4062
- const fail = (err) => {
4063
- exited = true;
4064
- lines.close();
4065
- throw err;
4066
- };
4067
- let kill;
4068
- let runPromise;
4074
+ let exitCode = null;
4075
+ let exitStderr = "";
4076
+ let transportError = null;
4077
+ let consumerStopped = false;
4078
+ let reap = () => {};
4079
+ let transport;
4080
+ const durableRead = this.sandbox.files.read?.bind(this.sandbox.files);
4069
4081
  if (this.sandbox.commands.runBackground) {
4070
- const handle = await this.sandbox.commands.runBackground(guardedCmd, runOpts);
4071
- kill = () => handle.kill();
4072
- runPromise = handle.wait().then(settle, fail);
4082
+ const outPath = `${promptPath}.out`;
4083
+ const errPath = `${promptPath}.err`;
4084
+ const detachedScript = `{ ${guardedCmd}; } >> ${shellQuote(outPath)} 2>> ${shellQuote(errPath)} </dev/null; ` + `printf '\\n%s %s\\n' ${shellQuote(sentinel)} "$?" >> ${shellQuote(outPath)}`;
4085
+ const pidPath = `${promptPath}.pid`;
4086
+ const launchCmd = `: > ${shellQuote(outPath)}; : > ${shellQuote(errPath)}; ` + `if command -v setsid >/dev/null 2>&1; then setsid sh -c ${shellQuote(detachedScript)} >/dev/null 2>&1 </dev/null & ` + `else sh -c ${shellQuote(detachedScript)} >/dev/null 2>&1 </dev/null & fi; ` + `echo "$!" > ${shellQuote(pidPath)}; echo "$!"`;
4087
+ let pid = Number.NaN;
4088
+ let launched = null;
4089
+ let launchErr = null;
4090
+ try {
4091
+ launched = await this.sandbox.commands.run(launchCmd, {
4092
+ ...this.options.cwd ? { cwd: this.options.cwd } : {},
4093
+ timeoutMs: LAUNCH_EXEC_TIMEOUT_MS
4094
+ });
4095
+ } catch (err) {
4096
+ launchErr = err;
4097
+ }
4098
+ if (launched) {
4099
+ pid = parsePid(launched.stdout ?? "");
4100
+ if (launched.exitCode !== 0 || Number.isNaN(pid)) {
4101
+ throw new Error(`failed to launch ${this.spec.kind}: ` + `exit ${launched.exitCode}${launched.stderr ? `: ${launched.stderr.slice(-500)}` : ""}`);
4102
+ }
4103
+ } else {
4104
+ pid = durableRead ? await recoverLaunchPid(durableRead, pidPath) : Number.NaN;
4105
+ if (Number.isNaN(pid))
4106
+ throw launchErr;
4107
+ console.warn(`[cli-agent] ${this.spec.kind} launch exec stream died but the detached runner is up ` + `(pid ${pid} via ${pidPath}) — continuing on the durable transport: ${formatError(launchErr)}`);
4108
+ }
4109
+ let offset = 0;
4110
+ let currentTail = null;
4111
+ const consumeLine = (raw) => {
4112
+ offset += Buffer.byteLength(raw, "utf8") + 1;
4113
+ const line = raw.trim();
4114
+ if (!line)
4115
+ return;
4116
+ if (line.startsWith(sentinel)) {
4117
+ const code = Number(line.slice(sentinel.length).trim());
4118
+ exitCode = Number.isFinite(code) ? code : 0;
4119
+ return;
4120
+ }
4121
+ lines.push(line);
4122
+ };
4123
+ reap = () => {
4124
+ this.sandbox.commands.run(`kill -TERM -- -${pid} 2>/dev/null; kill -TERM ${pid} 2>/dev/null; true`, { timeoutMs: 1e4 }).catch(() => {});
4125
+ currentTail?.kill().catch(() => {});
4126
+ };
4127
+ transport = (async () => {
4128
+ while (exitCode === null && !consumerStopped && !opts.signal?.aborted) {
4129
+ let buf = "";
4130
+ let handle = null;
4131
+ const onStdout = (data) => {
4132
+ buf += data;
4133
+ let nl;
4134
+ while ((nl = buf.indexOf(`
4135
+ `)) >= 0) {
4136
+ consumeLine(buf.slice(0, nl));
4137
+ buf = buf.slice(nl + 1);
4138
+ if (exitCode !== null) {
4139
+ handle?.kill().catch(() => {});
4140
+ return;
4141
+ }
4142
+ }
4143
+ };
4144
+ try {
4145
+ handle = await this.sandbox.commands.runBackground(`tail -c +${offset + 1} -f ${shellQuote(outPath)}`, { timeoutMs: 0, onStdout });
4146
+ } catch {}
4147
+ if (handle) {
4148
+ if (exitCode !== null)
4149
+ handle.kill().catch(() => {});
4150
+ currentTail = handle;
4151
+ await handle.wait().catch(() => {
4152
+ return;
4153
+ });
4154
+ currentTail = null;
4155
+ }
4156
+ if (exitCode !== null || consumerStopped || opts.signal?.aborted)
4157
+ break;
4158
+ if (durableRead) {
4159
+ try {
4160
+ const bytes = Buffer.from(await durableRead(outPath), "utf8");
4161
+ let rest = bytes.subarray(offset).toString("utf8");
4162
+ let nl;
4163
+ while (exitCode === null && (nl = rest.indexOf(`
4164
+ `)) >= 0) {
4165
+ consumeLine(rest.slice(0, nl));
4166
+ rest = rest.slice(nl + 1);
4167
+ }
4168
+ } catch {}
4169
+ }
4170
+ if (exitCode !== null)
4171
+ break;
4172
+ let alive = null;
4173
+ try {
4174
+ const probe = await this.sandbox.commands.run(`kill -0 ${pid} 2>/dev/null`, { timeoutMs: 1e4 });
4175
+ alive = probe.exitCode === 0;
4176
+ } catch {
4177
+ alive = null;
4178
+ }
4179
+ if (alive === false) {
4180
+ exitCode = JSONL_GUARD_NO_SENTINEL_EXIT;
4181
+ break;
4182
+ }
4183
+ console.warn(`[cli-agent] ${this.spec.kind} turn stream lost with the runner alive — re-tailing ${outPath} from byte ${offset}`);
4184
+ await new Promise((r) => setTimeout(r, TAIL_REATTACH_DELAY_MS));
4185
+ }
4186
+ if (exitCode !== null && exitCode !== 0 && durableRead) {
4187
+ try {
4188
+ exitStderr = (await durableRead(errPath)).slice(-2000);
4189
+ } catch {}
4190
+ }
4191
+ })().finally(() => lines.close());
4073
4192
  } else {
4074
- runPromise = this.sandbox.commands.run(guardedCmd, runOpts).then(settle, fail);
4193
+ let buf = "";
4194
+ const onStdout = (data) => {
4195
+ buf += data;
4196
+ let nl;
4197
+ while ((nl = buf.indexOf(`
4198
+ `)) >= 0) {
4199
+ const line = buf.slice(0, nl).trim();
4200
+ buf = buf.slice(nl + 1);
4201
+ if (line)
4202
+ lines.push(line);
4203
+ }
4204
+ };
4205
+ const runOpts = {
4206
+ ...this.options.cwd ? { cwd: this.options.cwd } : {},
4207
+ onStdout,
4208
+ timeoutMs: 0
4209
+ };
4210
+ transport = this.sandbox.commands.run(guardedCmd, runOpts).then((res) => {
4211
+ const tail = buf.trim();
4212
+ if (tail)
4213
+ lines.push(tail);
4214
+ exitCode = res.exitCode;
4215
+ exitStderr = (res.stderr ?? "").slice(-2000);
4216
+ }, (err) => {
4217
+ transportError = err;
4218
+ }).finally(() => lines.close());
4075
4219
  }
4076
- runPromise.catch(() => {});
4077
- const reap = () => {
4078
- if (!exited)
4079
- kill?.().catch(() => {});
4080
- };
4220
+ transport.catch(() => {});
4081
4221
  if (opts.signal) {
4082
4222
  if (opts.signal.aborted)
4083
4223
  reap();
@@ -4101,18 +4241,28 @@ class CliAgentRunner {
4101
4241
  yield msg;
4102
4242
  }
4103
4243
  }
4104
- const res = await runPromise;
4105
- if (res.exitCode !== 0 && !sawError) {
4106
- const tail = (res.stderr ?? "").slice(-2000);
4107
- yield { type: "error", text: `${this.spec.kind} exited with code ${res.exitCode}${tail ? `: ${tail}` : ""}`, timestamp: now4() };
4244
+ await transport;
4245
+ if (transportError)
4246
+ throw transportError;
4247
+ if (exitCode === null) {
4248
+ if (!sawError) {
4249
+ yield { type: "error", text: `${this.spec.kind} aborted — the in-sandbox runner was terminated`, timestamp: now4() };
4250
+ }
4251
+ return;
4252
+ }
4253
+ if (exitCode !== 0 && !sawError) {
4254
+ const detail = exitCode === JSONL_GUARD_NO_SENTINEL_EXIT ? " (the runner died without reporting an exit code)" : "";
4255
+ yield { type: "error", text: `${this.spec.kind} exited with code ${exitCode}${detail}${exitStderr ? `: ${exitStderr}` : ""}`, timestamp: now4() };
4108
4256
  return;
4109
4257
  }
4110
4258
  if (!sawError)
4111
4259
  yield { type: "done", sessionId: sessionId ?? "", timestamp: now4() };
4112
4260
  } finally {
4113
4261
  opts.signal?.removeEventListener("abort", reap);
4114
- reap();
4115
- this.sandbox.commands.run(`rm -f ${shellQuote(promptPath)} ${shellQuote(guardPath)}`, { timeoutMs: 1e4 }).catch(() => {});
4262
+ consumerStopped = true;
4263
+ if (exitCode === null)
4264
+ reap();
4265
+ this.sandbox.commands.run(`rm -f ${shellQuote(promptPath)} ${shellQuote(guardPath)} ${shellQuote(`${promptPath}.out`)} ${shellQuote(`${promptPath}.err`)} ${shellQuote(`${promptPath}.pid`)}`, { timeoutMs: 1e4 }).catch(() => {});
4116
4266
  }
4117
4267
  } catch (err) {
4118
4268
  yield { type: "error", text: formatError(err), timestamp: now4() };
@@ -4153,11 +4303,11 @@ var codexSpec = {
4153
4303
  "--skip-git-repo-check",
4154
4304
  "--dangerously-bypass-approvals-and-sandbox",
4155
4305
  ...model ? ["-m", shellQuote(model)] : [],
4156
- ...effort ? ["-c", `model_reasoning_effort=${effort === "max" ? "xhigh" : effort}`] : [],
4157
- ...cwd ? ["-C", shellQuote(cwd)] : []
4306
+ ...effort ? ["-c", `model_reasoning_effort=${effort === "max" ? "xhigh" : effort}`] : []
4158
4307
  ].join(" ");
4308
+ const cd = cwd ? `cd ${shellQuote(cwd)} && ` : "";
4159
4309
  const exec = sessionId ? `codex exec resume ${shellQuote(sessionId)} ${flags}` : `codex exec ${flags}`;
4160
- return `${exec} - < ${shellQuote(promptPath)}`;
4310
+ return `${cd}${exec} - < ${shellQuote(promptPath)}`;
4161
4311
  },
4162
4312
  extractSessionId: (p) => p.type === "thread.started" && typeof p.thread_id === "string" ? p.thread_id : undefined,
4163
4313
  mapEvent: (p) => {
@@ -4552,23 +4702,36 @@ function toE2bNetwork(policy) {
4552
4702
  }
4553
4703
 
4554
4704
  // src/sandbox/sizes.ts
4555
- var SANDBOX_VCPUS = {
4556
- "2vcpu-4gb": 2,
4557
- "4vcpu-8gb": 4,
4558
- "8vcpu-16gb": 8,
4559
- "32vcpu-64gb": 32
4705
+ var SANDBOX_MACHINES = {
4706
+ "1vcpu-2gb": { vcpus: 1, memoryMB: 2048 },
4707
+ "2vcpu-4gb": { vcpus: 2, memoryMB: 4096 },
4708
+ "4vcpu-8gb": { vcpus: 4, memoryMB: 8192 },
4709
+ "8vcpu-8gb": { vcpus: 8, memoryMB: 8192 },
4710
+ "8vcpu-16gb": { vcpus: 8, memoryMB: 16384 },
4711
+ "32vcpu-64gb": { vcpus: 32, memoryMB: 65536 }
4560
4712
  };
4713
+ var SANDBOX_SIZES = Object.keys(SANDBOX_MACHINES);
4714
+ var SANDBOX_VCPUS = Object.fromEntries(SANDBOX_SIZES.map((s) => [s, SANDBOX_MACHINES[s].vcpus]));
4561
4715
  var DEFAULT_SANDBOX_SIZE = "2vcpu-4gb";
4562
- var E2B_TEMPLATE_SIZES = [
4563
- "2vcpu-4gb",
4564
- "4vcpu-8gb"
4565
- ];
4716
+ var SESSION_DEFAULT_SANDBOX_SIZE = "4vcpu-8gb";
4717
+ var E2B_MAX_VCPUS = 8;
4718
+ var E2B_MAX_MEMORY_MB = 8192;
4719
+ var E2B_TEMPLATE_SIZES = SANDBOX_SIZES.filter((s) => SANDBOX_MACHINES[s].vcpus <= E2B_MAX_VCPUS && SANDBOX_MACHINES[s].memoryMB <= E2B_MAX_MEMORY_MB);
4566
4720
  function isE2bSupportedSize(size) {
4567
4721
  return E2B_TEMPLATE_SIZES.includes(size);
4568
4722
  }
4723
+ var VERCEL_MEMORY_MB_PER_VCPU = 2048;
4724
+ function isVercelSupportedSize(size) {
4725
+ const m = SANDBOX_MACHINES[size];
4726
+ return m.memoryMB === m.vcpus * VERCEL_MEMORY_MB_PER_VCPU;
4727
+ }
4569
4728
  function e2bMachineSpec(size) {
4570
- const cpuCount = SANDBOX_VCPUS[size];
4571
- return { cpuCount, memoryMB: cpuCount * 2048 };
4729
+ const m = SANDBOX_MACHINES[size];
4730
+ return { cpuCount: m.vcpus, memoryMB: m.memoryMB };
4731
+ }
4732
+ function sandboxSizeLabel(size) {
4733
+ const m = SANDBOX_MACHINES[size];
4734
+ return `${m.vcpus} vCPU · ${Math.round(m.memoryMB / 1024)} GB`;
4572
4735
  }
4573
4736
  function e2bBaseTemplate(size) {
4574
4737
  return `agent-compose-base-${size}`;
@@ -4785,7 +4948,7 @@ function makeE2bSandboxProvider(sb) {
4785
4948
  return { resumeHandle: sb.sandboxId };
4786
4949
  },
4787
4950
  async extendLifetime(ms) {
4788
- await sb.setTimeout(ms);
4951
+ await sb.setTimeout(Math.min(ms, e2bMaxSandboxMs()));
4789
4952
  },
4790
4953
  async updateNetworkPolicy(policy) {
4791
4954
  await sb.updateNetwork(toE2bNetwork(policy));
@@ -6439,38 +6602,49 @@ there is nothing to type, paste, or screenshot a credential from.
6439
6602
 
6440
6603
  ## Recording a demo — the desktop, captured to a video the human can play
6441
6604
 
6442
- "Record a demo of you using X" is a normal ask, and this machine does it:
6443
- start a screen recording, drive the app with \`xdotool\` exactly as in Computer
6444
- Use, stop the recording, and report the file. (For a LIVE view no recording is
6445
- needed — the session header's **Desktop** button already streams this display
6446
- to any teammate watching; a recording is the durable, replayable artifact.
6447
- Both modes exist; say so when it matters.)
6605
+ "Record a demo of you using X" is a normal ask, and this machine does it.
6606
+ (For a LIVE view no recording is needed — the session header's **Desktop**
6607
+ button already streams this display to any teammate watching; a recording is
6608
+ the durable, replayable artifact. Both modes exist; say so when it matters.)
6609
+
6610
+ **Use \`ac-record\` — the platform recorder is already on PATH** (cloud
6611
+ sessions; \`command -v ac-record\` to confirm on older machines):
6448
6612
 
6449
- **ffmpeg is NOT pre-installed** — install it first, once per machine:
6613
+ ac-record start # begins capturing the desktop (display :0)
6614
+ # ... drive the app with xdotool, screenshotting as you go ...
6615
+ ac-record stop # finishes + saves to recordings/ in your workspace
6616
+ ac-record status # one JSON line: {"recording":true,...}
6450
6617
 
6451
- sudo apt-get update -q && sudo apt-get install -y -q ffmpeg
6618
+ It records the whole display (with desktop audio when the machine has a
6619
+ PulseAudio monitor), enforces sane caps (5 min / 200 MB — start a fresh
6620
+ recording per scene rather than one long take), keeps the file playable even
6621
+ if the machine dies mid-take, and \`stop\` prints the saved path — the file
6622
+ lands ON THE DRIVE in \`recordings/\`, visible in Files and playable in the
6623
+ dashboard. A human watching the Desktop pane sees the recording indicator
6624
+ while you record.
6452
6625
 
6453
- (drop \`sudo\` if you are already root). Then the whole recipe:
6626
+ If \`ac-record\` is missing (older machine), record by hand.
6627
+ **ffmpeg IS pre-installed** on platform images (\`command -v ffmpeg\`; only
6628
+ if absent: \`sudo apt-get update -q && sudo apt-get install -y -q ffmpeg\`):
6454
6629
 
6455
6630
  DISPLAY=:0 ffmpeg -f x11grab \\
6456
6631
  -video_size "$(DISPLAY=:0 xdotool getdisplaygeometry | tr ' ' x)" \\
6457
6632
  -framerate 10 -i :0 -c:v libvpx -b:v 1M -deadline realtime -cpu-used 8 \\
6458
6633
  demo.webm &
6459
6634
  FFMPEG_PID=$!
6460
- # ... drive the app with xdotool, screenshotting as you go ...
6635
+ # ... drive the app with xdotool ...
6461
6636
  kill -INT "$FFMPEG_PID" && wait "$FFMPEG_PID"
6462
6637
 
6463
- The gotchas, each one earned:
6638
+ The hand-rolled gotchas, each one earned:
6464
6639
  - **Stop with SIGINT (\`kill -INT\`), never SIGKILL** — ffmpeg finalizes the
6465
6640
  file on SIGINT; a hard kill truncates the encode mid-write.
6466
- - **Record WebM (matroska-family), not MP4** — mp4 writes its moov atom at the
6467
- END, so a killed or crashed encode leaves an UNPLAYABLE file; webm stays
6468
- playable up to the last written frame and plays natively in the browser.
6469
- MP4's only edge is compatibility with some external players — transcode
6470
- afterwards if you truly need it, never record straight to it.
6641
+ - **Record WebM (matroska-family), not plain MP4** — mp4 writes its moov atom
6642
+ at the END, so a killed or crashed encode leaves an UNPLAYABLE file; webm
6643
+ stays playable up to the last written frame and plays natively in the
6644
+ browser. (\`ac-record\` sidesteps this with fragmented mp4.)
6471
6645
  - **\`-video_size\` must match the real screen** — x11grab does not default to
6472
6646
  it; read the geometry from \`xdotool getdisplaygeometry\` as above.
6473
- - **10–12 fps is right for a screen demo** — small files, legible UI motion;
6647
+ - **10–15 fps is right for a screen demo** — small files, legible UI motion;
6474
6648
  this is not video production.
6475
6649
  - **Write to the drive, not /tmp** — the recording must land in your working
6476
6650
  directory to persist and show up in Files; a file in /tmp dies with the
@@ -7546,6 +7720,7 @@ export {
7546
7720
  shellWords,
7547
7721
  serveStep,
7548
7722
  serializeDisplayMarker,
7723
+ sandboxSizeLabel,
7549
7724
  runWorkflowSteps,
7550
7725
  runWorkflowSingleStep,
7551
7726
  runWorkflow,
@@ -7579,6 +7754,7 @@ export {
7579
7754
  killSandboxById,
7580
7755
  killAllSandboxes,
7581
7756
  isWorkflow,
7757
+ isVercelSupportedSize,
7582
7758
  isPlausibleDrivePath,
7583
7759
  isPlatformE2bTemplateAlias,
7584
7760
  isE2bSupportedSize,
@@ -7645,6 +7821,7 @@ export {
7645
7821
  WorkflowError,
7646
7822
  Verdict,
7647
7823
  VercelRunner,
7824
+ VERCEL_MEMORY_MB_PER_VCPU,
7648
7825
  TMP_DIR,
7649
7826
  TABLE_MAX_ROWS,
7650
7827
  TABLE_MAX_COLUMNS,
@@ -7655,10 +7832,13 @@ export {
7655
7832
  STEP_RESULT_PREFIX,
7656
7833
  STEP_PAUSE_PREFIX,
7657
7834
  STEP_ENV,
7835
+ SESSION_DEFAULT_SANDBOX_SIZE,
7658
7836
  SDK_SPECIFIER_ALIASES,
7659
7837
  SDK_PACKAGE,
7660
7838
  SANDBOX_VCPUS,
7661
7839
  SANDBOX_UNAVAILABLE_PREFIX,
7840
+ SANDBOX_SIZES,
7841
+ SANDBOX_MACHINES,
7662
7842
  ReservedKeyError,
7663
7843
  RequestContext,
7664
7844
  RUNNER_COMMAND,
@@ -7675,6 +7855,8 @@ export {
7675
7855
  LATEST_VERSION,
7676
7856
  EngineError,
7677
7857
  E2B_TEMPLATE_SIZES,
7858
+ E2B_MAX_VCPUS,
7859
+ E2B_MAX_MEMORY_MB,
7678
7860
  E2B_DEVBOX_TEMPLATE,
7679
7861
  E2B_DEVBOX_SPEC,
7680
7862
  E2B_DEVBOX_RECIPE_VERSION,
@@ -25,6 +25,31 @@
25
25
  */
26
26
  import type { AgentMessage, ModelExecutionContract, RuntimeOptions, SandboxProvider, ToolCallGateResult } from "../index.js";
27
27
  import type { ProcessorContext, ToolCall } from "../processors/processor.js";
28
+ /** Backoff between tail re-attach attempts after a mid-turn stream fault
29
+ * (durable detached transport). Short: the runner is alive and producing,
30
+ * and each retry costs one exec; the executor's evidence machinery — not
31
+ * this loop — bounds a truly dead sandbox. Exported for tests. */
32
+ export declare const TAIL_REATTACH_DELAY_MS = 2000;
33
+ /** Provider deadline on the detached LAUNCH exec. The launch shell only
34
+ * truncates the durable files, forks the detached runner (stdio fully
35
+ * redirected — see the launch command), writes the pidfile, and echoes the
36
+ * pid — sub-second work, so 30s is generous headroom for a slow envd, not a
37
+ * budget the runner ever consumes. Exported for tests. */
38
+ export declare const LAUNCH_EXEC_TIMEOUT_MS = 30000;
39
+ /** How many times (and how spaced) a THROWN launch exec falls back to reading
40
+ * the durable pidfile before the fault is surfaced. A deadlined/canceled
41
+ * launch STREAM does not mean the launch failed — the detached tree may be
42
+ * up and working — so the pid is recovered over the envd HTTP file
43
+ * transport (immune to the command-stream fault) and the turn proceeds.
44
+ * Only "no pidfile after these attempts" is a real launch failure.
45
+ * Exported for tests. */
46
+ export declare const LAUNCH_PID_RECOVERY_ATTEMPTS = 3;
47
+ export declare const LAUNCH_PID_RECOVERY_DELAY_MS = 1000;
48
+ /** Explicit deadline on the `command -v` install probe. E2B's default command
49
+ * deadline (60s) throws the deadline_exceeded TimeoutError — every provider
50
+ * call in the turn path carries an explicit timeout so no SDK default can
51
+ * decide a turn's fate. Exported for tests. */
52
+ export declare const INSTALL_PROBE_TIMEOUT_MS = 30000;
28
53
  /** Single-quote a value for safe interpolation into a `sh -c` command line. */
29
54
  export declare function shellQuote(value: string): string;
30
55
  /**
@@ -0,0 +1,9 @@
1
+ /**
2
+ * codexSpec.buildCommand — the in-sandbox `codex exec --json` invocation for
3
+ * cloud codex sessions. Regression pin for the resume `-C` bug: the working
4
+ * directory rides a shell `cd` (like every other runtime), NOT codex's `-C`
5
+ * flag, because `-C` is accepted by `codex exec` but REJECTED by
6
+ * `codex exec resume` ("unexpected argument '-C'") — so a fresh turn worked
7
+ * and every follow-up died with exit code 2.
8
+ */
9
+ export {};