@agent-relay/cloud 12.0.0-rc.0 → 12.1.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.
@@ -1,8 +1,16 @@
1
+ /**
2
+ * Cloud may hand back the gateway route owned by a provisioned sandbox. This
3
+ * is deliberately an exact-origin allowlist: a route is control-plane input,
4
+ * not a caller-controlled SDK override.
5
+ */
6
+ export declare const CANONICAL_RELAYCAST_ORIGIN = "https://cast.agentrelay.com";
7
+ export declare const AGENT37_RELAYCAST_ORIGIN = "https://agent37-cast.agentrelay.com";
1
8
  export type CloudFleetSandboxRequestOptions = {
2
9
  apiUrl?: string;
3
10
  signal?: AbortSignal;
4
11
  timeoutMs?: number;
5
12
  };
13
+ export type CloudFleetSandboxProviderId = 'daytona' | 'e2b' | 'vercel' | 'freestyle' | 'agent37' | 'microsandbox';
6
14
  /**
7
15
  * Carries every safe identifier Cloud returned when provisioning failed after
8
16
  * the request may have created a billable sandbox.
@@ -11,11 +19,16 @@ export declare class CloudFleetSandboxProvisionError extends Error {
11
19
  readonly cloudWorkspaceId?: string;
12
20
  readonly sandboxId?: string;
13
21
  readonly nodeName?: string;
22
+ readonly providerId?: CloudFleetSandboxProviderId;
23
+ /** A 2xx response proved this exact caller-owned sandbox was provisioned. */
24
+ readonly confirmedProvisioned: boolean;
14
25
  readonly outcomeUnknown: boolean;
15
26
  constructor(message: string, identity?: {
16
27
  cloudWorkspaceId?: string;
17
28
  sandboxId?: string;
18
29
  nodeName?: string;
30
+ providerId?: CloudFleetSandboxProviderId;
31
+ confirmedProvisioned?: boolean;
19
32
  outcomeUnknown?: boolean;
20
33
  cause?: unknown;
21
34
  });
