@mastra/factory 0.11.1 → 0.12.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/factory.d.ts +2 -22
  3. package/dist/factory.d.ts.map +1 -1
  4. package/dist/factory.js +15 -58
  5. package/dist/factory.js.map +1 -1
  6. package/dist/index.d.ts +2 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/integrations/base.d.ts +5 -13
  9. package/dist/integrations/base.d.ts.map +1 -1
  10. package/dist/integrations/github/config.d.ts +4 -4
  11. package/dist/integrations/github/config.d.ts.map +1 -1
  12. package/dist/integrations/github/config.js +3 -3
  13. package/dist/integrations/github/config.js.map +1 -1
  14. package/dist/integrations/github/integration.d.ts.map +1 -1
  15. package/dist/integrations/github/integration.js +2 -4
  16. package/dist/integrations/github/integration.js.map +1 -1
  17. package/dist/integrations/github/reconcile-worker.d.ts +0 -5
  18. package/dist/integrations/github/reconcile-worker.d.ts.map +1 -1
  19. package/dist/integrations/github/reconcile-worker.js +0 -7
  20. package/dist/integrations/github/reconcile-worker.js.map +1 -1
  21. package/dist/integrations/github/routes.d.ts +6 -5
  22. package/dist/integrations/github/routes.d.ts.map +1 -1
  23. package/dist/integrations/github/routes.js +36 -263
  24. package/dist/integrations/github/routes.js.map +1 -1
  25. package/dist/integrations/github/sandbox-release.d.ts +17 -47
  26. package/dist/integrations/github/sandbox-release.d.ts.map +1 -1
  27. package/dist/integrations/github/sandbox-release.js +24 -103
  28. package/dist/integrations/github/sandbox-release.js.map +1 -1
  29. package/dist/integrations/github/sandbox.d.ts +25 -139
  30. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  31. package/dist/integrations/github/sandbox.js +24 -212
  32. package/dist/integrations/github/sandbox.js.map +1 -1
  33. package/dist/integrations/github/webhook.d.ts.map +1 -1
  34. package/dist/integrations/github/webhook.js +1 -2
  35. package/dist/integrations/github/webhook.js.map +1 -1
  36. package/dist/integrations/platform/github/event-worker.d.ts +0 -2
  37. package/dist/integrations/platform/github/event-worker.d.ts.map +1 -1
  38. package/dist/integrations/platform/github/event-worker.js +0 -7
  39. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  40. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  41. package/dist/integrations/platform/github/integration.js +5 -7
  42. package/dist/integrations/platform/github/integration.js.map +1 -1
  43. package/dist/routes/config.d.ts.map +1 -1
  44. package/dist/routes/config.js +3 -1
  45. package/dist/routes/config.js.map +1 -1
  46. package/dist/routes/fs.d.ts +4 -6
  47. package/dist/routes/fs.d.ts.map +1 -1
  48. package/dist/routes/fs.js +28 -34
  49. package/dist/routes/fs.js.map +1 -1
  50. package/dist/routes/skills.d.ts.map +1 -1
  51. package/dist/routes/skills.js +6 -3
  52. package/dist/routes/skills.js.map +1 -1
  53. package/dist/routes/surface.d.ts +4 -9
  54. package/dist/routes/surface.d.ts.map +1 -1
  55. package/dist/routes/surface.js +3 -5
  56. package/dist/routes/surface.js.map +1 -1
  57. package/dist/sandbox/materialization.d.ts +36 -0
  58. package/dist/sandbox/materialization.d.ts.map +1 -0
  59. package/dist/sandbox/materialization.js +18 -0
  60. package/dist/sandbox/materialization.js.map +1 -0
  61. package/dist/sandbox/session-retirement.d.ts +8 -6
  62. package/dist/sandbox/session-retirement.d.ts.map +1 -1
  63. package/dist/sandbox/session-retirement.js +28 -123
  64. package/dist/sandbox/session-retirement.js.map +1 -1
  65. package/dist/sandbox/session-sandbox.d.ts +117 -0
  66. package/dist/sandbox/session-sandbox.d.ts.map +1 -0
  67. package/dist/sandbox/session-sandbox.js +132 -0
  68. package/dist/sandbox/session-sandbox.js.map +1 -0
  69. package/dist/sandbox/workdir.d.ts +30 -0
  70. package/dist/sandbox/workdir.d.ts.map +1 -0
  71. package/dist/sandbox/workdir.js +48 -0
  72. package/dist/sandbox/workdir.js.map +1 -0
  73. package/dist/session/filesystem-capture.d.ts +2 -1
  74. package/dist/session/filesystem-capture.d.ts.map +1 -1
  75. package/dist/session/filesystem-capture.js +45 -4
  76. package/dist/session/filesystem-capture.js.map +1 -1
  77. package/dist/session/first-exec-capture.d.ts +1 -0
  78. package/dist/session/first-exec-capture.d.ts.map +1 -1
  79. package/dist/session/first-exec-capture.js +1 -1
  80. package/dist/session/first-exec-capture.js.map +1 -1
  81. package/dist/storage/domains/integrations/base.d.ts +1 -1
  82. package/dist/storage/domains/integrations/base.js +1 -1
  83. package/dist/storage/domains/integrations/base.js.map +1 -1
  84. package/dist/storage/domains/source-control/base.d.ts +0 -144
  85. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  86. package/dist/storage/domains/source-control/base.js +3 -290
  87. package/dist/storage/domains/source-control/base.js.map +1 -1
  88. package/dist/storage/domains/source-control/inmemory.d.ts +1 -63
  89. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  90. package/dist/storage/domains/source-control/inmemory.js +0 -89
  91. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  92. package/dist/workspace.d.ts +5 -37
  93. package/dist/workspace.d.ts.map +1 -1
  94. package/dist/workspace.js +107 -245
  95. package/dist/workspace.js.map +1 -1
  96. package/package.json +4 -4
  97. package/dist/sandbox/base-checkpoint-triggers.d.ts +0 -77
  98. package/dist/sandbox/base-checkpoint-triggers.d.ts.map +0 -1
  99. package/dist/sandbox/base-checkpoint-triggers.js +0 -137
  100. package/dist/sandbox/base-checkpoint-triggers.js.map +0 -1
  101. package/dist/sandbox/base-checkpoint.d.ts +0 -59
  102. package/dist/sandbox/base-checkpoint.d.ts.map +0 -1
  103. package/dist/sandbox/base-checkpoint.js +0 -145
  104. package/dist/sandbox/base-checkpoint.js.map +0 -1
  105. package/dist/sandbox/fleet.d.ts +0 -258
  106. package/dist/sandbox/fleet.d.ts.map +0 -1
  107. package/dist/sandbox/fleet.js +0 -356
  108. package/dist/sandbox/fleet.js.map +0 -1
  109. package/dist/sandbox/reattach.d.ts +0 -3
  110. package/dist/sandbox/reattach.d.ts.map +0 -1
  111. package/dist/sandbox/reattach.js +0 -16
  112. package/dist/sandbox/reattach.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.js","names":["#factorySource","#fallbackSkillRoots","#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 { getDynamicWorkspace } from '@mastra/code-sdk/agents/workspace';\nimport type { 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 { LocalSandbox, LocalSkillSource, Workspace } from '@mastra/core/workspace';\nimport type { SkillSource, SkillSourceEntry, SkillSourceStat } from '@mastra/core/workspace';\nimport { getFactoryAuthUserFromContext, getFactoryAuthUserId } from './auth.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 hasExistingCheckout,\n DEFAULT_COMMAND_TIMEOUT_MS,\n MaterializeError,\n materializeRepo,\n recycleClaimedWorkdir,\n runWorktreeSetup,\n runWorktreeTeardown,\n} from './integrations/github/sandbox.js';\nimport { registerGithubPatKind, registerGithubTokenInjector } from './integrations/github/token-refresh.js';\nimport { getFactorySessionAddress } from './rules/binding-context.js';\nimport { baseCheckpointIsStale } from './sandbox/base-checkpoint-triggers.js';\nimport type { SandboxBindingStore, SandboxFleet } from './sandbox/fleet.js';\nimport type { WorkItemsStorage } from './storage/domains/work-items/base.js';\n\nconst WORKSPACE_ID_PREFIX = 'mfw';\nconst SESSION_CHECKPOINT_PREFIX = 'mastracode-session';\n\nexport function checkpointNameForSession(sessionId: string): string {\n return `${SESSION_CHECKPOINT_PREFIX}-${sessionId}`;\n}\n\n/**\n * Whether a command failure means the sandbox itself is gone (destroyed by\n * idle GC or provider teardown) AND the command provably never started, so\n * reviving the sandbox and replaying the command cannot run a side effect\n * twice. Matched by error name so any provider's equivalent error classes\n * participate without a package dependency.\n *\n * `SandboxExecTransportError` means both WebSocket attempts closed without an\n * exit frame against a live sandbox. It only proves the command never started\n * when the transport never opened (`opened: false` — the upgrade was refused\n * outright). When the transport opened, the command may have run and mutated\n * state before the result was lost, so replaying `git commit`, uploads, or\n * arbitrary shell commands could execute the side effect twice; those errors\n * surface to the caller instead.\n */\nexport function isDeadSandboxError(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n if (error.name === 'SandboxDestroyedError') return true;\n if (error.name === 'SandboxExecTransportError') {\n return (error as Error & { opened?: boolean }).opened === false;\n }\n return /sandbox .*(destroyed|no longer exists|not found)/i.test(error.message);\n}\n\n/**\n * The local-provider equivalent of {@link isDeadSandboxError}: a local sandbox\n * is just a directory, so it \"dies\" when that directory is removed — which\n * session retirement does while an in-flight run still holds the handle.\n *\n * Node surfaces a missing `cwd` as ENOENT against the binary it tried to spawn\n * (`spawn /bin/sh ENOENT`), which is textually identical to the shell itself\n * being absent, and is also what a genuinely missing command reports. Probing\n * the working directory is what separates \"the sandbox is gone\" from \"that\n * command does not exist\", so only the former triggers a rebuild.\n */\nexport function isMissingWorkdirError(error: unknown, workdir: string | undefined): boolean {\n if (!workdir) return false;\n if ((error as NodeJS.ErrnoException | null)?.code !== 'ENOENT') return false;\n return !existsSync(workdir);\n}\n\nconst bundleDirectory = dirname(fileURLToPath(import.meta.url));\nconst bundledFactorySkillsPath = join(bundleDirectory, 'factory-skills');\nexport const FACTORY_SKILLS_SOURCE_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 // Consumer repo running from its package root before a build.\n join(process.cwd(), 'src', 'mastra', 'public', 'factory-skills'),\n ].find(existsSync) ?? bundledFactorySkillsPath;\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-plan',\n 'factory-rereview',\n 'factory-review',\n 'factory-triage',\n]);\n\nclass FactorySkillSource implements SkillSource {\n readonly #factorySource = new LocalSkillSource({ basePath: FACTORY_SKILLS_SOURCE_PATH });\n readonly #fallbackSkillRoots: Set<string>;\n\n constructor(\n readonly fallback: SkillSource,\n fallbackSkillRoots: string[],\n ) {\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 exists(skillPath: string): Promise<boolean> {\n return this.#isFactoryPath(skillPath)\n ? this.#factorySource.exists(this.#factoryPath(skillPath))\n : this.fallback.exists(skillPath);\n }\n\n stat(skillPath: string): Promise<SkillSourceStat> {\n return this.#isFactoryPath(skillPath)\n ? this.#factorySource.stat(this.#factoryPath(skillPath))\n : this.fallback.stat(skillPath);\n }\n\n readFile(skillPath: string): Promise<string | Buffer> {\n return this.#isFactoryPath(skillPath)\n ? this.#factorySource.readFile(this.#factoryPath(skillPath))\n : this.fallback.readFile(skillPath);\n }\n\n async readdir(skillPath: string): Promise<SkillSourceEntry[]> {\n if (this.#isFactoryPath(skillPath)) {\n return this.#factorySource.readdir(this.#factoryPath(skillPath));\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\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\nconst factorySkillExtension: WorkspaceSkillExtension = {\n id: 'web-factory',\n paths: [FACTORY_SKILLS_MOUNT],\n createSource: (fallback, fallbackSkillRoots) => new FactorySkillSource(fallback, fallbackSkillRoots),\n};\n\ntype DynamicWorkspaceContext = Parameters<typeof getDynamicWorkspace>[0];\n\nexport interface CreateWorkspaceFactoryOptions {\n /** Factory sandbox runtime config (template machine + workdir base). */\n sandbox?: MastraFactorySandboxConfig;\n /** GitHub integration used to resolve Factory sessions and mint repo tokens. */\n github?: GithubIntegration;\n /** Fleet the per-session sandboxes are provisioned/reattached through. */\n fleet?: SandboxFleet;\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, 'findRunBindingBySession'>;\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\nexport function createWorkspaceFactory(options: CreateWorkspaceFactoryOptions = {}) {\n const { sandbox: sandboxConfig, github, fleet, workItems } = options;\n const workspaceRegistry = options.workspaceRegistry ?? new FactoryWorkspaceRegistry();\n const isLocalSandbox = sandboxConfig?.machine instanceof LocalSandbox;\n type GithubTokenRegistration = {\n inject: (token: string) => void;\n patKind: GithubPatKind;\n ghToken: string;\n generation: number;\n tokenReplacementPending: boolean;\n };\n type FleetSandbox = Awaited<ReturnType<SandboxFleet['ensureSandbox']>>;\n const githubTokenInjectors = new Map<string, GithubTokenRegistration>();\n const githubTokenReconciliations = new Map<string, Promise<void>>();\n // Concurrent requests for the same session (thread list + activity polling +\n // chat) must not each provision a sandbox and clone the repository. The\n // first caller materializes; followers await the same promise. Failed\n // materializations are dropped from the map so the next use retries.\n const inflightMaterializations = new Map<string, Promise<FleetSandbox>>();\n // Fully materialized sandboxes, keyed by workspace id. The lazy sandbox\n // handle delegates here once materialization completed.\n const materializedSandboxes = new Map<string, FleetSandbox>();\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\n return async ({ requestContext, mastra, skillExtension }: DynamicWorkspaceContext) => {\n const effectiveSkillExtension = skillExtension ?? factorySkillExtension;\n const ctx = requestContext.get('controller') as AgentControllerRequestContext<MastraCodeState> | undefined;\n const session =\n ctx?.resourceId && github ? await github.sourceControlStorage.sessions.getBySessionId(ctx.resourceId) : null;\n\n if (!session) {\n if (sandboxConfig && !isLocalSandbox) {\n // Chat-only session on a remote-sandbox deploy: there is no repository\n // to materialize, and the server host must never execute commands on a\n // shared deployment. Run the session without a workspace (chat works,\n // workspace tools are simply not registered) instead of erroring on\n // every message.\n return undefined;\n }\n return getDynamicWorkspace({ requestContext, mastra, skillExtension: effectiveSkillExtension });\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 || !github || !fleet) {\n throw new Error('GitHub and sandbox providers are required to create a Factory session workspace');\n }\n\n const storage = github.sourceControlStorage;\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) throw new Error(`GitHub installation ${connection.installationId} was not found`);\n const repoFullName = repository.slug;\n\n let workdir = isLocalSandbox\n ? fleet.computeLocalSessionWorkdir(repoFullName, session.id)\n : (session.sandboxWorkdir ?? projectRepository.sandboxWorkdir);\n // The system prompt derives its working directory from `state.projectPath`\n // and falls back to the server's own process.cwd() when unset — which\n // points the agent at the host checkout (and lets it run `git checkout`\n // there instead of in its session workdir). Pin it to the session workdir.\n // During createSession this seeds the session's initial state (the\n // workspace resolves before the session is built); on later requests it\n // self-heals live state.\n let stateSeeded = false;\n if (ctx && workdir && ctx.getState()?.projectPath !== workdir) {\n await ctx.setState({ projectPath: workdir, projectName: repoFullName });\n stateSeeded = true;\n }\n const binding: SandboxBindingStore = {\n // Read through to the session row so teardown after a fresh provision\n // sees the just-persisted id instead of a stale snapshot.\n get sandboxId() {\n return session.sandboxId;\n },\n checkpointName: checkpointNameForSession(session.id),\n // Boot-only fallback: a brand-new session (no session checkpoint yet)\n // seeds from the repo's warm base checkpoint when one is available and\n // still matches the current setup command. Snapshots keep writing to\n // the session checkpoint, so the shared base image is never mutated.\n ...(!session.materializedAt && projectRepository.baseCheckpoint && !baseCheckpointIsStale(projectRepository)\n ? { seedCheckpointName: projectRepository.baseCheckpoint.name }\n : {}),\n setSandboxId: async id => {\n await storage.sessions.setSandbox({ id: session.id, sandboxId: id, sandboxWorkdir: workdir });\n session.sandboxId = id;\n session.sandboxWorkdir = workdir;\n },\n clear: async () => {\n await storage.sessions.setSandbox({ id: session.id, sandboxId: null, sandboxWorkdir: workdir });\n session.sandboxId = null;\n },\n };\n\n const extensionId = effectiveSkillExtension ? `-${effectiveSkillExtension.id}` : '';\n const workspaceId = `${WORKSPACE_ID_PREFIX}-${projectRepository.id}-${session.id}${extensionId}`;\n const workspaceGeneration = workspaceRegistry.generation(session.sessionId);\n const configDir = sandboxConfig.workdir ?? DEFAULT_CONFIG_DIR;\n\n const getRepositoryToken = async (): Promise<string> => {\n const access = await github.versionControl.getRepositoryAccess({\n orgId: session.orgId,\n repositoryId: repository.id,\n });\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 return token;\n };\n const resolveGithubPatKind = async (fallback: GithubPatKind): Promise<GithubPatKind> => {\n if (!workItems) return 'default';\n try {\n const address = getFactorySessionAddress(requestContext);\n const runBinding = address ? await workItems.findRunBindingBySession(address) : null;\n return runBinding?.role === 'review' && runBinding.status === 'active' && runBinding.orgId === session.orgId\n ? 'reviewer'\n : '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 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(() => github.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 materializedSandboxes.delete(workspaceId);\n constructedWorkspaces.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 // 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 const materializeSandbox = async (): Promise<FleetSandbox> => {\n // A session already retired by the time a held lazy handle re-enters\n // materialization must not provision anything — bail before touching\n // the pool or the fleet budget.\n if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) {\n throw retiredError();\n }\n // A terminal work item or a deleted session may have returned a\n // still-warm VM — with this repository already cloned — to the reuse\n // pool. Adopt it before provisioning a fresh sandbox. Pooled VMs carry\n // no credentials (tokens are injected per command, and the workdir is\n // scrubbed on release and again below), so any user's session for this\n // repository can claim one.\n let claimedPooledSandbox = false;\n if (!isLocalSandbox && !session.sandboxId) {\n const pooled = await storage.sandboxPool.claim({\n projectRepositoryId: session.projectRepositoryId,\n });\n if (pooled) {\n await storage.sessions.setSandbox({\n id: session.id,\n sandboxId: pooled.sandboxId,\n sandboxWorkdir: pooled.sandboxWorkdir,\n });\n session.sandboxId = pooled.sandboxId;\n session.sandboxWorkdir = pooled.sandboxWorkdir;\n workdir = pooled.sandboxWorkdir;\n claimedPooledSandbox = true;\n }\n }\n\n const token = await getRepositoryToken();\n\n // The `gh` CLI needs a PAT when the org configured one (installation\n // tokens 403 on integration-restricted endpoints); git clone/checkout\n // below keep using the minted installation token. Review-board sessions\n // (run-binding role `review`) authenticate `gh` as the reviewer account\n // when a reviewer token is configured; everything else — including\n // sessions with no resolvable run binding — uses the worker token.\n const patKind = await resolveGithubPatKind('default');\n const ghCliToken = (await getGithubPat(() => github.integrationStorage, session.orgId, patKind)) ?? token;\n\n const ensureSandbox = () =>\n fleet.ensureSandbox(binding, { GH_TOKEN: ghCliToken }, undefined, {\n ...(isLocalSandbox ? { workingDirectory: workdir } : {}),\n actingUserId: userId,\n });\n const runMaterialize = (target: Awaited<ReturnType<typeof ensureSandbox>>, skipPull: boolean) =>\n materializeRepo({\n row: { id: session.id, sandboxWorkdir: workdir, materializedAt: session.materializedAt },\n repoInfo: { repoFullName: repoFullName, defaultBranch: repository.defaultBranch },\n sandbox: target,\n token,\n storage: storage.sessions,\n // A checkpoint-seeded checkout is already at (or minutes behind) the\n // default branch HEAD — skip the redundant network pull so the first\n // agent turn isn't stalled behind it.\n skipPullOnExistingCheckout: skipPull,\n });\n const isGitMissing = (error: unknown) => error instanceof MaterializeError && error.code === 'git-missing';\n\n let sandbox = await ensureSandbox();\n // A claimed VM still has the previous session's branch checked out —\n // reset it to the default branch before materialize/checkout. When the\n // pooled VM was already reaped, `ensureSandbox` provisioned fresh and\n // the recycle is a no-op (no checkout on disk yet).\n if (claimedPooledSandbox) await recycleClaimedWorkdir(sandbox, workdir, repository.defaultBranch);\n // A never-materialized session whose workdir already holds this repo's\n // checkout was seeded from the warm base checkpoint — the setup command\n // already ran during the base build, so skip it below.\n const seededFromBaseCheckpoint =\n !!binding.seedCheckpointName &&\n !session.materializedAt &&\n !claimedPooledSandbox &&\n (await hasExistingCheckout(sandbox, workdir, repoFullName));\n try {\n await runMaterialize(sandbox, seededFromBaseCheckpoint);\n } catch (error) {\n if (!isGitMissing(error)) throw error;\n // A sandbox without git was booted from a bare base image (e.g. the\n // platform proxy falls back to a clean Debian base when its template\n // build fails). That VM can never materialize a repo, and its id is\n // already persisted on the binding — tear it down so re-opens stop\n // reattaching to the poisoned sandbox, then retry once on a fresh VM.\n await fleet.teardownSandbox(binding, sandbox);\n sandbox = await ensureSandbox();\n try {\n // The retry runs on a freshly provisioned VM with no checkout, so\n // the skip flag is moot — pass false to take the normal clone path.\n await runMaterialize(sandbox, false);\n } catch (retryError) {\n // Still bare — the provider's template is persistently broken.\n // Clear the binding so a later manual retry provisions fresh.\n if (isGitMissing(retryError)) await fleet.teardownSandbox(binding, sandbox);\n throw retryError;\n }\n }\n await checkoutSessionBranch(sandbox, workdir, {\n branch: session.branch,\n baseBranch: session.baseBranch || projectRepository.branch || repository.defaultBranch,\n token,\n repoFullName: repoFullName,\n });\n // A checkpoint-seeded checkout already ran the setup command during the\n // base build, so re-running it here is pure latency.\n if (projectRepository.setupCommand && !seededFromBaseCheckpoint) {\n try {\n await runWorktreeSetup(sandbox, workdir, projectRepository.setupCommand);\n } catch (setupError) {\n if (projectRepository.teardownCommand) {\n try {\n await runWorktreeTeardown(sandbox, 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 throw setupError;\n }\n }\n\n const tokenRegistration: GithubTokenRegistration = {\n inject: freshToken => {\n if (!sandbox.setEnvironmentVariable) {\n throw new Error('The active sandbox provider does not support runtime GitHub token refresh.');\n }\n sandbox.setEnvironmentVariable('GH_TOKEN', freshToken);\n tokenRegistration.ghToken = freshToken;\n },\n patKind,\n ghToken: ghCliToken,\n generation: 0,\n tokenReplacementPending: false,\n };\n // The session can be retired while this deferred phase is in flight.\n // Registration happened back at construction time (the workspace exists\n // before it materializes), so the retirement callback has already torn\n // the workspace down — tear the just-built sandbox back down (freeing\n // its binding and, on remote providers, its fleet budget slot) and\n // surface the retirement to the caller instead of handing back a\n // sandbox belonging to a dead session.\n if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) {\n try {\n await fleet.teardownSandbox(binding, sandbox);\n } catch (teardownError) {\n console.warn('[Mastra Factory] Sandbox teardown after mid-materialization retirement failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n error: teardownError instanceof Error ? teardownError.message.slice(-2000) : String(teardownError),\n });\n }\n throw retiredError();\n }\n githubTokenInjectors.set(workspaceId, tokenRegistration);\n registerGithubTokenContext(tokenRegistration);\n return sandbox;\n };\n\n // Memoized deferred phase. The first caller (a background warm-up at\n // session start, or the first FS/sandbox operation) materializes; followers\n // await the same in-flight promise. Failures are dropped from the map so\n // the next use retries instead of caching a broken sandbox.\n const ensureMaterialized = async (): Promise<FleetSandbox> => {\n const ready = materializedSandboxes.get(workspaceId);\n if (ready) return ready;\n let inflight = inflightMaterializations.get(workspaceId);\n if (!inflight) {\n inflight = materializeSandbox();\n inflightMaterializations.set(workspaceId, inflight);\n inflight.then(\n sb => {\n materializedSandboxes.set(workspaceId, sb);\n // Project skill roots (.claude/skills etc.) were reported empty by\n // the unmaterialized-source guard during discovery; rescan now that\n // the checkout exists so repo-local skills become visible without\n // waiting for the maybeRefresh cooldown. Fire-and-forget.\n void workspace.skills?.refresh().catch(() => {});\n },\n () => {},\n );\n }\n try {\n return await inflight;\n } finally {\n if (inflightMaterializations.get(workspaceId) === inflight) {\n inflightMaterializations.delete(workspaceId);\n }\n }\n };\n\n // Lazy sandbox handle: resolution returns immediately and the sandbox\n // work (provision/boot-from-checkpoint + materialize + checkout + setup)\n // runs on first use. Metadata-only resolutions (thread-list polling)\n // never touch it.\n const lazySandbox = {\n get id() {\n return materializedSandboxes.get(workspaceId)?.id ?? workspaceId;\n },\n name: 'Factory Lazy Sandbox',\n get provider() {\n return fleet.provider;\n },\n get status() {\n return materializedSandboxes.has(workspaceId) ? 'ready' : 'pending';\n },\n get supportsCheckpoints() {\n return materializedSandboxes.get(workspaceId)?.supportsCheckpoints ?? false;\n },\n getInstructions() {\n // Prefer the live sandbox's instructions once materialized; before\n // that, forward the configured template machine's instructions so\n // tool descriptions are accurate without forcing materialization.\n return materializedSandboxes.get(workspaceId)?.getInstructions?.() ?? fleet.getInstructions();\n },\n clone(): never {\n throw new Error('The Factory session sandbox cannot be cloned from a lazy handle.');\n },\n async start() {\n // Intentionally a no-op. `Workspace.init()` calls `sandbox.start()`\n // during session creation, and sessions are get-or-created by\n // metadata-only GET routes (/threads, /messages). Materializing here\n // would provision a sandbox for every read-only poll. The sandbox\n // materializes on first real use (executeCommand/getInfo) instead.\n },\n async getInfo() {\n const sandbox = await ensureMaterialized();\n return sandbox.getInfo();\n },\n async executeCommand(command: string, args?: string[], options?: Record<string, unknown>) {\n const sandbox = await ensureMaterialized();\n try {\n return await sandbox.executeCommand(command, args, options);\n } catch (error) {\n if (!isDeadSandboxError(error) && !(isLocalSandbox && isMissingWorkdirError(error, workdir))) throw error;\n // The sandbox died mid-session (idle GC, provider destroy, broken\n // transport, or a retired local checkout removed from under us).\n // Drop the dead handle and re-run the materialization\n // pipeline — fleet's ensureSandbox walks the revival ladder\n // (reattach → checkpoint-seeded provision → fresh clone) — then\n // retry the command once. Concurrent failures coalesce onto the\n // same revival through `inflightMaterializations`.\n if (materializedSandboxes.get(workspaceId) === sandbox) {\n materializedSandboxes.delete(workspaceId);\n }\n const revived = await ensureMaterialized();\n return revived.executeCommand(command, args, options);\n }\n },\n setEnvironmentVariable(name: string, value: string) {\n const sandbox = materializedSandboxes.get(workspaceId);\n if (!sandbox?.setEnvironmentVariable) {\n throw new Error('The Factory session sandbox is not materialized yet.');\n }\n sandbox.setEnvironmentVariable(name, value);\n },\n async snapshot() {\n // Nothing to checkpoint before the sandbox exists.\n await materializedSandboxes.get(workspaceId)?.snapshot?.();\n },\n async stop() {\n await materializedSandboxes.get(workspaceId)?.stop?.();\n },\n };\n\n const filesystem = new SandboxFilesystem({\n id: `sandbox-fs:${workspaceId}:${workdir}`,\n sandbox: lazySandbox,\n workdir,\n });\n const projectSkillPaths = [path.join(configDir, 'skills'), '.claude/skills', '.agents/skills'];\n const guardedSkillFallback = new UnmaterializedAwareSkillSource(filesystem, () =>\n materializedSandboxes.has(workspaceId),\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: lazySandbox 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 (the\n // deferred phase is deduped separately through `inflightMaterializations`).\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 now eager while materialization is deferred, so\n // a session retired before its first tool call still has a workspace (and 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 materializedSandboxes.delete(workspaceId);\n constructedWorkspaces.delete(workspaceId);\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 // Session start (the resolution that seeds the session's initial state)\n // warms the sandbox in the background so it materializes in parallel with\n // the model's first turn instead of on the first tool call.\n if (stateSeeded) {\n void ensureMaterialized().catch(error => {\n console.error(`[factory:workspace] background materialization for ${workspaceId} failed`, error);\n });\n }\n return workspace;\n };\n}\n\nexport const getFactoryWorkspace = createWorkspaceFactory();\n"],"mappings":"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"}
