@promigence/sdk 0.1.0-beta.15 → 0.1.0-beta.17

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.
@@ -11,9 +11,11 @@
11
11
  */
12
12
  import { inspect, type InspectOptions } from "node:util";
13
13
  import { type CommandResult, type CommandRunOpts, type ForkOpts, type OptimizeFor, type SandboxOpts, type Tier } from "./core.js";
14
- import { PromigenceClient, type BackgroundExecRecord, type DirEntry, type DirListOpts, type ExtractResult, type FileInfo, type FileMatch, type Page, type PortRecord, type AutoPauseMode, type SandboxRecord, type SearchOpts, type SecretBinding, type SnapshotBuild, type VerifySpec } from "./client.js";
14
+ import { PromigenceClient, type BackgroundExecRecord, type DirEntry, type DirListOpts, type ExtractResult, type FileInfo, type FileMatch, type Page, type PortExposeOpts, type PortRecord, type AutoPauseMode, type SandboxRecord, type SearchOpts, type SecretBinding, type SnapshotBuild, type VerifySpec } from "./client.js";
15
15
  import { CommandHandle } from "./handles.js";
16
16
  import type { PtyExit, PtySession, PtySize } from "./pty.js";
17
+ /** `setTimeout`'s fix when the longer timeout does not fit under the run's spend cap. */
18
+ export declare const EXTEND_OVER_CAP_FIX: string;
17
19
  /**
18
20
  * `commands.run` options for a foreground command. The call resolves with the whole result when
19
21
  * the command ends; `onStdout` / `onStderr` also receive its output as it is written.
@@ -150,6 +152,8 @@ export interface SandboxCreateOpts extends SandboxOpts {
150
152
  start?: string | string[];
151
153
  tier?: Tier;
152
154
  alias?: string;
155
+ /** With `alias`: take the name from the snapshot that has it (refused without this). */
156
+ moveAlias?: boolean;
153
157
  /** Name of a stored secret holding the registry credentials. */
154
158
  registrySecret?: string;
155
159
  /** Bound on the first build of an image (default 15 min). Expiry cancels nothing. */
@@ -196,7 +200,7 @@ export interface SandboxCreateOpts extends SandboxOpts {
196
200
  * inputs) was built before, or 202 with a `build_id`. The **body**, not the status, decides: an
197
201
  * idempotency replay answers 202 with a document that may already be `verified`.
198
202
  */
