@mastra/factory 0.2.1 → 0.2.2-alpha.1

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 (59) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/auth.d.ts +2 -2
  3. package/dist/auth.d.ts.map +1 -1
  4. package/dist/auth.js +22 -2
  5. package/dist/auth.js.map +1 -1
  6. package/dist/factory.d.ts.map +1 -1
  7. package/dist/factory.js +411 -244
  8. package/dist/factory.js.map +1 -1
  9. package/dist/index.js +411 -244
  10. package/dist/index.js.map +1 -1
  11. package/dist/integrations/github/integration.js +85 -37
  12. package/dist/integrations/github/integration.js.map +1 -1
  13. package/dist/integrations/github/routes.d.ts.map +1 -1
  14. package/dist/integrations/github/routes.js +85 -37
  15. package/dist/integrations/github/routes.js.map +1 -1
  16. package/dist/integrations/github/sandbox.d.ts +2 -0
  17. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  18. package/dist/integrations/github/sandbox.js +28 -8
  19. package/dist/integrations/github/sandbox.js.map +1 -1
  20. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  21. package/dist/integrations/platform/github/integration.js +119 -39
  22. package/dist/integrations/platform/github/integration.js.map +1 -1
  23. package/dist/routes/config.d.ts.map +1 -1
  24. package/dist/routes/config.js +1 -4
  25. package/dist/routes/config.js.map +1 -1
  26. package/dist/routes/surface.js +1 -4
  27. package/dist/routes/surface.js.map +1 -1
  28. package/dist/routes/work-items.js.map +1 -1
  29. package/dist/rules/binding-context.js.map +1 -1
  30. package/dist/rules/dispatcher.d.ts.map +1 -1
  31. package/dist/rules/dispatcher.js +7 -1
  32. package/dist/rules/dispatcher.js.map +1 -1
  33. package/dist/rules/processor.js.map +1 -1
  34. package/dist/rules/tools.js.map +1 -1
  35. package/dist/spa-static.d.ts.map +1 -1
  36. package/dist/spa-static.js +1 -0
  37. package/dist/spa-static.js.map +1 -1
  38. package/dist/storage/domains/source-control/base.d.ts +9 -0
  39. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  40. package/dist/storage/domains/source-control/base.js +4 -0
  41. package/dist/storage/domains/source-control/base.js.map +1 -1
  42. package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
  43. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  44. package/dist/storage/domains/source-control/inmemory.js +4 -0
  45. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  46. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  47. package/dist/storage/domains/work-items/base.js +20 -4
  48. package/dist/storage/domains/work-items/base.js.map +1 -1
  49. package/dist/storage/domains/work-items/metrics.js.map +1 -1
  50. package/dist/timing.d.ts +15 -0
  51. package/dist/timing.d.ts.map +1 -0
  52. package/dist/timing.js +27 -0
  53. package/dist/timing.js.map +1 -0
  54. package/dist/workspace.d.ts +2 -3
  55. package/dist/workspace.d.ts.map +1 -1
  56. package/dist/workspace.js +104 -65
  57. package/dist/workspace.js.map +1 -1
  58. package/factory-skills/factory-review/SKILL.md +9 -2
  59. package/package.json +7 -7
