@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.
- package/dist/fleet-sandbox.d.ts +73 -3
- package/dist/fleet-sandbox.d.ts.map +1 -1
- package/dist/fleet-sandbox.js +277 -21
- package/dist/fleet-sandbox.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/project-workspace-key.d.ts +30 -1
- package/dist/project-workspace-key.d.ts.map +1 -1
- package/dist/project-workspace-key.js +282 -8
- package/dist/project-workspace-key.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/workflows.d.ts +1 -9
- package/dist/workflows.d.ts.map +1 -1
- package/dist/workflows.js +17 -0
- package/dist/workflows.js.map +1 -1
- package/dist/workspace-key.d.ts +1 -1
- package/dist/workspace-key.d.ts.map +1 -1
- package/dist/workspace-key.js +1 -1
- package/dist/workspace-key.js.map +1 -1
- package/package.json +2 -2
package/dist/fleet-sandbox.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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":"
|
|
1
|
+
{"version":3,"file":"fleet-sandbox.d.ts","sourceRoot":"","sources":["../src/fleet-sandbox.ts"],"names":[],"mappings":"AAWA;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,gCAAgC,CAAC;AACxE,eAAO,MAAM,wBAAwB,wCAAwC,CAAC;AAW9E,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,MAAM,MAAM,2BAA2B,GACnC,SAAS,GACT,KAAK,GACL,QAAQ,GACR,WAAW,GACX,SAAS,GACT,cAAc,CAAC;AAEnB;;;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,UAAU,CAAC,EAAE,2BAA2B,CAAC;IAClD,6EAA6E;IAC7E,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC;IACvC,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,UAAU,CAAC,EAAE,2BAA2B,CAAC;QACzC,oBAAoB,CAAC,EAAE,OAAO,CAAC;QAC/B,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,KAAK,CAAC,EAAE,OAAO,CAAC;KACZ;CAWT;AAID,MAAM,MAAM,4BAA4B,GAAG;IACzC,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kBAAkB,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,2BAA2B,CAAC;IACzC,oEAAoE;IACpE,eAAe,CAAC,EAAE,gCAAgC,CAAC;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,gCAAgC,GACxC,UAAU,GACV,oBAAoB,GACpB,6BAA6B,CAAC;AAWlC,KAAK,0BAA0B,GAAG;IAChC,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,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,4FAA4F;IAC5F,eAAe,CAAC,EAAE,yBAAyB,CAAC;IAC5C,gBAAgB,EAAE,OAAO,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,2BAA2B,CAAC;CAC1C,CAAC;AAEF,+EAA+E;AAC/E,MAAM,MAAM,sBAAsB,GAC9B,CAAC,0BAA0B,GAAG;IAC5B,UAAU,EAAE,SAAS,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC,GACF,CAAC,0BAA0B,GAAG;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAC,2BAA2B,EAAE,SAAS,CAAC,CAAC;CAC9D,CAAC,CAAC;AAEP,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;IACzB,UAAU,CAAC,EAAE,2BAA2B,CAAC;IACzC,4FAA4F;IAC5F,eAAe,CAAC,EAAE,yBAAyB,CAAC;CAC7C,CAAC;AAEF,KAAK,wCAAwC,GAAG;IAC9C,OAAO,EAAE,sBAAsB,CAAC;IAChC,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,yBAAyB,CAAC;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,2BAA2B,CAAC;CAC1C,CAAC;AAEF,uFAAuF;AACvF,MAAM,MAAM,oCAAoC,GAC5C,CAAC,wCAAwC,GAAG;IAC1C,UAAU,EAAE,SAAS,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC,GACF,CAAC,wCAAwC,GAAG;IAC1C,UAAU,CAAC,EAAE,OAAO,CAAC,2BAA2B,EAAE,SAAS,CAAC,CAAC;CAC9D,CAAC,CAAC;AAEP,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;IAClB,UAAU,CAAC,EAAE,2BAA2B,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG,WAAW,GAAG,kBAAkB,CAAC;AAExE,MAAM,MAAM,yBAAyB,GAAG;IACtC,KAAK,EAAE,wBAAwB,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAoCF,yFAAyF;AACzF,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,yBAAyB,CAsBlF;AAwTD,0FAA0F;AAC1F,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,4BAA4B,EACnC,OAAO,GAAE,+BAAoC,GAC5C,OAAO,CAAC,6BAA6B,CAAC,CAoIxC;AAED,iEAAiE;AACjE,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,4BAA4B,EACnC,OAAO,GAAE,+BAAoC,GAC5C,OAAO,CAAC,IAAI,CAAC,CAyBf"}
|
package/dist/fleet-sandbox.js
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|
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
|
|
111
|
-
|
|
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
|
|
134
|
-
|
|
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,
|
|
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
|
-
...(
|
|
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:
|
|
187
|
-
|
|
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
|
-
...(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
...(
|
|
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
|
|
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({
|
|
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)
|