@mastra/factory 0.12.0-alpha.10 → 0.12.0-alpha.11
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/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/packages/_internals/workspace/dist/index.js +25 -0
- package/dist/packages/_internals/workspace/dist/index.js.map +1 -0
- package/dist/sandbox/session-sandbox.d.ts +22 -8
- package/dist/sandbox/session-sandbox.d.ts.map +1 -1
- package/dist/sandbox/session-sandbox.js +33 -37
- package/dist/sandbox/session-sandbox.js.map +1 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +5 -4
- package/dist/workspace.js.map +1 -1
- package/package.json +8 -7
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { MastraFactory } from './factory.js';
|
|
2
2
|
export type { MastraArgs, MastraFactoryConfig, MastraFactorySandboxConfig } from './factory.js';
|
|
3
|
-
export type { FactorySandboxContext, SessionSetupRun } from './sandbox/session-sandbox.js';
|
|
3
|
+
export type { FactorySandboxContext, SessionSetupGate, SessionSetupRun } from './sandbox/session-sandbox.js';
|
|
4
4
|
export { ChannelIdentityStorage } from './storage/domains/channel-identity/base.js';
|
|
5
5
|
export type { ChannelAccountLink, ChannelAccountLinkEntry, ChannelAccountLinkKey, ChannelAccountLinkNames, } from './storage/domains/channel-identity/base.js';
|
|
6
6
|
export { FactoryProjectsStorage } from './storage/domains/projects/base.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,YAAY,EAAE,UAAU,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAChG,YAAY,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,YAAY,EAAE,UAAU,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAChG,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC7G,OAAO,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAC;AACpF,YAAY,EACV,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAC5E,YAAY,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AACxE,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAC7F,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,6BAA6B,EAAE,sCAAsC,EAAE,MAAM,wBAAwB,CAAC;AAC/G,YAAY,EACV,sBAAsB,EACtB,uBAAuB,EACvB,6BAA6B,EAC7B,0BAA0B,GAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnD,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAInD,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createHash } from "crypto";
|
|
2
|
+
//#region ../../packages/_internals/workspace/dist/index.js
|
|
3
|
+
/**
|
|
4
|
+
* Setup completion marker shared by repo templates and their consumers.
|
|
5
|
+
*
|
|
6
|
+
* A repo template writes this file beside the checkout as its last build
|
|
7
|
+
* step, so it exists only in images where every setup command succeeded. Its
|
|
8
|
+
* content is a digest of the setup commands the image ran, letting a sandbox
|
|
9
|
+
* booted from the image tell whether the setup it is about to run already
|
|
10
|
+
* happened. Relative to the template's build cwd, which is also the runtime
|
|
11
|
+
* working directory the repo was cloned into.
|
|
12
|
+
*/
|
|
13
|
+
const SETUP_MARKER_PATH = ".mastra-sandbox/setup";
|
|
14
|
+
/** Blank entries never become build steps, so they never count toward the digest either. */
|
|
15
|
+
function normalizeSetupCommands(setupCommand) {
|
|
16
|
+
return (setupCommand === void 0 ? [] : Array.isArray(setupCommand) ? setupCommand : [setupCommand]).filter((command) => command.trim() !== "");
|
|
17
|
+
}
|
|
18
|
+
/** The marker content for a setup command list: `sha256:<hex>` over the commands joined by newlines. */
|
|
19
|
+
function setupMarkerContent(setupCommand) {
|
|
20
|
+
return `sha256:${createHash("sha256").update(normalizeSetupCommands(setupCommand).join("\n")).digest("hex")}`;
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { SETUP_MARKER_PATH, normalizeSetupCommands, setupMarkerContent };
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../../../../../packages/_internals/workspace/dist/index.js"],"sourcesContent":["import { createHash } from \"crypto\";\n//#region src/setup-marker.ts\n/**\n* Setup completion marker shared by repo templates and their consumers.\n*\n* A repo template writes this file beside the checkout as its last build\n* step, so it exists only in images where every setup command succeeded. Its\n* content is a digest of the setup commands the image ran, letting a sandbox\n* booted from the image tell whether the setup it is about to run already\n* happened. Relative to the template's build cwd, which is also the runtime\n* working directory the repo was cloned into.\n*/\nconst SETUP_MARKER_PATH = \".mastra-sandbox/setup\";\n/** Blank entries never become build steps, so they never count toward the digest either. */\nfunction normalizeSetupCommands(setupCommand) {\n\treturn (setupCommand === void 0 ? [] : Array.isArray(setupCommand) ? setupCommand : [setupCommand]).filter((command) => command.trim() !== \"\");\n}\n/** The marker content for a setup command list: `sha256:<hex>` over the commands joined by newlines. */\nfunction setupMarkerContent(setupCommand) {\n\treturn `sha256:${createHash(\"sha256\").update(normalizeSetupCommands(setupCommand).join(\"\\n\")).digest(\"hex\")}`;\n}\n/** Shell step that writes the marker relative to the cwd. `content` is a digest, so it is shell-safe. */\nfunction setupMarkerCommand(content) {\n\treturn `mkdir -p \"$(dirname \"${SETUP_MARKER_PATH}\")\" && printf '%s' '${content}' > \"${SETUP_MARKER_PATH}\"`;\n}\n//#endregion\nexport { SETUP_MARKER_PATH, normalizeSetupCommands, setupMarkerCommand, setupMarkerContent };\n\n//# sourceMappingURL=index.js.map"],"mappings":";;;;;;;;;;;;AAYA,MAAM,oBAAoB;;AAE1B,SAAS,uBAAuB,cAAc;CAC7C,QAAQ,iBAAiB,KAAK,IAAI,CAAC,IAAI,MAAM,QAAQ,YAAY,IAAI,eAAe,CAAC,YAAY,EAAA,CAAG,QAAQ,YAAY,QAAQ,KAAK,MAAM,EAAE;AAC9I;;AAEA,SAAS,mBAAmB,cAAc;CACzC,OAAO,UAAU,WAAW,QAAQ,CAAC,CAAC,OAAO,uBAAuB,YAAY,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,KAAK;AAC3G"}
|
|
@@ -52,8 +52,22 @@ export interface FactorySandboxContext {
|
|
|
52
52
|
* ```
|
|
53
53
|
*/
|
|
54
54
|
export type MastraFactorySandboxConfig = (ctx: FactorySandboxContext) => MastraSandbox;
|
|
55
|
-
/**
|
|
56
|
-
|
|
55
|
+
/**
|
|
56
|
+
* What the start hook learned about the setup command before running the
|
|
57
|
+
* session setup, and how to record its completion afterwards.
|
|
58
|
+
*/
|
|
59
|
+
export interface SessionSetupGate {
|
|
60
|
+
/** True when the sandbox already carries a marker for the current setup command. */
|
|
61
|
+
setupDone: boolean;
|
|
62
|
+
/** Write the marker once the setup command succeeded. Best-effort. */
|
|
63
|
+
markSetupDone: () => Promise<void>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The session's setup work, run against a started sandbox on EVERY start.
|
|
67
|
+
* Materialize and checkout are idempotent and must always run (a warm boot
|
|
68
|
+
* still needs its pull); only the setup command consults `gate`.
|
|
69
|
+
*/
|
|
70
|
+
export type SessionSetupRun = (sandbox: WorkspaceSandbox, workdir: string, gate: SessionSetupGate) => Promise<void>;
|
|
57
71
|
/**
|
|
58
72
|
* Per-process session-id → sandbox instance memo.
|
|
59
73
|
*
|
|
@@ -106,12 +120,12 @@ export declare function recordFailedSetupCommand(sessionId: string, command: str
|
|
|
106
120
|
export declare function hasFailedSetupCommand(sessionId: string, command: string): boolean;
|
|
107
121
|
/**
|
|
108
122
|
* Build the session setup hook, which factory attaches to the constructed
|
|
109
|
-
* sandbox with `setOnStart`. Runs inside the sandbox start
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* onStart errors as fatal.
|
|
123
|
+
* sandbox with `setOnStart`. Runs inside the sandbox start lifecycle on
|
|
124
|
+
* every start, fresh VM or reconnect: materialize and checkout always run,
|
|
125
|
+
* and the setup command runs unless the sandbox already carries the marker
|
|
126
|
+
* for it (a warm template image, or an earlier successful start). Throwing
|
|
127
|
+
* fails `start()` loudly; core treats onStart errors as fatal.
|
|
114
128
|
*/
|
|
115
|
-
export declare function createSessionSetupHook(run: SessionSetupRun, sessionId: string, repoFullName: string): SandboxStartHook;
|
|
129
|
+
export declare function createSessionSetupHook(run: SessionSetupRun, sessionId: string, repoFullName: string, setupCommand: string | undefined): SandboxStartHook;
|
|
116
130
|
export {};
|
|
117
131
|
//# sourceMappingURL=session-sandbox.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-sandbox.d.ts","sourceRoot":"","sources":["../../src/sandbox/session-sandbox.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"session-sandbox.d.ts","sourceRoot":"","sources":["../../src/sandbox/session-sandbox.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAChG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAG3E;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;OAWG;IACH,mBAAmB,EAAE,CAAC,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAC,GAAG,SAAS,CAAC;CACpE;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,GAAG,EAAE,qBAAqB,KAAK,aAAa,CAAC;AAEvF;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oFAAoF;IACpF,SAAS,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC;AAED;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEpH;;;;;;;;;GASG;AACH,UAAU,mBAAmB;IAC3B,OAAO,EAAE,gBAAgB,CAAC;IAC1B;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,gBAAgB,GAChC,mBAAmB,CAQrB;AAED;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,gBAAgB,EACzB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,CASjB;AAmBD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAErF;AAED,uFAAuF;AACvF,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAG3D;AAED,4DAA4D;AAC5D,wBAAgB,+BAA+B,IAAI,IAAI,CAGtD;AAcD,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAEjF;AAED,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAEjF;AAqCD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,eAAe,EACpB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,GAAG,SAAS,GAC/B,gBAAgB,CAkBlB"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { SETUP_MARKER_PATH, setupMarkerContent } from "../packages/_internals/workspace/dist/index.js";
|
|
1
2
|
import { deriveLocalWorkdir, deriveRemoteRepoDir, repoDirUnder } from "./workdir.js";
|
|
3
|
+
import path from "path";
|
|
2
4
|
//#region src/sandbox/session-sandbox.ts
|
|
3
5
|
const sessionSandboxes = /* @__PURE__ */ new Map();
|
|
4
6
|
/**
|
|
@@ -79,50 +81,44 @@ function hasFailedSetupCommand(sessionId, command) {
|
|
|
79
81
|
return failedSetupCommands.get(sessionId) === command;
|
|
80
82
|
}
|
|
81
83
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
84
|
+
* The setup completion marker is a convention shared with the repo templates
|
|
85
|
+
* (`@internal/workspace`): `.mastra-sandbox/setup` beside the checkout,
|
|
86
|
+
* containing a digest of the setup commands. Templates write it as their last
|
|
87
|
+
* build step, so a sandbox booted from a warm image already carries it; the
|
|
88
|
+
* start hook writes it after a successful runtime setup. It is a skip cache,
|
|
89
|
+
* not a correctness mechanism: the setup command is assumed idempotent, and a
|
|
90
|
+
* missing or mismatched marker only re-runs it.
|
|
91
|
+
*
|
|
92
|
+
* The working directory is the parent of the repo dir, which is also the
|
|
93
|
+
* template's build cwd, so this is the file the template's marker step wrote.
|
|
87
94
|
*/
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return sandbox.provider === "local" ? `./${SESSION_SETUP_MARKER}` : `$HOME/${SESSION_SETUP_MARKER}`;
|
|
95
|
+
function markerShellPath(workdir) {
|
|
96
|
+
return `${path.posix.dirname(workdir)}/${SETUP_MARKER_PATH}`;
|
|
91
97
|
}
|
|
92
|
-
async function
|
|
93
|
-
|
|
98
|
+
async function markerMatches(sandbox, workdir, content) {
|
|
99
|
+
const marker = markerShellPath(workdir);
|
|
100
|
+
return (await sandbox.executeCommand(`test -d "${workdir}/.git" && test -f "${marker}" && [ "$(cat "${marker}")" = "${content}" ]`)).exitCode === 0;
|
|
94
101
|
}
|
|
95
|
-
async function writeMarker(sandbox) {
|
|
96
|
-
const marker = markerShellPath(
|
|
97
|
-
await sandbox.executeCommand(`mkdir -p "$(dirname "${marker}")" &&
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Run the session setup, marker-guarded: skip when the marker exists (unless
|
|
101
|
-
* the VM is known-fresh), otherwise run and write the marker only on
|
|
102
|
-
* success. Setup failures propagate — no marker is written, so the next
|
|
103
|
-
* attempt re-runs.
|
|
104
|
-
*/
|
|
105
|
-
async function runGuardedSetup(sandbox, run, { skipMarkerProbe, sessionId, repoFullName }) {
|
|
106
|
-
if (!sandbox.executeCommand) throw new Error(`Sandbox '${sandbox.id}' cannot run the session setup: no executeCommand implementation`);
|
|
107
|
-
const workdir = await resolveSessionWorkdir(sessionId, sandbox, repoFullName);
|
|
108
|
-
if (!skipMarkerProbe && await markerPresent(sandbox, workdir)) return;
|
|
109
|
-
await run(sandbox, workdir);
|
|
110
|
-
await writeMarker(sandbox);
|
|
102
|
+
async function writeMarker(sandbox, workdir, content) {
|
|
103
|
+
const marker = markerShellPath(workdir);
|
|
104
|
+
await sandbox.executeCommand(`mkdir -p "$(dirname "${marker}")" && printf '%s' '${content}' > "${marker}"`).catch(() => {});
|
|
111
105
|
}
|
|
112
106
|
/**
|
|
113
107
|
* Build the session setup hook, which factory attaches to the constructed
|
|
114
|
-
* sandbox with `setOnStart`. Runs inside the sandbox start
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* onStart errors as fatal.
|
|
108
|
+
* sandbox with `setOnStart`. Runs inside the sandbox start lifecycle on
|
|
109
|
+
* every start, fresh VM or reconnect: materialize and checkout always run,
|
|
110
|
+
* and the setup command runs unless the sandbox already carries the marker
|
|
111
|
+
* for it (a warm template image, or an earlier successful start). Throwing
|
|
112
|
+
* fails `start()` loudly; core treats onStart errors as fatal.
|
|
119
113
|
*/
|
|
120
|
-
function createSessionSetupHook(run, sessionId, repoFullName) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
114
|
+
function createSessionSetupHook(run, sessionId, repoFullName, setupCommand) {
|
|
115
|
+
const marker = setupCommand?.trim() ? setupMarkerContent(setupCommand) : void 0;
|
|
116
|
+
return async ({ sandbox }) => {
|
|
117
|
+
if (!sandbox.executeCommand) throw new Error(`Sandbox '${sandbox.id}' cannot run the session setup: no executeCommand implementation`);
|
|
118
|
+
const workdir = await resolveSessionWorkdir(sessionId, sandbox, repoFullName);
|
|
119
|
+
await run(sandbox, workdir, {
|
|
120
|
+
setupDone: marker ? await markerMatches(sandbox, workdir, marker) : true,
|
|
121
|
+
markSetupDone: () => marker ? writeMarker(sandbox, workdir, marker) : Promise.resolve()
|
|
126
122
|
});
|
|
127
123
|
};
|
|
128
124
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-sandbox.js","names":[],"sources":["../../src/sandbox/session-sandbox.ts"],"sourcesContent":["import type { MastraSandbox, SandboxStartHook, WorkspaceSandbox } from '@mastra/core/workspace';\nimport type { RepositoryAccess } from '../capabilities/version-control.js';\nimport { deriveLocalWorkdir, deriveRemoteRepoDir, repoDirUnder } from './workdir.js';\n\n/**\n * Everything factory knows about a session's sandbox needs — the whole\n * contract between factory and the deployer's sandbox callback. Factory owns\n * intent; the provider owns resolving `sessionId` to a runnable VM.\n */\nexport interface FactorySandboxContext {\n /** Stable session id — the sandbox identity. */\n sessionId: string;\n /** owner/name of the repository, when the session is repo-backed. */\n repoFullName?: string;\n /**\n * Configured repo setup command, when present. Part of a repo template's\n * identity: a different setup command produces a different template.\n */\n setupCommand?: string;\n /**\n * Resolves the session repository's clone URL and a fresh short-lived\n * credential for it. Providers use it for authenticated work that runs\n * outside the VM — resolving a private repo's head, or cloning it during\n * a template build. The credential is minted per call (installation\n * tokens expire in ~1h); never an org PAT.\n *\n * `undefined` when the session has no repository, which is how a provider\n * knows to build no repo template. The key is always present so that\n * passing the whole context to a provider helper keeps working when this\n * field changes, instead of silently resolving to \"no repository\".\n */\n getRepositoryAccess: (() => Promise<RepositoryAccess>) | undefined;\n}\n\n/**\n * The deploy's sandbox configuration: construct a session's sandbox from\n * intent. The sandbox identity is the session id; the provider must honor\n * id-keyed getOrCreate on `start()` (reconnect/resume an existing VM for the\n * id, create otherwise). Construction must be cheap and side-effect-free —\n * VMs are provisioned on `start()` only. Local sandboxes should root their\n * `workingDirectory` at a per-session directory (e.g.\n * `join(root, ctx.sessionId)`); the repo checks out as a subdirectory of it.\n *\n * Returns a `MastraSandbox`, not the bare `WorkspaceSandbox` interface:\n * factory relies on the base class for the start lifecycle and the runtime\n * env, so providers extend it rather than reimplementing the contract.\n *\n * Factory attaches its own session setup to the returned sandbox, so the\n * callback never has to wire it up. A callback may still pass its own\n * `onStart`; it runs after factory's setup, against a prepared workspace.\n *\n * @example\n * ```typescript\n * sandbox: ({ sessionId }) => new E2BSandbox({ id: sessionId })\n * ```\n */\nexport type MastraFactorySandboxConfig = (ctx: FactorySandboxContext) => MastraSandbox;\n\n/** The session's setup work, run against a started sandbox. Must be idempotent. */\nexport type SessionSetupRun = (sandbox: WorkspaceSandbox, workdir: string) => Promise<void>;\n\n/**\n * Per-process session-id → sandbox instance memo.\n *\n * The provider contract is id-keyed getOrCreate, but provider find-then-create\n * has a real double-create race across independent instances. Memoizing the\n * instance per session makes the base class's per-instance start coalescing\n * apply process-wide per session — the same single-flight scope the fleet's\n * per-binding coalescing provided. Cross-replica races are accepted (the\n * fleet was also per-replica).\n */\ninterface SessionSandboxEntry {\n sandbox: WorkspaceSandbox;\n /**\n * The session's repo checkout root, recorded for passive readers (fs\n * routes, capture, authz). Local sandboxes derive it at construction;\n * remote sandboxes clone into the VM's own home, so it is undefined until\n * `resolveSessionWorkdir` probes the first started VM — passive readers\n * treat an unresolved workdir as \"nothing materialized\".\n */\n workdir?: string;\n}\n\nconst sessionSandboxes = new Map<string, SessionSandboxEntry>();\n\n/**\n * Get the session's memoized sandbox entry, constructing (and memoizing) it on\n * first access. Construction is cheap and side-effect-free by contract; VMs\n * are provisioned on `start()` only. Local sandboxes get their workdir here;\n * remote workdirs are a runtime fact of the VM, resolved on first start.\n */\nexport function getSessionSandbox(\n sessionId: string,\n repoFullName: string,\n construct: () => WorkspaceSandbox,\n): SessionSandboxEntry {\n const existing = sessionSandboxes.get(sessionId);\n if (existing) return existing;\n const sandbox = construct();\n const local = deriveLocalWorkdir(sandbox, repoFullName);\n const entry: SessionSandboxEntry = { sandbox, ...(local ? { workdir: local } : {}) };\n sessionSandboxes.set(sessionId, entry);\n return entry;\n}\n\n/**\n * Resolve (and memoize on the session entry) the session's repo checkout\n * root. Local sandboxes answer synchronously from their configured\n * `workingDirectory`; remote sandboxes clone into the VM's own default cwd,\n * so the first resolution probes it with one `pwd` — the VM tells us where\n * home is, we never invent a path. Calling this against a stopped sandbox\n * lazily starts it (the probe is a command), so passive readers must peek\n * `entry.workdir` instead.\n */\nexport async function resolveSessionWorkdir(\n sessionId: string,\n sandbox: WorkspaceSandbox,\n repoFullName: string,\n): Promise<string> {\n const entry = sessionSandboxes.get(sessionId);\n if (entry?.workdir && entry.sandbox === sandbox) return entry.workdir;\n const workdir =\n deriveLocalWorkdir(sandbox, repoFullName) ??\n deriveRemoteRepoDir(sandbox, repoFullName) ??\n repoDirUnder(await probeHome(sandbox), repoFullName);\n if (entry && entry.sandbox === sandbox) entry.workdir = workdir;\n return workdir;\n}\n\n/** One `pwd` in the VM's default shell cwd — its home dir, by provider convention. */\nasync function probeHome(sandbox: WorkspaceSandbox): Promise<string> {\n if (!sandbox.executeCommand) {\n throw new Error(`Sandbox '${sandbox.id}' cannot resolve its workdir: no executeCommand implementation`);\n }\n const probe = await sandbox.executeCommand('pwd');\n const home = probe.stdout.trim().split('\\n').pop()?.trim() ?? '';\n if (probe.exitCode !== 0 || !home.startsWith('/')) {\n throw new Error(\n `Sandbox '${sandbox.id}' default cwd probe failed (exit ${probe.exitCode}): ${\n probe.stderr.trim() || probe.stdout.trim() || 'empty output'\n }`,\n );\n }\n return home;\n}\n\n/**\n * The session's memoized sandbox (and its workdir) when one was already\n * constructed in this process, else undefined. Never constructs — passive\n * read paths use this so browsing files cannot provision a VM.\n */\nexport function peekSessionSandbox(sessionId: string): SessionSandboxEntry | undefined {\n return sessionSandboxes.get(sessionId);\n}\n\n/** Drop the memoized instance (on stop/destroy/retirement or construction failure). */\nexport function evictSessionSandbox(sessionId: string): void {\n sessionSandboxes.delete(sessionId);\n failedSetupCommands.delete(sessionId);\n}\n\n/** Test-only: reset the process-wide memo between tests. */\nexport function __clearSessionSandboxesForTests(): void {\n sessionSandboxes.clear();\n failedSetupCommands.clear();\n}\n\n/**\n * Setup commands that already failed once for a session. The first failure\n * fails the start loudly — the agent sees the real error in the tool result\n * that triggered it. Recording it lets the next start skip the known-bad\n * command instead of wedging the session behind a permanently failing\n * onStart: clone and checkout still run, and the agent can fix or re-run\n * the setup itself. Keyed by the exact command so an edited setup command\n * runs fresh. In-memory only — a server restart re-runs the (idempotent)\n * setup.\n */\nconst failedSetupCommands = new Map<string, string>();\n\nexport function recordFailedSetupCommand(sessionId: string, command: string): void {\n failedSetupCommands.set(sessionId, command);\n}\n\nexport function hasFailedSetupCommand(sessionId: string, command: string): boolean {\n return failedSetupCommands.get(sessionId) === command;\n}\n\n/**\n * Completion marker for the session setup. Factory owns this end-to-end:\n * the `onStart` hook and the post-start fallback guard the exact same path,\n * so setup never double-runs regardless of which layer executed it. It is a\n * skip cache, not a correctness mechanism — the setup work is idempotent by\n * construction (materialize probes the disk, checkout/setup re-run safely).\n */\nconst SESSION_SETUP_MARKER = '.mastra-factory/bootstrap';\n\nfunction markerShellPath(sandbox: Pick<WorkspaceSandbox, 'provider'>): string {\n // Local sandboxes exec with cwd = the session working directory; remote\n // VMs are one-per-session so $HOME is private to the session.\n return sandbox.provider === 'local' ? `./${SESSION_SETUP_MARKER}` : `$HOME/${SESSION_SETUP_MARKER}`;\n}\n\nasync function markerPresent(sandbox: WorkspaceSandbox, workdir: string): Promise<boolean> {\n // The marker is a skip cache — it sits beside the checkout, not inside it,\n // so it can outlive a removed checkout (e.g. a wiped local session dir or\n // a recovered VM). Trust it only when the checkout it describes exists.\n const probe = await sandbox.executeCommand!(`test -f \"${markerShellPath(sandbox)}\" && test -d \"${workdir}/.git\"`);\n return probe.exitCode === 0;\n}\n\nasync function writeMarker(sandbox: WorkspaceSandbox): Promise<void> {\n // Best-effort: a missing marker only re-runs the idempotent setup later.\n const marker = markerShellPath(sandbox);\n await sandbox.executeCommand!(`mkdir -p \"$(dirname \"${marker}\")\" && touch \"${marker}\"`).catch(() => {});\n}\n\n/**\n * Run the session setup, marker-guarded: skip when the marker exists (unless\n * the VM is known-fresh), otherwise run and write the marker only on\n * success. Setup failures propagate — no marker is written, so the next\n * attempt re-runs.\n */\nasync function runGuardedSetup(\n sandbox: WorkspaceSandbox,\n run: SessionSetupRun,\n { skipMarkerProbe, sessionId, repoFullName }: { skipMarkerProbe: boolean; sessionId: string; repoFullName: string },\n): Promise<void> {\n if (!sandbox.executeCommand) {\n throw new Error(`Sandbox '${sandbox.id}' cannot run the session setup: no executeCommand implementation`);\n }\n // Resolved from the live instance (the hook runs inside `start()`, so the\n // VM is up) and memoized on the session entry for passive readers.\n const workdir = await resolveSessionWorkdir(sessionId, sandbox, repoFullName);\n if (!skipMarkerProbe && (await markerPresent(sandbox, workdir))) return;\n await run(sandbox, workdir);\n await writeMarker(sandbox);\n}\n\n/**\n * Build the session setup hook, which factory attaches to the constructed\n * sandbox with `setOnStart`. Runs inside the sandbox start\n * lifecycle: a fresh VM (`outcome: 'created'`) runs setup with no probe; a\n * reconnect probes the marker first, which re-runs setup after a failed or\n * crash-interrupted attempt. Throwing fails `start()` loudly — core treats\n * onStart errors as fatal.\n */\nexport function createSessionSetupHook(\n run: SessionSetupRun,\n sessionId: string,\n repoFullName: string,\n): SandboxStartHook {\n return async ({ sandbox, outcome }) => {\n await runGuardedSetup(sandbox, run, { skipMarkerProbe: outcome === 'created', sessionId, repoFullName });\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"session-sandbox.js","names":[],"sources":["../../src/sandbox/session-sandbox.ts"],"sourcesContent":["import path from 'node:path';\nimport { SETUP_MARKER_PATH, setupMarkerContent } from '@internal/workspace';\n\nimport type { MastraSandbox, SandboxStartHook, WorkspaceSandbox } from '@mastra/core/workspace';\nimport type { RepositoryAccess } from '../capabilities/version-control.js';\nimport { deriveLocalWorkdir, deriveRemoteRepoDir, repoDirUnder } from './workdir.js';\n\n/**\n * Everything factory knows about a session's sandbox needs — the whole\n * contract between factory and the deployer's sandbox callback. Factory owns\n * intent; the provider owns resolving `sessionId` to a runnable VM.\n */\nexport interface FactorySandboxContext {\n /** Stable session id — the sandbox identity. */\n sessionId: string;\n /** owner/name of the repository, when the session is repo-backed. */\n repoFullName?: string;\n /**\n * Configured repo setup command, when present. Part of a repo template's\n * identity: a different setup command produces a different template.\n */\n setupCommand?: string;\n /**\n * Resolves the session repository's clone URL and a fresh short-lived\n * credential for it. Providers use it for authenticated work that runs\n * outside the VM — resolving a private repo's head, or cloning it during\n * a template build. The credential is minted per call (installation\n * tokens expire in ~1h); never an org PAT.\n *\n * `undefined` when the session has no repository, which is how a provider\n * knows to build no repo template. The key is always present so that\n * passing the whole context to a provider helper keeps working when this\n * field changes, instead of silently resolving to \"no repository\".\n */\n getRepositoryAccess: (() => Promise<RepositoryAccess>) | undefined;\n}\n\n/**\n * The deploy's sandbox configuration: construct a session's sandbox from\n * intent. The sandbox identity is the session id; the provider must honor\n * id-keyed getOrCreate on `start()` (reconnect/resume an existing VM for the\n * id, create otherwise). Construction must be cheap and side-effect-free —\n * VMs are provisioned on `start()` only. Local sandboxes should root their\n * `workingDirectory` at a per-session directory (e.g.\n * `join(root, ctx.sessionId)`); the repo checks out as a subdirectory of it.\n *\n * Returns a `MastraSandbox`, not the bare `WorkspaceSandbox` interface:\n * factory relies on the base class for the start lifecycle and the runtime\n * env, so providers extend it rather than reimplementing the contract.\n *\n * Factory attaches its own session setup to the returned sandbox, so the\n * callback never has to wire it up. A callback may still pass its own\n * `onStart`; it runs after factory's setup, against a prepared workspace.\n *\n * @example\n * ```typescript\n * sandbox: ({ sessionId }) => new E2BSandbox({ id: sessionId })\n * ```\n */\nexport type MastraFactorySandboxConfig = (ctx: FactorySandboxContext) => MastraSandbox;\n\n/**\n * What the start hook learned about the setup command before running the\n * session setup, and how to record its completion afterwards.\n */\nexport interface SessionSetupGate {\n /** True when the sandbox already carries a marker for the current setup command. */\n setupDone: boolean;\n /** Write the marker once the setup command succeeded. Best-effort. */\n markSetupDone: () => Promise<void>;\n}\n\n/**\n * The session's setup work, run against a started sandbox on EVERY start.\n * Materialize and checkout are idempotent and must always run (a warm boot\n * still needs its pull); only the setup command consults `gate`.\n */\nexport type SessionSetupRun = (sandbox: WorkspaceSandbox, workdir: string, gate: SessionSetupGate) => Promise<void>;\n\n/**\n * Per-process session-id → sandbox instance memo.\n *\n * The provider contract is id-keyed getOrCreate, but provider find-then-create\n * has a real double-create race across independent instances. Memoizing the\n * instance per session makes the base class's per-instance start coalescing\n * apply process-wide per session — the same single-flight scope the fleet's\n * per-binding coalescing provided. Cross-replica races are accepted (the\n * fleet was also per-replica).\n */\ninterface SessionSandboxEntry {\n sandbox: WorkspaceSandbox;\n /**\n * The session's repo checkout root, recorded for passive readers (fs\n * routes, capture, authz). Local sandboxes derive it at construction;\n * remote sandboxes clone into the VM's own home, so it is undefined until\n * `resolveSessionWorkdir` probes the first started VM — passive readers\n * treat an unresolved workdir as \"nothing materialized\".\n */\n workdir?: string;\n}\n\nconst sessionSandboxes = new Map<string, SessionSandboxEntry>();\n\n/**\n * Get the session's memoized sandbox entry, constructing (and memoizing) it on\n * first access. Construction is cheap and side-effect-free by contract; VMs\n * are provisioned on `start()` only. Local sandboxes get their workdir here;\n * remote workdirs are a runtime fact of the VM, resolved on first start.\n */\nexport function getSessionSandbox(\n sessionId: string,\n repoFullName: string,\n construct: () => WorkspaceSandbox,\n): SessionSandboxEntry {\n const existing = sessionSandboxes.get(sessionId);\n if (existing) return existing;\n const sandbox = construct();\n const local = deriveLocalWorkdir(sandbox, repoFullName);\n const entry: SessionSandboxEntry = { sandbox, ...(local ? { workdir: local } : {}) };\n sessionSandboxes.set(sessionId, entry);\n return entry;\n}\n\n/**\n * Resolve (and memoize on the session entry) the session's repo checkout\n * root. Local sandboxes answer synchronously from their configured\n * `workingDirectory`; remote sandboxes clone into the VM's own default cwd,\n * so the first resolution probes it with one `pwd` — the VM tells us where\n * home is, we never invent a path. Calling this against a stopped sandbox\n * lazily starts it (the probe is a command), so passive readers must peek\n * `entry.workdir` instead.\n */\nexport async function resolveSessionWorkdir(\n sessionId: string,\n sandbox: WorkspaceSandbox,\n repoFullName: string,\n): Promise<string> {\n const entry = sessionSandboxes.get(sessionId);\n if (entry?.workdir && entry.sandbox === sandbox) return entry.workdir;\n const workdir =\n deriveLocalWorkdir(sandbox, repoFullName) ??\n deriveRemoteRepoDir(sandbox, repoFullName) ??\n repoDirUnder(await probeHome(sandbox), repoFullName);\n if (entry && entry.sandbox === sandbox) entry.workdir = workdir;\n return workdir;\n}\n\n/** One `pwd` in the VM's default shell cwd — its home dir, by provider convention. */\nasync function probeHome(sandbox: WorkspaceSandbox): Promise<string> {\n if (!sandbox.executeCommand) {\n throw new Error(`Sandbox '${sandbox.id}' cannot resolve its workdir: no executeCommand implementation`);\n }\n const probe = await sandbox.executeCommand('pwd');\n const home = probe.stdout.trim().split('\\n').pop()?.trim() ?? '';\n if (probe.exitCode !== 0 || !home.startsWith('/')) {\n throw new Error(\n `Sandbox '${sandbox.id}' default cwd probe failed (exit ${probe.exitCode}): ${\n probe.stderr.trim() || probe.stdout.trim() || 'empty output'\n }`,\n );\n }\n return home;\n}\n\n/**\n * The session's memoized sandbox (and its workdir) when one was already\n * constructed in this process, else undefined. Never constructs — passive\n * read paths use this so browsing files cannot provision a VM.\n */\nexport function peekSessionSandbox(sessionId: string): SessionSandboxEntry | undefined {\n return sessionSandboxes.get(sessionId);\n}\n\n/** Drop the memoized instance (on stop/destroy/retirement or construction failure). */\nexport function evictSessionSandbox(sessionId: string): void {\n sessionSandboxes.delete(sessionId);\n failedSetupCommands.delete(sessionId);\n}\n\n/** Test-only: reset the process-wide memo between tests. */\nexport function __clearSessionSandboxesForTests(): void {\n sessionSandboxes.clear();\n failedSetupCommands.clear();\n}\n\n/**\n * Setup commands that already failed once for a session. The first failure\n * fails the start loudly — the agent sees the real error in the tool result\n * that triggered it. Recording it lets the next start skip the known-bad\n * command instead of wedging the session behind a permanently failing\n * onStart: clone and checkout still run, and the agent can fix or re-run\n * the setup itself. Keyed by the exact command so an edited setup command\n * runs fresh. In-memory only — a server restart re-runs the (idempotent)\n * setup.\n */\nconst failedSetupCommands = new Map<string, string>();\n\nexport function recordFailedSetupCommand(sessionId: string, command: string): void {\n failedSetupCommands.set(sessionId, command);\n}\n\nexport function hasFailedSetupCommand(sessionId: string, command: string): boolean {\n return failedSetupCommands.get(sessionId) === command;\n}\n\n/**\n * The setup completion marker is a convention shared with the repo templates\n * (`@internal/workspace`): `.mastra-sandbox/setup` beside the checkout,\n * containing a digest of the setup commands. Templates write it as their last\n * build step, so a sandbox booted from a warm image already carries it; the\n * start hook writes it after a successful runtime setup. It is a skip cache,\n * not a correctness mechanism: the setup command is assumed idempotent, and a\n * missing or mismatched marker only re-runs it.\n *\n * The working directory is the parent of the repo dir, which is also the\n * template's build cwd, so this is the file the template's marker step wrote.\n */\nfunction markerShellPath(workdir: string): string {\n return `${path.posix.dirname(workdir)}/${SETUP_MARKER_PATH}`;\n}\n\nasync function markerMatches(sandbox: WorkspaceSandbox, workdir: string, content: string): Promise<boolean> {\n // The marker sits beside the checkout, not inside it, so it can outlive a\n // removed checkout (a wiped local session dir, a recovered VM). Trust it\n // only when the checkout it describes exists and it names this command.\n const marker = markerShellPath(workdir);\n const probe = await sandbox.executeCommand!(\n `test -d \"${workdir}/.git\" && test -f \"${marker}\" && [ \"$(cat \"${marker}\")\" = \"${content}\" ]`,\n );\n return probe.exitCode === 0;\n}\n\nasync function writeMarker(sandbox: WorkspaceSandbox, workdir: string, content: string): Promise<void> {\n // Best-effort: a missing marker only re-runs the idempotent setup later.\n const marker = markerShellPath(workdir);\n await sandbox.executeCommand!(`mkdir -p \"$(dirname \"${marker}\")\" && printf '%s' '${content}' > \"${marker}\"`).catch(\n () => {},\n );\n}\n\n/**\n * Build the session setup hook, which factory attaches to the constructed\n * sandbox with `setOnStart`. Runs inside the sandbox start lifecycle on\n * every start, fresh VM or reconnect: materialize and checkout always run,\n * and the setup command runs unless the sandbox already carries the marker\n * for it (a warm template image, or an earlier successful start). Throwing\n * fails `start()` loudly; core treats onStart errors as fatal.\n */\nexport function createSessionSetupHook(\n run: SessionSetupRun,\n sessionId: string,\n repoFullName: string,\n setupCommand: string | undefined,\n): SandboxStartHook {\n // No command, no marker: nothing to gate.\n const marker = setupCommand?.trim() ? setupMarkerContent(setupCommand) : undefined;\n return async ({ sandbox }) => {\n if (!sandbox.executeCommand) {\n throw new Error(`Sandbox '${sandbox.id}' cannot run the session setup: no executeCommand implementation`);\n }\n // Resolved from the live instance (the hook runs inside `start()`, so the\n // VM is up) and memoized on the session entry for passive readers.\n const workdir = await resolveSessionWorkdir(sessionId, sandbox, repoFullName);\n // Probed before materialize so a wiped checkout reads as \"not done\" even\n // though materialize is about to restore it.\n const setupDone = marker ? await markerMatches(sandbox, workdir, marker) : true;\n await run(sandbox, workdir, {\n setupDone,\n markSetupDone: () => (marker ? writeMarker(sandbox, workdir, marker) : Promise.resolve()),\n });\n };\n}\n"],"mappings":"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"}
|
package/dist/workspace.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAC;AAE/E,OAAO,KAAK,EAAE,mBAAmB,EAA2B,MAAM,mCAAmC,CAAC;AAItG,OAAO,EAAoB,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,KAAK,EAEV,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EACjB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;
|
|
1
|
+
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAC;AAE/E,OAAO,KAAK,EAAE,mBAAmB,EAA2B,MAAM,mCAAmC,CAAC;AAItG,OAAO,EAAoB,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,KAAK,EAEV,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EACjB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAyB9E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAK7E,eAAO,MAAM,2BAA2B,QAQQ,CAAC;AAEjD;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAAC,GAAG,GAAE,MAAsB,GAAG,MAAM,GAAG,SAAS,CAS7F;AAED,eAAO,MAAM,mBAAmB,aAO9B,CAAC;AAEH,qBAAa,kBAAmB,YAAW,WAAW;;IAMlD,QAAQ,CAAC,QAAQ,EAAE,WAAW;gBAArB,QAAQ,EAAE,WAAW,EAC9B,kBAAkB,EAAE,MAAM,EAAE,EAC5B,eAAe,GAAE,MAAM,GAAG,SAA2C;IAqBjE,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAO3C,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAMjD,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC;IAMrD,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAyB7D,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAI7C;AAuDD,KAAK,uBAAuB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;AAEzE,MAAM,WAAW,6BAA6B;IAC5C,iEAAiE;IACjE,OAAO,CAAC,EAAE,0BAA0B,CAAC;IACrC,gFAAgF;IAChF,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B;;iEAE6D;IAC7D,SAAS,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,yBAAyB,CAAC,CAAC;IAC9D,gFAAgF;IAChF,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;CAC9C;AAED,KAAK,mBAAmB,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAEtD,gFAAgF;AAChF,qBAAa,wBAAwB;;IAInC,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAI/B,QAAQ,CACZ,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,mBAAmB,GAC9B,OAAO,CAAC,OAAO,CAAC;IAWb,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAS1D;AAED,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,6BAAkC,IAsBlE,4CAA4C,uBAAuB,2MA6blF"}
|
package/dist/workspace.js
CHANGED
|
@@ -207,8 +207,8 @@ function createWorkspaceFactory(options = {}) {
|
|
|
207
207
|
id: connection.installationId
|
|
208
208
|
})) throw new Error(`GitHub installation ${connection.installationId} was not found`);
|
|
209
209
|
const repoFullName = repository.slug;
|
|
210
|
-
const runSetupOn = (target, workdir) => runSessionSetup(requireExec(target), workdir);
|
|
211
|
-
const guardedSetup = createSessionSetupHook(runSetupOn, session.id, repoFullName);
|
|
210
|
+
const runSetupOn = (target, workdir, gate) => runSessionSetup(requireExec(target), workdir, gate);
|
|
211
|
+
const guardedSetup = createSessionSetupHook(runSetupOn, session.id, repoFullName, projectRepository.setupCommand ?? void 0);
|
|
212
212
|
const setupHook = async (args) => {
|
|
213
213
|
if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) throw retiredError();
|
|
214
214
|
await guardedSetup(args);
|
|
@@ -363,7 +363,7 @@ function createWorkspaceFactory(options = {}) {
|
|
|
363
363
|
return reconcileRegisteredWorkspace(existing);
|
|
364
364
|
}
|
|
365
365
|
const retiredError = () => /* @__PURE__ */ new Error(`Factory session ${session.sessionId} was retired during workspace materialization`);
|
|
366
|
-
const runSessionSetup = async (target, workdir) => {
|
|
366
|
+
const runSessionSetup = async (target, workdir, gate) => {
|
|
367
367
|
const token = await getRepositoryToken();
|
|
368
368
|
const setupPatKind = await resolveGithubPatKind("default");
|
|
369
369
|
const setupGhToken = await getGithubPat(() => github.integrationStorage, session.orgId, setupPatKind) ?? token;
|
|
@@ -391,7 +391,7 @@ function createWorkspaceFactory(options = {}) {
|
|
|
391
391
|
token,
|
|
392
392
|
repoFullName
|
|
393
393
|
});
|
|
394
|
-
if (projectRepository.setupCommand) {
|
|
394
|
+
if (projectRepository.setupCommand && !gate.setupDone) {
|
|
395
395
|
if (hasFailedSetupCommand(session.id, projectRepository.setupCommand)) {
|
|
396
396
|
console.warn("[Mastra Factory] Skipping setup command that already failed this session", {
|
|
397
397
|
orgId: session.orgId,
|
|
@@ -402,6 +402,7 @@ function createWorkspaceFactory(options = {}) {
|
|
|
402
402
|
}
|
|
403
403
|
try {
|
|
404
404
|
await runSetupCommand(target, workdir, projectRepository.setupCommand);
|
|
405
|
+
await gate.markSetupDone();
|
|
405
406
|
} catch (setupError) {
|
|
406
407
|
if (projectRepository.teardownCommand) try {
|
|
407
408
|
await runTeardownCommand(target, workdir, projectRepository.teardownCommand, { timeoutMs: DEFAULT_COMMAND_TIMEOUT_MS });
|
package/dist/workspace.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace.js","names":["#bundledSource","#localSource","#fallbackSkillRoots","#layerFor","#isFactoryPath","#factoryPath","#entries","#generations"],"sources":["../src/workspace.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport path, { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { SandboxFilesystem } from '@mastra/code-sdk/agents/sandbox-filesystem';\nimport { MASTRACODE_WORKSPACE_TOOLS } from '@mastra/code-sdk/agents/tool-availability';\nimport type { getDynamicWorkspace, WorkspaceSkillExtension } from '@mastra/code-sdk/agents/workspace';\nimport { DEFAULT_CONFIG_DIR } from '@mastra/code-sdk/constants';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport { LocalSkillSource, Workspace } from '@mastra/core/workspace';\nimport type {\n SandboxStartHook,\n SkillSource,\n SkillSourceEntry,\n SkillSourceStat,\n WorkspaceSandbox,\n} from '@mastra/core/workspace';\nimport { getFactoryAuthUserFromContext, getFactoryAuthUserId } from './auth.js';\nimport type { MastraFactorySandboxConfig } from './factory.js';\nimport type { GithubIntegration } from './integrations/github/integration.js';\nimport { getGithubPat } from './integrations/github/pat.js';\nimport type { GithubPatKind } from './integrations/github/pat.js';\nimport {\n checkoutSessionBranch,\n DEFAULT_COMMAND_TIMEOUT_MS,\n materializeRepo,\n runSetupCommand,\n runTeardownCommand,\n SetupCommandError,\n} from './integrations/github/sandbox.js';\nimport { registerGithubPatKind, registerGithubTokenInjector } from './integrations/github/token-refresh.js';\nimport { getFactorySessionAddress } from './rules/binding-context.js';\nimport { requireExec } from './sandbox/materialization.js';\nimport type { ExecutableSandbox } from './sandbox/materialization.js';\nimport {\n createSessionSetupHook,\n evictSessionSandbox,\n getSessionSandbox,\n hasFailedSetupCommand,\n recordFailedSetupCommand,\n resolveSessionWorkdir,\n} from './sandbox/session-sandbox.js';\n\nimport type { WorkItemsStorage } from './storage/domains/work-items/base.js';\n\nconst WORKSPACE_ID_PREFIX = 'mfw';\nconst bundleDirectory = dirname(fileURLToPath(import.meta.url));\nconst bundledFactorySkillsPath = join(bundleDirectory, 'factory-skills');\nexport const BUNDLED_FACTORY_SKILLS_PATH =\n [\n // Deploy bundle: the consumer copies `factory-skills/` next to the built\n // server module (e.g. via its public/ dir).\n bundledFactorySkillsPath,\n // Package layout: `dist/../factory-skills` (also `src/../factory-skills`\n // when running tests against sources).\n join(bundleDirectory, '..', 'factory-skills'),\n ].find(existsSync) ?? bundledFactorySkillsPath;\n\n/**\n * Resolve the consumer repo's local Factory skills root, if any. Checked in\n * addition to the bundled skills so projects can add (or override) Factory\n * skills without patching the installed package. Candidates cover the cwd\n * variants the dev server runs with (`repo root`, `--dir src/mastra` which\n * runs with cwd `src/mastra/public`).\n */\nexport function resolveLocalFactorySkillsPath(cwd: string = process.cwd()): string | undefined {\n const candidates = [\n join(cwd, 'src', 'mastra', 'public', 'factory-skills'),\n join(cwd, 'public', 'factory-skills'),\n join(cwd, 'factory-skills'),\n ];\n return candidates.find(\n candidate => path.normalize(candidate) !== path.normalize(BUNDLED_FACTORY_SKILLS_PATH) && existsSync(candidate),\n );\n}\nconst FACTORY_SKILLS_MOUNT = path.resolve(path.parse(process.cwd()).root, '__mastracode_factory_skills__');\nexport const FACTORY_SKILL_NAMES = new Set([\n 'configure-factory-rules',\n 'factory-complete-issue',\n 'factory-plan',\n 'factory-rereview',\n 'factory-review',\n 'factory-triage',\n]);\n\nexport class FactorySkillSource implements SkillSource {\n readonly #bundledSource = new LocalSkillSource({ basePath: BUNDLED_FACTORY_SKILLS_PATH });\n readonly #localSource: LocalSkillSource | undefined;\n readonly #fallbackSkillRoots: Set<string>;\n\n constructor(\n readonly fallback: SkillSource,\n fallbackSkillRoots: string[],\n localSkillsPath: string | undefined = resolveLocalFactorySkillsPath(),\n ) {\n this.#localSource = localSkillsPath ? new LocalSkillSource({ basePath: localSkillsPath }) : undefined;\n this.#fallbackSkillRoots = new Set(fallbackSkillRoots.map(skillPath => path.normalize(skillPath)));\n }\n\n #isFactoryPath(skillPath: string): boolean {\n const normalized = path.normalize(skillPath);\n return normalized === FACTORY_SKILLS_MOUNT || normalized.startsWith(`${FACTORY_SKILLS_MOUNT}${path.sep}`);\n }\n\n #factoryPath(skillPath: string): string {\n return path.relative(FACTORY_SKILLS_MOUNT, path.normalize(skillPath));\n }\n\n /** Pick the layer serving this mount-relative path: local wins when it has the entry. */\n async #layerFor(relativePath: string): Promise<LocalSkillSource> {\n if (this.#localSource && (await this.#localSource.exists(relativePath))) return this.#localSource;\n return this.#bundledSource;\n }\n\n async exists(skillPath: string): Promise<boolean> {\n if (!this.#isFactoryPath(skillPath)) return this.fallback.exists(skillPath);\n const relative = this.#factoryPath(skillPath);\n if (this.#localSource && (await this.#localSource.exists(relative))) return true;\n return this.#bundledSource.exists(relative);\n }\n\n async stat(skillPath: string): Promise<SkillSourceStat> {\n if (!this.#isFactoryPath(skillPath)) return this.fallback.stat(skillPath);\n const relative = this.#factoryPath(skillPath);\n return (await this.#layerFor(relative)).stat(relative);\n }\n\n async readFile(skillPath: string): Promise<string | Buffer> {\n if (!this.#isFactoryPath(skillPath)) return this.fallback.readFile(skillPath);\n const relative = this.#factoryPath(skillPath);\n return (await this.#layerFor(relative)).readFile(relative);\n }\n\n async readdir(skillPath: string): Promise<SkillSourceEntry[]> {\n if (this.#isFactoryPath(skillPath)) {\n const relative = this.#factoryPath(skillPath);\n const [bundledExists, localExists] = await Promise.all([\n this.#bundledSource.exists(relative),\n this.#localSource?.exists(relative) ?? Promise.resolve(false),\n ]);\n if (!bundledExists && !localExists) throw skillSourceEnoent(skillPath);\n const [bundledEntries, localEntries] = await Promise.all([\n bundledExists ? this.#bundledSource.readdir(relative) : [],\n localExists ? this.#localSource!.readdir(relative) : [],\n ]);\n const merged = new Map<string, SkillSourceEntry>();\n for (const entry of bundledEntries) merged.set(entry.name, entry);\n // Local entries override bundled names.\n for (const entry of localEntries) merged.set(entry.name, entry);\n return [...merged.values()];\n }\n const entries = await this.fallback.readdir(skillPath);\n if (this.#fallbackSkillRoots.has(path.normalize(skillPath))) {\n return entries.filter(entry => !FACTORY_SKILL_NAMES.has(entry.name));\n }\n return entries;\n }\n\n realpath(skillPath: string): Promise<string> {\n if (this.#isFactoryPath(skillPath)) return Promise.resolve(path.normalize(skillPath));\n return this.fallback.realpath ? this.fallback.realpath(skillPath) : Promise.resolve(skillPath);\n }\n}\n\n/** Build a Node-style ENOENT error so callers can treat missing skills like fs misses. */\nfunction skillSourceEnoent(skillPath: string): Error {\n const error = new Error(`ENOENT: no such file or directory, '${skillPath}'`) as Error & { code: string };\n error.code = 'ENOENT';\n return error;\n}\n\n/**\n * Sandbox-backed skill fallback that stays inert until the session sandbox is\n * actually materialized. Skill discovery runs on latency-sensitive paths (the\n * Factory start coordinator resolves the kickoff skill before the start route\n * responds); without this guard the first project-root read would hit the lazy\n * sandbox handle and force full provisioning + repo materialization. While the\n * sandbox is unmaterialized, project skill roots simply appear empty — bundled\n * Factory skills resolve from local disk via `FactorySkillSource`. Once the\n * sandbox exists, every call delegates straight through.\n */\nclass UnmaterializedAwareSkillSource implements SkillSource {\n constructor(\n readonly fallback: SkillSource,\n readonly isMaterialized: () => boolean,\n ) {}\n\n async exists(skillPath: string): Promise<boolean> {\n return this.isMaterialized() ? this.fallback.exists(skillPath) : false;\n }\n\n async stat(skillPath: string): Promise<SkillSourceStat> {\n if (!this.isMaterialized()) throw skillSourceEnoent(skillPath);\n return this.fallback.stat(skillPath);\n }\n\n async readFile(skillPath: string): Promise<string | Buffer> {\n if (!this.isMaterialized()) throw skillSourceEnoent(skillPath);\n return this.fallback.readFile(skillPath);\n }\n\n async readdir(skillPath: string): Promise<SkillSourceEntry[]> {\n return this.isMaterialized() ? this.fallback.readdir(skillPath) : [];\n }\n\n realpath(skillPath: string): Promise<string> {\n if (!this.isMaterialized()) return Promise.resolve(skillPath);\n return this.fallback.realpath ? this.fallback.realpath(skillPath) : Promise.resolve(skillPath);\n }\n}\n\nconst factorySkillExtension: WorkspaceSkillExtension = {\n id: 'web-factory',\n paths: [FACTORY_SKILLS_MOUNT],\n createSource: (fallback, fallbackSkillRoots) => new FactorySkillSource(fallback, fallbackSkillRoots),\n};\n\ntype DynamicWorkspaceContext = Parameters<typeof getDynamicWorkspace>[0];\n\nexport interface CreateWorkspaceFactoryOptions {\n /** Factory sandbox runtime config (session sandbox callback). */\n sandbox?: MastraFactorySandboxConfig;\n /** GitHub integration used to resolve Factory sessions and mint repo tokens. */\n github?: GithubIntegration;\n /** Work-items storage used to resolve the session's run-binding role, so\n * review-board sessions get the reviewer PAT as `GH_TOKEN`. Optional —\n * without it every session uses the default (worker) PAT. */\n workItems?: Pick<WorkItemsStorage, 'findRunBindingBySession'>;\n /** Runtime workspace/token registrations invalidated when a session retires. */\n workspaceRegistry?: FactoryWorkspaceRegistry;\n}\n\ntype WorkspaceUnregister = () => Promise<void> | void;\n\n/** Tracks dynamic Factory workspaces by persisted session id for retirement. */\nexport class FactoryWorkspaceRegistry {\n readonly #entries = new Map<string, Map<string, WorkspaceUnregister>>();\n readonly #generations = new Map<string, number>();\n\n generation(sessionId: string): number {\n return this.#generations.get(sessionId) ?? 0;\n }\n\n async register(\n sessionId: string,\n workspaceId: string,\n generation: number,\n unregister: WorkspaceUnregister,\n ): Promise<boolean> {\n if (generation !== this.generation(sessionId)) {\n await unregister();\n return false;\n }\n const entries = this.#entries.get(sessionId) ?? new Map<string, WorkspaceUnregister>();\n entries.set(workspaceId, unregister);\n this.#entries.set(sessionId, entries);\n return true;\n }\n\n async invalidateSession(sessionId: string): Promise<void> {\n this.#generations.set(sessionId, this.generation(sessionId) + 1);\n const entries = this.#entries.get(sessionId);\n if (!entries) return;\n this.#entries.delete(sessionId);\n const results = await Promise.allSettled([...entries.values()].map(unregister => unregister()));\n const failure = results.find(result => result.status === 'rejected');\n if (failure?.status === 'rejected') throw failure.reason;\n }\n}\n\nexport function createWorkspaceFactory(options: CreateWorkspaceFactoryOptions = {}) {\n const { sandbox: sandboxConfig, github, workItems } = options;\n const workspaceRegistry = options.workspaceRegistry ?? new FactoryWorkspaceRegistry();\n type GithubTokenRegistration = {\n inject: (token: string) => void;\n patKind: GithubPatKind;\n ghToken: string;\n generation: number;\n tokenReplacementPending: boolean;\n };\n // The session setup path runs commands and installs credentials, so it\n // needs `executeCommand` (required by `ExecutableSandbox`) plus core's\n // optional `setEnv`, which stays optional here because the token-refresh\n // path checks for it and reports its absence.\n type SessionSandbox = ExecutableSandbox & { setEnv?: WorkspaceSandbox['setEnv'] };\n const githubTokenInjectors = new Map<string, GithubTokenRegistration>();\n const githubTokenReconciliations = new Map<string, Promise<void>>();\n // Workspace identity cache: concurrent resolutions of the same session must\n // observe the same Workspace object even when no Mastra registry is wired\n // (the registry stays the source of truth when present).\n const constructedWorkspaces = new Map<string, Workspace>();\n\n return async ({ requestContext, mastra, skillExtension }: DynamicWorkspaceContext) => {\n const effectiveSkillExtension = skillExtension ?? factorySkillExtension;\n const ctx = requestContext.get('controller') as AgentControllerRequestContext<MastraCodeState> | undefined;\n const session =\n ctx?.resourceId && github ? await github.sourceControlStorage.sessions.getBySessionId(ctx.resourceId) : null;\n\n if (!session) {\n // No factory session, no workspace. Chat still works; workspace tools\n // are simply not registered. Host-cwd behavior is opt-in via a\n // LocalSandbox callback rooted wherever the deployer wants — the\n // resolver never hands out the server host's own filesystem.\n return undefined;\n }\n\n const user = getFactoryAuthUserFromContext(requestContext);\n const userId = getFactoryAuthUserId(user);\n // No identity at all is a server-side caller that forgot to seed one\n // (webhook, cron), not someone reaching for another user's session.\n if (!user?.organizationId || !userId) {\n throw new Error(`Factory session ${session.sessionId} was resolved without a caller identity`);\n }\n // Org-visible sessions open to any member of the owning organization;\n // only private sessions stay owner-only. Cross-org access never passes.\n if (user.organizationId !== session.orgId || (session.visibility === 'private' && userId !== session.userId)) {\n throw new Error(`Factory session ${session.sessionId} is not available to the current user`);\n }\n if (!sandboxConfig || !github) {\n throw new Error('GitHub and a sandbox callback are required to create a Factory session workspace');\n }\n const createSessionSandboxInstance = sandboxConfig;\n\n const storage = github.sourceControlStorage;\n const projectRepository = await storage.projectRepositories.get({\n orgId: session.orgId,\n id: session.projectRepositoryId,\n });\n if (!projectRepository) throw new Error(`Repository link ${session.projectRepositoryId} was not found`);\n // The remaining reads only depend on the repository link — issue them in\n // parallel instead of paying four sequential storage round-trips.\n const [connection, repository] = await Promise.all([\n storage.connections.get({ orgId: session.orgId, id: projectRepository.connectionId }),\n storage.repositories.get({ orgId: session.orgId, id: projectRepository.repositoryId }),\n ]);\n if (!connection || !repository) throw new Error(`Repository link ${session.projectRepositoryId} is incomplete`);\n const installation = await storage.installations.get({ orgId: session.orgId, id: connection.installationId });\n if (!installation) throw new Error(`GitHub installation ${connection.installationId} was not found`);\n const repoFullName = repository.slug;\n\n // Construct (or fetch) the session's memoized sandbox instance.\n // Construction is cheap and side-effect-free by the callback contract —\n // the VM is provisioned on `start()`, which only the materialization\n // pipeline calls. The workdir is never persisted or trusted from storage\n // or client input (the stale-workdir incident class came from reusing\n // `session.sandboxWorkdir` written under a different provider): local\n // sandboxes derive it at construction, remote sandboxes clone into the\n // VM's own home so it resolves lazily at first start.\n // `runSetupOn` references `runSessionSetup`, defined below — it is only\n // invoked during start, long after this closure fully initializes.\n const runSetupOn = (target: unknown, workdir: string) =>\n runSessionSetup(requireExec(target as WorkspaceSandbox), workdir);\n const guardedSetup = createSessionSetupHook(runSetupOn, session.id, repoFullName);\n // Composed start hook: marker-guarded repo setup, then per-start\n // credential install. It runs inside the provider's start lifecycle on\n // EVERY start (create or reconnect) — providers own lazy start\n // (`ensureRunning()` on first command) and dead-VM self-healing (E2B\n // `retryOnDead`, Platform status reset on destroy), so a replacement VM\n // re-enters this hook and heals itself with credentials at least as\n // fresh as the start that installed them.\n const setupHook: SandboxStartHook = async args => {\n // A session retired before its first start must not set anything up.\n if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) {\n throw retiredError();\n }\n await guardedSetup(args);\n // Re-check after the (long) setup: a session retired mid-setup must not\n // register credentials for a workspace whose retirement teardown has\n // already run — the entry would leak forever. The VM itself is left to\n // the provider's idle timeout (accepted).\n if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) {\n throw retiredError();\n }\n const target: SessionSandbox = requireExec(args.sandbox);\n // The `gh` CLI needs a PAT when the org configured one (installation\n // tokens 403 on integration-restricted endpoints); git clone/checkout\n // keep using the minted installation token. Resolved per start so the\n // installed credential never outlives rotation.\n const patKind = await resolveGithubPatKind('default');\n const ghCliToken =\n (await getGithubPat(() => github.integrationStorage, session.orgId, patKind)) ?? (await getRepositoryToken());\n target.setEnv?.(env => ({ ...env, GH_TOKEN: ghCliToken }));\n // Observability only — nothing reads these columns for decisions. The\n // workdir was resolved (and memoized on the entry) by the guarded setup.\n void storage.sessions\n .setSandbox({ id: session.id, sandboxId: target.id, sandboxWorkdir: sessionEntry.workdir ?? '' })\n .catch(() => {});\n const tokenRegistration: GithubTokenRegistration = {\n inject: freshToken => {\n if (!target.setEnv) {\n throw new Error('The active sandbox provider does not support runtime GitHub token refresh.');\n }\n target.setEnv(env => ({ ...env, GH_TOKEN: freshToken }));\n tokenRegistration.ghToken = freshToken;\n },\n patKind,\n ghToken: ghCliToken,\n generation: 0,\n tokenReplacementPending: false,\n };\n githubTokenInjectors.set(workspaceId, tokenRegistration);\n registerGithubTokenContext(tokenRegistration);\n // Project skill roots were reported empty by the unmaterialized-source\n // guard before the checkout existed; rescan now. Fire-and-forget.\n void constructedWorkspaces\n .get(workspaceId)\n ?.skills?.refresh()\n .catch(() => {});\n };\n const constructSessionEntry = () =>\n getSessionSandbox(session.id, repoFullName, () => {\n const sandbox = createSessionSandboxInstance({\n sessionId: session.id,\n repoFullName,\n // Stored nullable; the context speaks `undefined` for absent.\n setupCommand: projectRepository.setupCommand ?? undefined,\n // Deferred call — only dereferenced when a provider needs the repo\n // outside the VM (template build time).\n getRepositoryAccess: () =>\n github.versionControl.getRepositoryAccess({ orgId: session.orgId, repositoryId: repository.id }),\n });\n // Attached inside the construction closure, so exactly once per\n // instance — `constructSessionEntry` runs on every open and would\n // stack a wrapper per call. Factory's setup runs first: a hook the\n // callback installed itself expects a prepared workspace.\n sandbox.setOnStart(previous => async args => {\n await setupHook(args);\n await previous?.(args);\n });\n return sandbox;\n });\n const sessionEntry = constructSessionEntry();\n const workdir = sessionEntry.workdir;\n const isLocalSandbox = sessionEntry.sandbox.provider === 'local';\n // The system prompt derives its working directory from `state.projectPath`\n // and falls back to the server's own process.cwd() when unset — which\n // points the agent at the host checkout (and lets it run `git checkout`\n // there instead of in its session workdir). Pin it to the session workdir\n // once known. A remote workdir resolves at the sandbox's first start, so\n // the pin self-heals on the next resolution after the VM has run.\n if (ctx && workdir && ctx.getState()?.projectPath !== workdir) {\n await ctx.setState({ projectPath: workdir, projectName: repoFullName });\n }\n\n const extensionId = effectiveSkillExtension ? `-${effectiveSkillExtension.id}` : '';\n const workspaceId = `${WORKSPACE_ID_PREFIX}-${projectRepository.id}-${session.id}${extensionId}`;\n const workspaceGeneration = workspaceRegistry.generation(session.sessionId);\n const configDir = DEFAULT_CONFIG_DIR;\n\n const getRepositoryToken = async (): Promise<string> => {\n const access = await github.versionControl.getRepositoryAccess({\n orgId: session.orgId,\n repositoryId: repository.id,\n });\n const token = access.authorization?.token;\n if (!token) throw new Error('Repository access did not include a bearer token for the Factory session');\n return token;\n };\n const resolveGithubPatKind = async (fallback: GithubPatKind): Promise<GithubPatKind> => {\n if (!workItems) return 'default';\n try {\n const address = getFactorySessionAddress(requestContext);\n const runBinding = address ? await workItems.findRunBindingBySession(address) : null;\n return runBinding?.role === 'review' && runBinding.status === 'active' && runBinding.orgId === session.orgId\n ? 'reviewer'\n : 'default';\n } catch {\n // Preserve the installed role when binding storage is temporarily unavailable.\n return fallback;\n }\n };\n const registerGithubTokenContext = (registered: GithubTokenRegistration): void => {\n const generation = registered.generation;\n registerGithubTokenInjector(requestContext, token => {\n if (githubTokenInjectors.get(workspaceId) !== registered || registered.generation !== generation) {\n throw new Error('GitHub token refresh no longer matches the active Factory workspace role.');\n }\n registered.inject(token);\n });\n registerGithubPatKind(requestContext, registered.patKind);\n };\n const reconcileGithubToken = async (): Promise<void> => {\n const previous = githubTokenReconciliations.get(workspaceId) ?? Promise.resolve();\n const reconciliation = previous\n .catch(() => {})\n .then(async () => {\n const registered = githubTokenInjectors.get(workspaceId);\n if (!registered) return;\n\n const previousPatKind = registered.patKind;\n const patKind = await resolveGithubPatKind(previousPatKind);\n if (githubTokenInjectors.get(workspaceId) !== registered) return;\n\n if (patKind !== previousPatKind) {\n registered.patKind = patKind;\n registered.generation += 1;\n }\n if (patKind === 'reviewer') registered.tokenReplacementPending = false;\n if (previousPatKind === 'reviewer' && patKind === 'default') {\n // Invalidate reviewer refresh contexts before replacement I/O so\n // they cannot restore reviewer credentials after a failed downgrade.\n registered.tokenReplacementPending = true;\n }\n\n let token = await getGithubPat(() => github.integrationStorage, session.orgId, patKind);\n if (!token && registered.tokenReplacementPending) token = await getRepositoryToken();\n if (githubTokenInjectors.get(workspaceId) !== registered) return;\n\n if (token && token !== registered.ghToken) {\n try {\n registered.inject(token);\n } catch (error) {\n if (registered.tokenReplacementPending) throw error;\n // Same-role rotations and reviewer upgrades remain best-effort.\n }\n }\n if (token && token === registered.ghToken) registered.tokenReplacementPending = false;\n registerGithubTokenContext(registered);\n });\n githubTokenReconciliations.set(workspaceId, reconciliation);\n try {\n await reconciliation;\n } finally {\n if (githubTokenReconciliations.get(workspaceId) === reconciliation) {\n githubTokenReconciliations.delete(workspaceId);\n }\n }\n };\n const reconcileRegisteredWorkspace = async (workspace: Workspace): Promise<Workspace> => {\n const registered = githubTokenInjectors.get(workspaceId);\n try {\n await reconcileGithubToken();\n } catch (error) {\n if (registered?.tokenReplacementPending && githubTokenInjectors.get(workspaceId) === registered) {\n // The role generation already invalidated reviewer refresh contexts.\n // Keep the pending registration so failed eviction cannot make a\n // still-live reviewer workspace look safe on the next reuse.\n let evicted = false;\n try {\n evicted = (await mastra?.removeWorkspace?.(workspaceId)) === true;\n } catch {\n // Preserve the credential-replacement error and retry on the next reuse.\n }\n try {\n await workspace.destroy();\n evicted = true;\n } catch {\n // The pending registration keeps the workspace quarantined if cleanup also fails.\n }\n if (evicted && githubTokenInjectors.get(workspaceId) === registered) {\n githubTokenInjectors.delete(workspaceId);\n constructedWorkspaces.delete(workspaceId);\n }\n }\n throw error;\n }\n if (registered && githubTokenInjectors.get(workspaceId) !== registered) {\n throw new Error('Factory workspace GitHub credential registration is no longer active.');\n }\n return workspace;\n };\n\n let existing: Workspace | undefined;\n try {\n existing = mastra?.getWorkspaceById(workspaceId) as Workspace | undefined;\n } catch {\n // Not registered yet.\n existing = undefined;\n }\n existing ??= constructedWorkspaces.get(workspaceId);\n if (existing) {\n existing.setToolsConfig(MASTRACODE_WORKSPACE_TOOLS);\n // A materialization kicked off by another caller may still be running.\n // Deliberately do NOT wait for it: a metadata-only resolution (thread\n // list, messages, activity) must not block on the clone/setup that lazy\n // materialization exists to avoid. Token reconciliation below no-ops\n // until the leader registers the injector, and the next reuse after\n // materialization completes reconciles against the live sandbox.\n return reconcileRegisteredWorkspace(existing);\n }\n\n const retiredError = () =>\n new Error(`Factory session ${session.sessionId} was retired during workspace materialization`);\n\n // The session's setup work: materialize the repo (disk-truth idempotent),\n // check out the session branch, run the configured setup command. Minted\n // tokens are fetched inside the run so a replacement VM healed mid-session\n // gets fresh credentials, not ones captured at workspace construction.\n const runSessionSetup = async (target: SessionSandbox, workdir: string): Promise<void> => {\n const token = await getRepositoryToken();\n // The configured setup command may shell out to `gh`/https fetches, so\n // GH_TOKEN must exist before setup runs — and it must be the same\n // gh-capable credential the session gets after start (installation\n // tokens 403 on integration-restricted endpoints when the org\n // configured a PAT).\n const setupPatKind = await resolveGithubPatKind('default');\n const setupGhToken = (await getGithubPat(() => github.integrationStorage, session.orgId, setupPatKind)) ?? token;\n target.setEnv?.(env => ({ ...env, GH_TOKEN: setupGhToken }));\n await materializeRepo({\n row: { id: session.id, sandboxWorkdir: workdir, materializedAt: session.materializedAt },\n repoInfo: { repoFullName: repoFullName, defaultBranch: repository.defaultBranch },\n sandbox: target,\n token,\n storage: storage.sessions,\n });\n await checkoutSessionBranch(target, workdir, {\n branch: session.branch,\n baseBranch: session.baseBranch || projectRepository.branch || repository.defaultBranch,\n token,\n repoFullName: repoFullName,\n });\n if (projectRepository.setupCommand) {\n // A setup command that already failed this session is skipped rather\n // than failing every start: the first failure surfaced loudly in the\n // tool result that triggered it, and a permanently failing onStart\n // would wedge the session — the agent could never get a shell to fix\n // the problem. Clone and checkout above still ran, so the tree is\n // real; the agent (or an edited setup command) takes it from here.\n if (hasFailedSetupCommand(session.id, projectRepository.setupCommand)) {\n console.warn('[Mastra Factory] Skipping setup command that already failed this session', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n });\n return;\n }\n try {\n await runSetupCommand(target, workdir, projectRepository.setupCommand);\n } catch (setupError) {\n if (projectRepository.teardownCommand) {\n try {\n await runTeardownCommand(target, workdir, projectRepository.teardownCommand, {\n timeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,\n });\n } catch (teardownError) {\n console.warn('[Mastra Factory] Worktree teardown after setup failure failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: teardownError instanceof Error ? teardownError.message.slice(-2000) : String(teardownError),\n });\n }\n }\n if (setupError instanceof SetupCommandError) {\n // The command ran and exited non-zero — a config problem, not an\n // infra one. Remember it so the next start recovers, and tell the\n // agent what happens next. Infra failures (transport, clone)\n // rethrow untouched and retry in full.\n recordFailedSetupCommand(session.id, projectRepository.setupCommand);\n throw new SetupCommandError(\n `${setupError.message}. The sandbox stays usable: this setup command is skipped for the rest of the session — retry your command, then fix the setup command in the repository settings or run it manually.`,\n setupError.code,\n );\n }\n throw setupError;\n }\n }\n };\n // The session's real sandbox goes straight onto the Workspace. Providers\n // own lazy start (`ensureRunning()` inside the first command/process op)\n // and dead-VM self-healing, and the composed `onStart` hook runs the repo\n // setup + credential install inside that lifecycle. Metadata-only\n // resolutions (thread-list polling) construct but never start.\n const sessionSandbox: SessionSandbox = requireExec(sessionEntry.sandbox);\n\n const filesystem = new SandboxFilesystem({\n id: `sandbox-fs:${workspaceId}`,\n sandbox: sessionSandbox,\n // Lazy: a remote workdir is only knowable once a VM runs. The first\n // file operation resolves it (starting the VM — which materializes the\n // repo via the onStart hook — when needed) and memoizes it.\n workdir: () => resolveSessionWorkdir(session.id, sessionEntry.sandbox, repoFullName),\n });\n const projectSkillPaths = [path.join(configDir, 'skills'), '.claude/skills', '.agents/skills'];\n const guardedSkillFallback = new UnmaterializedAwareSkillSource(\n filesystem,\n () => sessionEntry.sandbox.status === 'running',\n );\n const skillPaths = [...(effectiveSkillExtension?.paths ?? []), ...projectSkillPaths];\n const workspace = new Workspace({\n id: workspaceId,\n name: 'Mastra Code Factory Session Workspace',\n filesystem,\n sandbox: sessionSandbox as unknown as ConstructorParameters<typeof Workspace>[0]['sandbox'],\n tools: MASTRACODE_WORKSPACE_TOOLS,\n skills: skillPaths,\n // Project skill roots live in the sandbox checkout; guard them so skill\n // discovery before materialization (e.g. kickoff skill resolution in the\n // start coordinator) never forces sandbox provisioning.\n skillSource:\n effectiveSkillExtension?.createSource(guardedSkillFallback, projectSkillPaths) ?? guardedSkillFallback,\n });\n // Register with the Mastra instance so sync HTTP handlers that resolve\n // the workspace via `mastra.getWorkspaceById(id)` (file tree, permissions\n // probe, MCP/tool routes) find it instead of throwing\n // `MASTRA_GET_WORKSPACE_BY_ID_NOT_FOUND`. `addWorkspace` is idempotent on\n // key collision, so concurrent first resolutions stay race-safe (start\n // itself is coalesced by the sandbox base class + the session memo).\n mastra?.addWorkspace(workspace, workspaceId, { source: 'mastra' });\n // Cache synchronously with construction: the `await` below is a suspension\n // point, and a concurrent resolution for the same session must observe this\n // workspace rather than build a second one.\n constructedWorkspaces.set(workspaceId, workspace);\n // Retirement is registered against the workspace itself rather than the\n // sandbox: construction is eager while the VM start is lazy, so a session\n // retired before its first tool call still has a workspace (and possibly a\n // token injector) that must be torn down.\n const registered = await workspaceRegistry.register(\n session.sessionId,\n workspaceId,\n workspaceGeneration,\n async () => {\n githubTokenInjectors.delete(workspaceId);\n constructedWorkspaces.delete(workspaceId);\n // Retirement drops the memoized session sandbox so a later re-open\n // constructs (and the provider resolves) fresh instead of reusing an\n // instance whose VM the retirement path may stop or destroy.\n evictSessionSandbox(session.id);\n await mastra?.removeWorkspace?.(workspaceId);\n },\n );\n if (!registered) {\n throw new Error(`Factory session ${session.sessionId} was retired during workspace materialization`);\n }\n\n // Fully lazy: nothing provisions until the first real sandbox operation\n // (`ensureRunning()` inside the provider). A background warm-up at session\n // start was considered and dropped — it speculatively created a VM for\n // every session, including ones whose agent never touches the workspace.\n return workspace;\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"workspace.js","names":["#bundledSource","#localSource","#fallbackSkillRoots","#layerFor","#isFactoryPath","#factoryPath","#entries","#generations"],"sources":["../src/workspace.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport path, { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { SandboxFilesystem } from '@mastra/code-sdk/agents/sandbox-filesystem';\nimport { MASTRACODE_WORKSPACE_TOOLS } from '@mastra/code-sdk/agents/tool-availability';\nimport type { getDynamicWorkspace, WorkspaceSkillExtension } from '@mastra/code-sdk/agents/workspace';\nimport { DEFAULT_CONFIG_DIR } from '@mastra/code-sdk/constants';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport { LocalSkillSource, Workspace } from '@mastra/core/workspace';\nimport type {\n SandboxStartHook,\n SkillSource,\n SkillSourceEntry,\n SkillSourceStat,\n WorkspaceSandbox,\n} from '@mastra/core/workspace';\nimport { getFactoryAuthUserFromContext, getFactoryAuthUserId } from './auth.js';\nimport type { MastraFactorySandboxConfig } from './factory.js';\nimport type { GithubIntegration } from './integrations/github/integration.js';\nimport { getGithubPat } from './integrations/github/pat.js';\nimport type { GithubPatKind } from './integrations/github/pat.js';\nimport {\n checkoutSessionBranch,\n DEFAULT_COMMAND_TIMEOUT_MS,\n materializeRepo,\n runSetupCommand,\n runTeardownCommand,\n SetupCommandError,\n} from './integrations/github/sandbox.js';\nimport { registerGithubPatKind, registerGithubTokenInjector } from './integrations/github/token-refresh.js';\nimport { getFactorySessionAddress } from './rules/binding-context.js';\nimport { requireExec } from './sandbox/materialization.js';\nimport type { ExecutableSandbox } from './sandbox/materialization.js';\nimport {\n createSessionSetupHook,\n evictSessionSandbox,\n getSessionSandbox,\n hasFailedSetupCommand,\n recordFailedSetupCommand,\n resolveSessionWorkdir,\n} from './sandbox/session-sandbox.js';\nimport type { SessionSetupGate } from './sandbox/session-sandbox.js';\n\nimport type { WorkItemsStorage } from './storage/domains/work-items/base.js';\n\nconst WORKSPACE_ID_PREFIX = 'mfw';\nconst bundleDirectory = dirname(fileURLToPath(import.meta.url));\nconst bundledFactorySkillsPath = join(bundleDirectory, 'factory-skills');\nexport const BUNDLED_FACTORY_SKILLS_PATH =\n [\n // Deploy bundle: the consumer copies `factory-skills/` next to the built\n // server module (e.g. via its public/ dir).\n bundledFactorySkillsPath,\n // Package layout: `dist/../factory-skills` (also `src/../factory-skills`\n // when running tests against sources).\n join(bundleDirectory, '..', 'factory-skills'),\n ].find(existsSync) ?? bundledFactorySkillsPath;\n\n/**\n * Resolve the consumer repo's local Factory skills root, if any. Checked in\n * addition to the bundled skills so projects can add (or override) Factory\n * skills without patching the installed package. Candidates cover the cwd\n * variants the dev server runs with (`repo root`, `--dir src/mastra` which\n * runs with cwd `src/mastra/public`).\n */\nexport function resolveLocalFactorySkillsPath(cwd: string = process.cwd()): string | undefined {\n const candidates = [\n join(cwd, 'src', 'mastra', 'public', 'factory-skills'),\n join(cwd, 'public', 'factory-skills'),\n join(cwd, 'factory-skills'),\n ];\n return candidates.find(\n candidate => path.normalize(candidate) !== path.normalize(BUNDLED_FACTORY_SKILLS_PATH) && existsSync(candidate),\n );\n}\nconst FACTORY_SKILLS_MOUNT = path.resolve(path.parse(process.cwd()).root, '__mastracode_factory_skills__');\nexport const FACTORY_SKILL_NAMES = new Set([\n 'configure-factory-rules',\n 'factory-complete-issue',\n 'factory-plan',\n 'factory-rereview',\n 'factory-review',\n 'factory-triage',\n]);\n\nexport class FactorySkillSource implements SkillSource {\n readonly #bundledSource = new LocalSkillSource({ basePath: BUNDLED_FACTORY_SKILLS_PATH });\n readonly #localSource: LocalSkillSource | undefined;\n readonly #fallbackSkillRoots: Set<string>;\n\n constructor(\n readonly fallback: SkillSource,\n fallbackSkillRoots: string[],\n localSkillsPath: string | undefined = resolveLocalFactorySkillsPath(),\n ) {\n this.#localSource = localSkillsPath ? new LocalSkillSource({ basePath: localSkillsPath }) : undefined;\n this.#fallbackSkillRoots = new Set(fallbackSkillRoots.map(skillPath => path.normalize(skillPath)));\n }\n\n #isFactoryPath(skillPath: string): boolean {\n const normalized = path.normalize(skillPath);\n return normalized === FACTORY_SKILLS_MOUNT || normalized.startsWith(`${FACTORY_SKILLS_MOUNT}${path.sep}`);\n }\n\n #factoryPath(skillPath: string): string {\n return path.relative(FACTORY_SKILLS_MOUNT, path.normalize(skillPath));\n }\n\n /** Pick the layer serving this mount-relative path: local wins when it has the entry. */\n async #layerFor(relativePath: string): Promise<LocalSkillSource> {\n if (this.#localSource && (await this.#localSource.exists(relativePath))) return this.#localSource;\n return this.#bundledSource;\n }\n\n async exists(skillPath: string): Promise<boolean> {\n if (!this.#isFactoryPath(skillPath)) return this.fallback.exists(skillPath);\n const relative = this.#factoryPath(skillPath);\n if (this.#localSource && (await this.#localSource.exists(relative))) return true;\n return this.#bundledSource.exists(relative);\n }\n\n async stat(skillPath: string): Promise<SkillSourceStat> {\n if (!this.#isFactoryPath(skillPath)) return this.fallback.stat(skillPath);\n const relative = this.#factoryPath(skillPath);\n return (await this.#layerFor(relative)).stat(relative);\n }\n\n async readFile(skillPath: string): Promise<string | Buffer> {\n if (!this.#isFactoryPath(skillPath)) return this.fallback.readFile(skillPath);\n const relative = this.#factoryPath(skillPath);\n return (await this.#layerFor(relative)).readFile(relative);\n }\n\n async readdir(skillPath: string): Promise<SkillSourceEntry[]> {\n if (this.#isFactoryPath(skillPath)) {\n const relative = this.#factoryPath(skillPath);\n const [bundledExists, localExists] = await Promise.all([\n this.#bundledSource.exists(relative),\n this.#localSource?.exists(relative) ?? Promise.resolve(false),\n ]);\n if (!bundledExists && !localExists) throw skillSourceEnoent(skillPath);\n const [bundledEntries, localEntries] = await Promise.all([\n bundledExists ? this.#bundledSource.readdir(relative) : [],\n localExists ? this.#localSource!.readdir(relative) : [],\n ]);\n const merged = new Map<string, SkillSourceEntry>();\n for (const entry of bundledEntries) merged.set(entry.name, entry);\n // Local entries override bundled names.\n for (const entry of localEntries) merged.set(entry.name, entry);\n return [...merged.values()];\n }\n const entries = await this.fallback.readdir(skillPath);\n if (this.#fallbackSkillRoots.has(path.normalize(skillPath))) {\n return entries.filter(entry => !FACTORY_SKILL_NAMES.has(entry.name));\n }\n return entries;\n }\n\n realpath(skillPath: string): Promise<string> {\n if (this.#isFactoryPath(skillPath)) return Promise.resolve(path.normalize(skillPath));\n return this.fallback.realpath ? this.fallback.realpath(skillPath) : Promise.resolve(skillPath);\n }\n}\n\n/** Build a Node-style ENOENT error so callers can treat missing skills like fs misses. */\nfunction skillSourceEnoent(skillPath: string): Error {\n const error = new Error(`ENOENT: no such file or directory, '${skillPath}'`) as Error & { code: string };\n error.code = 'ENOENT';\n return error;\n}\n\n/**\n * Sandbox-backed skill fallback that stays inert until the session sandbox is\n * actually materialized. Skill discovery runs on latency-sensitive paths (the\n * Factory start coordinator resolves the kickoff skill before the start route\n * responds); without this guard the first project-root read would hit the lazy\n * sandbox handle and force full provisioning + repo materialization. While the\n * sandbox is unmaterialized, project skill roots simply appear empty — bundled\n * Factory skills resolve from local disk via `FactorySkillSource`. Once the\n * sandbox exists, every call delegates straight through.\n */\nclass UnmaterializedAwareSkillSource implements SkillSource {\n constructor(\n readonly fallback: SkillSource,\n readonly isMaterialized: () => boolean,\n ) {}\n\n async exists(skillPath: string): Promise<boolean> {\n return this.isMaterialized() ? this.fallback.exists(skillPath) : false;\n }\n\n async stat(skillPath: string): Promise<SkillSourceStat> {\n if (!this.isMaterialized()) throw skillSourceEnoent(skillPath);\n return this.fallback.stat(skillPath);\n }\n\n async readFile(skillPath: string): Promise<string | Buffer> {\n if (!this.isMaterialized()) throw skillSourceEnoent(skillPath);\n return this.fallback.readFile(skillPath);\n }\n\n async readdir(skillPath: string): Promise<SkillSourceEntry[]> {\n return this.isMaterialized() ? this.fallback.readdir(skillPath) : [];\n }\n\n realpath(skillPath: string): Promise<string> {\n if (!this.isMaterialized()) return Promise.resolve(skillPath);\n return this.fallback.realpath ? this.fallback.realpath(skillPath) : Promise.resolve(skillPath);\n }\n}\n\nconst factorySkillExtension: WorkspaceSkillExtension = {\n id: 'web-factory',\n paths: [FACTORY_SKILLS_MOUNT],\n createSource: (fallback, fallbackSkillRoots) => new FactorySkillSource(fallback, fallbackSkillRoots),\n};\n\ntype DynamicWorkspaceContext = Parameters<typeof getDynamicWorkspace>[0];\n\nexport interface CreateWorkspaceFactoryOptions {\n /** Factory sandbox runtime config (session sandbox callback). */\n sandbox?: MastraFactorySandboxConfig;\n /** GitHub integration used to resolve Factory sessions and mint repo tokens. */\n github?: GithubIntegration;\n /** Work-items storage used to resolve the session's run-binding role, so\n * review-board sessions get the reviewer PAT as `GH_TOKEN`. Optional —\n * without it every session uses the default (worker) PAT. */\n workItems?: Pick<WorkItemsStorage, 'findRunBindingBySession'>;\n /** Runtime workspace/token registrations invalidated when a session retires. */\n workspaceRegistry?: FactoryWorkspaceRegistry;\n}\n\ntype WorkspaceUnregister = () => Promise<void> | void;\n\n/** Tracks dynamic Factory workspaces by persisted session id for retirement. */\nexport class FactoryWorkspaceRegistry {\n readonly #entries = new Map<string, Map<string, WorkspaceUnregister>>();\n readonly #generations = new Map<string, number>();\n\n generation(sessionId: string): number {\n return this.#generations.get(sessionId) ?? 0;\n }\n\n async register(\n sessionId: string,\n workspaceId: string,\n generation: number,\n unregister: WorkspaceUnregister,\n ): Promise<boolean> {\n if (generation !== this.generation(sessionId)) {\n await unregister();\n return false;\n }\n const entries = this.#entries.get(sessionId) ?? new Map<string, WorkspaceUnregister>();\n entries.set(workspaceId, unregister);\n this.#entries.set(sessionId, entries);\n return true;\n }\n\n async invalidateSession(sessionId: string): Promise<void> {\n this.#generations.set(sessionId, this.generation(sessionId) + 1);\n const entries = this.#entries.get(sessionId);\n if (!entries) return;\n this.#entries.delete(sessionId);\n const results = await Promise.allSettled([...entries.values()].map(unregister => unregister()));\n const failure = results.find(result => result.status === 'rejected');\n if (failure?.status === 'rejected') throw failure.reason;\n }\n}\n\nexport function createWorkspaceFactory(options: CreateWorkspaceFactoryOptions = {}) {\n const { sandbox: sandboxConfig, github, workItems } = options;\n const workspaceRegistry = options.workspaceRegistry ?? new FactoryWorkspaceRegistry();\n type GithubTokenRegistration = {\n inject: (token: string) => void;\n patKind: GithubPatKind;\n ghToken: string;\n generation: number;\n tokenReplacementPending: boolean;\n };\n // The session setup path runs commands and installs credentials, so it\n // needs `executeCommand` (required by `ExecutableSandbox`) plus core's\n // optional `setEnv`, which stays optional here because the token-refresh\n // path checks for it and reports its absence.\n type SessionSandbox = ExecutableSandbox & { setEnv?: WorkspaceSandbox['setEnv'] };\n const githubTokenInjectors = new Map<string, GithubTokenRegistration>();\n const githubTokenReconciliations = new Map<string, Promise<void>>();\n // Workspace identity cache: concurrent resolutions of the same session must\n // observe the same Workspace object even when no Mastra registry is wired\n // (the registry stays the source of truth when present).\n const constructedWorkspaces = new Map<string, Workspace>();\n\n return async ({ requestContext, mastra, skillExtension }: DynamicWorkspaceContext) => {\n const effectiveSkillExtension = skillExtension ?? factorySkillExtension;\n const ctx = requestContext.get('controller') as AgentControllerRequestContext<MastraCodeState> | undefined;\n const session =\n ctx?.resourceId && github ? await github.sourceControlStorage.sessions.getBySessionId(ctx.resourceId) : null;\n\n if (!session) {\n // No factory session, no workspace. Chat still works; workspace tools\n // are simply not registered. Host-cwd behavior is opt-in via a\n // LocalSandbox callback rooted wherever the deployer wants — the\n // resolver never hands out the server host's own filesystem.\n return undefined;\n }\n\n const user = getFactoryAuthUserFromContext(requestContext);\n const userId = getFactoryAuthUserId(user);\n // No identity at all is a server-side caller that forgot to seed one\n // (webhook, cron), not someone reaching for another user's session.\n if (!user?.organizationId || !userId) {\n throw new Error(`Factory session ${session.sessionId} was resolved without a caller identity`);\n }\n // Org-visible sessions open to any member of the owning organization;\n // only private sessions stay owner-only. Cross-org access never passes.\n if (user.organizationId !== session.orgId || (session.visibility === 'private' && userId !== session.userId)) {\n throw new Error(`Factory session ${session.sessionId} is not available to the current user`);\n }\n if (!sandboxConfig || !github) {\n throw new Error('GitHub and a sandbox callback are required to create a Factory session workspace');\n }\n const createSessionSandboxInstance = sandboxConfig;\n\n const storage = github.sourceControlStorage;\n const projectRepository = await storage.projectRepositories.get({\n orgId: session.orgId,\n id: session.projectRepositoryId,\n });\n if (!projectRepository) throw new Error(`Repository link ${session.projectRepositoryId} was not found`);\n // The remaining reads only depend on the repository link — issue them in\n // parallel instead of paying four sequential storage round-trips.\n const [connection, repository] = await Promise.all([\n storage.connections.get({ orgId: session.orgId, id: projectRepository.connectionId }),\n storage.repositories.get({ orgId: session.orgId, id: projectRepository.repositoryId }),\n ]);\n if (!connection || !repository) throw new Error(`Repository link ${session.projectRepositoryId} is incomplete`);\n const installation = await storage.installations.get({ orgId: session.orgId, id: connection.installationId });\n if (!installation) throw new Error(`GitHub installation ${connection.installationId} was not found`);\n const repoFullName = repository.slug;\n\n // Construct (or fetch) the session's memoized sandbox instance.\n // Construction is cheap and side-effect-free by the callback contract —\n // the VM is provisioned on `start()`, which only the materialization\n // pipeline calls. The workdir is never persisted or trusted from storage\n // or client input (the stale-workdir incident class came from reusing\n // `session.sandboxWorkdir` written under a different provider): local\n // sandboxes derive it at construction, remote sandboxes clone into the\n // VM's own home so it resolves lazily at first start.\n // `runSetupOn` references `runSessionSetup`, defined below — it is only\n // invoked during start, long after this closure fully initializes.\n const runSetupOn = (target: unknown, workdir: string, gate: SessionSetupGate) =>\n runSessionSetup(requireExec(target as WorkspaceSandbox), workdir, gate);\n const guardedSetup = createSessionSetupHook(\n runSetupOn,\n session.id,\n repoFullName,\n projectRepository.setupCommand ?? undefined,\n );\n // Composed start hook: marker-guarded repo setup, then per-start\n // credential install. It runs inside the provider's start lifecycle on\n // EVERY start (create or reconnect) — providers own lazy start\n // (`ensureRunning()` on first command) and dead-VM self-healing (E2B\n // `retryOnDead`, Platform status reset on destroy), so a replacement VM\n // re-enters this hook and heals itself with credentials at least as\n // fresh as the start that installed them.\n const setupHook: SandboxStartHook = async args => {\n // A session retired before its first start must not set anything up.\n if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) {\n throw retiredError();\n }\n await guardedSetup(args);\n // Re-check after the (long) setup: a session retired mid-setup must not\n // register credentials for a workspace whose retirement teardown has\n // already run — the entry would leak forever. The VM itself is left to\n // the provider's idle timeout (accepted).\n if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) {\n throw retiredError();\n }\n const target: SessionSandbox = requireExec(args.sandbox);\n // The `gh` CLI needs a PAT when the org configured one (installation\n // tokens 403 on integration-restricted endpoints); git clone/checkout\n // keep using the minted installation token. Resolved per start so the\n // installed credential never outlives rotation.\n const patKind = await resolveGithubPatKind('default');\n const ghCliToken =\n (await getGithubPat(() => github.integrationStorage, session.orgId, patKind)) ?? (await getRepositoryToken());\n target.setEnv?.(env => ({ ...env, GH_TOKEN: ghCliToken }));\n // Observability only — nothing reads these columns for decisions. The\n // workdir was resolved (and memoized on the entry) by the guarded setup.\n void storage.sessions\n .setSandbox({ id: session.id, sandboxId: target.id, sandboxWorkdir: sessionEntry.workdir ?? '' })\n .catch(() => {});\n const tokenRegistration: GithubTokenRegistration = {\n inject: freshToken => {\n if (!target.setEnv) {\n throw new Error('The active sandbox provider does not support runtime GitHub token refresh.');\n }\n target.setEnv(env => ({ ...env, GH_TOKEN: freshToken }));\n tokenRegistration.ghToken = freshToken;\n },\n patKind,\n ghToken: ghCliToken,\n generation: 0,\n tokenReplacementPending: false,\n };\n githubTokenInjectors.set(workspaceId, tokenRegistration);\n registerGithubTokenContext(tokenRegistration);\n // Project skill roots were reported empty by the unmaterialized-source\n // guard before the checkout existed; rescan now. Fire-and-forget.\n void constructedWorkspaces\n .get(workspaceId)\n ?.skills?.refresh()\n .catch(() => {});\n };\n const constructSessionEntry = () =>\n getSessionSandbox(session.id, repoFullName, () => {\n const sandbox = createSessionSandboxInstance({\n sessionId: session.id,\n repoFullName,\n // Stored nullable; the context speaks `undefined` for absent.\n setupCommand: projectRepository.setupCommand ?? undefined,\n // Deferred call — only dereferenced when a provider needs the repo\n // outside the VM (template build time).\n getRepositoryAccess: () =>\n github.versionControl.getRepositoryAccess({ orgId: session.orgId, repositoryId: repository.id }),\n });\n // Attached inside the construction closure, so exactly once per\n // instance — `constructSessionEntry` runs on every open and would\n // stack a wrapper per call. Factory's setup runs first: a hook the\n // callback installed itself expects a prepared workspace.\n sandbox.setOnStart(previous => async args => {\n await setupHook(args);\n await previous?.(args);\n });\n return sandbox;\n });\n const sessionEntry = constructSessionEntry();\n const workdir = sessionEntry.workdir;\n const isLocalSandbox = sessionEntry.sandbox.provider === 'local';\n // The system prompt derives its working directory from `state.projectPath`\n // and falls back to the server's own process.cwd() when unset — which\n // points the agent at the host checkout (and lets it run `git checkout`\n // there instead of in its session workdir). Pin it to the session workdir\n // once known. A remote workdir resolves at the sandbox's first start, so\n // the pin self-heals on the next resolution after the VM has run.\n if (ctx && workdir && ctx.getState()?.projectPath !== workdir) {\n await ctx.setState({ projectPath: workdir, projectName: repoFullName });\n }\n\n const extensionId = effectiveSkillExtension ? `-${effectiveSkillExtension.id}` : '';\n const workspaceId = `${WORKSPACE_ID_PREFIX}-${projectRepository.id}-${session.id}${extensionId}`;\n const workspaceGeneration = workspaceRegistry.generation(session.sessionId);\n const configDir = DEFAULT_CONFIG_DIR;\n\n const getRepositoryToken = async (): Promise<string> => {\n const access = await github.versionControl.getRepositoryAccess({\n orgId: session.orgId,\n repositoryId: repository.id,\n });\n const token = access.authorization?.token;\n if (!token) throw new Error('Repository access did not include a bearer token for the Factory session');\n return token;\n };\n const resolveGithubPatKind = async (fallback: GithubPatKind): Promise<GithubPatKind> => {\n if (!workItems) return 'default';\n try {\n const address = getFactorySessionAddress(requestContext);\n const runBinding = address ? await workItems.findRunBindingBySession(address) : null;\n return runBinding?.role === 'review' && runBinding.status === 'active' && runBinding.orgId === session.orgId\n ? 'reviewer'\n : 'default';\n } catch {\n // Preserve the installed role when binding storage is temporarily unavailable.\n return fallback;\n }\n };\n const registerGithubTokenContext = (registered: GithubTokenRegistration): void => {\n const generation = registered.generation;\n registerGithubTokenInjector(requestContext, token => {\n if (githubTokenInjectors.get(workspaceId) !== registered || registered.generation !== generation) {\n throw new Error('GitHub token refresh no longer matches the active Factory workspace role.');\n }\n registered.inject(token);\n });\n registerGithubPatKind(requestContext, registered.patKind);\n };\n const reconcileGithubToken = async (): Promise<void> => {\n const previous = githubTokenReconciliations.get(workspaceId) ?? Promise.resolve();\n const reconciliation = previous\n .catch(() => {})\n .then(async () => {\n const registered = githubTokenInjectors.get(workspaceId);\n if (!registered) return;\n\n const previousPatKind = registered.patKind;\n const patKind = await resolveGithubPatKind(previousPatKind);\n if (githubTokenInjectors.get(workspaceId) !== registered) return;\n\n if (patKind !== previousPatKind) {\n registered.patKind = patKind;\n registered.generation += 1;\n }\n if (patKind === 'reviewer') registered.tokenReplacementPending = false;\n if (previousPatKind === 'reviewer' && patKind === 'default') {\n // Invalidate reviewer refresh contexts before replacement I/O so\n // they cannot restore reviewer credentials after a failed downgrade.\n registered.tokenReplacementPending = true;\n }\n\n let token = await getGithubPat(() => github.integrationStorage, session.orgId, patKind);\n if (!token && registered.tokenReplacementPending) token = await getRepositoryToken();\n if (githubTokenInjectors.get(workspaceId) !== registered) return;\n\n if (token && token !== registered.ghToken) {\n try {\n registered.inject(token);\n } catch (error) {\n if (registered.tokenReplacementPending) throw error;\n // Same-role rotations and reviewer upgrades remain best-effort.\n }\n }\n if (token && token === registered.ghToken) registered.tokenReplacementPending = false;\n registerGithubTokenContext(registered);\n });\n githubTokenReconciliations.set(workspaceId, reconciliation);\n try {\n await reconciliation;\n } finally {\n if (githubTokenReconciliations.get(workspaceId) === reconciliation) {\n githubTokenReconciliations.delete(workspaceId);\n }\n }\n };\n const reconcileRegisteredWorkspace = async (workspace: Workspace): Promise<Workspace> => {\n const registered = githubTokenInjectors.get(workspaceId);\n try {\n await reconcileGithubToken();\n } catch (error) {\n if (registered?.tokenReplacementPending && githubTokenInjectors.get(workspaceId) === registered) {\n // The role generation already invalidated reviewer refresh contexts.\n // Keep the pending registration so failed eviction cannot make a\n // still-live reviewer workspace look safe on the next reuse.\n let evicted = false;\n try {\n evicted = (await mastra?.removeWorkspace?.(workspaceId)) === true;\n } catch {\n // Preserve the credential-replacement error and retry on the next reuse.\n }\n try {\n await workspace.destroy();\n evicted = true;\n } catch {\n // The pending registration keeps the workspace quarantined if cleanup also fails.\n }\n if (evicted && githubTokenInjectors.get(workspaceId) === registered) {\n githubTokenInjectors.delete(workspaceId);\n constructedWorkspaces.delete(workspaceId);\n }\n }\n throw error;\n }\n if (registered && githubTokenInjectors.get(workspaceId) !== registered) {\n throw new Error('Factory workspace GitHub credential registration is no longer active.');\n }\n return workspace;\n };\n\n let existing: Workspace | undefined;\n try {\n existing = mastra?.getWorkspaceById(workspaceId) as Workspace | undefined;\n } catch {\n // Not registered yet.\n existing = undefined;\n }\n existing ??= constructedWorkspaces.get(workspaceId);\n if (existing) {\n existing.setToolsConfig(MASTRACODE_WORKSPACE_TOOLS);\n // A materialization kicked off by another caller may still be running.\n // Deliberately do NOT wait for it: a metadata-only resolution (thread\n // list, messages, activity) must not block on the clone/setup that lazy\n // materialization exists to avoid. Token reconciliation below no-ops\n // until the leader registers the injector, and the next reuse after\n // materialization completes reconciles against the live sandbox.\n return reconcileRegisteredWorkspace(existing);\n }\n\n const retiredError = () =>\n new Error(`Factory session ${session.sessionId} was retired during workspace materialization`);\n\n // The session's setup work: materialize the repo (disk-truth idempotent),\n // check out the session branch, run the configured setup command. Minted\n // tokens are fetched inside the run so a replacement VM healed mid-session\n // gets fresh credentials, not ones captured at workspace construction.\n const runSessionSetup = async (target: SessionSandbox, workdir: string, gate: SessionSetupGate): Promise<void> => {\n const token = await getRepositoryToken();\n // The configured setup command may shell out to `gh`/https fetches, so\n // GH_TOKEN must exist before setup runs — and it must be the same\n // gh-capable credential the session gets after start (installation\n // tokens 403 on integration-restricted endpoints when the org\n // configured a PAT).\n const setupPatKind = await resolveGithubPatKind('default');\n const setupGhToken = (await getGithubPat(() => github.integrationStorage, session.orgId, setupPatKind)) ?? token;\n target.setEnv?.(env => ({ ...env, GH_TOKEN: setupGhToken }));\n await materializeRepo({\n row: { id: session.id, sandboxWorkdir: workdir, materializedAt: session.materializedAt },\n repoInfo: { repoFullName: repoFullName, defaultBranch: repository.defaultBranch },\n sandbox: target,\n token,\n storage: storage.sessions,\n });\n await checkoutSessionBranch(target, workdir, {\n branch: session.branch,\n baseBranch: session.baseBranch || projectRepository.branch || repository.defaultBranch,\n token,\n repoFullName: repoFullName,\n });\n if (projectRepository.setupCommand && !gate.setupDone) {\n // A setup command that already failed this session is skipped rather\n // than failing every start: the first failure surfaced loudly in the\n // tool result that triggered it, and a permanently failing onStart\n // would wedge the session — the agent could never get a shell to fix\n // the problem. Clone and checkout above still ran, so the tree is\n // real; the agent (or an edited setup command) takes it from here.\n if (hasFailedSetupCommand(session.id, projectRepository.setupCommand)) {\n console.warn('[Mastra Factory] Skipping setup command that already failed this session', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n });\n return;\n }\n try {\n await runSetupCommand(target, workdir, projectRepository.setupCommand);\n await gate.markSetupDone();\n } catch (setupError) {\n if (projectRepository.teardownCommand) {\n try {\n await runTeardownCommand(target, workdir, projectRepository.teardownCommand, {\n timeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,\n });\n } catch (teardownError) {\n console.warn('[Mastra Factory] Worktree teardown after setup failure failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: teardownError instanceof Error ? teardownError.message.slice(-2000) : String(teardownError),\n });\n }\n }\n if (setupError instanceof SetupCommandError) {\n // The command ran and exited non-zero — a config problem, not an\n // infra one. Remember it so the next start recovers, and tell the\n // agent what happens next. Infra failures (transport, clone)\n // rethrow untouched and retry in full.\n recordFailedSetupCommand(session.id, projectRepository.setupCommand);\n throw new SetupCommandError(\n `${setupError.message}. The sandbox stays usable: this setup command is skipped for the rest of the session — retry your command, then fix the setup command in the repository settings or run it manually.`,\n setupError.code,\n );\n }\n throw setupError;\n }\n }\n };\n // The session's real sandbox goes straight onto the Workspace. Providers\n // own lazy start (`ensureRunning()` inside the first command/process op)\n // and dead-VM self-healing, and the composed `onStart` hook runs the repo\n // setup + credential install inside that lifecycle. Metadata-only\n // resolutions (thread-list polling) construct but never start.\n const sessionSandbox: SessionSandbox = requireExec(sessionEntry.sandbox);\n\n const filesystem = new SandboxFilesystem({\n id: `sandbox-fs:${workspaceId}`,\n sandbox: sessionSandbox,\n // Lazy: a remote workdir is only knowable once a VM runs. The first\n // file operation resolves it (starting the VM — which materializes the\n // repo via the onStart hook — when needed) and memoizes it.\n workdir: () => resolveSessionWorkdir(session.id, sessionEntry.sandbox, repoFullName),\n });\n const projectSkillPaths = [path.join(configDir, 'skills'), '.claude/skills', '.agents/skills'];\n const guardedSkillFallback = new UnmaterializedAwareSkillSource(\n filesystem,\n () => sessionEntry.sandbox.status === 'running',\n );\n const skillPaths = [...(effectiveSkillExtension?.paths ?? []), ...projectSkillPaths];\n const workspace = new Workspace({\n id: workspaceId,\n name: 'Mastra Code Factory Session Workspace',\n filesystem,\n sandbox: sessionSandbox as unknown as ConstructorParameters<typeof Workspace>[0]['sandbox'],\n tools: MASTRACODE_WORKSPACE_TOOLS,\n skills: skillPaths,\n // Project skill roots live in the sandbox checkout; guard them so skill\n // discovery before materialization (e.g. kickoff skill resolution in the\n // start coordinator) never forces sandbox provisioning.\n skillSource:\n effectiveSkillExtension?.createSource(guardedSkillFallback, projectSkillPaths) ?? guardedSkillFallback,\n });\n // Register with the Mastra instance so sync HTTP handlers that resolve\n // the workspace via `mastra.getWorkspaceById(id)` (file tree, permissions\n // probe, MCP/tool routes) find it instead of throwing\n // `MASTRA_GET_WORKSPACE_BY_ID_NOT_FOUND`. `addWorkspace` is idempotent on\n // key collision, so concurrent first resolutions stay race-safe (start\n // itself is coalesced by the sandbox base class + the session memo).\n mastra?.addWorkspace(workspace, workspaceId, { source: 'mastra' });\n // Cache synchronously with construction: the `await` below is a suspension\n // point, and a concurrent resolution for the same session must observe this\n // workspace rather than build a second one.\n constructedWorkspaces.set(workspaceId, workspace);\n // Retirement is registered against the workspace itself rather than the\n // sandbox: construction is eager while the VM start is lazy, so a session\n // retired before its first tool call still has a workspace (and possibly a\n // token injector) that must be torn down.\n const registered = await workspaceRegistry.register(\n session.sessionId,\n workspaceId,\n workspaceGeneration,\n async () => {\n githubTokenInjectors.delete(workspaceId);\n constructedWorkspaces.delete(workspaceId);\n // Retirement drops the memoized session sandbox so a later re-open\n // constructs (and the provider resolves) fresh instead of reusing an\n // instance whose VM the retirement path may stop or destroy.\n evictSessionSandbox(session.id);\n await mastra?.removeWorkspace?.(workspaceId);\n },\n );\n if (!registered) {\n throw new Error(`Factory session ${session.sessionId} was retired during workspace materialization`);\n }\n\n // Fully lazy: nothing provisions until the first real sandbox operation\n // (`ensureRunning()` inside the provider). A background warm-up at session\n // start was considered and dropped — it speculatively created a VM for\n // every session, including ones whose agent never touches the workspace.\n return workspace;\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/factory",
|
|
3
|
-
"version": "0.12.0-alpha.
|
|
3
|
+
"version": "0.12.0-alpha.11",
|
|
4
4
|
"description": "Mastra Software Factory module: the server core behind the Mastra Software Factory — storage domains, integrations, and surfaces for agent-powered software delivery",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -51,10 +51,10 @@
|
|
|
51
51
|
"hono": "^4.12.8",
|
|
52
52
|
"zod": "^4.3.6",
|
|
53
53
|
"@mastra/auth-studio": "1.3.5-alpha.0",
|
|
54
|
-
"@mastra/
|
|
55
|
-
"@mastra/
|
|
54
|
+
"@mastra/auth-workos": "1.6.5-alpha.0",
|
|
55
|
+
"@mastra/code-sdk": "1.6.0-alpha.8",
|
|
56
56
|
"@mastra/slack": "1.6.3-alpha.0",
|
|
57
|
-
"@mastra/
|
|
57
|
+
"@mastra/core": "1.64.0-alpha.6"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/node": "22.20.1",
|
|
@@ -63,10 +63,11 @@
|
|
|
63
63
|
"typescript": "^6.0.3",
|
|
64
64
|
"typescript-eslint": "^8.57.0",
|
|
65
65
|
"vitest": "4.1.10",
|
|
66
|
-
"@mastra/libsql": "1.22.3-alpha.1",
|
|
67
|
-
"@mastra/pg": "1.22.3-alpha.1",
|
|
68
66
|
"@internal/lint": "0.0.129",
|
|
69
|
-
"@
|
|
67
|
+
"@mastra/libsql": "1.22.3-alpha.2",
|
|
68
|
+
"@mastra/pg": "1.22.3-alpha.2",
|
|
69
|
+
"@internal/types-builder": "0.0.104",
|
|
70
|
+
"@internal/workspace": "0.0.1"
|
|
70
71
|
},
|
|
71
72
|
"engines": {
|
|
72
73
|
"node": ">=22.19.0"
|