@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.
Files changed (112) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/factory.d.ts +2 -22
  3. package/dist/factory.d.ts.map +1 -1
  4. package/dist/factory.js +15 -58
  5. package/dist/factory.js.map +1 -1
  6. package/dist/index.d.ts +2 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/integrations/base.d.ts +5 -13
  9. package/dist/integrations/base.d.ts.map +1 -1
  10. package/dist/integrations/github/config.d.ts +4 -4
  11. package/dist/integrations/github/config.d.ts.map +1 -1
  12. package/dist/integrations/github/config.js +3 -3
  13. package/dist/integrations/github/config.js.map +1 -1
  14. package/dist/integrations/github/integration.d.ts.map +1 -1
  15. package/dist/integrations/github/integration.js +2 -4
  16. package/dist/integrations/github/integration.js.map +1 -1
  17. package/dist/integrations/github/reconcile-worker.d.ts +0 -5
  18. package/dist/integrations/github/reconcile-worker.d.ts.map +1 -1
  19. package/dist/integrations/github/reconcile-worker.js +0 -7
  20. package/dist/integrations/github/reconcile-worker.js.map +1 -1
  21. package/dist/integrations/github/routes.d.ts +6 -5
  22. package/dist/integrations/github/routes.d.ts.map +1 -1
  23. package/dist/integrations/github/routes.js +36 -263
  24. package/dist/integrations/github/routes.js.map +1 -1
  25. package/dist/integrations/github/sandbox-release.d.ts +17 -47
  26. package/dist/integrations/github/sandbox-release.d.ts.map +1 -1
  27. package/dist/integrations/github/sandbox-release.js +24 -103
  28. package/dist/integrations/github/sandbox-release.js.map +1 -1
  29. package/dist/integrations/github/sandbox.d.ts +25 -139
  30. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  31. package/dist/integrations/github/sandbox.js +24 -212
  32. package/dist/integrations/github/sandbox.js.map +1 -1
  33. package/dist/integrations/github/webhook.d.ts.map +1 -1
  34. package/dist/integrations/github/webhook.js +1 -2
  35. package/dist/integrations/github/webhook.js.map +1 -1
  36. package/dist/integrations/platform/github/event-worker.d.ts +0 -2
  37. package/dist/integrations/platform/github/event-worker.d.ts.map +1 -1
  38. package/dist/integrations/platform/github/event-worker.js +0 -7
  39. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  40. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  41. package/dist/integrations/platform/github/integration.js +5 -7
  42. package/dist/integrations/platform/github/integration.js.map +1 -1
  43. package/dist/routes/config.d.ts.map +1 -1
  44. package/dist/routes/config.js +3 -1
  45. package/dist/routes/config.js.map +1 -1
  46. package/dist/routes/fs.d.ts +4 -6
  47. package/dist/routes/fs.d.ts.map +1 -1
  48. package/dist/routes/fs.js +28 -34
  49. package/dist/routes/fs.js.map +1 -1
  50. package/dist/routes/skills.d.ts.map +1 -1
  51. package/dist/routes/skills.js +6 -3
  52. package/dist/routes/skills.js.map +1 -1
  53. package/dist/routes/surface.d.ts +4 -9
  54. package/dist/routes/surface.d.ts.map +1 -1
  55. package/dist/routes/surface.js +3 -5
  56. package/dist/routes/surface.js.map +1 -1
  57. package/dist/sandbox/materialization.d.ts +36 -0
  58. package/dist/sandbox/materialization.d.ts.map +1 -0
  59. package/dist/sandbox/materialization.js +18 -0
  60. package/dist/sandbox/materialization.js.map +1 -0
  61. package/dist/sandbox/session-retirement.d.ts +8 -6
  62. package/dist/sandbox/session-retirement.d.ts.map +1 -1
  63. package/dist/sandbox/session-retirement.js +28 -123
  64. package/dist/sandbox/session-retirement.js.map +1 -1
  65. package/dist/sandbox/session-sandbox.d.ts +117 -0
  66. package/dist/sandbox/session-sandbox.d.ts.map +1 -0
  67. package/dist/sandbox/session-sandbox.js +132 -0
  68. package/dist/sandbox/session-sandbox.js.map +1 -0
  69. package/dist/sandbox/workdir.d.ts +30 -0
  70. package/dist/sandbox/workdir.d.ts.map +1 -0
  71. package/dist/sandbox/workdir.js +48 -0
  72. package/dist/sandbox/workdir.js.map +1 -0
  73. package/dist/session/filesystem-capture.d.ts +2 -1
  74. package/dist/session/filesystem-capture.d.ts.map +1 -1
  75. package/dist/session/filesystem-capture.js +45 -4
  76. package/dist/session/filesystem-capture.js.map +1 -1
  77. package/dist/session/first-exec-capture.d.ts +1 -0
  78. package/dist/session/first-exec-capture.d.ts.map +1 -1
  79. package/dist/session/first-exec-capture.js +1 -1
  80. package/dist/session/first-exec-capture.js.map +1 -1
  81. package/dist/storage/domains/integrations/base.d.ts +1 -1
  82. package/dist/storage/domains/integrations/base.js +1 -1
  83. package/dist/storage/domains/integrations/base.js.map +1 -1
  84. package/dist/storage/domains/source-control/base.d.ts +0 -144
  85. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  86. package/dist/storage/domains/source-control/base.js +3 -290
  87. package/dist/storage/domains/source-control/base.js.map +1 -1
  88. package/dist/storage/domains/source-control/inmemory.d.ts +1 -63
  89. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  90. package/dist/storage/domains/source-control/inmemory.js +0 -89
  91. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  92. package/dist/workspace.d.ts +5 -37
  93. package/dist/workspace.d.ts.map +1 -1
  94. package/dist/workspace.js +107 -245
  95. package/dist/workspace.js.map +1 -1
  96. package/package.json +4 -4
  97. package/dist/sandbox/base-checkpoint-triggers.d.ts +0 -77
  98. package/dist/sandbox/base-checkpoint-triggers.d.ts.map +0 -1
  99. package/dist/sandbox/base-checkpoint-triggers.js +0 -137
  100. package/dist/sandbox/base-checkpoint-triggers.js.map +0 -1
  101. package/dist/sandbox/base-checkpoint.d.ts +0 -59
  102. package/dist/sandbox/base-checkpoint.d.ts.map +0 -1
  103. package/dist/sandbox/base-checkpoint.js +0 -145
  104. package/dist/sandbox/base-checkpoint.js.map +0 -1
  105. package/dist/sandbox/fleet.d.ts +0 -258
  106. package/dist/sandbox/fleet.d.ts.map +0 -1
  107. package/dist/sandbox/fleet.js +0 -356
  108. package/dist/sandbox/fleet.js.map +0 -1
  109. package/dist/sandbox/reattach.d.ts +0 -3
  110. package/dist/sandbox/reattach.d.ts.map +0 -1
  111. package/dist/sandbox/reattach.js +0 -16
  112. 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 { 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";
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 { LocalSandbox, LocalSkillSource, Workspace } from "@mastra/core/workspace";
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, fleet, workItems } = options;
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 || !fleet) throw new Error("GitHub and sandbox providers are required to create a Factory session workspace");
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
- let workdir = isLocalSandbox ? fleet.computeLocalSessionWorkdir(repoFullName, session.id) : session.sandboxWorkdir ?? projectRepository.sandboxWorkdir;
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
- }
235
- const binding = {
236
- get sandboxId() {
237
- return session.sandboxId;
238
- },
239
- checkpointName: checkpointNameForSession(session.id),
240
- ...!session.materializedAt && projectRepository.baseCheckpoint && !baseCheckpointIsStale(projectRepository) ? { seedCheckpointName: projectRepository.baseCheckpoint.name } : {},
241
- setSandboxId: async (id) => {
242
- await storage.sessions.setSandbox({
243
- id: session.id,
244
- sandboxId: id,
245
- sandboxWorkdir: workdir
246
- });
247
- session.sandboxId = id;
248
- session.sandboxWorkdir = workdir;
249
- },
250
- clear: async () => {
251
- await storage.sessions.setSandbox({
252
- id: session.id,
253
- sandboxId: null,
254
- sandboxWorkdir: workdir
255
- });
256
- session.sandboxId = null;
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 = sandboxConfig.workdir ?? DEFAULT_CONFIG_DIR;
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 materializeSandbox = async () => {
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 patKind = await resolveGithubPatKind("default");
376
- const ghCliToken = await getGithubPat(() => github.integrationStorage, session.orgId, patKind) ?? token;
377
- const ensureSandbox = () => fleet.ensureSandbox(binding, { GH_TOKEN: ghCliToken }, void 0, {
378
- ...isLocalSandbox ? { workingDirectory: workdir } : {},
379
- actingUserId: userId
380
- });
381
- const runMaterialize = (target, skipPull) => materializeRepo({
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
- const isGitMissing = (error) => error instanceof MaterializeError && error.code === "git-missing";
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 && !seededFromBaseCheckpoint) try {
420
- await runWorktreeSetup(sandbox, workdir, projectRepository.setupCommand);
421
- } catch (setupError) {
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
- error: teardownError instanceof Error ? teardownError.message.slice(-2e3) : String(teardownError)
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
- 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);
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}:${workdir}`,
527
- sandbox: lazySandbox,
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, () => materializedSandboxes.has(workspaceId));
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: lazySandbox,
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, checkpointNameForSession, createWorkspaceFactory, getFactoryWorkspace, isDeadSandboxError, isMissingWorkdirError };
423
+ export { FACTORY_SKILLS_SOURCE_PATH, FACTORY_SKILL_NAMES, FactoryWorkspaceRegistry, createWorkspaceFactory };
562
424
 
563
425
  //# sourceMappingURL=workspace.js.map