@mastra/factory 0.8.0-alpha.14 → 0.8.0-alpha.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +30 -2
- package/dist/factory.js.map +1 -1
- package/dist/integrations/base.d.ts +8 -0
- package/dist/integrations/base.d.ts.map +1 -1
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +3 -1
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/reconcile-worker.d.ts +5 -0
- package/dist/integrations/github/reconcile-worker.d.ts.map +1 -1
- package/dist/integrations/github/reconcile-worker.js +7 -0
- package/dist/integrations/github/reconcile-worker.js.map +1 -1
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +7 -3
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +31 -1
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +12 -6
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts +7 -0
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +32 -3
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/routes/config.d.ts +8 -6
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +91 -56
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/projects.d.ts +9 -1
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +8 -0
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/surface.d.ts +5 -0
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +2 -0
- package/dist/routes/surface.js.map +1 -1
- package/dist/sandbox/base-checkpoint-triggers.d.ts +77 -0
- package/dist/sandbox/base-checkpoint-triggers.d.ts.map +1 -0
- package/dist/sandbox/base-checkpoint-triggers.js +137 -0
- package/dist/sandbox/base-checkpoint-triggers.js.map +1 -0
- package/dist/sandbox/base-checkpoint.d.ts +59 -0
- package/dist/sandbox/base-checkpoint.d.ts.map +1 -0
- package/dist/sandbox/base-checkpoint.js +145 -0
- package/dist/sandbox/base-checkpoint.js.map +1 -0
- package/dist/sandbox/fleet.d.ts +32 -0
- package/dist/sandbox/fleet.d.ts.map +1 -1
- package/dist/sandbox/fleet.js +39 -10
- package/dist/sandbox/fleet.js.map +1 -1
- package/dist/session/checkpoint-capture.d.ts +3 -2
- package/dist/session/checkpoint-capture.d.ts.map +1 -1
- package/dist/session/checkpoint-capture.js +3 -3
- package/dist/session/checkpoint-capture.js.map +1 -1
- package/dist/session/model-pack-hydration.d.ts +55 -0
- package/dist/session/model-pack-hydration.d.ts.map +1 -0
- package/dist/session/model-pack-hydration.js +56 -0
- package/dist/session/model-pack-hydration.js.map +1 -0
- package/dist/storage/domains/model-packs/base.d.ts +26 -0
- package/dist/storage/domains/model-packs/base.d.ts.map +1 -1
- package/dist/storage/domains/model-packs/base.js +102 -13
- package/dist/storage/domains/model-packs/base.js.map +1 -1
- package/dist/storage/domains/source-control/base.d.ts +28 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -1
- package/dist/storage/domains/source-control/base.js +70 -14
- package/dist/storage/domains/source-control/base.js.map +1 -1
- package/dist/storage/domains/source-control/inmemory.d.ts +9 -1
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/source-control/inmemory.js +8 -0
- package/dist/storage/domains/source-control/inmemory.js.map +1 -1
- package/dist/workspace.d.ts +31 -2
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +224 -53
- package/dist/workspace.js.map +1 -1
- package/package.json +6 -6
package/dist/workspace.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SandboxFilesystem } from '@mastra/code-sdk/agents/sandbox-filesystem';
|
|
1
2
|
import { getDynamicWorkspace } from '@mastra/code-sdk/agents/workspace';
|
|
2
3
|
import { LocalSandbox, Workspace } from '@mastra/core/workspace';
|
|
3
4
|
import type { MastraFactorySandboxConfig } from './factory.js';
|
|
@@ -5,6 +6,34 @@ import type { GithubIntegration } from './integrations/github/integration.js';
|
|
|
5
6
|
import type { SandboxFleet } from './sandbox/fleet.js';
|
|
6
7
|
import type { WorkItemsStorage } from './storage/domains/work-items/base.js';
|
|
7
8
|
export declare function checkpointNameForSession(sessionId: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Whether a command failure means the sandbox itself is gone (destroyed by
|
|
11
|
+
* idle GC or provider teardown) AND the command provably never started, so
|
|
12
|
+
* reviving the sandbox and replaying the command cannot run a side effect
|
|
13
|
+
* twice. Matched by error name so any provider's equivalent error classes
|
|
14
|
+
* participate without a package dependency.
|
|
15
|
+
*
|
|
16
|
+
* `SandboxExecTransportError` means both WebSocket attempts closed without an
|
|
17
|
+
* exit frame against a live sandbox. It only proves the command never started
|
|
18
|
+
* when the transport never opened (`opened: false` — the upgrade was refused
|
|
19
|
+
* outright). When the transport opened, the command may have run and mutated
|
|
20
|
+
* state before the result was lost, so replaying `git commit`, uploads, or
|
|
21
|
+
* arbitrary shell commands could execute the side effect twice; those errors
|
|
22
|
+
* surface to the caller instead.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isDeadSandboxError(error: unknown): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* The local-provider equivalent of {@link isDeadSandboxError}: a local sandbox
|
|
27
|
+
* is just a directory, so it "dies" when that directory is removed — which
|
|
28
|
+
* session retirement does while an in-flight run still holds the handle.
|
|
29
|
+
*
|
|
30
|
+
* Node surfaces a missing `cwd` as ENOENT against the binary it tried to spawn
|
|
31
|
+
* (`spawn /bin/sh ENOENT`), which is textually identical to the shell itself
|
|
32
|
+
* being absent, and is also what a genuinely missing command reports. Probing
|
|
33
|
+
* the working directory is what separates "the sandbox is gone" from "that
|
|
34
|
+
* command does not exist", so only the former triggers a rebuild.
|
|
35
|
+
*/
|
|
36
|
+
export declare function isMissingWorkdirError(error: unknown, workdir: string | undefined): boolean;
|
|
8
37
|
export declare const FACTORY_SKILLS_SOURCE_PATH: string;
|
|
9
38
|
export declare const FACTORY_SKILL_NAMES: Set<string>;
|
|
10
39
|
type DynamicWorkspaceContext = Parameters<typeof getDynamicWorkspace>[0];
|
|
@@ -30,7 +59,7 @@ export declare class FactoryWorkspaceRegistry {
|
|
|
30
59
|
register(sessionId: string, workspaceId: string, generation: number, unregister: WorkspaceUnregister): Promise<boolean>;
|
|
31
60
|
invalidateSession(sessionId: string): Promise<void>;
|
|
32
61
|
}
|
|
33
|
-
export declare function createWorkspaceFactory(options?: CreateWorkspaceFactoryOptions): ({ requestContext, mastra, skillExtension }: DynamicWorkspaceContext) => Promise<Workspace<import("@mastra/core/workspace").WorkspaceFilesystem | undefined, import("@mastra/core/workspace").WorkspaceSandbox | undefined, undefined> | Workspace<import("@mastra/core/workspace").LocalFilesystem, LocalSandbox, undefined> | undefined>;
|
|
34
|
-
export declare const getFactoryWorkspace: ({ requestContext, mastra, skillExtension }: DynamicWorkspaceContext) => Promise<Workspace<import("@mastra/core/workspace").WorkspaceFilesystem | undefined, import("@mastra/core/workspace").WorkspaceSandbox | undefined, undefined> | Workspace<import("@mastra/core/workspace").LocalFilesystem, LocalSandbox, undefined> | undefined>;
|
|
62
|
+
export declare function createWorkspaceFactory(options?: CreateWorkspaceFactoryOptions): ({ requestContext, mastra, skillExtension }: DynamicWorkspaceContext) => Promise<Workspace<import("@mastra/core/workspace").WorkspaceFilesystem | undefined, import("@mastra/core/workspace").WorkspaceSandbox | undefined, undefined> | Workspace<import("@mastra/core/workspace").LocalFilesystem, LocalSandbox, undefined> | Workspace<SandboxFilesystem, import("@mastra/core/workspace").WorkspaceSandbox, undefined> | undefined>;
|
|
63
|
+
export declare const getFactoryWorkspace: ({ requestContext, mastra, skillExtension }: DynamicWorkspaceContext) => Promise<Workspace<import("@mastra/core/workspace").WorkspaceFilesystem | undefined, import("@mastra/core/workspace").WorkspaceSandbox | undefined, undefined> | Workspace<import("@mastra/core/workspace").LocalFilesystem, LocalSandbox, undefined> | Workspace<SandboxFilesystem, import("@mastra/core/workspace").WorkspaceSandbox, undefined> | undefined>;
|
|
35
64
|
export {};
|
|
36
65
|
//# sourceMappingURL=workspace.d.ts.map
|
package/dist/workspace.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"
|
|
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,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAKxE,OAAO,EAAE,YAAY,EAAoB,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAGnF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAgB9E,OAAO,KAAK,EAAuB,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAK7E,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAElE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAO1D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAI1F;AAID,eAAO,MAAM,0BAA0B,QAUS,CAAC;AAEjD,eAAO,MAAM,mBAAmB,aAO9B,CAAC;AA6GH,KAAK,uBAAuB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;AAEzE,MAAM,WAAW,6BAA6B;IAC5C,wEAAwE;IACxE,OAAO,CAAC,EAAE,0BAA0B,CAAC;IACrC,gFAAgF;IAChF,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,0EAA0E;IAC1E,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB;;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,IA2BlE,4CAA4C,uBAAuB,oWAojBlF;AAED,eAAO,MAAM,mBAAmB,+CAtjB4B,uBAAuB,mWAsjBxB,CAAC"}
|
package/dist/workspace.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { getFactoryAuthUserFromContext, getFactoryAuthUserId } from "./auth.js";
|
|
2
2
|
import { getGithubPat } from "./integrations/github/pat.js";
|
|
3
3
|
import { registerGithubPatKind, registerGithubTokenInjector } from "./integrations/github/token-refresh.js";
|
|
4
|
-
import { DEFAULT_COMMAND_TIMEOUT_MS, MaterializeError, checkoutSessionBranch, materializeRepo, recycleClaimedWorkdir, runWorktreeSetup, runWorktreeTeardown } from "./integrations/github/sandbox.js";
|
|
4
|
+
import { DEFAULT_COMMAND_TIMEOUT_MS, MaterializeError, checkoutSessionBranch, hasExistingCheckout, materializeRepo, recycleClaimedWorkdir, runWorktreeSetup, runWorktreeTeardown } from "./integrations/github/sandbox.js";
|
|
5
|
+
import { baseCheckpointIsStale } from "./sandbox/base-checkpoint-triggers.js";
|
|
5
6
|
import { getFactorySessionAddress } from "./rules/binding-context.js";
|
|
6
7
|
import path, { dirname, join } from "path";
|
|
7
8
|
import { LocalSandbox, LocalSkillSource, Workspace } from "@mastra/core/workspace";
|
|
@@ -17,6 +18,43 @@ const SESSION_CHECKPOINT_PREFIX = "mastracode-session";
|
|
|
17
18
|
function checkpointNameForSession(sessionId) {
|
|
18
19
|
return `${SESSION_CHECKPOINT_PREFIX}-${sessionId}`;
|
|
19
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Whether a command failure means the sandbox itself is gone (destroyed by
|
|
23
|
+
* idle GC or provider teardown) AND the command provably never started, so
|
|
24
|
+
* reviving the sandbox and replaying the command cannot run a side effect
|
|
25
|
+
* twice. Matched by error name so any provider's equivalent error classes
|
|
26
|
+
* participate without a package dependency.
|
|
27
|
+
*
|
|
28
|
+
* `SandboxExecTransportError` means both WebSocket attempts closed without an
|
|
29
|
+
* exit frame against a live sandbox. It only proves the command never started
|
|
30
|
+
* when the transport never opened (`opened: false` — the upgrade was refused
|
|
31
|
+
* outright). When the transport opened, the command may have run and mutated
|
|
32
|
+
* state before the result was lost, so replaying `git commit`, uploads, or
|
|
33
|
+
* arbitrary shell commands could execute the side effect twice; those errors
|
|
34
|
+
* surface to the caller instead.
|
|
35
|
+
*/
|
|
36
|
+
function isDeadSandboxError(error) {
|
|
37
|
+
if (!(error instanceof Error)) return false;
|
|
38
|
+
if (error.name === "SandboxDestroyedError") return true;
|
|
39
|
+
if (error.name === "SandboxExecTransportError") return error.opened === false;
|
|
40
|
+
return /sandbox .*(destroyed|no longer exists|not found)/i.test(error.message);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The local-provider equivalent of {@link isDeadSandboxError}: a local sandbox
|
|
44
|
+
* is just a directory, so it "dies" when that directory is removed — which
|
|
45
|
+
* session retirement does while an in-flight run still holds the handle.
|
|
46
|
+
*
|
|
47
|
+
* Node surfaces a missing `cwd` as ENOENT against the binary it tried to spawn
|
|
48
|
+
* (`spawn /bin/sh ENOENT`), which is textually identical to the shell itself
|
|
49
|
+
* being absent, and is also what a genuinely missing command reports. Probing
|
|
50
|
+
* the working directory is what separates "the sandbox is gone" from "that
|
|
51
|
+
* command does not exist", so only the former triggers a rebuild.
|
|
52
|
+
*/
|
|
53
|
+
function isMissingWorkdirError(error, workdir) {
|
|
54
|
+
if (!workdir) return false;
|
|
55
|
+
if (error?.code !== "ENOENT") return false;
|
|
56
|
+
return !existsSync(workdir);
|
|
57
|
+
}
|
|
20
58
|
const bundleDirectory = dirname(fileURLToPath(import.meta.url));
|
|
21
59
|
const bundledFactorySkillsPath = join(bundleDirectory, "factory-skills");
|
|
22
60
|
const FACTORY_SKILLS_SOURCE_PATH = [
|
|
@@ -68,6 +106,47 @@ var FactorySkillSource = class {
|
|
|
68
106
|
return this.fallback.realpath ? this.fallback.realpath(skillPath) : Promise.resolve(skillPath);
|
|
69
107
|
}
|
|
70
108
|
};
|
|
109
|
+
function skillSourceEnoent(skillPath) {
|
|
110
|
+
const error = /* @__PURE__ */ new Error(`ENOENT: no such file or directory, '${skillPath}'`);
|
|
111
|
+
error.code = "ENOENT";
|
|
112
|
+
return error;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Sandbox-backed skill fallback that stays inert until the session sandbox is
|
|
116
|
+
* actually materialized. Skill discovery runs on latency-sensitive paths (the
|
|
117
|
+
* Factory start coordinator resolves the kickoff skill before the start route
|
|
118
|
+
* responds); without this guard the first project-root read would hit the lazy
|
|
119
|
+
* sandbox handle and force full provisioning + repo materialization. While the
|
|
120
|
+
* sandbox is unmaterialized, project skill roots simply appear empty — bundled
|
|
121
|
+
* Factory skills resolve from local disk via `FactorySkillSource`. Once the
|
|
122
|
+
* sandbox exists, every call delegates straight through.
|
|
123
|
+
*/
|
|
124
|
+
var UnmaterializedAwareSkillSource = class {
|
|
125
|
+
fallback;
|
|
126
|
+
isMaterialized;
|
|
127
|
+
constructor(fallback, isMaterialized) {
|
|
128
|
+
this.fallback = fallback;
|
|
129
|
+
this.isMaterialized = isMaterialized;
|
|
130
|
+
}
|
|
131
|
+
async exists(skillPath) {
|
|
132
|
+
return this.isMaterialized() ? this.fallback.exists(skillPath) : false;
|
|
133
|
+
}
|
|
134
|
+
async stat(skillPath) {
|
|
135
|
+
if (!this.isMaterialized()) throw skillSourceEnoent(skillPath);
|
|
136
|
+
return this.fallback.stat(skillPath);
|
|
137
|
+
}
|
|
138
|
+
async readFile(skillPath) {
|
|
139
|
+
if (!this.isMaterialized()) throw skillSourceEnoent(skillPath);
|
|
140
|
+
return this.fallback.readFile(skillPath);
|
|
141
|
+
}
|
|
142
|
+
async readdir(skillPath) {
|
|
143
|
+
return this.isMaterialized() ? this.fallback.readdir(skillPath) : [];
|
|
144
|
+
}
|
|
145
|
+
realpath(skillPath) {
|
|
146
|
+
if (!this.isMaterialized()) return Promise.resolve(skillPath);
|
|
147
|
+
return this.fallback.realpath ? this.fallback.realpath(skillPath) : Promise.resolve(skillPath);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
71
150
|
const factorySkillExtension = {
|
|
72
151
|
id: "web-factory",
|
|
73
152
|
paths: [FACTORY_SKILLS_MOUNT],
|
|
@@ -106,6 +185,8 @@ function createWorkspaceFactory(options = {}) {
|
|
|
106
185
|
const githubTokenInjectors = /* @__PURE__ */ new Map();
|
|
107
186
|
const githubTokenReconciliations = /* @__PURE__ */ new Map();
|
|
108
187
|
const inflightMaterializations = /* @__PURE__ */ new Map();
|
|
188
|
+
const materializedSandboxes = /* @__PURE__ */ new Map();
|
|
189
|
+
const constructedWorkspaces = /* @__PURE__ */ new Map();
|
|
109
190
|
return async ({ requestContext, mastra, skillExtension }) => {
|
|
110
191
|
const effectiveSkillExtension = skillExtension ?? factorySkillExtension;
|
|
111
192
|
const ctx = requestContext.get("controller");
|
|
@@ -129,14 +210,13 @@ function createWorkspaceFactory(options = {}) {
|
|
|
129
210
|
id: session.projectRepositoryId
|
|
130
211
|
});
|
|
131
212
|
if (!projectRepository) throw new Error(`Repository link ${session.projectRepositoryId} was not found`);
|
|
132
|
-
const connection = await storage.connections.get({
|
|
213
|
+
const [connection, repository] = await Promise.all([storage.connections.get({
|
|
133
214
|
orgId: session.orgId,
|
|
134
215
|
id: projectRepository.connectionId
|
|
135
|
-
})
|
|
136
|
-
const repository = await storage.repositories.get({
|
|
216
|
+
}), storage.repositories.get({
|
|
137
217
|
orgId: session.orgId,
|
|
138
218
|
id: projectRepository.repositoryId
|
|
139
|
-
});
|
|
219
|
+
})]);
|
|
140
220
|
if (!connection || !repository) throw new Error(`Repository link ${session.projectRepositoryId} is incomplete`);
|
|
141
221
|
if (!await storage.installations.get({
|
|
142
222
|
orgId: session.orgId,
|
|
@@ -144,15 +224,20 @@ function createWorkspaceFactory(options = {}) {
|
|
|
144
224
|
})) throw new Error(`GitHub installation ${connection.installationId} was not found`);
|
|
145
225
|
const repoFullName = repository.slug;
|
|
146
226
|
let workdir = isLocalSandbox ? fleet.computeLocalSessionWorkdir(repoFullName, session.id) : session.sandboxWorkdir ?? projectRepository.sandboxWorkdir;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
227
|
+
let stateSeeded = false;
|
|
228
|
+
if (ctx && workdir && ctx.getState()?.projectPath !== workdir) {
|
|
229
|
+
await ctx.setState({
|
|
230
|
+
projectPath: workdir,
|
|
231
|
+
projectName: repoFullName
|
|
232
|
+
});
|
|
233
|
+
stateSeeded = true;
|
|
234
|
+
}
|
|
151
235
|
const binding = {
|
|
152
236
|
get sandboxId() {
|
|
153
237
|
return session.sandboxId;
|
|
154
238
|
},
|
|
155
239
|
checkpointName: checkpointNameForSession(session.id),
|
|
240
|
+
...!session.materializedAt && projectRepository.baseCheckpoint && !baseCheckpointIsStale(projectRepository) ? { seedCheckpointName: projectRepository.baseCheckpoint.name } : {},
|
|
156
241
|
setSandboxId: async (id) => {
|
|
157
242
|
await storage.sessions.setSandbox({
|
|
158
243
|
id: session.id,
|
|
@@ -246,7 +331,11 @@ function createWorkspaceFactory(options = {}) {
|
|
|
246
331
|
await workspace.destroy();
|
|
247
332
|
evicted = true;
|
|
248
333
|
} catch {}
|
|
249
|
-
if (evicted && githubTokenInjectors.get(workspaceId) === registered)
|
|
334
|
+
if (evicted && githubTokenInjectors.get(workspaceId) === registered) {
|
|
335
|
+
githubTokenInjectors.delete(workspaceId);
|
|
336
|
+
materializedSandboxes.delete(workspaceId);
|
|
337
|
+
constructedWorkspaces.delete(workspaceId);
|
|
338
|
+
}
|
|
250
339
|
}
|
|
251
340
|
throw error;
|
|
252
341
|
}
|
|
@@ -256,12 +345,17 @@ function createWorkspaceFactory(options = {}) {
|
|
|
256
345
|
let existing;
|
|
257
346
|
try {
|
|
258
347
|
existing = mastra?.getWorkspaceById(workspaceId);
|
|
259
|
-
existing?.setToolsConfig(MASTRACODE_WORKSPACE_TOOLS);
|
|
260
348
|
} catch {
|
|
261
349
|
existing = void 0;
|
|
262
350
|
}
|
|
263
|
-
|
|
264
|
-
|
|
351
|
+
existing ??= constructedWorkspaces.get(workspaceId);
|
|
352
|
+
if (existing) {
|
|
353
|
+
existing.setToolsConfig(MASTRACODE_WORKSPACE_TOOLS);
|
|
354
|
+
return reconcileRegisteredWorkspace(existing);
|
|
355
|
+
}
|
|
356
|
+
const retiredError = () => /* @__PURE__ */ new Error(`Factory session ${session.sessionId} was retired during workspace materialization`);
|
|
357
|
+
const materializeSandbox = async () => {
|
|
358
|
+
if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) throw retiredError();
|
|
265
359
|
let claimedPooledSandbox = false;
|
|
266
360
|
if (!isLocalSandbox && !session.sandboxId) {
|
|
267
361
|
const pooled = await storage.sandboxPool.claim({ projectRepositoryId: session.projectRepositoryId });
|
|
@@ -284,7 +378,7 @@ function createWorkspaceFactory(options = {}) {
|
|
|
284
378
|
...isLocalSandbox ? { workingDirectory: workdir } : {},
|
|
285
379
|
actingUserId: userId
|
|
286
380
|
});
|
|
287
|
-
const runMaterialize = (target) => materializeRepo({
|
|
381
|
+
const runMaterialize = (target, skipPull) => materializeRepo({
|
|
288
382
|
row: {
|
|
289
383
|
id: session.id,
|
|
290
384
|
sandboxWorkdir: workdir,
|
|
@@ -296,19 +390,21 @@ function createWorkspaceFactory(options = {}) {
|
|
|
296
390
|
},
|
|
297
391
|
sandbox: target,
|
|
298
392
|
token,
|
|
299
|
-
storage: storage.sessions
|
|
393
|
+
storage: storage.sessions,
|
|
394
|
+
skipPullOnExistingCheckout: skipPull
|
|
300
395
|
});
|
|
301
396
|
const isGitMissing = (error) => error instanceof MaterializeError && error.code === "git-missing";
|
|
302
397
|
let sandbox = await ensureSandbox();
|
|
303
398
|
if (claimedPooledSandbox) await recycleClaimedWorkdir(sandbox, workdir, repository.defaultBranch);
|
|
399
|
+
const seededFromBaseCheckpoint = !!binding.seedCheckpointName && !session.materializedAt && !claimedPooledSandbox && await hasExistingCheckout(sandbox, workdir, repoFullName);
|
|
304
400
|
try {
|
|
305
|
-
await runMaterialize(sandbox);
|
|
401
|
+
await runMaterialize(sandbox, seededFromBaseCheckpoint);
|
|
306
402
|
} catch (error) {
|
|
307
403
|
if (!isGitMissing(error)) throw error;
|
|
308
404
|
await fleet.teardownSandbox(binding, sandbox);
|
|
309
405
|
sandbox = await ensureSandbox();
|
|
310
406
|
try {
|
|
311
|
-
await runMaterialize(sandbox);
|
|
407
|
+
await runMaterialize(sandbox, false);
|
|
312
408
|
} catch (retryError) {
|
|
313
409
|
if (isGitMissing(retryError)) await fleet.teardownSandbox(binding, sandbox);
|
|
314
410
|
throw retryError;
|
|
@@ -320,7 +416,7 @@ function createWorkspaceFactory(options = {}) {
|
|
|
320
416
|
token,
|
|
321
417
|
repoFullName
|
|
322
418
|
});
|
|
323
|
-
if (projectRepository.setupCommand) try {
|
|
419
|
+
if (projectRepository.setupCommand && !seededFromBaseCheckpoint) try {
|
|
324
420
|
await runWorktreeSetup(sandbox, workdir, projectRepository.setupCommand);
|
|
325
421
|
} catch (setupError) {
|
|
326
422
|
if (projectRepository.teardownCommand) try {
|
|
@@ -346,47 +442,122 @@ function createWorkspaceFactory(options = {}) {
|
|
|
346
442
|
generation: 0,
|
|
347
443
|
tokenReplacementPending: false
|
|
348
444
|
};
|
|
445
|
+
if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) {
|
|
446
|
+
try {
|
|
447
|
+
await fleet.teardownSandbox(binding, sandbox);
|
|
448
|
+
} catch (teardownError) {
|
|
449
|
+
console.warn("[Mastra Factory] Sandbox teardown after mid-materialization retirement failed", {
|
|
450
|
+
orgId: session.orgId,
|
|
451
|
+
sessionId: session.sessionId,
|
|
452
|
+
error: teardownError instanceof Error ? teardownError.message.slice(-2e3) : String(teardownError)
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
throw retiredError();
|
|
456
|
+
}
|
|
349
457
|
githubTokenInjectors.set(workspaceId, tokenRegistration);
|
|
350
458
|
registerGithubTokenContext(tokenRegistration);
|
|
351
|
-
|
|
352
|
-
sandbox,
|
|
353
|
-
workdir
|
|
354
|
-
});
|
|
355
|
-
const projectSkillPaths = [
|
|
356
|
-
path.join(configDir, "skills"),
|
|
357
|
-
".claude/skills",
|
|
358
|
-
".agents/skills"
|
|
359
|
-
];
|
|
360
|
-
const skillPaths = [...effectiveSkillExtension?.paths ?? [], ...projectSkillPaths];
|
|
361
|
-
const workspace = new Workspace({
|
|
362
|
-
id: workspaceId,
|
|
363
|
-
name: "Mastra Code Factory Session Workspace",
|
|
364
|
-
filesystem,
|
|
365
|
-
sandbox,
|
|
366
|
-
tools: MASTRACODE_WORKSPACE_TOOLS,
|
|
367
|
-
skills: skillPaths,
|
|
368
|
-
skillSource: effectiveSkillExtension?.createSource(filesystem, projectSkillPaths) ?? filesystem
|
|
369
|
-
});
|
|
370
|
-
mastra?.addWorkspace(workspace, workspaceId, { source: "mastra" });
|
|
371
|
-
if (!await workspaceRegistry.register(session.sessionId, workspaceId, workspaceGeneration, async () => {
|
|
372
|
-
githubTokenInjectors.delete(workspaceId);
|
|
373
|
-
await mastra?.removeWorkspace?.(workspaceId);
|
|
374
|
-
})) throw new Error(`Factory session ${session.sessionId} was retired during workspace materialization`);
|
|
375
|
-
return workspace;
|
|
459
|
+
return sandbox;
|
|
376
460
|
};
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
461
|
+
const ensureMaterialized = async () => {
|
|
462
|
+
const ready = materializedSandboxes.get(workspaceId);
|
|
463
|
+
if (ready) return ready;
|
|
464
|
+
let inflight = inflightMaterializations.get(workspaceId);
|
|
465
|
+
if (!inflight) {
|
|
466
|
+
inflight = materializeSandbox();
|
|
467
|
+
inflightMaterializations.set(workspaceId, inflight);
|
|
468
|
+
inflight.then((sb) => {
|
|
469
|
+
materializedSandboxes.set(workspaceId, sb);
|
|
470
|
+
workspace.skills?.refresh().catch(() => {});
|
|
471
|
+
}, () => {});
|
|
472
|
+
}
|
|
473
|
+
try {
|
|
474
|
+
return await inflight;
|
|
475
|
+
} finally {
|
|
476
|
+
if (inflightMaterializations.get(workspaceId) === inflight) inflightMaterializations.delete(workspaceId);
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
const lazySandbox = {
|
|
480
|
+
get id() {
|
|
481
|
+
return materializedSandboxes.get(workspaceId)?.id ?? workspaceId;
|
|
482
|
+
},
|
|
483
|
+
name: "Factory Lazy Sandbox",
|
|
484
|
+
get provider() {
|
|
485
|
+
return fleet.provider;
|
|
486
|
+
},
|
|
487
|
+
get status() {
|
|
488
|
+
return materializedSandboxes.has(workspaceId) ? "ready" : "pending";
|
|
489
|
+
},
|
|
490
|
+
get supportsCheckpoints() {
|
|
491
|
+
return materializedSandboxes.get(workspaceId)?.supportsCheckpoints ?? false;
|
|
492
|
+
},
|
|
493
|
+
getInstructions() {
|
|
494
|
+
return materializedSandboxes.get(workspaceId)?.getInstructions?.() ?? fleet.getInstructions();
|
|
495
|
+
},
|
|
496
|
+
clone() {
|
|
497
|
+
throw new Error("The Factory session sandbox cannot be cloned from a lazy handle.");
|
|
498
|
+
},
|
|
499
|
+
async start() {},
|
|
500
|
+
async getInfo() {
|
|
501
|
+
return (await ensureMaterialized()).getInfo();
|
|
502
|
+
},
|
|
503
|
+
async executeCommand(command, args, options) {
|
|
504
|
+
const sandbox = await ensureMaterialized();
|
|
505
|
+
try {
|
|
506
|
+
return await sandbox.executeCommand(command, args, options);
|
|
507
|
+
} catch (error) {
|
|
508
|
+
if (!isDeadSandboxError(error) && !(isLocalSandbox && isMissingWorkdirError(error, workdir))) throw error;
|
|
509
|
+
if (materializedSandboxes.get(workspaceId) === sandbox) materializedSandboxes.delete(workspaceId);
|
|
510
|
+
return (await ensureMaterialized()).executeCommand(command, args, options);
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
setEnvironmentVariable(name, value) {
|
|
514
|
+
const sandbox = materializedSandboxes.get(workspaceId);
|
|
515
|
+
if (!sandbox?.setEnvironmentVariable) throw new Error("The Factory session sandbox is not materialized yet.");
|
|
516
|
+
sandbox.setEnvironmentVariable(name, value);
|
|
517
|
+
},
|
|
518
|
+
async snapshot() {
|
|
519
|
+
await materializedSandboxes.get(workspaceId)?.snapshot?.();
|
|
520
|
+
},
|
|
521
|
+
async stop() {
|
|
522
|
+
await materializedSandboxes.get(workspaceId)?.stop?.();
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
const filesystem = new SandboxFilesystem({
|
|
526
|
+
id: `sandbox-fs:${workspaceId}:${workdir}`,
|
|
527
|
+
sandbox: lazySandbox,
|
|
528
|
+
workdir
|
|
529
|
+
});
|
|
530
|
+
const projectSkillPaths = [
|
|
531
|
+
path.join(configDir, "skills"),
|
|
532
|
+
".claude/skills",
|
|
533
|
+
".agents/skills"
|
|
534
|
+
];
|
|
535
|
+
const guardedSkillFallback = new UnmaterializedAwareSkillSource(filesystem, () => materializedSandboxes.has(workspaceId));
|
|
536
|
+
const workspace = new Workspace({
|
|
537
|
+
id: workspaceId,
|
|
538
|
+
name: "Mastra Code Factory Session Workspace",
|
|
539
|
+
filesystem,
|
|
540
|
+
sandbox: lazySandbox,
|
|
541
|
+
tools: MASTRACODE_WORKSPACE_TOOLS,
|
|
542
|
+
skills: [...effectiveSkillExtension?.paths ?? [], ...projectSkillPaths],
|
|
543
|
+
skillSource: effectiveSkillExtension?.createSource(guardedSkillFallback, projectSkillPaths) ?? guardedSkillFallback
|
|
544
|
+
});
|
|
545
|
+
mastra?.addWorkspace(workspace, workspaceId, { source: "mastra" });
|
|
546
|
+
constructedWorkspaces.set(workspaceId, workspace);
|
|
547
|
+
if (!await workspaceRegistry.register(session.sessionId, workspaceId, workspaceGeneration, async () => {
|
|
548
|
+
githubTokenInjectors.delete(workspaceId);
|
|
549
|
+
materializedSandboxes.delete(workspaceId);
|
|
550
|
+
constructedWorkspaces.delete(workspaceId);
|
|
551
|
+
await mastra?.removeWorkspace?.(workspaceId);
|
|
552
|
+
})) throw new Error(`Factory session ${session.sessionId} was retired during workspace materialization`);
|
|
553
|
+
if (stateSeeded) ensureMaterialized().catch((error) => {
|
|
554
|
+
console.error(`[factory:workspace] background materialization for ${workspaceId} failed`, error);
|
|
555
|
+
});
|
|
556
|
+
return workspace;
|
|
386
557
|
};
|
|
387
558
|
}
|
|
388
559
|
const getFactoryWorkspace = createWorkspaceFactory();
|
|
389
560
|
//#endregion
|
|
390
|
-
export { FACTORY_SKILLS_SOURCE_PATH, FACTORY_SKILL_NAMES, FactoryWorkspaceRegistry, checkpointNameForSession, createWorkspaceFactory, getFactoryWorkspace };
|
|
561
|
+
export { FACTORY_SKILLS_SOURCE_PATH, FACTORY_SKILL_NAMES, FactoryWorkspaceRegistry, checkpointNameForSession, createWorkspaceFactory, getFactoryWorkspace, isDeadSandboxError, isMissingWorkdirError };
|
|
391
562
|
|
|
392
563
|
//# sourceMappingURL=workspace.js.map
|