@mastra/factory 0.11.1 → 0.12.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/dist/factory.d.ts +2 -22
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +15 -58
- package/dist/factory.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/integrations/base.d.ts +5 -13
- package/dist/integrations/base.d.ts.map +1 -1
- package/dist/integrations/github/config.d.ts +4 -4
- package/dist/integrations/github/config.d.ts.map +1 -1
- package/dist/integrations/github/config.js +3 -3
- package/dist/integrations/github/config.js.map +1 -1
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +2 -4
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/reconcile-worker.d.ts +0 -5
- package/dist/integrations/github/reconcile-worker.d.ts.map +1 -1
- package/dist/integrations/github/reconcile-worker.js +0 -7
- package/dist/integrations/github/reconcile-worker.js.map +1 -1
- package/dist/integrations/github/routes.d.ts +6 -5
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +36 -263
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/sandbox-release.d.ts +17 -47
- package/dist/integrations/github/sandbox-release.d.ts.map +1 -1
- package/dist/integrations/github/sandbox-release.js +24 -103
- package/dist/integrations/github/sandbox-release.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +25 -139
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +24 -212
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/github/webhook.d.ts.map +1 -1
- package/dist/integrations/github/webhook.js +1 -2
- package/dist/integrations/github/webhook.js.map +1 -1
- package/dist/integrations/platform/github/event-worker.d.ts +0 -2
- package/dist/integrations/platform/github/event-worker.d.ts.map +1 -1
- package/dist/integrations/platform/github/event-worker.js +0 -7
- package/dist/integrations/platform/github/event-worker.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +5 -7
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +3 -1
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/fs.d.ts +4 -6
- package/dist/routes/fs.d.ts.map +1 -1
- package/dist/routes/fs.js +28 -34
- package/dist/routes/fs.js.map +1 -1
- package/dist/routes/skills.d.ts.map +1 -1
- package/dist/routes/skills.js +6 -3
- package/dist/routes/skills.js.map +1 -1
- package/dist/routes/surface.d.ts +4 -9
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +3 -5
- package/dist/routes/surface.js.map +1 -1
- package/dist/sandbox/materialization.d.ts +36 -0
- package/dist/sandbox/materialization.d.ts.map +1 -0
- package/dist/sandbox/materialization.js +18 -0
- package/dist/sandbox/materialization.js.map +1 -0
- package/dist/sandbox/session-retirement.d.ts +8 -6
- package/dist/sandbox/session-retirement.d.ts.map +1 -1
- package/dist/sandbox/session-retirement.js +28 -123
- package/dist/sandbox/session-retirement.js.map +1 -1
- package/dist/sandbox/session-sandbox.d.ts +117 -0
- package/dist/sandbox/session-sandbox.d.ts.map +1 -0
- package/dist/sandbox/session-sandbox.js +132 -0
- package/dist/sandbox/session-sandbox.js.map +1 -0
- package/dist/sandbox/workdir.d.ts +30 -0
- package/dist/sandbox/workdir.d.ts.map +1 -0
- package/dist/sandbox/workdir.js +48 -0
- package/dist/sandbox/workdir.js.map +1 -0
- package/dist/session/filesystem-capture.d.ts +2 -1
- package/dist/session/filesystem-capture.d.ts.map +1 -1
- package/dist/session/filesystem-capture.js +45 -4
- package/dist/session/filesystem-capture.js.map +1 -1
- package/dist/session/first-exec-capture.d.ts +1 -0
- package/dist/session/first-exec-capture.d.ts.map +1 -1
- package/dist/session/first-exec-capture.js +1 -1
- package/dist/session/first-exec-capture.js.map +1 -1
- package/dist/storage/domains/integrations/base.d.ts +1 -1
- package/dist/storage/domains/integrations/base.js +1 -1
- package/dist/storage/domains/integrations/base.js.map +1 -1
- package/dist/storage/domains/source-control/base.d.ts +0 -144
- package/dist/storage/domains/source-control/base.d.ts.map +1 -1
- package/dist/storage/domains/source-control/base.js +3 -290
- package/dist/storage/domains/source-control/base.js.map +1 -1
- package/dist/storage/domains/source-control/inmemory.d.ts +1 -63
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/source-control/inmemory.js +0 -89
- package/dist/storage/domains/source-control/inmemory.js.map +1 -1
- package/dist/workspace.d.ts +5 -37
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +107 -245
- package/dist/workspace.js.map +1 -1
- package/package.json +4 -4
- package/dist/sandbox/base-checkpoint-triggers.d.ts +0 -77
- package/dist/sandbox/base-checkpoint-triggers.d.ts.map +0 -1
- package/dist/sandbox/base-checkpoint-triggers.js +0 -137
- package/dist/sandbox/base-checkpoint-triggers.js.map +0 -1
- package/dist/sandbox/base-checkpoint.d.ts +0 -59
- package/dist/sandbox/base-checkpoint.d.ts.map +0 -1
- package/dist/sandbox/base-checkpoint.js +0 -145
- package/dist/sandbox/base-checkpoint.js.map +0 -1
- package/dist/sandbox/fleet.d.ts +0 -258
- package/dist/sandbox/fleet.d.ts.map +0 -1
- package/dist/sandbox/fleet.js +0 -356
- package/dist/sandbox/fleet.js.map +0 -1
- package/dist/sandbox/reattach.d.ts +0 -3
- package/dist/sandbox/reattach.d.ts.map +0 -1
- package/dist/sandbox/reattach.js +0 -16
- package/dist/sandbox/reattach.js.map +0 -1
package/dist/workspace.js
CHANGED
|
@@ -1,60 +1,19 @@
|
|
|
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 {
|
|
5
|
-
import {
|
|
4
|
+
import { requireExec } from "./sandbox/materialization.js";
|
|
5
|
+
import { createSessionSetupHook, evictSessionSandbox, getSessionSandbox, hasFailedSetupCommand, recordFailedSetupCommand, resolveSessionWorkdir } from "./sandbox/session-sandbox.js";
|
|
6
|
+
import { DEFAULT_COMMAND_TIMEOUT_MS, SetupCommandError, checkoutSessionBranch, materializeRepo, runSetupCommand, runTeardownCommand } from "./integrations/github/sandbox.js";
|
|
6
7
|
import { getFactorySessionAddress } from "./rules/binding-context.js";
|
|
7
8
|
import path, { dirname, join } from "path";
|
|
8
|
-
import {
|
|
9
|
+
import { LocalSkillSource, Workspace } from "@mastra/core/workspace";
|
|
9
10
|
import { DEFAULT_CONFIG_DIR } from "@mastra/code-sdk/constants";
|
|
10
11
|
import { SandboxFilesystem } from "@mastra/code-sdk/agents/sandbox-filesystem";
|
|
11
12
|
import { existsSync } from "fs";
|
|
12
13
|
import { fileURLToPath } from "url";
|
|
13
14
|
import { MASTRACODE_WORKSPACE_TOOLS } from "@mastra/code-sdk/agents/tool-availability";
|
|
14
|
-
import { getDynamicWorkspace } from "@mastra/code-sdk/agents/workspace";
|
|
15
15
|
//#region src/workspace.ts
|
|
16
16
|
const WORKSPACE_ID_PREFIX = "mfw";
|
|
17
|
-
const SESSION_CHECKPOINT_PREFIX = "mastracode-session";
|
|
18
|
-
function checkpointNameForSession(sessionId) {
|
|
19
|
-
return `${SESSION_CHECKPOINT_PREFIX}-${sessionId}`;
|
|
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
|
-
}
|
|
58
17
|
const bundleDirectory = dirname(fileURLToPath(import.meta.url));
|
|
59
18
|
const bundledFactorySkillsPath = join(bundleDirectory, "factory-skills");
|
|
60
19
|
const FACTORY_SKILLS_SOURCE_PATH = [
|
|
@@ -179,31 +138,22 @@ var FactoryWorkspaceRegistry = class {
|
|
|
179
138
|
}
|
|
180
139
|
};
|
|
181
140
|
function createWorkspaceFactory(options = {}) {
|
|
182
|
-
const { sandbox: sandboxConfig, github,
|
|
141
|
+
const { sandbox: sandboxConfig, github, workItems } = options;
|
|
183
142
|
const workspaceRegistry = options.workspaceRegistry ?? new FactoryWorkspaceRegistry();
|
|
184
|
-
const isLocalSandbox = sandboxConfig?.machine instanceof LocalSandbox;
|
|
185
143
|
const githubTokenInjectors = /* @__PURE__ */ new Map();
|
|
186
144
|
const githubTokenReconciliations = /* @__PURE__ */ new Map();
|
|
187
|
-
const inflightMaterializations = /* @__PURE__ */ new Map();
|
|
188
|
-
const materializedSandboxes = /* @__PURE__ */ new Map();
|
|
189
145
|
const constructedWorkspaces = /* @__PURE__ */ new Map();
|
|
190
146
|
return async ({ requestContext, mastra, skillExtension }) => {
|
|
191
147
|
const effectiveSkillExtension = skillExtension ?? factorySkillExtension;
|
|
192
148
|
const ctx = requestContext.get("controller");
|
|
193
149
|
const session = ctx?.resourceId && github ? await github.sourceControlStorage.sessions.getBySessionId(ctx.resourceId) : null;
|
|
194
|
-
if (!session)
|
|
195
|
-
if (sandboxConfig && !isLocalSandbox) return;
|
|
196
|
-
return getDynamicWorkspace({
|
|
197
|
-
requestContext,
|
|
198
|
-
mastra,
|
|
199
|
-
skillExtension: effectiveSkillExtension
|
|
200
|
-
});
|
|
201
|
-
}
|
|
150
|
+
if (!session) return;
|
|
202
151
|
const user = getFactoryAuthUserFromContext(requestContext);
|
|
203
152
|
const userId = getFactoryAuthUserId(user);
|
|
204
153
|
if (!user?.organizationId || !userId) throw new Error(`Factory session ${session.sessionId} was resolved without a caller identity`);
|
|
205
154
|
if (user.organizationId !== session.orgId || session.visibility === "private" && userId !== session.userId) throw new Error(`Factory session ${session.sessionId} is not available to the current user`);
|
|
206
|
-
if (!sandboxConfig || !github
|
|
155
|
+
if (!sandboxConfig || !github) throw new Error("GitHub and a sandbox callback are required to create a Factory session workspace");
|
|
156
|
+
const createSessionSandboxInstance = sandboxConfig;
|
|
207
157
|
const storage = github.sourceControlStorage;
|
|
208
158
|
const projectRepository = await storage.projectRepositories.get({
|
|
209
159
|
orgId: session.orgId,
|
|
@@ -223,43 +173,69 @@ function createWorkspaceFactory(options = {}) {
|
|
|
223
173
|
id: connection.installationId
|
|
224
174
|
})) throw new Error(`GitHub installation ${connection.installationId} was not found`);
|
|
225
175
|
const repoFullName = repository.slug;
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
176
|
+
const runSetupOn = (target, workdir) => runSessionSetup(requireExec(target), workdir);
|
|
177
|
+
const guardedSetup = createSessionSetupHook(runSetupOn, session.id, repoFullName);
|
|
178
|
+
const setupHook = async (args) => {
|
|
179
|
+
if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) throw retiredError();
|
|
180
|
+
await guardedSetup(args);
|
|
181
|
+
if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) throw retiredError();
|
|
182
|
+
const target = requireExec(args.sandbox);
|
|
183
|
+
const patKind = await resolveGithubPatKind("default");
|
|
184
|
+
const ghCliToken = await getGithubPat(() => github.integrationStorage, session.orgId, patKind) ?? await getRepositoryToken();
|
|
185
|
+
target.setEnv?.((env) => ({
|
|
186
|
+
...env,
|
|
187
|
+
GH_TOKEN: ghCliToken
|
|
188
|
+
}));
|
|
189
|
+
storage.sessions.setSandbox({
|
|
190
|
+
id: session.id,
|
|
191
|
+
sandboxId: target.id,
|
|
192
|
+
sandboxWorkdir: sessionEntry.workdir ?? ""
|
|
193
|
+
}).catch(() => {});
|
|
194
|
+
const tokenRegistration = {
|
|
195
|
+
inject: (freshToken) => {
|
|
196
|
+
if (!target.setEnv) throw new Error("The active sandbox provider does not support runtime GitHub token refresh.");
|
|
197
|
+
target.setEnv((env) => ({
|
|
198
|
+
...env,
|
|
199
|
+
GH_TOKEN: freshToken
|
|
200
|
+
}));
|
|
201
|
+
tokenRegistration.ghToken = freshToken;
|
|
202
|
+
},
|
|
203
|
+
patKind,
|
|
204
|
+
ghToken: ghCliToken,
|
|
205
|
+
generation: 0,
|
|
206
|
+
tokenReplacementPending: false
|
|
207
|
+
};
|
|
208
|
+
githubTokenInjectors.set(workspaceId, tokenRegistration);
|
|
209
|
+
registerGithubTokenContext(tokenRegistration);
|
|
210
|
+
constructedWorkspaces.get(workspaceId)?.skills?.refresh().catch(() => {});
|
|
258
211
|
};
|
|
212
|
+
const constructSessionEntry = () => getSessionSandbox(session.id, repoFullName, () => {
|
|
213
|
+
const sandbox = createSessionSandboxInstance({
|
|
214
|
+
sessionId: session.id,
|
|
215
|
+
repoFullName,
|
|
216
|
+
setupCommand: projectRepository.setupCommand ?? void 0,
|
|
217
|
+
getRepositoryAccess: () => github.versionControl.getRepositoryAccess({
|
|
218
|
+
orgId: session.orgId,
|
|
219
|
+
repositoryId: repository.id
|
|
220
|
+
})
|
|
221
|
+
});
|
|
222
|
+
sandbox.setOnStart((previous) => async (args) => {
|
|
223
|
+
await setupHook(args);
|
|
224
|
+
await previous?.(args);
|
|
225
|
+
});
|
|
226
|
+
return sandbox;
|
|
227
|
+
});
|
|
228
|
+
const sessionEntry = constructSessionEntry();
|
|
229
|
+
const workdir = sessionEntry.workdir;
|
|
230
|
+
sessionEntry.sandbox.provider;
|
|
231
|
+
if (ctx && workdir && ctx.getState()?.projectPath !== workdir) await ctx.setState({
|
|
232
|
+
projectPath: workdir,
|
|
233
|
+
projectName: repoFullName
|
|
234
|
+
});
|
|
259
235
|
const extensionId = effectiveSkillExtension ? `-${effectiveSkillExtension.id}` : "";
|
|
260
236
|
const workspaceId = `${WORKSPACE_ID_PREFIX}-${projectRepository.id}-${session.id}${extensionId}`;
|
|
261
237
|
const workspaceGeneration = workspaceRegistry.generation(session.sessionId);
|
|
262
|
-
const configDir =
|
|
238
|
+
const configDir = DEFAULT_CONFIG_DIR;
|
|
263
239
|
const getRepositoryToken = async () => {
|
|
264
240
|
const token = (await github.versionControl.getRepositoryAccess({
|
|
265
241
|
orgId: session.orgId,
|
|
@@ -333,7 +309,6 @@ function createWorkspaceFactory(options = {}) {
|
|
|
333
309
|
} catch {}
|
|
334
310
|
if (evicted && githubTokenInjectors.get(workspaceId) === registered) {
|
|
335
311
|
githubTokenInjectors.delete(workspaceId);
|
|
336
|
-
materializedSandboxes.delete(workspaceId);
|
|
337
312
|
constructedWorkspaces.delete(workspaceId);
|
|
338
313
|
}
|
|
339
314
|
}
|
|
@@ -354,31 +329,15 @@ function createWorkspaceFactory(options = {}) {
|
|
|
354
329
|
return reconcileRegisteredWorkspace(existing);
|
|
355
330
|
}
|
|
356
331
|
const retiredError = () => /* @__PURE__ */ new Error(`Factory session ${session.sessionId} was retired during workspace materialization`);
|
|
357
|
-
const
|
|
358
|
-
if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) throw retiredError();
|
|
359
|
-
let claimedPooledSandbox = false;
|
|
360
|
-
if (!isLocalSandbox && !session.sandboxId) {
|
|
361
|
-
const pooled = await storage.sandboxPool.claim({ projectRepositoryId: session.projectRepositoryId });
|
|
362
|
-
if (pooled) {
|
|
363
|
-
await storage.sessions.setSandbox({
|
|
364
|
-
id: session.id,
|
|
365
|
-
sandboxId: pooled.sandboxId,
|
|
366
|
-
sandboxWorkdir: pooled.sandboxWorkdir
|
|
367
|
-
});
|
|
368
|
-
session.sandboxId = pooled.sandboxId;
|
|
369
|
-
session.sandboxWorkdir = pooled.sandboxWorkdir;
|
|
370
|
-
workdir = pooled.sandboxWorkdir;
|
|
371
|
-
claimedPooledSandbox = true;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
332
|
+
const runSessionSetup = async (target, workdir) => {
|
|
374
333
|
const token = await getRepositoryToken();
|
|
375
|
-
const
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
...
|
|
379
|
-
|
|
380
|
-
});
|
|
381
|
-
|
|
334
|
+
const setupPatKind = await resolveGithubPatKind("default");
|
|
335
|
+
const setupGhToken = await getGithubPat(() => github.integrationStorage, session.orgId, setupPatKind) ?? token;
|
|
336
|
+
target.setEnv?.((env) => ({
|
|
337
|
+
...env,
|
|
338
|
+
GH_TOKEN: setupGhToken
|
|
339
|
+
}));
|
|
340
|
+
await materializeRepo({
|
|
382
341
|
row: {
|
|
383
342
|
id: session.id,
|
|
384
343
|
sandboxWorkdir: workdir,
|
|
@@ -390,154 +349,61 @@ function createWorkspaceFactory(options = {}) {
|
|
|
390
349
|
},
|
|
391
350
|
sandbox: target,
|
|
392
351
|
token,
|
|
393
|
-
storage: storage.sessions
|
|
394
|
-
skipPullOnExistingCheckout: skipPull
|
|
352
|
+
storage: storage.sessions
|
|
395
353
|
});
|
|
396
|
-
|
|
397
|
-
let sandbox = await ensureSandbox();
|
|
398
|
-
if (claimedPooledSandbox) await recycleClaimedWorkdir(sandbox, workdir, repository.defaultBranch);
|
|
399
|
-
const seededFromBaseCheckpoint = !!binding.seedCheckpointName && !session.materializedAt && !claimedPooledSandbox && await hasExistingCheckout(sandbox, workdir, repoFullName);
|
|
400
|
-
try {
|
|
401
|
-
await runMaterialize(sandbox, seededFromBaseCheckpoint);
|
|
402
|
-
} catch (error) {
|
|
403
|
-
if (!isGitMissing(error)) throw error;
|
|
404
|
-
await fleet.teardownSandbox(binding, sandbox);
|
|
405
|
-
sandbox = await ensureSandbox();
|
|
406
|
-
try {
|
|
407
|
-
await runMaterialize(sandbox, false);
|
|
408
|
-
} catch (retryError) {
|
|
409
|
-
if (isGitMissing(retryError)) await fleet.teardownSandbox(binding, sandbox);
|
|
410
|
-
throw retryError;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
await checkoutSessionBranch(sandbox, workdir, {
|
|
354
|
+
await checkoutSessionBranch(target, workdir, {
|
|
414
355
|
branch: session.branch,
|
|
415
356
|
baseBranch: session.baseBranch || projectRepository.branch || repository.defaultBranch,
|
|
416
357
|
token,
|
|
417
358
|
repoFullName
|
|
418
359
|
});
|
|
419
|
-
if (projectRepository.setupCommand
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
if (projectRepository.teardownCommand) try {
|
|
423
|
-
await runWorktreeTeardown(sandbox, workdir, projectRepository.teardownCommand, { timeoutMs: DEFAULT_COMMAND_TIMEOUT_MS });
|
|
424
|
-
} catch (teardownError) {
|
|
425
|
-
console.warn("[Mastra Factory] Worktree teardown after setup failure failed", {
|
|
426
|
-
orgId: session.orgId,
|
|
427
|
-
sessionId: session.sessionId,
|
|
428
|
-
projectRepositoryId: session.projectRepositoryId,
|
|
429
|
-
error: teardownError instanceof Error ? teardownError.message.slice(-2e3) : String(teardownError)
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
throw setupError;
|
|
433
|
-
}
|
|
434
|
-
const tokenRegistration = {
|
|
435
|
-
inject: (freshToken) => {
|
|
436
|
-
if (!sandbox.setEnvironmentVariable) throw new Error("The active sandbox provider does not support runtime GitHub token refresh.");
|
|
437
|
-
sandbox.setEnvironmentVariable("GH_TOKEN", freshToken);
|
|
438
|
-
tokenRegistration.ghToken = freshToken;
|
|
439
|
-
},
|
|
440
|
-
patKind,
|
|
441
|
-
ghToken: ghCliToken,
|
|
442
|
-
generation: 0,
|
|
443
|
-
tokenReplacementPending: false
|
|
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", {
|
|
360
|
+
if (projectRepository.setupCommand) {
|
|
361
|
+
if (hasFailedSetupCommand(session.id, projectRepository.setupCommand)) {
|
|
362
|
+
console.warn("[Mastra Factory] Skipping setup command that already failed this session", {
|
|
450
363
|
orgId: session.orgId,
|
|
451
364
|
sessionId: session.sessionId,
|
|
452
|
-
|
|
365
|
+
projectRepositoryId: session.projectRepositoryId
|
|
453
366
|
});
|
|
367
|
+
return;
|
|
454
368
|
}
|
|
455
|
-
throw retiredError();
|
|
456
|
-
}
|
|
457
|
-
githubTokenInjectors.set(workspaceId, tokenRegistration);
|
|
458
|
-
registerGithubTokenContext(tokenRegistration);
|
|
459
|
-
return sandbox;
|
|
460
|
-
};
|
|
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
369
|
try {
|
|
506
|
-
|
|
507
|
-
} catch (
|
|
508
|
-
if (
|
|
509
|
-
|
|
510
|
-
|
|
370
|
+
await runSetupCommand(target, workdir, projectRepository.setupCommand);
|
|
371
|
+
} catch (setupError) {
|
|
372
|
+
if (projectRepository.teardownCommand) try {
|
|
373
|
+
await runTeardownCommand(target, workdir, projectRepository.teardownCommand, { timeoutMs: DEFAULT_COMMAND_TIMEOUT_MS });
|
|
374
|
+
} catch (teardownError) {
|
|
375
|
+
console.warn("[Mastra Factory] Worktree teardown after setup failure failed", {
|
|
376
|
+
orgId: session.orgId,
|
|
377
|
+
sessionId: session.sessionId,
|
|
378
|
+
projectRepositoryId: session.projectRepositoryId,
|
|
379
|
+
error: teardownError instanceof Error ? teardownError.message.slice(-2e3) : String(teardownError)
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
if (setupError instanceof SetupCommandError) {
|
|
383
|
+
recordFailedSetupCommand(session.id, projectRepository.setupCommand);
|
|
384
|
+
throw new SetupCommandError(`${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.`, setupError.code);
|
|
385
|
+
}
|
|
386
|
+
throw setupError;
|
|
511
387
|
}
|
|
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
388
|
}
|
|
524
389
|
};
|
|
390
|
+
const sessionSandbox = requireExec(sessionEntry.sandbox);
|
|
525
391
|
const filesystem = new SandboxFilesystem({
|
|
526
|
-
id: `sandbox-fs:${workspaceId}
|
|
527
|
-
sandbox:
|
|
528
|
-
workdir
|
|
392
|
+
id: `sandbox-fs:${workspaceId}`,
|
|
393
|
+
sandbox: sessionSandbox,
|
|
394
|
+
workdir: () => resolveSessionWorkdir(session.id, sessionEntry.sandbox, repoFullName)
|
|
529
395
|
});
|
|
530
396
|
const projectSkillPaths = [
|
|
531
397
|
path.join(configDir, "skills"),
|
|
532
398
|
".claude/skills",
|
|
533
399
|
".agents/skills"
|
|
534
400
|
];
|
|
535
|
-
const guardedSkillFallback = new UnmaterializedAwareSkillSource(filesystem, () =>
|
|
401
|
+
const guardedSkillFallback = new UnmaterializedAwareSkillSource(filesystem, () => sessionEntry.sandbox.status === "running");
|
|
536
402
|
const workspace = new Workspace({
|
|
537
403
|
id: workspaceId,
|
|
538
404
|
name: "Mastra Code Factory Session Workspace",
|
|
539
405
|
filesystem,
|
|
540
|
-
sandbox:
|
|
406
|
+
sandbox: sessionSandbox,
|
|
541
407
|
tools: MASTRACODE_WORKSPACE_TOOLS,
|
|
542
408
|
skills: [...effectiveSkillExtension?.paths ?? [], ...projectSkillPaths],
|
|
543
409
|
skillSource: effectiveSkillExtension?.createSource(guardedSkillFallback, projectSkillPaths) ?? guardedSkillFallback
|
|
@@ -546,18 +412,14 @@ function createWorkspaceFactory(options = {}) {
|
|
|
546
412
|
constructedWorkspaces.set(workspaceId, workspace);
|
|
547
413
|
if (!await workspaceRegistry.register(session.sessionId, workspaceId, workspaceGeneration, async () => {
|
|
548
414
|
githubTokenInjectors.delete(workspaceId);
|
|
549
|
-
materializedSandboxes.delete(workspaceId);
|
|
550
415
|
constructedWorkspaces.delete(workspaceId);
|
|
416
|
+
evictSessionSandbox(session.id);
|
|
551
417
|
await mastra?.removeWorkspace?.(workspaceId);
|
|
552
418
|
})) 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
419
|
return workspace;
|
|
557
420
|
};
|
|
558
421
|
}
|
|
559
|
-
const getFactoryWorkspace = createWorkspaceFactory();
|
|
560
422
|
//#endregion
|
|
561
|
-
export { FACTORY_SKILLS_SOURCE_PATH, FACTORY_SKILL_NAMES, FactoryWorkspaceRegistry,
|
|
423
|
+
export { FACTORY_SKILLS_SOURCE_PATH, FACTORY_SKILL_NAMES, FactoryWorkspaceRegistry, createWorkspaceFactory };
|
|
562
424
|
|
|
563
425
|
//# sourceMappingURL=workspace.js.map
|