@mastra/factory 0.18.0-alpha.9 → 0.18.1-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 (105) hide show
  1. package/dist/factory.d.ts.map +1 -1
  2. package/dist/factory.js +5 -2
  3. package/dist/factory.js.map +1 -1
  4. package/dist/integrations/github/default-rules.d.ts.map +1 -1
  5. package/dist/integrations/github/default-rules.js +3 -3
  6. package/dist/integrations/github/default-rules.js.map +1 -1
  7. package/dist/integrations/github/reconcile-worker.d.ts.map +1 -1
  8. package/dist/integrations/github/reconcile-worker.js +1 -2
  9. package/dist/integrations/github/reconcile-worker.js.map +1 -1
  10. package/dist/integrations/github/routes.d.ts.map +1 -1
  11. package/dist/integrations/github/routes.js +1 -14
  12. package/dist/integrations/github/routes.js.map +1 -1
  13. package/dist/integrations/gitlab/version-control.d.ts.map +1 -1
  14. package/dist/integrations/gitlab/version-control.js +4 -5
  15. package/dist/integrations/gitlab/version-control.js.map +1 -1
  16. package/dist/integrations/gitlab/webhook.d.ts.map +1 -1
  17. package/dist/integrations/gitlab/webhook.js +2 -2
  18. package/dist/integrations/gitlab/webhook.js.map +1 -1
  19. package/dist/integrations/issue-reconcile-worker.d.ts.map +1 -1
  20. package/dist/integrations/issue-reconcile-worker.js +1 -2
  21. package/dist/integrations/issue-reconcile-worker.js.map +1 -1
  22. package/dist/integrations/linear/integration.d.ts.map +1 -1
  23. package/dist/integrations/linear/integration.js +10 -30
  24. package/dist/integrations/linear/integration.js.map +1 -1
  25. package/dist/integrations/platform/github/event-worker.d.ts.map +1 -1
  26. package/dist/integrations/platform/github/event-worker.js +1 -2
  27. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  28. package/dist/integrations/platform/gitlab/event-worker.d.ts.map +1 -1
  29. package/dist/integrations/platform/gitlab/event-worker.js +1 -2
  30. package/dist/integrations/platform/gitlab/event-worker.js.map +1 -1
  31. package/dist/integrations/platform/linear/event-worker.d.ts.map +1 -1
  32. package/dist/integrations/platform/linear/event-worker.js +1 -2
  33. package/dist/integrations/platform/linear/event-worker.js.map +1 -1
  34. package/dist/integrations/platform/linear/integration.d.ts.map +1 -1
  35. package/dist/integrations/platform/linear/integration.js +9 -9
  36. package/dist/integrations/platform/linear/integration.js.map +1 -1
  37. package/dist/integrations/slack/slack.d.ts.map +1 -1
  38. package/dist/integrations/slack/slack.js +137 -19
  39. package/dist/integrations/slack/slack.js.map +1 -1
  40. package/dist/integrations/subscription-session.d.ts +3 -1
  41. package/dist/integrations/subscription-session.d.ts.map +1 -1
  42. package/dist/integrations/subscription-session.js +4 -2
  43. package/dist/integrations/subscription-session.js.map +1 -1
  44. package/dist/review-verdict.d.ts +6 -0
  45. package/dist/review-verdict.d.ts.map +1 -0
  46. package/dist/review-verdict.js +14 -0
  47. package/dist/review-verdict.js.map +1 -0
  48. package/dist/routes/contracts.d.ts +23 -0
  49. package/dist/routes/contracts.d.ts.map +1 -1
  50. package/dist/routes/contracts.js +21 -0
  51. package/dist/routes/contracts.js.map +1 -1
  52. package/dist/routes/oauth.d.ts.map +1 -1
  53. package/dist/routes/oauth.js +3 -3
  54. package/dist/routes/oauth.js.map +1 -1
  55. package/dist/routes/projects.d.ts +22 -2
  56. package/dist/routes/projects.d.ts.map +1 -1
  57. package/dist/routes/projects.js +101 -0
  58. package/dist/routes/projects.js.map +1 -1
  59. package/dist/routes/source-control-sessions.d.ts.map +1 -1
  60. package/dist/routes/source-control-sessions.js +1 -2
  61. package/dist/routes/source-control-sessions.js.map +1 -1
  62. package/dist/rules/dispatcher.d.ts.map +1 -1
  63. package/dist/rules/dispatcher.js +1 -2
  64. package/dist/rules/dispatcher.js.map +1 -1
  65. package/dist/rules/processor.d.ts.map +1 -1
  66. package/dist/rules/processor.js +5 -5
  67. package/dist/rules/processor.js.map +1 -1
  68. package/dist/rules/transition-service.d.ts.map +1 -1
  69. package/dist/rules/transition-service.js +1 -2
  70. package/dist/rules/transition-service.js.map +1 -1
  71. package/dist/sandbox/materialization.d.ts +5 -1
  72. package/dist/sandbox/materialization.d.ts.map +1 -1
  73. package/dist/sandbox/materialization.js.map +1 -1
  74. package/dist/sandbox/session-sandbox.d.ts +13 -1
  75. package/dist/sandbox/session-sandbox.d.ts.map +1 -1
  76. package/dist/sandbox/session-sandbox.js.map +1 -1
  77. package/dist/secret-encryption.js +2 -2
  78. package/dist/secret-encryption.js.map +1 -1
  79. package/dist/session/factory-session.d.ts.map +1 -1
  80. package/dist/session/factory-session.js +26 -9
  81. package/dist/session/factory-session.js.map +1 -1
  82. package/dist/session/source-control-tools.d.ts +9 -0
  83. package/dist/session/source-control-tools.d.ts.map +1 -1
  84. package/dist/session/source-control-tools.js +51 -2
  85. package/dist/session/source-control-tools.js.map +1 -1
  86. package/dist/state-signing.js +3 -3
  87. package/dist/state-signing.js.map +1 -1
  88. package/dist/storage/domains/audit/base.d.ts.map +1 -1
  89. package/dist/storage/domains/audit/base.js +2 -3
  90. package/dist/storage/domains/audit/base.js.map +1 -1
  91. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  92. package/dist/storage/domains/source-control/inmemory.js +5 -6
  93. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  94. package/dist/storage/domains/work-items/base.d.ts +2 -0
  95. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  96. package/dist/storage/domains/work-items/base.js +9 -1
  97. package/dist/storage/domains/work-items/base.js.map +1 -1
  98. package/dist/workspace.d.ts.map +1 -1
  99. package/dist/workspace.js +8 -7
  100. package/dist/workspace.js.map +1 -1
  101. package/factory-skills/factory-gitlab-rereview/SKILL.md +1 -1
  102. package/factory-skills/factory-gitlab-review/SKILL.md +1 -1
  103. package/factory-skills/factory-rereview/SKILL.md +12 -3
  104. package/factory-skills/factory-review/SKILL.md +12 -3
  105. package/package.json +10 -10
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"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"}
package/dist/workspace.js CHANGED
@@ -289,16 +289,16 @@ function createWorkspaceFactory(options = {}) {
289
289
  await guardedSetup(args);
290
290
  if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) throw retiredError();
291
291
  const target = requireExec(args.sandbox);
292
- const publishStartSideEffects = () => {
293
- storage.sessions.setSandbox({
292
+ const publishStartSideEffects = async () => {
293
+ await storage.sessions.setSandbox({
294
294
  id: session.id,
295
- sandboxId: target.id,
295
+ sandboxId: target.sandboxId ?? target.id,
296
296
  sandboxWorkdir: sessionEntry.workdir ?? ""
297
- }).catch(() => {});
297
+ });
298
298
  constructedWorkspaces.get(workspaceId)?.skills?.refresh().catch(() => {});
299
299
  };
300
300
  if (!githubProvider) {
301
- publishStartSideEffects();
301
+ await publishStartSideEffects();
302
302
  return;
303
303
  }
304
304
  const existingRegistration = githubTokenInjectors.get(workspaceId);
@@ -312,7 +312,7 @@ function createWorkspaceFactory(options = {}) {
312
312
  existingRegistration.ghToken = freshToken;
313
313
  };
314
314
  existingRegistration.inject(existingRegistration.ghToken);
315
- publishStartSideEffects();
315
+ await publishStartSideEffects();
316
316
  return;
317
317
  }
318
318
  const patKind = await resolveGithubPatKind("default");
@@ -337,11 +337,12 @@ function createWorkspaceFactory(options = {}) {
337
337
  };
338
338
  githubTokenInjectors.set(workspaceId, tokenRegistration);
339
339
  registerGithubTokenContext(tokenRegistration);
340
- publishStartSideEffects();
340
+ await publishStartSideEffects();
341
341
  };