@@ -23,23 +36,56 @@ export declare class CloudFleetSandboxProvisionError extends Error {
23
36
  export type EnsureCloudFleetSandboxInput = {
24
37
  /** Cloud UUID or unified rw_* workspace id. */
25
38
  workspaceId: string;
39
+ /** Caller-declared one-time Cloud identity used to resume a cut-off provision. */
40
+ sandboxId?: string;
26
41
  name?: string;
27
42
  requiredCapability: string;
28
43
  maxAgents?: number;
29
44
  mountRelayfile?: boolean;
45
+ /**
46
+ * Relayfile directory subtrees to materialize in the sandbox. Each path
47
+ * must use the explicit `/path/**` subtree form accepted by Cloud.
48
+ */
49
+ relayfilePaths?: readonly string[];
30
50
  forceProvision?: boolean;
51
+ /** Constrain provisioning to a provider that Cloud has enabled for routing. */
52
+ providerId?: CloudFleetSandboxProviderId;
53
+ /** Provider-neutral semantics; Cloud owns the provider decision. */
54
+ workloadProfile?: CloudFleetSandboxWorkloadProfile;
31
55
  waitTimeoutMs?: number;
56
+ /**
57
+ * Repositories to clone into `/srv/agent-workforce/<name>` inside the
58
+ * provisioned sandbox. Each entry is a bare `owner/name`; cloud validates
59
+ * the shape on the wire before the sandbox script ever sees it.
60
+ *
61
+ * Required for the factory-cloud dispatch path so its worker_cwd
62
+ * (`/srv/agent-workforce/<repo>`) is resolvable on the JIT node. Cloud
63
+ * PR #3212 implements the ensure-side; this helper just plumbs it through.
64
+ */
65
+ repos?: readonly string[];
32
66
  };
33
- export type CloudFleetSandboxReady = {
67
+ export type CloudFleetSandboxWorkloadProfile = 'standard' | 'long-running-agent' | 'standard-long-running-agent';
68
+ type CloudFleetSandboxReadyBase = {
34
69
  outcome: 'provisioned';
35
70
  cloudWorkspaceId: string;
36
71
  nodeId: string;
37
72
  nodeName: string;
38
73
  sandboxId: string;
74
+ providerSandboxId?: string;
39
75
  relayWorkspaceId: string;
76
+ /** Closed server-owned Relaycast contract when Cloud returned one. Required for Agent37. */
77
+ relaycastTarget?: CloudFleetRelaycastTarget;
40
78
  relayfileMounted: boolean;
41
79
  relayfileMountPath?: string;
80
+ providerId?: CloudFleetSandboxProviderId;
42
81
  };
82
+ /** Daytona responses always carry the independently attested provider UUID. */
83
+ export type CloudFleetSandboxReady = (CloudFleetSandboxReadyBase & {
84
+ providerId: 'daytona';
85
+ providerSandboxId: string;
86
+ }) | (CloudFleetSandboxReadyBase & {
87
+ providerId?: Exclude<CloudFleetSandboxProviderId, 'daytona'>;
88
+ });
43
89
  export type CloudFleetSandboxReused = {
44
90
  outcome: 'reused';
45
91
  cloudWorkspaceId: string;
@@ -48,22 +94,46 @@ export type CloudFleetSandboxReused = {
48
94
  status: string;
49
95
  activeAgents: number | null;
50
96
  maxAgents: number | null;
97
+ providerId?: CloudFleetSandboxProviderId;
98
+ /** Closed server-owned Relaycast contract when Cloud returned one. Required for Agent37. */
99
+ relaycastTarget?: CloudFleetRelaycastTarget;
51
100
  };
52
- export type CloudFleetSandboxProvisioningTimeout = {
101
+ type CloudFleetSandboxProvisioningTimeoutBase = {
53
102
  outcome: 'provisioning_timeout';
54
103
  cloudWorkspaceId: string;
55
104
  sandboxId: string;
105
+ providerSandboxId?: string;
56
106
  relayWorkspaceId: string;
107
+ relaycastTarget?: CloudFleetRelaycastTarget;
57
108
  nodeName: string;
58
109
  waitedMs: number;
110
+ providerId?: CloudFleetSandboxProviderId;
59
111
  };
112
+ /** Daytona timeout responses also prove the provider UUID before they are surfaced. */
113
+ export type CloudFleetSandboxProvisioningTimeout = (CloudFleetSandboxProvisioningTimeoutBase & {
114
+ providerId: 'daytona';
115
+ providerSandboxId: string;
116
+ }) | (CloudFleetSandboxProvisioningTimeoutBase & {
117
+ providerId?: Exclude<CloudFleetSandboxProviderId, 'daytona'>;
118
+ });
60
119
  export type EnsureCloudFleetSandboxResult = CloudFleetSandboxReady | CloudFleetSandboxReused | CloudFleetSandboxProvisioningTimeout;
61
120
  export type DeleteCloudFleetSandboxInput = {
62
121
  cloudWorkspaceId: string;
63
122
  sandboxId: string;
123
+ providerId?: CloudFleetSandboxProviderId;
124
+ };
125
+ export type CloudFleetRelaycastRoute = 'canonical' | 'agent37-isolated';
126
+ export type CloudFleetRelaycastTarget = {
127
+ route: CloudFleetRelaycastRoute;
128
+ baseUrl: string;
129
+ workspaceId: string;
130
+ relaycastApiKey: string;
64
131
  };
132
+ /** Validate Cloud's closed Relaycast route, identity, and scoped credential contract. */
133
+ export declare function normalizeRelaycastTarget(value: unknown): CloudFleetRelaycastTarget;
65
134
  /** Resolve a Relay workspace in Cloud, provision/reuse a node, and wait for readiness. */
66
135
  export declare function ensureCloudFleetSandbox(input: EnsureCloudFleetSandboxInput, options?: CloudFleetSandboxRequestOptions): Promise<EnsureCloudFleetSandboxResult>;
67
- /** Best-effort-safe deletion for a Cloud-owned Daytona fleet sandbox. */
136
+ /** Best-effort-safe deletion for a Cloud-owned fleet sandbox. */
68
137
  export declare function deleteCloudFleetSandbox(input: DeleteCloudFleetSandboxInput, options?: CloudFleetSandboxRequestOptions): Promise<void>;
138
+ export {};
69
139
  //# sourceMappingURL=fleet-sandbox.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fleet-sandbox.d.ts","sourceRoot":"","sources":["../src/fleet-sandbox.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,+BAA+B,GAAG;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;GAGG;AACH,qBAAa,+BAAgC,SAAQ,KAAK;IACxD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;gBAG/B,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE;QACR,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,KAAK,CAAC,EAAE,OAAO,CAAC;KACZ;CAST;AAED,MAAM,MAAM,4BAA4B,GAAG;IACzC,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kBAAkB,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,aAAa,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,OAAO,EAAE,QAAQ,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG;IACjD,OAAO,EAAE,sBAAsB,CAAC;IAChC,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GACrC,sBAAsB,GACtB,uBAAuB,GACvB,oCAAoC,CAAC;AAEzC,MAAM,MAAM,4BAA4B,GAAG;IACzC,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAgJF,0FAA0F;AAC1F,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,4BAA4B,EACnC,OAAO,GAAE,+BAAoC,GAC5C,OAAO,CAAC,6BAA6B,CAAC,CA+ExC;AAED,yEAAyE;AACzE,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,4BAA4B,EACnC,OAAO,GAAE,+BAAoC,GAC5C,OAAO,CAAC,IAAI,CAAC,CAsBf"}
1
+ {"version":3,"file":"fleet-sandbox.d.ts","sourceRoot":"","sources":["../src/fleet-sandbox.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,23 @@ import { authorizedApiFetch, ensureCloudSession } from './auth.js';
2
2
  import { redactCredentialValues } from './redact.js';
3
3
  import { defaultApiUrl } from './types.js';
4
4
  const CLOUD_WORKSPACE_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
5
- const DEFAULT_ENSURE_TIMEOUT_MS = 120_000;
5
+ const CLOUD_SANDBOX_ID_PATTERN = /^sbx_[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
6
+ const DAYTONA_PROVIDER_SANDBOX_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
7
+ /**
8
+ * Cloud may hand back the gateway route owned by a provisioned sandbox. This
9
+ * is deliberately an exact-origin allowlist: a route is control-plane input,
10
+ * not a caller-controlled SDK override.
11
+ */
12
+ export const CANONICAL_RELAYCAST_ORIGIN = 'https://cast.agentrelay.com';
13
+ export const AGENT37_RELAYCAST_ORIGIN = 'https://agent37-cast.agentrelay.com';
14
+ const TRUSTED_RELAYCAST_ORIGINS = new Set([CANONICAL_RELAYCAST_ORIGIN, AGENT37_RELAYCAST_ORIGIN]);
15
+ const DEFAULT_RESOLUTION_TIMEOUT_MS = 120_000;
16
+ // Mounted provisioning can spend up to 240s completing the initial Relayfile
17
+ // sync, then up to 90s waiting for the enrolled node to report ready. Leave a
18
+ // bounded margin for Daytona creation and credential setup so the client does
19
+ // not abandon a successful server-side request without receiving its sandbox
20
+ // identity (which prevents the CLI from cleaning it up safely).
21
+ const DEFAULT_ENSURE_TIMEOUT_MS = 480_000;
6
22
  const DEFAULT_DELETE_TIMEOUT_MS = 30_000;
7
23
  /**
8
24
  * Carries every safe identifier Cloud returned when provisioning failed after
@@ -12,6 +28,9 @@ export class CloudFleetSandboxProvisionError extends Error {
12
28
  cloudWorkspaceId;
13
29
  sandboxId;
14
30
  nodeName;
31
+ providerId;
32
+ /** A 2xx response proved this exact caller-owned sandbox was provisioned. */
33
+ confirmedProvisioned;
15
34
  outcomeUnknown;
16
35
  constructor(message, identity = {}) {
17
36
  super(message, identity.cause === undefined ? undefined : { cause: identity.cause });
@@ -19,9 +38,21 @@ export class CloudFleetSandboxProvisionError extends Error {
19
38
  this.cloudWorkspaceId = identity.cloudWorkspaceId;
20
39
  this.sandboxId = identity.sandboxId;
21
40
  this.nodeName = identity.nodeName;
22
- this.outcomeUnknown = identity.outcomeUnknown === true;
41
+ this.providerId = identity.providerId;
42
+ this.confirmedProvisioned = identity.confirmedProvisioned === true;
43
+ this.outcomeUnknown = !this.confirmedProvisioned && identity.outcomeUnknown === true;
23
44
  }
24
45
  }
46
+ class CloudFleetSandboxIdentityMismatchError extends Error {
47
+ }
48
+ const CLOUD_FLEET_SANDBOX_PROVIDER_IDS = [
49
+ 'daytona',
50
+ 'e2b',
51
+ 'vercel',
52
+ 'freestyle',
53
+ 'agent37',
54
+ 'microsandbox',
55
+ ];
25
56
  function isObject(value) {
26
57
  return value !== null && typeof value === 'object' && !Array.isArray(value);
27
58
  }
@@ -29,6 +60,53 @@ function readString(payload, key) {
29
60
  const value = payload[key];
30
61
  return typeof value === 'string' && value.trim() ? value.trim() : undefined;
31
62
  }
63
+ function normalizeRelaycastOrigin(value, field) {
64
+ if (typeof value !== 'string' || !value.trim()) {
65
+ throw new Error(`Cloud fleet sandbox response has an invalid ${field}.`);
66
+ }
67
+ let parsed;
68
+ try {
69
+ parsed = new URL(value.trim());
70
+ }
71
+ catch {
72
+ throw new Error(`Cloud fleet sandbox response has an invalid ${field}.`);
73
+ }
74
+ if (parsed.protocol !== 'https:' ||
75
+ parsed.username ||
76
+ parsed.password ||
77
+ parsed.port ||
78
+ parsed.search ||
79
+ parsed.hash ||
80
+ (parsed.pathname !== '' && parsed.pathname !== '/') ||
81
+ !TRUSTED_RELAYCAST_ORIGINS.has(parsed.origin)) {
82
+ throw new Error(`Cloud fleet sandbox response has an untrusted ${field}.`);
83
+ }
84
+ return parsed.origin;
85
+ }
86
+ /** Validate Cloud's closed Relaycast route, identity, and scoped credential contract. */
87
+ export function normalizeRelaycastTarget(value) {
88
+ if (!isObject(value)) {
89
+ throw new Error('Cloud fleet sandbox response is missing relaycastTarget.');
90
+ }
91
+ const route = readString(value, 'route');
92
+ if (route !== 'canonical' && route !== 'agent37-isolated') {
93
+ throw new Error('Cloud fleet sandbox response has an unknown Relaycast route.');
94
+ }
95
+ const baseUrl = normalizeRelaycastOrigin(value.baseUrl, 'relaycastTarget.baseUrl');
96
+ const expectedOrigin = route === 'canonical' ? CANONICAL_RELAYCAST_ORIGIN : AGENT37_RELAYCAST_ORIGIN;
97
+ if (baseUrl !== expectedOrigin) {
98
+ throw new Error('Cloud fleet sandbox response mapped Relaycast route to the wrong origin.');
99
+ }
100
+ const workspaceId = readString(value, 'workspaceId');
101
+ if (!workspaceId) {
102
+ throw new Error('Cloud fleet sandbox response is missing relaycastTarget.workspaceId.');
103
+ }
104
+ const relaycastApiKey = readString(value, 'relaycastApiKey');
105
+ if (!relaycastApiKey || !/^rk_live_[A-Za-z0-9_-]+$/.test(relaycastApiKey)) {
106
+ throw new Error('Cloud fleet sandbox response has an invalid Relaycast API key.');
107
+ }
108
+ return { route, baseUrl, workspaceId, relaycastApiKey };
109
+ }
32
110
  function readNumber(payload, key) {
33
111
  const value = payload[key];
34
112
  return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
@@ -39,6 +117,22 @@ function requiredNumber(payload, key, context) {
39
117
  throw new Error(`${context} response is missing ${key}.`);
40
118
  return value;
41
119
  }
120
+ function assertProviderRelaycastTarget(providerId, target) {
121
+ if (providerId === 'agent37') {
122
+ if (!target) {
123
+ throw new Error('Cloud fleet sandbox response is missing the Agent37 Relaycast target.');
124
+ }
125
+ if (target.route !== 'agent37-isolated' || target.baseUrl !== AGENT37_RELAYCAST_ORIGIN) {
126
+ throw new Error('Cloud fleet sandbox response mapped Agent37 to a non-isolated Relaycast target.');
127
+ }
128
+ return;
129
+ }
130
+ if (providerId !== undefined && target) {
131
+ if (target.route !== 'canonical' || target.baseUrl !== CANONICAL_RELAYCAST_ORIGIN) {
132
+ throw new Error(`Cloud fleet sandbox response mapped ${providerId} to a non-canonical Relaycast target.`);
133
+ }
134
+ }
135
+ }
42
136
  function boundedSignal(options, defaultTimeoutMs) {
43
137
  const timeoutMs = options.timeoutMs ?? defaultTimeoutMs;
44
138
  if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
@@ -77,6 +171,39 @@ function requiredString(payload, key, context) {
77
171
  throw new Error(`${context} response is missing ${key}.`);
78
172
  return value;
79
173
  }
174
+ function validateSandboxIdentity(input) {
175
+ if (input.sandboxId !== undefined && typeof input.sandboxId !== 'string') {
176
+ throw new Error('Cloud fleet sandbox sandboxId must be a string.');
177
+ }
178
+ if (input.name !== undefined && typeof input.name !== 'string') {
179
+ throw new Error('Cloud fleet sandbox name must be a string.');
180
+ }
181
+ const sandboxId = input.sandboxId?.trim();
182
+ const name = input.name?.trim();
183
+ if (input.sandboxId !== undefined && (!sandboxId || !CLOUD_SANDBOX_ID_PATTERN.test(sandboxId))) {
184
+ throw new Error('Cloud fleet sandbox sandboxId must match lowercase sbx_<UUID> using an RFC 4122 UUID.');
185
+ }
186
+ if (sandboxId !== undefined && input.forceProvision !== true) {
187
+ throw new Error('Cloud fleet sandbox sandboxId requires forceProvision: true.');
188
+ }
189
+ if (sandboxId !== undefined && !name) {
190
+ throw new Error('Cloud fleet sandbox sandboxId requires a node name.');
191
+ }
192
+ const longRunning = input.workloadProfile === 'long-running-agent' || input.workloadProfile === 'standard-long-running-agent';
193
+ if (longRunning && sandboxId !== undefined) {
194
+ if (input.forceProvision !== true) {
195
+ throw new Error('Long-running Cloud fleet sandbox requests require forceProvision: true.');
196
+ }
197
+ const expectedName = `fleet-sandbox-${sandboxId.slice('sbx_'.length)}`;
198
+ if (name !== expectedName) {
199
+ throw new Error(`Long-running Cloud fleet sandbox requests require name '${expectedName}' to preserve the one-to-one sandbox identity.`);
200
+ }
201
+ }
202
+ return {
203
+ ...(sandboxId === undefined ? {} : { sandboxId }),
204
+ ...(name === undefined ? {} : { name }),
205
+ };
206
+ }
80
207
  async function resolveCloudWorkspaceId(workspaceId, auth, signal) {
81
208
  const { response, auth: activeAuth } = await authorizedApiFetch(auth, `/api/v1/workspaces/${encodeURIComponent(workspaceId)}/resolve`, { method: 'GET', signal }, { interactive: false });
82
209
  const payload = await readJson(response);
@@ -93,29 +220,105 @@ async function resolveCloudWorkspaceId(workspaceId, auth, signal) {
93
220
  auth: activeAuth,
94
221
  };
95
222
  }
96
- function normalizeEnsureResult(payload, cloudWorkspaceId) {
223
+ function readProviderId(payload, exactProviderRequested) {
224
+ const value = readString(payload, 'providerId');
225
+ if (value === undefined)
226
+ return undefined;
227
+ if (CLOUD_FLEET_SANDBOX_PROVIDER_IDS.includes(value)) {
228
+ return value;
229
+ }
230
+ if (exactProviderRequested) {
231
+ throw new Error('Cloud fleet sandbox response has an unknown providerId.');
232
+ }
233
+ return undefined;
234
+ }
235
+ function assertExpectedSandboxIdentity(payload, expectedSandboxId) {
236
+ const sandboxId = requiredString(payload, 'sandboxId', 'Cloud fleet sandbox');
237
+ if (sandboxId !== expectedSandboxId) {
238
+ throw new CloudFleetSandboxIdentityMismatchError(`Cloud returned sandboxId ${sandboxId} instead of requested sandboxId ${expectedSandboxId}.`);
239
+ }
240
+ }
241
+ /** Daytona's control-plane identity and its provider UUID are distinct. */
242
+ function normalizeProviderSandboxId(payload, providerId) {
243
+ const providerSandboxId = readString(payload, 'providerSandboxId');
244
+ if (providerId !== 'daytona')
245
+ return providerSandboxId;
246
+ if (!providerSandboxId || !DAYTONA_PROVIDER_SANDBOX_ID_PATTERN.test(providerSandboxId)) {
247
+ throw new Error('Cloud fleet sandbox response is missing a valid Daytona providerSandboxId.');
248
+ }
249
+ return providerSandboxId;
250
+ }
251
+ /**
252
+ * A malformed success response can still leave a billable sandbox behind. It
253
+ * is safe to delete only when the response itself confirms the exact
254
+ * caller-checkpointed identity; never promote a returned or ambient identity
255
+ * to cleanup authority.
256
+ */
257
+ function confirmsProvisionedSandboxIdentity(payload, expectedSandboxId, expectedNodeName, requestedProviderId) {
258
+ if (!isObject(payload) || expectedSandboxId === undefined || requestedProviderId !== 'daytona')
259
+ return false;
260
+ // A timeout is also a response from an accepted provision request. When it
261
+ // echoes the exact caller-checkpointed public identity, Cloud can safely
262
+ // delete that one sandbox even if the provider UUID is malformed or absent.
263
+ if (!['provisioned', 'provisioning_timeout'].includes(readString(payload, 'outcome') ?? ''))
264
+ return false;
265
+ if (readString(payload, 'sandboxId') !== expectedSandboxId)
266
+ return false;
267
+ if (expectedNodeName !== undefined && readString(payload, 'nodeName') !== expectedNodeName)
268
+ return false;
269
+ return readString(payload, 'providerId') === requestedProviderId;
270
+ }
271
+ function normalizeEnsureResult(payload, cloudWorkspaceId, expectedSandboxId, expectedNodeName, requestedProviderId) {
97
272
  if (!isObject(payload))
98
273
  throw new Error('Cloud fleet sandbox response was not valid JSON.');
274
+ // A caller-declared identity is the cleanup authority. Validate it before
275
+ // reading any other response field so malformed and future outcomes cannot
276
+ // make an untrusted public ID eligible for automatic deletion.
277
+ if (expectedSandboxId !== undefined) {
278
+ assertExpectedSandboxIdentity(payload, expectedSandboxId);
279
+ }
99
280
  const outcome = readString(payload, 'outcome');
100
281
  const nodeName = requiredString(payload, 'nodeName', 'Cloud fleet sandbox');
282
+ if (expectedSandboxId !== undefined && expectedNodeName !== undefined && nodeName !== expectedNodeName) {
283
+ throw new CloudFleetSandboxIdentityMismatchError(`Cloud returned nodeName ${nodeName} instead of requested nodeName ${expectedNodeName}.`);
284
+ }
285
+ const providerId = readProviderId(payload, requestedProviderId !== undefined);
286
+ if (requestedProviderId !== undefined && providerId !== requestedProviderId) {
287
+ throw new Error(providerId === undefined
288
+ ? `Cloud did not prove requested provider ${requestedProviderId}.`
289
+ : `Cloud returned provider ${providerId} instead of requested provider ${requestedProviderId}.`);
290
+ }
101
291
  if (outcome === 'provisioned') {
102
292
  if (typeof payload.relayfileMounted !== 'boolean') {
103
293
  throw new Error('Cloud fleet sandbox response is missing relayfileMounted.');
104
294
  }
295
+ const sandboxId = requiredString(payload, 'sandboxId', 'Cloud fleet sandbox');
296
+ const providerSandboxId = normalizeProviderSandboxId(payload, providerId);
297
+ const relayWorkspaceId = requiredString(payload, 'relayWorkspaceId', 'Cloud fleet sandbox');
298
+ const relaycastTarget = payload.relaycastTarget === undefined ? undefined : normalizeRelaycastTarget(payload.relaycastTarget);
299
+ if (relaycastTarget !== undefined && relaycastTarget.workspaceId !== relayWorkspaceId) {
300
+ throw new Error('Cloud fleet sandbox response has mismatched Relaycast workspace identities.');
301
+ }
302
+ assertProviderRelaycastTarget(providerId, relaycastTarget);
105
303
  return {
106
304
  outcome,
107
305
  cloudWorkspaceId,
108
306
  nodeId: requiredString(payload, 'nodeId', 'Cloud fleet sandbox'),
109
307
  nodeName,
110
- sandboxId: requiredString(payload, 'sandboxId', 'Cloud fleet sandbox'),
111
- relayWorkspaceId: requiredString(payload, 'relayWorkspaceId', 'Cloud fleet sandbox'),
308
+ sandboxId,
309
+ ...(providerSandboxId === undefined ? {} : { providerSandboxId }),
310
+ relayWorkspaceId,
311
+ ...(relaycastTarget === undefined ? {} : { relaycastTarget }),
112
312
  relayfileMounted: payload.relayfileMounted,
313
+ ...(providerId === undefined ? {} : { providerId }),
113
314
  ...(readString(payload, 'relayfileMountPath')
114
315
  ? { relayfileMountPath: readString(payload, 'relayfileMountPath') }
115
316
  : {}),
116
317
  };
117
318
  }
118
319
  if (outcome === 'reused') {
320
+ const relaycastTarget = payload.relaycastTarget === undefined ? undefined : normalizeRelaycastTarget(payload.relaycastTarget);
321
+ assertProviderRelaycastTarget(providerId, relaycastTarget);
119
322
  return {
120
323
  outcome,
121
324
  cloudWorkspaceId,
@@ -124,16 +327,28 @@ function normalizeEnsureResult(payload, cloudWorkspaceId) {
124
327
  status: requiredString(payload, 'status', 'Cloud fleet sandbox'),
125
328
  activeAgents: readNumber(payload, 'activeAgents') ?? null,
126
329
  maxAgents: readNumber(payload, 'maxAgents') ?? null,
330
+ ...(providerId === undefined ? {} : { providerId }),
331
+ ...(relaycastTarget === undefined ? {} : { relaycastTarget }),
127
332
  };
128
333
  }
129
334
  if (outcome === 'provisioning_timeout') {
335
+ const sandboxId = requiredString(payload, 'sandboxId', 'Cloud fleet sandbox');
336
+ const providerSandboxId = normalizeProviderSandboxId(payload, providerId);
337
+ const relayWorkspaceId = requiredString(payload, 'relayWorkspaceId', 'Cloud fleet sandbox');
338
+ const relaycastTarget = payload.relaycastTarget === undefined ? undefined : normalizeRelaycastTarget(payload.relaycastTarget);
339
+ if (relaycastTarget !== undefined && relaycastTarget.workspaceId !== relayWorkspaceId) {
340
+ throw new Error('Cloud fleet sandbox response has mismatched Relaycast workspace identities.');
341
+ }
130
342
  return {
131
343
  outcome,
132
344
  cloudWorkspaceId,
133
- sandboxId: requiredString(payload, 'sandboxId', 'Cloud fleet sandbox'),
134
- relayWorkspaceId: requiredString(payload, 'relayWorkspaceId', 'Cloud fleet sandbox'),
345
+ sandboxId,
346
+ ...(providerSandboxId === undefined ? {} : { providerSandboxId }),
347
+ relayWorkspaceId,
348
+ ...(relaycastTarget === undefined ? {} : { relaycastTarget }),
135
349
  nodeName,
136
350
  waitedMs: requiredNumber(payload, 'waitedMs', 'Cloud fleet sandbox'),
351
+ ...(providerId === undefined ? {} : { providerId }),
137
352
  };
138
353
  }
139
354
  throw new Error('Cloud fleet sandbox response has an unknown outcome.');
@@ -146,11 +361,15 @@ export async function ensureCloudFleetSandbox(input, options = {}) {
146
361
  throw new Error('A workspace ID is required to provision a fleet sandbox.');
147
362
  if (!requiredCapability)
148
363
  throw new Error('A spawn capability is required to provision a fleet sandbox.');
364
+ const sandboxIdentity = validateSandboxIdentity(input);
365
+ if (input.relayfilePaths !== undefined && input.relayfilePaths.length === 0) {
366
+ throw new Error('At least one Relayfile subtree path is required when relayfilePaths is provided.');
367
+ }
149
368
  const session = await ensureCloudSession({
150
369
  apiUrl: options.apiUrl || defaultApiUrl(),
151
370
  interactive: false,
152
371
  });
153
- const resolutionSignal = boundedSignal(options, DEFAULT_ENSURE_TIMEOUT_MS);
372
+ const resolutionSignal = boundedSignal(options, DEFAULT_RESOLUTION_TIMEOUT_MS);
154
373
  const resolved = await resolveCloudWorkspaceId(workspaceId, session.auth, resolutionSignal);
155
374
  const signal = boundedSignal(options, DEFAULT_ENSURE_TIMEOUT_MS);
156
375
  let response;
@@ -161,53 +380,87 @@ export async function ensureCloudFleetSandbox(input, options = {}) {
161
380
  body: JSON.stringify({
162
381
  workspaceId: resolved.cloudWorkspaceId,
163
382
  requiredCapability,
164
- ...(input.name ? { name: input.name } : {}),
383
+ ...(sandboxIdentity.sandboxId === undefined ? {} : { sandboxId: sandboxIdentity.sandboxId }),
384
+ ...(sandboxIdentity.name === undefined ? {} : { name: sandboxIdentity.name }),
165
385
  ...(input.maxAgents !== undefined ? { maxAgents: input.maxAgents } : {}),
166
386
  ...(input.mountRelayfile !== undefined ? { mountRelayfile: input.mountRelayfile } : {}),
387
+ ...(input.relayfilePaths === undefined ? {} : { relayfilePaths: [...input.relayfilePaths] }),
167
388
  ...(input.forceProvision !== undefined ? { forceProvision: input.forceProvision } : {}),
389
+ ...(input.providerId !== undefined ? { providerId: input.providerId } : {}),
390
+ ...(input.workloadProfile !== undefined ? { workloadProfile: input.workloadProfile } : {}),
168
391
  ...(input.waitTimeoutMs !== undefined ? { waitTimeoutMs: input.waitTimeoutMs } : {}),
392
+ ...(input.repos !== undefined && input.repos.length > 0 ? { repos: [...input.repos] } : {}),
169
393
  }),
170
394
  }, { interactive: false }));
171
395
  }
172
396
  catch (error) {
173
397
  throw new CloudFleetSandboxProvisionError(redactCredentialValues(`Cloud fleet sandbox request ended without a complete response: ${error instanceof Error ? error.message : String(error)}`), {
174
398
  cloudWorkspaceId: resolved.cloudWorkspaceId,
399
+ ...(sandboxIdentity.sandboxId === undefined ? {} : { sandboxId: sandboxIdentity.sandboxId }),
175
400
  ...(input.name ? { nodeName: input.name } : {}),
401
+ ...(input.providerId ? { providerId: input.providerId } : {}),
176
402
  outcomeUnknown: true,
177
403
  cause: error,
178
404
  });
179
405
  }
180
406
  const payload = await readJson(response);
181
407
  if (!response.ok) {
408
+ const returnedSandboxId = isObject(payload) ? readString(payload, 'sandboxId') : undefined;
409
+ if (sandboxIdentity.sandboxId !== undefined &&
410
+ returnedSandboxId !== undefined &&
411
+ returnedSandboxId !== sandboxIdentity.sandboxId) {
412
+ const mismatch = new CloudFleetSandboxIdentityMismatchError(`Cloud returned sandboxId ${returnedSandboxId} instead of requested sandboxId ${sandboxIdentity.sandboxId}.`);
413
+ throw new CloudFleetSandboxProvisionError(mismatch.message, {
414
+ cloudWorkspaceId: resolved.cloudWorkspaceId,
415
+ ...(sandboxIdentity.name === undefined ? {} : { nodeName: sandboxIdentity.name }),
416
+ ...(input.providerId === undefined ? {} : { providerId: input.providerId }),
417
+ outcomeUnknown: true,
418
+ cause: mismatch,
419
+ });
420
+ }
182
421
  const error = endpointError('provision the fleet sandbox', response, payload);
422
+ // Gateway/server failures can arrive after Cloud accepted the ensure
423
+ // request but before it could return an identity. Keep every 5xx failure
424
+ // replayable as an unknown outcome, even for legacy custom-name callers;
425
+ // never copy an unverified response ID into cleanup authority.
426
+ if (response.status >= 500) {
427
+ throw new CloudFleetSandboxProvisionError(error.message, {
428
+ cloudWorkspaceId: resolved.cloudWorkspaceId,
429
+ ...(sandboxIdentity.sandboxId === undefined ? {} : { sandboxId: sandboxIdentity.sandboxId }),
430
+ ...(sandboxIdentity.name === undefined ? {} : { nodeName: sandboxIdentity.name }),
431
+ ...(input.providerId === undefined ? {} : { providerId: input.providerId }),
432
+ outcomeUnknown: true,
433
+ cause: error,
434
+ });
435
+ }
183
436
  if (isObject(payload) && readString(payload, 'sandboxId')) {
184
437
  throw new CloudFleetSandboxProvisionError(error.message, {
185
438
  cloudWorkspaceId: resolved.cloudWorkspaceId,
186
- sandboxId: readString(payload, 'sandboxId'),
187
- nodeName: readString(payload, 'nodeName') ?? input.name,
439
+ ...(sandboxIdentity.sandboxId === undefined ? {} : { sandboxId: sandboxIdentity.sandboxId }),
440
+ ...(sandboxIdentity.name === undefined ? {} : { nodeName: sandboxIdentity.name }),
441
+ ...(input.providerId === undefined ? {} : { providerId: input.providerId }),
442
+ outcomeUnknown: true,
188
443
  cause: error,
189
444
  });
190
445
  }
191
446
  throw error;
192
447
  }
193
448
  try {
194
- return normalizeEnsureResult(payload, resolved.cloudWorkspaceId);
449
+ return normalizeEnsureResult(payload, resolved.cloudWorkspaceId, sandboxIdentity.sandboxId, sandboxIdentity.name, input.providerId);
195
450
  }
196
451
  catch (error) {
452
+ const confirmedProvisioned = confirmsProvisionedSandboxIdentity(payload, sandboxIdentity.sandboxId, sandboxIdentity.name, input.providerId);
197
453
  throw new CloudFleetSandboxProvisionError(error instanceof Error ? error.message : 'Cloud fleet sandbox response was invalid.', {
198
454
  cloudWorkspaceId: resolved.cloudWorkspaceId,
199
- ...(isObject(payload) && readString(payload, 'sandboxId')
200
- ? { sandboxId: readString(payload, 'sandboxId') }
201
- : {}),
202
- ...(isObject(payload) && (readString(payload, 'nodeName') ?? input.name)
203
- ? { nodeName: readString(payload, 'nodeName') ?? input.name }
204
- : {}),
205
- outcomeUnknown: true,
455
+ ...(sandboxIdentity.sandboxId === undefined ? {} : { sandboxId: sandboxIdentity.sandboxId }),
456
+ ...(sandboxIdentity.name === undefined ? {} : { nodeName: sandboxIdentity.name }),
457
+ ...(input.providerId === undefined ? {} : { providerId: input.providerId }),
458
+ ...(confirmedProvisioned ? { confirmedProvisioned: true } : { outcomeUnknown: true }),
206
459
  cause: error,
207
460
  });
208
461
  }
209
462
  }
210
- /** Best-effort-safe deletion for a Cloud-owned Daytona fleet sandbox. */
463
+ /** Best-effort-safe deletion for a Cloud-owned fleet sandbox. */
211
464
  export async function deleteCloudFleetSandbox(input, options = {}) {
212
465
  const cloudWorkspaceId = input.cloudWorkspaceId.trim();
213
466
  const sandboxId = input.sandboxId.trim();
@@ -221,7 +474,10 @@ export async function deleteCloudFleetSandbox(input, options = {}) {
221
474
  const { response } = await authorizedApiFetch(session.auth, `/api/v1/fleet/nodes/sandbox/${encodeURIComponent(sandboxId)}`, {
222
475
  method: 'DELETE',
223
476
  signal,
224
- body: JSON.stringify({ workspaceId: cloudWorkspaceId }),
477
+ body: JSON.stringify({
478
+ workspaceId: cloudWorkspaceId,
479
+ ...(input.providerId === undefined ? {} : { providerId: input.providerId }),
480
+ }),
225
481
  }, { interactive: false });
226
482
  const payload = await readJson(response);
227
483
  if (!response.ok)