1
+ {"version":3,"file":"workspace.js","names":["#factorySource","#fallbackSkillRoots","#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 { getFactoryAuthUserFromContext, getFactoryAuthUserId } from './auth.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 { getFactorySessionAddress } from './rules/binding-context.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';\n\nimport type { WorkItemsStorage } from './storage/domains/work-items/base.js';\n\nconst WORKSPACE_ID_PREFIX = 'mfw';\nconst bundleDirectory = dirname(fileURLToPath(import.meta.url));\nconst bundledFactorySkillsPath = join(bundleDirectory, 'factory-skills');\nexport const FACTORY_SKILLS_SOURCE_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 // Consumer repo running from its package root before a build.\n join(process.cwd(), 'src', 'mastra', 'public', 'factory-skills'),\n ].find(existsSync) ?? bundledFactorySkillsPath;\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-plan',\n 'factory-rereview',\n 'factory-review',\n 'factory-triage',\n]);\n\nclass FactorySkillSource implements SkillSource {\n readonly #factorySource = new LocalSkillSource({ basePath: FACTORY_SKILLS_SOURCE_PATH });\n readonly #fallbackSkillRoots: Set<string>;\n\n constructor(\n readonly fallback: SkillSource,\n fallbackSkillRoots: string[],\n ) {\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 exists(skillPath: string): Promise<boolean> {\n return this.#isFactoryPath(skillPath)\n ? this.#factorySource.exists(this.#factoryPath(skillPath))\n : this.fallback.exists(skillPath);\n }\n\n stat(skillPath: string): Promise<SkillSourceStat> {\n return this.#isFactoryPath(skillPath)\n ? this.#factorySource.stat(this.#factoryPath(skillPath))\n : this.fallback.stat(skillPath);\n }\n\n readFile(skillPath: string): Promise<string | Buffer> {\n return this.#isFactoryPath(skillPath)\n ? this.#factorySource.readFile(this.#factoryPath(skillPath))\n : this.fallback.readFile(skillPath);\n }\n\n async readdir(skillPath: string): Promise<SkillSourceEntry[]> {\n if (this.#isFactoryPath(skillPath)) {\n return this.#factorySource.readdir(this.#factoryPath(skillPath));\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\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\nconst factorySkillExtension: WorkspaceSkillExtension = {\n id: 'web-factory',\n paths: [FACTORY_SKILLS_MOUNT],\n createSource: (fallback, fallbackSkillRoots) => new FactorySkillSource(fallback, fallbackSkillRoots),\n};\n\ntype DynamicWorkspaceContext = Parameters<typeof getDynamicWorkspace>[0];\n\nexport interface CreateWorkspaceFactoryOptions {\n /** Factory sandbox runtime config (session sandbox callback). */\n sandbox?: MastraFactorySandboxConfig;\n /** GitHub integration used to resolve Factory sessions and mint repo tokens. */\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, 'findRunBindingBySession'>;\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\nexport function createWorkspaceFactory(options: CreateWorkspaceFactoryOptions = {}) {\n const { sandbox: sandboxConfig, github, workItems } = options;\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\n return async ({ requestContext, mastra, skillExtension }: DynamicWorkspaceContext) => {\n const effectiveSkillExtension = skillExtension ?? factorySkillExtension;\n const ctx = requestContext.get('controller') as AgentControllerRequestContext<MastraCodeState> | undefined;\n const session =\n ctx?.resourceId && github ? await github.sourceControlStorage.sessions.getBySessionId(ctx.resourceId) : null;\n\n if (!session) {\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 || !github) {\n throw new Error('GitHub and a sandbox callback are required to create a Factory session workspace');\n }\n const createSessionSandboxInstance = sandboxConfig;\n\n const storage = github.sourceControlStorage;\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) throw new Error(`GitHub installation ${connection.installationId} was not found`);\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) =>\n runSessionSetup(requireExec(target as WorkspaceSandbox), workdir);\n const guardedSetup = createSessionSetupHook(runSetupOn, session.id, repoFullName);\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 // The `gh` CLI needs a PAT when the org configured one (installation\n // tokens 403 on integration-restricted endpoints); git clone/checkout\n // keep using the minted installation token. Resolved per start so the\n // installed credential never outlives rotation.\n const patKind = await resolveGithubPatKind('default');\n const ghCliToken =\n (await getGithubPat(() => github.integrationStorage, session.orgId, patKind)) ?? (await getRepositoryToken());\n target.setEnv?.(env => ({ ...env, GH_TOKEN: ghCliToken }));\n // Observability only — nothing reads these columns for decisions. The\n // workdir was resolved (and memoized on the entry) by the guarded setup.\n void storage.sessions\n .setSandbox({ id: session.id, sandboxId: target.id, sandboxWorkdir: sessionEntry.workdir ?? '' })\n .catch(() => {});\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 // Project skill roots were reported empty by the unmaterialized-source\n // guard before the checkout existed; rescan now. Fire-and-forget.\n void constructedWorkspaces\n .get(workspaceId)\n ?.skills?.refresh()\n .catch(() => {});\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 github.versionControl.getRepositoryAccess({ orgId: session.orgId, repositoryId: repository.id }),\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 setupHook(args);\n await previous?.(args);\n });\n return sandbox;\n });\n const sessionEntry = constructSessionEntry();\n const workdir = sessionEntry.workdir;\n const isLocalSandbox = sessionEntry.sandbox.provider === 'local';\n // The system prompt derives its working directory from `state.projectPath`\n // and falls back to the server's own process.cwd() when unset — which\n // points the agent at the host checkout (and lets it run `git checkout`\n // there instead of in its session workdir). Pin it to the session workdir\n // once known. A remote workdir resolves at the sandbox's first start, so\n // the pin self-heals on the next resolution after the VM has run.\n if (ctx && workdir && ctx.getState()?.projectPath !== workdir) {\n await ctx.setState({ projectPath: workdir, projectName: repoFullName });\n }\n\n const extensionId = effectiveSkillExtension ? `-${effectiveSkillExtension.id}` : '';\n const workspaceId = `${WORKSPACE_ID_PREFIX}-${projectRepository.id}-${session.id}${extensionId}`;\n const workspaceGeneration = workspaceRegistry.generation(session.sessionId);\n const configDir = DEFAULT_CONFIG_DIR;\n\n const getRepositoryToken = async (): Promise<string> => {\n const access = await github.versionControl.getRepositoryAccess({\n orgId: session.orgId,\n repositoryId: repository.id,\n });\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 return token;\n };\n const resolveGithubPatKind = async (fallback: GithubPatKind): Promise<GithubPatKind> => {\n if (!workItems) return 'default';\n try {\n const address = getFactorySessionAddress(requestContext);\n const runBinding = address ? await workItems.findRunBindingBySession(address) : null;\n return runBinding?.role === 'review' && runBinding.status === 'active' && runBinding.orgId === session.orgId\n ? 'reviewer'\n : '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 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(() => github.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 }\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 // 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): Promise<void> => {\n const token = await getRepositoryToken();\n // The configured setup command may shell out to `gh`/https fetches, so\n // GH_TOKEN must exist before setup runs — and it must be the same\n // gh-capable credential the session gets after start (installation\n // tokens 403 on integration-restricted endpoints when the org\n // configured a PAT).\n const setupPatKind = await resolveGithubPatKind('default');\n const setupGhToken = (await getGithubPat(() => github.integrationStorage, session.orgId, setupPatKind)) ?? token;\n target.setEnv?.(env => ({ ...env, GH_TOKEN: setupGhToken }));\n await materializeRepo({\n row: { id: session.id, sandboxWorkdir: workdir, materializedAt: session.materializedAt },\n repoInfo: { repoFullName: repoFullName, defaultBranch: repository.defaultBranch },\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: repoFullName,\n });\n if (projectRepository.setupCommand) {\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 runSetupCommand(target, workdir, projectRepository.setupCommand);\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 // 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 // Fully lazy: nothing provisions until the first real sandbox operation\n // (`ensureRunning()` inside the provider). A background warm-up at session\n // start was considered and dropped — it speculatively created a VM for\n // every session, including ones whose agent never touches the workspace.\n return workspace;\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/factory",
3
- "version": "0.11.1",
3
+ "version": "0.12.0-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": {
@@ -52,10 +52,10 @@
52
52
  "hono": "^4.12.8",
53
53
  "zod": "^4.3.6",
54
54
  "@mastra/auth-studio": "1.3.4",
55
+ "@mastra/core": "1.63.3-alpha.0",
56
+ "@mastra/code-sdk": "1.6.0-alpha.0",
55
57
  "@mastra/auth-workos": "1.6.4",
56
- "@mastra/core": "1.63.2",
57
- "@mastra/slack": "1.6.2",
58
- "@mastra/code-sdk": "1.5.3"
58
+ "@mastra/slack": "1.6.2"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/node": "22.20.1",
@@ -1,77 +0,0 @@
1
- /**
2
- * Trigger surface for base-checkpoint builds.
3
- *
4
- * Three entry points feed the {@link BaseCheckpointBuilder}:
5
- * - repo connect: the projects route calls `onProjectRepositoryLinked` right
6
- * after `projectRepositories.link()` succeeds — the initial build.
7
- * - webhooks: `onWebhookEvent` classifies merged PRs and pushes targeting the
8
- * repository's default branch and requests a rebuild for every factory
9
- * project linked to that external repository.
10
- * - reconcile sweep: `sweep()` walks configured repositories and rebuilds any
11
- * whose checkpoint is missing or whose setup command changed since it was
12
- * built (webhooks can be missed; setup edits invalidate metadata).
13
- *
14
- * Every trigger is fire-and-forget relative to its caller: classification
15
- * errors log, builds coalesce inside the builder, and nothing here ever blocks
16
- * webhook dispatch, route responses, or the sweep lease.
17
- */
18
- import type { IMastraLogger } from '@mastra/core/logger';
19
- import type { RepositoryAccess } from '../capabilities/version-control.js';
20
- import type { ParsedGithubWebhook } from '../integrations/github/webhook.js';
21
- import type { ProjectRepository, SourceControlStorageHandle } from '../storage/domains/source-control/base.js';
22
- import type { BaseCheckpointBuilder } from './base-checkpoint.js';
23
- import type { SandboxFleet } from './fleet.js';
24
- /** The slice of the GitHub integration the triggers need. */
25
- export interface BaseCheckpointGithubSource {
26
- readonly sourceControlStorage: SourceControlStorageHandle;
27
- readonly versionControl?: {
28
- getRepositoryAccess(input: {
29
- orgId: string;
30
- repositoryId: string;
31
- }): Promise<RepositoryAccess>;
32
- };
33
- }
34
- export interface BaseCheckpointTriggers {
35
- /** Kick the initial build after a repo is linked to a factory project. */
36
- onProjectRepositoryLinked(args: {
37
- orgId: string;
38
- projectRepository: ProjectRepository;
39
- }): void;
40
- /** Classify a webhook and request rebuilds for default-branch updates. */
41
- onWebhookEvent(parsed: ParsedGithubWebhook): void;
42
- /** Rebuild stale/missing checkpoints for all configured repositories. */
43
- sweep(): Promise<void>;
44
- }
45
- /**
46
- * True when the webhook is a merged pull request into — or a push to — the
47
- * repository's default branch. Returns the external id pair used to find the
48
- * linked factory projects, or null when the event is not a rebuild trigger.
49
- */
50
- export declare function classifyDefaultBranchUpdate(parsed: ParsedGithubWebhook): {
51
- installationExternalId: string;
52
- repositoryExternalId: string;
53
- } | null;
54
- /**
55
- * Rebuild sweep age bound: even when webhooks are healthy, a checkpoint older
56
- * than this is rebuilt so a missed default-branch webhook cannot pin sessions
57
- * to an old commit indefinitely.
58
- */
59
- export declare const BASE_CHECKPOINT_MAX_AGE_MS: number;
60
- /**
61
- * A checkpoint is stale when absent, built against a different setup command,
62
- * or older than {@link BASE_CHECKPOINT_MAX_AGE_MS}.
63
- */
64
- export declare function baseCheckpointIsStale(projectRepository: ProjectRepository, now?: Date): boolean;
65
- export declare function createBaseCheckpointTriggers(options: {
66
- builder: BaseCheckpointBuilder;
67
- fleet: SandboxFleet;
68
- github: BaseCheckpointGithubSource;
69
- logger?: IMastraLogger;
70
- }): BaseCheckpointTriggers;
71
- /**
72
- * Wrap a webhook ingest callback so every parsed event also feeds the
73
- * base-checkpoint triggers. Trigger dispatch is fire-and-forget; the wrapped
74
- * ingest keeps its original awaited semantics.
75
- */
76
- export declare function withBaseCheckpointWebhookTrigger(ingest: ((event: ParsedGithubWebhook) => Promise<unknown>) | undefined, triggers: BaseCheckpointTriggers | undefined): ((event: ParsedGithubWebhook) => Promise<unknown>) | undefined;
77
- //# sourceMappingURL=base-checkpoint-triggers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"base-checkpoint-triggers.d.ts","sourceRoot":"","sources":["../../src/sandbox/base-checkpoint-triggers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,KAAK,EAEV,iBAAiB,EACjB,0BAA0B,EAC3B,MAAM,2CAA2C,CAAC;AACnD,OAAO,KAAK,EAAE,qBAAqB,EAAqB,MAAM,sBAAsB,CAAC;AAErF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,6DAA6D;AAC7D,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,oBAAoB,EAAE,0BAA0B,CAAC;IAC1D,QAAQ,CAAC,cAAc,CAAC,EAAE;QACxB,mBAAmB,CAAC,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;KAChG,CAAC;CACH;AAED,MAAM,WAAW,sBAAsB;IACrC,0EAA0E;IAC1E,yBAAyB,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,iBAAiB,EAAE,iBAAiB,CAAA;KAAE,GAAG,IAAI,CAAC;IAC/F,0EAA0E;IAC1E,cAAc,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAClD,yEAAyE;IACzE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,mBAAmB,GAC1B;IAAE,sBAAsB,EAAE,MAAM,CAAC;IAAC,oBAAoB,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAqBzE;AAED;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,QAAsB,CAAC;AAK9D;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,iBAAiB,EAAE,iBAAiB,EAAE,GAAG,GAAE,IAAiB,GAAG,OAAO,CAK3G;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE;IACpD,OAAO,EAAE,qBAAqB,CAAC;IAC/B,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,EAAE,0BAA0B,CAAC;IACnC,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB,GAAG,sBAAsB,CA6EzB;AAED;;;;GAIG;AACH,wBAAgB,gCAAgC,CAC9C,MAAM,EAAE,CAAC,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS,EACtE,QAAQ,EAAE,sBAAsB,GAAG,SAAS,GAC3C,CAAC,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS,CAMhE"}
@@ -1,137 +0,0 @@
1
- import { hashSetupCommand } from "./base-checkpoint.js";
2
- //#region src/sandbox/base-checkpoint-triggers.ts
3
- /**
4
- * True when the webhook is a merged pull request into — or a push to — the
5
- * repository's default branch. Returns the external id pair used to find the
6
- * linked factory projects, or null when the event is not a rebuild trigger.
7
- */
8
- function classifyDefaultBranchUpdate(parsed) {
9
- const payload = parsed.payload;
10
- const repository = asRecord(payload.repository);
11
- const installationId = idString(asRecord(payload.installation)?.id);
12
- const repositoryId = idString(repository?.id);
13
- const defaultBranch = typeof repository?.default_branch === "string" ? repository.default_branch : null;
14
- if (!installationId || !repositoryId || !defaultBranch) return null;
15
- if (parsed.event === "pull_request" && payload.action === "closed") {
16
- const pullRequest = asRecord(payload.pull_request);
17
- const base = asRecord(pullRequest?.base);
18
- if (pullRequest?.merged === true && base?.ref === defaultBranch) return {
19
- installationExternalId: installationId,
20
- repositoryExternalId: repositoryId
21
- };
22
- return null;
23
- }
24
- if (parsed.event === "push" && payload.ref === `refs/heads/${defaultBranch}`) return {
25
- installationExternalId: installationId,
26
- repositoryExternalId: repositoryId
27
- };
28
- return null;
29
- }
30
- /**
31
- * Rebuild sweep age bound: even when webhooks are healthy, a checkpoint older
32
- * than this is rebuilt so a missed default-branch webhook cannot pin sessions
33
- * to an old commit indefinitely.
34
- */
35
- const BASE_CHECKPOINT_MAX_AGE_MS = 1440 * 60 * 1e3;
36
- /** How many sweep-triggered checkpoint builds may run at once. */
37
- const SWEEP_BUILD_CONCURRENCY = 3;
38
- /**
39
- * A checkpoint is stale when absent, built against a different setup command,
40
- * or older than {@link BASE_CHECKPOINT_MAX_AGE_MS}.
41
- */
42
- function baseCheckpointIsStale(projectRepository, now = /* @__PURE__ */ new Date()) {
43
- const checkpoint = projectRepository.baseCheckpoint;
44
- if (!checkpoint) return true;
45
- if (checkpoint.setupCommandHash !== hashSetupCommand(projectRepository.setupCommand)) return true;
46
- return now.getTime() - checkpoint.builtAt.getTime() > BASE_CHECKPOINT_MAX_AGE_MS;
47
- }
48
- function createBaseCheckpointTriggers(options) {
49
- const { builder, fleet, github, logger } = options;
50
- const storage = github.sourceControlStorage;
51
- const jobFor = async (orgId, projectRepository) => {
52
- const getRepositoryAccess = github.versionControl?.getRepositoryAccess.bind(github.versionControl);
53
- if (!fleet.enabled || !getRepositoryAccess) return null;
54
- const repository = await storage.repositories.get({
55
- orgId,
56
- id: projectRepository.repositoryId
57
- });
58
- if (!repository) return null;
59
- return {
60
- projectRepositoryId: projectRepository.id,
61
- repoFullName: repository.slug,
62
- defaultBranch: repository.defaultBranch,
63
- setupCommand: projectRepository.setupCommand,
64
- workdir: fleet.computeWorkdir(repository.slug),
65
- getToken: async () => {
66
- const token = (await getRepositoryAccess({
67
- orgId,
68
- repositoryId: repository.id
69
- })).authorization?.token;
70
- if (!token) throw new Error(`No installation token available for ${repository.slug}`);
71
- return token;
72
- },
73
- storage
74
- };
75
- };
76
- const request = async (orgId, projectRepository) => {
77
- const job = await jobFor(orgId, projectRepository);
78
- if (job) await builder.request(job);
79
- };
80
- const requestTargets = async (targets) => {
81
- for (const target of targets) await request(target.orgId, target.projectRepository);
82
- };
83
- const warn = (context, error) => {
84
- logger?.warn?.(`Base-checkpoint trigger failed (${context}): ${error instanceof Error ? error.message : String(error)}`);
85
- };
86
- return {
87
- onProjectRepositoryLinked({ orgId, projectRepository }) {
88
- request(orgId, projectRepository).catch((error) => warn("repo-connect", error));
89
- },
90
- onWebhookEvent(parsed) {
91
- const key = classifyDefaultBranchUpdate(parsed);
92
- if (!key) return;
93
- storage.projectRepositories.listByExternalRepository(key).then((targets) => requestTargets(targets)).catch((error) => warn("webhook", error));
94
- },
95
- async sweep() {
96
- try {
97
- const keys = await storage.projectRepositories.listConfiguredExternalKeys();
98
- const stale = [];
99
- for (const key of keys) {
100
- const targets = await storage.projectRepositories.listByExternalRepository(key);
101
- stale.push(...targets.filter((target) => baseCheckpointIsStale(target.projectRepository)));
102
- }
103
- const queue = [...stale];
104
- const workers = Array.from({ length: Math.min(SWEEP_BUILD_CONCURRENCY, queue.length) }, async () => {
105
- for (let target = queue.shift(); target; target = queue.shift()) await request(target.orgId, target.projectRepository).catch((error) => warn("sweep-build", error));
106
- });
107
- await Promise.all(workers);
108
- } catch (error) {
109
- warn("sweep", error);
110
- }
111
- }
112
- };
113
- }
114
- /**
115
- * Wrap a webhook ingest callback so every parsed event also feeds the
116
- * base-checkpoint triggers. Trigger dispatch is fire-and-forget; the wrapped
117
- * ingest keeps its original awaited semantics.
118
- */
119
- function withBaseCheckpointWebhookTrigger(ingest, triggers) {
120
- if (!triggers) return ingest;
121
- return async (event) => {
122
- triggers.onWebhookEvent(event);
123
- return ingest ? ingest(event) : void 0;
124
- };
125
- }
126
- function asRecord(value) {
127
- return value && typeof value === "object" && !Array.isArray(value) ? value : null;
128
- }
129
- function idString(value) {
130
- if (typeof value === "number" && Number.isFinite(value)) return String(value);
131
- if (typeof value === "string" && value) return value;
132
- return null;
133
- }
134
- //#endregion
135
- export { BASE_CHECKPOINT_MAX_AGE_MS, baseCheckpointIsStale, classifyDefaultBranchUpdate, createBaseCheckpointTriggers, withBaseCheckpointWebhookTrigger };
136
-
137
- //# sourceMappingURL=base-checkpoint-triggers.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"base-checkpoint-triggers.js","names":[],"sources":["../../src/sandbox/base-checkpoint-triggers.ts"],"sourcesContent":["/**\n * Trigger surface for base-checkpoint builds.\n *\n * Three entry points feed the {@link BaseCheckpointBuilder}:\n * - repo connect: the projects route calls `onProjectRepositoryLinked` right\n * after `projectRepositories.link()` succeeds — the initial build.\n * - webhooks: `onWebhookEvent` classifies merged PRs and pushes targeting the\n * repository's default branch and requests a rebuild for every factory\n * project linked to that external repository.\n * - reconcile sweep: `sweep()` walks configured repositories and rebuilds any\n * whose checkpoint is missing or whose setup command changed since it was\n * built (webhooks can be missed; setup edits invalidate metadata).\n *\n * Every trigger is fire-and-forget relative to its caller: classification\n * errors log, builds coalesce inside the builder, and nothing here ever blocks\n * webhook dispatch, route responses, or the sweep lease.\n */\n\nimport type { IMastraLogger } from '@mastra/core/logger';\nimport type { RepositoryAccess } from '../capabilities/version-control.js';\nimport type { ParsedGithubWebhook } from '../integrations/github/webhook.js';\nimport type {\n ExternalRepositoryProjectTarget,\n ProjectRepository,\n SourceControlStorageHandle,\n} from '../storage/domains/source-control/base.js';\nimport type { BaseCheckpointBuilder, BaseCheckpointJob } from './base-checkpoint.js';\nimport { hashSetupCommand } from './base-checkpoint.js';\nimport type { SandboxFleet } from './fleet.js';\n\n/** The slice of the GitHub integration the triggers need. */\nexport interface BaseCheckpointGithubSource {\n readonly sourceControlStorage: SourceControlStorageHandle;\n readonly versionControl?: {\n getRepositoryAccess(input: { orgId: string; repositoryId: string }): Promise<RepositoryAccess>;\n };\n}\n\nexport interface BaseCheckpointTriggers {\n /** Kick the initial build after a repo is linked to a factory project. */\n onProjectRepositoryLinked(args: { orgId: string; projectRepository: ProjectRepository }): void;\n /** Classify a webhook and request rebuilds for default-branch updates. */\n onWebhookEvent(parsed: ParsedGithubWebhook): void;\n /** Rebuild stale/missing checkpoints for all configured repositories. */\n sweep(): Promise<void>;\n}\n\n/**\n * True when the webhook is a merged pull request into — or a push to — the\n * repository's default branch. Returns the external id pair used to find the\n * linked factory projects, or null when the event is not a rebuild trigger.\n */\nexport function classifyDefaultBranchUpdate(\n parsed: ParsedGithubWebhook,\n): { installationExternalId: string; repositoryExternalId: string } | null {\n const payload = parsed.payload;\n const repository = asRecord(payload.repository);\n const installation = asRecord(payload.installation);\n const installationId = idString(installation?.id);\n const repositoryId = idString(repository?.id);\n const defaultBranch = typeof repository?.default_branch === 'string' ? repository.default_branch : null;\n if (!installationId || !repositoryId || !defaultBranch) return null;\n\n if (parsed.event === 'pull_request' && payload.action === 'closed') {\n const pullRequest = asRecord(payload.pull_request);\n const base = asRecord(pullRequest?.base);\n if (pullRequest?.merged === true && base?.ref === defaultBranch) {\n return { installationExternalId: installationId, repositoryExternalId: repositoryId };\n }\n return null;\n }\n if (parsed.event === 'push' && payload.ref === `refs/heads/${defaultBranch}`) {\n return { installationExternalId: installationId, repositoryExternalId: repositoryId };\n }\n return null;\n}\n\n/**\n * Rebuild sweep age bound: even when webhooks are healthy, a checkpoint older\n * than this is rebuilt so a missed default-branch webhook cannot pin sessions\n * to an old commit indefinitely.\n */\nexport const BASE_CHECKPOINT_MAX_AGE_MS = 24 * 60 * 60 * 1000;\n\n/** How many sweep-triggered checkpoint builds may run at once. */\nconst SWEEP_BUILD_CONCURRENCY = 3;\n\n/**\n * A checkpoint is stale when absent, built against a different setup command,\n * or older than {@link BASE_CHECKPOINT_MAX_AGE_MS}.\n */\nexport function baseCheckpointIsStale(projectRepository: ProjectRepository, now: Date = new Date()): boolean {\n const checkpoint = projectRepository.baseCheckpoint;\n if (!checkpoint) return true;\n if (checkpoint.setupCommandHash !== hashSetupCommand(projectRepository.setupCommand)) return true;\n return now.getTime() - checkpoint.builtAt.getTime() > BASE_CHECKPOINT_MAX_AGE_MS;\n}\n\nexport function createBaseCheckpointTriggers(options: {\n builder: BaseCheckpointBuilder;\n fleet: SandboxFleet;\n github: BaseCheckpointGithubSource;\n logger?: IMastraLogger;\n}): BaseCheckpointTriggers {\n const { builder, fleet, github, logger } = options;\n const storage = github.sourceControlStorage;\n\n const jobFor = async (orgId: string, projectRepository: ProjectRepository): Promise<BaseCheckpointJob | null> => {\n const getRepositoryAccess = github.versionControl?.getRepositoryAccess.bind(github.versionControl);\n if (!fleet.enabled || !getRepositoryAccess) return null;\n const repository = await storage.repositories.get({ orgId, id: projectRepository.repositoryId });\n if (!repository) return null;\n return {\n projectRepositoryId: projectRepository.id,\n repoFullName: repository.slug,\n defaultBranch: repository.defaultBranch,\n setupCommand: projectRepository.setupCommand,\n workdir: fleet.computeWorkdir(repository.slug),\n getToken: async () => {\n const access = await getRepositoryAccess({ orgId, repositoryId: repository.id });\n const token = access.authorization?.token;\n if (!token) throw new Error(`No installation token available for ${repository.slug}`);\n return token;\n },\n storage,\n };\n };\n\n const request = async (orgId: string, projectRepository: ProjectRepository): Promise<void> => {\n const job = await jobFor(orgId, projectRepository);\n if (job) await builder.request(job);\n };\n\n const requestTargets = async (targets: ExternalRepositoryProjectTarget[]): Promise<void> => {\n for (const target of targets) {\n await request(target.orgId, target.projectRepository);\n }\n };\n\n const warn = (context: string, error: unknown) => {\n logger?.warn?.(\n `Base-checkpoint trigger failed (${context}): ${error instanceof Error ? error.message : String(error)}`,\n );\n };\n\n return {\n onProjectRepositoryLinked({ orgId, projectRepository }) {\n void request(orgId, projectRepository).catch(error => warn('repo-connect', error));\n },\n onWebhookEvent(parsed) {\n const key = classifyDefaultBranchUpdate(parsed);\n if (!key) return;\n void storage.projectRepositories\n .listByExternalRepository(key)\n .then(targets => requestTargets(targets))\n .catch(error => warn('webhook', error));\n },\n async sweep() {\n try {\n const keys = await storage.projectRepositories.listConfiguredExternalKeys();\n const stale: ExternalRepositoryProjectTarget[] = [];\n for (const key of keys) {\n const targets = await storage.projectRepositories.listByExternalRepository(key);\n stale.push(...targets.filter(target => baseCheckpointIsStale(target.projectRepository)));\n }\n // Builds run for minutes each; a sequential walk over many stale repos\n // would overrun the sweep lease TTL. Run them with bounded concurrency\n // so one sweep still finishes promptly without stampeding the fleet.\n const queue = [...stale];\n const workers = Array.from({ length: Math.min(SWEEP_BUILD_CONCURRENCY, queue.length) }, async () => {\n for (let target = queue.shift(); target; target = queue.shift()) {\n await request(target.orgId, target.projectRepository).catch(error => warn('sweep-build', error));\n }\n });\n await Promise.all(workers);\n } catch (error) {\n warn('sweep', error);\n }\n },\n };\n}\n\n/**\n * Wrap a webhook ingest callback so every parsed event also feeds the\n * base-checkpoint triggers. Trigger dispatch is fire-and-forget; the wrapped\n * ingest keeps its original awaited semantics.\n */\nexport function withBaseCheckpointWebhookTrigger(\n ingest: ((event: ParsedGithubWebhook) => Promise<unknown>) | undefined,\n triggers: BaseCheckpointTriggers | undefined,\n): ((event: ParsedGithubWebhook) => Promise<unknown>) | undefined {\n if (!triggers) return ingest;\n return async event => {\n triggers.onWebhookEvent(event);\n return ingest ? ingest(event) : undefined;\n };\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | null {\n return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : null;\n}\n\nfunction idString(value: unknown): string | null {\n if (typeof value === 'number' && Number.isFinite(value)) return String(value);\n if (typeof value === 'string' && value) return value;\n return null;\n}\n"],"mappings":"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"}
@@ -1,59 +0,0 @@
1
- /**
2
- * Base-checkpoint build job.
3
- *
4
- * Keeps a continuously-refreshed "base checkpoint" per connected repo: a
5
- * sandbox filesystem with the default branch already cloned and the repo's
6
- * setup command already run, snapshotted under `repo-<projectRepositoryId>`.
7
- * New sessions boot from this checkpoint and only pay `git fetch` + branch
8
- * checkout instead of a cold clone + install.
9
- *
10
- * Builds are triggered on repo connect, merges/pushes to the default branch,
11
- * and the reconcile sweep. Rapid triggers coalesce: only one build runs per
12
- * repo at a time, and a trigger arriving mid-build marks it dirty so exactly
13
- * one follow-up build runs afterwards.
14
- *
15
- * Providers without real checkpoint support (capability-driven via
16
- * `supportsCheckpoints`) skip the snapshot — sessions keep using the cold
17
- * path. A failed build never blocks sessions either; it only logs, and the
18
- * reconcile sweep retries later.
19
- */
20
- import type { IMastraLogger } from '@mastra/core/logger';
21
- import type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';
22
- import type { SandboxFleet } from './fleet.js';
23
- /** Provider checkpoint name for a repo's warm base image. */
24
- export declare function baseCheckpointName(projectRepositoryId: string): string;
25
- /** Stable hash of the setup command, used to invalidate stale checkpoints. */
26
- export declare function hashSetupCommand(setupCommand: string | null): string | null;
27
- /** Everything one build run needs; resolved by the trigger before enqueueing. */
28
- export interface BaseCheckpointJob {
29
- projectRepositoryId: string;
30
- repoFullName: string;
31
- defaultBranch: string;
32
- setupCommand: string | null;
33
- /** Workdir the clone lives at — must match what sessions use. */
34
- workdir: string;
35
- /** Mint a fresh short-lived installation token for git operations. */
36
- getToken(): Promise<string>;
37
- storage: SourceControlStorageHandle;
38
- actingUserId?: string;
39
- }
40
- /**
41
- * Coalescing runner for base-checkpoint builds. One instance per server;
42
- * `request()` is fire-and-forget safe (never throws).
43
- */
44
- export declare class BaseCheckpointBuilder {
45
- #private;
46
- constructor(options: {
47
- fleet: SandboxFleet;
48
- logger?: IMastraLogger;
49
- });
50
- /** True when a build for this repo is currently running. */
51
- isBuilding(projectRepositoryId: string): boolean;
52
- /**
53
- * Request a (re)build of the repo's base checkpoint. Coalesces with any
54
- * in-flight build for the same repo. Resolves when this request's build
55
- * (or the follow-up build it folded into) settles. Never rejects.
56
- */
57
- request(job: BaseCheckpointJob): Promise<void>;
58
- }
59
- //# sourceMappingURL=base-checkpoint.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"base-checkpoint.d.ts","sourceRoot":"","sources":["../../src/sandbox/base-checkpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AAC5F,OAAO,KAAK,EAA+C,YAAY,EAAE,MAAM,YAAY,CAAC;AAE5F,6DAA6D;AAC7D,wBAAgB,kBAAkB,CAAC,mBAAmB,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAG3E;AAED,iFAAiF;AACjF,MAAM,WAAW,iBAAiB;IAChC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5B,OAAO,EAAE,0BAA0B,CAAC;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,qBAAa,qBAAqB;;gBAQpB,OAAO,EAAE;QAAE,KAAK,EAAE,YAAY,CAAC;QAAC,MAAM,CAAC,EAAE,aAAa,CAAA;KAAE;IAKpE,4DAA4D;IAC5D,UAAU,CAAC,mBAAmB,EAAE,MAAM,GAAG,OAAO;IAIhD;;;;OAIG;IACH,OAAO,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;CA+G/C"}