@mastra/platform-workspace 1.2.0-alpha.1 → 1.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,111 @@
1
1
  # @mastra/platform
2
2
 
3
+ ## 1.2.0
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
+ - Split `PlatformSandbox.stop()` from `PlatformSandbox.destroy()` so the two lifecycle exits mirror `@mastra/railway` `RailwaySandbox` ([#20956](https://github.com/mastra-ai/mastra/pull/20956))
27
+
28
+ **Before:** `stop()` was an alias for `destroy()`, and `destroy()` only released the sandbox VM — the on-provider recovery checkpoint was never actively deleted. There was no way to end a hosted sandbox while preserving its checkpoint for a later resume, and destroyed sandboxes accumulated stray checkpoints until the upstream provider's own GC.
29
+
30
+ **After:**
31
+
32
+ - **`stop()`** — releases the VM but **preserves the recovery checkpoint**. Any in-flight capture is awaited first so the preserved checkpoint reflects the caller's latest state. Corresponds to `DELETE /v1/projects/:pid/sandbox/:sandboxId` on workspace-proxy, which by contract does not touch the checkpoint.
33
+ - **`destroy()`** — releases the VM **and deletes the recovery checkpoint**. Cancels any in-flight capture (no reason to burn a capture on state we're releasing), asks the proxy to delete the checkpoint via `DELETE /v1/projects/:pid/sandbox/:sandboxId/checkpoint`, then releases the VM. Both remote operations are best-effort — an already-absent checkpoint or a transient checkpoint-delete failure does not block the VM teardown, since a half-torn-down sandbox is worse than a lingering checkpoint alone.
34
+
35
+ Callers constructed without a recovery `id` skip the checkpoint DELETE and behave identically to `stop()`, because they have no on-provider checkpoint to release.
36
+
37
+ This restores the "providers move in lockstep" invariant that broke after `@mastra/railway` gained its own `stop()`/`destroy()` split.
38
+
39
+ **Requires** a matching workspace-proxy release that exposes `DELETE /v1/projects/:pid/sandbox/:sandboxId/checkpoint`. Callers on older workspace-proxy versions will see the checkpoint DELETE 404 and fall through to the VM DELETE — same net effect as the pre-split behavior.
40
+
41
+ ### Patch Changes
42
+
43
+ - Add public `captureCheckpoint()` method to `PlatformSandbox` — mirrors `@mastra/railway`'s `RailwaySandbox.captureCheckpoint()` so callers (e.g. a factory-side scheduler) can capture the recovery checkpoint on demand at semantic moments (turn end, session-idle, pre-teardown) without having to know which provider is underneath. ([#20882](https://github.com/mastra-ai/mastra/pull/20882))
44
+
45
+ ```ts
46
+ const result = await sandbox.captureCheckpoint();
47
+ switch (result.status) {
48
+ case 'captured':
49
+ case 'coalesced':
50
+ await persistBinding({ sessionId, checkpointName: result.checkpointName });
51
+ break;
52
+ case 'skipped':
53
+ // result.reason: 'no-checkpoint-name-configured' | 'sandbox-not-running'
54
+ break;
55
+ }
56
+ ```
57
+
58
+ - POSTs to `/v1/projects/:projectId/sandbox/:sandboxId/checkpoint` with the caller-supplied recovery key (the `id` the sandbox was constructed with) as the body, matching the shape the workspace-proxy expects.
59
+ - Coalesces concurrent callers on the same instance onto a single upstream request, so N simultaneous turn-end fires do not each round-trip the proxy.
60
+ - Returns `{ status: 'skipped', reason: 'no-checkpoint-name-configured' }` when the sandbox was constructed without a caller-supplied `id` (an auto-generated random id is never a meaningful recovery key), and `{ status: 'skipped', reason: 'sandbox-not-running' }` when the sandbox has not been started yet.
61
+ - Normalizes upstream "sandbox destroyed" outcomes (a 410 from the proxy, or the proxy's own `skipped` status) to `{ status: 'skipped', reason: 'sandbox-not-running' }` — the discriminant matches the pre-flight case so callers branch uniformly, and the sandbox's local state is cleared as a side effect so the next `start()` provisions fresh instead of reattaching to a dead id.
62
+ - Transport failures other than 410 (5xx, 429) propagate as `PlatformApiError` for the caller to handle.
63
+
64
+ - 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))
65
+
66
+ - Coalesce concurrent `PlatformSandbox.start()` callers onto a single in-flight attempt ([#20960](https://github.com/mastra-ai/mastra/pull/20960))
67
+
68
+ Two callers hitting `start()` on the same instance before the first one resolves used to both race to `POST /v1/projects/:pid/sandbox` (or `GET /sandbox/:id` on the reattach path), burning N proxy provisions and leaving `N-1` stray sandboxes behind. Fleet-level coalescing on the caller side masked most of this, but the underlying invariant "providers move in lockstep" was false — `@mastra/railway` `RailwaySandbox` has always had `_startInFlight` coalescing.
69
+
70
+ `start()` now publishes a single shared promise via `??=` **before** the first `await`, so a second caller entering `start()` while the first is mid-round-trip joins the existing promise instead of racing past the null check. The slot is cleared in `.finally()` on both success and failure paths so a failed attempt isn't a permanent latch — the next call starts fresh. Failures propagate to every joined caller.
71
+
72
+ Bug fix; no public API surface change. Callers already awaiting `start()` see the same success/failure semantics; the only observable difference is one upstream call instead of N.
73
+
74
+ - Improved `PlatformSandbox.getInfo()` to return cached sandbox information when the sandbox is known to be directly reachable, removing a redundant network round-trip on every workspace status poll. When no cached address is available, `getInfo()` behaves exactly as before. ([#20855](https://github.com/mastra-ai/mastra/pull/20855))
75
+
76
+ - Updated dependencies [[`e7109ee`](https://github.com/mastra-ai/mastra/commit/e7109ee6f731bacc79c885906f3c7dca8d8f013a), [`b8ce7ec`](https://github.com/mastra-ai/mastra/commit/b8ce7ec96e39343c6c2f36d12d68a9ad816c09f7), [`2e4624e`](https://github.com/mastra-ai/mastra/commit/2e4624edb6917e61249cb60ee377735e7af7e4a9), [`45a9147`](https://github.com/mastra-ai/mastra/commit/45a914741f578754d79d8b7de7b4e4f304d8e14a), [`a3a3624`](https://github.com/mastra-ai/mastra/commit/a3a3624f646b98e409424d8defccbd334da9e8b8), [`6246914`](https://github.com/mastra-ai/mastra/commit/62469146636911f3cbbe0880bd011c6a897a59a7), [`6445eba`](https://github.com/mastra-ai/mastra/commit/6445eba6020abac681aba1cc9289f446cb400cbe), [`86b7b77`](https://github.com/mastra-ai/mastra/commit/86b7b777980d30f66e1fd134a37d2af4c22e54cc), [`1c75e32`](https://github.com/mastra-ai/mastra/commit/1c75e32f7fc0b9fb6f548b4407feaec8a1440212), [`296dc9a`](https://github.com/mastra-ai/mastra/commit/296dc9af29f3616e786c7825ec32e0df92d754c5), [`f59032a`](https://github.com/mastra-ai/mastra/commit/f59032a73699443555a08a479e7ac578975784f2), [`cdd5c33`](https://github.com/mastra-ai/mastra/commit/cdd5c33ac6c7118a9f139e6dc0e14e6a8ae31658), [`3f73c07`](https://github.com/mastra-ai/mastra/commit/3f73c076727e8c36b4fff7a1b40290fb68957fa8), [`772c0c8`](https://github.com/mastra-ai/mastra/commit/772c0c897cec383258de2e6178147f8014767c7b), [`d7cf7fa`](https://github.com/mastra-ai/mastra/commit/d7cf7fafc1ae1b50bd8462dd0e6c671a8606db93), [`7c1ebb1`](https://github.com/mastra-ai/mastra/commit/7c1ebb15690c4b3f0eabb19077cf8af573311e57), [`0f9a448`](https://github.com/mastra-ai/mastra/commit/0f9a448502157e59f7b76f24360ad497168f5ef8), [`578bf2e`](https://github.com/mastra-ai/mastra/commit/578bf2e6a88e9d5b8bf502204e15a95dfbb679ae), [`c47165c`](https://github.com/mastra-ai/mastra/commit/c47165c983c87594c6952f1fd2fa51a90205034c), [`289f4ce`](https://github.com/mastra-ai/mastra/commit/289f4ce16e3293370440172132c52ee787cbc09f), [`df31eb0`](https://github.com/mastra-ai/mastra/commit/df31eb0c7087d782a0d9346e467f9a4af4b0eef6), [`9571e3a`](https://github.com/mastra-ai/mastra/commit/9571e3a06ed2c5220196460bf82a2129255c3a8b), [`4f16ff8`](https://github.com/mastra-ai/mastra/commit/4f16ff824bf2f9b0ddc93f210477c10c8a4fb1ab), [`b4c89b4`](https://github.com/mastra-ai/mastra/commit/b4c89b4371b0c86da57403ad1a3b3ef0681f3128), [`e6534fa`](https://github.com/mastra-ai/mastra/commit/e6534fab031216f6cb48c4c9907cbfdce9d60bc6), [`210cb7a`](https://github.com/mastra-ai/mastra/commit/210cb7a167998c7bbf72cb3b93e6eb0563330239), [`06b2d87`](https://github.com/mastra-ai/mastra/commit/06b2d87e63bcdd0ed59215c6789692b9b12de376), [`1c67d85`](https://github.com/mastra-ai/mastra/commit/1c67d85e9da8285662f4dbbf47e0378c3fee0747), [`ac01d63`](https://github.com/mastra-ai/mastra/commit/ac01d6355974aec73fdb8781449ed12bac582094), [`80a3324`](https://github.com/mastra-ai/mastra/commit/80a33245d3110204de6f56d61211523ffe338692), [`e44e8f3`](https://github.com/mastra-ai/mastra/commit/e44e8f370b66c339ddcaba946d33da6d3c3f06cd), [`d9d2881`](https://github.com/mastra-ai/mastra/commit/d9d2881ede6dd6c023d144215fc812062aed0890), [`a810a05`](https://github.com/mastra-ai/mastra/commit/a810a058f62ad407cfc1701e0be36ae91145d7cf), [`ba24be6`](https://github.com/mastra-ai/mastra/commit/ba24be662439c331ab23a600041f93803c89eca8), [`842b5fe`](https://github.com/mastra-ai/mastra/commit/842b5fe22b6a7fa811bd14e48eb9af523ac989f2), [`990611b`](https://github.com/mastra-ai/mastra/commit/990611ba76eb876d86c9c594371ae5f02f94b432), [`80bdf3a`](https://github.com/mastra-ai/mastra/commit/80bdf3ae16ade6ff63bde0cb16fa2df8ab7dd4dd), [`c967a5e`](https://github.com/mastra-ai/mastra/commit/c967a5eec150c5dc5418c4a4388982d1fb7ad27c), [`dc4a25d`](https://github.com/mastra-ai/mastra/commit/dc4a25d41af4e2fe97a816070eaec6aa963ab53b), [`9ba1247`](https://github.com/mastra-ai/mastra/commit/9ba12470c77f1c03642d720ce67e517e878f666e), [`fd96298`](https://github.com/mastra-ai/mastra/commit/fd96298a8367622f4ebfcaa97b5b6c1fbbd14564), [`66bbfb5`](https://github.com/mastra-ai/mastra/commit/66bbfb5f05b473d39f88c0e4a481ccac41634f3a), [`dc4a25d`](https://github.com/mastra-ai/mastra/commit/dc4a25d41af4e2fe97a816070eaec6aa963ab53b), [`f8da216`](https://github.com/mastra-ai/mastra/commit/f8da21633e7eb0e31c9ce0fc30567870d19416d3), [`4a09a9c`](https://github.com/mastra-ai/mastra/commit/4a09a9c0474ef643558fcb5f0edc542b82f1cab0), [`5f798b3`](https://github.com/mastra-ai/mastra/commit/5f798b3362e9bdf4d690f85245606e146eef60b9), [`6a84954`](https://github.com/mastra-ai/mastra/commit/6a84954a2667f85b6d59da652dab1bbff007ccb0), [`1e83a47`](https://github.com/mastra-ai/mastra/commit/1e83a4734ab61ba5926af6793e3569a78b72ed37), [`52d8ef0`](https://github.com/mastra-ai/mastra/commit/52d8ef03801f1deb7ee48532fc4190dd4a33916c), [`cdd5c33`](https://github.com/mastra-ai/mastra/commit/cdd5c33ac6c7118a9f139e6dc0e14e6a8ae31658), [`7fdcaa6`](https://github.com/mastra-ai/mastra/commit/7fdcaa66105d64290f9b14432a12ec99f39c4d3a), [`d6c56f9`](https://github.com/mastra-ai/mastra/commit/d6c56f951db3213330b98b0abafa9778c8770e58), [`e08e789`](https://github.com/mastra-ai/mastra/commit/e08e789c1bf4cd2fe46363f7a4728536ceccc9bd), [`bf936e2`](https://github.com/mastra-ai/mastra/commit/bf936e2c89b2ff0dad5695b873ddc009ba96d41e), [`7fb580a`](https://github.com/mastra-ai/mastra/commit/7fb580ac73fbcacf2ff00872a3395f73ae1b9fa5), [`ed5d606`](https://github.com/mastra-ai/mastra/commit/ed5d606739c5e3fbdfa9f272df7809aa5ab43b1d), [`f53d5bd`](https://github.com/mastra-ai/mastra/commit/f53d5bd4885b29e4ac29a428a6044088ea8d6aa3), [`32980a3`](https://github.com/mastra-ai/mastra/commit/32980a3e2413d0274ac244d32c37d910edc13f00), [`01a2943`](https://github.com/mastra-ai/mastra/commit/01a2943a7d886edefdff072bfa51f055bab54437), [`82e3365`](https://github.com/mastra-ai/mastra/commit/82e3365ef7c9bf7bee2e7a7029035ea262d68895), [`6104347`](https://github.com/mastra-ai/mastra/commit/61043473ba6bfd0a25156824e853e13165562e6c), [`35cc901`](https://github.com/mastra-ai/mastra/commit/35cc90102cf834a84827acaf9eee0b6d6d1e2a3b), [`a8b4cf0`](https://github.com/mastra-ai/mastra/commit/a8b4cf02823cffebc4751a53337dfacf097c1ae1), [`9571e3a`](https://github.com/mastra-ai/mastra/commit/9571e3a06ed2c5220196460bf82a2129255c3a8b), [`333785c`](https://github.com/mastra-ai/mastra/commit/333785c93cbb01e42c60167e995457c28897ddbf), [`bda2235`](https://github.com/mastra-ai/mastra/commit/bda22353ee28f2df0eaea555f7cae1549f979c0b), [`efd5c81`](https://github.com/mastra-ai/mastra/commit/efd5c81cc25fde3c2ddd86fc1178deb4ec176e19), [`1b482c2`](https://github.com/mastra-ai/mastra/commit/1b482c2d89244dd758c41e5f927a2b44041388d2), [`45bfb88`](https://github.com/mastra-ai/mastra/commit/45bfb88fd52f1dd3be20e2a38905777c96499c90), [`ff28284`](https://github.com/mastra-ai/mastra/commit/ff2828416f14daff9d956e6a352fdaa23c950979), [`4bcdfaf`](https://github.com/mastra-ai/mastra/commit/4bcdfaf0eac3199d7cb171b0a19a92c9c341eea4), [`e3b9307`](https://github.com/mastra-ai/mastra/commit/e3b9307098daefbfae2a52ae2ef51bc9fc701190), [`d6834c5`](https://github.com/mastra-ai/mastra/commit/d6834c5a7866b16734d23900163c2414ed70d791), [`f33264f`](https://github.com/mastra-ai/mastra/commit/f33264f517ae603279afd5c4251e2b40f6dd3618), [`689f2c4`](https://github.com/mastra-ai/mastra/commit/689f2c4b6c0835fe455702b01d21daa8abcd9331), [`fcd0667`](https://github.com/mastra-ai/mastra/commit/fcd0667a4e378be35c9a1b1eb19cce78fbfd7282), [`cfd0d9e`](https://github.com/mastra-ai/mastra/commit/cfd0d9ec77ec3c69dd96f79cdb579e03d79f22ce), [`acc3513`](https://github.com/mastra-ai/mastra/commit/acc3513b19f79bf0a7ec2998694580edca54086c), [`1670533`](https://github.com/mastra-ai/mastra/commit/1670533986f6bacf567746245348125e3a106448), [`a7eb4a1`](https://github.com/mastra-ai/mastra/commit/a7eb4a11450f6170274ed5141bffe821d4fdd5a6), [`0976933`](https://github.com/mastra-ai/mastra/commit/0976933142333ec78451feef265b68bcb45aa5e7), [`242b945`](https://github.com/mastra-ai/mastra/commit/242b94558777bfbdeb42cbfea84afff0b6ad0633), [`c52d346`](https://github.com/mastra-ai/mastra/commit/c52d3462ec831a5d95926ecd3d3373f5928ad2e5), [`af4636a`](https://github.com/mastra-ai/mastra/commit/af4636a74463275d71c1d13a38f7d2b738f128bf), [`01a2943`](https://github.com/mastra-ai/mastra/commit/01a2943a7d886edefdff072bfa51f055bab54437), [`2eabc09`](https://github.com/mastra-ai/mastra/commit/2eabc097d86d52fbd0123da36a7c874154cc384f), [`0023e79`](https://github.com/mastra-ai/mastra/commit/0023e7919431078280abd11c89d1edeae35fcc69), [`c2ad51e`](https://github.com/mastra-ai/mastra/commit/c2ad51e2467f901eecba8c9f4a45e22a50bd7c18), [`25ca73d`](https://github.com/mastra-ai/mastra/commit/25ca73d25dee7ce9f0ca72939e3a505c4db7257e), [`2f9ef3f`](https://github.com/mastra-ai/mastra/commit/2f9ef3f4ca06fc2dcdd5088c26b7f4da6a016791), [`e7eefcb`](https://github.com/mastra-ai/mastra/commit/e7eefcb162cda7c493e8c3bf43050ead0efbcb2c), [`fea5cae`](https://github.com/mastra-ai/mastra/commit/fea5caedc7e2cfea51784a15e015952692027abf), [`4d7aca2`](https://github.com/mastra-ai/mastra/commit/4d7aca2fe75f225c83d1502d63079568e6ec163f), [`e1cead1`](https://github.com/mastra-ai/mastra/commit/e1cead17b5f3653cf00d2f90cc19b113119c02ba), [`01a2943`](https://github.com/mastra-ai/mastra/commit/01a2943a7d886edefdff072bfa51f055bab54437), [`d9d93b2`](https://github.com/mastra-ai/mastra/commit/d9d93b25e4a65ad5fa153fa35be7ed149c8d587f), [`c4ec889`](https://github.com/mastra-ai/mastra/commit/c4ec889561c0264c43f66d04d587bee4ce35e792), [`4b59f78`](https://github.com/mastra-ai/mastra/commit/4b59f786cbc9a7d1ef07a07517dbd4b96865e99d), [`eeae63e`](https://github.com/mastra-ai/mastra/commit/eeae63e7fbe8e1f237adc69bca6e2ac13c5ca907), [`3dc97ea`](https://github.com/mastra-ai/mastra/commit/3dc97ea415fad353b48a13095fad1835933cc12a), [`94e7ae9`](https://github.com/mastra-ai/mastra/commit/94e7ae970b37c888cd1244ef013292639a2fe6d1), [`e6a2860`](https://github.com/mastra-ai/mastra/commit/e6a2860649cc51f87d32d78b766ae2126446ba07), [`7010c5d`](https://github.com/mastra-ai/mastra/commit/7010c5d15728bf9c5dfe4fb6b1bf80ce23bf143a), [`bab06b1`](https://github.com/mastra-ai/mastra/commit/bab06b18923873a584bdfc71a6b4ec7fb4727fb7), [`3d01cd3`](https://github.com/mastra-ai/mastra/commit/3d01cd387321b6f9c5cac31d487c84bf51b19c78), [`7bf3086`](https://github.com/mastra-ai/mastra/commit/7bf308663f0115ca74ad20554ade740f06640859), [`4c186a0`](https://github.com/mastra-ai/mastra/commit/4c186a017275f45e6ed4c09de0f89550e2d09e8c), [`b0fa077`](https://github.com/mastra-ai/mastra/commit/b0fa077bcbc9b08551846fe372a0d3d15b71ed72), [`0282e16`](https://github.com/mastra-ai/mastra/commit/0282e16115538c8e9b248b90f0748eb01cb5dc98), [`a8dd139`](https://github.com/mastra-ai/mastra/commit/a8dd1391a9fe9a6632c25809ef236980afa9a020), [`6a667b4`](https://github.com/mastra-ai/mastra/commit/6a667b4b7cd6a93fe41fcdd357b08c5a8c09b9ab), [`9be8878`](https://github.com/mastra-ai/mastra/commit/9be8878dcf0388e84fc4873e0eec27bd49b881a4), [`e5786be`](https://github.com/mastra-ai/mastra/commit/e5786be02bb903073082bd9d6da880ebaacc343f), [`2440e09`](https://github.com/mastra-ai/mastra/commit/2440e096ea6c2def1ccc1eb2d0f3f5b88c4af940), [`2093fbd`](https://github.com/mastra-ai/mastra/commit/2093fbd53bb744bae19ec89f6d73db9a66fbe8a7), [`a59049b`](https://github.com/mastra-ai/mastra/commit/a59049b1652a13efff66ac826326b5ed9a550342), [`7bd85ea`](https://github.com/mastra-ai/mastra/commit/7bd85ea7588b71c25ce9f4019c88f8539be5dcbc), [`83fa004`](https://github.com/mastra-ai/mastra/commit/83fa0044bfda8b703a83883dbd8bef204844d13f), [`a463cdf`](https://github.com/mastra-ai/mastra/commit/a463cdf1c95c3059e70f0bff27959e8558bb899d), [`e7a5da4`](https://github.com/mastra-ai/mastra/commit/e7a5da4ef8e4dd452d2f232961b4e682a85ffe43), [`7b4393d`](https://github.com/mastra-ai/mastra/commit/7b4393d557411fdcf07b0e30e5acaf7cc85154ae), [`0ea6b80`](https://github.com/mastra-ai/mastra/commit/0ea6b8001408ce02b56e8be0536b0fd8cbaf8ad2)]:
77
+ - @mastra/core@1.58.0
78
+
79
+ ## 1.2.0-alpha.2
80
+
81
+ ### Minor Changes
82
+
83
+ - 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))
84
+
85
+ ```ts
86
+ import { PlatformSandbox } from '@mastra/platform-workspace';
87
+
88
+ const sandbox = new PlatformSandbox({
89
+ projectId: 'proj_123',
90
+ environmentId: 'env_123',
91
+ sessionId: 'session_abc', // correlate startup logs with your session
92
+ threadId: 'thread_xyz', // optional finer-grained correlation
93
+ });
94
+ ```
95
+
96
+ - Added `PlatformSandbox.snapshot()` to capture the configured recovery checkpoint. ([#21221](https://github.com/mastra-ai/mastra/pull/21221))
97
+
98
+ ```ts
99
+ await sandbox.snapshot();
100
+ ```
101
+
102
+ ### Patch Changes
103
+
104
+ - 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))
105
+
106
+ - 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)]:
107
+ - @mastra/core@1.58.0-alpha.13
108
+
3
109
  ## 1.2.0-alpha.1
4
110
 
5
111
  ### 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>;
@@ -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;;;;;EAOpE;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,QAAQ,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC;gBAEjB,OAAO,EAAE,qBAAqB;IAQpC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,QAAQ,CAAC;CAoBrF"}
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 json = await (await this._client.request(`/sandbox/${encodeURIComponent(this._sandboxId)}`)).json();
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.set(json.id, json.instanceUrl);
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);