package/dist/workspace.js CHANGED
@@ -58,8 +58,23 @@ function reportProgress(onProgress, event) {
58
58
  function shellQuote(value) {
59
59
  return `'` + value.split(`'`).join(`'\\''`) + `'`;
60
60
  }
61
- async function sh(sandbox, script) {
62
- return sandbox.executeCommand("sh", ["-c", script]);
61
+ var DEFAULT_COMMAND_TIMEOUT_MS = 15 * 6e4;
62
+ var CHECKOUT_COMMAND_TIMEOUT_MS = 5 * 6e4;
63
+ async function sh(sandbox, script, options = {}) {
64
+ const timeoutMs = options.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS;
65
+ let timer;
66
+ const hangGuard = new Promise((_, reject) => {
67
+ timer = setTimeout(() => {
68
+ const phase = options.phase ? ` during ${options.phase}` : "";
69
+ reject(new Error(`Sandbox command timed out after ${Math.round(timeoutMs / 1e3)}s${phase}.`));
70
+ }, timeoutMs);
71
+ timer.unref?.();
72
+ });
73
+ try {
74
+ return await Promise.race([sandbox.executeCommand("sh", ["-c", script], { timeout: timeoutMs }), hangGuard]);
75
+ } finally {
76
+ clearTimeout(timer);
77
+ }
63
78
  }
64
79
  var MaterializeError = class extends Error {
65
80
  constructor(message, code) {
@@ -96,7 +111,8 @@ async function materializeRepo(options) {
96
111
  );
97
112
  }
98
113
  const authUrl = tokenUrl(repo, token);
99
- const alreadyMaterialized = Boolean(sandboxRow.materializedAt) || await hasExistingCheckout(sandbox, workdir, repo);
114
+ const alreadyMaterialized = await hasExistingCheckout(sandbox, workdir, repo);
115
+ let succeeded = false;
100
116
  try {
101
117
  if (!alreadyMaterialized) {
102
118
  reportProgress(onProgress, {
@@ -105,7 +121,8 @@ async function materializeRepo(options) {
105
121
  });
106
122
  const clone = await sh(
107
123
  sandbox,
108
- `git clone --depth=1 --single-branch --branch ${shellQuote(repoInfo.defaultBranch)} ${shellQuote(authUrl)} ${shellQuote(workdir)}`
124
+ `git clone --depth=1 --single-branch --branch ${shellQuote(repoInfo.defaultBranch)} ${shellQuote(authUrl)} ${shellQuote(workdir)}`,
125
+ { phase: "repository clone" }
109
126
  );
110
127
  if (clone.exitCode !== 0) {
111
128
  throw classifyGitFailure(clone, "clone-failed");
@@ -116,13 +133,14 @@ async function materializeRepo(options) {
116
133
  if (setUrl.exitCode !== 0) {
117
134
  throw new MaterializeError(`Failed to set git remote: ${setUrl.stderr}`, "pull-failed");
118
135
  }
119
- const pull = await sh(sandbox, `git -C ${shellQuote(workdir)} pull --ff-only`);
136
+ const pull = await sh(sandbox, `git -C ${shellQuote(workdir)} pull --ff-only`, { phase: "repository pull" });
120
137
  if (pull.exitCode !== 0) {
121
138
  throw classifyGitFailure(pull, "pull-failed");
122
139
  }
123
140
  }
141
+ succeeded = true;
124
142
  } finally {
125
- await scrubRemote(sandbox, workdir, repo, alreadyMaterialized);
143
+ await scrubRemote(sandbox, workdir, repo, succeeded);
126
144
  }
127
145
  reportProgress(onProgress, { phase: "finalizing", message: "Finalizing workspace\u2026" });
128
146
  await storage.markMaterialized({ id: sandboxRow.id });
@@ -153,7 +171,8 @@ async function checkoutSessionBranch(sandbox, workdir, {
153
171
  if (setUrl.exitCode !== 0) throw classifyGitFailure(setUrl, "pull-failed");
154
172
  const fetch = await sh(
155
173
  sandbox,
156
- `git -C ${shellQuote(workdir)} fetch origin ${shellQuote(baseBranch)} && git -C ${shellQuote(workdir)} checkout -b ${shellQuote(branch)} FETCH_HEAD`
174
+ `git -C ${shellQuote(workdir)} fetch origin ${shellQuote(baseBranch)} && git -C ${shellQuote(workdir)} checkout -b ${shellQuote(branch)} FETCH_HEAD`,
175
+ { timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS, phase: "branch checkout" }
157
176
  );
158
177
  if (fetch.exitCode !== 0) throw classifyGitFailure(fetch, "clone-failed");
159
178
  } finally {
@@ -205,7 +224,7 @@ var WorktreeError = class extends Error {
205
224
  };
206
225
  async function runWorktreeSetup(sandbox, worktreePath, command) {
207
226
  const result = await sh(sandbox, `cd ${shellQuote(worktreePath)} && { ${command}
208
- }`);
227
+ }`, { phase: "worktree setup" });
209
228
  if (result.exitCode !== 0) {
210
229
  const detail = (result.stderr.trim() || result.stdout.trim()).slice(-2e3);
211
230
  throw new WorktreeError(`Setup command failed (exit ${result.exitCode}): ${detail}`, "setup-failed");
@@ -312,6 +331,7 @@ function createWorkspaceFactory(options = {}) {
312
331
  const { sandbox: sandboxConfig, github, fleet, workItems } = options;
313
332
  const isLocalSandbox = sandboxConfig?.machine instanceof LocalSandbox;
314
333
  const githubTokenInjectors = /* @__PURE__ */ new Map();
334
+ const inflightMaterializations = /* @__PURE__ */ new Map();
315
335
  return async ({ requestContext, mastra, skillExtension }) => {
316
336
  const effectiveSkillExtension = skillExtension ?? factorySkillExtension;
317
337
  const ctx = requestContext.get("controller");
@@ -379,65 +399,84 @@ function createWorkspaceFactory(options = {}) {
379
399
  }
380
400
  } catch {
381
401
  }
382
- const access = await github.versionControl.getRepositoryAccess({
383
- orgId: session.orgId,
384
- repositoryId: repository.id
385
- });
386
- const token = access.authorization?.token;
387
- if (!token) throw new Error("Repository access did not include a bearer token for the Factory session");
388
- let patKind = "default";
389
- if (workItems) {
390
- try {
391
- const address = getFactorySessionAddress(requestContext);
392
- const runBinding = address ? await workItems.findRunBindingBySession(address) : null;
393
- if (runBinding?.role === "review" && runBinding.orgId === session.orgId) patKind = "reviewer";
394
- } catch {
395
- }
396
- }
397
- const ghCliToken = await getGithubPat(() => github.integrationStorage, session.orgId, patKind) ?? token;
398
- const sandbox = await fleet.ensureSandbox(
399
- binding,
400
- { GH_TOKEN: ghCliToken },
401
- void 0,
402
- isLocalSandbox ? { workingDirectory: workdir } : {}
403
- );
404
- await materializeRepo({
405
- row: { id: session.id, sandboxWorkdir: workdir, materializedAt: session.materializedAt },
406
- repoInfo: { repoFullName, defaultBranch: repository.defaultBranch },
407
- sandbox,
408
- token,
409
- storage: storage.sessions
410
- });
411
- await checkoutSessionBranch(sandbox, workdir, {
412
- branch: session.branch,
413
- baseBranch: session.baseBranch || projectRepository.branch || repository.defaultBranch,
414
- token,
415
- repoFullName
416
- });
417
- if (projectRepository.setupCommand) await runWorktreeSetup(sandbox, workdir, projectRepository.setupCommand);
418
- const injectGithubToken = (freshToken) => {
419
- if (!sandbox.setEnvironmentVariable) {
420
- throw new Error("The active sandbox provider does not support runtime GitHub token refresh.");
402
+ const materialize = async () => {
403
+ const access = await github.versionControl.getRepositoryAccess({
404
+ orgId: session.orgId,
405
+ repositoryId: repository.id
406
+ });
407
+ const token = access.authorization?.token;
408
+ if (!token) throw new Error("Repository access did not include a bearer token for the Factory session");
409
+ let patKind = "default";
410
+ if (workItems) {
411
+ try {
412
+ const address = getFactorySessionAddress(requestContext);
413
+ const runBinding = address ? await workItems.findRunBindingBySession(address) : null;
414
+ if (runBinding?.role === "review" && runBinding.orgId === session.orgId) patKind = "reviewer";
415
+ } catch {
416
+ }
421
417
  }
422
- sandbox.setEnvironmentVariable("GH_TOKEN", freshToken);
423
- const registered = githubTokenInjectors.get(workspaceId);
424
- if (registered) registered.ghToken = freshToken;
418
+ const ghCliToken = await getGithubPat(() => github.integrationStorage, session.orgId, patKind) ?? token;
419
+ const sandbox = await fleet.ensureSandbox(
420
+ binding,
421
+ { GH_TOKEN: ghCliToken },
422
+ void 0,
423
+ isLocalSandbox ? { workingDirectory: workdir } : {}
424
+ );
425
+ await materializeRepo({
426
+ row: { id: session.id, sandboxWorkdir: workdir, materializedAt: session.materializedAt },
427
+ repoInfo: { repoFullName, defaultBranch: repository.defaultBranch },
428
+ sandbox,
429
+ token,
430
+ storage: storage.sessions
431
+ });
432
+ await checkoutSessionBranch(sandbox, workdir, {
433
+ branch: session.branch,
434
+ baseBranch: session.baseBranch || projectRepository.branch || repository.defaultBranch,
435
+ token,
436
+ repoFullName
437
+ });
438
+ if (projectRepository.setupCommand) await runWorktreeSetup(sandbox, workdir, projectRepository.setupCommand);
439
+ const injectGithubToken = (freshToken) => {
440
+ if (!sandbox.setEnvironmentVariable) {
441
+ throw new Error("The active sandbox provider does not support runtime GitHub token refresh.");
442
+ }
443
+ sandbox.setEnvironmentVariable("GH_TOKEN", freshToken);
444
+ const registered = githubTokenInjectors.get(workspaceId);
445
+ if (registered) registered.ghToken = freshToken;
446
+ };
447
+ githubTokenInjectors.set(workspaceId, { inject: injectGithubToken, patKind, ghToken: ghCliToken });
448
+ registerGithubTokenInjector(requestContext, injectGithubToken);
449
+ registerGithubPatKind(requestContext, patKind);
450
+ const filesystem = new SandboxFilesystem({ sandbox, workdir });
451
+ const projectSkillPaths = [path2.join(configDir, "skills"), ".claude/skills", ".agents/skills"];
452
+ const skillPaths = [...effectiveSkillExtension?.paths ?? [], ...projectSkillPaths];
453
+ return new Workspace({
454
+ id: workspaceId,
455
+ name: "Mastra Code Factory Session Workspace",
456
+ filesystem,
457
+ sandbox,
458
+ tools: MASTRACODE_WORKSPACE_TOOLS,
459
+ skills: skillPaths,
460
+ skillSource: effectiveSkillExtension?.createSource(filesystem, projectSkillPaths) ?? filesystem
461
+ });
425
462
  };
426
- githubTokenInjectors.set(workspaceId, { inject: injectGithubToken, patKind, ghToken: ghCliToken });
427
- registerGithubTokenInjector(requestContext, injectGithubToken);
428
- registerGithubPatKind(requestContext, patKind);
429
- const filesystem = new SandboxFilesystem({ sandbox, workdir });
430
- const projectSkillPaths = [path2.join(configDir, "skills"), ".claude/skills", ".agents/skills"];
431
- const skillPaths = [...effectiveSkillExtension?.paths ?? [], ...projectSkillPaths];
432
- return new Workspace({
433
- id: workspaceId,
434
- name: "Mastra Code Factory Session Workspace",
435
- filesystem,
436
- sandbox,
437
- tools: MASTRACODE_WORKSPACE_TOOLS,
438
- skills: skillPaths,
439
- skillSource: effectiveSkillExtension?.createSource(filesystem, projectSkillPaths) ?? filesystem
440
- });
463
+ const inflight = inflightMaterializations.get(workspaceId);
464
+ if (inflight) {
465
+ const workspace = await inflight;
466
+ const registered = githubTokenInjectors.get(workspaceId);
467
+ if (registered) {
468
+ registerGithubTokenInjector(requestContext, registered.inject);
469
+ registerGithubPatKind(requestContext, registered.patKind);
470
+ }
471
+ return workspace;
472
+ }
473
+ const materialization = materialize();
474
+ inflightMaterializations.set(workspaceId, materialization);
475
+ try {
476
+ return await materialization;
477
+ } finally {
478
+ inflightMaterializations.delete(workspaceId);
479
+ }
441
480
  };
442
481
  }
443
482
  var getFactoryWorkspace = createWorkspaceFactory();