@mastra/factory 0.21.1-alpha.3 → 0.22.0-alpha.4

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 (174) hide show
  1. package/dist/capabilities/version-control.d.ts +13 -8
  2. package/dist/capabilities/version-control.d.ts.map +1 -1
  3. package/dist/environment/build-runner.d.ts +72 -0
  4. package/dist/environment/build-runner.d.ts.map +1 -0
  5. package/dist/environment/build-runner.js +131 -0
  6. package/dist/environment/build-runner.js.map +1 -0
  7. package/dist/environment/build-schedule.d.ts +40 -0
  8. package/dist/environment/build-schedule.d.ts.map +1 -0
  9. package/dist/environment/build-schedule.js +94 -0
  10. package/dist/environment/build-schedule.js.map +1 -0
  11. package/dist/environment/build-workflow.d.ts +62 -0
  12. package/dist/environment/build-workflow.d.ts.map +1 -0
  13. package/dist/environment/build-workflow.js +149 -0
  14. package/dist/environment/build-workflow.js.map +1 -0
  15. package/dist/environment/build.d.ts +111 -0
  16. package/dist/environment/build.d.ts.map +1 -0
  17. package/dist/environment/build.js +344 -0
  18. package/dist/environment/build.js.map +1 -0
  19. package/dist/environment/heads.d.ts +36 -0
  20. package/dist/environment/heads.d.ts.map +1 -0
  21. package/dist/environment/heads.js +60 -0
  22. package/dist/environment/heads.js.map +1 -0
  23. package/dist/factory.d.ts +18 -4
  24. package/dist/factory.d.ts.map +1 -1
  25. package/dist/factory.js +89 -15
  26. package/dist/factory.js.map +1 -1
  27. package/dist/index.d.ts +2 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +3 -2
  30. package/dist/integrations/base.d.ts +17 -2
  31. package/dist/integrations/base.d.ts.map +1 -1
  32. package/dist/integrations/github/commits.d.ts +11 -0
  33. package/dist/integrations/github/commits.d.ts.map +1 -1
  34. package/dist/integrations/github/commits.js +20 -1
  35. package/dist/integrations/github/commits.js.map +1 -1
  36. package/dist/integrations/github/config.d.ts +3 -3
  37. package/dist/integrations/github/config.d.ts.map +1 -1
  38. package/dist/integrations/github/config.js +1 -1
  39. package/dist/integrations/github/config.js.map +1 -1
  40. package/dist/integrations/github/routes.d.ts +2 -2
  41. package/dist/integrations/github/routes.d.ts.map +1 -1
  42. package/dist/integrations/github/routes.js +6 -5
  43. package/dist/integrations/github/routes.js.map +1 -1
  44. package/dist/integrations/github/rules.d.ts +3 -1
  45. package/dist/integrations/github/rules.d.ts.map +1 -1
  46. package/dist/integrations/github/rules.js +31 -1
  47. package/dist/integrations/github/rules.js.map +1 -1
  48. package/dist/integrations/github/sandbox.d.ts +35 -0
  49. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  50. package/dist/integrations/github/sandbox.js +203 -2
  51. package/dist/integrations/github/sandbox.js.map +1 -1
  52. package/dist/integrations/github/session-subscriptions.d.ts.map +1 -1
  53. package/dist/integrations/github/session-subscriptions.js +117 -62
  54. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  55. package/dist/integrations/github/token-refresh.d.ts +7 -0
  56. package/dist/integrations/github/token-refresh.d.ts.map +1 -1
  57. package/dist/integrations/github/token-refresh.js.map +1 -1
  58. package/dist/integrations/github/webhook.js.map +1 -1
  59. package/dist/integrations/gitlab/agent-tools.js +1 -1
  60. package/dist/integrations/gitlab/routes.d.ts +2 -2
  61. package/dist/integrations/gitlab/routes.d.ts.map +1 -1
  62. package/dist/integrations/gitlab/routes.js.map +1 -1
  63. package/dist/integrations/gitlab/session-subscriptions.d.ts.map +1 -1
  64. package/dist/integrations/gitlab/session-subscriptions.js +8 -7
  65. package/dist/integrations/gitlab/session-subscriptions.js.map +1 -1
  66. package/dist/integrations/incidentio/agent-tools.js +1 -1
  67. package/dist/integrations/jira/agent-tools.js +1 -1
  68. package/dist/integrations/linear/agent-tools.js +1 -1
  69. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  70. package/dist/integrations/platform/github/integration.js +46 -2
  71. package/dist/integrations/platform/github/integration.js.map +1 -1
  72. package/dist/integrations/platform/jira/agent-tools.js +1 -1
  73. package/dist/integrations/slack/slack.d.ts.map +1 -1
  74. package/dist/integrations/slack/slack.js +15 -16
  75. package/dist/integrations/slack/slack.js.map +1 -1
  76. package/dist/integrations/subscription-session.js +1 -1
  77. package/dist/packages/_internals/workspace/dist/index.js +21 -1
  78. package/dist/packages/_internals/workspace/dist/index.js.map +1 -1
  79. package/dist/routes/contracts.d.ts +386 -2
  80. package/dist/routes/contracts.d.ts.map +1 -1
  81. package/dist/routes/contracts.js +166 -1
  82. package/dist/routes/contracts.js.map +1 -1
  83. package/dist/routes/fs.d.ts +25 -0
  84. package/dist/routes/fs.d.ts.map +1 -1
  85. package/dist/routes/fs.js +142 -46
  86. package/dist/routes/fs.js.map +1 -1
  87. package/dist/routes/projects.d.ts +6 -0
  88. package/dist/routes/projects.d.ts.map +1 -1
  89. package/dist/routes/projects.js +344 -1
  90. package/dist/routes/projects.js.map +1 -1
  91. package/dist/routes/source-control-sessions.d.ts +2 -0
  92. package/dist/routes/source-control-sessions.d.ts.map +1 -1
  93. package/dist/routes/source-control-sessions.js +36 -1
  94. package/dist/routes/source-control-sessions.js.map +1 -1
  95. package/dist/routes/supervisor.js +1 -1
  96. package/dist/routes/surface.d.ts +6 -3
  97. package/dist/routes/surface.d.ts.map +1 -1
  98. package/dist/routes/surface.js +21 -34
  99. package/dist/routes/surface.js.map +1 -1
  100. package/dist/rules/binding-context.d.ts +1 -1
  101. package/dist/rules/binding-context.d.ts.map +1 -1
  102. package/dist/rules/binding-context.js +1 -1
  103. package/dist/rules/binding-context.js.map +1 -1
  104. package/dist/rules/review-source-tool.js +1 -1
  105. package/dist/rules/start-coordinator.d.ts.map +1 -1
  106. package/dist/rules/start-coordinator.js +7 -2
  107. package/dist/rules/start-coordinator.js.map +1 -1
  108. package/dist/rules/tools.js +1 -1
  109. package/dist/sandbox/callback-factory-sandbox.d.ts +15 -0
  110. package/dist/sandbox/callback-factory-sandbox.d.ts.map +1 -0
  111. package/dist/sandbox/callback-factory-sandbox.js +22 -0
  112. package/dist/sandbox/callback-factory-sandbox.js.map +1 -0
  113. package/dist/sandbox/local-factory-sandbox.d.ts +20 -0
  114. package/dist/sandbox/local-factory-sandbox.d.ts.map +1 -0
  115. package/dist/sandbox/local-factory-sandbox.js +27 -0
  116. package/dist/sandbox/local-factory-sandbox.js.map +1 -0
  117. package/dist/sandbox/session-retirement.d.ts.map +1 -1
  118. package/dist/sandbox/session-retirement.js +23 -3
  119. package/dist/sandbox/session-retirement.js.map +1 -1
  120. package/dist/sandbox/session-sandbox.d.ts +82 -40
  121. package/dist/sandbox/session-sandbox.d.ts.map +1 -1
  122. package/dist/sandbox/session-sandbox.js +130 -13
  123. package/dist/sandbox/session-sandbox.js.map +1 -1
  124. package/dist/sandbox/workdir.d.ts +10 -0
  125. package/dist/sandbox/workdir.d.ts.map +1 -1
  126. package/dist/sandbox/workdir.js +10 -1
  127. package/dist/sandbox/workdir.js.map +1 -1
  128. package/dist/session/environment-repositories.d.ts +31 -0
  129. package/dist/session/environment-repositories.d.ts.map +1 -0
  130. package/dist/session/environment-repositories.js +89 -0
  131. package/dist/session/environment-repositories.js.map +1 -0
  132. package/dist/session/environment-state-processor.d.ts +59 -0
  133. package/dist/session/environment-state-processor.d.ts.map +1 -0
  134. package/dist/session/environment-state-processor.js +105 -0
  135. package/dist/session/environment-state-processor.js.map +1 -0
  136. package/dist/session/factory-session.d.ts +26 -35
  137. package/dist/session/factory-session.d.ts.map +1 -1
  138. package/dist/session/factory-session.js +34 -71
  139. package/dist/session/factory-session.js.map +1 -1
  140. package/dist/session/filesystem-capture.d.ts +13 -0
  141. package/dist/session/filesystem-capture.d.ts.map +1 -1
  142. package/dist/session/filesystem-capture.js +73 -14
  143. package/dist/session/filesystem-capture.js.map +1 -1
  144. package/dist/session/source-control-tools.d.ts +1 -1
  145. package/dist/session/source-control-tools.d.ts.map +1 -1
  146. package/dist/session/source-control-tools.js +144 -71
  147. package/dist/session/source-control-tools.js.map +1 -1
  148. package/dist/spa-static.js +2 -2
  149. package/dist/storage/domains/audit/agent-audit.d.ts +6 -1
  150. package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -1
  151. package/dist/storage/domains/audit/agent-audit.js +31 -13
  152. package/dist/storage/domains/audit/agent-audit.js.map +1 -1
  153. package/dist/storage/domains/projects/base.d.ts +115 -2
  154. package/dist/storage/domains/projects/base.d.ts.map +1 -1
  155. package/dist/storage/domains/projects/base.js +219 -12
  156. package/dist/storage/domains/projects/base.js.map +1 -1
  157. package/dist/storage/domains/source-control/base.d.ts +123 -7
  158. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  159. package/dist/storage/domains/source-control/base.js +406 -33
  160. package/dist/storage/domains/source-control/base.js.map +1 -1
  161. package/dist/storage/domains/source-control/inmemory.d.ts +19 -6
  162. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  163. package/dist/storage/domains/source-control/inmemory.js +90 -2
  164. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  165. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  166. package/dist/storage/domains/work-items/base.js +12 -5
  167. package/dist/storage/domains/work-items/base.js.map +1 -1
  168. package/dist/supervisor/read-tools.js +1 -1
  169. package/dist/supervisor/write-tools.js +1 -1
  170. package/dist/workspace.d.ts +50 -7
  171. package/dist/workspace.d.ts.map +1 -1
  172. package/dist/workspace.js +344 -24
  173. package/dist/workspace.js.map +1 -1
  174. package/package.json +7 -7
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","names":[],"sources":["../../src/environment/build.ts"],"sourcesContent":["/**\n * One proactive environment build: decide whether a trigger should build,\n * hand the sandbox's `builds` capability a context identical to a session's,\n * and record the started build on the project row. Polling the build to its\n * end and pinning the active template is the workflow's second step\n * (`build-workflow.ts`), so a request that only needs the start answer never\n * waits on the provider.\n */\n\nimport { createHash, randomUUID } from 'node:crypto';\n\nimport type { FactorySandbox, FactorySandboxContext } from '@mastra/core/workspace';\nimport type { VersionControl } from '../capabilities/version-control.js';\nimport type { FactoryProject, FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport { environmentSandboxContext, resolveProjectEnvironment } from '../workspace.js';\nimport type { SessionEnvironment } from '../workspace.js';\nimport { headsChanged, recordedHeadResolver, resolveCurrentHeads } from './heads.js';\nimport type { EnvironmentHeads } from './heads.js';\n\nexport const ENVIRONMENT_BUILD_TRIGGERS = ['manual', 'schedule', 'push'] as const;\nexport type EnvironmentBuildTrigger = (typeof ENVIRONMENT_BUILD_TRIGGERS)[number];\n\nexport interface EnvironmentBuildSourceControl {\n storage: Pick<SourceControlStorageHandle, 'projectRepositories' | 'repositories'>;\n versionControl: Pick<VersionControl, 'getRepositoryAccess'>;\n}\n\nexport interface EnvironmentBuildDeps {\n /** The factory's normalized sandbox; builds are unavailable without its `builds`. */\n sandbox: FactorySandbox | undefined;\n projects: Pick<\n FactoryProjectsStorage,\n 'getById' | 'claimBuildAttempt' | 'claimBuildStart' | 'renewBuildStart' | 'releaseBuildStart' | 'pinActiveTemplate'\n >;\n /** Absent when the factory has no source-control integration to resolve heads with. */\n sourceControl: EnvironmentBuildSourceControl | undefined;\n logger?: { info: (msg: string, meta?: Record<string, unknown>) => void };\n now?: () => Date;\n /** How often a run renews its build-start lease; a third of `BUILD_START_LEASE_MS` by default. */\n leaseRenewMs?: number;\n /** Waits between tries at recording a started build. */\n sleep?: (ms: number) => Promise<void>;\n}\n\nexport interface EnvironmentBuildOutcome {\n outcome: 'started' | 'skipped' | 'unavailable' | 'failed';\n buildId?: string;\n templateId?: string;\n /** Why nothing started: `debounced`, `starting` (another run is starting a build), `in_progress`, `unchanged`, `unresolved` (a lapsed run may have started a build the provider cannot list), `no_environment`, `no_builds`, `no_source_control`; why a run failed: `not_recorded`, `superseded`, or an error. */\n reason?: string;\n /** The heads the started build pins; the workflow stores them once the build is ready. */\n heads?: EnvironmentHeads;\n}\n\n/** The synthetic session id a project's build runs under; never a session. */\nexport function environmentBuildSessionId(projectId: string): string {\n return `environment-build:${projectId}`;\n}\n\n/**\n * A build or setup error may echo a clone URL or a token; strip URL userinfo\n * and GitHub token shapes before the message is stored, logged or shown.\n */\nexport function redactCredentials(message: string): string {\n return message.replace(/\\/\\/[^/\\s@]+@/g, '//***@').replace(/\\b(?:gh[pousr]|github_pat)_[A-Za-z0-9_]+/g, '***');\n}\n\n/** An error's message with credentials redacted, safe to log, store or return. */\nexport function errorReason(error: unknown): string {\n return redactCredentials(error instanceof Error ? error.message : String(error));\n}\n\n/**\n * The context an environment build (or a read of its status) hands the\n * provider: the same list form a session boots with, under the build's own\n * session id, with heads served from `heads` so the provider never resolves\n * them itself.\n */\nexport function environmentBuildContext(\n project: Pick<FactoryProject, 'id' | 'orgId'>,\n environment: SessionEnvironment,\n heads: EnvironmentHeads,\n versionControl: Pick<VersionControl, 'getRepositoryAccess'>,\n): FactorySandboxContext {\n return {\n sessionId: environmentBuildSessionId(project.id),\n ...environmentSandboxContext(environment, {\n orgId: project.orgId,\n getRepositoryAccess: args => versionControl.getRepositoryAccess(args),\n resolveHead: recordedHeadResolver(heads),\n }),\n };\n}\n\n/**\n * Fingerprint of everything a build bakes into the template besides the\n * repository heads: provider settings, the repositories and their order,\n * their setup commands, the workspace setup command and the working\n * directory. A schedule or push skips a ready build only while this matches.\n */\nexport function environmentBuildInputs(environment: SessionEnvironment): string {\n const inputs = {\n settings: environment.settings,\n workspaceSetupCommand: environment.workspaceSetupCommand ?? null,\n workingDirectory: environment.workingDirectory ?? null,\n repos: environment.repos.map(repo => ({\n repositoryId: repo.repositoryId,\n templateBranch: repo.templateBranch,\n setupCommand: repo.setupCommand ?? null,\n })),\n };\n return createHash('sha256').update(JSON.stringify(inputs)).digest('hex');\n}\n\n/**\n * The current head of every environment repository's template branch, keyed\n * by slug. The template clones the repository default branch (a link's own\n * branch is the session's base, checked out after boot), so that is the\n * branch a build pins; sessions let the provider resolve the same head.\n */\nexport async function resolveEnvironmentHeads(\n sourceControl: EnvironmentBuildSourceControl,\n project: Pick<FactoryProject, 'orgId'>,\n environment: SessionEnvironment,\n): Promise<EnvironmentHeads> {\n return resolveCurrentHeads(sourceControl, {\n orgId: project.orgId,\n repositories: environment.repos.map(repo => ({\n id: repo.repositoryId,\n slug: repo.slug,\n branch: repo.templateBranch,\n })),\n });\n}\n\n/**\n * How long a build-start lease lasts without renewal. The run holding it\n * renews it every third of that while it works, so a lease lapses only when\n * its run died or stalled.\n */\nexport const BUILD_START_LEASE_MS = 10 * 60_000;\n\n/** Tries at recording a started build before the run leaves its lease to lapse. */\nconst RECORD_ATTEMPTS = 5;\n\n/**\n * Start a build for `projectId` when `trigger` calls for one. Every trigger\n * takes the project's build-start lease before reading build state and renews\n * it until the started build is recorded, so overlapping manual, scheduled and\n * push runs never both start a build, however long the provider takes; a run\n * that finds the lease held skips as `starting`. Every trigger skips while the\n * last build is still running; `schedule` and `push` also skip when the last\n * build is ready and neither a head nor any other template input changed;\n * `manual` otherwise always builds. A `push` that would build then takes the\n * leading-edge debounce claim, so only a push that starts a build opens the\n * window. Saving settings never builds.\n *\n * Taking over a lapsed lease leaves the start uncertain: the lapsed run may\n * have started a build it never recorded. The mark persists until resolved.\n * While it stands, a provider that lists builds is asked for a running one\n * (and every trigger skips as `in_progress` on it); with a provider that\n * cannot list, every trigger skips as `unresolved`.\n *\n * The result is written as the lease is released, and only while this run\n * still holds it. A failed `builds.start` clears the last build id, so a later\n * `schedule` or `push` cannot mistake the previous ready build for the current\n * template; any other error keeps a running build's id. A started build whose\n * recording keeps failing yields `failed` (`not_recorded`) with the lease left\n * to lapse rather than released, and a run that lost its lease while the\n * provider started yields `failed` (`superseded`); neither is polled or pinned.\n */\nexport async function runEnvironmentBuild(\n deps: EnvironmentBuildDeps,\n input: { projectId: string; trigger: EnvironmentBuildTrigger },\n): Promise<EnvironmentBuildOutcome> {\n const builds = deps.sandbox?.builds;\n if (!builds) return { outcome: 'unavailable', reason: 'no_builds' };\n if (!deps.sourceControl) return { outcome: 'unavailable', reason: 'no_source_control' };\n const sourceControl = deps.sourceControl;\n const initial = await deps.projects.getById({ id: input.projectId });\n if (!initial) return { outcome: 'skipped', reason: 'no_environment' };\n const now = deps.now ?? (() => new Date());\n const sleep = deps.sleep ?? ((ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms)));\n\n const pushDebounced = (project: FactoryProject, at: Date) =>\n project.lastPushBuildAt !== null &&\n at.getTime() - project.lastPushBuildAt.getTime() < project.buildPushDebounceMinutes * 60_000;\n if (input.trigger === 'push' && pushDebounced(initial, now())) return { outcome: 'skipped', reason: 'debounced' };\n\n const token = randomUUID();\n const lease = { id: initial.id, token, leaseMs: BUILD_START_LEASE_MS };\n const claim = await deps.projects.claimBuildStart({ ...lease, now: now() });\n if (!claim.claimed) return { outcome: 'skipped', reason: 'starting' };\n let held = true;\n const renewal = setInterval(\n () => {\n deps.projects.renewBuildStart({ ...lease, now: now() }).then(\n renewed => {\n if (!renewed) held = false;\n },\n // A failed renewal is retried on the next tick; the lease outlasts two misses.\n () => {},\n );\n },\n deps.leaseRenewMs ?? BUILD_START_LEASE_MS / 3,\n );\n renewal.unref?.();\n\n const attempted = { lastBuildAttemptedAt: now() };\n // Set once this run has reconciled an uncertain start, or builds past it.\n let resolved = false;\n const release = (changes: Parameters<EnvironmentBuildDeps['projects']['releaseBuildStart']>[0]['changes'] = {}) =>\n deps.projects.releaseBuildStart({ id: initial.id, token, changes, resolved, now: now() });\n const failed = async (error: unknown, changes: Parameters<typeof release>[0]) => {\n const reason = errorReason(error);\n await release(changes);\n deps.logger?.info('environment build failed', { factoryProjectId: initial.id, trigger: input.trigger, reason });\n return { outcome: 'failed', reason } satisfies EnvironmentBuildOutcome;\n };\n\n try {\n let project: FactoryProject;\n let environment: SessionEnvironment;\n let heads: EnvironmentHeads;\n let ctx: FactorySandboxContext;\n let buildInputs: string;\n try {\n // Read build state only once this run holds the lease, so a run that\n // finished while this one waited has already recorded its build.\n project = (await deps.projects.getById({ id: initial.id })) ?? initial;\n environment = await resolveProjectEnvironment(sourceControl.storage, project);\n if (environment.repos.length === 0) {\n await release();\n return { outcome: 'skipped', reason: 'no_environment' };\n }\n heads = await resolveEnvironmentHeads(sourceControl, project, environment);\n ctx = environmentBuildContext(project, environment, heads, sourceControl.versionControl);\n buildInputs = environmentBuildInputs(environment);\n\n if (claim.uncertain) {\n // A run whose lease lapsed may have reached the provider before it\n // died; a build it left running is never raced by a second one. The\n // mark stays until that build is gone, since its inputs are unknown.\n if (builds.list) {\n const listed = await builds.list(ctx, environment.settings);\n const running = listed.find(build => build.status === 'pending' || build.status === 'building');\n if (running) {\n await release();\n return { outcome: 'skipped', reason: 'in_progress', buildId: running.buildId };\n }\n resolved = true;\n } else {\n // Without history the earlier start can never be reconciled, so no\n // trigger starts another build beside it.\n await release();\n return { outcome: 'skipped', reason: 'unresolved' };\n }\n }\n\n if (project.lastBuildId) {\n const last = await builds.get(ctx, environment.settings, project.lastBuildId);\n // Two runs polling one template fight over it (each poll resubmits its\n // own definition and the provider cancels the other), so a running\n // build is never joined by a second one, whatever asked.\n if (last.status === 'pending' || last.status === 'building') {\n await release();\n return { outcome: 'skipped', reason: 'in_progress', buildId: project.lastBuildId };\n }\n // A ready build whose poll ran out or died with its host was never\n // pinned; pin it now so the skip below compares against what it built.\n let pinnedHeads = project.activeTemplateHeads;\n const lastTemplateId = last.templateId ?? project.lastBuildId;\n if (last.status === 'ready' && project.lastBuildHeads && project.activeTemplateId !== lastTemplateId) {\n const pinned = await deps.projects.pinActiveTemplate({\n id: project.id,\n buildId: project.lastBuildId,\n templateId: lastTemplateId,\n heads: project.lastBuildHeads,\n });\n if (pinned) pinnedHeads = project.lastBuildHeads;\n }\n if (\n (input.trigger === 'schedule' || input.trigger === 'push') &&\n last.status === 'ready' &&\n !headsChanged(pinnedHeads, heads) &&\n project.lastBuildInputs === buildInputs\n ) {\n await release();\n return { outcome: 'skipped', reason: 'unchanged' };\n }\n }\n } catch (error) {\n return await failed(error, attempted);\n }\n\n // A run that lost its lease while reading must not start beside its successor.\n if (!held) return { outcome: 'skipped', reason: 'starting' };\n\n if (input.trigger === 'push') {\n // Claimed only now, so a push that skipped above never closes the window.\n let claimed: boolean;\n try {\n claimed = await deps.projects.claimBuildAttempt({\n id: project.id,\n debounceMinutes: project.buildPushDebounceMinutes,\n now: now(),\n });\n } catch (error) {\n return await failed(error, {});\n }\n if (!claimed) {\n await release();\n return { outcome: 'skipped', reason: 'debounced' };\n }\n }\n\n let started: Awaited<ReturnType<typeof builds.start>>;\n try {\n started = await builds.start(ctx, environment.settings);\n } catch (error) {\n // Only a failed start clears the last build: the previous one no longer\n // describes the template this project wants.\n return await failed(error, { lastBuildId: null, lastBuildHeads: null, lastBuildInputs: null, ...attempted });\n }\n\n // The provider has started the build: from here on, no error clears it.\n const log = { factoryProjectId: project.id, trigger: input.trigger, buildId: started.buildId };\n const changes = { lastBuildId: started.buildId, lastBuildHeads: heads, lastBuildInputs: buildInputs, ...attempted };\n let recorded: boolean | undefined;\n for (let attempt = 1; recorded === undefined; attempt++) {\n try {\n recorded = await release(changes);\n } catch (error) {\n if (attempt >= RECORD_ATTEMPTS) {\n // Keep the lease: it lapses once renewal stops, and the run that\n // takes it over looks for this build at the provider first.\n deps.logger?.info('environment build started but not recorded', { ...log, reason: errorReason(error) });\n return { outcome: 'failed', reason: 'not_recorded', buildId: started.buildId };\n }\n await sleep(1000 * 2 ** (attempt - 1));\n }\n }\n if (!recorded) {\n // A newer run took the lease while the provider started this build; the\n // project tracks that run's build, so this one is never polled or pinned.\n deps.logger?.info('environment build superseded', log);\n return { outcome: 'failed', reason: 'superseded', buildId: started.buildId };\n }\n try {\n deps.logger?.info('environment build started', { ...log, templateId: started.templateId });\n } catch {\n // Logging never turns a recorded build into a failure.\n }\n return {\n outcome: 'started',\n buildId: started.buildId,\n ...(started.templateId ? { templateId: started.templateId } : {}),\n heads,\n };\n } finally {\n clearInterval(renewal);\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Which commit each environment repository pins to.
3
+ *
4
+ * The repo templates key `resolveHead` by clone URL; the factory records heads
5
+ * by repository slug. These helpers translate between the two and decide
6
+ * whether a set of current heads differs from the recorded one.
7
+ */
8
+ import type { VersionControl } from '../capabilities/version-control.js';
9
+ export type EnvironmentHeads = Record<string, string>;
10
+ /** `owner/repo` from a clone URL in any common spelling, lower-cased, `.git` stripped; undefined when unparseable. */
11
+ export declare function cloneUrlSlug(cloneUrl: string): string | undefined;
12
+ /** The environment slug a clone URL names, in the environment's own spelling; undefined when none matches. */
13
+ export declare function matchEnvironmentSlug(cloneUrl: string, slugs: Iterable<string>): string | undefined;
14
+ /**
15
+ * A `resolveHead` that answers from recorded heads only: the template pins to
16
+ * what the last build used, so its identity moves when a build does. A URL
17
+ * outside the recorded set resolves undefined and the template falls back to
18
+ * its own lookup for that repository.
19
+ */
20
+ export declare function recordedHeadResolver(heads: EnvironmentHeads): (cloneUrl: string) => Promise<string | undefined>;
21
+ /** True when any repository's current head differs from the recorded one, or either side names a repository the other lacks. */
22
+ export declare function headsChanged(recorded: EnvironmentHeads | null, current: EnvironmentHeads): boolean;
23
+ export interface HeadRepository {
24
+ id: string;
25
+ slug: string;
26
+ /** The ref the environment pins: the link's branch, else the repository default. */
27
+ branch: string;
28
+ }
29
+ /** Current head of every repository's pinned ref, keyed by slug. Throws when any lookup fails. */
30
+ export declare function resolveCurrentHeads(github: {
31
+ versionControl: Pick<VersionControl, 'getRepositoryAccess'>;
32
+ }, input: {
33
+ orgId: string;
34
+ repositories: HeadRepository[];
35
+ }): Promise<EnvironmentHeads>;
36
+ //# sourceMappingURL=heads.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heads.d.ts","sourceRoot":"","sources":["../../src/environment/heads.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAGzE,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEtD,sHAAsH;AACtH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAkBjE;AAED,8GAA8G;AAC9G,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,SAAS,CAOlG;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,gBAAgB,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAM/G;AAED,gIAAgI;AAChI,wBAAgB,YAAY,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAMlG;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,kGAAkG;AAClG,wBAAsB,mBAAmB,CACvC,MAAM,EAAE;IAAE,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,qBAAqB,CAAC,CAAA;CAAE,EACvE,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,cAAc,EAAE,CAAA;CAAE,GACvD,OAAO,CAAC,gBAAgB,CAAC,CAU3B"}
@@ -0,0 +1,60 @@
1
+ import { getBranchHead } from "../integrations/github/commits.js";
2
+ //#region src/environment/heads.ts
3
+ /** `owner/repo` from a clone URL in any common spelling, lower-cased, `.git` stripped; undefined when unparseable. */
4
+ function cloneUrlSlug(cloneUrl) {
5
+ let path;
6
+ const scp = /^[^@/]+@[^:/]+:(.+)$/.exec(cloneUrl.trim());
7
+ if (scp) path = scp[1];
8
+ else try {
9
+ path = new URL(cloneUrl.trim()).pathname;
10
+ } catch {
11
+ return;
12
+ }
13
+ const parts = path.replace(/\.git$/i, "").split("/").filter(Boolean);
14
+ if (parts.length < 2) return void 0;
15
+ return `${parts[parts.length - 2]}/${parts[parts.length - 1]}`.toLowerCase();
16
+ }
17
+ /** The environment slug a clone URL names, in the environment's own spelling; undefined when none matches. */
18
+ function matchEnvironmentSlug(cloneUrl, slugs) {
19
+ const wanted = cloneUrlSlug(cloneUrl);
20
+ if (!wanted) return void 0;
21
+ for (const slug of slugs) if (slug.toLowerCase() === wanted) return slug;
22
+ }
23
+ /**
24
+ * A `resolveHead` that answers from recorded heads only: the template pins to
25
+ * what the last build used, so its identity moves when a build does. A URL
26
+ * outside the recorded set resolves undefined and the template falls back to
27
+ * its own lookup for that repository.
28
+ */
29
+ function recordedHeadResolver(heads) {
30
+ const slugs = Object.keys(heads);
31
+ return async (cloneUrl) => {
32
+ const slug = matchEnvironmentSlug(cloneUrl, slugs);
33
+ return slug ? heads[slug] : void 0;
34
+ };
35
+ }
36
+ /** True when any repository's current head differs from the recorded one, or either side names a repository the other lacks. */
37
+ function headsChanged(recorded, current) {
38
+ if (!recorded) return true;
39
+ const recordedKeys = Object.keys(recorded);
40
+ const currentKeys = Object.keys(current);
41
+ if (recordedKeys.length !== currentKeys.length) return true;
42
+ return currentKeys.some((slug) => recorded[slug] !== current[slug]);
43
+ }
44
+ /** Current head of every repository's pinned ref, keyed by slug. Throws when any lookup fails. */
45
+ async function resolveCurrentHeads(github, input) {
46
+ const heads = {};
47
+ for (const repository of input.repositories) heads[repository.slug] = await getBranchHead(github, {
48
+ orgId: input.orgId,
49
+ repository: {
50
+ id: repository.id,
51
+ slug: repository.slug
52
+ },
53
+ branch: repository.branch
54
+ });
55
+ return heads;
56
+ }
57
+ //#endregion
58
+ export { cloneUrlSlug, headsChanged, matchEnvironmentSlug, recordedHeadResolver, resolveCurrentHeads };
59
+
60
+ //# sourceMappingURL=heads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heads.js","names":[],"sources":["../../src/environment/heads.ts"],"sourcesContent":["/**\n * Which commit each environment repository pins to.\n *\n * The repo templates key `resolveHead` by clone URL; the factory records heads\n * by repository slug. These helpers translate between the two and decide\n * whether a set of current heads differs from the recorded one.\n */\n\nimport type { VersionControl } from '../capabilities/version-control.js';\nimport { getBranchHead } from '../integrations/github/commits.js';\n\nexport type EnvironmentHeads = Record<string, string>;\n\n/** `owner/repo` from a clone URL in any common spelling, lower-cased, `.git` stripped; undefined when unparseable. */\nexport function cloneUrlSlug(cloneUrl: string): string | undefined {\n let path: string;\n const scp = /^[^@/]+@[^:/]+:(.+)$/.exec(cloneUrl.trim());\n if (scp) {\n path = scp[1]!;\n } else {\n try {\n path = new URL(cloneUrl.trim()).pathname;\n } catch {\n return undefined;\n }\n }\n const parts = path\n .replace(/\\.git$/i, '')\n .split('/')\n .filter(Boolean);\n if (parts.length < 2) return undefined;\n return `${parts[parts.length - 2]}/${parts[parts.length - 1]}`.toLowerCase();\n}\n\n/** The environment slug a clone URL names, in the environment's own spelling; undefined when none matches. */\nexport function matchEnvironmentSlug(cloneUrl: string, slugs: Iterable<string>): string | undefined {\n const wanted = cloneUrlSlug(cloneUrl);\n if (!wanted) return undefined;\n for (const slug of slugs) {\n if (slug.toLowerCase() === wanted) return slug;\n }\n return undefined;\n}\n\n/**\n * A `resolveHead` that answers from recorded heads only: the template pins to\n * what the last build used, so its identity moves when a build does. A URL\n * outside the recorded set resolves undefined and the template falls back to\n * its own lookup for that repository.\n */\nexport function recordedHeadResolver(heads: EnvironmentHeads): (cloneUrl: string) => Promise<string | undefined> {\n const slugs = Object.keys(heads);\n return async cloneUrl => {\n const slug = matchEnvironmentSlug(cloneUrl, slugs);\n return slug ? heads[slug] : undefined;\n };\n}\n\n/** True when any repository's current head differs from the recorded one, or either side names a repository the other lacks. */\nexport function headsChanged(recorded: EnvironmentHeads | null, current: EnvironmentHeads): boolean {\n if (!recorded) return true;\n const recordedKeys = Object.keys(recorded);\n const currentKeys = Object.keys(current);\n if (recordedKeys.length !== currentKeys.length) return true;\n return currentKeys.some(slug => recorded[slug] !== current[slug]);\n}\n\nexport interface HeadRepository {\n id: string;\n slug: string;\n /** The ref the environment pins: the link's branch, else the repository default. */\n branch: string;\n}\n\n/** Current head of every repository's pinned ref, keyed by slug. Throws when any lookup fails. */\nexport async function resolveCurrentHeads(\n github: { versionControl: Pick<VersionControl, 'getRepositoryAccess'> },\n input: { orgId: string; repositories: HeadRepository[] },\n): Promise<EnvironmentHeads> {\n const heads: EnvironmentHeads = {};\n for (const repository of input.repositories) {\n heads[repository.slug] = await getBranchHead(github, {\n orgId: input.orgId,\n repository: { id: repository.id, slug: repository.slug },\n branch: repository.branch,\n });\n }\n return heads;\n}\n"],"mappings":";;;AAcA,SAAgB,aAAa,UAAsC;CACjE,IAAI;CACJ,MAAM,MAAM,uBAAuB,KAAK,SAAS,KAAK,CAAC;CACvD,IAAI,KACF,OAAO,IAAI;MAEX,IAAI;EACF,OAAO,IAAI,IAAI,SAAS,KAAK,CAAC,CAAC,CAAC;CAClC,QAAQ;EACN;CACF;CAEF,MAAM,QAAQ,KACX,QAAQ,WAAW,EAAE,CAAC,CACtB,MAAM,GAAG,CAAC,CACV,OAAO,OAAO;CACjB,IAAI,MAAM,SAAS,GAAG,OAAO,KAAA;CAC7B,OAAO,GAAG,MAAM,MAAM,SAAS,GAAG,GAAG,MAAM,MAAM,SAAS,KAAK,YAAY;AAC7E;;AAGA,SAAgB,qBAAqB,UAAkB,OAA6C;CAClG,MAAM,SAAS,aAAa,QAAQ;CACpC,IAAI,CAAC,QAAQ,OAAO,KAAA;CACpB,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,YAAY,MAAM,QAAQ,OAAO;AAG9C;;;;;;;AAQA,SAAgB,qBAAqB,OAA4E;CAC/G,MAAM,QAAQ,OAAO,KAAK,KAAK;CAC/B,OAAO,OAAM,aAAY;EACvB,MAAM,OAAO,qBAAqB,UAAU,KAAK;EACjD,OAAO,OAAO,MAAM,QAAQ,KAAA;CAC9B;AACF;;AAGA,SAAgB,aAAa,UAAmC,SAAoC;CAClG,IAAI,CAAC,UAAU,OAAO;CACtB,MAAM,eAAe,OAAO,KAAK,QAAQ;CACzC,MAAM,cAAc,OAAO,KAAK,OAAO;CACvC,IAAI,aAAa,WAAW,YAAY,QAAQ,OAAO;CACvD,OAAO,YAAY,MAAK,SAAQ,SAAS,UAAU,QAAQ,KAAK;AAClE;;AAUA,eAAsB,oBACpB,QACA,OAC2B;CAC3B,MAAM,QAA0B,CAAC;CACjC,KAAK,MAAM,cAAc,MAAM,cAC7B,MAAM,WAAW,QAAQ,MAAM,cAAc,QAAQ;EACnD,OAAO,MAAM;EACb,YAAY;GAAE,IAAI,WAAW;GAAI,MAAM,WAAW;EAAK;EACvD,QAAQ,WAAW;CACrB,CAAC;CAEH,OAAO;AACT"}
package/dist/factory.d.ts CHANGED
@@ -24,10 +24,11 @@ import type { Mastra } from '@mastra/core/mastra';
24
24
  import type { IMastraAuthProvider } from '@mastra/core/server';
25
25
  import type { FactoryStorage } from '@mastra/core/storage';
26
26
  import type { MastraVector } from '@mastra/core/vector';
27
+ import type { FactorySandboxDescription } from '@mastra/core/workspace';
27
28
  import type { InstalledBoard } from './boards/index.js';
28
29
  import type { FactoryIntegration } from './integrations/base.js';
29
30
  import type { GithubRuleOverrides } from './integrations/github/default-rules.js';
30
- import type { MastraFactorySandboxConfig } from './sandbox/session-sandbox.js';
31
+ import type { MastraFactorySandboxOption } from './sandbox/session-sandbox.js';
31
32
  import type { FactorySecretEncryption } from './secret-encryption.js';
32
33
  import type { FactorySandboxStart } from './workspace.js';
33
34
  /** Constructor args for the `new Mastra(...)` literal in the deploy entry. */
@@ -92,8 +93,15 @@ export interface MastraFactoryConfig {
92
93
  * static host, so credentialed requests must be explicitly allowed.
93
94
  */
94
95
  allowedOrigins?: string[];
95
- /** Sandbox configuration. Omitted → repository sandboxes are disabled. */
96
- sandbox?: MastraFactorySandboxConfig;
96
+ /**
97
+ * The host's sandbox: a FactorySandbox instance such as
98
+ * `new PlatformFactorySandbox({ ... })`, which owns the session sandbox
99
+ * constructor, the repo template and the schema of environment settings.
100
+ * A bare callback is also accepted and wrapped as a `provider: 'custom'`
101
+ * sandbox with no settings. Omitted → repository sandboxes are
102
+ * disabled.
103
+ */
104
+ sandbox?: MastraFactorySandboxOption;
97
105
  /**
98
106
  * When a session's sandbox boots: on the agent's first command (`'lazy'`,
99
107
  * the default) or as soon as the session's workspace is first resolved
@@ -172,7 +180,7 @@ export interface MastraFactoryConfig {
172
180
  };
173
181
  };
174
182
  }
175
- export type { MastraFactorySandboxConfig } from './sandbox/session-sandbox.js';
183
+ export type { MastraFactorySandboxConfig, MastraFactorySandboxOption } from './sandbox/session-sandbox.js';
176
184
  export type { FactorySandboxStart } from './workspace.js';
177
185
  /**
178
186
  * Per-process cap on concurrent background Factory dispatches. Omitted means
@@ -185,6 +193,12 @@ export interface MastraFactoryDispatcherConfig {
185
193
  export declare class MastraFactory {
186
194
  #private;
187
195
  constructor(config: MastraFactoryConfig);
196
+ /**
197
+ * The host sandbox's provider, settings schema (JSON Schema) and
198
+ * capabilities, as the environment route reports them. Undefined before
199
+ * `prepare()` and when sandboxes are disabled.
200
+ */
201
+ get sandboxDescription(): FactorySandboxDescription | undefined;
188
202
  /**
189
203
  * Resolve feature readiness, wire every dependency explicitly, and assemble
190
204
  * everything needed to construct the server-owned Mastra. Returns the args
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAQH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAGlD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAaxD,OAAO,KAAK,EAAiB,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEvE,OAAO,KAAK,EAAE,kBAAkB,EAAgD,MAAM,wBAAwB,CAAC;AAE/G,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAuClF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAE/E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AA8CtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAI1D,8EAA8E;AAC9E,MAAM,MAAM,UAAU,GAAG,WAAW,CAAC,qBAAqB,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE9E,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;;;;;;;OAcG;IACH,IAAI,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAClC;;;;;;;OAOG;IACH,OAAO,EAAE,cAAc,CAAC;IACxB;;;;OAIG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;OAGG;IACH,SAAS,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,SAAS,CAAA;KAAE,CAAC;IACjD;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,0EAA0E;IAC1E,OAAO,CAAC,EAAE,0BAA0B,CAAC;IACrC;;;;OAIG;IACH,YAAY,CAAC,EAAE,mBAAmB,CAAC;IACnC,mDAAmD;IACnD,UAAU,CAAC,EAAE,6BAA6B,CAAC;IAC3C;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;IAC3C;;;;;;;;OAQG;IACH,wBAAwB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACpC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACnC,gFAAgF;IAChF,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAE/B;;OAEG;IACH,QAAQ,CAAC,EAAE;QACT,sGAAsG;QACtG,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB;;;;;;WAMG;QACH,MAAM,CAAC,EAAE;YACP,uFAAuF;YACvF,KAAK,CAAC,EAAE,mBAAmB,CAAC;YAC5B;;;eAGG;YACH,IAAI,CAAC,EAAE,MAAM,CAAC;SACf,CAAC;KACH,CAAC;CACH;AAED,YAAY,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC/E,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,6BAA6B;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAmFD,qBAAa,aAAa;;gBASZ,MAAM,EAAE,mBAAmB;IAsBvC;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC;IAwrCpC;;;;OAIG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAY/B,iFAAiF;IAC3E,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAGhC"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"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"}
package/dist/factory.js CHANGED
@@ -4,6 +4,16 @@ import { assertFactoryConfigVersion } from "./rules/validation.js";
4
4
  import { isTerminalWorkItem } from "./boards/semantics.js";
5
5
  import { createBoardRegistry } from "./boards/registry.js";
6
6
  import "./boards/index.js";
7
+ import { isValidGitRef } from "./sandbox/git-ref.js";
8
+ import { FactoryEnvironmentStateProcessor } from "./session/environment-state-processor.js";
9
+ import { FactoryProjectsStorage } from "./storage/domains/projects/base.js";
10
+ import { SourceControlStorage } from "./storage/domains/source-control/base.js";
11
+ import { environmentSlugs, resolveSessionRepositories } from "./session/environment-repositories.js";
12
+ import { createSourceControlSessionLookup } from "./session/factory-session.js";
13
+ import { hydrateSupervisorSession, parseSupervisorResourceId, resolveSupervisorScope } from "./supervisor/session.js";
14
+ import { FactoryWorkspaceRegistry, createWorkspaceFactory } from "./workspace.js";
15
+ import { ENVIRONMENT_BUILD_WORKFLOW_ID } from "./environment/build-workflow.js";
16
+ import { EnvironmentBuildRunner } from "./environment/build-runner.js";
7
17
  import { touchFeed } from "./feed-events.js";
8
18
  import { reconcileGithubAcceptanceLabels } from "./integrations/github/acceptance-labels.js";
9
19
  import { WorkItemsStorage } from "./storage/domains/work-items/base.js";
@@ -15,7 +25,6 @@ import { PlatformApiClient, platformApiClientConfigFromEnv } from "./integration
15
25
  import { buildPlatformConnectRoutes } from "./integrations/platform/connect/routes.js";
16
26
  import { observeSessionFirstExec } from "./session/first-exec-capture.js";
17
27
  import { observeSessionFilesystem } from "./session/filesystem-capture.js";
18
- import { isValidGitRef } from "./sandbox/git-ref.js";
19
28
  import { createPlaintextFactorySecretEncryption } from "./secret-encryption.js";
20
29
  import { ModelCredentialsStorage } from "./storage/domains/credentials/base.js";
21
30
  import { createTenantCredentialPrimer, primeTenantCredentials, registerTenantCredentialResolver } from "./routes/tenant-credentials.js";
@@ -29,15 +38,10 @@ import { hydrateSessionMemorySettings, layerPersonalMemorySettings } from "./ses
29
38
  import { MemorySettingsStorage, factoryMemorySettingsUserId } from "./storage/domains/memory-settings/base.js";
30
39
  import { resolveDeploymentModelProviders } from "./routes/config.js";
31
40
  import { createCustomProvidersPrimer, registerCustomProvidersSource } from "./routes/custom-provider-source.js";
32
- import { FactoryProjectsStorage } from "./storage/domains/projects/base.js";
33
41
  import { ProjectRoutes } from "./routes/projects.js";
34
- import { SourceControlStorage } from "./storage/domains/source-control/base.js";
35
- import { createSourceControlSessionLookup } from "./session/factory-session.js";
36
42
  import { FactoryTransitionService } from "./rules/transition-service.js";
37
43
  import { AuditStorage } from "./storage/domains/audit/base.js";
38
44
  import { AuditDomain } from "./storage/domains/audit/domain.js";
39
- import { hydrateSupervisorSession, parseSupervisorResourceId, resolveSupervisorScope } from "./supervisor/session.js";
40
- import { FactoryWorkspaceRegistry, createWorkspaceFactory } from "./workspace.js";
41
45
  import { QueueHealthStorage } from "./storage/domains/queue-health/base.js";
42
46
  import { assembleFactoryApiRoutes, buildIntegrationContext } from "./routes/surface.js";
43
47
  import { TelemetryRoutes } from "./routes/telemetry.js";
@@ -49,6 +53,7 @@ import { FactoryPhaseStateProcessor, reportMemorySettingsUnavailable } from "./r
49
53
  import { createReviewSourceTool, resolveReviewSourceUiOrigin } from "./rules/review-source-tool.js";
50
54
  import { createTerminalStageCleanup } from "./rules/terminal-cleanup.js";
51
55
  import { createFactoryTransitionTools } from "./rules/tools.js";
56
+ import { CallbackFactorySandbox } from "./sandbox/callback-factory-sandbox.js";
52
57
  import { SessionRetirementCoordinator } from "./sandbox/session-retirement.js";
53
58
  import { handleServerError } from "./server-error.js";
54
59
  import { hydrateSessionDefaultModel } from "./session/default-model-hydration.js";
@@ -76,6 +81,7 @@ import { MastraAuthStudio } from "@mastra/auth-studio";
76
81
  import { prepareAgentControllerMount } from "@mastra/code-sdk";
77
82
  import { AgentControllerChannels } from "@mastra/core/channels";
78
83
  import { EventEmitterPubSub } from "@mastra/core/events";
84
+ import { describeFactorySandbox, isFactorySandbox } from "@mastra/core/workspace";
79
85
  //#region src/factory.ts
80
86
  /**
81
87
  * `MastraFactory` — the single entry point to the whole Mastra Software Factory.
@@ -163,13 +169,43 @@ function liveSessionsTouchingTheFeed(controller, eventBus) {
163
169
  });
164
170
  return liveSessions;
165
171
  }
172
+ /**
173
+ * The fleet-era `sandbox` options and what replaced each one. Only the keys
174
+ * the host actually passed are explained, so a host that never used the fleet
175
+ * gets the short message.
176
+ */
177
+ const RETIRED_SANDBOX_OPTIONS = {
178
+ machine: "'machine' becomes the provider instance: one sandbox per session, constructed by the FactorySandbox, instead of one template machine cloned per repository",
179
+ workdir: "'workdir' becomes the workingDirectory of the FactorySandboxContext, which every provider passes to its sandbox; when it is absent the provider uses its own default",
180
+ maxSandboxes: "'maxSandboxes' is gone with the sandbox fleet: there is one sandbox per session and no pool to cap"
181
+ };
182
+ function invalidSandboxOptionMessage(option) {
183
+ const lines = [
184
+ `MastraFactory: 'sandbox' must be a FactorySandbox instance or a callback constructing a MastraSandbox from a FactorySandboxContext:`,
185
+ ` sandbox: new PlatformFactorySandbox()`,
186
+ ` sandbox: ctx => new PlatformSandbox({ id: ctx.sessionId, sandboxId: ctx.sandboxId })`
187
+ ];
188
+ if (typeof option === "object" && option !== null) {
189
+ const retired = Object.keys(RETIRED_SANDBOX_OPTIONS).filter((key) => key in option);
190
+ if (retired.length > 0) {
191
+ lines.push(`The object you passed is the retired alpha sandbox config. Its options map as follows:`);
192
+ for (const key of retired) lines.push(` ${RETIRED_SANDBOX_OPTIONS[key]}.`);
193
+ }
194
+ }
195
+ return lines.join("\n");
196
+ }
166
197
  var MastraFactory = class {
167
198
  #config;
199
+ /** The normalized sandbox, set by `prepare()`. Undefined until then and when sandboxes are disabled. */
200
+ #sandbox;
201
+ #sandboxDescription;
168
202
  #boards;
169
203
  #configVersion;
170
204
  #prepared;
171
205
  #dispatcher;
172
206
  #factoryProcessor;
207
+ /** Environment builds through the sandbox's `builds`; undefined when the sandbox has none. */
208
+ #environmentBuilds;
173
209
  #preparing = false;
174
210
  constructor(config) {
175
211
  if (!config?.storage) throw new Error("MastraFactory: 'storage' is required. Pass a FactoryStorage backend — e.g. new PgFactoryStorage({ connectionString }) from '@mastra/pg' for deployments, or new LibSQLFactoryStorage({ url }) from '@mastra/libsql' for local dev.");
@@ -182,6 +218,16 @@ var MastraFactory = class {
182
218
  this.#config = config;
183
219
  }
184
220
  /**
221
+ * The host sandbox's provider, settings schema (JSON Schema) and
222
+ * capabilities, as the environment route reports them. Undefined before
223
+ * `prepare()` and when sandboxes are disabled.
224
+ */
225
+ get sandboxDescription() {
226
+ if (!this.#sandbox) return void 0;
227
+ this.#sandboxDescription ??= describeFactorySandbox(this.#sandbox);
228
+ return this.#sandboxDescription;
229
+ }
230
+ /**
185
231
  * Resolve feature readiness, wire every dependency explicitly, and assemble
186
232
  * everything needed to construct the server-owned Mastra. Returns the args
187
233
  * for the `new Mastra(...)` literal that must live in the entry file.
@@ -278,11 +324,12 @@ var MastraFactory = class {
278
324
  publishers: feedPublishers,
279
325
  pubsub: eventBus
280
326
  });
281
- const sandboxConfig = this.#config.sandbox;
282
- if (sandboxConfig !== void 0 && typeof sandboxConfig !== "function") {
283
- if (typeof sandboxConfig === "object" && sandboxConfig !== null) throw new Error("MastraFactory: 'sandbox' is now a callback, not an options object. It receives a FactorySandboxContext and returns a MastraSandbox, so the host chooses the provider per session:\n sandbox: ctx => new E2BSandbox({ id: ctx.sessionId, sandboxId: ctx.sandboxId })\nThe old options map three ways: 'machine' becomes the provider instance you construct inside the callback (one per session instead of one cloned template); 'workdir' is gone — remote providers clone into the VM's home directory and local providers check out under their own workingDirectory; 'maxSandboxes' is gone with the sandbox fleet — there is one sandbox per session and no pool to cap. Omit 'sandbox' entirely to disable sandboxes.");
284
- throw new Error(`MastraFactory: 'sandbox' must be a function constructing a MastraSandbox from a FactorySandboxContext.`);
285
- }
327
+ const sandboxOption = this.#config.sandbox;
328
+ let sandboxConfig;
329
+ if (sandboxOption === void 0 || isFactorySandbox(sandboxOption)) sandboxConfig = sandboxOption;
330
+ else if (typeof sandboxOption === "function") sandboxConfig = new CallbackFactorySandbox(sandboxOption);
331
+ else throw new Error(invalidSandboxOptionMessage(sandboxOption));
332
+ this.#sandbox = sandboxConfig;
286
333
  const workspaceRegistry = new FactoryWorkspaceRegistry();
287
334
  const stateSigner = createStateSigner(this.#config.stateSecret);
288
335
  if (auth && hasAuthInit(auth)) await timedPhase("prepare.auth.init", () => auth.init({
@@ -383,8 +430,20 @@ var MastraFactory = class {
383
430
  ...onTerminalStage ? { onTerminalStage } : {},
384
431
  ...githubIntegration ? { onAccepted: (args) => reconcileGithubAcceptanceLabels(githubIntegration, sourceControlStorage.forIntegration(githubIntegration.id), args) } : {}
385
432
  }) : void 0;
433
+ this.#environmentBuilds = this.#sandbox?.builds ? new EnvironmentBuildRunner({
434
+ sandbox: this.#sandbox,
435
+ projects: factoryProjectsStorage,
436
+ sourceControl: githubIntegration?.versionControl ? {
437
+ storage: sourceControlStorage.forIntegration(githubIntegration.id),
438
+ versionControl: githubIntegration.versionControl
439
+ } : void 0,
440
+ logger: { info: (msg, meta) => console.info(`[Mastra Factory] ${msg}`, meta ?? {}) }
441
+ }, { getMastra: () => this.#prepared?.base.controller.getMastra() }) : void 0;
442
+ const environmentBuilds = this.#environmentBuilds;
386
443
  const projectRoutes = new ProjectRoutes({
387
444
  auth: routeAuth,
445
+ sandbox: this.#sandbox,
446
+ ...environmentBuilds ? { environmentBuilds } : {},
388
447
  projects: factoryProjectsStorage,
389
448
  sourceControl: sourceControlStorage,
390
449
  versionControlIntegrationIds: integrations.filter((integration) => integration.versionControl).map((integration) => integration.id),
@@ -557,9 +616,10 @@ var MastraFactory = class {
557
616
  ...mastraStorageBackend ? { storageBackend: mastraStorageBackend } : {},
558
617
  ...this.#config.knowledge ? { knowledge: this.#config.knowledge } : {},
559
618
  inputProcessors: async ({ requestContext }) => {
619
+ const environmentProcessor = new FactoryEnvironmentStateProcessor();
560
620
  if (factoryProcessor) {
561
621
  await factoryProcessor.prepareMemorySettings(requestContext);
562
- return [factoryProcessor];
622
+ return [environmentProcessor, factoryProcessor];
563
623
  }
564
624
  try {
565
625
  await loadMemorySettings({
@@ -569,7 +629,7 @@ var MastraFactory = class {
569
629
  } catch (error) {
570
630
  reportMemorySettingsUnavailable(requestContext, error instanceof Error ? error.message : String(error));
571
631
  }
572
- return [];
632
+ return [environmentProcessor];
573
633
  },
574
634
  ...vector ? { vector } : {},
575
635
  ...toolIntegrations.length > 0 || sourceControlToolProviders.length > 0 || workItemsStorage && transitionService ? { extraTools: async ({ requestContext }) => {
@@ -583,7 +643,7 @@ var MastraFactory = class {
583
643
  tools[name] = tool;
584
644
  }
585
645
  };
586
- if (storage.isDomainReady("source-control")) mergeTools("source-control", createSourceControlTools({
646
+ if (storage.isDomainReady("source-control")) mergeTools("source-control", await createSourceControlTools({
587
647
  requestContext,
588
648
  providers: sourceControlToolProviders,
589
649
  audit: auditDomain
@@ -659,6 +719,17 @@ var MastraFactory = class {
659
719
  toolContext: {
660
720
  ...toolContext,
661
721
  context: requestContext
722
+ },
723
+ resolveRepositorySlugs: async (sessionId) => {
724
+ for (const provider of sourceControlToolProviders) {
725
+ const session = await provider.storage.sessions.getBySessionId(sessionId);
726
+ if (!session) continue;
727
+ return environmentSlugs(await resolveSessionRepositories({
728
+ sourceControl: provider.storage,
729
+ session
730
+ }));
731
+ }
732
+ return [];
662
733
  }
663
734
  });
664
735
  await Promise.all(integrations.map(async (integration) => {
@@ -696,6 +767,7 @@ var MastraFactory = class {
696
767
  publicOrigin,
697
768
  stateSigner,
698
769
  sandbox: sandboxConfig,
770
+ ...environmentBuilds ? { onRepositoryPush: environmentBuilds.onRepositoryPush } : {},
699
771
  sessionRetirement,
700
772
  factoryStorage: storage,
701
773
  integrationStorage,
@@ -896,7 +968,8 @@ var MastraFactory = class {
896
968
  return {
897
969
  ...prepared.mastraArgs,
898
970
  ...auth ? { studio: { auth } } : {},
899
- ...integrationWorkers.length > 0 ? { workers: integrationWorkers } : {}
971
+ ...integrationWorkers.length > 0 ? { workers: integrationWorkers } : {},
972
+ ...environmentBuilds ? { workflows: { [ENVIRONMENT_BUILD_WORKFLOW_ID]: environmentBuilds.workflow } } : {}
900
973
  };
901
974
  }
902
975
  /**
@@ -907,6 +980,7 @@ var MastraFactory = class {
907
980
  async finalize() {
908
981
  if (!this.#prepared) throw new Error("MastraFactory.finalize() called before prepare()");
909
982
  await timedPhase("finalize.controller", () => this.#prepared.finalize());
983
+ await this.#environmentBuilds?.probeSchedules();
910
984
  await timedPhase("finalize.reconcileBoundThreads", () => this.#factoryProcessor?.reconcileAllBoundThreads() ?? Promise.resolve());
911
985
  this.#dispatcher?.start();
912
986
  }