@mastra/platform-workspace 0.2.4-alpha.0 → 1.0.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 +56 -0
- package/README.md +15 -8
- package/dist/client.d.ts.map +1 -1
- package/dist/index.cjs +128 -79
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +127 -80
- package/dist/index.js.map +1 -1
- package/dist/sandbox.d.ts +71 -12
- package/dist/sandbox.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ function requireOption(value, name) {
|
|
|
15
15
|
}
|
|
16
16
|
function resolvePlatformOptions(options) {
|
|
17
17
|
return {
|
|
18
|
-
accessToken: requireOption(options.accessToken ?? process.env.
|
|
18
|
+
accessToken: requireOption(options.accessToken ?? process.env.MASTRA_PLATFORM_ACCESS_TOKEN, "accessToken"),
|
|
19
19
|
projectId: requireOption(options.projectId ?? process.env.MASTRA_PROJECT_ID, "projectId"),
|
|
20
20
|
proxyUrl: (process.env.MASTRA_WORKSPACE_PROXY_URL ?? DEFAULT_PROXY_URL).replace(/\/$/, ""),
|
|
21
21
|
fetch: options.fetch ?? fetch
|
|
@@ -500,6 +500,59 @@ const CREATE_MAX_ATTEMPTS = 3;
|
|
|
500
500
|
/** Base delay between create retries; multiplied by the attempt number. */
|
|
501
501
|
const CREATE_RETRY_BASE_DELAY_MS = 2e3;
|
|
502
502
|
/**
|
|
503
|
+
* Diagnostic error thrown when the direct-exec WebSocket transport fails
|
|
504
|
+
* twice in a row (opening handshake refused or socket closed mid-stream
|
|
505
|
+
* without an `exit` frame). Distinguishes "the sandbox transport is broken"
|
|
506
|
+
* from "your command failed" so callers can decide whether to retry at a
|
|
507
|
+
* higher level (e.g. reprovision the sandbox) or surface the error.
|
|
508
|
+
*
|
|
509
|
+
* `opened` is `true` when the WebSocket completed its handshake at least
|
|
510
|
+
* once before closing; `false` when Railway refused the upgrade outright.
|
|
511
|
+
*/
|
|
512
|
+
var SandboxExecTransportError = class extends Error {
|
|
513
|
+
sandboxId;
|
|
514
|
+
command;
|
|
515
|
+
attempts;
|
|
516
|
+
opened;
|
|
517
|
+
closeCode;
|
|
518
|
+
closeReason;
|
|
519
|
+
wsEndpoint;
|
|
520
|
+
constructor(message, diagnostics) {
|
|
521
|
+
super(message);
|
|
522
|
+
this.name = "SandboxExecTransportError";
|
|
523
|
+
this.sandboxId = diagnostics.sandboxId;
|
|
524
|
+
this.command = diagnostics.command;
|
|
525
|
+
this.attempts = diagnostics.attempts;
|
|
526
|
+
this.opened = diagnostics.opened;
|
|
527
|
+
this.closeCode = diagnostics.closeCode;
|
|
528
|
+
this.closeReason = diagnostics.closeReason;
|
|
529
|
+
this.wsEndpoint = diagnostics.wsEndpoint;
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
/**
|
|
533
|
+
* Thrown when `/exec-lease` returns 410 Gone — the sandbox has been destroyed
|
|
534
|
+
* (Railway destroy, quota reclamation, etc.). The client cannot recover from
|
|
535
|
+
* this on its own because it does not own the binding store; only the fleet
|
|
536
|
+
* layer can clear the stale sandbox id and provision a fresh one. Callers
|
|
537
|
+
* (typically `SandboxFleet`) must catch this and reprovision-and-replay.
|
|
538
|
+
*
|
|
539
|
+
* When this is thrown the cached `_lease` and `_sandboxId` on the sandbox
|
|
540
|
+
* instance are cleared, so the next `ensureRunning()` on a reused instance
|
|
541
|
+
* will re-provision cleanly.
|
|
542
|
+
*/
|
|
543
|
+
var SandboxDestroyedError = class extends Error {
|
|
544
|
+
sandboxId;
|
|
545
|
+
command;
|
|
546
|
+
attempts;
|
|
547
|
+
constructor(message, diagnostics) {
|
|
548
|
+
super(message);
|
|
549
|
+
this.name = "SandboxDestroyedError";
|
|
550
|
+
this.sandboxId = diagnostics.sandboxId;
|
|
551
|
+
this.command = diagnostics.command;
|
|
552
|
+
this.attempts = diagnostics.attempts;
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
/**
|
|
503
556
|
* Compose a shell command line from a `command` string and optional `args`.
|
|
504
557
|
*
|
|
505
558
|
* IMPORTANT: `command` is treated as a **shell string** and passed to the
|
|
@@ -599,16 +652,6 @@ var PlatformSandbox = class PlatformSandbox extends MastraSandbox {
|
|
|
599
652
|
* Cleared (regardless of success or failure) when the request settles.
|
|
600
653
|
*/
|
|
601
654
|
_leaseInFlight = null;
|
|
602
|
-
/**
|
|
603
|
-
* Tri-state feature detection for the platform's exec-lease endpoint:
|
|
604
|
-
* undefined — not yet tried (default; try direct on first exec)
|
|
605
|
-
* true — endpoint present, use direct exec
|
|
606
|
-
* false — endpoint absent (404/501) OR the WebSocket transport failed
|
|
607
|
-
* once; fall back permanently to /exec for this sandbox
|
|
608
|
-
* Sticky per instance so we make the fallback decision once per sandbox
|
|
609
|
-
* lifetime instead of paying an extra round-trip on every exec.
|
|
610
|
-
*/
|
|
611
|
-
_directExecAvailable = void 0;
|
|
612
655
|
constructor(options = {}) {
|
|
613
656
|
super({
|
|
614
657
|
...options,
|
|
@@ -728,47 +771,7 @@ var PlatformSandbox = class PlatformSandbox extends MastraSandbox {
|
|
|
728
771
|
const started = Date.now();
|
|
729
772
|
const fullCommand = buildCommand(command, args);
|
|
730
773
|
const effectiveTimeout = options?.timeout ?? this._timeout;
|
|
731
|
-
|
|
732
|
-
const leaseResult = await this._tryDirectExec(fullCommand, effectiveTimeout, options);
|
|
733
|
-
if (leaseResult) return {
|
|
734
|
-
...leaseResult,
|
|
735
|
-
executionTimeMs: Date.now() - started
|
|
736
|
-
};
|
|
737
|
-
}
|
|
738
|
-
return this._execViaProxy(fullCommand, effectiveTimeout, options, started);
|
|
739
|
-
}
|
|
740
|
-
async _tryDirectExec(fullCommand, effectiveTimeout, options) {
|
|
741
|
-
let lease;
|
|
742
|
-
try {
|
|
743
|
-
lease = await this._ensureLease();
|
|
744
|
-
} catch (error) {
|
|
745
|
-
if (error instanceof PlatformApiError && (error.status === 404 || error.status === 501)) {
|
|
746
|
-
this._directExecAvailable = false;
|
|
747
|
-
return null;
|
|
748
|
-
}
|
|
749
|
-
throw error;
|
|
750
|
-
}
|
|
751
|
-
this._directExecAvailable = true;
|
|
752
|
-
const filteredEnv = options?.env ? Object.fromEntries(Object.entries(options.env).filter((entry) => entry[1] !== void 0)) : void 0;
|
|
753
|
-
const result = await execViaLease(lease, {
|
|
754
|
-
command: fullCommand,
|
|
755
|
-
...options?.cwd !== void 0 && { cwd: options.cwd },
|
|
756
|
-
...filteredEnv !== void 0 && { env: filteredEnv },
|
|
757
|
-
...effectiveTimeout != null && effectiveTimeout > 0 && { timeoutMs: effectiveTimeout },
|
|
758
|
-
...this._webSocketFactory && { webSocketFactory: this._webSocketFactory }
|
|
759
|
-
});
|
|
760
|
-
if (result.exitCode === null && !result.timedOut) {
|
|
761
|
-
console.warn("[platform-workspace] direct-exec transport failed; falling back to /exec permanently for this sandbox", {
|
|
762
|
-
sandboxId: this._sandboxId,
|
|
763
|
-
opened: result.opened,
|
|
764
|
-
closeCode: result.closeCode,
|
|
765
|
-
closeReason: result.closeReason,
|
|
766
|
-
wsEndpoint: lease.wsEndpoint
|
|
767
|
-
});
|
|
768
|
-
this._lease = null;
|
|
769
|
-
this._directExecAvailable = false;
|
|
770
|
-
return null;
|
|
771
|
-
}
|
|
774
|
+
const result = await this._runDirectExec(fullCommand, effectiveTimeout, options);
|
|
772
775
|
const exitCode = result.exitCode ?? 124;
|
|
773
776
|
return {
|
|
774
777
|
success: exitCode === 0,
|
|
@@ -776,34 +779,78 @@ var PlatformSandbox = class PlatformSandbox extends MastraSandbox {
|
|
|
776
779
|
stdout: result.stdout,
|
|
777
780
|
stderr: result.stderr,
|
|
778
781
|
timedOut: result.timedOut,
|
|
779
|
-
command: fullCommand
|
|
782
|
+
command: fullCommand,
|
|
783
|
+
executionTimeMs: Date.now() - started
|
|
780
784
|
};
|
|
781
785
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
786
|
+
/**
|
|
787
|
+
* Run a single exec against the direct-exec transport, with one in-flight
|
|
788
|
+
* retry on WebSocket transport failure (socket closed without an `exit`
|
|
789
|
+
* frame and the exec did not time out). The retry mints a fresh lease
|
|
790
|
+
* — the failure could be a stale JWT — and reopens a new WebSocket.
|
|
791
|
+
*
|
|
792
|
+
* Error taxonomy:
|
|
793
|
+
* - **410 on `/exec-lease`** (either attempt) → the sandbox is gone.
|
|
794
|
+
* Nulls the cached `_lease` and `_sandboxId` and throws
|
|
795
|
+
* {@link SandboxDestroyedError}. Callers (typically `SandboxFleet`) must
|
|
796
|
+
* catch this, clear the stale binding, and reprovision + replay.
|
|
797
|
+
* - **Persistent transport failure** (both WS attempts close without an
|
|
798
|
+
* `exit` frame against a live sandbox) → {@link SandboxExecTransportError}
|
|
799
|
+
* with WebSocket close diagnostics.
|
|
800
|
+
* - **Other `PlatformApiError`s** (404/500/501) propagate directly.
|
|
801
|
+
* - **Real command result** (exit code from Railway's exit frame, or
|
|
802
|
+
* `timedOut: true`) returns normally.
|
|
803
|
+
*
|
|
804
|
+
* Returns a result with a real `exitCode` OR `timedOut: true`. Never
|
|
805
|
+
* returns `{ exitCode: null, timedOut: false }` — that case throws.
|
|
806
|
+
*/
|
|
807
|
+
async _runDirectExec(fullCommand, effectiveTimeout, options) {
|
|
808
|
+
const filteredEnv = options?.env ? Object.fromEntries(Object.entries(options.env).filter((entry) => entry[1] !== void 0)) : void 0;
|
|
809
|
+
let lastResult;
|
|
810
|
+
let lastLease;
|
|
811
|
+
let attemptsMade = 0;
|
|
812
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
813
|
+
if (attempt > 0 && lastLease && this._lease === lastLease) this._lease = null;
|
|
814
|
+
let lease;
|
|
815
|
+
try {
|
|
816
|
+
lease = await this._ensureLease();
|
|
817
|
+
} catch (error) {
|
|
818
|
+
if (error instanceof PlatformApiError && error.status === 410) {
|
|
819
|
+
this._lease = null;
|
|
820
|
+
const priorSandboxId = this._sandboxId;
|
|
821
|
+
this._sandboxId = void 0;
|
|
822
|
+
throw new SandboxDestroyedError(`Sandbox ${priorSandboxId ?? "(unknown)"} was destroyed; /exec-lease returned 410`, {
|
|
823
|
+
...priorSandboxId && { sandboxId: priorSandboxId },
|
|
824
|
+
command: fullCommand,
|
|
825
|
+
attempts: attempt + 1
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
throw error;
|
|
829
|
+
}
|
|
830
|
+
lastLease = lease;
|
|
831
|
+
attemptsMade = attempt + 1;
|
|
832
|
+
const result = await execViaLease(lease, {
|
|
790
833
|
command: fullCommand,
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
834
|
+
...options?.cwd !== void 0 && { cwd: options.cwd },
|
|
835
|
+
...filteredEnv !== void 0 && { env: filteredEnv },
|
|
836
|
+
...effectiveTimeout != null && effectiveTimeout > 0 && { timeoutMs: effectiveTimeout },
|
|
837
|
+
...this._webSocketFactory && { webSocketFactory: this._webSocketFactory }
|
|
838
|
+
});
|
|
839
|
+
lastResult = result;
|
|
840
|
+
if (result.exitCode !== null || result.timedOut) return result;
|
|
841
|
+
}
|
|
842
|
+
const result = lastResult;
|
|
843
|
+
const lease = lastLease;
|
|
844
|
+
if (this._lease === lease) this._lease = null;
|
|
845
|
+
throw new SandboxExecTransportError(`Direct-exec transport failed for sandbox ${this._sandboxId ?? "(unknown)"} after ${attemptsMade} attempt(s)` + (result.closeCode !== void 0 ? ` (close ${result.closeCode}${result.closeReason ? ` ${result.closeReason}` : ""})` : ""), {
|
|
846
|
+
...this._sandboxId && { sandboxId: this._sandboxId },
|
|
847
|
+
command: fullCommand,
|
|
848
|
+
attempts: attemptsMade,
|
|
849
|
+
opened: result.opened ?? false,
|
|
850
|
+
...result.closeCode !== void 0 && { closeCode: result.closeCode },
|
|
851
|
+
...result.closeReason !== void 0 && { closeReason: result.closeReason },
|
|
852
|
+
wsEndpoint: lease.wsEndpoint
|
|
853
|
+
});
|
|
807
854
|
}
|
|
808
855
|
/**
|
|
809
856
|
* Return a cached exec lease, minting a fresh one when the cache is empty
|
|
@@ -881,7 +928,7 @@ const platformSandboxProvider = {
|
|
|
881
928
|
properties: {
|
|
882
929
|
accessToken: {
|
|
883
930
|
type: "string",
|
|
884
|
-
description: "Mastra Platform
|
|
931
|
+
description: "Mastra Platform access token (falls back to MASTRA_PLATFORM_ACCESS_TOKEN)"
|
|
885
932
|
},
|
|
886
933
|
projectId: {
|
|
887
934
|
type: "string",
|
|
@@ -927,7 +974,7 @@ const platformFilesystemProvider = {
|
|
|
927
974
|
properties: {
|
|
928
975
|
accessToken: {
|
|
929
976
|
type: "string",
|
|
930
|
-
description: "Mastra Platform
|
|
977
|
+
description: "Mastra Platform access token (falls back to MASTRA_PLATFORM_ACCESS_TOKEN)"
|
|
931
978
|
},
|
|
932
979
|
projectId: {
|
|
933
980
|
type: "string",
|
|
@@ -947,6 +994,6 @@ const platformFilesystemProvider = {
|
|
|
947
994
|
createFilesystem: (config) => new PlatformFilesystem(config)
|
|
948
995
|
};
|
|
949
996
|
//#endregion
|
|
950
|
-
export { PlatformApiError, PlatformClient, PlatformFilesystem, PlatformSandbox, platformFilesystemProvider, platformSandboxProvider };
|
|
997
|
+
export { PlatformApiError, PlatformClient, PlatformFilesystem, PlatformSandbox, SandboxDestroyedError, SandboxExecTransportError, platformFilesystemProvider, platformSandboxProvider };
|
|
951
998
|
|
|
952
999
|
//# sourceMappingURL=index.js.map
|