342
342
  const constructSessionEntry = () => getSessionSandbox(session.id, repoFullName, () => {
343
343
  const sandbox = createSessionSandboxInstance({
344
344
  sessionId: session.id,
345
+ sandboxId: session.sandboxId ?? void 0,
345
346
  repoFullName,
346
347
  setupCommand: projectRepository.setupCommand ?? void 0,
347
348
  getRepositoryAccess: () => sourceControl.versionControl.getRepositoryAccess({
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.js","names":["#bundledSource","#localSource","#fallbackSkillRoots","#isHiddenReviewPath","#layerFor","#isFactoryPath","#factoryPath","#entries","#generations"],"sources":["../src/workspace.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport path, { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { SandboxFilesystem } from '@mastra/code-sdk/agents/sandbox-filesystem';\nimport { MASTRACODE_WORKSPACE_TOOLS } from '@mastra/code-sdk/agents/tool-availability';\nimport type { getDynamicWorkspace, WorkspaceSkillExtension } from '@mastra/code-sdk/agents/workspace';\nimport { DEFAULT_CONFIG_DIR } from '@mastra/code-sdk/constants';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport { LocalSkillSource, Workspace } from '@mastra/core/workspace';\nimport type {\n SandboxStartHook,\n SkillSource,\n SkillSourceEntry,\n SkillSourceStat,\n WorkspaceSandbox,\n} from '@mastra/core/workspace';\nimport { getFactoryAuthOrgId, getFactoryAuthUserFromContext, getFactoryAuthUserId } from './auth.js';\nimport type { VersionControl } from './capabilities/version-control.js';\nimport type { MastraFactorySandboxConfig } from './factory.js';\nimport type { GithubIntegration } from './integrations/github/integration.js';\nimport { getGithubPat } from './integrations/github/pat.js';\nimport type { GithubPatKind } from './integrations/github/pat.js';\nimport {\n checkoutSessionBranch,\n DEFAULT_COMMAND_TIMEOUT_MS,\n materializeRepo,\n runSetupCommand,\n runTeardownCommand,\n SetupCommandError,\n} from './integrations/github/sandbox.js';\nimport { registerGithubPatKind, registerGithubTokenInjector } from './integrations/github/token-refresh.js';\nimport { requireExec } from './sandbox/materialization.js';\nimport type { ExecutableSandbox } from './sandbox/materialization.js';\nimport {\n createSessionSetupHook,\n evictSessionSandbox,\n getSessionSandbox,\n hasFailedSetupCommand,\n recordFailedSetupCommand,\n resolveSessionWorkdir,\n} from './sandbox/session-sandbox.js';\nimport type { SessionSetupGate } from './sandbox/session-sandbox.js';\nimport type { FactoryProjectsStorage } from './storage/domains/projects/base.js';\nimport type { SourceControlSession, SourceControlStorageHandle } from './storage/domains/source-control/base.js';\nimport type { WorkItemsStorage } from './storage/domains/work-items/base.js';\nimport { parseSupervisorResourceId } from './supervisor/session.js';\nimport { timedPhase } from './timing.js';\nimport { mergeRequestNumberFromBranch, pullRequestNumberFromBranch } from './work-item-branch.js';\n\nconst WORKSPACE_ID_PREFIX = 'mfw';\nconst bundleDirectory = dirname(fileURLToPath(import.meta.url));\nconst bundledFactorySkillsPath = join(bundleDirectory, 'factory-skills');\nexport const BUNDLED_FACTORY_SKILLS_PATH =\n [\n // Deploy bundle: the consumer copies `factory-skills/` next to the built\n // server module (e.g. via its public/ dir).\n bundledFactorySkillsPath,\n // Package layout: `dist/../factory-skills` (also `src/../factory-skills`\n // when running tests against sources).\n join(bundleDirectory, '..', 'factory-skills'),\n ].find(existsSync) ?? bundledFactorySkillsPath;\n\n/**\n * Resolve the consumer repo's local Factory skills root, if any. Checked in\n * addition to the bundled skills so projects can add (or override) Factory\n * skills without patching the installed package. Candidates cover the cwd\n * variants the dev server runs with (`repo root`, `--dir src/mastra` which\n * runs with cwd `src/mastra/public`).\n */\nexport function resolveLocalFactorySkillsPath(cwd: string = process.cwd()): string | undefined {\n const candidates = [\n join(cwd, 'src', 'mastra', 'public', 'factory-skills'),\n join(cwd, 'public', 'factory-skills'),\n join(cwd, 'factory-skills'),\n ];\n return candidates.find(\n candidate => path.normalize(candidate) !== path.normalize(BUNDLED_FACTORY_SKILLS_PATH) && existsSync(candidate),\n );\n}\nconst FACTORY_SKILLS_MOUNT = path.resolve(path.parse(process.cwd()).root, '__mastracode_factory_skills__');\nexport const FACTORY_SKILL_NAMES = new Set([\n 'configure-factory-rules',\n 'factory-complete-issue',\n 'factory-gitlab-rereview',\n 'factory-gitlab-review',\n 'factory-plan',\n 'factory-rereview',\n 'factory-review',\n 'factory-triage',\n]);\n\n/** Review skills only sessions with an active review run binding may discover or load. */\nexport const REVIEW_ONLY_FACTORY_SKILLS = new Set([\n 'factory-gitlab-rereview',\n 'factory-gitlab-review',\n 'factory-rereview',\n 'factory-review',\n]);\n\n/**\n * Skill caches rescanned outside the workspace resolver. The resolver records\n * which role each cache was last scanned for; a rescan it did not perform makes\n * that record untrustworthy, so the next reuse must rescan again.\n */\nconst rescannedOutsideResolver = new WeakMap<object, Promise<void>>();\n\n/**\n * Rescan a session's skill cache so a role gained after the cache was built\n * (a review binding minted after the session's workspace resolved) takes effect.\n * The source still checks the live binding, so this never widens access.\n */\nexport async function rescanFactorySkills(skills: NonNullable<Workspace['skills']>): Promise<void> {\n // Registered so the resolver waits it out: a refresh started meanwhile would\n // join this scan (possibly for the old role) instead of starting a fresh one.\n const pending = skills.refresh();\n rescannedOutsideResolver.set(\n skills,\n pending.catch(() => {}),\n );\n await pending;\n}\n\nexport class FactorySkillSource implements SkillSource {\n readonly #bundledSource = new LocalSkillSource({ basePath: BUNDLED_FACTORY_SKILLS_PATH });\n readonly #localSource: LocalSkillSource | undefined;\n readonly #fallbackSkillRoots: Set<string>;\n\n constructor(\n readonly fallback: SkillSource,\n fallbackSkillRoots: string[],\n localSkillsPath: string | undefined = resolveLocalFactorySkillsPath(),\n /** Evaluated per call because a session's bound role changes across stages. */\n readonly isReviewSession: () => Promise<boolean> = () => Promise.resolve(true),\n ) {\n this.#localSource = localSkillsPath ? new LocalSkillSource({ basePath: localSkillsPath }) : undefined;\n this.#fallbackSkillRoots = new Set(fallbackSkillRoots.map(skillPath => path.normalize(skillPath)));\n }\n\n #isFactoryPath(skillPath: string): boolean {\n const normalized = path.normalize(skillPath);\n return normalized === FACTORY_SKILLS_MOUNT || normalized.startsWith(`${FACTORY_SKILLS_MOUNT}${path.sep}`);\n }\n\n #factoryPath(skillPath: string): string {\n return path.relative(FACTORY_SKILLS_MOUNT, path.normalize(skillPath));\n }\n\n /** True when the path is inside a review-only skill that this session may not use. */\n async #isHiddenReviewPath(relativePath: string): Promise<boolean> {\n const skillName = relativePath.split(path.sep)[0];\n if (!skillName || !REVIEW_ONLY_FACTORY_SKILLS.has(skillName)) return false;\n return !(await this.isReviewSession());\n }\n\n /** Pick the layer serving this mount-relative path: local wins when it has the entry. */\n async #layerFor(relativePath: string): Promise<LocalSkillSource> {\n if (this.#localSource && (await this.#localSource.exists(relativePath))) return this.#localSource;\n return this.#bundledSource;\n }\n\n async exists(skillPath: string): Promise<boolean> {\n if (!this.#isFactoryPath(skillPath)) return this.fallback.exists(skillPath);\n const relative = this.#factoryPath(skillPath);\n if (await this.#isHiddenReviewPath(relative)) return false;\n if (this.#localSource && (await this.#localSource.exists(relative))) return true;\n return this.#bundledSource.exists(relative);\n }\n\n async stat(skillPath: string): Promise<SkillSourceStat> {\n if (!this.#isFactoryPath(skillPath)) return this.fallback.stat(skillPath);\n const relative = this.#factoryPath(skillPath);\n if (await this.#isHiddenReviewPath(relative)) throw skillSourceEnoent(skillPath);\n return (await this.#layerFor(relative)).stat(relative);\n }\n\n async readFile(skillPath: string): Promise<string | Buffer> {\n if (!this.#isFactoryPath(skillPath)) return this.fallback.readFile(skillPath);\n const relative = this.#factoryPath(skillPath);\n if (await this.#isHiddenReviewPath(relative)) throw skillSourceEnoent(skillPath);\n return (await this.#layerFor(relative)).readFile(relative);\n }\n\n async readdir(skillPath: string): Promise<SkillSourceEntry[]> {\n if (this.#isFactoryPath(skillPath)) {\n const relative = this.#factoryPath(skillPath);\n if (await this.#isHiddenReviewPath(relative)) throw skillSourceEnoent(skillPath);\n const [bundledExists, localExists] = await Promise.all([\n this.#bundledSource.exists(relative),\n this.#localSource?.exists(relative) ?? Promise.resolve(false),\n ]);\n if (!bundledExists && !localExists) throw skillSourceEnoent(skillPath);\n const [bundledEntries, localEntries] = await Promise.all([\n bundledExists ? this.#bundledSource.readdir(relative) : [],\n localExists ? this.#localSource!.readdir(relative) : [],\n ]);\n const merged = new Map<string, SkillSourceEntry>();\n for (const entry of bundledEntries) merged.set(entry.name, entry);\n // Local entries override bundled names.\n for (const entry of localEntries) merged.set(entry.name, entry);\n const entries = [...merged.values()];\n if (relative !== '' || (await this.isReviewSession())) return entries;\n return entries.filter(entry => !REVIEW_ONLY_FACTORY_SKILLS.has(entry.name));\n }\n const entries = await this.fallback.readdir(skillPath);\n if (this.#fallbackSkillRoots.has(path.normalize(skillPath))) {\n return entries.filter(entry => !FACTORY_SKILL_NAMES.has(entry.name));\n }\n return entries;\n }\n\n realpath(skillPath: string): Promise<string> {\n if (this.#isFactoryPath(skillPath)) return Promise.resolve(path.normalize(skillPath));\n return this.fallback.realpath ? this.fallback.realpath(skillPath) : Promise.resolve(skillPath);\n }\n}\n\n/** Build a Node-style ENOENT error so callers can treat missing skills like fs misses. */\nfunction skillSourceEnoent(skillPath: string): Error {\n const error = new Error(`ENOENT: no such file or directory, '${skillPath}'`) as Error & { code: string };\n error.code = 'ENOENT';\n return error;\n}\n\n/**\n * Sandbox-backed skill fallback that stays inert until the session sandbox is\n * actually materialized. Skill discovery runs on latency-sensitive paths (the\n * Factory start coordinator resolves the kickoff skill before the start route\n * responds); without this guard the first project-root read would hit the lazy\n * sandbox handle and force full provisioning + repo materialization. While the\n * sandbox is unmaterialized, project skill roots simply appear empty — bundled\n * Factory skills resolve from local disk via `FactorySkillSource`. Once the\n * sandbox exists, every call delegates straight through.\n */\nclass UnmaterializedAwareSkillSource implements SkillSource {\n constructor(\n readonly fallback: SkillSource,\n readonly isMaterialized: () => boolean,\n ) {}\n\n async exists(skillPath: string): Promise<boolean> {\n return this.isMaterialized() ? this.fallback.exists(skillPath) : false;\n }\n\n async stat(skillPath: string): Promise<SkillSourceStat> {\n if (!this.isMaterialized()) throw skillSourceEnoent(skillPath);\n return this.fallback.stat(skillPath);\n }\n\n async readFile(skillPath: string): Promise<string | Buffer> {\n if (!this.isMaterialized()) throw skillSourceEnoent(skillPath);\n return this.fallback.readFile(skillPath);\n }\n\n async readdir(skillPath: string): Promise<SkillSourceEntry[]> {\n return this.isMaterialized() ? this.fallback.readdir(skillPath) : [];\n }\n\n realpath(skillPath: string): Promise<string> {\n if (!this.isMaterialized()) return Promise.resolve(skillPath);\n return this.fallback.realpath ? this.fallback.realpath(skillPath) : Promise.resolve(skillPath);\n }\n}\n\nfunction createFactorySkillExtension(isReviewSession: () => Promise<boolean>): WorkspaceSkillExtension {\n return {\n id: 'web-factory',\n paths: [FACTORY_SKILLS_MOUNT],\n createSource: (fallback, fallbackSkillRoots) =>\n new FactorySkillSource(fallback, fallbackSkillRoots, resolveLocalFactorySkillsPath(), isReviewSession),\n };\n}\n\nfunction isActiveReviewBinding(\n runBinding: { role: string; status: string; orgId: string } | null | undefined,\n orgId: string | undefined,\n): boolean {\n return runBinding?.role === 'review' && runBinding.status === 'active' && !!orgId && runBinding.orgId === orgId;\n}\n\ntype DynamicWorkspaceContext = Parameters<typeof getDynamicWorkspace>[0];\n\n/**\n * When a session's sandbox boots: on the agent's first command (`'lazy'`, the\n * default) or as soon as the session's workspace is first resolved (`'eager'`).\n * An eager start is fire-and-forget; if it fails, the lazy path still runs.\n */\nexport type FactorySandboxStart = 'lazy' | 'eager';\n\nexport interface WorkspaceSourceControlProvider {\n /** Stable integration id used to select the provider-owned storage partition. */\n id: string;\n /** Provider capability used to resolve fresh repository credentials. */\n versionControl: Pick<VersionControl, 'getRepositoryAccess'>;\n /** Provider-owned source-control rows, including Factory sessions. */\n storage: SourceControlStorageHandle;\n /** Present only for GitHub, whose CLI PAT rotation has additional semantics. */\n github?: GithubIntegration;\n}\n\nexport interface CreateWorkspaceFactoryOptions {\n /** Factory sandbox runtime config (session sandbox callback). */\n sandbox?: MastraFactorySandboxConfig;\n /** Defaults to `'lazy'`. */\n sandboxStart?: FactorySandboxStart;\n /** Source-control providers whose repositories may back Factory sessions. */\n sourceControls?: WorkspaceSourceControlProvider[];\n /** @deprecated GitHub-only compatibility path; prefer sourceControls. */\n github?: GithubIntegration;\n /** Work-items storage used to resolve the session's run-binding role, so\n * review-board sessions get the reviewer PAT as `GH_TOKEN`. Optional —\n * without it every session uses the default (worker) PAT. */\n workItems?: Pick<WorkItemsStorage, 'findActiveRunBindingForSession'>;\n /** Projects storage used to authorize workspace-free supervisor sessions. */\n projects?: Pick<FactoryProjectsStorage, 'get'>;\n /** Runtime workspace/token registrations invalidated when a session retires. */\n workspaceRegistry?: FactoryWorkspaceRegistry;\n}\n\ntype WorkspaceUnregister = () => Promise<void> | void;\n\n/** Tracks dynamic Factory workspaces by persisted session id for retirement. */\nexport class FactoryWorkspaceRegistry {\n readonly #entries = new Map<string, Map<string, WorkspaceUnregister>>();\n readonly #generations = new Map<string, number>();\n\n generation(sessionId: string): number {\n return this.#generations.get(sessionId) ?? 0;\n }\n\n async register(\n sessionId: string,\n workspaceId: string,\n generation: number,\n unregister: WorkspaceUnregister,\n ): Promise<boolean> {\n if (generation !== this.generation(sessionId)) {\n await unregister();\n return false;\n }\n const entries = this.#entries.get(sessionId) ?? new Map<string, WorkspaceUnregister>();\n entries.set(workspaceId, unregister);\n this.#entries.set(sessionId, entries);\n return true;\n }\n\n async invalidateSession(sessionId: string): Promise<void> {\n this.#generations.set(sessionId, this.generation(sessionId) + 1);\n const entries = this.#entries.get(sessionId);\n if (!entries) return;\n this.#entries.delete(sessionId);\n const results = await Promise.allSettled([...entries.values()].map(unregister => unregister()));\n const failure = results.find(result => result.status === 'rejected');\n if (failure?.status === 'rejected') throw failure.reason;\n }\n}\n\nasync function resolveSourceControlSession(\n providers: WorkspaceSourceControlProvider[],\n sessionId: string,\n): Promise<{ provider: WorkspaceSourceControlProvider; session: SourceControlSession } | null> {\n const matches = (\n await Promise.all(\n providers.map(async provider => ({\n provider,\n session: await provider.storage.sessions.getBySessionId(sessionId),\n })),\n )\n ).filter(\n (match): match is { provider: WorkspaceSourceControlProvider; session: SourceControlSession } =>\n match.session !== null,\n );\n if (matches.length > 1) {\n throw new Error(`Factory session ${sessionId} is ambiguous across source-control providers`);\n }\n return matches[0] ?? null;\n}\n\nexport function createWorkspaceFactory(options: CreateWorkspaceFactoryOptions = {}) {\n const { sandbox: sandboxConfig, github, projects, workItems } = options;\n const sourceControls: WorkspaceSourceControlProvider[] =\n options.sourceControls ??\n (github\n ? [{ id: github.id, versionControl: github.versionControl, storage: github.sourceControlStorage, github }]\n : []);\n const eagerSandboxStart = options.sandboxStart === 'eager';\n const workspaceRegistry = options.workspaceRegistry ?? new FactoryWorkspaceRegistry();\n type GithubTokenRegistration = {\n inject: (token: string) => void;\n patKind: GithubPatKind;\n ghToken: string;\n generation: number;\n tokenReplacementPending: boolean;\n };\n // The session setup path runs commands and installs credentials, so it\n // needs `executeCommand` (required by `ExecutableSandbox`) plus core's\n // optional `setEnv`, which stays optional here because the token-refresh\n // path checks for it and reports its absence.\n type SessionSandbox = ExecutableSandbox & { setEnv?: WorkspaceSandbox['setEnv'] };\n const githubTokenInjectors = new Map<string, GithubTokenRegistration>();\n const githubTokenReconciliations = new Map<string, Promise<void>>();\n // Workspace identity cache: concurrent resolutions of the same session must\n // observe the same Workspace object even when no Mastra registry is wired\n // (the registry stays the source of truth when present).\n const constructedWorkspaces = new Map<string, Workspace>();\n // Review-skill visibility last used to populate each workspace's skill cache.\n const skillCacheReviewState = new Map<string, boolean>();\n const skillCacheRefreshes = new Map<string, Promise<void>>();\n // The review check of the latest request to resolve each workspace. The skill\n // source outlives the request that built it, so it must gate on this rather\n // than on the context it was constructed with.\n const skillReviewChecks = new Map<string, () => Promise<boolean>>();\n\n return async ({ requestContext, mastra, skillExtension }: DynamicWorkspaceContext) => {\n const ctx = requestContext.get('controller') as AgentControllerRequestContext<MastraCodeState> | undefined;\n const supervisorProjectId = parseSupervisorResourceId(ctx?.resourceId);\n if (supervisorProjectId) {\n const orgId = getFactoryAuthOrgId(getFactoryAuthUserFromContext(requestContext));\n const project = orgId && projects ? await projects.get({ orgId, id: supervisorProjectId }) : null;\n if (!project) throw new Error(`Factory supervisor ${supervisorProjectId} is not available to the current user`);\n return undefined;\n }\n const resolvedSession = ctx?.resourceId ? await resolveSourceControlSession(sourceControls, ctx.resourceId) : null;\n const session = resolvedSession?.session ?? null;\n const sourceControl = resolvedSession?.provider;\n\n if (!session || !sourceControl) {\n // No factory session, no workspace. Chat still works; workspace tools\n // are simply not registered. Host-cwd behavior is opt-in via a\n // LocalSandbox callback rooted wherever the deployer wants — the\n // resolver never hands out the server host's own filesystem.\n return undefined;\n }\n\n const user = getFactoryAuthUserFromContext(requestContext);\n const userId = getFactoryAuthUserId(user);\n // No identity at all is a server-side caller that forgot to seed one\n // (webhook, cron), not someone reaching for another user's session.\n if (!user?.organizationId || !userId) {\n throw new Error(`Factory session ${session.sessionId} was resolved without a caller identity`);\n }\n // Org-visible sessions open to any member of the owning organization;\n // only private sessions stay owner-only. Cross-org access never passes.\n if (user.organizationId !== session.orgId || (session.visibility === 'private' && userId !== session.userId)) {\n throw new Error(`Factory session ${session.sessionId} is not available to the current user`);\n }\n if (!sandboxConfig) {\n throw new Error('A sandbox callback is required to create a Factory session workspace');\n }\n const createSessionSandboxInstance = sandboxConfig;\n const githubProvider = sourceControl.github;\n\n const storage = sourceControl.storage;\n const projectRepository = await storage.projectRepositories.get({\n orgId: session.orgId,\n id: session.projectRepositoryId,\n });\n if (!projectRepository) throw new Error(`Repository link ${session.projectRepositoryId} was not found`);\n // The remaining reads only depend on the repository link — issue them in\n // parallel instead of paying four sequential storage round-trips.\n const [connection, repository] = await Promise.all([\n storage.connections.get({ orgId: session.orgId, id: projectRepository.connectionId }),\n storage.repositories.get({ orgId: session.orgId, id: projectRepository.repositoryId }),\n ]);\n if (!connection || !repository) throw new Error(`Repository link ${session.projectRepositoryId} is incomplete`);\n const installation = await storage.installations.get({ orgId: session.orgId, id: connection.installationId });\n if (!installation) {\n throw new Error(`${sourceControl.id} installation ${connection.installationId} was not found`);\n }\n const repoFullName = repository.slug;\n\n // Construct (or fetch) the session's memoized sandbox instance.\n // Construction is cheap and side-effect-free by the callback contract —\n // the VM is provisioned on `start()`, which only the materialization\n // pipeline calls. The workdir is never persisted or trusted from storage\n // or client input (the stale-workdir incident class came from reusing\n // `session.sandboxWorkdir` written under a different provider): local\n // sandboxes derive it at construction, remote sandboxes clone into the\n // VM's own home so it resolves lazily at first start.\n // `runSetupOn` references `runSessionSetup`, defined below — it is only\n // invoked during start, long after this closure fully initializes.\n const runSetupOn = (target: unknown, workdir: string, gate: SessionSetupGate) =>\n runSessionSetup(requireExec(target as WorkspaceSandbox), workdir, gate);\n const guardedSetup = createSessionSetupHook(\n runSetupOn,\n session.id,\n repoFullName,\n projectRepository.setupCommand ?? undefined,\n );\n // Composed start hook: marker-guarded repo setup, then per-start\n // credential install. It runs inside the provider's start lifecycle on\n // EVERY start (create or reconnect) — providers own lazy start\n // (`ensureRunning()` on first command) and dead-VM self-healing (E2B\n // `retryOnDead`, Platform status reset on destroy), so a replacement VM\n // re-enters this hook and heals itself with credentials at least as\n // fresh as the start that installed them.\n const setupHook: SandboxStartHook = async args => {\n // A session retired before its first start must not set anything up.\n if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) {\n throw retiredError();\n }\n await guardedSetup(args);\n // Re-check after the (long) setup: a session retired mid-setup must not\n // register credentials for a workspace whose retirement teardown has\n // already run — the entry would leak forever. The VM itself is left to\n // the provider's idle timeout (accepted).\n if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) {\n throw retiredError();\n }\n const target: SessionSandbox = requireExec(args.sandbox);\n // Observability plus the post-checkout skill rescan run on every start\n // (create or reconnect). Observability only — nothing reads these columns\n // for decisions; the workdir was resolved (and memoized on the entry) by\n // the guarded setup. The skill roots were reported empty by the\n // unmaterialized-source guard before the checkout existed, so rescan now.\n const publishStartSideEffects = () => {\n void storage.sessions\n .setSandbox({ id: session.id, sandboxId: target.id, sandboxWorkdir: sessionEntry.workdir ?? '' })\n .catch(() => {});\n void constructedWorkspaces\n .get(workspaceId)\n ?.skills?.refresh()\n .catch(() => {});\n };\n if (!githubProvider) {\n publishStartSideEffects();\n return;\n }\n const existingRegistration = githubTokenInjectors.get(workspaceId);\n if (existingRegistration) {\n // Reconnect: re-point the current registration's injection target at\n // this (possibly provider-healed) sandbox and reinstall its\n // reconcile-owned credential. Do NOT mint a new registration, bump\n // authority, or re-register the constructing request context —\n // reconcileGithubToken owns role/generation and the active context's\n // injector, so replacing them here would reauthorize the stale\n // constructing context and reject the current one.\n existingRegistration.inject = freshToken => {\n if (!target.setEnv) {\n throw new Error('The active sandbox provider does not support runtime GitHub token refresh.');\n }\n target.setEnv(env => ({ ...env, GH_TOKEN: freshToken }));\n existingRegistration.ghToken = freshToken;\n };\n // Install through inject (not optional-chained setEnv) so a provider\n // that cannot accept the credential fails the reconnect here instead of\n // deferring the failure to a later token refresh.\n existingRegistration.inject(existingRegistration.ghToken);\n publishStartSideEffects();\n return;\n }\n // First start: resolve the credential and authorize the constructing\n // request context. The `gh` CLI needs a PAT when the org configured one\n // (installation tokens 403 on integration-restricted endpoints); git\n // clone/checkout keep using the minted installation token. Resolved per\n // start so the installed credential never outlives rotation.\n const patKind = await resolveGithubPatKind('default');\n const ghCliToken =\n (await getGithubPat(() => githubProvider.integrationStorage, session.orgId, patKind)) ??\n (await getRepositoryToken());\n target.setEnv?.(env => ({ ...env, GH_TOKEN: ghCliToken }));\n const tokenRegistration: GithubTokenRegistration = {\n inject: freshToken => {\n if (!target.setEnv) {\n throw new Error('The active sandbox provider does not support runtime GitHub token refresh.');\n }\n target.setEnv(env => ({ ...env, GH_TOKEN: freshToken }));\n tokenRegistration.ghToken = freshToken;\n },\n patKind,\n ghToken: ghCliToken,\n generation: 0,\n tokenReplacementPending: false,\n };\n githubTokenInjectors.set(workspaceId, tokenRegistration);\n registerGithubTokenContext(tokenRegistration);\n publishStartSideEffects();\n };\n const constructSessionEntry = () =>\n getSessionSandbox(session.id, repoFullName, () => {\n const sandbox = createSessionSandboxInstance({\n sessionId: session.id,\n repoFullName,\n // Stored nullable; the context speaks `undefined` for absent.\n setupCommand: projectRepository.setupCommand ?? undefined,\n // Deferred call — only dereferenced when a provider needs the repo\n // outside the VM (template build time).\n getRepositoryAccess: () =>\n sourceControl.versionControl.getRepositoryAccess({\n orgId: session.orgId,\n repositoryId: repository.id,\n }),\n });\n // Attached inside the construction closure, so exactly once per\n // instance — `constructSessionEntry` runs on every open and would\n // stack a wrapper per call. Factory's setup runs first: a hook the\n // callback installed itself expects a prepared workspace.\n sandbox.setOnStart(previous => async args => {\n await timedPhase(`workspace.onStart(${args.outcome})`, async () => {\n await setupHook(args);\n });\n await previous?.(args);\n });\n // Only a freshly constructed instance starts eagerly; the start itself\n // waits for this resolver to finish because the start hook reads\n // bindings declared further down.\n startEagerly = eagerSandboxStart;\n return sandbox;\n });\n let startEagerly = false;\n const fireEagerStart = () => {\n if (!startEagerly) return;\n startEagerly = false;\n Promise.resolve()\n .then(() => sessionEntry.sandbox.start?.())\n .catch(error => {\n console.warn(`[factory] Eager sandbox start for session ${session.id} failed:`, error);\n });\n };\n const sessionEntry = constructSessionEntry();\n const workdir = sessionEntry.workdir;\n const isLocalSandbox = sessionEntry.sandbox.provider === 'local';\n // The SDK system prompt uses `state.projectPath` without falling back to\n // the server cwd. Pin the session workdir once known so the prompt and\n // workspace tools describe the same checkout. A remote workdir resolves at\n // the sandbox's first start, so the pin self-heals on the next resolution.\n if (ctx && workdir && ctx.getState()?.projectPath !== workdir) {\n await ctx.setState({ projectPath: workdir, projectName: repoFullName });\n }\n\n // Keyed by the session, not the request's thread: a workspace built for a\n // dispatcher kickoff has no live thread yet, but the session's binding\n // already says which role it is serving. When the request does carry a\n // thread, the binding must belong to it, so a stale thread reusing this\n // session never inherits another run's role.\n const findSessionBinding = async () => {\n const binding = await workItems!.findActiveRunBindingForSession({\n orgId: session.orgId,\n factoryProjectId: connection.factoryProjectId,\n sessionId: session.sessionId,\n });\n if (!binding || !ctx?.threadId) return binding;\n return binding.threadId === ctx.threadId && binding.resourceId === ctx.resourceId ? binding : null;\n };\n // Fails closed: without a readable active review binding, review skills stay hidden.\n const isReviewSession = async (): Promise<boolean> => {\n if (!workItems) return false;\n try {\n return isActiveReviewBinding(await findSessionBinding(), session.orgId);\n } catch {\n return false;\n }\n };\n const effectiveSkillExtension =\n skillExtension ??\n createFactorySkillExtension((): Promise<boolean> => (skillReviewChecks.get(workspaceId) ?? isReviewSession)());\n const extensionId = effectiveSkillExtension ? `-${effectiveSkillExtension.id}` : '';\n const workspaceId: string = `${WORKSPACE_ID_PREFIX}-${projectRepository.id}-${session.id}${extensionId}`;\n const workspaceGeneration = workspaceRegistry.generation(session.sessionId);\n if (!skillExtension) skillReviewChecks.set(workspaceId, isReviewSession);\n const configDir = DEFAULT_CONFIG_DIR;\n\n const getRepositoryAccess = () =>\n sourceControl.versionControl.getRepositoryAccess({\n orgId: session.orgId,\n repositoryId: repository.id,\n });\n const getRepositoryToken = async (): Promise<string> => {\n const token = (await getRepositoryAccess()).authorization?.token;\n if (!token) throw new Error('Repository access did not include a bearer token for the Factory session');\n return token;\n };\n const resolveGithubPatKind = async (fallback: GithubPatKind): Promise<GithubPatKind> => {\n if (!workItems) return 'default';\n try {\n return isActiveReviewBinding(await findSessionBinding(), session.orgId) ? 'reviewer' : 'default';\n } catch {\n // Preserve the installed role when binding storage is temporarily unavailable.\n return fallback;\n }\n };\n const registerGithubTokenContext = (registered: GithubTokenRegistration): void => {\n const generation = registered.generation;\n registerGithubTokenInjector(requestContext, token => {\n if (githubTokenInjectors.get(workspaceId) !== registered || registered.generation !== generation) {\n throw new Error('GitHub token refresh no longer matches the active Factory workspace role.');\n }\n registered.inject(token);\n });\n registerGithubPatKind(requestContext, registered.patKind);\n };\n const reconcileGithubToken = async (): Promise<void> => {\n if (!githubProvider) return;\n const previous = githubTokenReconciliations.get(workspaceId) ?? Promise.resolve();\n const reconciliation = previous\n .catch(() => {})\n .then(async () => {\n const registered = githubTokenInjectors.get(workspaceId);\n if (!registered) return;\n\n const previousPatKind = registered.patKind;\n const patKind = await resolveGithubPatKind(previousPatKind);\n if (githubTokenInjectors.get(workspaceId) !== registered) return;\n\n if (patKind !== previousPatKind) {\n registered.patKind = patKind;\n registered.generation += 1;\n }\n if (patKind === 'reviewer') registered.tokenReplacementPending = false;\n if (previousPatKind === 'reviewer' && patKind === 'default') {\n // Invalidate reviewer refresh contexts before replacement I/O so\n // they cannot restore reviewer credentials after a failed downgrade.\n registered.tokenReplacementPending = true;\n }\n\n let token = await getGithubPat(() => githubProvider.integrationStorage, session.orgId, patKind);\n if (!token && registered.tokenReplacementPending) token = await getRepositoryToken();\n if (githubTokenInjectors.get(workspaceId) !== registered) return;\n\n if (token && token !== registered.ghToken) {\n try {\n registered.inject(token);\n } catch (error) {\n if (registered.tokenReplacementPending) throw error;\n // Same-role rotations and reviewer upgrades remain best-effort.\n }\n }\n if (token && token === registered.ghToken) registered.tokenReplacementPending = false;\n registerGithubTokenContext(registered);\n });\n githubTokenReconciliations.set(workspaceId, reconciliation);\n try {\n await reconciliation;\n } finally {\n if (githubTokenReconciliations.get(workspaceId) === reconciliation) {\n githubTokenReconciliations.delete(workspaceId);\n }\n }\n };\n const reconcileRegisteredWorkspace = async (workspace: Workspace): Promise<Workspace> => {\n const registered = githubTokenInjectors.get(workspaceId);\n try {\n await reconcileGithubToken();\n } catch (error) {\n if (registered?.tokenReplacementPending && githubTokenInjectors.get(workspaceId) === registered) {\n // The role generation already invalidated reviewer refresh contexts.\n // Keep the pending registration so failed eviction cannot make a\n // still-live reviewer workspace look safe on the next reuse.\n let evicted = false;\n try {\n evicted = (await mastra?.removeWorkspace?.(workspaceId)) === true;\n } catch {\n // Preserve the credential-replacement error and retry on the next reuse.\n }\n try {\n await workspace.destroy();\n evicted = true;\n } catch {\n // The pending registration keeps the workspace quarantined if cleanup also fails.\n }\n if (evicted && githubTokenInjectors.get(workspaceId) === registered) {\n githubTokenInjectors.delete(workspaceId);\n constructedWorkspaces.delete(workspaceId);\n skillCacheReviewState.delete(workspaceId);\n }\n }\n throw error;\n }\n if (registered && githubTokenInjectors.get(workspaceId) !== registered) {\n throw new Error('Factory workspace GitHub credential registration is no longer active.');\n }\n return workspace;\n };\n\n let existing: Workspace | undefined;\n try {\n existing = mastra?.getWorkspaceById(workspaceId) as Workspace | undefined;\n } catch {\n // Not registered yet.\n existing = undefined;\n }\n existing ??= constructedWorkspaces.get(workspaceId);\n if (existing) {\n existing.setToolsConfig(MASTRACODE_WORKSPACE_TOOLS);\n // The skill cache does not recheck the source on get(), so rescan when the\n // session's role flips; otherwise a cached review skill outlives the review binding.\n if (!skillExtension) {\n // An outside rescan makes the recorded role untrustworthy. Wait for it to\n // settle first, so the refresh below starts a scan for the current role\n // rather than joining one started for the previous role.\n const consumeOutsideRescan = async () => {\n const outside = existing.skills && rescannedOutsideResolver.get(existing.skills);\n if (!outside) return false;\n await outside;\n if (rescannedOutsideResolver.get(existing.skills!) === outside)\n rescannedOutsideResolver.delete(existing.skills!);\n skillCacheReviewState.delete(workspaceId);\n return true;\n };\n await consumeOutsideRescan();\n let isReview = await isReviewSession();\n // Concurrent reuses share one refresh, and the state is recorded only\n // once the rescan succeeds. Wait out any in-flight rescan (it may have\n // been started for the opposite role) and re-read the role afterwards.\n for (;;) {\n const inFlight = skillCacheRefreshes.get(workspaceId);\n if (inFlight) {\n await inFlight;\n isReview = await isReviewSession();\n continue;\n }\n if (await consumeOutsideRescan()) {\n isReview = await isReviewSession();\n continue;\n }\n if (skillCacheReviewState.get(workspaceId) === isReview) break;\n const target = isReview;\n const pending = (async () => {\n await existing.skills?.refresh();\n skillCacheReviewState.set(workspaceId, target);\n })().finally(() => skillCacheRefreshes.delete(workspaceId));\n skillCacheRefreshes.set(workspaceId, pending);\n await pending;\n isReview = await isReviewSession();\n }\n }\n // A materialization kicked off by another caller may still be running.\n // Deliberately do NOT wait for it: a metadata-only resolution (thread\n // list, messages, activity) must not block on the clone/setup that lazy\n // materialization exists to avoid. Token reconciliation below no-ops\n // until the leader registers the injector, and the next reuse after\n // materialization completes reconciles against the live sandbox.\n return reconcileRegisteredWorkspace(existing);\n }\n\n const retiredError = () =>\n new Error(`Factory session ${session.sessionId} was retired during workspace materialization`);\n\n // The session's setup work: materialize the repo (disk-truth idempotent),\n // check out the session branch, run the configured setup command. Minted\n // tokens are fetched inside the run so a replacement VM healed mid-session\n // gets fresh credentials, not ones captured at workspace construction.\n const runSessionSetup = async (target: SessionSandbox, workdir: string, gate: SessionSetupGate): Promise<void> => {\n const access = await getRepositoryAccess();\n const token = access.authorization?.token;\n if (!token) throw new Error('Repository access did not include a bearer token for the Factory session');\n // GitHub setup commands may shell out to gh/HTTPS. Preserve its\n // role-aware PAT behavior without applying GitHub credentials to other\n // source-control providers.\n if (githubProvider) {\n const setupPatKind = await resolveGithubPatKind('default');\n const setupGhToken =\n (await getGithubPat(() => githubProvider.integrationStorage, session.orgId, setupPatKind)) ?? token;\n target.setEnv?.(env => ({ ...env, GH_TOKEN: setupGhToken }));\n }\n await materializeRepo({\n row: { id: session.id, sandboxWorkdir: workdir, materializedAt: session.materializedAt },\n repoInfo: {\n repoFullName,\n defaultBranch: repository.defaultBranch,\n cloneUrl: access.cloneUrl,\n authUsername: access.authorization?.username,\n },\n sandbox: target,\n token,\n storage: storage.sessions,\n });\n await checkoutSessionBranch(target, workdir, {\n branch: session.branch,\n baseBranch: session.baseBranch || projectRepository.branch || repository.defaultBranch,\n token,\n repoFullName,\n cloneUrl: access.cloneUrl,\n authUsername: access.authorization?.username,\n pullRequestNumber: pullRequestNumberFromBranch(session.branch),\n mergeRequestNumber: sourceControl.id === 'gitlab' ? mergeRequestNumberFromBranch(session.branch) : undefined,\n });\n if (projectRepository.setupCommand && !gate.setupDone) {\n // A setup command that already failed this session is skipped rather\n // than failing every start: the first failure surfaced loudly in the\n // tool result that triggered it, and a permanently failing onStart\n // would wedge the session — the agent could never get a shell to fix\n // the problem. Clone and checkout above still ran, so the tree is\n // real; the agent (or an edited setup command) takes it from here.\n if (hasFailedSetupCommand(session.id, projectRepository.setupCommand)) {\n console.warn('[Mastra Factory] Skipping setup command that already failed this session', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n });\n return;\n }\n try {\n await timedPhase('workspace.setup', () => runSetupCommand(target, workdir, projectRepository.setupCommand!));\n await gate.markSetupDone();\n } catch (setupError) {\n if (projectRepository.teardownCommand) {\n try {\n await runTeardownCommand(target, workdir, projectRepository.teardownCommand, {\n timeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,\n });\n } catch (teardownError) {\n console.warn('[Mastra Factory] Worktree teardown after setup failure failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: teardownError instanceof Error ? teardownError.message.slice(-2000) : String(teardownError),\n });\n }\n }\n if (setupError instanceof SetupCommandError) {\n // The command ran and exited non-zero — a config problem, not an\n // infra one. Remember it so the next start recovers, and tell the\n // agent what happens next. Infra failures (transport, clone)\n // rethrow untouched and retry in full.\n recordFailedSetupCommand(session.id, projectRepository.setupCommand);\n throw new SetupCommandError(\n `${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.`,\n setupError.code,\n );\n }\n throw setupError;\n }\n }\n };\n // The session's real sandbox goes straight onto the Workspace. Providers\n // own lazy start (`ensureRunning()` inside the first command/process op)\n // and dead-VM self-healing, and the composed `onStart` hook runs the repo\n // setup + credential install inside that lifecycle. Metadata-only\n // resolutions (thread-list polling) construct but never start.\n const sessionSandbox: SessionSandbox = requireExec(sessionEntry.sandbox);\n\n const filesystem = new SandboxFilesystem({\n id: `sandbox-fs:${workspaceId}`,\n sandbox: sessionSandbox,\n // Lazy: a remote workdir is only knowable once a VM runs. The first\n // file operation resolves it (starting the VM — which materializes the\n // repo via the onStart hook — when needed) and memoizes it.\n workdir: () => resolveSessionWorkdir(session.id, sessionEntry.sandbox, repoFullName),\n });\n const projectSkillPaths = [path.join(configDir, 'skills'), '.claude/skills', '.agents/skills'];\n const guardedSkillFallback = new UnmaterializedAwareSkillSource(\n filesystem,\n () => sessionEntry.sandbox.status === 'running',\n );\n const skillPaths = [...(effectiveSkillExtension?.paths ?? []), ...projectSkillPaths];\n const workspace = new Workspace({\n id: workspaceId,\n name: 'Mastra Code Factory Session Workspace',\n filesystem,\n sandbox: sessionSandbox as unknown as ConstructorParameters<typeof Workspace>[0]['sandbox'],\n tools: MASTRACODE_WORKSPACE_TOOLS,\n skills: skillPaths,\n // Project skill roots live in the sandbox checkout; guard them so skill\n // discovery before materialization (e.g. kickoff skill resolution in the\n // start coordinator) never forces sandbox provisioning.\n skillSource:\n effectiveSkillExtension?.createSource(guardedSkillFallback, projectSkillPaths) ?? guardedSkillFallback,\n });\n // Register with the Mastra instance so sync HTTP handlers that resolve\n // the workspace via `mastra.getWorkspaceById(id)` (file tree, permissions\n // probe, MCP/tool routes) find it instead of throwing\n // `MASTRA_GET_WORKSPACE_BY_ID_NOT_FOUND`. `addWorkspace` is idempotent on\n // key collision, so concurrent first resolutions stay race-safe (start\n // itself is coalesced by the sandbox base class + the session memo).\n mastra?.addWorkspace(workspace, workspaceId, { source: 'mastra' });\n // Cache synchronously with construction: the `await` below is a suspension\n // point, and a concurrent resolution for the same session must observe this\n // workspace rather than build a second one.\n constructedWorkspaces.set(workspaceId, workspace);\n // Retirement is registered against the workspace itself rather than the\n // sandbox: construction is eager while the VM start is lazy, so a session\n // retired before its first tool call still has a workspace (and possibly a\n // token injector) that must be torn down.\n const registered = await workspaceRegistry.register(\n session.sessionId,\n workspaceId,\n workspaceGeneration,\n async () => {\n githubTokenInjectors.delete(workspaceId);\n constructedWorkspaces.delete(workspaceId);\n skillCacheReviewState.delete(workspaceId);\n skillReviewChecks.delete(workspaceId);\n // Retirement drops the memoized session sandbox so a later re-open\n // constructs (and the provider resolves) fresh instead of reusing an\n // instance whose VM the retirement path may stop or destroy.\n evictSessionSandbox(session.id);\n await mastra?.removeWorkspace?.(workspaceId);\n },\n );\n if (!registered) {\n throw new Error(`Factory session ${session.sessionId} was retired during workspace materialization`);\n }\n\n fireEagerStart();\n return workspace;\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"workspace.js","names":["#bundledSource","#localSource","#fallbackSkillRoots","#isHiddenReviewPath","#layerFor","#isFactoryPath","#factoryPath","#entries","#generations"],"sources":["../src/workspace.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport path, { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { SandboxFilesystem } from '@mastra/code-sdk/agents/sandbox-filesystem';\nimport { MASTRACODE_WORKSPACE_TOOLS } from '@mastra/code-sdk/agents/tool-availability';\nimport type { getDynamicWorkspace, WorkspaceSkillExtension } from '@mastra/code-sdk/agents/workspace';\nimport { DEFAULT_CONFIG_DIR } from '@mastra/code-sdk/constants';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport { LocalSkillSource, Workspace } from '@mastra/core/workspace';\nimport type {\n SandboxStartHook,\n SkillSource,\n SkillSourceEntry,\n SkillSourceStat,\n WorkspaceSandbox,\n} from '@mastra/core/workspace';\nimport { getFactoryAuthOrgId, getFactoryAuthUserFromContext, getFactoryAuthUserId } from './auth.js';\nimport type { VersionControl } from './capabilities/version-control.js';\nimport type { MastraFactorySandboxConfig } from './factory.js';\nimport type { GithubIntegration } from './integrations/github/integration.js';\nimport { getGithubPat } from './integrations/github/pat.js';\nimport type { GithubPatKind } from './integrations/github/pat.js';\nimport {\n checkoutSessionBranch,\n DEFAULT_COMMAND_TIMEOUT_MS,\n materializeRepo,\n runSetupCommand,\n runTeardownCommand,\n SetupCommandError,\n} from './integrations/github/sandbox.js';\nimport { registerGithubPatKind, registerGithubTokenInjector } from './integrations/github/token-refresh.js';\nimport { requireExec } from './sandbox/materialization.js';\nimport type { ExecutableSandbox } from './sandbox/materialization.js';\nimport {\n createSessionSetupHook,\n evictSessionSandbox,\n getSessionSandbox,\n hasFailedSetupCommand,\n recordFailedSetupCommand,\n resolveSessionWorkdir,\n} from './sandbox/session-sandbox.js';\nimport type { SessionSetupGate } from './sandbox/session-sandbox.js';\nimport type { FactoryProjectsStorage } from './storage/domains/projects/base.js';\nimport type { SourceControlSession, SourceControlStorageHandle } from './storage/domains/source-control/base.js';\nimport type { WorkItemsStorage } from './storage/domains/work-items/base.js';\nimport { parseSupervisorResourceId } from './supervisor/session.js';\nimport { timedPhase } from './timing.js';\nimport { mergeRequestNumberFromBranch, pullRequestNumberFromBranch } from './work-item-branch.js';\n\nconst WORKSPACE_ID_PREFIX = 'mfw';\nconst bundleDirectory = dirname(fileURLToPath(import.meta.url));\nconst bundledFactorySkillsPath = join(bundleDirectory, 'factory-skills');\nexport const BUNDLED_FACTORY_SKILLS_PATH =\n [\n // Deploy bundle: the consumer copies `factory-skills/` next to the built\n // server module (e.g. via its public/ dir).\n bundledFactorySkillsPath,\n // Package layout: `dist/../factory-skills` (also `src/../factory-skills`\n // when running tests against sources).\n join(bundleDirectory, '..', 'factory-skills'),\n ].find(existsSync) ?? bundledFactorySkillsPath;\n\n/**\n * Resolve the consumer repo's local Factory skills root, if any. Checked in\n * addition to the bundled skills so projects can add (or override) Factory\n * skills without patching the installed package. Candidates cover the cwd\n * variants the dev server runs with (`repo root`, `--dir src/mastra` which\n * runs with cwd `src/mastra/public`).\n */\nexport function resolveLocalFactorySkillsPath(cwd: string = process.cwd()): string | undefined {\n const candidates = [\n join(cwd, 'src', 'mastra', 'public', 'factory-skills'),\n join(cwd, 'public', 'factory-skills'),\n join(cwd, 'factory-skills'),\n ];\n return candidates.find(\n candidate => path.normalize(candidate) !== path.normalize(BUNDLED_FACTORY_SKILLS_PATH) && existsSync(candidate),\n );\n}\nconst FACTORY_SKILLS_MOUNT = path.resolve(path.parse(process.cwd()).root, '__mastracode_factory_skills__');\nexport const FACTORY_SKILL_NAMES = new Set([\n 'configure-factory-rules',\n 'factory-complete-issue',\n 'factory-gitlab-rereview',\n 'factory-gitlab-review',\n 'factory-plan',\n 'factory-rereview',\n 'factory-review',\n 'factory-triage',\n]);\n\n/** Review skills only sessions with an active review run binding may discover or load. */\nexport const REVIEW_ONLY_FACTORY_SKILLS = new Set([\n 'factory-gitlab-rereview',\n 'factory-gitlab-review',\n 'factory-rereview',\n 'factory-review',\n]);\n\n/**\n * Skill caches rescanned outside the workspace resolver. The resolver records\n * which role each cache was last scanned for; a rescan it did not perform makes\n * that record untrustworthy, so the next reuse must rescan again.\n */\nconst rescannedOutsideResolver = new WeakMap<object, Promise<void>>();\n\n/**\n * Rescan a session's skill cache so a role gained after the cache was built\n * (a review binding minted after the session's workspace resolved) takes effect.\n * The source still checks the live binding, so this never widens access.\n */\nexport async function rescanFactorySkills(skills: NonNullable<Workspace['skills']>): Promise<void> {\n // Registered so the resolver waits it out: a refresh started meanwhile would\n // join this scan (possibly for the old role) instead of starting a fresh one.\n const pending = skills.refresh();\n rescannedOutsideResolver.set(\n skills,\n pending.catch(() => {}),\n );\n await pending;\n}\n\nexport class FactorySkillSource implements SkillSource {\n readonly #bundledSource = new LocalSkillSource({ basePath: BUNDLED_FACTORY_SKILLS_PATH });\n readonly #localSource: LocalSkillSource | undefined;\n readonly #fallbackSkillRoots: Set<string>;\n\n constructor(\n readonly fallback: SkillSource,\n fallbackSkillRoots: string[],\n localSkillsPath: string | undefined = resolveLocalFactorySkillsPath(),\n /** Evaluated per call because a session's bound role changes across stages. */\n readonly isReviewSession: () => Promise<boolean> = () => Promise.resolve(true),\n ) {\n this.#localSource = localSkillsPath ? new LocalSkillSource({ basePath: localSkillsPath }) : undefined;\n this.#fallbackSkillRoots = new Set(fallbackSkillRoots.map(skillPath => path.normalize(skillPath)));\n }\n\n #isFactoryPath(skillPath: string): boolean {\n const normalized = path.normalize(skillPath);\n return normalized === FACTORY_SKILLS_MOUNT || normalized.startsWith(`${FACTORY_SKILLS_MOUNT}${path.sep}`);\n }\n\n #factoryPath(skillPath: string): string {\n return path.relative(FACTORY_SKILLS_MOUNT, path.normalize(skillPath));\n }\n\n /** True when the path is inside a review-only skill that this session may not use. */\n async #isHiddenReviewPath(relativePath: string): Promise<boolean> {\n const skillName = relativePath.split(path.sep)[0];\n if (!skillName || !REVIEW_ONLY_FACTORY_SKILLS.has(skillName)) return false;\n return !(await this.isReviewSession());\n }\n\n /** Pick the layer serving this mount-relative path: local wins when it has the entry. */\n async #layerFor(relativePath: string): Promise<LocalSkillSource> {\n if (this.#localSource && (await this.#localSource.exists(relativePath))) return this.#localSource;\n return this.#bundledSource;\n }\n\n async exists(skillPath: string): Promise<boolean> {\n if (!this.#isFactoryPath(skillPath)) return this.fallback.exists(skillPath);\n const relative = this.#factoryPath(skillPath);\n if (await this.#isHiddenReviewPath(relative)) return false;\n if (this.#localSource && (await this.#localSource.exists(relative))) return true;\n return this.#bundledSource.exists(relative);\n }\n\n async stat(skillPath: string): Promise<SkillSourceStat> {\n if (!this.#isFactoryPath(skillPath)) return this.fallback.stat(skillPath);\n const relative = this.#factoryPath(skillPath);\n if (await this.#isHiddenReviewPath(relative)) throw skillSourceEnoent(skillPath);\n return (await this.#layerFor(relative)).stat(relative);\n }\n\n async readFile(skillPath: string): Promise<string | Buffer> {\n if (!this.#isFactoryPath(skillPath)) return this.fallback.readFile(skillPath);\n const relative = this.#factoryPath(skillPath);\n if (await this.#isHiddenReviewPath(relative)) throw skillSourceEnoent(skillPath);\n return (await this.#layerFor(relative)).readFile(relative);\n }\n\n async readdir(skillPath: string): Promise<SkillSourceEntry[]> {\n if (this.#isFactoryPath(skillPath)) {\n const relative = this.#factoryPath(skillPath);\n if (await this.#isHiddenReviewPath(relative)) throw skillSourceEnoent(skillPath);\n const [bundledExists, localExists] = await Promise.all([\n this.#bundledSource.exists(relative),\n this.#localSource?.exists(relative) ?? Promise.resolve(false),\n ]);\n if (!bundledExists && !localExists) throw skillSourceEnoent(skillPath);\n const [bundledEntries, localEntries] = await Promise.all([\n bundledExists ? this.#bundledSource.readdir(relative) : [],\n localExists ? this.#localSource!.readdir(relative) : [],\n ]);\n const merged = new Map<string, SkillSourceEntry>();\n for (const entry of bundledEntries) merged.set(entry.name, entry);\n // Local entries override bundled names.\n for (const entry of localEntries) merged.set(entry.name, entry);\n const entries = [...merged.values()];\n if (relative !== '' || (await this.isReviewSession())) return entries;\n return entries.filter(entry => !REVIEW_ONLY_FACTORY_SKILLS.has(entry.name));\n }\n const entries = await this.fallback.readdir(skillPath);\n if (this.#fallbackSkillRoots.has(path.normalize(skillPath))) {\n return entries.filter(entry => !FACTORY_SKILL_NAMES.has(entry.name));\n }\n return entries;\n }\n\n realpath(skillPath: string): Promise<string> {\n if (this.#isFactoryPath(skillPath)) return Promise.resolve(path.normalize(skillPath));\n return this.fallback.realpath ? this.fallback.realpath(skillPath) : Promise.resolve(skillPath);\n }\n}\n\n/** Build a Node-style ENOENT error so callers can treat missing skills like fs misses. */\nfunction skillSourceEnoent(skillPath: string): Error {\n const error = new Error(`ENOENT: no such file or directory, '${skillPath}'`) as Error & { code: string };\n error.code = 'ENOENT';\n return error;\n}\n\n/**\n * Sandbox-backed skill fallback that stays inert until the session sandbox is\n * actually materialized. Skill discovery runs on latency-sensitive paths (the\n * Factory start coordinator resolves the kickoff skill before the start route\n * responds); without this guard the first project-root read would hit the lazy\n * sandbox handle and force full provisioning + repo materialization. While the\n * sandbox is unmaterialized, project skill roots simply appear empty — bundled\n * Factory skills resolve from local disk via `FactorySkillSource`. Once the\n * sandbox exists, every call delegates straight through.\n */\nclass UnmaterializedAwareSkillSource implements SkillSource {\n constructor(\n readonly fallback: SkillSource,\n readonly isMaterialized: () => boolean,\n ) {}\n\n async exists(skillPath: string): Promise<boolean> {\n return this.isMaterialized() ? this.fallback.exists(skillPath) : false;\n }\n\n async stat(skillPath: string): Promise<SkillSourceStat> {\n if (!this.isMaterialized()) throw skillSourceEnoent(skillPath);\n return this.fallback.stat(skillPath);\n }\n\n async readFile(skillPath: string): Promise<string | Buffer> {\n if (!this.isMaterialized()) throw skillSourceEnoent(skillPath);\n return this.fallback.readFile(skillPath);\n }\n\n async readdir(skillPath: string): Promise<SkillSourceEntry[]> {\n return this.isMaterialized() ? this.fallback.readdir(skillPath) : [];\n }\n\n realpath(skillPath: string): Promise<string> {\n if (!this.isMaterialized()) return Promise.resolve(skillPath);\n return this.fallback.realpath ? this.fallback.realpath(skillPath) : Promise.resolve(skillPath);\n }\n}\n\nfunction createFactorySkillExtension(isReviewSession: () => Promise<boolean>): WorkspaceSkillExtension {\n return {\n id: 'web-factory',\n paths: [FACTORY_SKILLS_MOUNT],\n createSource: (fallback, fallbackSkillRoots) =>\n new FactorySkillSource(fallback, fallbackSkillRoots, resolveLocalFactorySkillsPath(), isReviewSession),\n };\n}\n\nfunction isActiveReviewBinding(\n runBinding: { role: string; status: string; orgId: string } | null | undefined,\n orgId: string | undefined,\n): boolean {\n return runBinding?.role === 'review' && runBinding.status === 'active' && !!orgId && runBinding.orgId === orgId;\n}\n\ntype DynamicWorkspaceContext = Parameters<typeof getDynamicWorkspace>[0];\n\n/**\n * When a session's sandbox boots: on the agent's first command (`'lazy'`, the\n * default) or as soon as the session's workspace is first resolved (`'eager'`).\n * An eager start is fire-and-forget; if it fails, the lazy path still runs.\n */\nexport type FactorySandboxStart = 'lazy' | 'eager';\n\nexport interface WorkspaceSourceControlProvider {\n /** Stable integration id used to select the provider-owned storage partition. */\n id: string;\n /** Provider capability used to resolve fresh repository credentials. */\n versionControl: Pick<VersionControl, 'getRepositoryAccess'>;\n /** Provider-owned source-control rows, including Factory sessions. */\n storage: SourceControlStorageHandle;\n /** Present only for GitHub, whose CLI PAT rotation has additional semantics. */\n github?: GithubIntegration;\n}\n\nexport interface CreateWorkspaceFactoryOptions {\n /** Factory sandbox runtime config (session sandbox callback). */\n sandbox?: MastraFactorySandboxConfig;\n /** Defaults to `'lazy'`. */\n sandboxStart?: FactorySandboxStart;\n /** Source-control providers whose repositories may back Factory sessions. */\n sourceControls?: WorkspaceSourceControlProvider[];\n /** @deprecated GitHub-only compatibility path; prefer sourceControls. */\n github?: GithubIntegration;\n /** Work-items storage used to resolve the session's run-binding role, so\n * review-board sessions get the reviewer PAT as `GH_TOKEN`. Optional —\n * without it every session uses the default (worker) PAT. */\n workItems?: Pick<WorkItemsStorage, 'findActiveRunBindingForSession'>;\n /** Projects storage used to authorize workspace-free supervisor sessions. */\n projects?: Pick<FactoryProjectsStorage, 'get'>;\n /** Runtime workspace/token registrations invalidated when a session retires. */\n workspaceRegistry?: FactoryWorkspaceRegistry;\n}\n\ntype WorkspaceUnregister = () => Promise<void> | void;\n\n/** Tracks dynamic Factory workspaces by persisted session id for retirement. */\nexport class FactoryWorkspaceRegistry {\n readonly #entries = new Map<string, Map<string, WorkspaceUnregister>>();\n readonly #generations = new Map<string, number>();\n\n generation(sessionId: string): number {\n return this.#generations.get(sessionId) ?? 0;\n }\n\n async register(\n sessionId: string,\n workspaceId: string,\n generation: number,\n unregister: WorkspaceUnregister,\n ): Promise<boolean> {\n if (generation !== this.generation(sessionId)) {\n await unregister();\n return false;\n }\n const entries = this.#entries.get(sessionId) ?? new Map<string, WorkspaceUnregister>();\n entries.set(workspaceId, unregister);\n this.#entries.set(sessionId, entries);\n return true;\n }\n\n async invalidateSession(sessionId: string): Promise<void> {\n this.#generations.set(sessionId, this.generation(sessionId) + 1);\n const entries = this.#entries.get(sessionId);\n if (!entries) return;\n this.#entries.delete(sessionId);\n const results = await Promise.allSettled([...entries.values()].map(unregister => unregister()));\n const failure = results.find(result => result.status === 'rejected');\n if (failure?.status === 'rejected') throw failure.reason;\n }\n}\n\nasync function resolveSourceControlSession(\n providers: WorkspaceSourceControlProvider[],\n sessionId: string,\n): Promise<{ provider: WorkspaceSourceControlProvider; session: SourceControlSession } | null> {\n const matches = (\n await Promise.all(\n providers.map(async provider => ({\n provider,\n session: await provider.storage.sessions.getBySessionId(sessionId),\n })),\n )\n ).filter(\n (match): match is { provider: WorkspaceSourceControlProvider; session: SourceControlSession } =>\n match.session !== null,\n );\n if (matches.length > 1) {\n throw new Error(`Factory session ${sessionId} is ambiguous across source-control providers`);\n }\n return matches[0] ?? null;\n}\n\nexport function createWorkspaceFactory(options: CreateWorkspaceFactoryOptions = {}) {\n const { sandbox: sandboxConfig, github, projects, workItems } = options;\n const sourceControls: WorkspaceSourceControlProvider[] =\n options.sourceControls ??\n (github\n ? [{ id: github.id, versionControl: github.versionControl, storage: github.sourceControlStorage, github }]\n : []);\n const eagerSandboxStart = options.sandboxStart === 'eager';\n const workspaceRegistry = options.workspaceRegistry ?? new FactoryWorkspaceRegistry();\n type GithubTokenRegistration = {\n inject: (token: string) => void;\n patKind: GithubPatKind;\n ghToken: string;\n generation: number;\n tokenReplacementPending: boolean;\n };\n // The session setup path runs commands and installs credentials, so it\n // needs `executeCommand` (required by `ExecutableSandbox`) plus core's\n // optional `setEnv`, which stays optional here because the token-refresh\n // path checks for it and reports its absence.\n type SessionSandbox = ExecutableSandbox & { setEnv?: WorkspaceSandbox['setEnv'] };\n const githubTokenInjectors = new Map<string, GithubTokenRegistration>();\n const githubTokenReconciliations = new Map<string, Promise<void>>();\n // Workspace identity cache: concurrent resolutions of the same session must\n // observe the same Workspace object even when no Mastra registry is wired\n // (the registry stays the source of truth when present).\n const constructedWorkspaces = new Map<string, Workspace>();\n // Review-skill visibility last used to populate each workspace's skill cache.\n const skillCacheReviewState = new Map<string, boolean>();\n const skillCacheRefreshes = new Map<string, Promise<void>>();\n // The review check of the latest request to resolve each workspace. The skill\n // source outlives the request that built it, so it must gate on this rather\n // than on the context it was constructed with.\n const skillReviewChecks = new Map<string, () => Promise<boolean>>();\n\n return async ({ requestContext, mastra, skillExtension }: DynamicWorkspaceContext) => {\n const ctx = requestContext.get('controller') as AgentControllerRequestContext<MastraCodeState> | undefined;\n const supervisorProjectId = parseSupervisorResourceId(ctx?.resourceId);\n if (supervisorProjectId) {\n const orgId = getFactoryAuthOrgId(getFactoryAuthUserFromContext(requestContext));\n const project = orgId && projects ? await projects.get({ orgId, id: supervisorProjectId }) : null;\n if (!project) throw new Error(`Factory supervisor ${supervisorProjectId} is not available to the current user`);\n return undefined;\n }\n const resolvedSession = ctx?.resourceId ? await resolveSourceControlSession(sourceControls, ctx.resourceId) : null;\n const session = resolvedSession?.session ?? null;\n const sourceControl = resolvedSession?.provider;\n\n if (!session || !sourceControl) {\n // No factory session, no workspace. Chat still works; workspace tools\n // are simply not registered. Host-cwd behavior is opt-in via a\n // LocalSandbox callback rooted wherever the deployer wants — the\n // resolver never hands out the server host's own filesystem.\n return undefined;\n }\n\n const user = getFactoryAuthUserFromContext(requestContext);\n const userId = getFactoryAuthUserId(user);\n // No identity at all is a server-side caller that forgot to seed one\n // (webhook, cron), not someone reaching for another user's session.\n if (!user?.organizationId || !userId) {\n throw new Error(`Factory session ${session.sessionId} was resolved without a caller identity`);\n }\n // Org-visible sessions open to any member of the owning organization;\n // only private sessions stay owner-only. Cross-org access never passes.\n if (user.organizationId !== session.orgId || (session.visibility === 'private' && userId !== session.userId)) {\n throw new Error(`Factory session ${session.sessionId} is not available to the current user`);\n }\n if (!sandboxConfig) {\n throw new Error('A sandbox callback is required to create a Factory session workspace');\n }\n const createSessionSandboxInstance = sandboxConfig;\n const githubProvider = sourceControl.github;\n\n const storage = sourceControl.storage;\n const projectRepository = await storage.projectRepositories.get({\n orgId: session.orgId,\n id: session.projectRepositoryId,\n });\n if (!projectRepository) throw new Error(`Repository link ${session.projectRepositoryId} was not found`);\n // The remaining reads only depend on the repository link — issue them in\n // parallel instead of paying four sequential storage round-trips.\n const [connection, repository] = await Promise.all([\n storage.connections.get({ orgId: session.orgId, id: projectRepository.connectionId }),\n storage.repositories.get({ orgId: session.orgId, id: projectRepository.repositoryId }),\n ]);\n if (!connection || !repository) throw new Error(`Repository link ${session.projectRepositoryId} is incomplete`);\n const installation = await storage.installations.get({ orgId: session.orgId, id: connection.installationId });\n if (!installation) {\n throw new Error(`${sourceControl.id} installation ${connection.installationId} was not found`);\n }\n const repoFullName = repository.slug;\n\n // Construct (or fetch) the session's memoized sandbox instance.\n // Construction is cheap and side-effect-free by the callback contract —\n // the VM is provisioned on `start()`, which only the materialization\n // pipeline calls. The workdir is never persisted or trusted from storage\n // or client input (the stale-workdir incident class came from reusing\n // `session.sandboxWorkdir` written under a different provider): local\n // sandboxes derive it at construction, remote sandboxes clone into the\n // VM's own home so it resolves lazily at first start.\n // `runSetupOn` references `runSessionSetup`, defined below — it is only\n // invoked during start, long after this closure fully initializes.\n const runSetupOn = (target: unknown, workdir: string, gate: SessionSetupGate) =>\n runSessionSetup(requireExec(target as WorkspaceSandbox), workdir, gate);\n const guardedSetup = createSessionSetupHook(\n runSetupOn,\n session.id,\n repoFullName,\n projectRepository.setupCommand ?? undefined,\n );\n // Composed start hook: marker-guarded repo setup, then per-start\n // credential install. It runs inside the provider's start lifecycle on\n // EVERY start (create or reconnect) — providers own lazy start\n // (`ensureRunning()` on first command) and dead-VM self-healing (E2B\n // `retryOnDead`, Platform status reset on destroy), so a replacement VM\n // re-enters this hook and heals itself with credentials at least as\n // fresh as the start that installed them.\n const setupHook: SandboxStartHook = async args => {\n // A session retired before its first start must not set anything up.\n if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) {\n throw retiredError();\n }\n await guardedSetup(args);\n // Re-check after the (long) setup: a session retired mid-setup must not\n // register credentials for a workspace whose retirement teardown has\n // already run — the entry would leak forever. The VM itself is left to\n // the provider's idle timeout (accepted).\n if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) {\n throw retiredError();\n }\n const target: SessionSandbox = requireExec(args.sandbox);\n // Sandbox persistence plus the post-checkout skill rescan run on every\n // start (create or reconnect). The persisted sandbox id is read back on\n // resume to reattach; the workdir was resolved (and memoized on the\n // entry) by the guarded setup. The skill roots were reported empty by the\n // unmaterialized-source guard before the checkout existed, so rescan now.\n // The physical-id write is awaited and its failure propagates: a start\n // that completes before the id is durable lets a concurrent resume read a\n // stale id and provision a replacement VM.\n const publishStartSideEffects = async () => {\n await storage.sessions.setSandbox({\n id: session.id,\n // Persist the provider's PHYSICAL, reattachable VM id so resume can\n // reattach to the same VM. Providers with no separate physical id\n // (e.g. local) fall back to the logical id, preserving prior behavior.\n sandboxId: target.sandboxId ?? target.id,\n sandboxWorkdir: sessionEntry.workdir ?? '',\n });\n void constructedWorkspaces\n .get(workspaceId)\n ?.skills?.refresh()\n .catch(() => {});\n };\n if (!githubProvider) {\n await publishStartSideEffects();\n return;\n }\n const existingRegistration = githubTokenInjectors.get(workspaceId);\n if (existingRegistration) {\n // Reconnect: re-point the current registration's injection target at\n // this (possibly provider-healed) sandbox and reinstall its\n // reconcile-owned credential. Do NOT mint a new registration, bump\n // authority, or re-register the constructing request context —\n // reconcileGithubToken owns role/generation and the active context's\n // injector, so replacing them here would reauthorize the stale\n // constructing context and reject the current one.\n existingRegistration.inject = freshToken => {\n if (!target.setEnv) {\n throw new Error('The active sandbox provider does not support runtime GitHub token refresh.');\n }\n target.setEnv(env => ({ ...env, GH_TOKEN: freshToken }));\n existingRegistration.ghToken = freshToken;\n };\n // Install through inject (not optional-chained setEnv) so a provider\n // that cannot accept the credential fails the reconnect here instead of\n // deferring the failure to a later token refresh.\n existingRegistration.inject(existingRegistration.ghToken);\n await publishStartSideEffects();\n return;\n }\n // First start: resolve the credential and authorize the constructing\n // request context. The `gh` CLI needs a PAT when the org configured one\n // (installation tokens 403 on integration-restricted endpoints); git\n // clone/checkout keep using the minted installation token. Resolved per\n // start so the installed credential never outlives rotation.\n const patKind = await resolveGithubPatKind('default');\n const ghCliToken =\n (await getGithubPat(() => githubProvider.integrationStorage, session.orgId, patKind)) ??\n (await getRepositoryToken());\n target.setEnv?.(env => ({ ...env, GH_TOKEN: ghCliToken }));\n const tokenRegistration: GithubTokenRegistration = {\n inject: freshToken => {\n if (!target.setEnv) {\n throw new Error('The active sandbox provider does not support runtime GitHub token refresh.');\n }\n target.setEnv(env => ({ ...env, GH_TOKEN: freshToken }));\n tokenRegistration.ghToken = freshToken;\n },\n patKind,\n ghToken: ghCliToken,\n generation: 0,\n tokenReplacementPending: false,\n };\n githubTokenInjectors.set(workspaceId, tokenRegistration);\n registerGithubTokenContext(tokenRegistration);\n await publishStartSideEffects();\n };\n const constructSessionEntry = () =>\n getSessionSandbox(session.id, repoFullName, () => {\n const sandbox = createSessionSandboxInstance({\n sessionId: session.id,\n // Physical VM id persisted from a prior start (undefined on first\n // start). Providers that reattach by physical id use it to resume the\n // original VM instead of provisioning a replacement.\n sandboxId: session.sandboxId ?? undefined,\n repoFullName,\n // Stored nullable; the context speaks `undefined` for absent.\n setupCommand: projectRepository.setupCommand ?? undefined,\n // Deferred call — only dereferenced when a provider needs the repo\n // outside the VM (template build time).\n getRepositoryAccess: () =>\n sourceControl.versionControl.getRepositoryAccess({\n orgId: session.orgId,\n repositoryId: repository.id,\n }),\n });\n // Attached inside the construction closure, so exactly once per\n // instance — `constructSessionEntry` runs on every open and would\n // stack a wrapper per call. Factory's setup runs first: a hook the\n // callback installed itself expects a prepared workspace.\n sandbox.setOnStart(previous => async args => {\n await timedPhase(`workspace.onStart(${args.outcome})`, async () => {\n await setupHook(args);\n });\n await previous?.(args);\n });\n // Only a freshly constructed instance starts eagerly; the start itself\n // waits for this resolver to finish because the start hook reads\n // bindings declared further down.\n startEagerly = eagerSandboxStart;\n return sandbox;\n });\n let startEagerly = false;\n const fireEagerStart = () => {\n if (!startEagerly) return;\n startEagerly = false;\n Promise.resolve()\n .then(() => sessionEntry.sandbox.start?.())\n .catch(error => {\n console.warn(`[factory] Eager sandbox start for session ${session.id} failed:`, error);\n });\n };\n const sessionEntry = constructSessionEntry();\n const workdir = sessionEntry.workdir;\n const isLocalSandbox = sessionEntry.sandbox.provider === 'local';\n // The SDK system prompt uses `state.projectPath` without falling back to\n // the server cwd. Pin the session workdir once known so the prompt and\n // workspace tools describe the same checkout. A remote workdir resolves at\n // the sandbox's first start, so the pin self-heals on the next resolution.\n if (ctx && workdir && ctx.getState()?.projectPath !== workdir) {\n await ctx.setState({ projectPath: workdir, projectName: repoFullName });\n }\n\n // Keyed by the session, not the request's thread: a workspace built for a\n // dispatcher kickoff has no live thread yet, but the session's binding\n // already says which role it is serving. When the request does carry a\n // thread, the binding must belong to it, so a stale thread reusing this\n // session never inherits another run's role.\n const findSessionBinding = async () => {\n const binding = await workItems!.findActiveRunBindingForSession({\n orgId: session.orgId,\n factoryProjectId: connection.factoryProjectId,\n sessionId: session.sessionId,\n });\n if (!binding || !ctx?.threadId) return binding;\n return binding.threadId === ctx.threadId && binding.resourceId === ctx.resourceId ? binding : null;\n };\n // Fails closed: without a readable active review binding, review skills stay hidden.\n const isReviewSession = async (): Promise<boolean> => {\n if (!workItems) return false;\n try {\n return isActiveReviewBinding(await findSessionBinding(), session.orgId);\n } catch {\n return false;\n }\n };\n const effectiveSkillExtension =\n skillExtension ??\n createFactorySkillExtension((): Promise<boolean> => (skillReviewChecks.get(workspaceId) ?? isReviewSession)());\n const extensionId = effectiveSkillExtension ? `-${effectiveSkillExtension.id}` : '';\n const workspaceId: string = `${WORKSPACE_ID_PREFIX}-${projectRepository.id}-${session.id}${extensionId}`;\n const workspaceGeneration = workspaceRegistry.generation(session.sessionId);\n if (!skillExtension) skillReviewChecks.set(workspaceId, isReviewSession);\n const configDir = DEFAULT_CONFIG_DIR;\n\n const getRepositoryAccess = () =>\n sourceControl.versionControl.getRepositoryAccess({\n orgId: session.orgId,\n repositoryId: repository.id,\n });\n const getRepositoryToken = async (): Promise<string> => {\n const token = (await getRepositoryAccess()).authorization?.token;\n if (!token) throw new Error('Repository access did not include a bearer token for the Factory session');\n return token;\n };\n const resolveGithubPatKind = async (fallback: GithubPatKind): Promise<GithubPatKind> => {\n if (!workItems) return 'default';\n try {\n return isActiveReviewBinding(await findSessionBinding(), session.orgId) ? 'reviewer' : 'default';\n } catch {\n // Preserve the installed role when binding storage is temporarily unavailable.\n return fallback;\n }\n };\n const registerGithubTokenContext = (registered: GithubTokenRegistration): void => {\n const generation = registered.generation;\n registerGithubTokenInjector(requestContext, token => {\n if (githubTokenInjectors.get(workspaceId) !== registered || registered.generation !== generation) {\n throw new Error('GitHub token refresh no longer matches the active Factory workspace role.');\n }\n registered.inject(token);\n });\n registerGithubPatKind(requestContext, registered.patKind);\n };\n const reconcileGithubToken = async (): Promise<void> => {\n if (!githubProvider) return;\n const previous = githubTokenReconciliations.get(workspaceId) ?? Promise.resolve();\n const reconciliation = previous\n .catch(() => {})\n .then(async () => {\n const registered = githubTokenInjectors.get(workspaceId);\n if (!registered) return;\n\n const previousPatKind = registered.patKind;\n const patKind = await resolveGithubPatKind(previousPatKind);\n if (githubTokenInjectors.get(workspaceId) !== registered) return;\n\n if (patKind !== previousPatKind) {\n registered.patKind = patKind;\n registered.generation += 1;\n }\n if (patKind === 'reviewer') registered.tokenReplacementPending = false;\n if (previousPatKind === 'reviewer' && patKind === 'default') {\n // Invalidate reviewer refresh contexts before replacement I/O so\n // they cannot restore reviewer credentials after a failed downgrade.\n registered.tokenReplacementPending = true;\n }\n\n let token = await getGithubPat(() => githubProvider.integrationStorage, session.orgId, patKind);\n if (!token && registered.tokenReplacementPending) token = await getRepositoryToken();\n if (githubTokenInjectors.get(workspaceId) !== registered) return;\n\n if (token && token !== registered.ghToken) {\n try {\n registered.inject(token);\n } catch (error) {\n if (registered.tokenReplacementPending) throw error;\n // Same-role rotations and reviewer upgrades remain best-effort.\n }\n }\n if (token && token === registered.ghToken) registered.tokenReplacementPending = false;\n registerGithubTokenContext(registered);\n });\n githubTokenReconciliations.set(workspaceId, reconciliation);\n try {\n await reconciliation;\n } finally {\n if (githubTokenReconciliations.get(workspaceId) === reconciliation) {\n githubTokenReconciliations.delete(workspaceId);\n }\n }\n };\n const reconcileRegisteredWorkspace = async (workspace: Workspace): Promise<Workspace> => {\n const registered = githubTokenInjectors.get(workspaceId);\n try {\n await reconcileGithubToken();\n } catch (error) {\n if (registered?.tokenReplacementPending && githubTokenInjectors.get(workspaceId) === registered) {\n // The role generation already invalidated reviewer refresh contexts.\n // Keep the pending registration so failed eviction cannot make a\n // still-live reviewer workspace look safe on the next reuse.\n let evicted = false;\n try {\n evicted = (await mastra?.removeWorkspace?.(workspaceId)) === true;\n } catch {\n // Preserve the credential-replacement error and retry on the next reuse.\n }\n try {\n await workspace.destroy();\n evicted = true;\n } catch {\n // The pending registration keeps the workspace quarantined if cleanup also fails.\n }\n if (evicted && githubTokenInjectors.get(workspaceId) === registered) {\n githubTokenInjectors.delete(workspaceId);\n constructedWorkspaces.delete(workspaceId);\n skillCacheReviewState.delete(workspaceId);\n }\n }\n throw error;\n }\n if (registered && githubTokenInjectors.get(workspaceId) !== registered) {\n throw new Error('Factory workspace GitHub credential registration is no longer active.');\n }\n return workspace;\n };\n\n let existing: Workspace | undefined;\n try {\n existing = mastra?.getWorkspaceById(workspaceId) as Workspace | undefined;\n } catch {\n // Not registered yet.\n existing = undefined;\n }\n existing ??= constructedWorkspaces.get(workspaceId);\n if (existing) {\n existing.setToolsConfig(MASTRACODE_WORKSPACE_TOOLS);\n // The skill cache does not recheck the source on get(), so rescan when the\n // session's role flips; otherwise a cached review skill outlives the review binding.\n if (!skillExtension) {\n // An outside rescan makes the recorded role untrustworthy. Wait for it to\n // settle first, so the refresh below starts a scan for the current role\n // rather than joining one started for the previous role.\n const consumeOutsideRescan = async () => {\n const outside = existing.skills && rescannedOutsideResolver.get(existing.skills);\n if (!outside) return false;\n await outside;\n if (rescannedOutsideResolver.get(existing.skills!) === outside)\n rescannedOutsideResolver.delete(existing.skills!);\n skillCacheReviewState.delete(workspaceId);\n return true;\n };\n await consumeOutsideRescan();\n let isReview = await isReviewSession();\n // Concurrent reuses share one refresh, and the state is recorded only\n // once the rescan succeeds. Wait out any in-flight rescan (it may have\n // been started for the opposite role) and re-read the role afterwards.\n for (;;) {\n const inFlight = skillCacheRefreshes.get(workspaceId);\n if (inFlight) {\n await inFlight;\n isReview = await isReviewSession();\n continue;\n }\n if (await consumeOutsideRescan()) {\n isReview = await isReviewSession();\n continue;\n }\n if (skillCacheReviewState.get(workspaceId) === isReview) break;\n const target = isReview;\n const pending = (async () => {\n await existing.skills?.refresh();\n skillCacheReviewState.set(workspaceId, target);\n })().finally(() => skillCacheRefreshes.delete(workspaceId));\n skillCacheRefreshes.set(workspaceId, pending);\n await pending;\n isReview = await isReviewSession();\n }\n }\n // A materialization kicked off by another caller may still be running.\n // Deliberately do NOT wait for it: a metadata-only resolution (thread\n // list, messages, activity) must not block on the clone/setup that lazy\n // materialization exists to avoid. Token reconciliation below no-ops\n // until the leader registers the injector, and the next reuse after\n // materialization completes reconciles against the live sandbox.\n return reconcileRegisteredWorkspace(existing);\n }\n\n const retiredError = () =>\n new Error(`Factory session ${session.sessionId} was retired during workspace materialization`);\n\n // The session's setup work: materialize the repo (disk-truth idempotent),\n // check out the session branch, run the configured setup command. Minted\n // tokens are fetched inside the run so a replacement VM healed mid-session\n // gets fresh credentials, not ones captured at workspace construction.\n const runSessionSetup = async (target: SessionSandbox, workdir: string, gate: SessionSetupGate): Promise<void> => {\n const access = await getRepositoryAccess();\n const token = access.authorization?.token;\n if (!token) throw new Error('Repository access did not include a bearer token for the Factory session');\n // GitHub setup commands may shell out to gh/HTTPS. Preserve its\n // role-aware PAT behavior without applying GitHub credentials to other\n // source-control providers.\n if (githubProvider) {\n const setupPatKind = await resolveGithubPatKind('default');\n const setupGhToken =\n (await getGithubPat(() => githubProvider.integrationStorage, session.orgId, setupPatKind)) ?? token;\n target.setEnv?.(env => ({ ...env, GH_TOKEN: setupGhToken }));\n }\n await materializeRepo({\n row: { id: session.id, sandboxWorkdir: workdir, materializedAt: session.materializedAt },\n repoInfo: {\n repoFullName,\n defaultBranch: repository.defaultBranch,\n cloneUrl: access.cloneUrl,\n authUsername: access.authorization?.username,\n },\n sandbox: target,\n token,\n storage: storage.sessions,\n });\n await checkoutSessionBranch(target, workdir, {\n branch: session.branch,\n baseBranch: session.baseBranch || projectRepository.branch || repository.defaultBranch,\n token,\n repoFullName,\n cloneUrl: access.cloneUrl,\n authUsername: access.authorization?.username,\n pullRequestNumber: pullRequestNumberFromBranch(session.branch),\n mergeRequestNumber: sourceControl.id === 'gitlab' ? mergeRequestNumberFromBranch(session.branch) : undefined,\n });\n if (projectRepository.setupCommand && !gate.setupDone) {\n // A setup command that already failed this session is skipped rather\n // than failing every start: the first failure surfaced loudly in the\n // tool result that triggered it, and a permanently failing onStart\n // would wedge the session — the agent could never get a shell to fix\n // the problem. Clone and checkout above still ran, so the tree is\n // real; the agent (or an edited setup command) takes it from here.\n if (hasFailedSetupCommand(session.id, projectRepository.setupCommand)) {\n console.warn('[Mastra Factory] Skipping setup command that already failed this session', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n });\n return;\n }\n try {\n await timedPhase('workspace.setup', () => runSetupCommand(target, workdir, projectRepository.setupCommand!));\n await gate.markSetupDone();\n } catch (setupError) {\n if (projectRepository.teardownCommand) {\n try {\n await runTeardownCommand(target, workdir, projectRepository.teardownCommand, {\n timeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,\n });\n } catch (teardownError) {\n console.warn('[Mastra Factory] Worktree teardown after setup failure failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: teardownError instanceof Error ? teardownError.message.slice(-2000) : String(teardownError),\n });\n }\n }\n if (setupError instanceof SetupCommandError) {\n // The command ran and exited non-zero — a config problem, not an\n // infra one. Remember it so the next start recovers, and tell the\n // agent what happens next. Infra failures (transport, clone)\n // rethrow untouched and retry in full.\n recordFailedSetupCommand(session.id, projectRepository.setupCommand);\n throw new SetupCommandError(\n `${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.`,\n setupError.code,\n );\n }\n throw setupError;\n }\n }\n };\n // The session's real sandbox goes straight onto the Workspace. Providers\n // own lazy start (`ensureRunning()` inside the first command/process op)\n // and dead-VM self-healing, and the composed `onStart` hook runs the repo\n // setup + credential install inside that lifecycle. Metadata-only\n // resolutions (thread-list polling) construct but never start.\n const sessionSandbox: SessionSandbox = requireExec(sessionEntry.sandbox);\n\n const filesystem = new SandboxFilesystem({\n id: `sandbox-fs:${workspaceId}`,\n sandbox: sessionSandbox,\n // Lazy: a remote workdir is only knowable once a VM runs. The first\n // file operation resolves it (starting the VM — which materializes the\n // repo via the onStart hook — when needed) and memoizes it.\n workdir: () => resolveSessionWorkdir(session.id, sessionEntry.sandbox, repoFullName),\n });\n const projectSkillPaths = [path.join(configDir, 'skills'), '.claude/skills', '.agents/skills'];\n const guardedSkillFallback = new UnmaterializedAwareSkillSource(\n filesystem,\n () => sessionEntry.sandbox.status === 'running',\n );\n const skillPaths = [...(effectiveSkillExtension?.paths ?? []), ...projectSkillPaths];\n const workspace = new Workspace({\n id: workspaceId,\n name: 'Mastra Code Factory Session Workspace',\n filesystem,\n sandbox: sessionSandbox as unknown as ConstructorParameters<typeof Workspace>[0]['sandbox'],\n tools: MASTRACODE_WORKSPACE_TOOLS,\n skills: skillPaths,\n // Project skill roots live in the sandbox checkout; guard them so skill\n // discovery before materialization (e.g. kickoff skill resolution in the\n // start coordinator) never forces sandbox provisioning.\n skillSource:\n effectiveSkillExtension?.createSource(guardedSkillFallback, projectSkillPaths) ?? guardedSkillFallback,\n });\n // Register with the Mastra instance so sync HTTP handlers that resolve\n // the workspace via `mastra.getWorkspaceById(id)` (file tree, permissions\n // probe, MCP/tool routes) find it instead of throwing\n // `MASTRA_GET_WORKSPACE_BY_ID_NOT_FOUND`. `addWorkspace` is idempotent on\n // key collision, so concurrent first resolutions stay race-safe (start\n // itself is coalesced by the sandbox base class + the session memo).\n mastra?.addWorkspace(workspace, workspaceId, { source: 'mastra' });\n // Cache synchronously with construction: the `await` below is a suspension\n // point, and a concurrent resolution for the same session must observe this\n // workspace rather than build a second one.\n constructedWorkspaces.set(workspaceId, workspace);\n // Retirement is registered against the workspace itself rather than the\n // sandbox: construction is eager while the VM start is lazy, so a session\n // retired before its first tool call still has a workspace (and possibly a\n // token injector) that must be torn down.\n const registered = await workspaceRegistry.register(\n session.sessionId,\n workspaceId,\n workspaceGeneration,\n async () => {\n githubTokenInjectors.delete(workspaceId);\n constructedWorkspaces.delete(workspaceId);\n skillCacheReviewState.delete(workspaceId);\n skillReviewChecks.delete(workspaceId);\n // Retirement drops the memoized session sandbox so a later re-open\n // constructs (and the provider resolves) fresh instead of reusing an\n // instance whose VM the retirement path may stop or destroy.\n evictSessionSandbox(session.id);\n await mastra?.removeWorkspace?.(workspaceId);\n },\n );\n if (!registered) {\n throw new Error(`Factory session ${session.sessionId} was retired during workspace materialization`);\n }\n\n fireEagerStart();\n return workspace;\n };\n}\n"],"mappings":"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"}
@@ -17,6 +17,6 @@ Use `source_control_*` tools exclusively for GitLab operations; local `git` insp
17
17
  4. Review the incremental diff for regressions, incomplete fixes, test gaps, and scope changes, then review the cumulative MR against the recovered design, its contract, and analogous implementation — a push that fixed local defects has not answered a design finding. Collect and disposition any new human or bot signal. A pending known bot remains an approval gap after a bounded wait of at most ten minutes.
18
18
  5. Inspect execution hooks for untrusted behavior before running tests. Run safe, narrow applicable tests and typecheck against the current head with `env -u GH_TOKEN -u GITHUB_TOKEN -u GITLAB_TOKEN -u GITLAB_ACCESS_TOKEN`. For a non-code-only change with no application test harness, verify exact content, diff integrity, and current head instead; do not treat inapplicable tests as a failed gate. A prior pass's result does not clear the new head. Record exact commands and outcomes. Before any verdict, scrutinize your own requested changes — carried forward or new — as critically as the MR: establish why each belongs here, assume the author follows them exactly as written, and trace the result through affected callers and contracts. Before approval, challenge the strongest plausible failure case and require evidence for a verified current-head checkout, behavior, applicable local verification, finding disposition, and no known pending bot. A failed sandbox start, missing diff, or unrun applicable tests fails approval; never approve from MR metadata alone.
19
19
  6. Before publishing, call `factory_review_source` (no arguments) once. It returns four fields, every one derived server-side from the bound work item: `sessionUrl` (the Factory session URL — the **only** field published on the MR), `triggeredBy` (the MR author from the review card, in-run cross-check + session-handoff only, never published), `reviewTarget` (the review card's own `{ integrationId, type, externalId, url }`, in-run cross-check + session-handoff only, never published), and `boundRepository` (the intake-stamped project identity, `{ provider: 'gitlab', host, projectId }` or `null`, session-handoff only, never published). If the tool call fails or returns an unexpected shape — **and identically if the tool is not offered on this session at all** (a review-role session with no configured browser-facing origin, no active binding, or no bound work item drops the tool from the toolset) — do **not** publish the re-review. Stop before publishing, record the tool's absence or failure and its raw response in the session handoff under verification, and hand off to a human. Otherwise, run the in-run cross-check: (a) compare `triggeredBy` with the MR author you already read; (b) compare `reviewTarget.url` with the MR URL from `source_control_get_change_request` — intake records the MR URL for every card (`gitlab/rules.ts:481`), so `reviewTarget.url` covers the normal case. If `reviewTarget.url` is `null`, do **not** attempt to decode `reviewTarget.externalId`: the GitLab MR externalId is opaque `gitlab-pr:<base64url({version, host, projectId, mergeRequestIid})>` (`gitlab/routes.ts:94`, `gitlab/rules.ts:109`), with no documented way to check it against an MR IID. The bound identity itself is not the gap — the tool returns the intake-stamped `boundRepository`, and the MR fetch is binding-scoped (`session/source-control-tools.ts` resolves the repository from the session record, so it cannot return another project's MR) — but the normalized change-request result carries no numeric project id or intake identifier to compare it against, and a matching `triggeredBy` author cannot distinguish two MRs by the same author within the project. With no URL, nothing confirms the fetched MR **is the card's MR**. Do not publish: record the `reviewTarget.url` gap and both raw values in the session handoff, skip the verdict call, and hand off to a human. On any mismatch, stop and record a blocking security finding with both values verbatim; do **not** publish anything on the MR — posting any verdict (a request-changes comment included) puts a review on an MR that may be the wrong one. Re-read the MR with `source_control_get_change_request` once and re-run the cross-check; publish only if the fresh check matches, otherwise record the mismatch in the **Factory routing** block, skip the verdict call, and hand off to a human. When publication does proceed, include only `sessionUrl` as a `Factory Session` section at the end of every published body (approve, request-changes comment, and the approval-fallback comment).
20
- 7. Publish a complete current-head verdict with `source_control_review_change_request`. Use `approve` only when all applicable gates pass; otherwise use `comment` with `Verdict: request changes` and an actionable defect or verification gap because GitLab has no request-changes review state. If approval is rejected for a confirmed authorization reason (self-review or missing permission), keep the substantive approve verdict: make a separate `source_control_comment_change_request` call with `Verdict: approve (approval not recorded)` and explain the rejection. Confirm that this fallback comment succeeded before recording the verdict. For any other rejection, refresh the checkout, re-establish the current head, and re-run the substantive review and approval gates against the refreshed head before retrying; a provider rejection is not a reason to switch to request changes, an approval evaluated against a head that has since changed is never published, and a retry that still fails means no verdict was posted — report the failure. Never claim a formal approval that was not recorded, and never merge the MR.
20
+ 7. Publish a complete current-head verdict with `source_control_review_change_request`. Compose the body fresh in this pass — never reuse an earlier pass's body. It opens with the verdict line (`Verdict: approve` or `Verdict: request changes`), then `Reviewed head: <full SHA>` equal to the current MR head, the `commitId`, and the `reviewedHeadSha` you record. The tool rejects a body whose verdict line contradicts `event` or whose reviewed head is stale; regenerate the body in that case, never flip the event to match it. Use `approve` only when all applicable gates pass; otherwise use `comment` with `Verdict: request changes` and an actionable defect or verification gap because GitLab has no request-changes review state. If approval is rejected for a confirmed authorization reason (self-review or missing permission), keep the substantive approve verdict: first re-read the MR with `source_control_get_change_request` and confirm its head still equals the head you reviewed — if it moved, do not post the fallback; refresh and re-run the review gates against the new head. Then make a separate `source_control_comment_change_request` call whose body opens with `Verdict: approve (approval not recorded)`, then `Reviewed head: <full SHA>` naming that verified head, and explains the rejection. Confirm that this fallback comment succeeded before recording the verdict. For any other rejection, refresh the checkout, re-establish the current head, and re-run the substantive review and approval gates against the refreshed head before retrying; a provider rejection is not a reason to switch to request changes, an approval evaluated against a head that has since changed is never published, and a retry that still fails means no verdict was posted — report the failure. Never claim a formal approval that was not recorded, and never merge the MR.
21
21
 
22
22
  First call `factory_record_review_verdict` with the published `verdict` and the verified `reviewedHeadSha`. If rejected, address the reason before retrying; the handoff must report whether the card was updated. The card stays in Reviewing; never request a stage transition to end the re-review. Then, as the terminal action, provide a session handoff with MR URL/current head, incremental and cumulative findings, a disposition for every prior finding, tests, assumptions, gaps, the actual publication outcome, the `Factory Session` block (`sessionUrl` only, matching the MR body), and a **Factory routing** block that records `triggeredBy` verbatim, `reviewTarget` verbatim (`integrationId`, `type`, `externalId`, `url`), `boundRepository` verbatim, and the cross-check outcome — "matched" with the compared MR value or "mismatch: <blocking-finding-ref>".
@@ -43,6 +43,6 @@ Otherwise, run the in-run cross-check:
43
43
 
44
44
  On any mismatch, stop and record it as a blocking security finding with both values verbatim. Do **not** publish anything on the MR — a mismatch means the fetched MR may not be the session's bound target, and posting any verdict (a request-changes comment included) puts a review on an MR that may be the wrong one. Re-read the MR with `source_control_get_change_request` once and re-run this cross-check; publish only if the fresh check matches. If it still mismatches, hand off without publishing: record the mismatch in the **Factory routing** block, skip the verdict call, and hand off to a human. When publication does proceed, include only `sessionUrl` as a `Factory Session` section at the end of every published body (approve, request-changes comment, and the approval-fallback comment) so a misattributed review can be traced back to its run. `triggeredBy` and `reviewTarget` stay in the session handoff, never on the MR.
45
45
 
46
- Use `source_control_review_change_request` on the same IID and current `commitId` when available. For an approve verdict use `event: "approve"`. GitLab cannot represent a GitHub-style request-changes review: for a blocking verdict use `event: "comment"` and start the body with `Verdict: request changes`, followed by concrete findings and evidence. Do not claim that a comment blocks merging. Use the approval fallback only for a confirmed authorization rejection — for example, the current account authored the MR or lacks review permission. In that case make a separate `source_control_comment_change_request` call immediately with a body beginning `Verdict: approve (approval not recorded)` and name the provider rejection in the handoff; never claim the MR was formally approved. For any other rejection (stale `commitId`, changed head, invalid request), refresh the checkout and re-establish the current head, then re-run the substantive review — the diff, finding validation, applicable verification, and approval gates — before retrying publication. Never publish an approval evaluated against a head that has since changed. If the gates cannot be re-run on the refreshed head, report that no verdict was posted. Confirm that the comment call succeeded before the handoff or verdict call. If publication fails, report that failure and do not imply a verdict was posted. Where useful, anchor a specific finding with `source_control_create_diff_comment`, but keep a complete verdict in the top-level review.
46
+ Use `source_control_review_change_request` on the same IID and current `commitId` when available. Compose the body fresh in this pass — never reuse a body from an earlier pass. Its first line is the verdict line (`Verdict: approve` or `Verdict: request changes`) and its second line is `Reviewed head: <full SHA>` naming the head you verified, which must equal the current MR head, the `commitId`, and the `reviewedHeadSha` you record. The tool rejects a body whose verdict line contradicts `event` or whose `Reviewed head:` is not the current head; treat that rejection as a stale body — regenerate it from this pass's findings, never flip the event to match it. For an approve verdict use `event: "approve"`. GitLab cannot represent a GitHub-style request-changes review: for a blocking verdict use `event: "comment"` and start the body with `Verdict: request changes`, followed by concrete findings and evidence. Do not claim that a comment blocks merging. Use the approval fallback only for a confirmed authorization rejection — for example, the current account authored the MR or lacks review permission. In that case re-read the MR with `source_control_get_change_request` and confirm its head still equals the head you reviewed — if it moved, do not post the fallback; refresh and re-run the review gates against the new head. Otherwise make a separate `source_control_comment_change_request` call with a body beginning `Verdict: approve (approval not recorded)`, then `Reviewed head: <full SHA>` naming that verified head, and name the provider rejection in the handoff; never claim the MR was formally approved. For any other rejection (stale `commitId`, changed head, invalid request), refresh the checkout and re-establish the current head, then re-run the substantive review — the diff, finding validation, applicable verification, and approval gates — before retrying publication. Never publish an approval evaluated against a head that has since changed. If the gates cannot be re-run on the refreshed head, report that no verdict was posted. Confirm that the comment call succeeded before the handoff or verdict call. If publication fails, report that failure and do not imply a verdict was posted. Where useful, anchor a specific finding with `source_control_create_diff_comment`, but keep a complete verdict in the top-level review.
47
47
 
48
48
  After the review publication attempt, call `factory_record_review_verdict` with the published `verdict` and the verified `reviewedHeadSha`. The card stays in Reviewing; never request a stage transition to end the review. If the call is rejected, address the stated reason before retrying. Then, as the terminal action, give the session handoff and report whether the card was updated. In the session handoff, include MR URL and head SHA, verdict and whether GitLab recorded it, goal, findings with file/line evidence, prior-review disposition, commands/results, assumptions, open questions, any verification limitation, the `Factory Session` block (`sessionUrl` only, matching the MR body), and a **Factory routing** block that records `triggeredBy` verbatim, `reviewTarget` verbatim (`integrationId`, `type`, `externalId`, `url`), `boundRepository` verbatim, and the cross-check outcome — "matched" with the compared MR value or "mismatch: <blocking-finding-ref>".
@@ -152,7 +152,7 @@ On any mismatch, stop and record it as a blocking security finding with both val
152
152
 
153
153
  Compose two artifacts, in order — the **published body** goes on the PR, the **session handoff** goes back into the run's conversation. Don't send either to the conversation yet; both are drafted here, the published body is sent to the PR, the verdict is recorded, and only then is the session handoff posted.
154
154
 
155
- The **published body** (what `gh pr review --body-file` receives) **must open with the verdict line**: `Verdict: approve` or `Verdict: request changes`, followed by:
155
+ The **published body** (what `gh pr review --body-file` receives) **must open with the verdict line**: `Verdict: approve` or `Verdict: request changes`, then on the next line `Reviewed head: <full 40-character SHA>` naming the exact head you verified, followed by:
156
156
 
157
157
  - **Prior pass disposition** — every substantive item from your previous review, classified: addressed, partially addressed, still open, refuted by the push, or invalidated by the push. Cite the commit or `file:line` proving each addressed/refuted/invalidated call. A prior blocking finding still open is called out plainly at the top of this section.
158
158
  - **Findings** — lead with the mechanism of the most consequential finding; new-this-pass findings from the push and from the fresh whole-PR sweep are each labeled as `[push]` or `[fresh]` so the record is honest about where they came from. Distill — this is a handoff, not a transcript.
@@ -172,19 +172,28 @@ The **session handoff** (posted as the final conversation message after the verd
172
172
 
173
173
  **The head must not have moved.** Immediately before publishing, run `gh pr view <number> --json headRefOid --jq .headRefOid` and compare it with the SHA your verification ran on (`git rev-parse HEAD`). A push can land while you verify or wait on bots, and a verdict on a superseded head misleads the author. If the head moved, do not publish: refresh the checkout to the new head, review the new commits and re-run the verification they affect, revise the handoff, then check again. Name the reviewed head SHA in the handoff.
174
174
 
175
- Next, publish the re-review on the PR itself — this is part of every pass, not something to wait to be asked for. Write the published body to `.artifacts/factory-rereview/pr-<number>.md` and submit a PR review matching the verdict:
175
+ Next, publish the re-review on the PR itself — this is part of every pass, not something to wait to be asked for. Write the published body to `.artifacts/factory-rereview/pr-<number>-<headSha>.md`, where `<headSha>` is the full SHA you reviewed. Always write this file fresh in the current pass — never reuse, copy, or edit a body file from an earlier pass, which describes a different head and may carry the opposite verdict.
176
+
177
+ **Gate the file immediately before posting.** Run these checks against the exact file you are about to pass to `--body-file`, and post nothing if any fails — regenerate the body from this pass's findings instead:
178
+
179
+ 1. `head -n1 <file>` is exactly `Verdict: approve` when you will use `--approve`, or exactly `Verdict: request changes` when you will use `--request-changes`.
180
+ 2. The `Reviewed head:` SHA in the file equals `gh pr view <number> --json headRefOid --jq .headRefOid` and the `reviewedHeadSha` you will pass to `factory_record_review_verdict`.
181
+
182
+ Then submit a PR review matching the verdict:
176
183
 
177
184
  - approve → `gh pr review <number> --approve --body-file <file>`
178
185
  - request changes → `gh pr review <number> --request-changes --body-file <file>`
179
186
 
180
187
  **Author-identity misconfiguration must be visible, never silent.** GitHub refuses both approve and request changes from the PR's author, so a review token that authored the PR can never record a verdict in `reviewDecision` or satisfy branch protection. Before submitting, compare the reviewing identity (`gh api user --jq .login`; for an App installation token that call may fail — then treat a submission rejected with GitHub's "Can not approve/request changes on your own pull request" error as the same signal) with the PR's `.author.login`. When they match:
181
188
 
182
- 1. Add this line to the published body immediately after the verdict line (the verdict line stays first): `> ⚠️ **Factory misconfiguration:** the review token is the PR author, so GitHub cannot record this verdict as an approving or changes-requested review (it will not satisfy branch protection or workflows that require an approving or changes-requested review). Configure a separate reviewer token for Factory reviews.`
189
+ 1. Add this line to the published body immediately after the `Reviewed head:` line (the verdict line stays first and `Reviewed head:` stays second): `> ⚠️ **Factory misconfiguration:** the review token is the PR author, so GitHub cannot record this verdict as an approving or changes-requested review (it will not satisfy branch protection or workflows that require an approving or changes-requested review). Configure a separate reviewer token for Factory reviews.`
183
190
  2. Publish with `gh pr comment <number> --body-file <file>`. Do not use `gh pr review --comment`: Factory's repair loop only routes a request-changes verdict from a plain PR comment whose first line is the verdict, and ignores `COMMENTED` reviews.
184
191
  3. Report the misconfiguration and the publish method under **Verification** and in the **Factory routing** block of the handoff.
185
192
 
186
193
  If submission fails for any other reason, fall back to `gh pr comment <number> --body-file <file>` so the verdict still lands on the PR, and report the fallback under **Verification** — how the verdict was published is an operational outcome, not an assumption.
187
194
 
195
+ After the body is posted successfully (review or comment fallback), delete the body file so no later pass can post it.
196
+
188
197
  After publishing, reconcile the verdict label: approve adds `status:auto-approved` and removes `status:changes-requested`; request changes adds `status:changes-requested` and removes `status:auto-approved`.
189
198
 
190
199
  **Non-blocking follow-ups become a PR, not homework.** After publishing the re-review, if it produced non-blocking findings with concrete mechanical fixes — typos, small hardening, a supplemental test case, doc touch-ups — implement them yourself instead of leaving them as a burden on the author. Supplemental means coverage beyond what the behavior-tested gate required: a test gap that failed that gate is a requested change on the reviewed PR, never follow-up work:
@@ -145,7 +145,7 @@ On any mismatch, stop and record it as a blocking security finding with both val
145
145
 
146
146
  Compose two artifacts, in order — the **published body** goes on the PR, the **session handoff** goes back into the run's conversation. Don't send either to the conversation yet; both are drafted here, the published body is sent to the PR, the verdict is recorded, and only then is the session handoff posted.
147
147
 
148
- The **published body** (what `gh pr review --body-file` receives) **must open with the verdict line**: `Verdict: approve` or `Verdict: request changes`, followed by:
148
+ The **published body** (what `gh pr review --body-file` receives) **must open with the verdict line**: `Verdict: approve` or `Verdict: request changes`, then on the next line `Reviewed head: <full 40-character SHA>` naming the exact head you verified, followed by:
149
149
 
150
150
  - **Findings** — lead with the mechanism of the most consequential finding, then correctness, tests, scope, and pattern-consistency, each grounded in the history you traced. Distill — this is a handoff, not a transcript.
151
151
  - **Approach** — the required outcome and the simplest sufficient design from your Phase 1 record, and whether the PR's approach and scope are justified against it. Agreement stated in one line; disagreement with the evidence that supports the alternative.
@@ -164,19 +164,28 @@ The **session handoff** (posted as the final conversation message after the verd
164
164
 
165
165
  **The head must not have moved.** Immediately before publishing, run `gh pr view <number> --json headRefOid --jq .headRefOid` and compare it with the SHA your verification ran on (`git rev-parse HEAD`). A push can land while you verify or wait on bots, and a verdict on a superseded head misleads the author. If the head moved, do not publish: refresh the checkout to the new head, review the new commits and re-run the verification they affect, revise the handoff, then check again. Name the reviewed head SHA in the handoff.
166
166
 
167
- Next, publish the review on the PR itself — this is part of every pass, not something to wait to be asked for. Write the published body to `.artifacts/factory-review/pr-<number>.md` and submit a PR review matching the verdict:
167
+ Next, publish the review on the PR itself — this is part of every pass, not something to wait to be asked for. Write the published body to `.artifacts/factory-review/pr-<number>-<headSha>.md`, where `<headSha>` is the full SHA you reviewed. Always write this file fresh in the current pass — never reuse, copy, or edit a body file from an earlier pass, which describes a different head and may carry the opposite verdict.
168
+
169
+ **Gate the file immediately before posting.** Run these checks against the exact file you are about to pass to `--body-file`, and post nothing if any fails — regenerate the body from this pass's findings instead:
170
+
171
+ 1. `head -n1 <file>` is exactly `Verdict: approve` when you will use `--approve`, or exactly `Verdict: request changes` when you will use `--request-changes`.
172
+ 2. The `Reviewed head:` SHA in the file equals `gh pr view <number> --json headRefOid --jq .headRefOid` and the `reviewedHeadSha` you will pass to `factory_record_review_verdict`.
173
+
174
+ Then submit a PR review matching the verdict:
168
175
 
169
176
  - approve → `gh pr review <number> --approve --body-file <file>`
170
177
  - request changes → `gh pr review <number> --request-changes --body-file <file>`
171
178
 
172
179
  **Author-identity misconfiguration must be visible, never silent.** GitHub refuses both approve and request changes from the PR's author, so a review token that authored the PR can never record a verdict in `reviewDecision` or satisfy branch protection. Before submitting, compare the reviewing identity (`gh api user --jq .login`; for an App installation token that call may fail — then treat a submission rejected with GitHub's "Can not approve/request changes on your own pull request" error as the same signal) with the PR's `.author.login`. When they match:
173
180
 
174
- 1. Add this line to the published body immediately after the verdict line (the verdict line stays first): `> ⚠️ **Factory misconfiguration:** the review token is the PR author, so GitHub cannot record this verdict as an approving or changes-requested review (it will not satisfy branch protection or workflows that require an approving or changes-requested review). Configure a separate reviewer token for Factory reviews.`
181
+ 1. Add this line to the published body immediately after the `Reviewed head:` line (the verdict line stays first and `Reviewed head:` stays second): `> ⚠️ **Factory misconfiguration:** the review token is the PR author, so GitHub cannot record this verdict as an approving or changes-requested review (it will not satisfy branch protection or workflows that require an approving or changes-requested review). Configure a separate reviewer token for Factory reviews.`
175
182
  2. Publish with `gh pr comment <number> --body-file <file>`. Do not use `gh pr review --comment`: Factory's repair loop only routes a request-changes verdict from a plain PR comment whose first line is the verdict, and ignores `COMMENTED` reviews.
176
183
  3. Report the misconfiguration and the publish method under **Verification** and in the **Factory routing** block of the handoff.
177
184
 
178
185
  If submission fails for any other reason, fall back to `gh pr comment <number> --body-file <file>` so the verdict still lands on the PR, and report the fallback under **Verification** — how the verdict was published is an operational outcome, not an assumption.
179
186
 
187
+ After the body is posted successfully (review or comment fallback), delete the body file so no later pass can post it.
188
+
180
189
  After publishing, reconcile the verdict label: approve adds `status:auto-approved` and removes `status:changes-requested`; request changes adds `status:changes-requested` and removes `status:auto-approved`.
181
190
 
182
191
  **Non-blocking follow-ups become a PR, not homework.** After publishing the review, if it produced non-blocking findings with concrete mechanical fixes — typos, small hardening, a supplemental test case, doc touch-ups — implement them yourself instead of leaving them as a burden on the author. Supplemental means coverage beyond what the behavior-tested gate required: a test gap that failed that gate is a requested change on the reviewed PR, never follow-up work:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/factory",
3
- "version": "0.18.0-alpha.9",
3
+ "version": "0.18.1-alpha.0",
4
4
  "description": "Mastra Software Factory module: the server core behind the Mastra Software Factory — storage domains, integrations, and surfaces for agent-powered software delivery",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -58,10 +58,10 @@
58
58
  "posthog-node": "^5.46.1",
59
59
  "zod": "^4.6.4",
60
60
  "@mastra/auth-studio": "1.3.7",
61
- "@mastra/auth-workos": "1.6.6-alpha.0",
62
- "@mastra/core": "1.72.0-alpha.9",
63
- "@mastra/code-sdk": "1.9.0-alpha.9",
64
- "@mastra/slack": "1.7.0-alpha.0"
61
+ "@mastra/auth-workos": "1.6.6",
62
+ "@mastra/code-sdk": "1.10.0-alpha.0",
63
+ "@mastra/core": "1.73.0-alpha.0",
64
+ "@mastra/slack": "1.7.1-alpha.0"
65
65
  },
66
66
  "devDependencies": {
67
67
  "@types/node": "22.20.1",
@@ -70,11 +70,11 @@
70
70
  "typescript": "^6.0.3",
71
71
  "typescript-eslint": "^8.57.0",
72
72
  "vitest": "4.1.11",
73
- "@mastra/libsql": "1.24.0-alpha.2",
74
- "@mastra/pg": "1.28.0-alpha.4",
75
- "@internal/types-builder": "0.0.112",
76
- "@internal/workspace": "0.0.9",
77
- "@internal/lint": "0.0.137"
73
+ "@internal/lint": "0.0.138",
74
+ "@mastra/libsql": "1.24.1-alpha.0",
75
+ "@mastra/pg": "1.28.1-alpha.0",
76
+ "@internal/workspace": "0.0.10",
77
+ "@internal/types-builder": "0.0.113"
78
78
  },
79
79
  "engines": {
80
80
  "node": ">=22.19.0"