@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.
- package/CHANGELOG.md +34 -0
- package/dist/auth.d.ts +2 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +22 -2
- package/dist/auth.js.map +1 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +411 -244
- package/dist/factory.js.map +1 -1
- package/dist/index.js +411 -244
- package/dist/index.js.map +1 -1
- package/dist/integrations/github/integration.js +85 -37
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +85 -37
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +2 -0
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +28 -8
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +119 -39
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +1 -4
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/surface.js +1 -4
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/binding-context.js.map +1 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +7 -1
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/tools.js.map +1 -1
- package/dist/spa-static.d.ts.map +1 -1
- package/dist/spa-static.js +1 -0
- package/dist/spa-static.js.map +1 -1
- package/dist/storage/domains/source-control/base.d.ts +9 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -1
- package/dist/storage/domains/source-control/base.js +4 -0
- package/dist/storage/domains/source-control/base.js.map +1 -1
- package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/source-control/inmemory.js +4 -0
- package/dist/storage/domains/source-control/inmemory.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +20 -4
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/storage/domains/work-items/metrics.js.map +1 -1
- package/dist/timing.d.ts +15 -0
- package/dist/timing.d.ts.map +1 -0
- package/dist/timing.js +27 -0
- package/dist/timing.js.map +1 -0
- package/dist/workspace.d.ts +2 -3
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +104 -65
- package/dist/workspace.js.map +1 -1
- package/factory-skills/factory-review/SKILL.md +9 -2
- 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
|
-
|
|
62
|
-
|
|
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 =
|
|
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,
|
|
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
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
423
|
-
const
|
|
424
|
-
|
|
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
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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();
|