@mastra/platform-workspace 1.2.0-alpha.1 → 1.2.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/dist/client.d.ts +19 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/index.cjs +149 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +149 -2
- package/dist/index.js.map +1 -1
- package/dist/sandbox.d.ts +57 -0
- package/dist/sandbox.d.ts.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @mastra/platform
|
|
2
2
|
|
|
3
|
+
## 1.2.0-alpha.2
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Added startup observability to `PlatformSandbox`. New optional `sessionId` and `threadId` options let you correlate all sandbox startup activity with the session that triggered it, and the sandbox now logs how long startup took and whether it became reachable. ([#21189](https://github.com/mastra-ai/mastra/pull/21189))
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { PlatformSandbox } from '@mastra/platform-workspace';
|
|
11
|
+
|
|
12
|
+
const sandbox = new PlatformSandbox({
|
|
13
|
+
projectId: 'proj_123',
|
|
14
|
+
environmentId: 'env_123',
|
|
15
|
+
sessionId: 'session_abc', // correlate startup logs with your session
|
|
16
|
+
threadId: 'thread_xyz', // optional finer-grained correlation
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- Added `PlatformSandbox.snapshot()` to capture the configured recovery checkpoint. ([#21221](https://github.com/mastra-ai/mastra/pull/21221))
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
await sandbox.snapshot();
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- Fixed Platform Sandbox startup so commands use a reliable connection while a new sandbox is starting. ([#21028](https://github.com/mastra-ai/mastra/pull/21028))
|
|
29
|
+
|
|
30
|
+
- Updated dependencies [[`9571e3a`](https://github.com/mastra-ai/mastra/commit/9571e3a06ed2c5220196460bf82a2129255c3a8b), [`d6c56f9`](https://github.com/mastra-ai/mastra/commit/d6c56f951db3213330b98b0abafa9778c8770e58), [`9571e3a`](https://github.com/mastra-ai/mastra/commit/9571e3a06ed2c5220196460bf82a2129255c3a8b), [`acc3513`](https://github.com/mastra-ai/mastra/commit/acc3513b19f79bf0a7ec2998694580edca54086c), [`94e7ae9`](https://github.com/mastra-ai/mastra/commit/94e7ae970b37c888cd1244ef013292639a2fe6d1), [`6a667b4`](https://github.com/mastra-ai/mastra/commit/6a667b4b7cd6a93fe41fcdd357b08c5a8c09b9ab), [`2440e09`](https://github.com/mastra-ai/mastra/commit/2440e096ea6c2def1ccc1eb2d0f3f5b88c4af940), [`a59049b`](https://github.com/mastra-ai/mastra/commit/a59049b1652a13efff66ac826326b5ed9a550342)]:
|
|
31
|
+
- @mastra/core@1.58.0-alpha.13
|
|
32
|
+
|
|
3
33
|
## 1.2.0-alpha.1
|
|
4
34
|
|
|
5
35
|
### Minor Changes
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
export interface PlatformClientOptions {
|
|
2
2
|
accessToken?: string;
|
|
3
3
|
projectId?: string;
|
|
4
|
+
/**
|
|
5
|
+
* Advisory correlation id for the factory session driving this client.
|
|
6
|
+
* Sent as `x-mastra-session-id` on every proxy request so proxy-side logs
|
|
7
|
+
* can be joined back to the calling session without a multi-store hand-join
|
|
8
|
+
* (`threadId → sessionId → sandboxId → providerResourceId`). Never used for
|
|
9
|
+
* authorization — the Bearer token remains the only credential.
|
|
10
|
+
*/
|
|
11
|
+
sessionId?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Advisory correlation id for the factory thread, sent as
|
|
14
|
+
* `x-mastra-thread-id` when present. See {@link PlatformClientOptions.sessionId}.
|
|
15
|
+
*/
|
|
16
|
+
threadId?: string;
|
|
4
17
|
fetch?: typeof fetch;
|
|
5
18
|
}
|
|
6
19
|
export interface PlatformRequestOptions extends RequestInit {
|
|
@@ -11,6 +24,8 @@ export declare function resolvePlatformOptions(options: PlatformClientOptions):
|
|
|
11
24
|
accessToken: string;
|
|
12
25
|
projectId: string;
|
|
13
26
|
proxyUrl: string;
|
|
27
|
+
sessionId: string | undefined;
|
|
28
|
+
threadId: string | undefined;
|
|
14
29
|
fetch: typeof fetch;
|
|
15
30
|
};
|
|
16
31
|
/**
|
|
@@ -37,6 +52,10 @@ export declare class PlatformClient {
|
|
|
37
52
|
readonly accessToken: string;
|
|
38
53
|
readonly projectId: string;
|
|
39
54
|
readonly proxyUrl: string;
|
|
55
|
+
/** Advisory session correlation id — see {@link PlatformClientOptions.sessionId}. */
|
|
56
|
+
readonly sessionId: string | undefined;
|
|
57
|
+
/** Advisory thread correlation id — see {@link PlatformClientOptions.threadId}. */
|
|
58
|
+
readonly threadId: string | undefined;
|
|
40
59
|
readonly fetch: typeof fetch;
|
|
41
60
|
constructor(options: PlatformClientOptions);
|
|
42
61
|
request(path: string, options?: PlatformRequestOptions): Promise<Response>;
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED,MAAM,WAAW,sBAAuB,SAAQ,WAAW;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;CAC/D;AAWD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG7E;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,qBAAqB
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED,MAAM,WAAW,sBAAuB,SAAQ,WAAW;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;CAC/D;AAWD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG7E;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,qBAAqB;;;;;;;EASpE;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,gGAAgG;IAChG,IAAI,EAAE,MAAM,CAAC;CACd;AAkBD,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2HAA2H;IAC3H,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,yGAAyG;IACzG,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;gBAE9B,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAUzC;AAED,qBAAa,cAAc;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,qFAAqF;IACrF,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,mFAAmF;IACnF,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC;gBAEjB,OAAO,EAAE,qBAAqB;IAUpC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,QAAQ,CAAC;CA0BrF"}
|
package/dist/index.cjs
CHANGED
|
@@ -42,6 +42,8 @@ function resolvePlatformOptions(options) {
|
|
|
42
42
|
accessToken: requireOption(options.accessToken ?? process.env.MASTRA_PLATFORM_ACCESS_TOKEN, "accessToken"),
|
|
43
43
|
projectId: requireOption(options.projectId ?? process.env.MASTRA_PROJECT_ID, "projectId"),
|
|
44
44
|
proxyUrl: (process.env.MASTRA_WORKSPACE_PROXY_URL ?? DEFAULT_PROXY_URL).replace(/\/$/, ""),
|
|
45
|
+
sessionId: options.sessionId,
|
|
46
|
+
threadId: options.threadId,
|
|
45
47
|
fetch: options.fetch ?? fetch
|
|
46
48
|
};
|
|
47
49
|
}
|
|
@@ -85,12 +87,18 @@ var PlatformClient = class {
|
|
|
85
87
|
accessToken;
|
|
86
88
|
projectId;
|
|
87
89
|
proxyUrl;
|
|
90
|
+
/** Advisory session correlation id — see {@link PlatformClientOptions.sessionId}. */
|
|
91
|
+
sessionId;
|
|
92
|
+
/** Advisory thread correlation id — see {@link PlatformClientOptions.threadId}. */
|
|
93
|
+
threadId;
|
|
88
94
|
fetch;
|
|
89
95
|
constructor(options) {
|
|
90
96
|
const resolved = resolvePlatformOptions(options);
|
|
91
97
|
this.accessToken = resolved.accessToken;
|
|
92
98
|
this.projectId = resolved.projectId;
|
|
93
99
|
this.proxyUrl = resolved.proxyUrl;
|
|
100
|
+
this.sessionId = resolved.sessionId;
|
|
101
|
+
this.threadId = resolved.threadId;
|
|
94
102
|
this.fetch = resolved.fetch;
|
|
95
103
|
}
|
|
96
104
|
async request(path, options = {}) {
|
|
@@ -98,6 +106,8 @@ var PlatformClient = class {
|
|
|
98
106
|
for (const [key, value] of Object.entries(options.query ?? {})) if (value !== void 0) url.searchParams.set(key, String(value));
|
|
99
107
|
const headers = new Headers(options.headers);
|
|
100
108
|
headers.set("authorization", `Bearer ${this.accessToken}`);
|
|
109
|
+
if (this.sessionId) headers.set("x-mastra-session-id", this.sessionId);
|
|
110
|
+
if (this.threadId) headers.set("x-mastra-thread-id", this.threadId);
|
|
101
111
|
const { query: _query, ...fetchOptions } = options;
|
|
102
112
|
const signal = fetchOptions.signal ?? AbortSignal.timeout(DEFAULT_REQUEST_TIMEOUT_MS);
|
|
103
113
|
const response = await this.fetch(url, {
|
|
@@ -693,6 +703,24 @@ const CREATE_MAX_ATTEMPTS = 3;
|
|
|
693
703
|
/** Base delay between create retries; multiplied by the attempt number. */
|
|
694
704
|
const CREATE_RETRY_BASE_DELAY_MS = 2e3;
|
|
695
705
|
/**
|
|
706
|
+
* How long to wait for the in-sandbox sidecar's `/health` endpoint to respond
|
|
707
|
+
* before giving up and leaving the address registry unpopulated (execs fall
|
|
708
|
+
* back to the lease path). This bounds the fire-and-forget probe that runs
|
|
709
|
+
* after `start()` resolves; the sandbox is usable immediately — the probe
|
|
710
|
+
* only controls whether early execs go via private-net or lease.
|
|
711
|
+
*/
|
|
712
|
+
const SIDECAR_PROBE_TIMEOUT_MS = 3e4;
|
|
713
|
+
/** Delay between sidecar probe attempts. */
|
|
714
|
+
const SIDECAR_PROBE_INTERVAL_MS = 250;
|
|
715
|
+
/**
|
|
716
|
+
* How long `executeCommand` waits for the transport to become ready before
|
|
717
|
+
* falling back to the lease path. This is much shorter than
|
|
718
|
+
* `SIDECAR_PROBE_TIMEOUT_MS` because we want execs to proceed quickly if
|
|
719
|
+
* the sidecar is slow to boot — the probe continues in the background and
|
|
720
|
+
* later execs will use private-net once it succeeds.
|
|
721
|
+
*/
|
|
722
|
+
const TRANSPORT_READY_WAIT_MS = 5e3;
|
|
723
|
+
/**
|
|
696
724
|
* Diagnostic error thrown when the direct-exec WebSocket transport fails
|
|
697
725
|
* twice in a row (opening handshake refused or socket closed mid-stream
|
|
698
726
|
* without an `exit` frame). Distinguishes "the sandbox transport is broken"
|
|
@@ -887,6 +915,22 @@ var PlatformSandbox = class PlatformSandbox extends _mastra_core_workspace.Mastr
|
|
|
887
915
|
* Mirrors OSS `@mastra/railway` `RailwaySandbox._startInFlight`.
|
|
888
916
|
*/
|
|
889
917
|
_startInFlight = null;
|
|
918
|
+
/**
|
|
919
|
+
* Generation token for the sidecar probe. Incremented on every `start()`
|
|
920
|
+
* and on teardown. The probe captures this value when it begins; if the
|
|
921
|
+
* generation has changed by the time the probe succeeds, the probe skips
|
|
922
|
+
* the `set()` to avoid re-populating a deleted or superseded sandbox entry.
|
|
923
|
+
*/
|
|
924
|
+
_probeGeneration = 0;
|
|
925
|
+
/**
|
|
926
|
+
* In-flight sidecar probe promise. Concurrent `executeCommand` callers that
|
|
927
|
+
* arrive before the registry is populated all await this single promise so
|
|
928
|
+
* we don't fire N independent lease requests during the sidecar boot window.
|
|
929
|
+
* Once the probe resolves (success or timeout), callers check the registry
|
|
930
|
+
* and proceed — either via private-net (probe succeeded) or via lease (probe
|
|
931
|
+
* failed/timed out, but now coalesced via `_leaseInFlight`).
|
|
932
|
+
*/
|
|
933
|
+
_transportReadyPromise = null;
|
|
890
934
|
constructor(options = {}) {
|
|
891
935
|
super({
|
|
892
936
|
...options,
|
|
@@ -929,6 +973,8 @@ var PlatformSandbox = class PlatformSandbox extends _mastra_core_workspace.Mastr
|
|
|
929
973
|
...id !== void 0 && { id },
|
|
930
974
|
accessToken: this._client.accessToken,
|
|
931
975
|
projectId: this._client.projectId,
|
|
976
|
+
...this._client.sessionId !== void 0 && { sessionId: this._client.sessionId },
|
|
977
|
+
...this._client.threadId !== void 0 && { threadId: this._client.threadId },
|
|
932
978
|
fetch: this._client.fetch,
|
|
933
979
|
environmentId: this._environmentId,
|
|
934
980
|
...options.sandboxId !== void 0 && { sandboxId: options.sandboxId },
|
|
@@ -958,11 +1004,16 @@ var PlatformSandbox = class PlatformSandbox extends _mastra_core_workspace.Mastr
|
|
|
958
1004
|
* awaiter.
|
|
959
1005
|
*/
|
|
960
1006
|
async _doStart() {
|
|
1007
|
+
const startedAt = Date.now();
|
|
961
1008
|
if (this._sandboxId) try {
|
|
962
|
-
const
|
|
1009
|
+
const requestStartedAt = Date.now();
|
|
1010
|
+
const response = await this._client.request(`/sandbox/${encodeURIComponent(this._sandboxId)}`);
|
|
1011
|
+
const requestMs = Date.now() - requestStartedAt;
|
|
1012
|
+
const json = await response.json();
|
|
963
1013
|
if (!json.destroyedAt) {
|
|
964
1014
|
this._createdAt = json.createdAt ? new Date(json.createdAt) : /* @__PURE__ */ new Date();
|
|
965
1015
|
this._populateAddressFromResponse(json);
|
|
1016
|
+
this._logStartComplete(json.id, startedAt, requestMs, "reattach");
|
|
966
1017
|
return;
|
|
967
1018
|
}
|
|
968
1019
|
this._sandboxId = void 0;
|
|
@@ -979,6 +1030,7 @@ var PlatformSandbox = class PlatformSandbox extends _mastra_core_workspace.Mastr
|
|
|
979
1030
|
env: this._env
|
|
980
1031
|
});
|
|
981
1032
|
let response;
|
|
1033
|
+
const requestStartedAt = Date.now();
|
|
982
1034
|
for (let attempt = 1;; attempt++) try {
|
|
983
1035
|
response = await this._client.request("/sandbox", {
|
|
984
1036
|
method: "POST",
|
|
@@ -990,10 +1042,32 @@ var PlatformSandbox = class PlatformSandbox extends _mastra_core_workspace.Mastr
|
|
|
990
1042
|
if (!(error instanceof PlatformApiError && error.status >= 500) || attempt >= CREATE_MAX_ATTEMPTS) throw error;
|
|
991
1043
|
await new Promise((resolve) => setTimeout(resolve, CREATE_RETRY_BASE_DELAY_MS * attempt));
|
|
992
1044
|
}
|
|
1045
|
+
const requestMs = Date.now() - requestStartedAt;
|
|
993
1046
|
const json = await response.json();
|
|
994
1047
|
this._sandboxId = json.id;
|
|
995
1048
|
this._createdAt = json.createdAt ? new Date(json.createdAt) : /* @__PURE__ */ new Date();
|
|
996
1049
|
this._populateAddressFromResponse(json);
|
|
1050
|
+
this._logStartComplete(json.id, startedAt, requestMs, "provision");
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* One timing summary per completed `start()` — the whole
|
|
1054
|
+
* `PlatformSandbox`-visible boot in a single greppable line.
|
|
1055
|
+
*
|
|
1056
|
+
* `requestMs` is the proxy round-trip (`GET /sandbox/:id` on reattach,
|
|
1057
|
+
* `POST /sandbox` including transient-5xx retries on provision) — a black
|
|
1058
|
+
* box from this side that rolls up Railway RPC, sidecar launch, and the
|
|
1059
|
+
* proxy's discovery exec. Sidecar probe cost is intentionally NOT here: the
|
|
1060
|
+
* probe is fire-and-forget and outlives `start()` by design, so its
|
|
1061
|
+
* duration lands on the `platform-workspace probe ok` line instead.
|
|
1062
|
+
*/
|
|
1063
|
+
_logStartComplete(sandboxId, startedAt, requestMs, mode) {
|
|
1064
|
+
this.logger.info("platform-workspace start complete", {
|
|
1065
|
+
sandboxId,
|
|
1066
|
+
sessionId: this._client.sessionId,
|
|
1067
|
+
mode,
|
|
1068
|
+
totalMs: Date.now() - startedAt,
|
|
1069
|
+
requestMs
|
|
1070
|
+
});
|
|
997
1071
|
}
|
|
998
1072
|
/**
|
|
999
1073
|
* Copy `response.instanceUrl` into the injected {@link SandboxAddressRegistry}
|
|
@@ -1014,7 +1088,74 @@ var PlatformSandbox = class PlatformSandbox extends _mastra_core_workspace.Mastr
|
|
|
1014
1088
|
_populateAddressFromResponse(json) {
|
|
1015
1089
|
if (!this._addressRegistry) return;
|
|
1016
1090
|
if (!json.instanceUrl) return;
|
|
1017
|
-
this._addressRegistry.
|
|
1091
|
+
this._addressRegistry.delete(json.id);
|
|
1092
|
+
const generation = ++this._probeGeneration;
|
|
1093
|
+
this._transportReadyPromise = this._probeSidecarThenRegister(json.id, json.instanceUrl, generation);
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Fire-and-forget probe that polls the sidecar's `/health` endpoint until
|
|
1097
|
+
* it responds, then populates the address registry. Runs detached from
|
|
1098
|
+
* `start()` so sandbox provision latency is unchanged; early execs simply
|
|
1099
|
+
* fall back to the lease path until the probe succeeds.
|
|
1100
|
+
*
|
|
1101
|
+
* If the sidecar never comes up within {@link SIDECAR_PROBE_TIMEOUT_MS},
|
|
1102
|
+
* the registry stays unpopulated and all execs go via lease for this
|
|
1103
|
+
* sandbox's lifetime (or until a future `start()` re-runs the probe).
|
|
1104
|
+
*
|
|
1105
|
+
* @param generation - The probe generation captured at call time. If this
|
|
1106
|
+
* no longer matches `_probeGeneration` when the probe succeeds, the probe
|
|
1107
|
+
* was superseded by a teardown or a new `start()`, so we skip the `set()`.
|
|
1108
|
+
*/
|
|
1109
|
+
async _probeSidecarThenRegister(sandboxId, instanceUrl, generation) {
|
|
1110
|
+
const probeStartedAt = Date.now();
|
|
1111
|
+
const deadline = probeStartedAt + SIDECAR_PROBE_TIMEOUT_MS;
|
|
1112
|
+
const fetchFn = this._privateNetFetch ?? fetch;
|
|
1113
|
+
let attempts = 0;
|
|
1114
|
+
while (Date.now() < deadline) {
|
|
1115
|
+
if (generation !== this._probeGeneration) return;
|
|
1116
|
+
attempts++;
|
|
1117
|
+
try {
|
|
1118
|
+
const res = await fetchFn(`${instanceUrl}/health`, {
|
|
1119
|
+
method: "GET",
|
|
1120
|
+
signal: AbortSignal.timeout(1e3)
|
|
1121
|
+
});
|
|
1122
|
+
const ok = res.ok;
|
|
1123
|
+
await res.body?.cancel().catch(() => {});
|
|
1124
|
+
if (ok) {
|
|
1125
|
+
this.logger.info("platform-workspace probe ok", {
|
|
1126
|
+
sandboxId,
|
|
1127
|
+
sessionId: this._client.sessionId,
|
|
1128
|
+
probeDurationMs: Date.now() - probeStartedAt,
|
|
1129
|
+
attempts
|
|
1130
|
+
});
|
|
1131
|
+
if (generation === this._probeGeneration && this._sandboxId === sandboxId) this._addressRegistry?.set(sandboxId, instanceUrl);
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
} catch {}
|
|
1135
|
+
await new Promise((r) => setTimeout(r, SIDECAR_PROBE_INTERVAL_MS));
|
|
1136
|
+
}
|
|
1137
|
+
this.logger.warn("platform-workspace probe timed out", {
|
|
1138
|
+
sandboxId,
|
|
1139
|
+
sessionId: this._client.sessionId,
|
|
1140
|
+
timeoutMs: SIDECAR_PROBE_TIMEOUT_MS,
|
|
1141
|
+
attempts
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Wait for the transport to become ready (sidecar probe succeeds) or time
|
|
1146
|
+
* out. Concurrent callers all await the same probe promise, coalescing the
|
|
1147
|
+
* cold-start storm into a single warmup attempt.
|
|
1148
|
+
*
|
|
1149
|
+
* If no probe is in flight (no registry, or registry already populated),
|
|
1150
|
+
* this returns immediately. After the wait (success or timeout), callers
|
|
1151
|
+
* check the registry and proceed — either via private-net or lease. The
|
|
1152
|
+
* lease path is still coalesced via `_leaseInFlight`, so even if the probe
|
|
1153
|
+
* times out, we only mint one lease for all concurrent execs.
|
|
1154
|
+
*/
|
|
1155
|
+
async _awaitTransportReady() {
|
|
1156
|
+
if (this._sandboxId && this._addressRegistry?.get(this._sandboxId)) return;
|
|
1157
|
+
if (!this._transportReadyPromise) return;
|
|
1158
|
+
await Promise.race([this._transportReadyPromise, new Promise((r) => setTimeout(r, TRANSPORT_READY_WAIT_MS))]);
|
|
1018
1159
|
}
|
|
1019
1160
|
/**
|
|
1020
1161
|
* Stop the sandbox while **preserving its recovery checkpoint**.
|
|
@@ -1079,12 +1220,17 @@ var PlatformSandbox = class PlatformSandbox extends _mastra_core_workspace.Mastr
|
|
|
1079
1220
|
async _teardownSandbox() {
|
|
1080
1221
|
if (!this._sandboxId) return;
|
|
1081
1222
|
const destroyedSandboxId = this._sandboxId;
|
|
1223
|
+
this._probeGeneration++;
|
|
1082
1224
|
await this._client.request(`/sandbox/${encodeURIComponent(destroyedSandboxId)}`, { method: "DELETE" });
|
|
1083
1225
|
this._sandboxId = void 0;
|
|
1084
1226
|
this._createdAt = null;
|
|
1085
1227
|
this._lease = null;
|
|
1086
1228
|
this._addressRegistry?.delete(destroyedSandboxId);
|
|
1087
1229
|
}
|
|
1230
|
+
/** Persist the configured recovery checkpoint when available. */
|
|
1231
|
+
async snapshot() {
|
|
1232
|
+
await this.captureCheckpoint();
|
|
1233
|
+
}
|
|
1088
1234
|
/**
|
|
1089
1235
|
* Capture the sandbox's checkpoint on demand, outside any refresh timer the
|
|
1090
1236
|
* workspace-proxy owns internally.
|
|
@@ -1233,6 +1379,7 @@ var PlatformSandbox = class PlatformSandbox extends _mastra_core_workspace.Mastr
|
|
|
1233
1379
|
const started = Date.now();
|
|
1234
1380
|
const fullCommand = buildCommand(command, args);
|
|
1235
1381
|
const effectiveTimeout = options?.timeout ?? this._timeout;
|
|
1382
|
+
await this._awaitTransportReady();
|
|
1236
1383
|
const instanceUrl = this._addressRegistry?.get(this._sandboxId);
|
|
1237
1384
|
if (instanceUrl) {
|
|
1238
1385
|
const privateNet = await this._tryExecViaPrivateNetwork(instanceUrl, fullCommand, effectiveTimeout, options);
|