199
- export declare function imageSnapshot(client: PromigenceClient, opts: Pick<SandboxCreateOpts, "image" | "setup" | "verify" | "start" | "tier" | "alias" | "registrySecret" | "buildTimeoutMs">, hooks?: {
203
+ export declare function imageSnapshot(client: PromigenceClient, opts: Pick<SandboxCreateOpts, "image" | "setup" | "verify" | "start" | "tier" | "alias" | "moveAlias" | "registrySecret" | "buildTimeoutMs">, hooks?: {
200
204
  /**
201
205
  * The `POST /snapshots` answer, before any wait: `build_price` (the most it can cost) and
202
206
  * whether it was `deduped` or `joined` (nothing new charged). An implicit build costs money, so a caller that prints prices prints this one too.
@@ -285,6 +289,7 @@ export declare class Sandbox {
285
289
  /**
286
290
  * Run `cmd` and resolve with its result. A non-zero exit throws `CommandExitError`; a command
287
291
  * killed at its own `timeoutMs` throws `CommandTimeoutError` (a `CommandExitError` too).
292
+ * With `throwOnExit: false` both resolve instead, and the result's `timedOut` tells them apart.
288
293
  */
289
294
  run(cmd: string, opts?: ForegroundRunOpts): Promise<CommandResult>;
290
295
  /** Background commands in this sandbox (running and ended, until killed with forget). */
@@ -312,9 +317,20 @@ export declare class Sandbox {
312
317
  resize(pid: number, size: PtySize): Promise<void>;
313
318
  kill(pid: number): Promise<boolean>;
314
319
  };
315
- /** Preview URLs for ports inside the sandbox (`getHost` gives the address alone). */
320
+ /**
321
+ * Preview URLs for ports inside the sandbox (`getHost` gives the address alone).
322
+ *
323
+ * Anyone holding a preview URL can open it; revoke it any time, and it ends with the sandbox.
324
+ * If your account's preview URLs aren't on, `expose` throws `PreviewsUnavailableError`; write to
325
+ * support@promigence.ai to have them turned on, and meanwhile reach the port with
326
+ * `promigence sandbox ssh <sid> -- -N -L <port>:localhost:<port>`.
327
+ */
316
328
  readonly ports: {
317
- expose(port: number): Promise<PortRecord>;
329
+ /**
330
+ * Publish `port`: `{ length: "long" }` for a longer, harder-to-guess URL (apps with sensitive
331
+ * data); omitted, the length this sandbox's project or organization chose (short by default).
332
+ */
333
+ expose(port: number, opts?: PortExposeOpts): Promise<PortRecord>;
318
334
  list(): Promise<PortRecord[]>;
319
335
  /** A token revokes that URL; a port number every URL for it. Returns the revoked tokens. */
320
336
  revoke(tokenOrPort: string | number): Promise<string[]>;
@@ -458,13 +474,16 @@ export declare class Sandbox {
458
474
  }>;
459
475
  isRunning(): Promise<boolean>;
460
476
  /**
461
- * `PATCH /sandboxes/:id` — extend this sandbox's timeout.
477
+ * `PATCH /sandboxes/:id` — set this sandbox's timeout, in milliseconds.
478
+ *
479
+ * The timeout is the sandbox's total lifetime, counted from its start, not from now:
480
+ * `setTimeout(600_000)` on a sandbox that has run 2 minutes leaves it 8 more.
462
481
  *
463
- * The timeout is the sandbox's total lifetime, not "from now", and the difference is billing:
464
- * commits *seconds* up front and `billable_seconds <= committed_seconds`, so an extension is a new
465
- * commitment that has to fit under the spend cap you set (the run's, or the org default). It throws
466
- * `SpendCapError` (409) rather than silently extending — which is the behaviour a spend
467
- * cap exists to produce, and the one difference here worth a caller knowing about.
482
+ * - Raising it commits the added seconds, which has to fit in what is left of the spend cap you set
483
+ * (the run's, or the org default): past it, it throws `SpendCapError` (409) rather than silently
484
+ * extending.
485
+ * - Lowering it goes down to what the sandbox has run plus 60 s, and no further
486
+ * (`InvalidArgumentError` below that). To stop it now, `kill()` it.
468
487
  */
469
488
  setTimeout(ms: number): Promise<void>;
470
489
  /**
@@ -476,8 +495,9 @@ export declare class Sandbox {
476
495
  * the window may be. Bring it back with `resume()`, which is the warm path.
477
496
  *
478
497
  * `mode: "activity"` is for an agent that runs inside the sandbox on its own: it also waits
479
- * until nothing inside has used CPU, moved a byte or held a connection open for the window
480
- * (`AutoPauseMode`). Omitted, the mode is left as it is (`"requests"` unless set).
498
+ * until, for the whole window, the sandbox has used under 5% of one core, sent and received no
499
+ * network byte and held no connection out open; disk writes do not count (`AutoPauseMode`).
500
+ * Omitted, the mode is left as it is (`"requests"` unless set).
481
501
  */
482
502
  setAutoPause(seconds: number, mode?: AutoPauseMode): Promise<void>;
483
503
  /** The idle rule in force: seconds before it pauses itself, `0` when it never does. */
@@ -499,7 +519,12 @@ export declare class Sandbox {
499
519
  * full `url` with its scheme.
500
520
  *
501
521
  * Async, because the address is a capability minted on demand rather than
502
- * a hostname derived from the id. An existing URL for the port is reused, not re-minted.
522
+ * a hostname derived from the id. An existing URL for the port is reused, not re-minted; a new
523
+ * one has the length this sandbox's project or organization chose (short by default).
524
+ *
525
+ * If your account's preview URLs aren't on, this throws `PreviewsUnavailableError`; write to
526
+ * support@promigence.ai to have them turned on, and meanwhile reach the port with
527
+ * `promigence sandbox ssh <sid> -- -N -L <port>:localhost:<port>`.
503
528
  */
504
529
  getHost(port: number): Promise<string>;
505
530
  /**
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Sandbox = exports.DEFAULT_SNAPSHOT = exports.DEFAULT_PTY_CMD = void 0;
3
+ exports.Sandbox = exports.DEFAULT_SNAPSHOT = exports.DEFAULT_PTY_CMD = exports.EXTEND_OVER_CAP_FIX = void 0;
4
4
  exports.shellQuote = shellQuote;
5
5
  exports.ptyCommand = ptyCommand;
6
6
  exports.toEntryInfo = toEntryInfo;
@@ -14,6 +14,8 @@ const client_js_1 = require("./client.js");
14
14
  const handles_js_1 = require("./handles.js");
15
15
  const command_errors_js_1 = require("./command-errors.js");
16
16
  const hint_js_1 = require("./hint.js");
17
+ exports.EXTEND_OVER_CAP_FIX = "Raising the timeout must fit in what is left of the run's spend cap, and this does not: start a new " +
18
+ "sandbox with the longer `timeoutMs` and a `spendCap` that covers it.";
17
19
  const EXEC_TIMEOUT_DEFAULT_MS = 15 * 60_000;
18
20
  function textSink(cb) {
19
21
  if (!cb)
@@ -47,6 +49,15 @@ function ptyCommand(opts) {
47
49
  parts.push(opts.cmd ?? exports.DEFAULT_PTY_CMD);
48
50
  return parts.join(" ");
49
51
  }
52
+ function allowList(hosts) {
53
+ if (!Array.isArray(hosts)) {
54
+ throw new core_js_1.InvalidArgumentError("allowHosts must be a list of hosts", 'Pass allowHosts: ["github.com"].');
55
+ }
56
+ if (!hosts.some((h) => typeof h !== "string" || h.trim() !== "")) {
57
+ throw new core_js_1.InvalidArgumentError("allowHosts names no host, so the allow-list is empty", 'Name each host the sandbox may reach: allowHosts: ["github.com"]. For no network pass allowInternetAccess: false; to reach anything leave allowHosts out.');
58
+ }
59
+ return hosts;
60
+ }
50
61
  function permString(mode) {
51
62
  const r = (m) => `${m & 4 ? "r" : "-"}${m & 2 ? "w" : "-"}${m & 1 ? "x" : "-"}`;
52
63
  return r((mode >> 6) & 7) + r((mode >> 3) & 7) + r(mode & 7);
@@ -99,6 +110,8 @@ async function imageSnapshot(client, opts, hooks = {}) {
99
110
  create.tier = opts.tier;
100
111
  if (opts.alias !== undefined)
101
112
  create.alias = opts.alias;
113
+ if (opts.moveAlias)
114
+ create.moveAlias = true;
102
115
  if (opts.registrySecret !== undefined)
103
116
  create.registrySecret = opts.registrySecret;
104
117
  let build = (await client.snapshots.create(create)) ?? {};
@@ -358,7 +371,7 @@ class Sandbox {
358
371
  },
359
372
  };
360
373
  this.ports = {
361
- expose: (port) => this.#client.sandboxes.ports.expose(this.sandboxId, port),
374
+ expose: (port, opts) => this.#client.sandboxes.ports.expose(this.sandboxId, port, opts),
362
375
  list: () => this.#client.sandboxes.ports.list(this.sandboxId),
363
376
  revoke: async (tokenOrPort) => (await this.#client.sandboxes.ports.revoke(this.sandboxId, tokenOrPort)).revoked,
364
377
  };
@@ -372,6 +385,9 @@ class Sandbox {
372
385
  return format === "bytes" ? bytes : new TextDecoder().decode(bytes);
373
386
  }),
374
387
  write: async (path, data, opts = {}) => {
388
+ if (typeof path !== "string") {
389
+ throw new core_js_1.InvalidArgumentError(`files.write takes one path and its data; got ${Array.isArray(path) ? "an array" : path === null ? "null" : typeof path} as the path`, "Write each file with its own call: for (const f of files) await sandbox.files.write(f.path, f.data).");
390
+ }
375
391
  let body;
376
392
  if (typeof data === "string" || data instanceof Uint8Array)
377
393
  body = data;
@@ -566,8 +582,8 @@ class Sandbox {
566
582
  start.timeoutMs = opts.timeoutMs;
567
583
  if (opts.allowInternetAccess === false)
568
584
  start.network = "none";
569
- if (opts.allowHosts?.length)
570
- start.network = { allow: opts.allowHosts };
585
+ if (opts.allowHosts !== undefined && opts.allowHosts !== null)
586
+ start.network = { allow: allowList(opts.allowHosts) };
571
587
  if (opts.metadata)
572
588
  start.labels = opts.metadata;
573
589
  if (opts.secrets?.length)
@@ -705,7 +721,15 @@ class Sandbox {
705
721
  return rec.state === "ready" || rec.state === "running";
706
722
  }
707
723
  async setTimeout(ms) {
708
- await this.#client.sandboxes.patch(this.sandboxId, { timeoutMs: ms });
724
+ try {
725
+ await this.#client.sandboxes.patch(this.sandboxId, { timeoutMs: ms });
726
+ }
727
+ catch (e) {
728
+ if (e instanceof core_js_1.SpendCapError && e.code === "cap_exceeded") {
729
+ Object.defineProperty(e, "fix", { value: exports.EXTEND_OVER_CAP_FIX, enumerable: true, configurable: true });
730
+ }
731
+ throw e;
732
+ }
709
733
  this.timeoutMs = ms;
710
734
  }
711
735
  async setAutoPause(seconds, mode) {
@@ -292,6 +292,7 @@ function start(init) {
292
292
  let rrAny = 0;
293
293
  let pumpTimer;
294
294
  let pumpSoon;
295
+ let turnsHeld = false;
295
296
  const freeNow = (l, now) => {
296
297
  for (let i = 0; i < l.length; i++) {
297
298
  const k = (rr + i) % l.length;
@@ -306,6 +307,8 @@ function start(init) {
306
307
  const pump = () => {
307
308
  pumpTimer = undefined;
308
309
  pumpSoon = undefined;
310
+ if (turnsHeld)
311
+ return;
309
312
  while (waiting.length > 0) {
310
313
  const l = live();
311
314
  if (l.length === 0) {
@@ -348,7 +351,7 @@ function start(init) {
348
351
  rrAny = (rrAny % l.length) + 1;
349
352
  return e;
350
353
  }
351
- if (waiting.length === 0) {
354
+ if (waiting.length === 0 && !turnsHeld) {
352
355
  const now = hrNow();
353
356
  const e = freeNow(l, now);
354
357
  if (e !== undefined) {
@@ -529,6 +532,12 @@ function start(init) {
529
532
  }
530
533
  else if (m?.t === "crash-for-test")
531
534
  setImmediate(() => { throw new Error("transport thread crash (test)"); });
535
+ else if (m?.t === "hold-turns-for-test")
536
+ turnsHeld = true;
537
+ else if (m?.t === "release-turns-for-test") {
538
+ turnsHeld = false;
539
+ pump();
540
+ }
532
541
  });
533
542
  void open();
534
543
  }
package/dist/client.d.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  import { inspect, type InspectOptions } from "node:util";
3
3
  import { PromigenceError, type CommandResult, type NetworkMode, type OptimizeFor, type Tier } from "./core.js";
4
4
  import { type Bundle } from "./bundle.js";
5
+ import { type CredentialStore } from "./credstore.js";
5
6
  import { type PtyExit, type PtySession } from "./pty.js";
6
7
  import { type SshTunnel } from "./ssh.js";
7
8
  import type * as M from "./models.gen.js";
@@ -177,6 +178,8 @@ export interface SandboxRef extends QueuedInfo {
177
178
  }
178
179
  /** How a grader's result is read. */
179
180
  export type ScoreMode = M.ScoreMode;
181
+ /** Which of a run's sandboxes are kept to reopen where they ended (`RunStartOpts.capture`). */
182
+ export type CaptureMode = M.CaptureMode;
180
183
  /**
181
184
  * Score the agent's work after the episode, in a clean copy of the snapshot with the agent's
182
185
  * changes applied. `holdout_cmd` is your own second test set; the report carries the gap
@@ -280,6 +283,10 @@ export interface ReportSandbox extends QueuedInfo {
280
283
  sandbox_id: string;
281
284
  state: SandboxState;
282
285
  attempt?: number;
286
+ /** `PROMIGENCE_INDEX`: its place in the run, from 0. Absent when the run's command set it itself. */
287
+ index?: number;
288
+ /** `PROMIGENCE_SEED`: the number its commands were given. Absent when the run's command set it itself. */
289
+ seed?: number;
283
290
  queued_ms?: number;
284
291
  ready_ms?: number;
285
292
  e2e_ms?: number;
@@ -319,11 +326,16 @@ export interface RunReport {
319
326
  snapshot_id?: string;
320
327
  alias?: string;
321
328
  labels?: Record<string, string>;
329
+ /**
330
+ * What the run asked for. `network` is `"allow"` for a run behind an allowlist, whose hosts are
331
+ * `allow` (a service before it said `"egress"` for one).
332
+ */
322
333
  requested?: {
323
334
  count: number;
324
335
  timeout_ms: number;
325
336
  verify_each: boolean;
326
- network: NetworkMode;
337
+ network: NetworkMode | "allow";
338
+ allow?: string[];
327
339
  allow_partial: boolean;
328
340
  secrets?: string[];
329
341
  grade?: GradeSpec;
@@ -386,6 +398,12 @@ export interface SecretRecord {
386
398
  raw?: "allowed" | "refused";
387
399
  /** Whether the value is kept out of the bound hosts' answers (present when `hosts` is). */
388
400
  scan_responses?: boolean;
401
+ /**
402
+ * On `secrets.set` only: things to know about the value just stored, one `code: sentence` each;
403
+ * absent when there are none. `api_key_value`: the value holds an API key of this service, which
404
+ * any command in a sandbox given this secret can use.
405
+ */
406
+ warnings?: string[];
389
407
  }
390
408
  /**
391
409
  * `secrets.set` options: bind the secret to hosts. Requests a sandbox sends to the host's address
@@ -437,13 +455,19 @@ export interface SnapshotBuild {
437
455
  repo?: string;
438
456
  commit?: string;
439
457
  verification?: Record<string, unknown>;
440
- /** A rejected build names the guard and the measured value; nothing was stored. */
458
+ /**
459
+ * A rejected build names the guard and the measured value; nothing was stored. `log_tail` is
460
+ * your build's own output: for a step of yours that failed (its start command, a setup step, its
461
+ * verify), that step's last lines. `fix`, when present, is what to change.
462
+ */
441
463
  error?: {
442
464
  code?: string;
443
465
  guard?: string;
444
466
  message?: string;
445
467
  values?: number[];
446
468
  stored?: boolean;
469
+ log_tail?: string[];
470
+ fix?: string;
447
471
  } | null;
448
472
  /** Set when an identical build already existed. */
449
473
  deduped?: boolean;
@@ -729,8 +753,25 @@ export interface OrgRecord {
729
753
  created_at: string;
730
754
  /** Days command output is kept after a sandbox ends; `null` = the default, 7. */
731
755
  output_retention_days?: number | null;
732
- /** Keep a failed sandbox to reopen where it broke, when a run does not say; absent or `null` = the default. */
756
+ /**
757
+ * This organization's choice: keep a failed sandbox to reopen where it broke, when a run does not say (`capture`);
758
+ * absent or `null` = not chosen, so the default applies: not kept unless the organization or project chose to keep
759
+ * them. What applies is `failure_captures_effective`.
760
+ */
733
761
  failure_captures?: boolean | null;
762
+ /** The length of new preview URLs when a request does not say; absent or `null` = the default, short. */
763
+ preview_link_length?: PreviewLinkLength | null;
764
+ /** What applies to a failed run that does not say: kept or not, and whose choice decided it. */
765
+ failure_captures_effective?: CapturesEffective;
766
+ }
767
+ /**
768
+ * What applies to a failed run's sandboxes when the run does not say (`capture`): `kept`, and `from`, whose choice
769
+ * decided it: `project`, `org`, or `default` (neither chose: not kept unless the organization or project chose to
770
+ * keep them). A run's own `capture` is followed either way.
771
+ */
772
+ export interface CapturesEffective {
773
+ kept: boolean;
774
+ from: "project" | "org" | "default" | (string & {});
734
775
  }
735
776
  export interface ProjectRecord {
736
777
  project_id: string;
@@ -742,8 +783,16 @@ export interface ProjectRecord {
742
783
  monthly_cap_usd?: number | null;
743
784
  /** Days this project's command output is kept after a sandbox ends; `null` = the organization's. */
744
785
  output_retention_days?: number | null;
745
- /** Keep this project's failed sandboxes to reopen, when a run does not say; absent or `null` = the organization's. */
786
+ /**
787
+ * This project's choice: keep its failed sandboxes to reopen, when a run does not say (`capture`); absent or `null` =
788
+ * the organization's choice, and where neither chose, the default: not kept unless the organization or project
789
+ * chose to keep them. What applies is `failure_captures_effective`.
790
+ */
746
791
  failure_captures?: boolean | null;
792
+ /** The length of this project's new preview URLs when a request does not say; absent or `null` = the organization's. */
793
+ preview_link_length?: PreviewLinkLength | null;
794
+ /** What applies to its failed runs that do not say: kept or not, and whose choice decided it. */
795
+ failure_captures_effective?: CapturesEffective;
747
796
  created_at: string;
748
797
  }
749
798
  /** The longest command output may be kept after a sandbox ends, in days. */
@@ -1153,8 +1202,9 @@ export interface SandboxPatchResponse {
1153
1202
  /**
1154
1203
  * What the idle rule counts as idle. `"requests"` (the default): nobody has called the sandbox
1155
1204
  * for the window and no command, terminal or SSH session is open. `"activity"`: all of that, and
1156
- * for the whole window nothing inside used CPU or sent or received a byte, and no network
1157
- * connection to the outside is open — for an agent that runs inside the sandbox on its own.
1205
+ * for the whole window the sandbox used under 5% of one core, sent and received no network byte
1206
+ * (disk writes do not count), and no network connection to the outside is open — for an agent
1207
+ * that runs inside the sandbox on its own.
1158
1208
  */
1159
1209
  export type AutoPauseMode = M.AutoPauseMode;
1160
1210
  /** `POST /snapshots/:id/verify` — re-run the recorded `verify` in a fresh fork. */
@@ -1366,12 +1416,33 @@ export interface SshRevoked {
1366
1416
  export interface PortRecord {
1367
1417
  sandbox_id: string;
1368
1418
  port: number;
1369
- /** 256-bit capability; anyone with the URL reaches the port. */
1419
+ /** The URL's own name: anyone with the URL reaches the port. Pass it to `revoke` to take this one URL down. */
1370
1420
  token: string;
1371
1421
  url: string;
1372
1422
  /** The same token as a DNS label, for a wildcard-DNS deployment (`<label>.<suffix>`). */
1373
1423
  host_url_label?: string;
1374
1424
  created_unix_ns?: number;
1425
+ /** When the URL stops working on its own (`ttl_s`); `null` for one that works until revoked. */
1426
+ expires_unix_ns?: number | null;
1427
+ /**
1428
+ * `"short"` (`https://brave-otter-48213.<domain>/`, the default) or `"long"` (a longer,
1429
+ * harder-to-guess URL, for apps with sensitive data).
1430
+ */
1431
+ length?: PreviewLinkLength;
1432
+ }
1433
+ /**
1434
+ * How long a preview URL is: `"short"` (the default, `https://brave-otter-48213.<domain>/`) or
1435
+ * `"long"` (a longer, harder-to-guess URL, for apps with sensitive data). Custom domains are
1436
+ * coming soon.
1437
+ */
1438
+ export type PreviewLinkLength = "short" | "long";
1439
+ /** Options of `ports.expose`. */
1440
+ export interface PortExposeOpts {
1441
+ /**
1442
+ * `"short"` or `"long"`. Omitted: the length the sandbox's project chose, else its
1443
+ * organization's, else short.
1444
+ */
1445
+ length?: PreviewLinkLength;
1375
1446
  }
1376
1447
  /** A key named without its secret (`/me`'s `key`, the key lists). */
1377
1448
  export interface KeyRef {
@@ -1403,8 +1474,31 @@ export interface MeResponse {
1403
1474
  max_concurrency?: number;
1404
1475
  max_session_s?: number | null;
1405
1476
  max_sandbox_vcpus?: number | null;
1477
+ /** Everything stored together, in GB: snapshots, paused sandboxes and saved failures; `null` for no limit. */
1478
+ max_storage_gb?: number | null;
1479
+ /**
1480
+ * The storage saved failures may use, in GB: when a new one would pass it, the oldest are removed to make room;
1481
+ * `null` for no limit.
1482
+ */
1483
+ max_capture_storage_gb?: number | null;
1484
+ /** Snapshots that may be kept; `null` for no limit. */
1485
+ max_snapshots?: number | null;
1486
+ /** Paused sandboxes that may be kept; `null` for no limit. */
1487
+ max_paused?: number | null;
1406
1488
  [extra: string]: number | null | undefined;
1407
1489
  };
1490
+ /**
1491
+ * What the organisation stores now, in bytes (a GB of `limits` is 2^30 bytes): everything together (`used_bytes`,
1492
+ * against `limits.max_storage_gb`) and its saved failures (`captures_bytes`, against
1493
+ * `limits.max_capture_storage_gb`), with the counts. Absent from a server before it.
1494
+ */
1495
+ storage?: {
1496
+ used_bytes: number;
1497
+ captures_bytes: number;
1498
+ snapshots: number;
1499
+ paused: number;
1500
+ captures: number;
1501
+ } | null;
1408
1502
  /**
1409
1503
  * Sandboxes holding capacity now (queued, starting, ready or running; paused ones hold none)
1410
1504
  * against the limit.
@@ -1518,6 +1612,12 @@ export interface PromigenceClientOpts {
1518
1612
  */
1519
1613
  onTiming?: (timing: Timing) => void;
1520
1614
  }
1615
+ /** Where a client's key came from: the option, PROMIGENCE_API_KEY (`env`), the stored sign-in, or a fallback. */
1616
+ export interface KeyOrigin {
1617
+ source: "option" | "env" | "stored" | "fallback";
1618
+ /** The stored sign-in's store, for `stored`. */
1619
+ store?: CredentialStore;
1620
+ }
1521
1621
  /** `GET /sandboxes/:id/network`. */
1522
1622
  export interface SandboxNetwork {
1523
1623
  sandbox_id: string;
@@ -1578,6 +1678,12 @@ export interface RunStartOpts {
1578
1678
  verifyEach?: boolean;
1579
1679
  network?: NetworkMode;
1580
1680
  optimizeFor?: OptimizeFor;
1681
+ /**
1682
+ * Which of this run's sandboxes are kept to reopen where they ended (`runs.open`): `on_failure`
1683
+ * keeps one whose command failed, `off` keeps none, `always` keeps every one. Left out, the
1684
+ * default applies.
1685
+ */
1686
+ capture?: CaptureMode;
1581
1687
  /**
1582
1688
  * Secrets for every sandbox of this run. An `exec` is no longer required: a run that names
1583
1689
  * its secrets here gives them to every command run against every one of its sandboxes,
@@ -1641,9 +1747,9 @@ export interface ExecOpts {
1641
1747
  */
1642
1748
  secrets?: SecretBinding[];
1643
1749
  /**
1644
- * Cancel an in-flight exec, streamed or not: the client stops waiting and the request is closed,
1645
- * which ends the command in the sandbox. A buffered exec then throws `TimeoutError` ("aborted by
1646
- * the caller's signal"); a streamed one rejects with the signal's `AbortError`.
1750
+ * Cancel an in-flight exec, streamed or not: the client stops waiting and tells the service to
1751
+ * stop the command in the sandbox. A buffered exec then throws `TimeoutError` ("aborted by the
1752
+ * caller's signal"); a streamed one rejects with the signal's `AbortError`.
1647
1753
  */
1648
1754
  signal?: AbortSignal;
1649
1755
  /**
@@ -1756,6 +1862,11 @@ export interface SnapshotCreateOpts {
1756
1862
  */
1757
1863
  setupUser?: string;
1758
1864
  alias?: string;
1865
+ /**
1866
+ * With `alias`: take the name from the snapshot that has it, which stays, unnamed. Without it, an
1867
+ * alias another snapshot has is refused (`usage`, 400) and nothing is built.
1868
+ */
1869
+ moveAlias?: boolean;
1759
1870
  /**
1760
1871
  * A DEFAULT grader for this snapshot: a run on it with no `grade` of its own uses this one.
1761
1872
  * The same shape as `runs.start({ grade })`.
@@ -1841,11 +1952,12 @@ export declare function credentialsExposure(path: string): string | undefined;
1841
1952
  * `credentialsTrustProblem`).
1842
1953
  */
1843
1954
  export declare function readCredentials(env?: NodeJS.ProcessEnv): Credentials;
1844
- export declare function apiUrlFromCompatUrl(url: string | undefined): string | undefined;
1845
- export declare function apiUrlFromCompatDomain(domain: string | undefined): string | undefined;
1846
- export declare function compatApiKey(key: string | undefined): string | undefined;
1847
1955
  /** Which variable a key came from, for `promigence doctor`. */
1848
1956
  export declare function keyOrigin(env?: NodeJS.ProcessEnv): string | undefined;
1957
+ /** A request with no key found anywhere: none passed, none in the environment, none stored. */
1958
+ export declare const NO_KEY_MESSAGE = "no API key: none was given, PROMIGENCE_API_KEY is not set, and none is stored";
1959
+ /** What a caller of this SDK does about it. */
1960
+ export declare const NO_KEY_FIX = "Pass { apiKey } or set PROMIGENCE_API_KEY. No key yet: `promigence login` signs you up or in and stores one.";
1849
1961
  /** `name@sha256:<64 hex>`, optionally with a tag before the `@`: a reference already pinned. */
1850
1962
  export declare const IMAGE_DIGEST_RE: RegExp;
1851
1963
  /**
@@ -2031,6 +2143,9 @@ export declare class PromigenceClient {
2031
2143
  private guardDispatcher;
2032
2144
  private dispatcherFor;
2033
2145
  private againKeyed;
2146
+ private heldExec;
2147
+ private reattachBuffered;
2148
+ private cancelHeldExec;
2034
2149
  request<T>(opts: RequestOpts): Promise<T>;
2035
2150
  /**
2036
2151
  * A request whose body — in either direction — is **bytes, not JSON** (`files get|put`).
@@ -2228,15 +2343,29 @@ export declare class PromigenceClient {
2228
2343
  /** `PATCH /orgs/:org/projects/:project {name}` — rename it (the `default` project keeps its name). */
2229
2344
  rename: (org: string, project: string, name: string) => Promise<ProjectRecord>;
2230
2345
  /**
2231
- * `PATCH /orgs/:org/projects/:project` — rename it, set how long its command output is kept, or
2232
- * whether its failed sandboxes are kept to reopen (that one on its own). The settings need an owner signed in.
2346
+ * `PATCH /orgs/:org/projects/:project` — rename it, set how long its command output is kept,
2347
+ * whether its failed sandboxes are kept to reopen (on its own; `null` = the organization's choice, and
2348
+ * where neither chose, not kept unless the organization or project chose to keep them), or how long
2349
+ * its new preview URLs are (on its own). The settings need an owner signed in.
2233
2350
  */
2234
2351
  update: (org: string, project: string, update: {
2235
2352
  name?: string;
2236
2353
  outputRetentionDays?: number | null;
2237
2354
  failureCaptures?: boolean | null;
2355
+ previewLinkLength?: PreviewLinkLength | null;
2238
2356
  }) => Promise<ProjectRecord>;
2239
2357
  };
2358
+ /**
2359
+ * `PATCH /orgs/:org` — rename it, set how long its command output is kept, whether its failed
2360
+ * sandboxes are kept to reopen, or how long its new preview URLs are (those two each on their
2361
+ * own). The settings need an owner signed in.
2362
+ */
2363
+ update: (org: string, update: {
2364
+ name?: string;
2365
+ outputRetentionDays?: number | null;
2366
+ failureCaptures?: boolean | null;
2367
+ previewLinkLength?: PreviewLinkLength | null;
2368
+ }) => Promise<OrgRecord>;
2240
2369
  };
2241
2370
  private group_orgs;
2242
2371
  readonly credits: {
@@ -2410,7 +2539,13 @@ export declare class PromigenceClient {
2410
2539
  */
2411
2540
  private waitLoop;
2412
2541
  readonly sandboxes: {
2413
- /** `POST /sandboxes/:id/execs` — foreground exec, inline output up to `maxOutputBytes`. */
2542
+ /**
2543
+ * `POST /sandboxes/:id/execs` — foreground exec, inline output up to `maxOutputBytes`.
2544
+ *
2545
+ * If the connection drops while the command runs, the SDK picks the command up again where the
2546
+ * service keeps it and returns its own answer; when it cannot, it throws `ExecLostError`. The
2547
+ * command is never sent a second time.
2548
+ */
2414
2549
  exec: (sandboxId: string, opts: ExecOpts) => Promise<ExecResponse>;
2415
2550
  /**
2416
2551
  * `POST /sandboxes/:id/execs` with `Accept: text/event-stream`.
@@ -2422,6 +2557,10 @@ export declare class PromigenceClient {
2422
2557
  *
2423
2558
  * Uses `fetch`'s streaming body rather than `res.text()`, which is what `request()` does and
2424
2559
  * why it cannot be reused here: `await res.text()` is precisely the buffering this removes.
2560
+ *
2561
+ * If the connection drops while the command runs, the SDK picks the command up again where the
2562
+ * service keeps it, and the stream goes on where it left off; when it cannot, it throws
2563
+ * `ExecLostError`. The command is never sent a second time.
2425
2564
  */
2426
2565
  execStream: (sandboxId: string, opts: ExecOpts, on?: {
2427
2566
  stdout?: (chunk: Uint8Array) => void;
@@ -2510,9 +2649,18 @@ export declare class PromigenceClient {
2510
2649
  /**
2511
2650
  * Preview URLs for ports inside the sandbox. A URL is a capability — anyone holding it reaches
2512
2651
  * that port — and dies with `revoke`, with a pause, or with the sandbox.
2652
+ *
2653
+ * If your account's preview URLs aren't on, `expose` throws `PreviewsUnavailableError`; write to
2654
+ * support@promigence.ai to have them turned on, and meanwhile reach the port with
2655
+ * `promigence sandbox ssh <sid> -- -N -L <port>:localhost:<port>`.
2513
2656
  */
2514
2657
  ports: {
2515
- expose: (sandboxId: string, port: number) => Promise<PortRecord>;
2658
+ /**
2659
+ * Publish a port: `{ length: "long" }` for a longer, harder-to-guess URL (apps with
2660
+ * sensitive data); omitted, the length the sandbox's project or organization chose
2661
+ * (short by default).
2662
+ */
2663
+ expose: (sandboxId: string, port: number, opts?: PortExposeOpts) => Promise<PortRecord>;
2516
2664
  list: (sandboxId: string) => Promise<PortRecord[]>;
2517
2665
  /** A token revokes that URL; a port number revokes every URL for that port. */
2518
2666
  revoke: (sandboxId: string, tokenOrPort: string | number) => Promise<{
@@ -3049,8 +3197,14 @@ export declare class PromigenceClient {
3049
3197
  alias?: string | null;
3050
3198
  deleted: boolean;
3051
3199
  }>;
3052
- /** `PUT /snapshots/:id/alias` — the sidecar's one mutable field. */
3053
- alias: (idOrAlias: string, alias: string | null) => Promise<SnapshotRecord>;
3200
+ /**
3201
+ * `PUT /snapshots/:id/alias` — the sidecar's one mutable field. A name another snapshot
3202
+ * has is refused (`usage`, 400) unless `moveAlias: true`, which takes it; that one stays,
3203
+ * unnamed.
3204
+ */
3205
+ alias: (idOrAlias: string, alias: string | null, opts?: {
3206
+ moveAlias?: boolean;
3207
+ }) => Promise<SnapshotRecord>;
3054
3208
  /**
3055
3209
  * Wait for a build to leave `running`, bounded by `waitTimeoutMs` (default 100 s).
3056
3210
  *