@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":"surface.js","names":[],"sources":["../../src/routes/surface.ts"],"sourcesContent":["import type { AuthStorage } from '@mastra/code-sdk/auth/storage';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport type { ApiRoute, IUserProvider } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { FactoryStorage } from '@mastra/core/storage';\n\nimport type { FactoryIntegration, IntegrationContext } from '../integrations/base.js';\nimport { getGithubFeatureDiagnostics } from '../integrations/github/config.js';\nimport type { GithubIntegration } from '../integrations/github/integration.js';\nimport { MaterializeError } from '../integrations/github/sandbox.js';\nimport { FactoryDispatchError } from '../rules/dispatch-errors.js';\nimport type { FactoryBindingPreparationInput } from '../rules/dispatcher.js';\nimport { FactoryStartCoordinator } from '../rules/start-coordinator.js';\nimport { FactoryTransitionService } from '../rules/transition-service.js';\nimport type { FactoryRules } from '../rules/types.js';\nimport { factoryRuleStage } from '../rules/types.js';\nimport type { BaseCheckpointTriggers } from '../sandbox/base-checkpoint-triggers.js';\nimport type { SandboxFleet } from '../sandbox/fleet.js';\nimport {\n ensureFactorySourceSession,\n FactorySourceSessionResolutionError,\n resolveFactoryDefaultModelId,\n} from '../session/factory-session.js';\nimport { LiveSessions } from '../session/live-sessions.js';\nimport type { StateSigner } from '../state-signing.js';\nimport type { AuditEmitter } from '../storage/domains/audit/domain.js';\nimport type { ChannelIdentityStorage } from '../storage/domains/channel-identity/base.js';\nimport type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';\nimport { FactoryFeedReader } from '../storage/domains/comments/feed-context.js';\nimport type { ModelCredentialsStorage } from '../storage/domains/credentials/base.js';\nimport type { CustomProvidersStorage } from '../storage/domains/custom-providers/base.js';\nimport type { FilesystemStorage } from '../storage/domains/filesystem/base.js';\nimport type { IntakeStorage } from '../storage/domains/intake/base.js';\nimport type { IntegrationStorage } from '../storage/domains/integrations/base.js';\nimport type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { ModelPacksStorage } from '../storage/domains/model-packs/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { QueueHealthStorage } from '../storage/domains/queue-health/base.js';\nimport {\n SourceControlConnectionNotFoundError,\n type SourceControlStorage,\n} from '../storage/domains/source-control/base.js';\nimport type { FactoryDispatchFailureCode, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { workItemBranch, workItemBranchSource } from '../work-item-branch.js';\nimport { ConfigRoutes } from './config.js';\nimport { invalidateCustomProvidersSnapshots } from './custom-provider-source.js';\nimport { buildFsRoutes } from './fs.js';\nimport { IntakeRoutes } from './intake.js';\nimport { KnowledgeRoutes } from './knowledge.js';\nimport { OAuthRoutes } from './oauth.js';\nimport type { RouteAuth } from './route.js';\nimport { SkillRoutes } from './skills.js';\nimport { invalidateTenantCredentialSnapshots } from './tenant-credentials.js';\nimport { WorkItemRoutes } from './work-items.js';\n\nconst MATERIALIZE_FAILURE_CODE = {\n 'git-missing': 'repository_git_missing',\n 'egress-blocked': 'repository_egress_blocked',\n 'clone-failed': 'repository_clone_failed',\n 'pull-failed': 'repository_pull_failed',\n 'push-failed': 'repository_push_failed',\n 'commit-failed': 'repository_commit_failed',\n 'gh-missing': 'repository_cli_missing',\n 'pr-failed': 'repository_pr_failed',\n} satisfies Record<MaterializeError['code'], FactoryDispatchFailureCode>;\nexport interface IntegrationRegistration {\n integration: FactoryIntegration;\n ready: boolean;\n ensureReady: () => Promise<void>;\n}\n\nexport interface FactoryApiRoutesDeps {\n controllerId: string;\n controller: AgentController<MastraCodeState>;\n /** Request-auth seam threaded from the host (no service locator). */\n auth: RouteAuth;\n /** Optional user directory for resolving persisted owners to display profiles. */\n users?: Pick<IUserProvider, 'getUser' | 'getUsers'>;\n authStorage: AuthStorage;\n audit: AuditEmitter;\n fsRoot?: string;\n publicOrigin: string;\n stateSigner?: StateSigner;\n /** Sandbox fleet constructed by the factory (disabled when no machine). */\n fleet: SandboxFleet;\n /** Base-checkpoint trigger surface, when the factory constructed one. */\n baseCheckpoints?: BaseCheckpointTriggers;\n /** Root factory storage backend (distributed locks, app-db diagnostics). */\n factoryStorage?: FactoryStorage;\n integrationStorage: IntegrationStorage;\n sourceControlStorage: SourceControlStorage;\n /** App-table domain handles, registered and owned by `MastraFactory.prepare()`. */\n domains: {\n intake: IntakeStorage;\n modelCredentials: ModelCredentialsStorage;\n memorySettings: MemorySettingsStorage;\n customProviders: CustomProvidersStorage;\n filesystem: FilesystemStorage;\n modelPacks: ModelPacksStorage;\n projects: FactoryProjectsStorage;\n queueHealth: QueueHealthStorage;\n workItems: WorkItemsStorage;\n channelIdentity: ChannelIdentityStorage;\n comments: WorkItemCommentsStorage;\n };\n integrations?: IntegrationRegistration[];\n intakeReady: boolean;\n factoryReady: boolean;\n knowledgeEnabled: boolean;\n /** Resolved Factory rule set, threaded from the host (no service locator). */\n rules: FactoryRules;\n factoryTransitionService?: FactoryTransitionService;\n sessionRetirement?: import('../sandbox/session-retirement.js').SessionRetirementCoordinator;\n onFactoryRuntime?: (runtime: {\n transitionService: FactoryTransitionService;\n prepareBinding?: (input: FactoryBindingPreparationInput) => Promise<void>;\n }) => void;\n}\n\nfunction guardIntegrationRoutes({\n integration,\n ready,\n ensureReady,\n routes,\n}: IntegrationRegistration & { routes: ApiRoute[] }): ApiRoute[] {\n if (ready) return routes;\n return routes.map(route => {\n if ('handler' in route) {\n const handler = route.handler;\n return {\n ...route,\n handler: async (context: Parameters<typeof handler>[0]) => {\n try {\n await ensureReady();\n } catch {\n return context.json(\n { error: 'integration_unavailable', message: `${integration.id} integration is unavailable.` },\n 503,\n );\n }\n return handler(context, async () => {});\n },\n };\n }\n\n const createHandler = route.createHandler;\n return {\n ...route,\n createHandler: async (args: Parameters<typeof createHandler>[0]) => {\n const handler = await createHandler(args);\n return async (context: Parameters<typeof handler>[0]) => {\n try {\n await ensureReady();\n } catch {\n return context.json(\n { error: 'integration_unavailable', message: `${integration.id} integration is unavailable.` },\n 503,\n );\n }\n return handler(context);\n };\n },\n };\n });\n}\n\n/**\n * Start a factory run for a rule binding: ensure the source-control session the\n * coordinator requires, then hand it to `prepare` along with the factory's\n * default model. Exported for tests — this is the autonomous entry point with no\n * browser and no interactive user, so nothing else would catch a regression in\n * what it forwards.\n */\nexport async function prepareFactoryRuleBinding(\n github: GithubIntegration,\n coordinator: Pick<FactoryStartCoordinator, 'prepare'>,\n projects: FactoryProjectsStorage,\n input: FactoryBindingPreparationInput,\n): Promise<void> {\n try {\n const branch = workItemBranch({\n id: input.item.id,\n source: workItemBranchSource(input.item.externalSource),\n metadata: input.item.metadata,\n });\n const destinationStage = factoryRuleStage(input.item.stages);\n if (!destinationStage) {\n throw new FactoryDispatchError(\n 'unsupported_provider_item',\n 'Factory skill invocation requires one exclusive board stage.',\n );\n }\n const repositorySlug =\n typeof input.item.metadata?.repository === 'string' ? input.item.metadata.repository : undefined;\n const preparedSession = await ensureFactorySourceSession({\n sourceControl: github.sourceControlStorage,\n orgId: input.record.orgId,\n factoryProjectId: input.record.factoryProjectId,\n repositorySlug,\n branch,\n // A human-approved proposal has an interactive user: attribute the run to\n // the approver, not the repo connector.\n attributeToUserId: input.record.approvedBy ?? undefined,\n });\n\n await coordinator.prepare({\n orgId: input.record.orgId,\n userId: preparedSession.userId,\n factoryProjectId: input.record.factoryProjectId,\n sessionId: preparedSession.sessionId,\n defaultModelId: await resolveFactoryDefaultModelId(projects, input.record.factoryProjectId),\n threadTitle: `${input.role === 'review' ? 'PR' : 'Issue'}: ${input.item.title}`,\n kickoffKey: input.record.id,\n destinationStage,\n workItem: {\n id: input.item.id,\n role: input.role,\n input: {\n externalSource: input.item.externalSource,\n parentWorkItemId: input.item.parentWorkItemId,\n title: input.item.title,\n stages: ['intake'],\n sessions: input.item.sessions,\n metadata: input.item.metadata,\n },\n },\n });\n } catch (error) {\n if (error instanceof FactoryDispatchError) throw error;\n if (error instanceof FactorySourceSessionResolutionError) {\n const code = error.reason === 'connection' ? 'source_control_missing' : 'source_repository_missing';\n throw new FactoryDispatchError(code, error.message, { cause: error });\n }\n if (error instanceof SourceControlConnectionNotFoundError) {\n throw new FactoryDispatchError('source_control_missing', error.message, { cause: error });\n }\n if (error instanceof MaterializeError) {\n throw new FactoryDispatchError(MATERIALIZE_FAILURE_CODE[error.code], error.message, { cause: error });\n }\n throw error;\n }\n}\n\n/**\n * Build the {@link IntegrationContext} handed to an integration when the\n * factory collects its capabilities (routes, workers). One shape everywhere:\n * `assembleFactoryApiRoutes` uses it per registration, and `MastraFactory` uses it\n * when collecting integration workers at finalize.\n */\nexport function buildIntegrationContext(\n deps: Pick<\n FactoryApiRoutesDeps,\n | 'controller'\n | 'publicOrigin'\n | 'auth'\n | 'users'\n | 'fleet'\n | 'factoryStorage'\n | 'integrationStorage'\n | 'sourceControlStorage'\n > & {\n stateSigner: StateSigner;\n emitAudit?: AuditEmitter['emit'];\n rules: FactoryRules;\n factoryReady: boolean;\n domains: Pick<\n FactoryApiRoutesDeps['domains'],\n 'projects' | 'intake' | 'workItems' | 'channelIdentity' | 'memorySettings'\n >;\n /**\n * Stable id of the registered source-control-owning integration (today:\n * `'github'` when registered). Every call site must derive and pass it so\n * `routes()`, `channels()`, and `workers()` all see the same context shape.\n */\n sourceControlOwnerId?: string;\n /** Base-checkpoint trigger surface, when the factory constructed one. */\n baseCheckpoints?: BaseCheckpointTriggers;\n },\n integrationId: string,\n): IntegrationContext {\n return {\n auth: deps.auth,\n ...(deps.users ? { users: deps.users } : {}),\n fleet: deps.fleet,\n ...(deps.baseCheckpoints ? { baseCheckpoints: deps.baseCheckpoints } : {}),\n factoryStorage: deps.factoryStorage,\n baseUrl: deps.publicOrigin,\n controller: deps.controller,\n stateSigner: deps.stateSigner,\n storage: {\n generic: deps.integrationStorage.forIntegration(integrationId),\n sourceControl: deps.sourceControlStorage.forIntegration(integrationId),\n ...(deps.sourceControlOwnerId\n ? { sourceControlOwner: deps.sourceControlStorage.forIntegration(deps.sourceControlOwnerId) }\n : {}),\n projects: deps.domains.projects,\n intake: deps.domains.intake,\n channelIdentity: deps.domains.channelIdentity,\n memorySettings: deps.domains.memorySettings,\n },\n ...(deps.factoryReady ? { workItems: deps.domains.workItems } : {}),\n ...(deps.factoryReady ? { rules: { config: deps.rules, workItems: deps.domains.workItems } } : {}),\n ...(deps.emitAudit ? { hooks: { emitAudit: deps.emitAudit } } : {}),\n };\n}\n\n/**\n * Disabled-status stub for the well-known integration ids. The SPA polls\n * `/web/github/status` and `/web/linear/status` unconditionally, so when an\n * integration is absent (or not ready) the status contract must still hold.\n * Unknown custom ids get no stub — the SPA doesn't poll them.\n */\nfunction disabledIntegrationStatusRoutes(deps: FactoryApiRoutesDeps, id: string, configured = false): ApiRoute[] {\n if (id === 'github') {\n return [\n registerApiRoute('/web/github/status', {\n method: 'GET',\n requiresAuth: false,\n handler: c =>\n c.json({\n enabled: false,\n connected: false,\n installations: [],\n reason: 'missing_config',\n diagnostics: getGithubFeatureDiagnostics({\n github: undefined,\n auth: deps.auth,\n appDbConfigured: deps.factoryStorage !== undefined,\n stateSigner: deps.stateSigner,\n fleet: deps.fleet,\n }),\n }),\n }),\n ];\n }\n if (id === 'linear') {\n return [\n registerApiRoute('/web/linear/status', {\n method: 'GET',\n requiresAuth: false,\n handler: c =>\n c.json({\n enabled: false,\n connected: false,\n workspace: null,\n reason: 'missing_config',\n diagnostics: {\n linearAppConfigured: configured,\n factoryAuthEnabled: deps.auth.enabled(),\n appDbConfigured: true,\n },\n }),\n }),\n ];\n }\n return [];\n}\n\n/**\n * Stub for `GET /web/channel-accounts` when NO Slack integration is\n * registered. The SPA's Connections section polls the path unconditionally;\n * without a stub the SPA fallback serves HTML, which the UI can only read as\n * \"old server / unknown\". The machine-readable reason lets it say the truth:\n * the integration isn't registered.\n *\n * Mounted only for ABSENT slack — a registered integration owns the path via\n * its connect routes (or, when the state signer is unstable, gets no routes\n * at all and the UI falls back to the generic copy). Static payload, leaks\n * nothing → no auth needed, same posture as the github/linear stubs.\n */\nfunction absentSlackChannelAccountsRoutes(): ApiRoute[] {\n return [\n registerApiRoute('/web/channel-accounts', {\n method: 'GET',\n requiresAuth: false,\n handler: c => c.json({ accounts: [], canConnect: false, reason: 'not_registered' }),\n }),\n ];\n}\n\n/**\n * Assemble the custom `/web/*` API routes as Mastra `server.apiRoutes`:\n * - fs browser routes (project picker), confined to `fsRoot`\n * - config routes (provider/API-key/model-pack/OM management)\n * - every registered integration's `routes()` surface (full set when ready,\n * disabled-status stub otherwise), plus stubs for absent known ids\n */\nexport function assembleFactoryApiRoutes(deps: FactoryApiRoutesDeps): ApiRoute[] {\n const emitAudit: AuditEmitter['emit'] = args => deps.audit.emit(args);\n const registrations = deps.integrations ?? [];\n const githubRegistration = registrations.find(({ integration }) => integration.id === 'github');\n const githubStorage = githubRegistration ? deps.sourceControlStorage.forIntegration('github') : undefined;\n const githubIntegration = githubRegistration?.integration as GithubIntegration | undefined;\n\n const integrationRoutes = registrations.flatMap(registration => {\n const { integration } = registration;\n if (!deps.stateSigner) return disabledIntegrationStatusRoutes(deps, integration.id, true);\n const context = buildIntegrationContext(\n {\n ...deps,\n stateSigner: deps.stateSigner,\n emitAudit,\n ...(githubRegistration ? { sourceControlOwnerId: 'github' } : {}),\n },\n integration.id,\n );\n return guardIntegrationRoutes({ ...registration, routes: integration.routes(context) });\n });\n // Absent known integrations still get their disabled-status stub.\n const absentStubs = ['github', 'linear']\n .filter(id => !registrations.some(({ integration }) => integration.id === id))\n .flatMap(id => disabledIntegrationStatusRoutes(deps, id));\n // Absent slack gets the channel-accounts not-registered stub (registered\n // slack owns the path via its own connect routes).\n const slackAbsentStubs = registrations.some(({ integration }) => integration.id === 'slack')\n ? []\n : absentSlackChannelAccountsRoutes();\n\n const transitionService = deps.factoryReady\n ? (deps.factoryTransitionService ??\n new FactoryTransitionService({ rules: deps.rules, storage: deps.domains.workItems }))\n : undefined;\n const startCoordinator = transitionService\n ? new FactoryStartCoordinator(\n deps.controller,\n deps.domains.workItems,\n transitionService,\n githubIntegration?.sourceControlStorage,\n deps.domains.memorySettings,\n new FactoryFeedReader(deps.domains.comments),\n )\n : undefined;\n if (transitionService && startCoordinator) {\n deps.onFactoryRuntime?.({\n transitionService,\n ...(githubIntegration\n ? {\n prepareBinding: (input: FactoryBindingPreparationInput) =>\n prepareFactoryRuleBinding(githubIntegration, startCoordinator, deps.domains.projects, input),\n }\n : {}),\n });\n }\n\n return [\n ...buildFsRoutes({\n root: deps.fsRoot,\n sessionFs: {\n auth: deps.auth,\n fleet: deps.fleet,\n sessions: deps.sourceControlStorage.forIntegration('github').sessions,\n filesystem: deps.domains.filesystem,\n },\n }),\n ...new ConfigRoutes({\n auth: deps.auth,\n controller: deps.controller,\n authStorage: deps.authStorage,\n modelCredentials: deps.domains.modelCredentials,\n modelPacks: deps.domains.modelPacks,\n sourceControlSessions: deps.sourceControlStorage.forIntegration('github').sessions,\n memorySettings: deps.domains.memorySettings,\n factoryProjects: deps.domains.projects,\n customProviders: deps.domains.customProviders,\n features: { knowledge: deps.knowledgeEnabled },\n onCredentialsChanged: invalidateTenantCredentialSnapshots,\n onCustomProvidersChanged: invalidateCustomProvidersSnapshots,\n }).routes(),\n ...new OAuthRoutes({\n auth: deps.auth,\n authStorage: deps.authStorage,\n modelCredentials: deps.domains.modelCredentials,\n onCredentialsChanged: invalidateTenantCredentialSnapshots,\n }).routes(),\n ...new SkillRoutes({\n auth: deps.auth,\n controllerId: deps.controllerId,\n controller: deps.controller,\n sourceControlStorage: githubStorage,\n ensureSourceControlReady: githubRegistration?.ensureReady,\n }).routes(),\n ...integrationRoutes,\n ...absentStubs,\n ...slackAbsentStubs,\n ...(deps.intakeReady\n ? new IntakeRoutes({\n auth: deps.auth,\n audit: deps.audit,\n intake: deps.domains.intake,\n projects: deps.domains.projects,\n integrations: (deps.integrations ?? []).flatMap(({ integration }) =>\n integration.intake ? [{ id: integration.id, intake: integration.intake }] : [],\n ),\n }).routes()\n : []),\n ...(deps.factoryReady && deps.knowledgeEnabled\n ? new KnowledgeRoutes({\n auth: deps.auth,\n projects: deps.domains.projects,\n knowledge: async () => deps.factoryStorage?.getMastraStorage().getStore('knowledge'),\n }).routes()\n : []),\n ...(deps.factoryReady\n ? new WorkItemRoutes({\n auth: deps.auth,\n audit: deps.audit,\n projects: deps.domains.projects,\n workItems: deps.domains.workItems,\n comments: deps.domains.comments,\n queueHealth: deps.domains.queueHealth,\n transitionService,\n startCoordinator,\n liveSessions: new LiveSessions(deps.controller),\n }).routes()\n : []),\n ];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"surface.js","names":[],"sources":["../../src/routes/surface.ts"],"sourcesContent":["import type { AuthStorage } from '@mastra/code-sdk/auth/storage';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport type { ApiRoute, IUserProvider } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { FactoryStorage } from '@mastra/core/storage';\n\nimport type { FactoryIntegration, IntegrationContext } from '../integrations/base.js';\nimport { getGithubFeatureDiagnostics } from '../integrations/github/config.js';\nimport type { GithubIntegration } from '../integrations/github/integration.js';\nimport { MaterializeError } from '../integrations/github/sandbox.js';\nimport { FactoryDispatchError } from '../rules/dispatch-errors.js';\nimport type { FactoryBindingPreparationInput } from '../rules/dispatcher.js';\nimport { FactoryStartCoordinator } from '../rules/start-coordinator.js';\nimport { FactoryTransitionService } from '../rules/transition-service.js';\nimport type { FactoryRules } from '../rules/types.js';\nimport { factoryRuleStage } from '../rules/types.js';\nimport type { MastraFactorySandboxConfig } from '../sandbox/session-sandbox.js';\nimport {\n ensureFactorySourceSession,\n FactorySourceSessionResolutionError,\n resolveFactoryDefaultModelId,\n} from '../session/factory-session.js';\nimport { LiveSessions } from '../session/live-sessions.js';\nimport type { StateSigner } from '../state-signing.js';\nimport type { AuditEmitter } from '../storage/domains/audit/domain.js';\nimport type { ChannelIdentityStorage } from '../storage/domains/channel-identity/base.js';\nimport type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';\nimport { FactoryFeedReader } from '../storage/domains/comments/feed-context.js';\nimport type { ModelCredentialsStorage } from '../storage/domains/credentials/base.js';\nimport type { CustomProvidersStorage } from '../storage/domains/custom-providers/base.js';\nimport type { FilesystemStorage } from '../storage/domains/filesystem/base.js';\nimport type { IntakeStorage } from '../storage/domains/intake/base.js';\nimport type { IntegrationStorage } from '../storage/domains/integrations/base.js';\nimport type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { ModelPacksStorage } from '../storage/domains/model-packs/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { QueueHealthStorage } from '../storage/domains/queue-health/base.js';\nimport {\n SourceControlConnectionNotFoundError,\n type SourceControlStorage,\n} from '../storage/domains/source-control/base.js';\nimport type { FactoryDispatchFailureCode, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { workItemBranch, workItemBranchSource } from '../work-item-branch.js';\nimport { ConfigRoutes } from './config.js';\nimport { invalidateCustomProvidersSnapshots } from './custom-provider-source.js';\nimport { buildFsRoutes } from './fs.js';\nimport { IntakeRoutes } from './intake.js';\nimport { KnowledgeRoutes } from './knowledge.js';\nimport { OAuthRoutes } from './oauth.js';\nimport type { RouteAuth } from './route.js';\nimport { SkillRoutes } from './skills.js';\nimport { invalidateTenantCredentialSnapshots } from './tenant-credentials.js';\nimport { WorkItemRoutes } from './work-items.js';\n\nconst MATERIALIZE_FAILURE_CODE = {\n 'git-missing': 'repository_git_missing',\n 'egress-blocked': 'repository_egress_blocked',\n 'clone-failed': 'repository_clone_failed',\n 'pull-failed': 'repository_pull_failed',\n 'push-failed': 'repository_push_failed',\n 'commit-failed': 'repository_commit_failed',\n 'gh-missing': 'repository_cli_missing',\n 'pr-failed': 'repository_pr_failed',\n} satisfies Record<MaterializeError['code'], FactoryDispatchFailureCode>;\nexport interface IntegrationRegistration {\n integration: FactoryIntegration;\n ready: boolean;\n ensureReady: () => Promise<void>;\n}\n\nexport interface FactoryApiRoutesDeps {\n controllerId: string;\n controller: AgentController<MastraCodeState>;\n /** Request-auth seam threaded from the host (no service locator). */\n auth: RouteAuth;\n /** Optional user directory for resolving persisted owners to display profiles. */\n users?: Pick<IUserProvider, 'getUser' | 'getUsers'>;\n authStorage: AuthStorage;\n audit: AuditEmitter;\n fsRoot?: string;\n publicOrigin: string;\n stateSigner?: StateSigner;\n /** Sandbox surface (enablement, provider label, create callback). */\n sandbox?: MastraFactorySandboxConfig;\n /** Root factory storage backend (distributed locks, app-db diagnostics). */\n factoryStorage?: FactoryStorage;\n integrationStorage: IntegrationStorage;\n sourceControlStorage: SourceControlStorage;\n /** App-table domain handles, registered and owned by `MastraFactory.prepare()`. */\n domains: {\n intake: IntakeStorage;\n modelCredentials: ModelCredentialsStorage;\n memorySettings: MemorySettingsStorage;\n customProviders: CustomProvidersStorage;\n filesystem: FilesystemStorage;\n modelPacks: ModelPacksStorage;\n projects: FactoryProjectsStorage;\n queueHealth: QueueHealthStorage;\n workItems: WorkItemsStorage;\n channelIdentity: ChannelIdentityStorage;\n comments: WorkItemCommentsStorage;\n };\n integrations?: IntegrationRegistration[];\n intakeReady: boolean;\n factoryReady: boolean;\n knowledgeEnabled: boolean;\n /** Resolved Factory rule set, threaded from the host (no service locator). */\n rules: FactoryRules;\n factoryTransitionService?: FactoryTransitionService;\n sessionRetirement?: import('../sandbox/session-retirement.js').SessionRetirementCoordinator;\n onFactoryRuntime?: (runtime: {\n transitionService: FactoryTransitionService;\n prepareBinding?: (input: FactoryBindingPreparationInput) => Promise<void>;\n }) => void;\n}\n\nfunction guardIntegrationRoutes({\n integration,\n ready,\n ensureReady,\n routes,\n}: IntegrationRegistration & { routes: ApiRoute[] }): ApiRoute[] {\n if (ready) return routes;\n return routes.map(route => {\n if ('handler' in route) {\n const handler = route.handler;\n return {\n ...route,\n handler: async (context: Parameters<typeof handler>[0]) => {\n try {\n await ensureReady();\n } catch {\n return context.json(\n { error: 'integration_unavailable', message: `${integration.id} integration is unavailable.` },\n 503,\n );\n }\n return handler(context, async () => {});\n },\n };\n }\n\n const createHandler = route.createHandler;\n return {\n ...route,\n createHandler: async (args: Parameters<typeof createHandler>[0]) => {\n const handler = await createHandler(args);\n return async (context: Parameters<typeof handler>[0]) => {\n try {\n await ensureReady();\n } catch {\n return context.json(\n { error: 'integration_unavailable', message: `${integration.id} integration is unavailable.` },\n 503,\n );\n }\n return handler(context);\n };\n },\n };\n });\n}\n\n/**\n * Start a factory run for a rule binding: ensure the source-control session the\n * coordinator requires, then hand it to `prepare` along with the factory's\n * default model. Exported for tests — this is the autonomous entry point with no\n * browser and no interactive user, so nothing else would catch a regression in\n * what it forwards.\n */\nexport async function prepareFactoryRuleBinding(\n github: GithubIntegration,\n coordinator: Pick<FactoryStartCoordinator, 'prepare'>,\n projects: FactoryProjectsStorage,\n input: FactoryBindingPreparationInput,\n): Promise<void> {\n try {\n const branch = workItemBranch({\n id: input.item.id,\n source: workItemBranchSource(input.item.externalSource),\n metadata: input.item.metadata,\n });\n const destinationStage = factoryRuleStage(input.item.stages);\n if (!destinationStage) {\n throw new FactoryDispatchError(\n 'unsupported_provider_item',\n 'Factory skill invocation requires one exclusive board stage.',\n );\n }\n const repositorySlug =\n typeof input.item.metadata?.repository === 'string' ? input.item.metadata.repository : undefined;\n const preparedSession = await ensureFactorySourceSession({\n sourceControl: github.sourceControlStorage,\n orgId: input.record.orgId,\n factoryProjectId: input.record.factoryProjectId,\n repositorySlug,\n branch,\n // A human-approved proposal has an interactive user: attribute the run to\n // the approver, not the repo connector.\n attributeToUserId: input.record.approvedBy ?? undefined,\n });\n\n await coordinator.prepare({\n orgId: input.record.orgId,\n userId: preparedSession.userId,\n factoryProjectId: input.record.factoryProjectId,\n sessionId: preparedSession.sessionId,\n defaultModelId: await resolveFactoryDefaultModelId(projects, input.record.factoryProjectId),\n threadTitle: `${input.role === 'review' ? 'PR' : 'Issue'}: ${input.item.title}`,\n kickoffKey: input.record.id,\n destinationStage,\n workItem: {\n id: input.item.id,\n role: input.role,\n input: {\n externalSource: input.item.externalSource,\n parentWorkItemId: input.item.parentWorkItemId,\n title: input.item.title,\n stages: ['intake'],\n sessions: input.item.sessions,\n metadata: input.item.metadata,\n },\n },\n });\n } catch (error) {\n if (error instanceof FactoryDispatchError) throw error;\n if (error instanceof FactorySourceSessionResolutionError) {\n const code = error.reason === 'connection' ? 'source_control_missing' : 'source_repository_missing';\n throw new FactoryDispatchError(code, error.message, { cause: error });\n }\n if (error instanceof SourceControlConnectionNotFoundError) {\n throw new FactoryDispatchError('source_control_missing', error.message, { cause: error });\n }\n if (error instanceof MaterializeError) {\n throw new FactoryDispatchError(MATERIALIZE_FAILURE_CODE[error.code], error.message, { cause: error });\n }\n throw error;\n }\n}\n\n/**\n * Build the {@link IntegrationContext} handed to an integration when the\n * factory collects its capabilities (routes, workers). One shape everywhere:\n * `assembleFactoryApiRoutes` uses it per registration, and `MastraFactory` uses it\n * when collecting integration workers at finalize.\n */\nexport function buildIntegrationContext(\n deps: Pick<\n FactoryApiRoutesDeps,\n | 'controller'\n | 'publicOrigin'\n | 'auth'\n | 'sandbox'\n | 'users'\n | 'factoryStorage'\n | 'integrationStorage'\n | 'sourceControlStorage'\n > & {\n stateSigner: StateSigner;\n emitAudit?: AuditEmitter['emit'];\n rules: FactoryRules;\n factoryReady: boolean;\n domains: Pick<\n FactoryApiRoutesDeps['domains'],\n 'projects' | 'intake' | 'workItems' | 'channelIdentity' | 'memorySettings'\n >;\n /**\n * Stable id of the registered source-control-owning integration (today:\n * `'github'` when registered). Every call site must derive and pass it so\n * `routes()`, `channels()`, and `workers()` all see the same context shape.\n */\n sourceControlOwnerId?: string;\n },\n integrationId: string,\n): IntegrationContext {\n return {\n auth: deps.auth,\n sandbox: deps.sandbox,\n ...(deps.users ? { users: deps.users } : {}),\n factoryStorage: deps.factoryStorage,\n baseUrl: deps.publicOrigin,\n controller: deps.controller,\n stateSigner: deps.stateSigner,\n storage: {\n generic: deps.integrationStorage.forIntegration(integrationId),\n sourceControl: deps.sourceControlStorage.forIntegration(integrationId),\n ...(deps.sourceControlOwnerId\n ? { sourceControlOwner: deps.sourceControlStorage.forIntegration(deps.sourceControlOwnerId) }\n : {}),\n projects: deps.domains.projects,\n intake: deps.domains.intake,\n channelIdentity: deps.domains.channelIdentity,\n memorySettings: deps.domains.memorySettings,\n },\n ...(deps.factoryReady ? { workItems: deps.domains.workItems } : {}),\n ...(deps.factoryReady ? { rules: { config: deps.rules, workItems: deps.domains.workItems } } : {}),\n ...(deps.emitAudit ? { hooks: { emitAudit: deps.emitAudit } } : {}),\n };\n}\n\n/**\n * Disabled-status stub for the well-known integration ids. The SPA polls\n * `/web/github/status` and `/web/linear/status` unconditionally, so when an\n * integration is absent (or not ready) the status contract must still hold.\n * Unknown custom ids get no stub — the SPA doesn't poll them.\n */\nfunction disabledIntegrationStatusRoutes(deps: FactoryApiRoutesDeps, id: string, configured = false): ApiRoute[] {\n if (id === 'github') {\n return [\n registerApiRoute('/web/github/status', {\n method: 'GET',\n requiresAuth: false,\n handler: c =>\n c.json({\n enabled: false,\n connected: false,\n installations: [],\n reason: 'missing_config',\n diagnostics: getGithubFeatureDiagnostics({\n github: undefined,\n auth: deps.auth,\n appDbConfigured: deps.factoryStorage !== undefined,\n stateSigner: deps.stateSigner,\n sandbox: deps.sandbox,\n }),\n }),\n }),\n ];\n }\n if (id === 'linear') {\n return [\n registerApiRoute('/web/linear/status', {\n method: 'GET',\n requiresAuth: false,\n handler: c =>\n c.json({\n enabled: false,\n connected: false,\n workspace: null,\n reason: 'missing_config',\n diagnostics: {\n linearAppConfigured: configured,\n factoryAuthEnabled: deps.auth.enabled(),\n appDbConfigured: true,\n },\n }),\n }),\n ];\n }\n return [];\n}\n\n/**\n * Stub for `GET /web/channel-accounts` when NO Slack integration is\n * registered. The SPA's Connections section polls the path unconditionally;\n * without a stub the SPA fallback serves HTML, which the UI can only read as\n * \"old server / unknown\". The machine-readable reason lets it say the truth:\n * the integration isn't registered.\n *\n * Mounted only for ABSENT slack — a registered integration owns the path via\n * its connect routes (or, when the state signer is unstable, gets no routes\n * at all and the UI falls back to the generic copy). Static payload, leaks\n * nothing → no auth needed, same posture as the github/linear stubs.\n */\nfunction absentSlackChannelAccountsRoutes(): ApiRoute[] {\n return [\n registerApiRoute('/web/channel-accounts', {\n method: 'GET',\n requiresAuth: false,\n handler: c => c.json({ accounts: [], canConnect: false, reason: 'not_registered' }),\n }),\n ];\n}\n\n/**\n * Assemble the custom `/web/*` API routes as Mastra `server.apiRoutes`:\n * - fs browser routes (project picker), confined to `fsRoot`\n * - config routes (provider/API-key/model-pack/OM management)\n * - every registered integration's `routes()` surface (full set when ready,\n * disabled-status stub otherwise), plus stubs for absent known ids\n */\nexport function assembleFactoryApiRoutes(deps: FactoryApiRoutesDeps): ApiRoute[] {\n const emitAudit: AuditEmitter['emit'] = args => deps.audit.emit(args);\n const registrations = deps.integrations ?? [];\n const githubRegistration = registrations.find(({ integration }) => integration.id === 'github');\n const githubStorage = githubRegistration ? deps.sourceControlStorage.forIntegration('github') : undefined;\n const githubIntegration = githubRegistration?.integration as GithubIntegration | undefined;\n\n const integrationRoutes = registrations.flatMap(registration => {\n const { integration } = registration;\n if (!deps.stateSigner) return disabledIntegrationStatusRoutes(deps, integration.id, true);\n const context = buildIntegrationContext(\n {\n ...deps,\n stateSigner: deps.stateSigner,\n emitAudit,\n ...(githubRegistration ? { sourceControlOwnerId: 'github' } : {}),\n },\n integration.id,\n );\n return guardIntegrationRoutes({ ...registration, routes: integration.routes(context) });\n });\n // Absent known integrations still get their disabled-status stub.\n const absentStubs = ['github', 'linear']\n .filter(id => !registrations.some(({ integration }) => integration.id === id))\n .flatMap(id => disabledIntegrationStatusRoutes(deps, id));\n // Absent slack gets the channel-accounts not-registered stub (registered\n // slack owns the path via its own connect routes).\n const slackAbsentStubs = registrations.some(({ integration }) => integration.id === 'slack')\n ? []\n : absentSlackChannelAccountsRoutes();\n\n const transitionService = deps.factoryReady\n ? (deps.factoryTransitionService ??\n new FactoryTransitionService({ rules: deps.rules, storage: deps.domains.workItems }))\n : undefined;\n const startCoordinator = transitionService\n ? new FactoryStartCoordinator(\n deps.controller,\n deps.domains.workItems,\n transitionService,\n githubIntegration?.sourceControlStorage,\n deps.domains.memorySettings,\n new FactoryFeedReader(deps.domains.comments),\n )\n : undefined;\n if (transitionService && startCoordinator) {\n deps.onFactoryRuntime?.({\n transitionService,\n ...(githubIntegration\n ? {\n prepareBinding: (input: FactoryBindingPreparationInput) =>\n prepareFactoryRuleBinding(githubIntegration, startCoordinator, deps.domains.projects, input),\n }\n : {}),\n });\n }\n\n return [\n ...buildFsRoutes({\n root: deps.fsRoot,\n sessionFs: {\n auth: deps.auth,\n sessions: deps.sourceControlStorage.forIntegration('github').sessions,\n filesystem: deps.domains.filesystem,\n },\n }),\n ...new ConfigRoutes({\n auth: deps.auth,\n controller: deps.controller,\n authStorage: deps.authStorage,\n modelCredentials: deps.domains.modelCredentials,\n modelPacks: deps.domains.modelPacks,\n sourceControlSessions: deps.sourceControlStorage.forIntegration('github').sessions,\n memorySettings: deps.domains.memorySettings,\n factoryProjects: deps.domains.projects,\n customProviders: deps.domains.customProviders,\n features: { knowledge: deps.knowledgeEnabled },\n onCredentialsChanged: invalidateTenantCredentialSnapshots,\n onCustomProvidersChanged: invalidateCustomProvidersSnapshots,\n }).routes(),\n ...new OAuthRoutes({\n auth: deps.auth,\n authStorage: deps.authStorage,\n modelCredentials: deps.domains.modelCredentials,\n onCredentialsChanged: invalidateTenantCredentialSnapshots,\n }).routes(),\n ...new SkillRoutes({\n auth: deps.auth,\n controllerId: deps.controllerId,\n controller: deps.controller,\n sourceControlStorage: githubStorage,\n ensureSourceControlReady: githubRegistration?.ensureReady,\n }).routes(),\n ...integrationRoutes,\n ...absentStubs,\n ...slackAbsentStubs,\n ...(deps.intakeReady\n ? new IntakeRoutes({\n auth: deps.auth,\n audit: deps.audit,\n intake: deps.domains.intake,\n projects: deps.domains.projects,\n integrations: (deps.integrations ?? []).flatMap(({ integration }) =>\n integration.intake ? [{ id: integration.id, intake: integration.intake }] : [],\n ),\n }).routes()\n : []),\n ...(deps.factoryReady && deps.knowledgeEnabled\n ? new KnowledgeRoutes({\n auth: deps.auth,\n projects: deps.domains.projects,\n knowledge: async () => deps.factoryStorage?.getMastraStorage().getStore('knowledge'),\n }).routes()\n : []),\n ...(deps.factoryReady\n ? new WorkItemRoutes({\n auth: deps.auth,\n audit: deps.audit,\n projects: deps.domains.projects,\n workItems: deps.domains.workItems,\n comments: deps.domains.comments,\n queueHealth: deps.domains.queueHealth,\n transitionService,\n startCoordinator,\n liveSessions: new LiveSessions(deps.controller),\n }).routes()\n : []),\n ];\n}\n"],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import type { ExecuteCommandOptions, WorkspaceSandbox } from '@mastra/core/workspace';
2
+ /** Result of one command executed inside a sandbox. */
3
+ export interface SandboxCommandResult {
4
+ exitCode: number;
5
+ stdout: string;
6
+ stderr: string;
7
+ }
8
+ /**
9
+ * A sandbox that can run commands.
10
+ *
11
+ * `executeCommand` is optional on `WorkspaceSandbox` because some providers
12
+ * only offer a filesystem. Every git and materialization helper here needs it,
13
+ * so this type promotes it to required. `id` is taken from core; the command
14
+ * signature is spelled out because core's `CommandResult` is wider than what
15
+ * these helpers read (see below).
16
+ */
17
+ export type ExecutableSandbox = Pick<WorkspaceSandbox, 'id'> & {
18
+ /**
19
+ * Runs a command. The options are core's, but the result is only the part
20
+ * these helpers read: core's `CommandResult` also carries `success` and
21
+ * `executionTimeMs`, and asking for them would reject test doubles that
22
+ * report exactly what the helpers consume.
23
+ */
24
+ executeCommand(command: string, args?: string[], options?: ExecuteCommandOptions): Promise<SandboxCommandResult>;
25
+ };
26
+ /**
27
+ * Narrow a sandbox to one that can run commands, or fail saying which
28
+ * capability is missing.
29
+ *
30
+ * Callers hold a `WorkspaceSandbox`, whose `executeCommand` is optional.
31
+ * Optional-chaining past it would hand every caller `undefined` where it
32
+ * expects an exit code, turning a misconfigured provider into a git command
33
+ * that silently did nothing.
34
+ */
35
+ export declare function requireExec(sandbox: WorkspaceSandbox): ExecutableSandbox;
36
+ //# sourceMappingURL=materialization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"materialization.d.ts","sourceRoot":"","sources":["../../src/sandbox/materialization.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAEtF,uDAAuD;AACvD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,GAAG;IAC7D;;;;;OAKG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;CAClH,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,gBAAgB,GAAG,iBAAiB,CAOxE"}
@@ -0,0 +1,18 @@
1
+ //#region src/sandbox/materialization.ts
2
+ /**
3
+ * Narrow a sandbox to one that can run commands, or fail saying which
4
+ * capability is missing.
5
+ *
6
+ * Callers hold a `WorkspaceSandbox`, whose `executeCommand` is optional.
7
+ * Optional-chaining past it would hand every caller `undefined` where it
8
+ * expects an exit code, turning a misconfigured provider into a git command
9
+ * that silently did nothing.
10
+ */
11
+ function requireExec(sandbox) {
12
+ if (typeof sandbox.executeCommand !== "function") throw new Error(`Sandbox provider '${sandbox.provider}' does not support executeCommand, which is required to run git and filesystem operations in a session.`);
13
+ return sandbox;
14
+ }
15
+ //#endregion
16
+ export { requireExec };
17
+
18
+ //# sourceMappingURL=materialization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"materialization.js","names":[],"sources":["../../src/sandbox/materialization.ts"],"sourcesContent":["import type { ExecuteCommandOptions, WorkspaceSandbox } from '@mastra/core/workspace';\n\n/** Result of one command executed inside a sandbox. */\nexport interface SandboxCommandResult {\n exitCode: number;\n stdout: string;\n stderr: string;\n}\n\n/**\n * A sandbox that can run commands.\n *\n * `executeCommand` is optional on `WorkspaceSandbox` because some providers\n * only offer a filesystem. Every git and materialization helper here needs it,\n * so this type promotes it to required. `id` is taken from core; the command\n * signature is spelled out because core's `CommandResult` is wider than what\n * these helpers read (see below).\n */\nexport type ExecutableSandbox = Pick<WorkspaceSandbox, 'id'> & {\n /**\n * Runs a command. The options are core's, but the result is only the part\n * these helpers read: core's `CommandResult` also carries `success` and\n * `executionTimeMs`, and asking for them would reject test doubles that\n * report exactly what the helpers consume.\n */\n executeCommand(command: string, args?: string[], options?: ExecuteCommandOptions): Promise<SandboxCommandResult>;\n};\n\n/**\n * Narrow a sandbox to one that can run commands, or fail saying which\n * capability is missing.\n *\n * Callers hold a `WorkspaceSandbox`, whose `executeCommand` is optional.\n * Optional-chaining past it would hand every caller `undefined` where it\n * expects an exit code, turning a misconfigured provider into a git command\n * that silently did nothing.\n */\nexport function requireExec(sandbox: WorkspaceSandbox): ExecutableSandbox {\n if (typeof sandbox.executeCommand !== 'function') {\n throw new Error(\n `Sandbox provider '${sandbox.provider}' does not support executeCommand, which is required to run git and filesystem operations in a session.`,\n );\n }\n return sandbox as ExecutableSandbox;\n}\n"],"mappings":";;;;;;;;;;AAqCA,SAAgB,YAAY,SAA8C;CACxE,IAAI,OAAO,QAAQ,mBAAmB,YACpC,MAAM,IAAI,MACR,qBAAqB,QAAQ,SAAS,wGACxC;CAEF,OAAO;AACT"}
@@ -1,10 +1,7 @@
1
1
  import type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';
2
2
  import type { WorkItemsStorage } from '../storage/domains/work-items/base.js';
3
- import type { SandboxFleet } from './fleet.js';
4
- type RetirementFleet = Pick<SandboxFleet, 'provider' | 'reattachSandbox' | 'teardownSandbox'>;
5
3
  type WarningLogger = (message: string, details: Record<string, unknown>) => void;
6
4
  export interface SessionRetirementCoordinatorOptions {
7
- fleet: RetirementFleet;
8
5
  invalidateSession?: (sessionId: string) => Promise<void> | void;
9
6
  warn?: WarningLogger;
10
7
  }
@@ -19,12 +16,17 @@ export interface RetireSessionInput {
19
16
  /**
20
17
  * Owns terminal and destructive session cleanup. Each session is serialized so
21
18
  * duplicate role bindings and competing deletion/transition requests cannot
22
- * run teardown concurrently. Once the sandbox binding is cleared, later calls
23
- * are idempotent no-ops apart from cache invalidation or requested row deletion.
19
+ * run teardown concurrently.
20
+ *
21
+ * Sandbox identity is the session id: retirement stops (or, for deleted
22
+ * sessions, destroys) the sandbox this process holds in the session memo.
23
+ * Sessions opened on another replica have no memo entry here — their VMs are
24
+ * left to the provider's idle lifecycle (pause / idle GC), which is the
25
+ * provider-owned half of the contract.
24
26
  */
25
27
  export declare class SessionRetirementCoordinator {
26
28
  #private;
27
- constructor(options: SessionRetirementCoordinatorOptions);
29
+ constructor(options?: SessionRetirementCoordinatorOptions);
28
30
  retireSession(input: RetireSessionInput): Promise<void>;
29
31
  retireWorkItemSessions(options: {
30
32
  workItems: Pick<WorkItemsStorage, 'get'>;
@@ -1 +1 @@
1
- {"version":3,"file":"session-retirement.d.ts","sourceRoot":"","sources":["../../src/sandbox/session-retirement.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAGV,0BAA0B,EAC3B,MAAM,2CAA2C,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAA+C,YAAY,EAAE,MAAM,YAAY,CAAC;AAE5F,KAAK,eAAe,GAAG,IAAI,CAAC,YAAY,EAAE,UAAU,GAAG,iBAAiB,GAAG,iBAAiB,CAAC,CAAC;AAC9F,KAAK,aAAa,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;AAEjF,MAAM,WAAW,mCAAmC;IAClD,KAAK,EAAE,eAAe,CAAC;IACvB,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAChE,IAAI,CAAC,EAAE,aAAa,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,0BAA0B,CAAC;IAC1C,8EAA8E;IAC9E,SAAS,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,wBAAwB,CAAC,CAAC;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;CACxB;AAQD;;;;;GAKG;AACH,qBAAa,4BAA4B;;gBAM3B,OAAO,EAAE,mCAAmC;IAMlD,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvD,sBAAsB,CAAC,OAAO,EAAE;QACpC,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;QACzC,aAAa,EAAE,0BAA0B,CAAC;QAC1C,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBX,+BAA+B,CAAC,OAAO,EAAE;QAC7C,aAAa,EAAE,0BAA0B,CAAC;QAC1C,SAAS,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,wBAAwB,CAAC,CAAC;QAC7D,KAAK,EAAE,MAAM,CAAC;QACd,mBAAmB,EAAE,MAAM,CAAC;KAC7B,GAAG,OAAO,CAAC,IAAI,CAAC;CA6LlB"}
1
+ {"version":3,"file":"session-retirement.d.ts","sourceRoot":"","sources":["../../src/sandbox/session-retirement.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAGV,0BAA0B,EAC3B,MAAM,2CAA2C,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAI9E,KAAK,aAAa,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;AAEjF,MAAM,WAAW,mCAAmC;IAClD,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAChE,IAAI,CAAC,EAAE,aAAa,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,0BAA0B,CAAC;IAC1C,8EAA8E;IAC9E,SAAS,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,wBAAwB,CAAC,CAAC;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;CACxB;AAQD;;;;;;;;;;GAUG;AACH,qBAAa,4BAA4B;;gBAK3B,OAAO,GAAE,mCAAwC;IAKvD,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvD,sBAAsB,CAAC,OAAO,EAAE;QACpC,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;QACzC,aAAa,EAAE,0BAA0B,CAAC;QAC1C,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBX,+BAA+B,CAAC,OAAO,EAAE;QAC7C,aAAa,EAAE,0BAA0B,CAAC;QAC1C,SAAS,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,wBAAwB,CAAC,CAAC;QAC7D,KAAK,EAAE,MAAM,CAAC;QACd,mBAAmB,EAAE,MAAM,CAAC;KAC7B,GAAG,OAAO,CAAC,IAAI,CAAC;CA0FlB"}
@@ -1,5 +1,7 @@
1
- import { DEFAULT_COMMAND_TIMEOUT_MS, runWorktreeTeardown } from "../integrations/github/sandbox.js";
2
- import { cleanReleasedSandbox } from "../integrations/github/sandbox-release.js";
1
+ import { requireExec } from "./materialization.js";
2
+ import { peekSessionSandbox } from "./session-sandbox.js";
3
+ import { releaseSessionSandbox } from "../integrations/github/sandbox-release.js";
4
+ import { DEFAULT_COMMAND_TIMEOUT_MS, runTeardownCommand } from "../integrations/github/sandbox.js";
3
5
  //#region src/sandbox/session-retirement.ts
4
6
  function boundedError(error) {
5
7
  const detail = error instanceof Error ? error.message : String(error);
@@ -9,16 +11,19 @@ function boundedError(error) {
9
11
  /**
10
12
  * Owns terminal and destructive session cleanup. Each session is serialized so
11
13
  * duplicate role bindings and competing deletion/transition requests cannot
12
- * run teardown concurrently. Once the sandbox binding is cleared, later calls
13
- * are idempotent no-ops apart from cache invalidation or requested row deletion.
14
+ * run teardown concurrently.
15
+ *
16
+ * Sandbox identity is the session id: retirement stops (or, for deleted
17
+ * sessions, destroys) the sandbox this process holds in the session memo.
18
+ * Sessions opened on another replica have no memo entry here — their VMs are
19
+ * left to the provider's idle lifecycle (pause / idle GC), which is the
20
+ * provider-owned half of the contract.
14
21
  */
15
22
  var SessionRetirementCoordinator = class {
16
- #fleet;
17
23
  #invalidateSession;
18
24
  #warn;
19
25
  #locks = /* @__PURE__ */ new Map();
20
- constructor(options) {
21
- this.#fleet = options.fleet;
26
+ constructor(options = {}) {
22
27
  this.#invalidateSession = options.invalidateSession ?? (() => {});
23
28
  this.#warn = options.warn ?? ((message, details) => console.warn(`[Mastra Factory] ${message}`, details));
24
29
  }
@@ -59,50 +64,7 @@ var SessionRetirementCoordinator = class {
59
64
  const session = await input.sourceControl.sessions.getBySessionId(input.sessionId);
60
65
  if (!session || session.orgId !== input.orgId) return;
61
66
  try {
62
- let projectRepository;
63
- try {
64
- projectRepository = await input.sourceControl.projectRepositories.get({
65
- orgId: input.orgId,
66
- id: session.projectRepositoryId
67
- });
68
- } catch (error) {
69
- this.#warn("Factory repository settings could not be loaded for session retirement", {
70
- orgId: session.orgId,
71
- sessionId: session.sessionId,
72
- projectRepositoryId: session.projectRepositoryId,
73
- error: boundedError(error)
74
- });
75
- }
76
- let sandbox;
77
- if (session.sandboxId && session.sandboxWorkdir) {
78
- try {
79
- sandbox = await this.#fleet.reattachSandbox(session.sandboxId, {
80
- actingUserId: session.userId,
81
- ...this.#fleet.provider === "local" ? { workingDirectory: session.sandboxWorkdir } : {}
82
- });
83
- } catch (error) {
84
- this.#warn("Factory session sandbox could not be reattached for retirement", {
85
- orgId: session.orgId,
86
- sessionId: session.sessionId,
87
- projectRepositoryId: session.projectRepositoryId,
88
- sandboxId: session.sandboxId,
89
- error: boundedError(error)
90
- });
91
- }
92
- if (sandbox && projectRepository?.teardownCommand) try {
93
- await runWorktreeTeardown(sandbox, session.sandboxWorkdir, projectRepository.teardownCommand, { timeoutMs: DEFAULT_COMMAND_TIMEOUT_MS });
94
- } catch (error) {
95
- this.#warn("Factory worktree teardown failed", {
96
- orgId: session.orgId,
97
- sessionId: session.sessionId,
98
- projectRepositoryId: session.projectRepositoryId,
99
- sandboxId: session.sandboxId,
100
- error: boundedError(error)
101
- });
102
- }
103
- if (this.#fleet.provider === "local") await this.#destroyLocalSandbox(input.sourceControl, session, sandbox);
104
- else await this.#releaseRemoteSandbox(input.sourceControl, session, sandbox);
105
- }
67
+ await this.#teardownSessionSandbox(input, session);
106
68
  } finally {
107
69
  try {
108
70
  await this.#invalidateSession(session.sessionId);
@@ -123,100 +85,43 @@ var SessionRetirementCoordinator = class {
123
85
  }
124
86
  }
125
87
  }
126
- async #releaseRemoteSandbox(sourceControl, session, sandbox) {
127
- const sandboxId = session.sandboxId;
128
- const sandboxWorkdir = session.sandboxWorkdir;
129
- if (!sandboxId || !sandboxWorkdir) return;
130
- await cleanReleasedSandbox({
131
- fleet: this.#fleet,
132
- sourceControl,
133
- orgId: session.orgId,
134
- projectRepositoryId: session.projectRepositoryId,
135
- sandboxId,
136
- sandboxWorkdir,
137
- actingUserId: session.userId,
138
- ...sandbox ? { sandbox } : {}
139
- });
88
+ async #teardownSessionSandbox(input, session) {
89
+ const entry = peekSessionSandbox(session.id);
90
+ if (!entry) return;
91
+ let projectRepository;
140
92
  try {
141
- await sourceControl.sandboxPool.release({
142
- orgId: session.orgId,
143
- projectRepositoryId: session.projectRepositoryId,
144
- userId: session.userId,
145
- sandboxId,
146
- sandboxWorkdir
93
+ projectRepository = await input.sourceControl.projectRepositories.get({
94
+ orgId: input.orgId,
95
+ id: session.projectRepositoryId
147
96
  });
148
97
  } catch (error) {
149
- this.#warn("Factory remote sandbox release failed", {
98
+ this.#warn("Factory repository settings could not be loaded for session retirement", {
150
99
  orgId: session.orgId,
151
100
  sessionId: session.sessionId,
152
101
  projectRepositoryId: session.projectRepositoryId,
153
- sandboxId,
154
102
  error: boundedError(error)
155
103
  });
156
104
  }
157
- try {
158
- await sourceControl.sessions.setSandbox({
159
- id: session.id,
160
- sandboxId: null,
161
- sandboxWorkdir
162
- });
105
+ if (projectRepository?.teardownCommand && entry.workdir) try {
106
+ await runTeardownCommand(requireExec(entry.sandbox), entry.workdir, projectRepository.teardownCommand, { timeoutMs: DEFAULT_COMMAND_TIMEOUT_MS });
163
107
  } catch (error) {
164
- this.#warn("Factory remote sandbox binding could not be cleared", {
108
+ this.#warn("Factory teardown command failed", {
165
109
  orgId: session.orgId,
166
110
  sessionId: session.sessionId,
167
111
  projectRepositoryId: session.projectRepositoryId,
168
- sandboxId,
169
112
  error: boundedError(error)
170
113
  });
171
114
  }
172
- }
173
- async #destroyLocalSandbox(sourceControl, session, sandbox) {
174
- const sandboxWorkdir = session.sandboxWorkdir ?? "";
175
- const binding = {
176
- get sandboxId() {
177
- return session.sandboxId;
178
- },
179
- setSandboxId: async (sandboxId) => {
180
- await sourceControl.sessions.setSandbox({
181
- id: session.id,
182
- sandboxId,
183
- sandboxWorkdir
184
- });
185
- session.sandboxId = sandboxId;
186
- },
187
- clear: async () => {
188
- await sourceControl.sessions.setSandbox({
189
- id: session.id,
190
- sandboxId: null,
191
- sandboxWorkdir
192
- });
193
- session.sandboxId = null;
194
- }
195
- };
196
115
  try {
197
- await this.#fleet.teardownSandbox(binding, sandbox);
198
- } catch (error) {
199
- this.#warn("Factory local sandbox destruction failed", {
200
- orgId: session.orgId,
201
- sessionId: session.sessionId,
202
- projectRepositoryId: session.projectRepositoryId,
203
- sandboxId: session.sandboxId,
204
- error: boundedError(error)
205
- });
206
- }
207
- if (session.sandboxId) try {
208
- await sourceControl.sessions.setSandbox({
209
- id: session.id,
210
- sandboxId: null,
211
- sandboxWorkdir
116
+ await releaseSessionSandbox({
117
+ sessionId: session.id,
118
+ destroy: input.deleteSession
212
119
  });
213
- session.sandboxId = null;
214
120
  } catch (error) {
215
- this.#warn("Factory local sandbox binding could not be cleared", {
121
+ this.#warn("Factory session sandbox release failed", {
216
122
  orgId: session.orgId,
217
123
  sessionId: session.sessionId,
218
124
  projectRepositoryId: session.projectRepositoryId,
219
- sandboxId: session.sandboxId,
220
125
  error: boundedError(error)
221
126
  });
222
127
  }
@@ -1 +1 @@
1
- {"version":3,"file":"session-retirement.js","names":["#fleet","#invalidateSession","#warn","#locks","#retireSession","#destroyLocalSandbox","#releaseRemoteSandbox"],"sources":["../../src/sandbox/session-retirement.ts"],"sourcesContent":["import { cleanReleasedSandbox } from '../integrations/github/sandbox-release.js';\nimport { DEFAULT_COMMAND_TIMEOUT_MS, runWorktreeTeardown } from '../integrations/github/sandbox.js';\nimport type {\n ProjectRepository,\n SourceControlSession,\n SourceControlStorageHandle,\n} from '../storage/domains/source-control/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { MaterializationSandbox, SandboxBindingStore, SandboxFleet } from './fleet.js';\n\ntype RetirementFleet = Pick<SandboxFleet, 'provider' | 'reattachSandbox' | 'teardownSandbox'>;\ntype WarningLogger = (message: string, details: Record<string, unknown>) => void;\n\nexport interface SessionRetirementCoordinatorOptions {\n fleet: RetirementFleet;\n invalidateSession?: (sessionId: string) => Promise<void> | void;\n warn?: WarningLogger;\n}\n\nexport interface RetireSessionInput {\n sourceControl: SourceControlStorageHandle;\n /** When provided, deleting the session row also strips its work-item refs. */\n workItems?: Pick<WorkItemsStorage, 'clearSessionReferences'>;\n orgId: string;\n sessionId: string;\n deleteSession: boolean;\n}\n\nfunction boundedError(error: unknown): string {\n const detail = error instanceof Error ? error.message : String(error);\n if (detail.length <= 2000) return detail;\n return `${detail.slice(0, 200)}...${detail.slice(-1797)}`;\n}\n\n/**\n * Owns terminal and destructive session cleanup. Each session is serialized so\n * duplicate role bindings and competing deletion/transition requests cannot\n * run teardown concurrently. Once the sandbox binding is cleared, later calls\n * are idempotent no-ops apart from cache invalidation or requested row deletion.\n */\nexport class SessionRetirementCoordinator {\n readonly #fleet: RetirementFleet;\n readonly #invalidateSession: NonNullable<SessionRetirementCoordinatorOptions['invalidateSession']>;\n readonly #warn: WarningLogger;\n readonly #locks = new Map<string, Promise<void>>();\n\n constructor(options: SessionRetirementCoordinatorOptions) {\n this.#fleet = options.fleet;\n this.#invalidateSession = options.invalidateSession ?? (() => {});\n this.#warn = options.warn ?? ((message, details) => console.warn(`[Mastra Factory] ${message}`, details));\n }\n\n async retireSession(input: RetireSessionInput): Promise<void> {\n const previous = this.#locks.get(input.sessionId) ?? Promise.resolve();\n const current = previous.catch(() => {}).then(() => this.#retireSession(input));\n this.#locks.set(input.sessionId, current);\n try {\n await current;\n } finally {\n if (this.#locks.get(input.sessionId) === current) this.#locks.delete(input.sessionId);\n }\n }\n\n async retireWorkItemSessions(options: {\n workItems: Pick<WorkItemsStorage, 'get'>;\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n workItemId: string;\n }): Promise<void> {\n const item = await options.workItems.get({ orgId: options.orgId, id: options.workItemId });\n if (!item) return;\n const sessionIds = [...new Set(Object.values(item.sessions).map(session => session.sessionId))];\n await Promise.all(\n sessionIds.map(sessionId =>\n this.retireSession({\n sourceControl: options.sourceControl,\n orgId: options.orgId,\n sessionId,\n deleteSession: false,\n }),\n ),\n );\n }\n\n async retireProjectRepositorySessions(options: {\n sourceControl: SourceControlStorageHandle;\n workItems?: Pick<WorkItemsStorage, 'clearSessionReferences'>;\n orgId: string;\n projectRepositoryId: string;\n }): Promise<void> {\n const sessions = await options.sourceControl.sessions.listByProjectRepository({\n projectRepositoryId: options.projectRepositoryId,\n });\n await Promise.all(\n sessions.map(session =>\n this.retireSession({\n sourceControl: options.sourceControl,\n ...(options.workItems ? { workItems: options.workItems } : {}),\n orgId: options.orgId,\n sessionId: session.sessionId,\n deleteSession: true,\n }),\n ),\n );\n }\n\n async #retireSession(input: RetireSessionInput): Promise<void> {\n const session = await input.sourceControl.sessions.getBySessionId(input.sessionId);\n if (!session || session.orgId !== input.orgId) return;\n\n try {\n let projectRepository: ProjectRepository | null | undefined;\n try {\n projectRepository = await input.sourceControl.projectRepositories.get({\n orgId: input.orgId,\n id: session.projectRepositoryId,\n });\n } catch (error) {\n this.#warn('Factory repository settings could not be loaded for session retirement', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: boundedError(error),\n });\n }\n let sandbox: MaterializationSandbox | undefined;\n\n if (session.sandboxId && session.sandboxWorkdir) {\n try {\n sandbox = await this.#fleet.reattachSandbox(session.sandboxId, {\n actingUserId: session.userId,\n ...(this.#fleet.provider === 'local' ? { workingDirectory: session.sandboxWorkdir } : {}),\n });\n } catch (error) {\n this.#warn('Factory session sandbox could not be reattached for retirement', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n sandboxId: session.sandboxId,\n error: boundedError(error),\n });\n }\n\n if (sandbox && projectRepository?.teardownCommand) {\n try {\n await runWorktreeTeardown(sandbox, session.sandboxWorkdir, projectRepository.teardownCommand, {\n timeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,\n });\n } catch (error) {\n this.#warn('Factory worktree teardown failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n sandboxId: session.sandboxId,\n error: boundedError(error),\n });\n }\n }\n\n if (this.#fleet.provider === 'local') {\n await this.#destroyLocalSandbox(input.sourceControl, session, sandbox);\n } else {\n await this.#releaseRemoteSandbox(input.sourceControl, session, sandbox);\n }\n }\n } finally {\n try {\n await this.#invalidateSession(session.sessionId);\n } catch (error) {\n this.#warn('Factory session workspace cache invalidation failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: boundedError(error),\n });\n }\n\n if (input.deleteSession) {\n // Refs first: clearing again is a no-op, but refs on a deleted row would dangle forever.\n await input.workItems?.clearSessionReferences({ orgId: input.orgId, sessionId: input.sessionId });\n await input.sourceControl.sessions.delete(session.id);\n }\n }\n }\n\n async #releaseRemoteSandbox(\n sourceControl: SourceControlStorageHandle,\n session: SourceControlSession,\n sandbox: MaterializationSandbox | undefined,\n ): Promise<void> {\n const sandboxId = session.sandboxId;\n const sandboxWorkdir = session.sandboxWorkdir;\n if (!sandboxId || !sandboxWorkdir) return;\n await cleanReleasedSandbox({\n fleet: this.#fleet,\n sourceControl,\n orgId: session.orgId,\n projectRepositoryId: session.projectRepositoryId,\n sandboxId,\n sandboxWorkdir,\n actingUserId: session.userId,\n ...(sandbox ? { sandbox } : {}),\n });\n try {\n await sourceControl.sandboxPool.release({\n orgId: session.orgId,\n projectRepositoryId: session.projectRepositoryId,\n userId: session.userId,\n sandboxId,\n sandboxWorkdir,\n });\n } catch (error) {\n this.#warn('Factory remote sandbox release failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n sandboxId,\n error: boundedError(error),\n });\n }\n try {\n await sourceControl.sessions.setSandbox({ id: session.id, sandboxId: null, sandboxWorkdir });\n } catch (error) {\n this.#warn('Factory remote sandbox binding could not be cleared', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n sandboxId,\n error: boundedError(error),\n });\n }\n }\n\n async #destroyLocalSandbox(\n sourceControl: SourceControlStorageHandle,\n session: SourceControlSession,\n sandbox: MaterializationSandbox | undefined,\n ): Promise<void> {\n const sandboxWorkdir = session.sandboxWorkdir ?? '';\n const binding: SandboxBindingStore = {\n get sandboxId() {\n return session.sandboxId;\n },\n setSandboxId: async sandboxId => {\n await sourceControl.sessions.setSandbox({ id: session.id, sandboxId, sandboxWorkdir });\n session.sandboxId = sandboxId;\n },\n clear: async () => {\n await sourceControl.sessions.setSandbox({ id: session.id, sandboxId: null, sandboxWorkdir });\n session.sandboxId = null;\n },\n };\n try {\n await this.#fleet.teardownSandbox(binding, sandbox);\n } catch (error) {\n this.#warn('Factory local sandbox destruction failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n sandboxId: session.sandboxId,\n error: boundedError(error),\n });\n }\n if (session.sandboxId) {\n try {\n await sourceControl.sessions.setSandbox({ id: session.id, sandboxId: null, sandboxWorkdir });\n session.sandboxId = null;\n } catch (error) {\n this.#warn('Factory local sandbox binding could not be cleared', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n sandboxId: session.sandboxId,\n error: boundedError(error),\n });\n }\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"session-retirement.js","names":["#invalidateSession","#warn","#locks","#retireSession","#teardownSessionSandbox"],"sources":["../../src/sandbox/session-retirement.ts"],"sourcesContent":["import { releaseSessionSandbox } from '../integrations/github/sandbox-release.js';\nimport { DEFAULT_COMMAND_TIMEOUT_MS, runTeardownCommand } from '../integrations/github/sandbox.js';\nimport type {\n ProjectRepository,\n SourceControlSession,\n SourceControlStorageHandle,\n} from '../storage/domains/source-control/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { requireExec } from './materialization.js';\nimport { peekSessionSandbox } from './session-sandbox.js';\n\ntype WarningLogger = (message: string, details: Record<string, unknown>) => void;\n\nexport interface SessionRetirementCoordinatorOptions {\n invalidateSession?: (sessionId: string) => Promise<void> | void;\n warn?: WarningLogger;\n}\n\nexport interface RetireSessionInput {\n sourceControl: SourceControlStorageHandle;\n /** When provided, deleting the session row also strips its work-item refs. */\n workItems?: Pick<WorkItemsStorage, 'clearSessionReferences'>;\n orgId: string;\n sessionId: string;\n deleteSession: boolean;\n}\n\nfunction boundedError(error: unknown): string {\n const detail = error instanceof Error ? error.message : String(error);\n if (detail.length <= 2000) return detail;\n return `${detail.slice(0, 200)}...${detail.slice(-1797)}`;\n}\n\n/**\n * Owns terminal and destructive session cleanup. Each session is serialized so\n * duplicate role bindings and competing deletion/transition requests cannot\n * run teardown concurrently.\n *\n * Sandbox identity is the session id: retirement stops (or, for deleted\n * sessions, destroys) the sandbox this process holds in the session memo.\n * Sessions opened on another replica have no memo entry here — their VMs are\n * left to the provider's idle lifecycle (pause / idle GC), which is the\n * provider-owned half of the contract.\n */\nexport class SessionRetirementCoordinator {\n readonly #invalidateSession: NonNullable<SessionRetirementCoordinatorOptions['invalidateSession']>;\n readonly #warn: WarningLogger;\n readonly #locks = new Map<string, Promise<void>>();\n\n constructor(options: SessionRetirementCoordinatorOptions = {}) {\n this.#invalidateSession = options.invalidateSession ?? (() => {});\n this.#warn = options.warn ?? ((message, details) => console.warn(`[Mastra Factory] ${message}`, details));\n }\n\n async retireSession(input: RetireSessionInput): Promise<void> {\n const previous = this.#locks.get(input.sessionId) ?? Promise.resolve();\n const current = previous.catch(() => {}).then(() => this.#retireSession(input));\n this.#locks.set(input.sessionId, current);\n try {\n await current;\n } finally {\n if (this.#locks.get(input.sessionId) === current) this.#locks.delete(input.sessionId);\n }\n }\n\n async retireWorkItemSessions(options: {\n workItems: Pick<WorkItemsStorage, 'get'>;\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n workItemId: string;\n }): Promise<void> {\n const item = await options.workItems.get({ orgId: options.orgId, id: options.workItemId });\n if (!item) return;\n const sessionIds = [...new Set(Object.values(item.sessions).map(session => session.sessionId))];\n await Promise.all(\n sessionIds.map(sessionId =>\n this.retireSession({\n sourceControl: options.sourceControl,\n orgId: options.orgId,\n sessionId,\n deleteSession: false,\n }),\n ),\n );\n }\n\n async retireProjectRepositorySessions(options: {\n sourceControl: SourceControlStorageHandle;\n workItems?: Pick<WorkItemsStorage, 'clearSessionReferences'>;\n orgId: string;\n projectRepositoryId: string;\n }): Promise<void> {\n const sessions = await options.sourceControl.sessions.listByProjectRepository({\n projectRepositoryId: options.projectRepositoryId,\n });\n await Promise.all(\n sessions.map(session =>\n this.retireSession({\n sourceControl: options.sourceControl,\n ...(options.workItems ? { workItems: options.workItems } : {}),\n orgId: options.orgId,\n sessionId: session.sessionId,\n deleteSession: true,\n }),\n ),\n );\n }\n\n async #retireSession(input: RetireSessionInput): Promise<void> {\n const session = await input.sourceControl.sessions.getBySessionId(input.sessionId);\n if (!session || session.orgId !== input.orgId) return;\n\n try {\n await this.#teardownSessionSandbox(input, session);\n } finally {\n try {\n await this.#invalidateSession(session.sessionId);\n } catch (error) {\n this.#warn('Factory session workspace cache invalidation failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: boundedError(error),\n });\n }\n\n if (input.deleteSession) {\n // Refs first: clearing again is a no-op, but refs on a deleted row would dangle forever.\n await input.workItems?.clearSessionReferences({ orgId: input.orgId, sessionId: input.sessionId });\n await input.sourceControl.sessions.delete(session.id);\n }\n }\n }\n\n async #teardownSessionSandbox(input: RetireSessionInput, session: SourceControlSession): Promise<void> {\n const entry = peekSessionSandbox(session.id);\n if (!entry) return;\n\n let projectRepository: ProjectRepository | null | undefined;\n try {\n projectRepository = await input.sourceControl.projectRepositories.get({\n orgId: input.orgId,\n id: session.projectRepositoryId,\n });\n } catch (error) {\n this.#warn('Factory repository settings could not be loaded for session retirement', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: boundedError(error),\n });\n }\n\n // An unresolved workdir means the sandbox never started in this process:\n // nothing was set up, so there is nothing for a teardown command to undo.\n if (projectRepository?.teardownCommand && entry.workdir) {\n try {\n await runTeardownCommand(requireExec(entry.sandbox), entry.workdir, projectRepository.teardownCommand, {\n timeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,\n });\n } catch (error) {\n this.#warn('Factory teardown command failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: boundedError(error),\n });\n }\n }\n\n try {\n await releaseSessionSandbox({ sessionId: session.id, destroy: input.deleteSession });\n } catch (error) {\n this.#warn('Factory session sandbox release failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: boundedError(error),\n });\n }\n }\n}\n"],"mappings":";;;;;AA2BA,SAAS,aAAa,OAAwB;CAC5C,MAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;CACpE,IAAI,OAAO,UAAU,KAAM,OAAO;CAClC,OAAO,GAAG,OAAO,MAAM,GAAG,GAAG,EAAE,KAAK,OAAO,MAAM,KAAK;AACxD;;;;;;;;;;;;AAaA,IAAa,+BAAb,MAA0C;CACxC;CACA;CACA,yBAAkB,IAAI,IAA2B;CAEjD,YAAY,UAA+C,CAAC,GAAG;EAC7D,KAAKA,qBAAqB,QAAQ,4BAA4B,CAAC;EAC/D,KAAKC,QAAQ,QAAQ,UAAU,SAAS,YAAY,QAAQ,KAAK,oBAAoB,WAAW,OAAO;CACzG;CAEA,MAAM,cAAc,OAA0C;EAE5D,MAAM,WADW,KAAKC,OAAO,IAAI,MAAM,SAAS,KAAK,QAAQ,QAAQ,EAAA,CAC5C,YAAY,CAAC,CAAC,CAAC,CAAC,WAAW,KAAKC,eAAe,KAAK,CAAC;EAC9E,KAAKD,OAAO,IAAI,MAAM,WAAW,OAAO;EACxC,IAAI;GACF,MAAM;EACR,UAAU;GACR,IAAI,KAAKA,OAAO,IAAI,MAAM,SAAS,MAAM,SAAS,KAAKA,OAAO,OAAO,MAAM,SAAS;EACtF;CACF;CAEA,MAAM,uBAAuB,SAKX;EAChB,MAAM,OAAO,MAAM,QAAQ,UAAU,IAAI;GAAE,OAAO,QAAQ;GAAO,IAAI,QAAQ;EAAW,CAAC;EACzF,IAAI,CAAC,MAAM;EACX,MAAM,aAAa,CAAC,GAAG,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,KAAI,YAAW,QAAQ,SAAS,CAAC,CAAC;EAC9F,MAAM,QAAQ,IACZ,WAAW,KAAI,cACb,KAAK,cAAc;GACjB,eAAe,QAAQ;GACvB,OAAO,QAAQ;GACf;GACA,eAAe;EACjB,CAAC,CACH,CACF;CACF;CAEA,MAAM,gCAAgC,SAKpB;EAChB,MAAM,WAAW,MAAM,QAAQ,cAAc,SAAS,wBAAwB,EAC5E,qBAAqB,QAAQ,oBAC/B,CAAC;EACD,MAAM,QAAQ,IACZ,SAAS,KAAI,YACX,KAAK,cAAc;GACjB,eAAe,QAAQ;GACvB,GAAI,QAAQ,YAAY,EAAE,WAAW,QAAQ,UAAU,IAAI,CAAC;GAC5D,OAAO,QAAQ;GACf,WAAW,QAAQ;GACnB,eAAe;EACjB,CAAC,CACH,CACF;CACF;CAEA,MAAMC,eAAe,OAA0C;EAC7D,MAAM,UAAU,MAAM,MAAM,cAAc,SAAS,eAAe,MAAM,SAAS;EACjF,IAAI,CAAC,WAAW,QAAQ,UAAU,MAAM,OAAO;EAE/C,IAAI;GACF,MAAM,KAAKC,wBAAwB,OAAO,OAAO;EACnD,UAAU;GACR,IAAI;IACF,MAAM,KAAKJ,mBAAmB,QAAQ,SAAS;GACjD,SAAS,OAAO;IACd,KAAKC,MAAM,uDAAuD;KAChE,OAAO,QAAQ;KACf,WAAW,QAAQ;KACnB,qBAAqB,QAAQ;KAC7B,OAAO,aAAa,KAAK;IAC3B,CAAC;GACH;GAEA,IAAI,MAAM,eAAe;IAEvB,MAAM,MAAM,WAAW,uBAAuB;KAAE,OAAO,MAAM;KAAO,WAAW,MAAM;IAAU,CAAC;IAChG,MAAM,MAAM,cAAc,SAAS,OAAO,QAAQ,EAAE;GACtD;EACF;CACF;CAEA,MAAMG,wBAAwB,OAA2B,SAA8C;EACrG,MAAM,QAAQ,mBAAmB,QAAQ,EAAE;EAC3C,IAAI,CAAC,OAAO;EAEZ,IAAI;EACJ,IAAI;GACF,oBAAoB,MAAM,MAAM,cAAc,oBAAoB,IAAI;IACpE,OAAO,MAAM;IACb,IAAI,QAAQ;GACd,CAAC;EACH,SAAS,OAAO;GACd,KAAKH,MAAM,0EAA0E;IACnF,OAAO,QAAQ;IACf,WAAW,QAAQ;IACnB,qBAAqB,QAAQ;IAC7B,OAAO,aAAa,KAAK;GAC3B,CAAC;EACH;EAIA,IAAI,mBAAmB,mBAAmB,MAAM,SAC9C,IAAI;GACF,MAAM,mBAAmB,YAAY,MAAM,OAAO,GAAG,MAAM,SAAS,kBAAkB,iBAAiB,EACrG,WAAW,2BACb,CAAC;EACH,SAAS,OAAO;GACd,KAAKA,MAAM,mCAAmC;IAC5C,OAAO,QAAQ;IACf,WAAW,QAAQ;IACnB,qBAAqB,QAAQ;IAC7B,OAAO,aAAa,KAAK;GAC3B,CAAC;EACH;EAGF,IAAI;GACF,MAAM,sBAAsB;IAAE,WAAW,QAAQ;IAAI,SAAS,MAAM;GAAc,CAAC;EACrF,SAAS,OAAO;GACd,KAAKA,MAAM,0CAA0C;IACnD,OAAO,QAAQ;IACf,WAAW,QAAQ;IACnB,qBAAqB,QAAQ;IAC7B,OAAO,aAAa,KAAK;GAC3B,CAAC;EACH;CACF;AACF"}
@@ -0,0 +1,117 @@
1
+ import type { MastraSandbox, SandboxStartHook, WorkspaceSandbox } from '@mastra/core/workspace';
2
+ import type { RepositoryAccess } from '../capabilities/version-control.js';
3
+ /**
4
+ * Everything factory knows about a session's sandbox needs — the whole
5
+ * contract between factory and the deployer's sandbox callback. Factory owns
6
+ * intent; the provider owns resolving `sessionId` to a runnable VM.
7
+ */
8
+ export interface FactorySandboxContext {
9
+ /** Stable session id — the sandbox identity. */
10
+ sessionId: string;
11
+ /** owner/name of the repository, when the session is repo-backed. */
12
+ repoFullName?: string;
13
+ /**
14
+ * Configured repo setup command, when present. Part of a repo template's
15
+ * identity: a different setup command produces a different template.
16
+ */
17
+ setupCommand?: string;
18
+ /**
19
+ * Resolves the session repository's clone URL and a fresh short-lived
20
+ * credential for it. Providers use it for authenticated work that runs
21
+ * outside the VM — resolving a private repo's head, or cloning it during
22
+ * a template build. The credential is minted per call (installation
23
+ * tokens expire in ~1h); never an org PAT.
24
+ *
25
+ * `undefined` when the session has no repository, which is how a provider
26
+ * knows to build no repo template. The key is always present so that
27
+ * passing the whole context to a provider helper keeps working when this
28
+ * field changes, instead of silently resolving to "no repository".
29
+ */
30
+ getRepositoryAccess: (() => Promise<RepositoryAccess>) | undefined;
31
+ }
32
+ /**
33
+ * The deploy's sandbox configuration: construct a session's sandbox from
34
+ * intent. The sandbox identity is the session id; the provider must honor
35
+ * id-keyed getOrCreate on `start()` (reconnect/resume an existing VM for the
36
+ * id, create otherwise). Construction must be cheap and side-effect-free —
37
+ * VMs are provisioned on `start()` only. Local sandboxes should root their
38
+ * `workingDirectory` at a per-session directory (e.g.
39
+ * `join(root, ctx.sessionId)`); the repo checks out as a subdirectory of it.
40
+ *
41
+ * Returns a `MastraSandbox`, not the bare `WorkspaceSandbox` interface:
42
+ * factory relies on the base class for the start lifecycle and the runtime
43
+ * env, so providers extend it rather than reimplementing the contract.
44
+ *
45
+ * Factory attaches its own session setup to the returned sandbox, so the
46
+ * callback never has to wire it up. A callback may still pass its own
47
+ * `onStart`; it runs after factory's setup, against a prepared workspace.
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * sandbox: ({ sessionId }) => new E2BSandbox({ id: sessionId })
52
+ * ```
53
+ */
54
+ export type MastraFactorySandboxConfig = (ctx: FactorySandboxContext) => MastraSandbox;
55
+ /** The session's setup work, run against a started sandbox. Must be idempotent. */
56
+ export type SessionSetupRun = (sandbox: WorkspaceSandbox, workdir: string) => Promise<void>;
57
+ /**
58
+ * Per-process session-id → sandbox instance memo.
59
+ *
60
+ * The provider contract is id-keyed getOrCreate, but provider find-then-create
61
+ * has a real double-create race across independent instances. Memoizing the
62
+ * instance per session makes the base class's per-instance start coalescing
63
+ * apply process-wide per session — the same single-flight scope the fleet's
64
+ * per-binding coalescing provided. Cross-replica races are accepted (the
65
+ * fleet was also per-replica).
66
+ */
67
+ interface SessionSandboxEntry {
68
+ sandbox: WorkspaceSandbox;
69
+ /**
70
+ * The session's repo checkout root, recorded for passive readers (fs
71
+ * routes, capture, authz). Local sandboxes derive it at construction;
72
+ * remote sandboxes clone into the VM's own home, so it is undefined until
73
+ * `resolveSessionWorkdir` probes the first started VM — passive readers
74
+ * treat an unresolved workdir as "nothing materialized".
75
+ */
76
+ workdir?: string;
77
+ }
78
+ /**
79
+ * Get the session's memoized sandbox entry, constructing (and memoizing) it on
80
+ * first access. Construction is cheap and side-effect-free by contract; VMs
81
+ * are provisioned on `start()` only. Local sandboxes get their workdir here;
82
+ * remote workdirs are a runtime fact of the VM, resolved on first start.
83
+ */
84
+ export declare function getSessionSandbox(sessionId: string, repoFullName: string, construct: () => WorkspaceSandbox): SessionSandboxEntry;
85
+ /**
86
+ * Resolve (and memoize on the session entry) the session's repo checkout
87
+ * root. Local sandboxes answer synchronously from their configured
88
+ * `workingDirectory`; remote sandboxes clone into the VM's own default cwd,
89
+ * so the first resolution probes it with one `pwd` — the VM tells us where
90
+ * home is, we never invent a path. Calling this against a stopped sandbox
91
+ * lazily starts it (the probe is a command), so passive readers must peek
92
+ * `entry.workdir` instead.
93
+ */
94
+ export declare function resolveSessionWorkdir(sessionId: string, sandbox: WorkspaceSandbox, repoFullName: string): Promise<string>;
95
+ /**
96
+ * The session's memoized sandbox (and its workdir) when one was already
97
+ * constructed in this process, else undefined. Never constructs — passive
98
+ * read paths use this so browsing files cannot provision a VM.
99
+ */
100
+ export declare function peekSessionSandbox(sessionId: string): SessionSandboxEntry | undefined;
101
+ /** Drop the memoized instance (on stop/destroy/retirement or construction failure). */
102
+ export declare function evictSessionSandbox(sessionId: string): void;
103
+ /** Test-only: reset the process-wide memo between tests. */
104
+ export declare function __clearSessionSandboxesForTests(): void;
105
+ export declare function recordFailedSetupCommand(sessionId: string, command: string): void;
106
+ export declare function hasFailedSetupCommand(sessionId: string, command: string): boolean;
107
+ /**
108
+ * Build the session setup hook, which factory attaches to the constructed
109
+ * sandbox with `setOnStart`. Runs inside the sandbox start
110
+ * lifecycle: a fresh VM (`outcome: 'created'`) runs setup with no probe; a
111
+ * reconnect probes the marker first, which re-runs setup after a failed or
112
+ * crash-interrupted attempt. Throwing fails `start()` loudly — core treats
113
+ * onStart errors as fatal.
114
+ */
115
+ export declare function createSessionSetupHook(run: SessionSetupRun, sessionId: string, repoFullName: string): SandboxStartHook;
116
+ export {};
117
+ //# sourceMappingURL=session-sandbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-sandbox.d.ts","sourceRoot":"","sources":["../../src/sandbox/session-sandbox.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAChG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAG3E;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;OAWG;IACH,mBAAmB,EAAE,CAAC,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAC,GAAG,SAAS,CAAC;CACpE;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,GAAG,EAAE,qBAAqB,KAAK,aAAa,CAAC;AAEvF,mFAAmF;AACnF,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE5F;;;;;;;;;GASG;AACH,UAAU,mBAAmB;IAC3B,OAAO,EAAE,gBAAgB,CAAC;IAC1B;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,gBAAgB,GAChC,mBAAmB,CAQrB;AAED;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,gBAAgB,EACzB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,CAOjB;AAmBD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAErF;AAED,uFAAuF;AACvF,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAG3D;AAED,4DAA4D;AAC5D,wBAAgB,+BAA+B,IAAI,IAAI,CAGtD;AAcD,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAEjF;AAED,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAEjF;AAqDD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,eAAe,EACpB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,GACnB,gBAAgB,CAIlB"}