@mastra/factory 0.21.0-alpha.7 → 0.21.0-alpha.8

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 (131) hide show
  1. package/README.md +72 -1
  2. package/dist/boards/define-board.d.ts +36 -1
  3. package/dist/boards/define-board.d.ts.map +1 -1
  4. package/dist/boards/define-board.js +46 -0
  5. package/dist/boards/define-board.js.map +1 -1
  6. package/dist/boards/index.d.ts +2 -2
  7. package/dist/boards/index.d.ts.map +1 -1
  8. package/dist/boards/index.js +2 -2
  9. package/dist/boards/index.js.map +1 -1
  10. package/dist/boards/semantics.d.ts +34 -1
  11. package/dist/boards/semantics.d.ts.map +1 -1
  12. package/dist/boards/semantics.js +65 -1
  13. package/dist/boards/semantics.js.map +1 -1
  14. package/dist/boards/work.d.ts.map +1 -1
  15. package/dist/boards/work.js +5 -1
  16. package/dist/boards/work.js.map +1 -1
  17. package/dist/factory.d.ts.map +1 -1
  18. package/dist/factory.js +8 -0
  19. package/dist/factory.js.map +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/integrations/github/default-rules.d.ts +2 -2
  23. package/dist/integrations/github/default-rules.d.ts.map +1 -1
  24. package/dist/integrations/github/default-rules.js +11 -8
  25. package/dist/integrations/github/default-rules.js.map +1 -1
  26. package/dist/integrations/github/integration.d.ts +18 -0
  27. package/dist/integrations/github/integration.d.ts.map +1 -1
  28. package/dist/integrations/github/integration.js +53 -2
  29. package/dist/integrations/github/integration.js.map +1 -1
  30. package/dist/integrations/github/routes.d.ts +12 -0
  31. package/dist/integrations/github/routes.d.ts.map +1 -1
  32. package/dist/integrations/github/routes.js +63 -1
  33. package/dist/integrations/github/routes.js.map +1 -1
  34. package/dist/integrations/github/rules.d.ts +5 -2
  35. package/dist/integrations/github/rules.d.ts.map +1 -1
  36. package/dist/integrations/github/rules.js +72 -19
  37. package/dist/integrations/github/rules.js.map +1 -1
  38. package/dist/integrations/gitlab/default-rules.d.ts +3 -3
  39. package/dist/integrations/gitlab/default-rules.d.ts.map +1 -1
  40. package/dist/integrations/gitlab/default-rules.js +13 -6
  41. package/dist/integrations/gitlab/default-rules.js.map +1 -1
  42. package/dist/integrations/gitlab/rules.d.ts.map +1 -1
  43. package/dist/integrations/gitlab/rules.js +39 -8
  44. package/dist/integrations/gitlab/rules.js.map +1 -1
  45. package/dist/integrations/incidentio/default-rules.d.ts +3 -3
  46. package/dist/integrations/incidentio/default-rules.d.ts.map +1 -1
  47. package/dist/integrations/incidentio/default-rules.js +8 -6
  48. package/dist/integrations/incidentio/default-rules.js.map +1 -1
  49. package/dist/integrations/incidentio/rules.d.ts.map +1 -1
  50. package/dist/integrations/incidentio/rules.js +6 -3
  51. package/dist/integrations/incidentio/rules.js.map +1 -1
  52. package/dist/integrations/jira/default-rules.d.ts +3 -3
  53. package/dist/integrations/jira/default-rules.d.ts.map +1 -1
  54. package/dist/integrations/jira/default-rules.js +8 -6
  55. package/dist/integrations/jira/default-rules.js.map +1 -1
  56. package/dist/integrations/jira/rules.d.ts.map +1 -1
  57. package/dist/integrations/jira/rules.js +6 -3
  58. package/dist/integrations/jira/rules.js.map +1 -1
  59. package/dist/integrations/linear/default-rules.d.ts +3 -3
  60. package/dist/integrations/linear/default-rules.d.ts.map +1 -1
  61. package/dist/integrations/linear/default-rules.js +8 -6
  62. package/dist/integrations/linear/default-rules.js.map +1 -1
  63. package/dist/integrations/linear/routes.js +1 -1
  64. package/dist/integrations/linear/rules.d.ts.map +1 -1
  65. package/dist/integrations/linear/rules.js +6 -3
  66. package/dist/integrations/linear/rules.js.map +1 -1
  67. package/dist/integrations/pull-request-head.d.ts +18 -0
  68. package/dist/integrations/pull-request-head.d.ts.map +1 -0
  69. package/dist/integrations/pull-request-head.js +45 -0
  70. package/dist/integrations/pull-request-head.js.map +1 -0
  71. package/dist/routes/attention-providers.js +3 -3
  72. package/dist/routes/attention-providers.js.map +1 -1
  73. package/dist/routes/contracts.js +1 -1
  74. package/dist/routes/intake.d.ts +3 -0
  75. package/dist/routes/intake.d.ts.map +1 -1
  76. package/dist/routes/intake.js +47 -6
  77. package/dist/routes/intake.js.map +1 -1
  78. package/dist/routes/surface.d.ts.map +1 -1
  79. package/dist/routes/surface.js +3 -2
  80. package/dist/routes/surface.js.map +1 -1
  81. package/dist/routes/work-items.d.ts.map +1 -1
  82. package/dist/routes/work-items.js +3 -4
  83. package/dist/routes/work-items.js.map +1 -1
  84. package/dist/rules/decision-applicability.d.ts +6 -0
  85. package/dist/rules/decision-applicability.d.ts.map +1 -1
  86. package/dist/rules/decision-applicability.js +10 -1
  87. package/dist/rules/decision-applicability.js.map +1 -1
  88. package/dist/rules/dispatcher.d.ts.map +1 -1
  89. package/dist/rules/dispatcher.js +20 -15
  90. package/dist/rules/dispatcher.js.map +1 -1
  91. package/dist/rules/event-rules.d.ts +23 -0
  92. package/dist/rules/event-rules.d.ts.map +1 -0
  93. package/dist/rules/event-rules.js +46 -0
  94. package/dist/rules/event-rules.js.map +1 -0
  95. package/dist/rules/index.js +1 -1
  96. package/dist/rules/processor.js +1 -1
  97. package/dist/rules/review-source-tool.d.ts +5 -3
  98. package/dist/rules/review-source-tool.d.ts.map +1 -1
  99. package/dist/rules/review-source-tool.js.map +1 -1
  100. package/dist/rules/tools.js +1 -1
  101. package/dist/rules/transition-service.js +1 -1
  102. package/dist/rules/types.d.ts +47 -0
  103. package/dist/rules/types.d.ts.map +1 -1
  104. package/dist/rules/types.js.map +1 -1
  105. package/dist/storage/domains/comments/base.d.ts +3 -0
  106. package/dist/storage/domains/comments/base.d.ts.map +1 -1
  107. package/dist/storage/domains/comments/base.js +8 -1
  108. package/dist/storage/domains/comments/base.js.map +1 -1
  109. package/dist/storage/domains/work-items/base.d.ts +7 -3
  110. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  111. package/dist/storage/domains/work-items/base.js +66 -16
  112. package/dist/storage/domains/work-items/base.js.map +1 -1
  113. package/dist/storage/domains/work-items/board-version.d.ts +26 -0
  114. package/dist/storage/domains/work-items/board-version.d.ts.map +1 -0
  115. package/dist/storage/domains/work-items/board-version.js +110 -0
  116. package/dist/storage/domains/work-items/board-version.js.map +1 -0
  117. package/dist/supervisor/health.d.ts.map +1 -1
  118. package/dist/supervisor/health.js +17 -5
  119. package/dist/supervisor/health.js.map +1 -1
  120. package/dist/supervisor/read-tools.d.ts.map +1 -1
  121. package/dist/supervisor/read-tools.js +13 -8
  122. package/dist/supervisor/read-tools.js.map +1 -1
  123. package/dist/supervisor/write-tools.d.ts.map +1 -1
  124. package/dist/supervisor/write-tools.js +5 -1
  125. package/dist/supervisor/write-tools.js.map +1 -1
  126. package/dist/workspace.d.ts.map +1 -1
  127. package/dist/workspace.js +17 -1
  128. package/dist/workspace.js.map +1 -1
  129. package/factory-skills/factory-rereview/SKILL.md +4 -4
  130. package/factory-skills/factory-review/SKILL.md +2 -2
  131. package/package.json +6 -6
@@ -1 +1 @@
1
- {"version":3,"file":"write-tools.js","names":[],"sources":["../../src/supervisor/write-tools.ts"],"sourcesContent":["import { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport { boardForWorkItem } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport type { IntegrationTools } from '../integrations/base.js';\nimport { overtakenDecisionIds } from '../rules/decision-applicability.js';\nimport type { FactoryTransitionService } from '../rules/transition-service.js';\nimport { BOARD_IDENTIFIER_RE, MAX_BOARD_IDENTIFIER_LENGTH } from '../rules/validation.js';\nimport type { AuditAction } from '../storage/domains/audit/actions.js';\nimport type { AuditRecorder } from '../storage/domains/audit/domain.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { SupervisorScope } from './read-tools.js';\nimport type { WorkerMessageResult } from './session-messaging.js';\n\ninterface SupervisorWriteDependencies {\n scope: SupervisorScope;\n userId: string;\n workItems: WorkItemsStorage;\n boards: BoardRegistry;\n audit: AuditRecorder;\n transitionService: FactoryTransitionService;\n reconcileAcceptanceLabels?: (input: { orgId: string; factoryProjectId: string; item: WorkItemRow }) => Promise<void>;\n messageSession?: (input: {\n sessionId: string;\n message: string;\n userId: string;\n delivery: 'send' | 'queue';\n }) => Promise<WorkerMessageResult>;\n now?: () => Date;\n}\n\nexport function createFactorySupervisorWriteTools(deps: SupervisorWriteDependencies): IntegrationTools {\n const now = deps.now ?? (() => new Date());\n const audit = async (\n action: AuditAction,\n target: { type: string; id: string },\n metadata: Record<string, unknown> = {},\n ) =>\n deps.audit.record({\n orgId: deps.scope.orgId,\n actorId: deps.userId,\n actorType: 'human',\n action,\n targets: [target],\n factoryProjectId: deps.scope.factoryProjectId,\n metadata: { ...metadata, cause: 'supervisor' },\n occurredAt: now(),\n });\n\n return {\n factory_retry_decision: createTool({\n id: 'factory_retry_decision',\n description: 'Retry one failed Factory decision after the person confirms the repair.',\n inputSchema: z.object({ decisionId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ decisionId }) => {\n const current = await deps.workItems.getDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n );\n if (current && (await overtakenDecisionIds(deps.workItems, deps.boards, deps.scope, [current])).size > 0) {\n throw new Error('The work item has moved on from the phase this run was decided for; it cannot be retried.');\n }\n const decision = await deps.workItems.retryDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n );\n if (!decision) throw new Error('The decision is not failed, no longer exists, or belongs to another factory.');\n await audit(\n 'factory.run.retry',\n { type: 'rule_decision', id: decisionId },\n { workItemId: decision.workItemId },\n );\n return { decisionId, status: decision.status, workItemId: decision.workItemId };\n },\n }),\n factory_dismiss_decision: createTool({\n id: 'factory_dismiss_decision',\n description: 'Dismiss one parked Factory proposal after the person confirms it is no longer wanted.',\n inputSchema: z.object({ decisionId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ decisionId }) => {\n const decision = await deps.workItems.dismissDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n );\n if (!decision)\n throw new Error('The decision is not proposed, no longer exists, or belongs to another factory.');\n await audit(\n 'factory.run.dismissed',\n { type: 'rule_decision', id: decisionId },\n { workItemId: decision.workItemId },\n );\n return { decisionId, status: decision.status, workItemId: decision.workItemId };\n },\n }),\n factory_resolve_proposal: createTool({\n id: 'factory_resolve_proposal',\n description: 'Approve or dismiss one proposed Factory decision after the person confirms the choice.',\n inputSchema: z.object({ decisionId: z.string().min(1), resolution: z.enum(['approve', 'dismiss']) }),\n requireApproval: true,\n execute: async ({ decisionId, resolution }) => {\n const decision =\n resolution === 'approve'\n ? await deps.workItems.approveDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n deps.userId,\n )\n : await deps.workItems.dismissDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n );\n if (!decision) throw new Error('The proposal is no longer open or belongs to another factory.');\n await audit(\n resolution === 'approve' ? 'factory.run.approved' : 'factory.run.dismissed',\n {\n type: 'rule_decision',\n id: decisionId,\n },\n { workItemId: decision.workItemId },\n );\n return { decisionId, resolution, status: decision.status, workItemId: decision.workItemId };\n },\n }),\n factory_transition_work_item: createTool({\n id: 'factory_transition_work_item',\n description: 'Move or accept one Factory work item after the person confirms the destination stage.',\n inputSchema: z.object({\n workItemId: z.string().min(1),\n stage: z.string().max(MAX_BOARD_IDENTIFIER_LENGTH).regex(BOARD_IDENTIFIER_RE),\n }),\n requireApproval: true,\n execute: async ({ workItemId, stage }) => {\n const item = await deps.workItems.get({ orgId: deps.scope.orgId, id: workItemId });\n if (!item || item.factoryProjectId !== deps.scope.factoryProjectId) throw new Error('Work item not found.');\n const from = item.stages.length === 1 ? item.stages[0] : undefined;\n if (!from) throw new Error('The work item does not have one valid Factory stage.');\n const result = await deps.transitionService.transition({\n orgId: deps.scope.orgId,\n factoryProjectId: deps.scope.factoryProjectId,\n workItemId,\n board: boardForWorkItem(item),\n stage,\n expectedRevision: item.revision,\n actor: { type: 'human', id: deps.userId },\n ingress: { type: 'human', identity: `supervisor:${deps.userId}:${workItemId}:${item.revision}:${stage}` },\n cause: 'supervisor',\n });\n if (result.status !== 'accepted') {\n throw new Error(`The transition was rejected (${result.code}): ${result.reason}`);\n }\n return result;\n },\n }),\n factory_reconcile_labels: createTool({\n id: 'factory_reconcile_labels',\n description: 'Reconcile stale acceptance labels for one accepted work item after the person confirms the repair.',\n inputSchema: z.object({ workItemId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ workItemId }) => {\n if (!deps.reconcileAcceptanceLabels) throw new Error('Acceptance-label reconciliation is unavailable.');\n const item = await deps.workItems.get({ orgId: deps.scope.orgId, id: workItemId });\n if (!item || item.factoryProjectId !== deps.scope.factoryProjectId) throw new Error('Work item not found.');\n if (!item.acceptedAt) throw new Error('The work item has not been accepted.');\n await deps.reconcileAcceptanceLabels({ ...deps.scope, item });\n await audit('factory.work_item.labels_reconciled', { type: 'work_item', id: workItemId });\n return { workItemId, reconciled: true };\n },\n }),\n factory_revoke_binding: createTool({\n id: 'factory_revoke_binding',\n description: 'Revoke an orphaned or stale run binding after a health finding and person confirmation.',\n inputSchema: z.object({ bindingId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ bindingId }) => {\n const binding = await deps.workItems.revokeRunBinding({\n orgId: deps.scope.orgId,\n factoryProjectId: deps.scope.factoryProjectId,\n bindingId,\n revokedAt: now(),\n });\n if (!binding) throw new Error('The binding is not active, no longer exists, or belongs to another factory.');\n await audit(\n 'factory.intake.binding_updated',\n { type: 'run_binding', id: bindingId },\n {\n workItemId: binding.workItemId,\n role: binding.role,\n status: 'revoked',\n },\n );\n return { bindingId, status: binding.status, workItemId: binding.workItemId, role: binding.role };\n },\n }),\n factory_signal_session: createTool({\n id: 'factory_signal_session',\n description:\n 'Send bounded guidance to a worker session after the person confirms the exact message. Use queue only when the guidance should wait for the current run to finish.',\n inputSchema: z.object({\n sessionId: z.string().min(1),\n message: z.string().trim().min(1).max(2000),\n delivery: z.enum(['send', 'queue']).default('send'),\n }),\n requireApproval: true,\n execute: async ({ sessionId, message, delivery }) => {\n if (!deps.messageSession) throw new Error('Worker session messaging is unavailable.');\n const bindings = await deps.workItems.listRunBindings(deps.scope.orgId, deps.scope.factoryProjectId);\n const binding = bindings.find(row => row.sessionId === sessionId);\n if (!binding) throw new Error('The session does not belong to this factory.');\n const result = await deps.messageSession({ sessionId, message, userId: deps.userId, delivery });\n if (result?.status === 'interrupted') {\n return {\n sessionId,\n delivered: false,\n status: 'interrupted',\n delivery,\n workItemId: binding.workItemId,\n role: binding.role,\n };\n }\n await audit(\n 'factory.agent.signaled',\n { type: 'factory_session', id: sessionId },\n {\n workItemId: binding.workItemId,\n role: binding.role,\n delivery,\n },\n );\n return { sessionId, delivered: true, delivery, workItemId: binding.workItemId, role: binding.role };\n },\n }),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"write-tools.js","names":[],"sources":["../../src/supervisor/write-tools.ts"],"sourcesContent":["import { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport { boardForWorkItem } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport type { IntegrationTools } from '../integrations/base.js';\nimport { overtakenDecisionIds } from '../rules/decision-applicability.js';\nimport { factoryDispatchFailureMetadata } from '../rules/dispatch-errors.js';\nimport type { FactoryTransitionService } from '../rules/transition-service.js';\nimport { BOARD_IDENTIFIER_RE, MAX_BOARD_IDENTIFIER_LENGTH } from '../rules/validation.js';\nimport type { AuditAction } from '../storage/domains/audit/actions.js';\nimport type { AuditRecorder } from '../storage/domains/audit/domain.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { SupervisorScope } from './read-tools.js';\nimport type { WorkerMessageResult } from './session-messaging.js';\n\ninterface SupervisorWriteDependencies {\n scope: SupervisorScope;\n userId: string;\n workItems: WorkItemsStorage;\n boards: BoardRegistry;\n audit: AuditRecorder;\n transitionService: FactoryTransitionService;\n reconcileAcceptanceLabels?: (input: { orgId: string; factoryProjectId: string; item: WorkItemRow }) => Promise<void>;\n messageSession?: (input: {\n sessionId: string;\n message: string;\n userId: string;\n delivery: 'send' | 'queue';\n }) => Promise<WorkerMessageResult>;\n now?: () => Date;\n}\n\nexport function createFactorySupervisorWriteTools(deps: SupervisorWriteDependencies): IntegrationTools {\n const now = deps.now ?? (() => new Date());\n const audit = async (\n action: AuditAction,\n target: { type: string; id: string },\n metadata: Record<string, unknown> = {},\n ) =>\n deps.audit.record({\n orgId: deps.scope.orgId,\n actorId: deps.userId,\n actorType: 'human',\n action,\n targets: [target],\n factoryProjectId: deps.scope.factoryProjectId,\n metadata: { ...metadata, cause: 'supervisor' },\n occurredAt: now(),\n });\n\n return {\n factory_retry_decision: createTool({\n id: 'factory_retry_decision',\n description: 'Retry one failed Factory decision after the person confirms the repair.',\n inputSchema: z.object({ decisionId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ decisionId }) => {\n const current = await deps.workItems.getDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n );\n if (!current || current.status !== 'failed') {\n throw new Error('The decision is not failed, no longer exists, or belongs to another factory.');\n }\n const failure = factoryDispatchFailureMetadata(current.failureCode);\n if (!failure.canRetry) {\n throw new Error(\n `This failure (${failure.label}) cannot be fixed by a retry; repair the cause and start a new run instead.`,\n );\n }\n if ((await overtakenDecisionIds(deps.workItems, deps.boards, deps.scope, [current])).size > 0) {\n throw new Error('The work item has moved on from the phase this run was decided for; it cannot be retried.');\n }\n const decision = await deps.workItems.retryDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n );\n if (!decision) throw new Error('The decision is not failed, no longer exists, or belongs to another factory.');\n await audit(\n 'factory.run.retry',\n { type: 'rule_decision', id: decisionId },\n { workItemId: decision.workItemId },\n );\n return { decisionId, status: decision.status, workItemId: decision.workItemId };\n },\n }),\n factory_dismiss_decision: createTool({\n id: 'factory_dismiss_decision',\n description: 'Dismiss one parked Factory proposal after the person confirms it is no longer wanted.',\n inputSchema: z.object({ decisionId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ decisionId }) => {\n const decision = await deps.workItems.dismissDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n );\n if (!decision)\n throw new Error('The decision is not proposed, no longer exists, or belongs to another factory.');\n await audit(\n 'factory.run.dismissed',\n { type: 'rule_decision', id: decisionId },\n { workItemId: decision.workItemId },\n );\n return { decisionId, status: decision.status, workItemId: decision.workItemId };\n },\n }),\n factory_resolve_proposal: createTool({\n id: 'factory_resolve_proposal',\n description: 'Approve or dismiss one proposed Factory decision after the person confirms the choice.',\n inputSchema: z.object({ decisionId: z.string().min(1), resolution: z.enum(['approve', 'dismiss']) }),\n requireApproval: true,\n execute: async ({ decisionId, resolution }) => {\n const decision =\n resolution === 'approve'\n ? await deps.workItems.approveDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n deps.userId,\n )\n : await deps.workItems.dismissDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n );\n if (!decision) throw new Error('The proposal is no longer open or belongs to another factory.');\n await audit(\n resolution === 'approve' ? 'factory.run.approved' : 'factory.run.dismissed',\n {\n type: 'rule_decision',\n id: decisionId,\n },\n { workItemId: decision.workItemId },\n );\n return { decisionId, resolution, status: decision.status, workItemId: decision.workItemId };\n },\n }),\n factory_transition_work_item: createTool({\n id: 'factory_transition_work_item',\n description: 'Move or accept one Factory work item after the person confirms the destination stage.',\n inputSchema: z.object({\n workItemId: z.string().min(1),\n stage: z.string().max(MAX_BOARD_IDENTIFIER_LENGTH).regex(BOARD_IDENTIFIER_RE),\n }),\n requireApproval: true,\n execute: async ({ workItemId, stage }) => {\n const item = await deps.workItems.get({ orgId: deps.scope.orgId, id: workItemId });\n if (!item || item.factoryProjectId !== deps.scope.factoryProjectId) throw new Error('Work item not found.');\n const from = item.stages.length === 1 ? item.stages[0] : undefined;\n if (!from) throw new Error('The work item does not have one valid Factory stage.');\n const result = await deps.transitionService.transition({\n orgId: deps.scope.orgId,\n factoryProjectId: deps.scope.factoryProjectId,\n workItemId,\n board: boardForWorkItem(item),\n stage,\n expectedRevision: item.revision,\n actor: { type: 'human', id: deps.userId },\n ingress: { type: 'human', identity: `supervisor:${deps.userId}:${workItemId}:${item.revision}:${stage}` },\n cause: 'supervisor',\n });\n if (result.status !== 'accepted') {\n throw new Error(`The transition was rejected (${result.code}): ${result.reason}`);\n }\n return result;\n },\n }),\n factory_reconcile_labels: createTool({\n id: 'factory_reconcile_labels',\n description: 'Reconcile stale acceptance labels for one accepted work item after the person confirms the repair.',\n inputSchema: z.object({ workItemId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ workItemId }) => {\n if (!deps.reconcileAcceptanceLabels) throw new Error('Acceptance-label reconciliation is unavailable.');\n const item = await deps.workItems.get({ orgId: deps.scope.orgId, id: workItemId });\n if (!item || item.factoryProjectId !== deps.scope.factoryProjectId) throw new Error('Work item not found.');\n if (!item.acceptedAt) throw new Error('The work item has not been accepted.');\n await deps.reconcileAcceptanceLabels({ ...deps.scope, item });\n await audit('factory.work_item.labels_reconciled', { type: 'work_item', id: workItemId });\n return { workItemId, reconciled: true };\n },\n }),\n factory_revoke_binding: createTool({\n id: 'factory_revoke_binding',\n description: 'Revoke an orphaned or stale run binding after a health finding and person confirmation.',\n inputSchema: z.object({ bindingId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ bindingId }) => {\n const binding = await deps.workItems.revokeRunBinding({\n orgId: deps.scope.orgId,\n factoryProjectId: deps.scope.factoryProjectId,\n bindingId,\n revokedAt: now(),\n });\n if (!binding) throw new Error('The binding is not active, no longer exists, or belongs to another factory.');\n await audit(\n 'factory.intake.binding_updated',\n { type: 'run_binding', id: bindingId },\n {\n workItemId: binding.workItemId,\n role: binding.role,\n status: 'revoked',\n },\n );\n return { bindingId, status: binding.status, workItemId: binding.workItemId, role: binding.role };\n },\n }),\n factory_signal_session: createTool({\n id: 'factory_signal_session',\n description:\n 'Send bounded guidance to a worker session after the person confirms the exact message. Use queue only when the guidance should wait for the current run to finish.',\n inputSchema: z.object({\n sessionId: z.string().min(1),\n message: z.string().trim().min(1).max(2000),\n delivery: z.enum(['send', 'queue']).default('send'),\n }),\n requireApproval: true,\n execute: async ({ sessionId, message, delivery }) => {\n if (!deps.messageSession) throw new Error('Worker session messaging is unavailable.');\n const bindings = await deps.workItems.listRunBindings(deps.scope.orgId, deps.scope.factoryProjectId);\n const binding = bindings.find(row => row.sessionId === sessionId);\n if (!binding) throw new Error('The session does not belong to this factory.');\n const result = await deps.messageSession({ sessionId, message, userId: deps.userId, delivery });\n if (result?.status === 'interrupted') {\n return {\n sessionId,\n delivered: false,\n status: 'interrupted',\n delivery,\n workItemId: binding.workItemId,\n role: binding.role,\n };\n }\n await audit(\n 'factory.agent.signaled',\n { type: 'factory_session', id: sessionId },\n {\n workItemId: binding.workItemId,\n role: binding.role,\n delivery,\n },\n );\n return { sessionId, delivered: true, delivery, workItemId: binding.workItemId, role: binding.role };\n },\n }),\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"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"}
package/dist/workspace.js CHANGED
@@ -131,9 +131,25 @@ var FactorySkillSource = class {
131
131
  return entries.filter((entry) => !REVIEW_ONLY_FACTORY_SKILLS.has(entry.name));
132
132
  }
133
133
  const entries = await this.fallback.readdir(skillPath);
134
- if (this.#fallbackSkillRoots.has(path.normalize(skillPath))) return entries.filter((entry) => !FACTORY_SKILL_NAMES.has(entry.name));
134
+ if (this.#fallbackSkillRoots.has(path.normalize(skillPath))) {
135
+ const reserved = await this.#reservedNames();
136
+ return entries.filter((entry) => !reserved.has(entry.name));
137
+ }
135
138
  return entries;
136
139
  }
140
+ /**
141
+ * Names the Factory mount serves: bundled skills plus the app's local skills.
142
+ * Repository skills with these names are hidden so they cannot collide with
143
+ * Factory's copy. Recomputed per scan so skill refreshes pick up local changes.
144
+ */
145
+ async #reservedNames() {
146
+ const reserved = new Set(FACTORY_SKILL_NAMES);
147
+ if (!this.#localSource) return reserved;
148
+ try {
149
+ for (const entry of await this.#localSource.readdir("")) if (entry.type === "directory") reserved.add(entry.name);
150
+ } catch {}
151
+ return reserved;
152
+ }
137
153
  realpath(skillPath) {
138
154
  if (this.#isFactoryPath(skillPath)) return Promise.resolve(path.normalize(skillPath));
139
155
  return this.fallback.realpath ? this.fallback.realpath(skillPath) : Promise.resolve(skillPath);
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.js","names":["#bundledSource","#localSource","#fallbackSkillRoots","#isHiddenReviewPath","#layerFor","#isFactoryPath","#factoryPath","#entries","#generations"],"sources":["../src/workspace.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport path, { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { SandboxFilesystem } from '@mastra/code-sdk/agents/sandbox-filesystem';\nimport { MASTRACODE_WORKSPACE_TOOLS } from '@mastra/code-sdk/agents/tool-availability';\nimport type { getDynamicWorkspace, WorkspaceSkillExtension } from '@mastra/code-sdk/agents/workspace';\nimport { DEFAULT_CONFIG_DIR } from '@mastra/code-sdk/constants';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport { LocalSkillSource, Workspace } from '@mastra/core/workspace';\nimport type {\n SandboxStartHook,\n SkillSource,\n SkillSourceEntry,\n SkillSourceStat,\n WorkspaceSandbox,\n} from '@mastra/core/workspace';\nimport { getFactoryAuthOrgId, getFactoryAuthUserFromContext, getFactoryAuthUserId } from './auth.js';\nimport type { VersionControl } from './capabilities/version-control.js';\nimport type { MastraFactorySandboxConfig } from './factory.js';\nimport type { GithubIntegration } from './integrations/github/integration.js';\nimport { getGithubPat } from './integrations/github/pat.js';\nimport type { GithubPatKind } from './integrations/github/pat.js';\nimport {\n checkoutSessionBranch,\n DEFAULT_COMMAND_TIMEOUT_MS,\n materializeRepo,\n runSetupCommand,\n runTeardownCommand,\n SetupCommandError,\n} from './integrations/github/sandbox.js';\nimport {\n registerGithubPatKind,\n registerGithubRefreshTarget,\n registerGithubTokenInjector,\n registerGithubTokenInjectorResolver,\n} from './integrations/github/token-refresh.js';\nimport { requireExec } from './sandbox/materialization.js';\nimport type { ExecutableSandbox } from './sandbox/materialization.js';\nimport {\n createSessionSetupHook,\n evictSessionSandbox,\n getSessionSandbox,\n hasFailedSetupCommand,\n recordFailedSetupCommand,\n resolveSessionWorkdir,\n} from './sandbox/session-sandbox.js';\nimport type { SessionSetupGate } from './sandbox/session-sandbox.js';\nimport type { FactoryProjectsStorage } from './storage/domains/projects/base.js';\nimport type { SourceControlSession, SourceControlStorageHandle } from './storage/domains/source-control/base.js';\nimport type { WorkItemsStorage } from './storage/domains/work-items/base.js';\nimport { parseSupervisorResourceId } from './supervisor/session.js';\nimport { timedPhase } from './timing.js';\nimport { mergeRequestNumberFromBranch, pullRequestNumberFromBranch } from './work-item-branch.js';\n\nconst WORKSPACE_ID_PREFIX = 'mfw';\nconst bundleDirectory = dirname(fileURLToPath(import.meta.url));\nconst bundledFactorySkillsPath = join(bundleDirectory, 'factory-skills');\nexport const BUNDLED_FACTORY_SKILLS_PATH =\n [\n // Deploy bundle: the consumer copies `factory-skills/` next to the built\n // server module (e.g. via its public/ dir).\n bundledFactorySkillsPath,\n // Package layout: `dist/../factory-skills` (also `src/../factory-skills`\n // when running tests against sources).\n join(bundleDirectory, '..', 'factory-skills'),\n ].find(existsSync) ?? bundledFactorySkillsPath;\n\n/**\n * Resolve the consumer repo's local Factory skills root, if any. Checked in\n * addition to the bundled skills so projects can add (or override) Factory\n * skills without patching the installed package. Candidates cover the cwd\n * variants the dev server runs with (`repo root`, `--dir src/mastra` which\n * runs with cwd `src/mastra/public`).\n */\nexport function resolveLocalFactorySkillsPath(cwd: string = process.cwd()): string | undefined {\n const candidates = [\n join(cwd, 'src', 'mastra', 'public', 'factory-skills'),\n join(cwd, 'public', 'factory-skills'),\n join(cwd, 'factory-skills'),\n ];\n return candidates.find(\n candidate => path.normalize(candidate) !== path.normalize(BUNDLED_FACTORY_SKILLS_PATH) && existsSync(candidate),\n );\n}\nconst FACTORY_SKILLS_MOUNT = path.resolve(path.parse(process.cwd()).root, '__mastracode_factory_skills__');\nexport const FACTORY_SKILL_NAMES = new Set([\n 'configure-factory-rules',\n 'factory-complete-issue',\n 'factory-gitlab-rereview',\n 'factory-gitlab-review',\n 'factory-plan',\n 'factory-rereview',\n 'factory-review',\n 'factory-triage',\n]);\n\n/** Review skills only sessions with an active review run binding may discover or load. */\nexport const REVIEW_ONLY_FACTORY_SKILLS = new Set([\n 'factory-gitlab-rereview',\n 'factory-gitlab-review',\n 'factory-rereview',\n 'factory-review',\n]);\n\n/**\n * Skill caches rescanned outside the workspace resolver. The resolver records\n * which role each cache was last scanned for; a rescan it did not perform makes\n * that record untrustworthy, so the next reuse must rescan again.\n */\nconst rescannedOutsideResolver = new WeakMap<object, Promise<void>>();\n\n/**\n * Rescan a session's skill cache so a role gained after the cache was built\n * (a review binding minted after the session's workspace resolved) takes effect.\n * The source still checks the live binding, so this never widens access.\n */\nexport async function rescanFactorySkills(skills: NonNullable<Workspace['skills']>): Promise<void> {\n // Registered so the resolver waits it out: a refresh started meanwhile would\n // join this scan (possibly for the old role) instead of starting a fresh one.\n const pending = skills.refresh();\n rescannedOutsideResolver.set(\n skills,\n pending.catch(() => {}),\n );\n await pending;\n}\n\nexport class FactorySkillSource implements SkillSource {\n readonly #bundledSource = new LocalSkillSource({ basePath: BUNDLED_FACTORY_SKILLS_PATH });\n readonly #localSource: LocalSkillSource | undefined;\n readonly #fallbackSkillRoots: Set<string>;\n\n constructor(\n readonly fallback: SkillSource,\n fallbackSkillRoots: string[],\n localSkillsPath: string | undefined = resolveLocalFactorySkillsPath(),\n /** Evaluated per call because a session's bound role changes across stages. */\n readonly isReviewSession: () => Promise<boolean> = () => Promise.resolve(true),\n ) {\n this.#localSource = localSkillsPath ? new LocalSkillSource({ basePath: localSkillsPath }) : undefined;\n this.#fallbackSkillRoots = new Set(fallbackSkillRoots.map(skillPath => path.normalize(skillPath)));\n }\n\n #isFactoryPath(skillPath: string): boolean {\n const normalized = path.normalize(skillPath);\n return normalized === FACTORY_SKILLS_MOUNT || normalized.startsWith(`${FACTORY_SKILLS_MOUNT}${path.sep}`);\n }\n\n #factoryPath(skillPath: string): string {\n return path.relative(FACTORY_SKILLS_MOUNT, path.normalize(skillPath));\n }\n\n /** True when the path is inside a review-only skill that this session may not use. */\n async #isHiddenReviewPath(relativePath: string): Promise<boolean> {\n const skillName = relativePath.split(path.sep)[0];\n if (!skillName || !REVIEW_ONLY_FACTORY_SKILLS.has(skillName)) return false;\n return !(await this.isReviewSession());\n }\n\n /** Pick the layer serving this mount-relative path: local wins when it has the entry. */\n async #layerFor(relativePath: string): Promise<LocalSkillSource> {\n if (this.#localSource && (await this.#localSource.exists(relativePath))) return this.#localSource;\n return this.#bundledSource;\n }\n\n async exists(skillPath: string): Promise<boolean> {\n if (!this.#isFactoryPath(skillPath)) return this.fallback.exists(skillPath);\n const relative = this.#factoryPath(skillPath);\n if (await this.#isHiddenReviewPath(relative)) return false;\n if (this.#localSource && (await this.#localSource.exists(relative))) return true;\n return this.#bundledSource.exists(relative);\n }\n\n async stat(skillPath: string): Promise<SkillSourceStat> {\n if (!this.#isFactoryPath(skillPath)) return this.fallback.stat(skillPath);\n const relative = this.#factoryPath(skillPath);\n if (await this.#isHiddenReviewPath(relative)) throw skillSourceEnoent(skillPath);\n return (await this.#layerFor(relative)).stat(relative);\n }\n\n async readFile(skillPath: string): Promise<string | Buffer> {\n if (!this.#isFactoryPath(skillPath)) return this.fallback.readFile(skillPath);\n const relative = this.#factoryPath(skillPath);\n if (await this.#isHiddenReviewPath(relative)) throw skillSourceEnoent(skillPath);\n return (await this.#layerFor(relative)).readFile(relative);\n }\n\n async readdir(skillPath: string): Promise<SkillSourceEntry[]> {\n if (this.#isFactoryPath(skillPath)) {\n const relative = this.#factoryPath(skillPath);\n if (await this.#isHiddenReviewPath(relative)) throw skillSourceEnoent(skillPath);\n const [bundledExists, localExists] = await Promise.all([\n this.#bundledSource.exists(relative),\n this.#localSource?.exists(relative) ?? Promise.resolve(false),\n ]);\n if (!bundledExists && !localExists) throw skillSourceEnoent(skillPath);\n const [bundledEntries, localEntries] = await Promise.all([\n bundledExists ? this.#bundledSource.readdir(relative) : [],\n localExists ? this.#localSource!.readdir(relative) : [],\n ]);\n const merged = new Map<string, SkillSourceEntry>();\n for (const entry of bundledEntries) merged.set(entry.name, entry);\n // Local entries override bundled names.\n for (const entry of localEntries) merged.set(entry.name, entry);\n const entries = [...merged.values()];\n if (relative !== '' || (await this.isReviewSession())) return entries;\n return entries.filter(entry => !REVIEW_ONLY_FACTORY_SKILLS.has(entry.name));\n }\n const entries = await this.fallback.readdir(skillPath);\n if (this.#fallbackSkillRoots.has(path.normalize(skillPath))) {\n return entries.filter(entry => !FACTORY_SKILL_NAMES.has(entry.name));\n }\n return entries;\n }\n\n realpath(skillPath: string): Promise<string> {\n if (this.#isFactoryPath(skillPath)) return Promise.resolve(path.normalize(skillPath));\n return this.fallback.realpath ? this.fallback.realpath(skillPath) : Promise.resolve(skillPath);\n }\n}\n\n/** Build a Node-style ENOENT error so callers can treat missing skills like fs misses. */\nfunction skillSourceEnoent(skillPath: string): Error {\n const error = new Error(`ENOENT: no such file or directory, '${skillPath}'`) as Error & { code: string };\n error.code = 'ENOENT';\n return error;\n}\n\n/**\n * Sandbox-backed skill fallback that stays inert until the session sandbox is\n * actually materialized. Skill discovery runs on latency-sensitive paths (the\n * Factory start coordinator resolves the kickoff skill before the start route\n * responds); without this guard the first project-root read would hit the lazy\n * sandbox handle and force full provisioning + repo materialization. While the\n * sandbox is unmaterialized, project skill roots simply appear empty — bundled\n * Factory skills resolve from local disk via `FactorySkillSource`. Once the\n * sandbox exists, every call delegates straight through.\n */\nclass UnmaterializedAwareSkillSource implements SkillSource {\n constructor(\n readonly fallback: SkillSource,\n readonly isMaterialized: () => boolean,\n ) {}\n\n async exists(skillPath: string): Promise<boolean> {\n return this.isMaterialized() ? this.fallback.exists(skillPath) : false;\n }\n\n async stat(skillPath: string): Promise<SkillSourceStat> {\n if (!this.isMaterialized()) throw skillSourceEnoent(skillPath);\n return this.fallback.stat(skillPath);\n }\n\n async readFile(skillPath: string): Promise<string | Buffer> {\n if (!this.isMaterialized()) throw skillSourceEnoent(skillPath);\n return this.fallback.readFile(skillPath);\n }\n\n async readdir(skillPath: string): Promise<SkillSourceEntry[]> {\n return this.isMaterialized() ? this.fallback.readdir(skillPath) : [];\n }\n\n realpath(skillPath: string): Promise<string> {\n if (!this.isMaterialized()) return Promise.resolve(skillPath);\n return this.fallback.realpath ? this.fallback.realpath(skillPath) : Promise.resolve(skillPath);\n }\n}\n\nfunction createFactorySkillExtension(isReviewSession: () => Promise<boolean>): WorkspaceSkillExtension {\n return {\n id: 'web-factory',\n paths: [FACTORY_SKILLS_MOUNT],\n createSource: (fallback, fallbackSkillRoots) =>\n new FactorySkillSource(fallback, fallbackSkillRoots, resolveLocalFactorySkillsPath(), isReviewSession),\n };\n}\n\nfunction isActiveReviewBinding(\n runBinding: { role: string; status: string; orgId: string } | null | undefined,\n orgId: string | undefined,\n): boolean {\n return runBinding?.role === 'review' && runBinding.status === 'active' && !!orgId && runBinding.orgId === orgId;\n}\n\ntype DynamicWorkspaceContext = Parameters<typeof getDynamicWorkspace>[0];\n\n/**\n * When a session's sandbox boots: on the agent's first command (`'lazy'`, the\n * default) or as soon as the session's workspace is first resolved (`'eager'`).\n * An eager start is fire-and-forget; if it fails, the lazy path still runs.\n */\nexport type FactorySandboxStart = 'lazy' | 'eager';\n\nexport interface WorkspaceSourceControlProvider {\n /** Stable integration id used to select the provider-owned storage partition. */\n id: string;\n /** Provider capability used to resolve fresh repository credentials. */\n versionControl: Pick<VersionControl, 'getRepositoryAccess'>;\n /** Provider-owned source-control rows, including Factory sessions. */\n storage: SourceControlStorageHandle;\n /** Present only for GitHub, whose CLI PAT rotation has additional semantics. */\n github?: GithubIntegration;\n}\n\nexport interface CreateWorkspaceFactoryOptions {\n /** Factory sandbox runtime config (session sandbox callback). */\n sandbox?: MastraFactorySandboxConfig;\n /** Defaults to `'lazy'`. */\n sandboxStart?: FactorySandboxStart;\n /** Source-control providers whose repositories may back Factory sessions. */\n sourceControls?: WorkspaceSourceControlProvider[];\n /** @deprecated GitHub-only compatibility path; prefer sourceControls. */\n github?: GithubIntegration;\n /** Work-items storage used to resolve the session's run-binding role, so\n * review-board sessions get the reviewer PAT as `GH_TOKEN`. Optional —\n * without it every session uses the default (worker) PAT. */\n workItems?: Pick<WorkItemsStorage, 'findActiveRunBindingForSession'>;\n /** Projects storage used to authorize workspace-free supervisor sessions. */\n projects?: Pick<FactoryProjectsStorage, 'get'>;\n /** Runtime workspace/token registrations invalidated when a session retires. */\n workspaceRegistry?: FactoryWorkspaceRegistry;\n}\n\ntype WorkspaceUnregister = () => Promise<void> | void;\n\n/** Tracks dynamic Factory workspaces by persisted session id for retirement. */\nexport class FactoryWorkspaceRegistry {\n readonly #entries = new Map<string, Map<string, WorkspaceUnregister>>();\n readonly #generations = new Map<string, number>();\n\n generation(sessionId: string): number {\n return this.#generations.get(sessionId) ?? 0;\n }\n\n async register(\n sessionId: string,\n workspaceId: string,\n generation: number,\n unregister: WorkspaceUnregister,\n ): Promise<boolean> {\n if (generation !== this.generation(sessionId)) {\n await unregister();\n return false;\n }\n const entries = this.#entries.get(sessionId) ?? new Map<string, WorkspaceUnregister>();\n entries.set(workspaceId, unregister);\n this.#entries.set(sessionId, entries);\n return true;\n }\n\n async invalidateSession(sessionId: string): Promise<void> {\n this.#generations.set(sessionId, this.generation(sessionId) + 1);\n const entries = this.#entries.get(sessionId);\n if (!entries) return;\n this.#entries.delete(sessionId);\n const results = await Promise.allSettled([...entries.values()].map(unregister => unregister()));\n const failure = results.find(result => result.status === 'rejected');\n if (failure?.status === 'rejected') throw failure.reason;\n }\n}\n\nasync function resolveSourceControlSession(\n providers: WorkspaceSourceControlProvider[],\n sessionId: string,\n): Promise<{ provider: WorkspaceSourceControlProvider; session: SourceControlSession } | null> {\n const matches = (\n await Promise.all(\n providers.map(async provider => ({\n provider,\n session: await provider.storage.sessions.getBySessionId(sessionId),\n })),\n )\n ).filter(\n (match): match is { provider: WorkspaceSourceControlProvider; session: SourceControlSession } =>\n match.session !== null,\n );\n if (matches.length > 1) {\n throw new Error(`Factory session ${sessionId} is ambiguous across source-control providers`);\n }\n return matches[0] ?? null;\n}\n\nexport function createWorkspaceFactory(options: CreateWorkspaceFactoryOptions = {}) {\n const { sandbox: sandboxConfig, github, projects, workItems } = options;\n const sourceControls: WorkspaceSourceControlProvider[] =\n options.sourceControls ??\n (github\n ? [{ id: github.id, versionControl: github.versionControl, storage: github.sourceControlStorage, github }]\n : []);\n const eagerSandboxStart = options.sandboxStart === 'eager';\n const workspaceRegistry = options.workspaceRegistry ?? new FactoryWorkspaceRegistry();\n type GithubTokenRegistration = {\n inject: (token: string) => void;\n patKind: GithubPatKind;\n ghToken: string;\n generation: number;\n tokenReplacementPending: boolean;\n };\n // The session setup path runs commands and installs credentials, so it\n // needs `executeCommand` (required by `ExecutableSandbox`) plus core's\n // optional `setEnv`, which stays optional here because the token-refresh\n // path checks for it and reports its absence.\n type SessionSandbox = ExecutableSandbox & { setEnv?: WorkspaceSandbox['setEnv'] };\n const githubTokenInjectors = new Map<string, GithubTokenRegistration>();\n const githubTokenReconciliations = new Map<string, Promise<void>>();\n // Workspace identity cache: concurrent resolutions of the same session must\n // observe the same Workspace object even when no Mastra registry is wired\n // (the registry stays the source of truth when present).\n const constructedWorkspaces = new Map<string, Workspace>();\n // Review-skill visibility last used to populate each workspace's skill cache.\n const skillCacheReviewState = new Map<string, boolean>();\n const skillCacheRefreshes = new Map<string, Promise<void>>();\n // The review check of the latest request to resolve each workspace. The skill\n // source outlives the request that built it, so it must gate on this rather\n // than on the context it was constructed with.\n const skillReviewChecks = new Map<string, () => Promise<boolean>>();\n\n return async ({ requestContext, mastra, skillExtension }: DynamicWorkspaceContext) => {\n const ctx = requestContext.get('controller') as AgentControllerRequestContext<MastraCodeState> | undefined;\n const supervisorProjectId = parseSupervisorResourceId(ctx?.resourceId);\n if (supervisorProjectId) {\n const orgId = getFactoryAuthOrgId(getFactoryAuthUserFromContext(requestContext));\n const project = orgId && projects ? await projects.get({ orgId, id: supervisorProjectId }) : null;\n if (!project) throw new Error(`Factory supervisor ${supervisorProjectId} is not available to the current user`);\n return undefined;\n }\n const resolvedSession = ctx?.resourceId ? await resolveSourceControlSession(sourceControls, ctx.resourceId) : null;\n const session = resolvedSession?.session ?? null;\n const sourceControl = resolvedSession?.provider;\n\n if (!session || !sourceControl) {\n // No factory session, no workspace. Chat still works; workspace tools\n // are simply not registered. Host-cwd behavior is opt-in via a\n // LocalSandbox callback rooted wherever the deployer wants — the\n // resolver never hands out the server host's own filesystem.\n return undefined;\n }\n\n const user = getFactoryAuthUserFromContext(requestContext);\n const userId = getFactoryAuthUserId(user);\n // No identity at all is a server-side caller that forgot to seed one\n // (webhook, cron), not someone reaching for another user's session.\n if (!user?.organizationId || !userId) {\n throw new Error(`Factory session ${session.sessionId} was resolved without a caller identity`);\n }\n // Org-visible sessions open to any member of the owning organization;\n // only private sessions stay owner-only. Cross-org access never passes.\n if (user.organizationId !== session.orgId || (session.visibility === 'private' && userId !== session.userId)) {\n throw new Error(`Factory session ${session.sessionId} is not available to the current user`);\n }\n if (!sandboxConfig) {\n throw new Error('A sandbox callback is required to create a Factory session workspace');\n }\n const createSessionSandboxInstance = sandboxConfig;\n const githubProvider = sourceControl.github;\n\n const storage = sourceControl.storage;\n const projectRepository = await storage.projectRepositories.get({\n orgId: session.orgId,\n id: session.projectRepositoryId,\n });\n if (!projectRepository) throw new Error(`Repository link ${session.projectRepositoryId} was not found`);\n // The remaining reads only depend on the repository link — issue them in\n // parallel instead of paying four sequential storage round-trips.\n const [connection, repository] = await Promise.all([\n storage.connections.get({ orgId: session.orgId, id: projectRepository.connectionId }),\n storage.repositories.get({ orgId: session.orgId, id: projectRepository.repositoryId }),\n ]);\n if (!connection || !repository) throw new Error(`Repository link ${session.projectRepositoryId} is incomplete`);\n const installation = await storage.installations.get({ orgId: session.orgId, id: connection.installationId });\n if (!installation) {\n throw new Error(`${sourceControl.id} installation ${connection.installationId} was not found`);\n }\n const repoFullName = repository.slug;\n if (githubProvider)\n registerGithubRefreshTarget(requestContext, { orgId: session.orgId, repositoryId: repository.id });\n\n // Construct (or fetch) the session's memoized sandbox instance.\n // Construction is cheap and side-effect-free by the callback contract —\n // the VM is provisioned on `start()`, which only the materialization\n // pipeline calls. The workdir is never persisted or trusted from storage\n // or client input (the stale-workdir incident class came from reusing\n // `session.sandboxWorkdir` written under a different provider): local\n // sandboxes derive it at construction, remote sandboxes clone into the\n // VM's own home so it resolves lazily at first start.\n // `runSetupOn` references `runSessionSetup`, defined below — it is only\n // invoked during start, long after this closure fully initializes.\n const runSetupOn = (target: unknown, workdir: string, gate: SessionSetupGate) =>\n runSessionSetup(requireExec(target as WorkspaceSandbox), workdir, gate);\n const guardedSetup = createSessionSetupHook(\n runSetupOn,\n session.id,\n repoFullName,\n projectRepository.setupCommand ?? undefined,\n );\n // Composed start hook: marker-guarded repo setup, then per-start\n // credential install. It runs inside the provider's start lifecycle on\n // EVERY start (create or reconnect) — providers own lazy start\n // (`ensureRunning()` on first command) and dead-VM self-healing (E2B\n // `retryOnDead`, Platform status reset on destroy), so a replacement VM\n // re-enters this hook and heals itself with credentials at least as\n // fresh as the start that installed them.\n const setupHook: SandboxStartHook = async args => {\n // A session retired before its first start must not set anything up.\n if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) {\n throw retiredError();\n }\n let setupError: SetupCommandError | undefined;\n try {\n await guardedSetup(args);\n } catch (error) {\n if (!(error instanceof SetupCommandError)) throw error;\n setupError = error;\n }\n // Re-check after the (long) setup: a session retired mid-setup must not\n // register credentials for a workspace whose retirement teardown has\n // already run — the entry would leak forever. The VM itself is left to\n // the provider's idle timeout (accepted).\n if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) {\n throw retiredError();\n }\n const target: SessionSandbox = requireExec(args.sandbox);\n // Sandbox persistence plus the post-checkout skill rescan run on every\n // start (create or reconnect). The persisted sandbox id is read back on\n // resume to reattach; the workdir was resolved (and memoized on the\n // entry) by the guarded setup. The skill roots were reported empty by the\n // unmaterialized-source guard before the checkout existed, so rescan now.\n // The physical-id write is awaited and its failure propagates: a start\n // that completes before the id is durable lets a concurrent resume read a\n // stale id and provision a replacement VM.\n const publishStartSideEffects = async () => {\n await storage.sessions.setSandbox({\n id: session.id,\n // Persist the provider's PHYSICAL, reattachable VM id so resume can\n // reattach to the same VM. Providers with no separate physical id\n // (e.g. local) persist nothing: a logical id handed back as a\n // `sandboxId` hint is unknown to the provider and breaks resume.\n sandboxId: target.sandboxId ?? null,\n sandboxWorkdir: sessionEntry.workdir ?? '',\n });\n void constructedWorkspaces\n .get(workspaceId)\n ?.skills?.refresh()\n .catch(() => {});\n };\n const finishStart = async () => {\n await publishStartSideEffects();\n if (setupError) throw setupError;\n };\n if (!githubProvider) {\n await finishStart();\n return;\n }\n const existingRegistration = githubTokenInjectors.get(workspaceId);\n if (existingRegistration) {\n // Reconnect: re-point the current registration's injection target at\n // this (possibly provider-healed) sandbox and reinstall its\n // reconcile-owned credential. Do NOT mint a new registration, bump\n // authority, or re-register the constructing request context —\n // reconcileGithubToken owns role/generation and the active context's\n // injector, so replacing them here would reauthorize the stale\n // constructing context and reject the current one.\n existingRegistration.inject = freshToken => {\n if (!target.setEnv) {\n throw new Error('The active sandbox provider does not support runtime GitHub token refresh.');\n }\n target.setEnv(env => ({ ...env, GH_TOKEN: freshToken }));\n existingRegistration.ghToken = freshToken;\n };\n // Install through inject (not optional-chained setEnv) so a provider\n // that cannot accept the credential fails the reconnect here instead of\n // deferring the failure to a later token refresh.\n existingRegistration.inject(existingRegistration.ghToken);\n await finishStart();\n return;\n }\n // First start: resolve the credential and authorize every request that\n // resolved this pending workspace. The `gh` CLI needs a PAT when the org configured one\n // (installation tokens 403 on integration-restricted endpoints); git\n // clone/checkout keep using the minted installation token. Resolved per\n // start so the installed credential never outlives rotation.\n const patKind = await resolveGithubPatKind('default');\n const ghCliToken =\n (await getGithubPat(() => githubProvider.integrationStorage, session.orgId, patKind)) ??\n (await getRepositoryToken());\n target.setEnv?.(env => ({ ...env, GH_TOKEN: ghCliToken }));\n const tokenRegistration: GithubTokenRegistration = {\n inject: freshToken => {\n if (!target.setEnv) {\n throw new Error('The active sandbox provider does not support runtime GitHub token refresh.');\n }\n target.setEnv(env => ({ ...env, GH_TOKEN: freshToken }));\n tokenRegistration.ghToken = freshToken;\n },\n patKind,\n ghToken: ghCliToken,\n generation: 0,\n tokenReplacementPending: false,\n };\n githubTokenInjectors.set(workspaceId, tokenRegistration);\n registerGithubTokenContext(tokenRegistration);\n await finishStart();\n };\n const constructSessionEntry = () =>\n getSessionSandbox(session.id, repoFullName, () => {\n const sandbox = createSessionSandboxInstance({\n sessionId: session.id,\n // Physical VM id persisted from a prior start (undefined on first\n // start). Providers that reattach by physical id use it to resume the\n // original VM instead of provisioning a replacement.\n sandboxId: session.sandboxId ?? undefined,\n repoFullName,\n // Stored nullable; the context speaks `undefined` for absent.\n setupCommand: projectRepository.setupCommand ?? undefined,\n // Deferred call — only dereferenced when a provider needs the repo\n // outside the VM (template build time).\n getRepositoryAccess: () =>\n sourceControl.versionControl.getRepositoryAccess({\n orgId: session.orgId,\n repositoryId: repository.id,\n }),\n });\n // Attached inside the construction closure, so exactly once per\n // instance — `constructSessionEntry` runs on every open and would\n // stack a wrapper per call. Factory's setup runs first: a hook the\n // callback installed itself expects a prepared workspace.\n sandbox.setOnStart(previous => async args => {\n await timedPhase(`workspace.onStart(${args.outcome})`, async () => {\n await setupHook(args);\n });\n await previous?.(args);\n });\n // Only a freshly constructed instance starts eagerly; the start itself\n // waits for this resolver to finish because the start hook reads\n // bindings declared further down.\n startEagerly = eagerSandboxStart;\n return sandbox;\n });\n let startEagerly = false;\n const fireEagerStart = () => {\n if (!startEagerly) return;\n startEagerly = false;\n Promise.resolve()\n .then(() => sessionEntry.sandbox.start?.())\n .catch(error => {\n console.warn(`[factory] Eager sandbox start for session ${session.id} failed:`, error);\n });\n };\n const sessionEntry = constructSessionEntry();\n const workdir = sessionEntry.workdir;\n const isLocalSandbox = sessionEntry.sandbox.provider === 'local';\n // The SDK system prompt uses `state.projectPath` without falling back to\n // the server cwd. Pin the session workdir once known so the prompt and\n // workspace tools describe the same checkout. A remote workdir resolves at\n // the sandbox's first start, so the pin self-heals on the next resolution.\n if (ctx && workdir && ctx.getState()?.projectPath !== workdir) {\n await ctx.setState({ projectPath: workdir, projectName: repoFullName });\n }\n\n // Keyed by the session, not the request's thread: a workspace built for a\n // dispatcher kickoff has no live thread yet, but the session's binding\n // already says which role it is serving. When the request does carry a\n // thread, the binding must belong to it, so a stale thread reusing this\n // session never inherits another run's role.\n const findSessionBinding = async () => {\n const binding = await workItems!.findActiveRunBindingForSession({\n orgId: session.orgId,\n factoryProjectId: connection.factoryProjectId,\n sessionId: session.sessionId,\n });\n if (!binding || !ctx?.threadId) return binding;\n return binding.threadId === ctx.threadId && binding.resourceId === ctx.resourceId ? binding : null;\n };\n // Fails closed: without a readable active review binding, review skills stay hidden.\n const isReviewSession = async (): Promise<boolean> => {\n if (!workItems) return false;\n try {\n return isActiveReviewBinding(await findSessionBinding(), session.orgId);\n } catch {\n return false;\n }\n };\n const effectiveSkillExtension =\n skillExtension ??\n createFactorySkillExtension((): Promise<boolean> => (skillReviewChecks.get(workspaceId) ?? isReviewSession)());\n const extensionId = effectiveSkillExtension ? `-${effectiveSkillExtension.id}` : '';\n const workspaceId: string = `${WORKSPACE_ID_PREFIX}-${projectRepository.id}-${session.id}${extensionId}`;\n const workspaceGeneration = workspaceRegistry.generation(session.sessionId);\n if (!skillExtension) skillReviewChecks.set(workspaceId, isReviewSession);\n const configDir = DEFAULT_CONFIG_DIR;\n\n const getRepositoryAccess = () =>\n sourceControl.versionControl.getRepositoryAccess({\n orgId: session.orgId,\n repositoryId: repository.id,\n });\n const getRepositoryToken = async (): Promise<string> => {\n const token = (await getRepositoryAccess()).authorization?.token;\n if (!token) throw new Error('Repository access did not include a bearer token for the Factory session');\n return token;\n };\n const resolveGithubPatKind = async (fallback: GithubPatKind): Promise<GithubPatKind> => {\n if (!workItems) return 'default';\n try {\n return isActiveReviewBinding(await findSessionBinding(), session.orgId) ? 'reviewer' : 'default';\n } catch {\n // Preserve the installed role when binding storage is temporarily unavailable.\n return fallback;\n }\n };\n const githubTokenInjector = (registered: GithubTokenRegistration, generation: number) => (token: string) => {\n if (githubTokenInjectors.get(workspaceId) !== registered || registered.generation !== generation) {\n throw new Error('GitHub token refresh no longer matches the active Factory workspace role.');\n }\n registered.inject(token);\n };\n const registerGithubTokenContext = (registered: GithubTokenRegistration): void => {\n registerGithubTokenInjector(requestContext, githubTokenInjector(registered, registered.generation));\n registerGithubPatKind(requestContext, registered.patKind);\n };\n if (githubProvider) {\n const registered = githubTokenInjectors.get(workspaceId);\n if (registered) {\n registerGithubTokenContext(registered);\n } else {\n registerGithubTokenInjectorResolver(requestContext, () => {\n if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) {\n throw new Error('GitHub token refresh no longer matches the active Factory workspace role.');\n }\n const active = githubTokenInjectors.get(workspaceId);\n if (!active) {\n throw new Error('GitHub token refresh requires an active Factory sandbox workspace.');\n }\n registerGithubPatKind(requestContext, active.patKind);\n return githubTokenInjector(active, 0);\n });\n }\n }\n const reconcileGithubToken = async (): Promise<void> => {\n if (!githubProvider) return;\n const previous = githubTokenReconciliations.get(workspaceId) ?? Promise.resolve();\n const reconciliation = previous\n .catch(() => {})\n .then(async () => {\n const registered = githubTokenInjectors.get(workspaceId);\n if (!registered) return;\n\n const previousPatKind = registered.patKind;\n const patKind = await resolveGithubPatKind(previousPatKind);\n if (githubTokenInjectors.get(workspaceId) !== registered) return;\n\n if (patKind !== previousPatKind) {\n registered.patKind = patKind;\n registered.generation += 1;\n }\n if (patKind === 'reviewer') registered.tokenReplacementPending = false;\n if (previousPatKind === 'reviewer' && patKind === 'default') {\n // Invalidate reviewer refresh contexts before replacement I/O so\n // they cannot restore reviewer credentials after a failed downgrade.\n registered.tokenReplacementPending = true;\n }\n\n let token = await getGithubPat(() => githubProvider.integrationStorage, session.orgId, patKind);\n if (!token && registered.tokenReplacementPending) token = await getRepositoryToken();\n if (githubTokenInjectors.get(workspaceId) !== registered) return;\n\n if (token && token !== registered.ghToken) {\n try {\n registered.inject(token);\n } catch (error) {\n if (registered.tokenReplacementPending) throw error;\n // Same-role rotations and reviewer upgrades remain best-effort.\n }\n }\n if (token && token === registered.ghToken) registered.tokenReplacementPending = false;\n registerGithubTokenContext(registered);\n });\n githubTokenReconciliations.set(workspaceId, reconciliation);\n try {\n await reconciliation;\n } finally {\n if (githubTokenReconciliations.get(workspaceId) === reconciliation) {\n githubTokenReconciliations.delete(workspaceId);\n }\n }\n };\n const reconcileRegisteredWorkspace = async (workspace: Workspace): Promise<Workspace> => {\n const registered = githubTokenInjectors.get(workspaceId);\n try {\n await reconcileGithubToken();\n } catch (error) {\n if (registered?.tokenReplacementPending && githubTokenInjectors.get(workspaceId) === registered) {\n // The role generation already invalidated reviewer refresh contexts.\n // Keep the pending registration so failed eviction cannot make a\n // still-live reviewer workspace look safe on the next reuse.\n let evicted = false;\n try {\n evicted = (await mastra?.removeWorkspace?.(workspaceId)) === true;\n } catch {\n // Preserve the credential-replacement error and retry on the next reuse.\n }\n try {\n await workspace.destroy();\n evicted = true;\n } catch {\n // The pending registration keeps the workspace quarantined if cleanup also fails.\n }\n if (evicted && githubTokenInjectors.get(workspaceId) === registered) {\n githubTokenInjectors.delete(workspaceId);\n constructedWorkspaces.delete(workspaceId);\n skillCacheReviewState.delete(workspaceId);\n }\n }\n throw error;\n }\n if (registered && githubTokenInjectors.get(workspaceId) !== registered) {\n throw new Error('Factory workspace GitHub credential registration is no longer active.');\n }\n return workspace;\n };\n\n let existing: Workspace | undefined;\n try {\n existing = mastra?.getWorkspaceById(workspaceId) as Workspace | undefined;\n } catch {\n // Not registered yet.\n existing = undefined;\n }\n existing ??= constructedWorkspaces.get(workspaceId);\n if (existing) {\n existing.setToolsConfig(MASTRACODE_WORKSPACE_TOOLS);\n // The skill cache does not recheck the source on get(), so rescan when the\n // session's role flips; otherwise a cached review skill outlives the review binding.\n if (!skillExtension) {\n // An outside rescan makes the recorded role untrustworthy. Wait for it to\n // settle first, so the refresh below starts a scan for the current role\n // rather than joining one started for the previous role.\n const consumeOutsideRescan = async () => {\n const outside = existing.skills && rescannedOutsideResolver.get(existing.skills);\n if (!outside) return false;\n await outside;\n if (rescannedOutsideResolver.get(existing.skills!) === outside)\n rescannedOutsideResolver.delete(existing.skills!);\n skillCacheReviewState.delete(workspaceId);\n return true;\n };\n await consumeOutsideRescan();\n let isReview = await isReviewSession();\n // Concurrent reuses share one refresh, and the state is recorded only\n // once the rescan succeeds. Wait out any in-flight rescan (it may have\n // been started for the opposite role) and re-read the role afterwards.\n for (;;) {\n const inFlight = skillCacheRefreshes.get(workspaceId);\n if (inFlight) {\n await inFlight;\n isReview = await isReviewSession();\n continue;\n }\n if (await consumeOutsideRescan()) {\n isReview = await isReviewSession();\n continue;\n }\n if (skillCacheReviewState.get(workspaceId) === isReview) break;\n const target = isReview;\n const pending = (async () => {\n await existing.skills?.refresh();\n skillCacheReviewState.set(workspaceId, target);\n })().finally(() => skillCacheRefreshes.delete(workspaceId));\n skillCacheRefreshes.set(workspaceId, pending);\n await pending;\n isReview = await isReviewSession();\n }\n }\n // A materialization kicked off by another caller may still be running.\n // Deliberately do NOT wait for it: a metadata-only resolution (thread\n // list, messages, activity) must not block on the clone/setup that lazy\n // materialization exists to avoid. Token reconciliation below no-ops\n // until the leader registers the injector, and the next reuse after\n // materialization completes reconciles against the live sandbox.\n return reconcileRegisteredWorkspace(existing);\n }\n\n const retiredError = () =>\n new Error(`Factory session ${session.sessionId} was retired during workspace materialization`);\n\n // The session's setup work: materialize the repo (disk-truth idempotent),\n // check out the session branch, run the configured setup command. Minted\n // tokens are fetched inside the run so a replacement VM healed mid-session\n // gets fresh credentials, not ones captured at workspace construction.\n const runSessionSetup = async (target: SessionSandbox, workdir: string, gate: SessionSetupGate): Promise<void> => {\n const access = await getRepositoryAccess();\n const token = access.authorization?.token;\n if (!token) throw new Error('Repository access did not include a bearer token for the Factory session');\n // GitHub setup commands may shell out to gh/HTTPS. Preserve its\n // role-aware PAT behavior without applying GitHub credentials to other\n // source-control providers.\n if (githubProvider) {\n const setupPatKind = await resolveGithubPatKind('default');\n const setupGhToken =\n (await getGithubPat(() => githubProvider.integrationStorage, session.orgId, setupPatKind)) ?? token;\n target.setEnv?.(env => ({ ...env, GH_TOKEN: setupGhToken }));\n }\n await materializeRepo({\n row: { id: session.id, sandboxWorkdir: workdir, materializedAt: session.materializedAt },\n repoInfo: {\n repoFullName,\n defaultBranch: repository.defaultBranch,\n cloneUrl: access.cloneUrl,\n authUsername: access.authorization?.username,\n },\n sandbox: target,\n token,\n storage: storage.sessions,\n });\n await checkoutSessionBranch(target, workdir, {\n branch: session.branch,\n baseBranch: session.baseBranch || projectRepository.branch || repository.defaultBranch,\n token,\n repoFullName,\n cloneUrl: access.cloneUrl,\n authUsername: access.authorization?.username,\n pullRequestNumber: pullRequestNumberFromBranch(session.branch),\n mergeRequestNumber: sourceControl.id === 'gitlab' ? mergeRequestNumberFromBranch(session.branch) : undefined,\n });\n if (projectRepository.setupCommand && !gate.setupDone) {\n // A setup command that already failed this session is skipped rather\n // than failing every start: the first failure surfaced loudly in the\n // tool result that triggered it, and a permanently failing onStart\n // would wedge the session — the agent could never get a shell to fix\n // the problem. Clone and checkout above still ran, so the tree is\n // real; the agent (or an edited setup command) takes it from here.\n if (hasFailedSetupCommand(session.id, projectRepository.setupCommand)) {\n console.warn('[Mastra Factory] Skipping setup command that already failed this session', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n });\n return;\n }\n try {\n await timedPhase('workspace.setup', () => runSetupCommand(target, workdir, projectRepository.setupCommand!));\n await gate.markSetupDone();\n } catch (setupError) {\n if (projectRepository.teardownCommand) {\n try {\n await runTeardownCommand(target, workdir, projectRepository.teardownCommand, {\n timeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,\n });\n } catch (teardownError) {\n console.warn('[Mastra Factory] Worktree teardown after setup failure failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: teardownError instanceof Error ? teardownError.message.slice(-2000) : String(teardownError),\n });\n }\n }\n if (setupError instanceof SetupCommandError) {\n // The command ran and exited non-zero — a config problem, not an\n // infra one. Remember it so the next start recovers, and tell the\n // agent what happens next. Infra failures (transport, clone)\n // rethrow untouched and retry in full.\n recordFailedSetupCommand(session.id, projectRepository.setupCommand);\n throw new SetupCommandError(\n `${setupError.message}. The sandbox stays usable: this setup command is skipped for the rest of the session — retry your command, then fix the setup command in the repository settings or run it manually.`,\n setupError.code,\n );\n }\n throw setupError;\n }\n }\n };\n // The session's real sandbox goes straight onto the Workspace. Providers\n // own lazy start (`ensureRunning()` inside the first command/process op)\n // and dead-VM self-healing, and the composed `onStart` hook runs the repo\n // setup + credential install inside that lifecycle. Metadata-only\n // resolutions (thread-list polling) construct but never start.\n const sessionSandbox: SessionSandbox = requireExec(sessionEntry.sandbox);\n\n const filesystem = new SandboxFilesystem({\n id: `sandbox-fs:${workspaceId}`,\n sandbox: sessionSandbox,\n // Lazy: a remote workdir is only knowable once a VM runs. The first\n // file operation resolves it (starting the VM — which materializes the\n // repo via the onStart hook — when needed) and memoizes it.\n workdir: () => resolveSessionWorkdir(session.id, sessionEntry.sandbox, repoFullName),\n });\n const projectSkillPaths = [path.join(configDir, 'skills'), '.claude/skills', '.agents/skills'];\n const guardedSkillFallback = new UnmaterializedAwareSkillSource(\n filesystem,\n () => sessionEntry.sandbox.status === 'running',\n );\n const skillPaths = [...(effectiveSkillExtension?.paths ?? []), ...projectSkillPaths];\n const workspace = new Workspace({\n id: workspaceId,\n name: 'Mastra Code Factory Session Workspace',\n filesystem,\n sandbox: sessionSandbox as unknown as ConstructorParameters<typeof Workspace>[0]['sandbox'],\n tools: MASTRACODE_WORKSPACE_TOOLS,\n skills: skillPaths,\n // Project skill roots live in the sandbox checkout; guard them so skill\n // discovery before materialization (e.g. kickoff skill resolution in the\n // start coordinator) never forces sandbox provisioning.\n skillSource:\n effectiveSkillExtension?.createSource(guardedSkillFallback, projectSkillPaths) ?? guardedSkillFallback,\n });\n // Register with the Mastra instance so sync HTTP handlers that resolve\n // the workspace via `mastra.getWorkspaceById(id)` (file tree, permissions\n // probe, MCP/tool routes) find it instead of throwing\n // `MASTRA_GET_WORKSPACE_BY_ID_NOT_FOUND`. `addWorkspace` is idempotent on\n // key collision, so concurrent first resolutions stay race-safe (start\n // itself is coalesced by the sandbox base class + the session memo).\n mastra?.addWorkspace(workspace, workspaceId, { source: 'mastra' });\n // Cache synchronously with construction: the `await` below is a suspension\n // point, and a concurrent resolution for the same session must observe this\n // workspace rather than build a second one.\n constructedWorkspaces.set(workspaceId, workspace);\n // Retirement is registered against the workspace itself rather than the\n // sandbox: construction is eager while the VM start is lazy, so a session\n // retired before its first tool call still has a workspace (and possibly a\n // token injector) that must be torn down.\n const registered = await workspaceRegistry.register(\n session.sessionId,\n workspaceId,\n workspaceGeneration,\n async () => {\n githubTokenInjectors.delete(workspaceId);\n constructedWorkspaces.delete(workspaceId);\n skillCacheReviewState.delete(workspaceId);\n skillReviewChecks.delete(workspaceId);\n // Retirement drops the memoized session sandbox so a later re-open\n // constructs (and the provider resolves) fresh instead of reusing an\n // instance whose VM the retirement path may stop or destroy.\n evictSessionSandbox(session.id);\n await mastra?.removeWorkspace?.(workspaceId);\n },\n );\n if (!registered) {\n throw new Error(`Factory session ${session.sessionId} was retired during workspace materialization`);\n }\n\n fireEagerStart();\n return workspace;\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"workspace.js","names":["#bundledSource","#localSource","#fallbackSkillRoots","#isHiddenReviewPath","#layerFor","#isFactoryPath","#factoryPath","#reservedNames","#entries","#generations"],"sources":["../src/workspace.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport path, { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { SandboxFilesystem } from '@mastra/code-sdk/agents/sandbox-filesystem';\nimport { MASTRACODE_WORKSPACE_TOOLS } from '@mastra/code-sdk/agents/tool-availability';\nimport type { getDynamicWorkspace, WorkspaceSkillExtension } from '@mastra/code-sdk/agents/workspace';\nimport { DEFAULT_CONFIG_DIR } from '@mastra/code-sdk/constants';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport { LocalSkillSource, Workspace } from '@mastra/core/workspace';\nimport type {\n SandboxStartHook,\n SkillSource,\n SkillSourceEntry,\n SkillSourceStat,\n WorkspaceSandbox,\n} from '@mastra/core/workspace';\nimport { getFactoryAuthOrgId, getFactoryAuthUserFromContext, getFactoryAuthUserId } from './auth.js';\nimport type { VersionControl } from './capabilities/version-control.js';\nimport type { MastraFactorySandboxConfig } from './factory.js';\nimport type { GithubIntegration } from './integrations/github/integration.js';\nimport { getGithubPat } from './integrations/github/pat.js';\nimport type { GithubPatKind } from './integrations/github/pat.js';\nimport {\n checkoutSessionBranch,\n DEFAULT_COMMAND_TIMEOUT_MS,\n materializeRepo,\n runSetupCommand,\n runTeardownCommand,\n SetupCommandError,\n} from './integrations/github/sandbox.js';\nimport {\n registerGithubPatKind,\n registerGithubRefreshTarget,\n registerGithubTokenInjector,\n registerGithubTokenInjectorResolver,\n} from './integrations/github/token-refresh.js';\nimport { requireExec } from './sandbox/materialization.js';\nimport type { ExecutableSandbox } from './sandbox/materialization.js';\nimport {\n createSessionSetupHook,\n evictSessionSandbox,\n getSessionSandbox,\n hasFailedSetupCommand,\n recordFailedSetupCommand,\n resolveSessionWorkdir,\n} from './sandbox/session-sandbox.js';\nimport type { SessionSetupGate } from './sandbox/session-sandbox.js';\nimport type { FactoryProjectsStorage } from './storage/domains/projects/base.js';\nimport type { SourceControlSession, SourceControlStorageHandle } from './storage/domains/source-control/base.js';\nimport type { WorkItemsStorage } from './storage/domains/work-items/base.js';\nimport { parseSupervisorResourceId } from './supervisor/session.js';\nimport { timedPhase } from './timing.js';\nimport { mergeRequestNumberFromBranch, pullRequestNumberFromBranch } from './work-item-branch.js';\n\nconst WORKSPACE_ID_PREFIX = 'mfw';\nconst bundleDirectory = dirname(fileURLToPath(import.meta.url));\nconst bundledFactorySkillsPath = join(bundleDirectory, 'factory-skills');\nexport const BUNDLED_FACTORY_SKILLS_PATH =\n [\n // Deploy bundle: the consumer copies `factory-skills/` next to the built\n // server module (e.g. via its public/ dir).\n bundledFactorySkillsPath,\n // Package layout: `dist/../factory-skills` (also `src/../factory-skills`\n // when running tests against sources).\n join(bundleDirectory, '..', 'factory-skills'),\n ].find(existsSync) ?? bundledFactorySkillsPath;\n\n/**\n * Resolve the consumer repo's local Factory skills root, if any. Checked in\n * addition to the bundled skills so projects can add (or override) Factory\n * skills without patching the installed package. Candidates cover the cwd\n * variants the dev server runs with (`repo root`, `--dir src/mastra` which\n * runs with cwd `src/mastra/public`).\n */\nexport function resolveLocalFactorySkillsPath(cwd: string = process.cwd()): string | undefined {\n const candidates = [\n join(cwd, 'src', 'mastra', 'public', 'factory-skills'),\n join(cwd, 'public', 'factory-skills'),\n join(cwd, 'factory-skills'),\n ];\n return candidates.find(\n candidate => path.normalize(candidate) !== path.normalize(BUNDLED_FACTORY_SKILLS_PATH) && existsSync(candidate),\n );\n}\nconst FACTORY_SKILLS_MOUNT = path.resolve(path.parse(process.cwd()).root, '__mastracode_factory_skills__');\nexport const FACTORY_SKILL_NAMES = new Set([\n 'configure-factory-rules',\n 'factory-complete-issue',\n 'factory-gitlab-rereview',\n 'factory-gitlab-review',\n 'factory-plan',\n 'factory-rereview',\n 'factory-review',\n 'factory-triage',\n]);\n\n/** Review skills only sessions with an active review run binding may discover or load. */\nexport const REVIEW_ONLY_FACTORY_SKILLS = new Set([\n 'factory-gitlab-rereview',\n 'factory-gitlab-review',\n 'factory-rereview',\n 'factory-review',\n]);\n\n/**\n * Skill caches rescanned outside the workspace resolver. The resolver records\n * which role each cache was last scanned for; a rescan it did not perform makes\n * that record untrustworthy, so the next reuse must rescan again.\n */\nconst rescannedOutsideResolver = new WeakMap<object, Promise<void>>();\n\n/**\n * Rescan a session's skill cache so a role gained after the cache was built\n * (a review binding minted after the session's workspace resolved) takes effect.\n * The source still checks the live binding, so this never widens access.\n */\nexport async function rescanFactorySkills(skills: NonNullable<Workspace['skills']>): Promise<void> {\n // Registered so the resolver waits it out: a refresh started meanwhile would\n // join this scan (possibly for the old role) instead of starting a fresh one.\n const pending = skills.refresh();\n rescannedOutsideResolver.set(\n skills,\n pending.catch(() => {}),\n );\n await pending;\n}\n\nexport class FactorySkillSource implements SkillSource {\n readonly #bundledSource = new LocalSkillSource({ basePath: BUNDLED_FACTORY_SKILLS_PATH });\n readonly #localSource: LocalSkillSource | undefined;\n readonly #fallbackSkillRoots: Set<string>;\n\n constructor(\n readonly fallback: SkillSource,\n fallbackSkillRoots: string[],\n localSkillsPath: string | undefined = resolveLocalFactorySkillsPath(),\n /** Evaluated per call because a session's bound role changes across stages. */\n readonly isReviewSession: () => Promise<boolean> = () => Promise.resolve(true),\n ) {\n this.#localSource = localSkillsPath ? new LocalSkillSource({ basePath: localSkillsPath }) : undefined;\n this.#fallbackSkillRoots = new Set(fallbackSkillRoots.map(skillPath => path.normalize(skillPath)));\n }\n\n #isFactoryPath(skillPath: string): boolean {\n const normalized = path.normalize(skillPath);\n return normalized === FACTORY_SKILLS_MOUNT || normalized.startsWith(`${FACTORY_SKILLS_MOUNT}${path.sep}`);\n }\n\n #factoryPath(skillPath: string): string {\n return path.relative(FACTORY_SKILLS_MOUNT, path.normalize(skillPath));\n }\n\n /** True when the path is inside a review-only skill that this session may not use. */\n async #isHiddenReviewPath(relativePath: string): Promise<boolean> {\n const skillName = relativePath.split(path.sep)[0];\n if (!skillName || !REVIEW_ONLY_FACTORY_SKILLS.has(skillName)) return false;\n return !(await this.isReviewSession());\n }\n\n /** Pick the layer serving this mount-relative path: local wins when it has the entry. */\n async #layerFor(relativePath: string): Promise<LocalSkillSource> {\n if (this.#localSource && (await this.#localSource.exists(relativePath))) return this.#localSource;\n return this.#bundledSource;\n }\n\n async exists(skillPath: string): Promise<boolean> {\n if (!this.#isFactoryPath(skillPath)) return this.fallback.exists(skillPath);\n const relative = this.#factoryPath(skillPath);\n if (await this.#isHiddenReviewPath(relative)) return false;\n if (this.#localSource && (await this.#localSource.exists(relative))) return true;\n return this.#bundledSource.exists(relative);\n }\n\n async stat(skillPath: string): Promise<SkillSourceStat> {\n if (!this.#isFactoryPath(skillPath)) return this.fallback.stat(skillPath);\n const relative = this.#factoryPath(skillPath);\n if (await this.#isHiddenReviewPath(relative)) throw skillSourceEnoent(skillPath);\n return (await this.#layerFor(relative)).stat(relative);\n }\n\n async readFile(skillPath: string): Promise<string | Buffer> {\n if (!this.#isFactoryPath(skillPath)) return this.fallback.readFile(skillPath);\n const relative = this.#factoryPath(skillPath);\n if (await this.#isHiddenReviewPath(relative)) throw skillSourceEnoent(skillPath);\n return (await this.#layerFor(relative)).readFile(relative);\n }\n\n async readdir(skillPath: string): Promise<SkillSourceEntry[]> {\n if (this.#isFactoryPath(skillPath)) {\n const relative = this.#factoryPath(skillPath);\n if (await this.#isHiddenReviewPath(relative)) throw skillSourceEnoent(skillPath);\n const [bundledExists, localExists] = await Promise.all([\n this.#bundledSource.exists(relative),\n this.#localSource?.exists(relative) ?? Promise.resolve(false),\n ]);\n if (!bundledExists && !localExists) throw skillSourceEnoent(skillPath);\n const [bundledEntries, localEntries] = await Promise.all([\n bundledExists ? this.#bundledSource.readdir(relative) : [],\n localExists ? this.#localSource!.readdir(relative) : [],\n ]);\n const merged = new Map<string, SkillSourceEntry>();\n for (const entry of bundledEntries) merged.set(entry.name, entry);\n // Local entries override bundled names.\n for (const entry of localEntries) merged.set(entry.name, entry);\n const entries = [...merged.values()];\n if (relative !== '' || (await this.isReviewSession())) return entries;\n return entries.filter(entry => !REVIEW_ONLY_FACTORY_SKILLS.has(entry.name));\n }\n const entries = await this.fallback.readdir(skillPath);\n if (this.#fallbackSkillRoots.has(path.normalize(skillPath))) {\n const reserved = await this.#reservedNames();\n return entries.filter(entry => !reserved.has(entry.name));\n }\n return entries;\n }\n\n /**\n * Names the Factory mount serves: bundled skills plus the app's local skills.\n * Repository skills with these names are hidden so they cannot collide with\n * Factory's copy. Recomputed per scan so skill refreshes pick up local changes.\n */\n async #reservedNames(): Promise<Set<string>> {\n const reserved = new Set(FACTORY_SKILL_NAMES);\n if (!this.#localSource) return reserved;\n try {\n for (const entry of await this.#localSource.readdir('')) {\n if (entry.type === 'directory') reserved.add(entry.name);\n }\n } catch {\n // A missing or unreadable local root reserves only the bundled names.\n }\n return reserved;\n }\n\n realpath(skillPath: string): Promise<string> {\n if (this.#isFactoryPath(skillPath)) return Promise.resolve(path.normalize(skillPath));\n return this.fallback.realpath ? this.fallback.realpath(skillPath) : Promise.resolve(skillPath);\n }\n}\n\n/** Build a Node-style ENOENT error so callers can treat missing skills like fs misses. */\nfunction skillSourceEnoent(skillPath: string): Error {\n const error = new Error(`ENOENT: no such file or directory, '${skillPath}'`) as Error & { code: string };\n error.code = 'ENOENT';\n return error;\n}\n\n/**\n * Sandbox-backed skill fallback that stays inert until the session sandbox is\n * actually materialized. Skill discovery runs on latency-sensitive paths (the\n * Factory start coordinator resolves the kickoff skill before the start route\n * responds); without this guard the first project-root read would hit the lazy\n * sandbox handle and force full provisioning + repo materialization. While the\n * sandbox is unmaterialized, project skill roots simply appear empty — bundled\n * Factory skills resolve from local disk via `FactorySkillSource`. Once the\n * sandbox exists, every call delegates straight through.\n */\nclass UnmaterializedAwareSkillSource implements SkillSource {\n constructor(\n readonly fallback: SkillSource,\n readonly isMaterialized: () => boolean,\n ) {}\n\n async exists(skillPath: string): Promise<boolean> {\n return this.isMaterialized() ? this.fallback.exists(skillPath) : false;\n }\n\n async stat(skillPath: string): Promise<SkillSourceStat> {\n if (!this.isMaterialized()) throw skillSourceEnoent(skillPath);\n return this.fallback.stat(skillPath);\n }\n\n async readFile(skillPath: string): Promise<string | Buffer> {\n if (!this.isMaterialized()) throw skillSourceEnoent(skillPath);\n return this.fallback.readFile(skillPath);\n }\n\n async readdir(skillPath: string): Promise<SkillSourceEntry[]> {\n return this.isMaterialized() ? this.fallback.readdir(skillPath) : [];\n }\n\n realpath(skillPath: string): Promise<string> {\n if (!this.isMaterialized()) return Promise.resolve(skillPath);\n return this.fallback.realpath ? this.fallback.realpath(skillPath) : Promise.resolve(skillPath);\n }\n}\n\nfunction createFactorySkillExtension(isReviewSession: () => Promise<boolean>): WorkspaceSkillExtension {\n return {\n id: 'web-factory',\n paths: [FACTORY_SKILLS_MOUNT],\n createSource: (fallback, fallbackSkillRoots) =>\n new FactorySkillSource(fallback, fallbackSkillRoots, resolveLocalFactorySkillsPath(), isReviewSession),\n };\n}\n\nfunction isActiveReviewBinding(\n runBinding: { role: string; status: string; orgId: string } | null | undefined,\n orgId: string | undefined,\n): boolean {\n return runBinding?.role === 'review' && runBinding.status === 'active' && !!orgId && runBinding.orgId === orgId;\n}\n\ntype DynamicWorkspaceContext = Parameters<typeof getDynamicWorkspace>[0];\n\n/**\n * When a session's sandbox boots: on the agent's first command (`'lazy'`, the\n * default) or as soon as the session's workspace is first resolved (`'eager'`).\n * An eager start is fire-and-forget; if it fails, the lazy path still runs.\n */\nexport type FactorySandboxStart = 'lazy' | 'eager';\n\nexport interface WorkspaceSourceControlProvider {\n /** Stable integration id used to select the provider-owned storage partition. */\n id: string;\n /** Provider capability used to resolve fresh repository credentials. */\n versionControl: Pick<VersionControl, 'getRepositoryAccess'>;\n /** Provider-owned source-control rows, including Factory sessions. */\n storage: SourceControlStorageHandle;\n /** Present only for GitHub, whose CLI PAT rotation has additional semantics. */\n github?: GithubIntegration;\n}\n\nexport interface CreateWorkspaceFactoryOptions {\n /** Factory sandbox runtime config (session sandbox callback). */\n sandbox?: MastraFactorySandboxConfig;\n /** Defaults to `'lazy'`. */\n sandboxStart?: FactorySandboxStart;\n /** Source-control providers whose repositories may back Factory sessions. */\n sourceControls?: WorkspaceSourceControlProvider[];\n /** @deprecated GitHub-only compatibility path; prefer sourceControls. */\n github?: GithubIntegration;\n /** Work-items storage used to resolve the session's run-binding role, so\n * review-board sessions get the reviewer PAT as `GH_TOKEN`. Optional —\n * without it every session uses the default (worker) PAT. */\n workItems?: Pick<WorkItemsStorage, 'findActiveRunBindingForSession'>;\n /** Projects storage used to authorize workspace-free supervisor sessions. */\n projects?: Pick<FactoryProjectsStorage, 'get'>;\n /** Runtime workspace/token registrations invalidated when a session retires. */\n workspaceRegistry?: FactoryWorkspaceRegistry;\n}\n\ntype WorkspaceUnregister = () => Promise<void> | void;\n\n/** Tracks dynamic Factory workspaces by persisted session id for retirement. */\nexport class FactoryWorkspaceRegistry {\n readonly #entries = new Map<string, Map<string, WorkspaceUnregister>>();\n readonly #generations = new Map<string, number>();\n\n generation(sessionId: string): number {\n return this.#generations.get(sessionId) ?? 0;\n }\n\n async register(\n sessionId: string,\n workspaceId: string,\n generation: number,\n unregister: WorkspaceUnregister,\n ): Promise<boolean> {\n if (generation !== this.generation(sessionId)) {\n await unregister();\n return false;\n }\n const entries = this.#entries.get(sessionId) ?? new Map<string, WorkspaceUnregister>();\n entries.set(workspaceId, unregister);\n this.#entries.set(sessionId, entries);\n return true;\n }\n\n async invalidateSession(sessionId: string): Promise<void> {\n this.#generations.set(sessionId, this.generation(sessionId) + 1);\n const entries = this.#entries.get(sessionId);\n if (!entries) return;\n this.#entries.delete(sessionId);\n const results = await Promise.allSettled([...entries.values()].map(unregister => unregister()));\n const failure = results.find(result => result.status === 'rejected');\n if (failure?.status === 'rejected') throw failure.reason;\n }\n}\n\nasync function resolveSourceControlSession(\n providers: WorkspaceSourceControlProvider[],\n sessionId: string,\n): Promise<{ provider: WorkspaceSourceControlProvider; session: SourceControlSession } | null> {\n const matches = (\n await Promise.all(\n providers.map(async provider => ({\n provider,\n session: await provider.storage.sessions.getBySessionId(sessionId),\n })),\n )\n ).filter(\n (match): match is { provider: WorkspaceSourceControlProvider; session: SourceControlSession } =>\n match.session !== null,\n );\n if (matches.length > 1) {\n throw new Error(`Factory session ${sessionId} is ambiguous across source-control providers`);\n }\n return matches[0] ?? null;\n}\n\nexport function createWorkspaceFactory(options: CreateWorkspaceFactoryOptions = {}) {\n const { sandbox: sandboxConfig, github, projects, workItems } = options;\n const sourceControls: WorkspaceSourceControlProvider[] =\n options.sourceControls ??\n (github\n ? [{ id: github.id, versionControl: github.versionControl, storage: github.sourceControlStorage, github }]\n : []);\n const eagerSandboxStart = options.sandboxStart === 'eager';\n const workspaceRegistry = options.workspaceRegistry ?? new FactoryWorkspaceRegistry();\n type GithubTokenRegistration = {\n inject: (token: string) => void;\n patKind: GithubPatKind;\n ghToken: string;\n generation: number;\n tokenReplacementPending: boolean;\n };\n // The session setup path runs commands and installs credentials, so it\n // needs `executeCommand` (required by `ExecutableSandbox`) plus core's\n // optional `setEnv`, which stays optional here because the token-refresh\n // path checks for it and reports its absence.\n type SessionSandbox = ExecutableSandbox & { setEnv?: WorkspaceSandbox['setEnv'] };\n const githubTokenInjectors = new Map<string, GithubTokenRegistration>();\n const githubTokenReconciliations = new Map<string, Promise<void>>();\n // Workspace identity cache: concurrent resolutions of the same session must\n // observe the same Workspace object even when no Mastra registry is wired\n // (the registry stays the source of truth when present).\n const constructedWorkspaces = new Map<string, Workspace>();\n // Review-skill visibility last used to populate each workspace's skill cache.\n const skillCacheReviewState = new Map<string, boolean>();\n const skillCacheRefreshes = new Map<string, Promise<void>>();\n // The review check of the latest request to resolve each workspace. The skill\n // source outlives the request that built it, so it must gate on this rather\n // than on the context it was constructed with.\n const skillReviewChecks = new Map<string, () => Promise<boolean>>();\n\n return async ({ requestContext, mastra, skillExtension }: DynamicWorkspaceContext) => {\n const ctx = requestContext.get('controller') as AgentControllerRequestContext<MastraCodeState> | undefined;\n const supervisorProjectId = parseSupervisorResourceId(ctx?.resourceId);\n if (supervisorProjectId) {\n const orgId = getFactoryAuthOrgId(getFactoryAuthUserFromContext(requestContext));\n const project = orgId && projects ? await projects.get({ orgId, id: supervisorProjectId }) : null;\n if (!project) throw new Error(`Factory supervisor ${supervisorProjectId} is not available to the current user`);\n return undefined;\n }\n const resolvedSession = ctx?.resourceId ? await resolveSourceControlSession(sourceControls, ctx.resourceId) : null;\n const session = resolvedSession?.session ?? null;\n const sourceControl = resolvedSession?.provider;\n\n if (!session || !sourceControl) {\n // No factory session, no workspace. Chat still works; workspace tools\n // are simply not registered. Host-cwd behavior is opt-in via a\n // LocalSandbox callback rooted wherever the deployer wants — the\n // resolver never hands out the server host's own filesystem.\n return undefined;\n }\n\n const user = getFactoryAuthUserFromContext(requestContext);\n const userId = getFactoryAuthUserId(user);\n // No identity at all is a server-side caller that forgot to seed one\n // (webhook, cron), not someone reaching for another user's session.\n if (!user?.organizationId || !userId) {\n throw new Error(`Factory session ${session.sessionId} was resolved without a caller identity`);\n }\n // Org-visible sessions open to any member of the owning organization;\n // only private sessions stay owner-only. Cross-org access never passes.\n if (user.organizationId !== session.orgId || (session.visibility === 'private' && userId !== session.userId)) {\n throw new Error(`Factory session ${session.sessionId} is not available to the current user`);\n }\n if (!sandboxConfig) {\n throw new Error('A sandbox callback is required to create a Factory session workspace');\n }\n const createSessionSandboxInstance = sandboxConfig;\n const githubProvider = sourceControl.github;\n\n const storage = sourceControl.storage;\n const projectRepository = await storage.projectRepositories.get({\n orgId: session.orgId,\n id: session.projectRepositoryId,\n });\n if (!projectRepository) throw new Error(`Repository link ${session.projectRepositoryId} was not found`);\n // The remaining reads only depend on the repository link — issue them in\n // parallel instead of paying four sequential storage round-trips.\n const [connection, repository] = await Promise.all([\n storage.connections.get({ orgId: session.orgId, id: projectRepository.connectionId }),\n storage.repositories.get({ orgId: session.orgId, id: projectRepository.repositoryId }),\n ]);\n if (!connection || !repository) throw new Error(`Repository link ${session.projectRepositoryId} is incomplete`);\n const installation = await storage.installations.get({ orgId: session.orgId, id: connection.installationId });\n if (!installation) {\n throw new Error(`${sourceControl.id} installation ${connection.installationId} was not found`);\n }\n const repoFullName = repository.slug;\n if (githubProvider)\n registerGithubRefreshTarget(requestContext, { orgId: session.orgId, repositoryId: repository.id });\n\n // Construct (or fetch) the session's memoized sandbox instance.\n // Construction is cheap and side-effect-free by the callback contract —\n // the VM is provisioned on `start()`, which only the materialization\n // pipeline calls. The workdir is never persisted or trusted from storage\n // or client input (the stale-workdir incident class came from reusing\n // `session.sandboxWorkdir` written under a different provider): local\n // sandboxes derive it at construction, remote sandboxes clone into the\n // VM's own home so it resolves lazily at first start.\n // `runSetupOn` references `runSessionSetup`, defined below — it is only\n // invoked during start, long after this closure fully initializes.\n const runSetupOn = (target: unknown, workdir: string, gate: SessionSetupGate) =>\n runSessionSetup(requireExec(target as WorkspaceSandbox), workdir, gate);\n const guardedSetup = createSessionSetupHook(\n runSetupOn,\n session.id,\n repoFullName,\n projectRepository.setupCommand ?? undefined,\n );\n // Composed start hook: marker-guarded repo setup, then per-start\n // credential install. It runs inside the provider's start lifecycle on\n // EVERY start (create or reconnect) — providers own lazy start\n // (`ensureRunning()` on first command) and dead-VM self-healing (E2B\n // `retryOnDead`, Platform status reset on destroy), so a replacement VM\n // re-enters this hook and heals itself with credentials at least as\n // fresh as the start that installed them.\n const setupHook: SandboxStartHook = async args => {\n // A session retired before its first start must not set anything up.\n if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) {\n throw retiredError();\n }\n let setupError: SetupCommandError | undefined;\n try {\n await guardedSetup(args);\n } catch (error) {\n if (!(error instanceof SetupCommandError)) throw error;\n setupError = error;\n }\n // Re-check after the (long) setup: a session retired mid-setup must not\n // register credentials for a workspace whose retirement teardown has\n // already run — the entry would leak forever. The VM itself is left to\n // the provider's idle timeout (accepted).\n if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) {\n throw retiredError();\n }\n const target: SessionSandbox = requireExec(args.sandbox);\n // Sandbox persistence plus the post-checkout skill rescan run on every\n // start (create or reconnect). The persisted sandbox id is read back on\n // resume to reattach; the workdir was resolved (and memoized on the\n // entry) by the guarded setup. The skill roots were reported empty by the\n // unmaterialized-source guard before the checkout existed, so rescan now.\n // The physical-id write is awaited and its failure propagates: a start\n // that completes before the id is durable lets a concurrent resume read a\n // stale id and provision a replacement VM.\n const publishStartSideEffects = async () => {\n await storage.sessions.setSandbox({\n id: session.id,\n // Persist the provider's PHYSICAL, reattachable VM id so resume can\n // reattach to the same VM. Providers with no separate physical id\n // (e.g. local) persist nothing: a logical id handed back as a\n // `sandboxId` hint is unknown to the provider and breaks resume.\n sandboxId: target.sandboxId ?? null,\n sandboxWorkdir: sessionEntry.workdir ?? '',\n });\n void constructedWorkspaces\n .get(workspaceId)\n ?.skills?.refresh()\n .catch(() => {});\n };\n const finishStart = async () => {\n await publishStartSideEffects();\n if (setupError) throw setupError;\n };\n if (!githubProvider) {\n await finishStart();\n return;\n }\n const existingRegistration = githubTokenInjectors.get(workspaceId);\n if (existingRegistration) {\n // Reconnect: re-point the current registration's injection target at\n // this (possibly provider-healed) sandbox and reinstall its\n // reconcile-owned credential. Do NOT mint a new registration, bump\n // authority, or re-register the constructing request context —\n // reconcileGithubToken owns role/generation and the active context's\n // injector, so replacing them here would reauthorize the stale\n // constructing context and reject the current one.\n existingRegistration.inject = freshToken => {\n if (!target.setEnv) {\n throw new Error('The active sandbox provider does not support runtime GitHub token refresh.');\n }\n target.setEnv(env => ({ ...env, GH_TOKEN: freshToken }));\n existingRegistration.ghToken = freshToken;\n };\n // Install through inject (not optional-chained setEnv) so a provider\n // that cannot accept the credential fails the reconnect here instead of\n // deferring the failure to a later token refresh.\n existingRegistration.inject(existingRegistration.ghToken);\n await finishStart();\n return;\n }\n // First start: resolve the credential and authorize every request that\n // resolved this pending workspace. The `gh` CLI needs a PAT when the org configured one\n // (installation tokens 403 on integration-restricted endpoints); git\n // clone/checkout keep using the minted installation token. Resolved per\n // start so the installed credential never outlives rotation.\n const patKind = await resolveGithubPatKind('default');\n const ghCliToken =\n (await getGithubPat(() => githubProvider.integrationStorage, session.orgId, patKind)) ??\n (await getRepositoryToken());\n target.setEnv?.(env => ({ ...env, GH_TOKEN: ghCliToken }));\n const tokenRegistration: GithubTokenRegistration = {\n inject: freshToken => {\n if (!target.setEnv) {\n throw new Error('The active sandbox provider does not support runtime GitHub token refresh.');\n }\n target.setEnv(env => ({ ...env, GH_TOKEN: freshToken }));\n tokenRegistration.ghToken = freshToken;\n },\n patKind,\n ghToken: ghCliToken,\n generation: 0,\n tokenReplacementPending: false,\n };\n githubTokenInjectors.set(workspaceId, tokenRegistration);\n registerGithubTokenContext(tokenRegistration);\n await finishStart();\n };\n const constructSessionEntry = () =>\n getSessionSandbox(session.id, repoFullName, () => {\n const sandbox = createSessionSandboxInstance({\n sessionId: session.id,\n // Physical VM id persisted from a prior start (undefined on first\n // start). Providers that reattach by physical id use it to resume the\n // original VM instead of provisioning a replacement.\n sandboxId: session.sandboxId ?? undefined,\n repoFullName,\n // Stored nullable; the context speaks `undefined` for absent.\n setupCommand: projectRepository.setupCommand ?? undefined,\n // Deferred call — only dereferenced when a provider needs the repo\n // outside the VM (template build time).\n getRepositoryAccess: () =>\n sourceControl.versionControl.getRepositoryAccess({\n orgId: session.orgId,\n repositoryId: repository.id,\n }),\n });\n // Attached inside the construction closure, so exactly once per\n // instance — `constructSessionEntry` runs on every open and would\n // stack a wrapper per call. Factory's setup runs first: a hook the\n // callback installed itself expects a prepared workspace.\n sandbox.setOnStart(previous => async args => {\n await timedPhase(`workspace.onStart(${args.outcome})`, async () => {\n await setupHook(args);\n });\n await previous?.(args);\n });\n // Only a freshly constructed instance starts eagerly; the start itself\n // waits for this resolver to finish because the start hook reads\n // bindings declared further down.\n startEagerly = eagerSandboxStart;\n return sandbox;\n });\n let startEagerly = false;\n const fireEagerStart = () => {\n if (!startEagerly) return;\n startEagerly = false;\n Promise.resolve()\n .then(() => sessionEntry.sandbox.start?.())\n .catch(error => {\n console.warn(`[factory] Eager sandbox start for session ${session.id} failed:`, error);\n });\n };\n const sessionEntry = constructSessionEntry();\n const workdir = sessionEntry.workdir;\n const isLocalSandbox = sessionEntry.sandbox.provider === 'local';\n // The SDK system prompt uses `state.projectPath` without falling back to\n // the server cwd. Pin the session workdir once known so the prompt and\n // workspace tools describe the same checkout. A remote workdir resolves at\n // the sandbox's first start, so the pin self-heals on the next resolution.\n if (ctx && workdir && ctx.getState()?.projectPath !== workdir) {\n await ctx.setState({ projectPath: workdir, projectName: repoFullName });\n }\n\n // Keyed by the session, not the request's thread: a workspace built for a\n // dispatcher kickoff has no live thread yet, but the session's binding\n // already says which role it is serving. When the request does carry a\n // thread, the binding must belong to it, so a stale thread reusing this\n // session never inherits another run's role.\n const findSessionBinding = async () => {\n const binding = await workItems!.findActiveRunBindingForSession({\n orgId: session.orgId,\n factoryProjectId: connection.factoryProjectId,\n sessionId: session.sessionId,\n });\n if (!binding || !ctx?.threadId) return binding;\n return binding.threadId === ctx.threadId && binding.resourceId === ctx.resourceId ? binding : null;\n };\n // Fails closed: without a readable active review binding, review skills stay hidden.\n const isReviewSession = async (): Promise<boolean> => {\n if (!workItems) return false;\n try {\n return isActiveReviewBinding(await findSessionBinding(), session.orgId);\n } catch {\n return false;\n }\n };\n const effectiveSkillExtension =\n skillExtension ??\n createFactorySkillExtension((): Promise<boolean> => (skillReviewChecks.get(workspaceId) ?? isReviewSession)());\n const extensionId = effectiveSkillExtension ? `-${effectiveSkillExtension.id}` : '';\n const workspaceId: string = `${WORKSPACE_ID_PREFIX}-${projectRepository.id}-${session.id}${extensionId}`;\n const workspaceGeneration = workspaceRegistry.generation(session.sessionId);\n if (!skillExtension) skillReviewChecks.set(workspaceId, isReviewSession);\n const configDir = DEFAULT_CONFIG_DIR;\n\n const getRepositoryAccess = () =>\n sourceControl.versionControl.getRepositoryAccess({\n orgId: session.orgId,\n repositoryId: repository.id,\n });\n const getRepositoryToken = async (): Promise<string> => {\n const token = (await getRepositoryAccess()).authorization?.token;\n if (!token) throw new Error('Repository access did not include a bearer token for the Factory session');\n return token;\n };\n const resolveGithubPatKind = async (fallback: GithubPatKind): Promise<GithubPatKind> => {\n if (!workItems) return 'default';\n try {\n return isActiveReviewBinding(await findSessionBinding(), session.orgId) ? 'reviewer' : 'default';\n } catch {\n // Preserve the installed role when binding storage is temporarily unavailable.\n return fallback;\n }\n };\n const githubTokenInjector = (registered: GithubTokenRegistration, generation: number) => (token: string) => {\n if (githubTokenInjectors.get(workspaceId) !== registered || registered.generation !== generation) {\n throw new Error('GitHub token refresh no longer matches the active Factory workspace role.');\n }\n registered.inject(token);\n };\n const registerGithubTokenContext = (registered: GithubTokenRegistration): void => {\n registerGithubTokenInjector(requestContext, githubTokenInjector(registered, registered.generation));\n registerGithubPatKind(requestContext, registered.patKind);\n };\n if (githubProvider) {\n const registered = githubTokenInjectors.get(workspaceId);\n if (registered) {\n registerGithubTokenContext(registered);\n } else {\n registerGithubTokenInjectorResolver(requestContext, () => {\n if (workspaceRegistry.generation(session.sessionId) !== workspaceGeneration) {\n throw new Error('GitHub token refresh no longer matches the active Factory workspace role.');\n }\n const active = githubTokenInjectors.get(workspaceId);\n if (!active) {\n throw new Error('GitHub token refresh requires an active Factory sandbox workspace.');\n }\n registerGithubPatKind(requestContext, active.patKind);\n return githubTokenInjector(active, 0);\n });\n }\n }\n const reconcileGithubToken = async (): Promise<void> => {\n if (!githubProvider) return;\n const previous = githubTokenReconciliations.get(workspaceId) ?? Promise.resolve();\n const reconciliation = previous\n .catch(() => {})\n .then(async () => {\n const registered = githubTokenInjectors.get(workspaceId);\n if (!registered) return;\n\n const previousPatKind = registered.patKind;\n const patKind = await resolveGithubPatKind(previousPatKind);\n if (githubTokenInjectors.get(workspaceId) !== registered) return;\n\n if (patKind !== previousPatKind) {\n registered.patKind = patKind;\n registered.generation += 1;\n }\n if (patKind === 'reviewer') registered.tokenReplacementPending = false;\n if (previousPatKind === 'reviewer' && patKind === 'default') {\n // Invalidate reviewer refresh contexts before replacement I/O so\n // they cannot restore reviewer credentials after a failed downgrade.\n registered.tokenReplacementPending = true;\n }\n\n let token = await getGithubPat(() => githubProvider.integrationStorage, session.orgId, patKind);\n if (!token && registered.tokenReplacementPending) token = await getRepositoryToken();\n if (githubTokenInjectors.get(workspaceId) !== registered) return;\n\n if (token && token !== registered.ghToken) {\n try {\n registered.inject(token);\n } catch (error) {\n if (registered.tokenReplacementPending) throw error;\n // Same-role rotations and reviewer upgrades remain best-effort.\n }\n }\n if (token && token === registered.ghToken) registered.tokenReplacementPending = false;\n registerGithubTokenContext(registered);\n });\n githubTokenReconciliations.set(workspaceId, reconciliation);\n try {\n await reconciliation;\n } finally {\n if (githubTokenReconciliations.get(workspaceId) === reconciliation) {\n githubTokenReconciliations.delete(workspaceId);\n }\n }\n };\n const reconcileRegisteredWorkspace = async (workspace: Workspace): Promise<Workspace> => {\n const registered = githubTokenInjectors.get(workspaceId);\n try {\n await reconcileGithubToken();\n } catch (error) {\n if (registered?.tokenReplacementPending && githubTokenInjectors.get(workspaceId) === registered) {\n // The role generation already invalidated reviewer refresh contexts.\n // Keep the pending registration so failed eviction cannot make a\n // still-live reviewer workspace look safe on the next reuse.\n let evicted = false;\n try {\n evicted = (await mastra?.removeWorkspace?.(workspaceId)) === true;\n } catch {\n // Preserve the credential-replacement error and retry on the next reuse.\n }\n try {\n await workspace.destroy();\n evicted = true;\n } catch {\n // The pending registration keeps the workspace quarantined if cleanup also fails.\n }\n if (evicted && githubTokenInjectors.get(workspaceId) === registered) {\n githubTokenInjectors.delete(workspaceId);\n constructedWorkspaces.delete(workspaceId);\n skillCacheReviewState.delete(workspaceId);\n }\n }\n throw error;\n }\n if (registered && githubTokenInjectors.get(workspaceId) !== registered) {\n throw new Error('Factory workspace GitHub credential registration is no longer active.');\n }\n return workspace;\n };\n\n let existing: Workspace | undefined;\n try {\n existing = mastra?.getWorkspaceById(workspaceId) as Workspace | undefined;\n } catch {\n // Not registered yet.\n existing = undefined;\n }\n existing ??= constructedWorkspaces.get(workspaceId);\n if (existing) {\n existing.setToolsConfig(MASTRACODE_WORKSPACE_TOOLS);\n // The skill cache does not recheck the source on get(), so rescan when the\n // session's role flips; otherwise a cached review skill outlives the review binding.\n if (!skillExtension) {\n // An outside rescan makes the recorded role untrustworthy. Wait for it to\n // settle first, so the refresh below starts a scan for the current role\n // rather than joining one started for the previous role.\n const consumeOutsideRescan = async () => {\n const outside = existing.skills && rescannedOutsideResolver.get(existing.skills);\n if (!outside) return false;\n await outside;\n if (rescannedOutsideResolver.get(existing.skills!) === outside)\n rescannedOutsideResolver.delete(existing.skills!);\n skillCacheReviewState.delete(workspaceId);\n return true;\n };\n await consumeOutsideRescan();\n let isReview = await isReviewSession();\n // Concurrent reuses share one refresh, and the state is recorded only\n // once the rescan succeeds. Wait out any in-flight rescan (it may have\n // been started for the opposite role) and re-read the role afterwards.\n for (;;) {\n const inFlight = skillCacheRefreshes.get(workspaceId);\n if (inFlight) {\n await inFlight;\n isReview = await isReviewSession();\n continue;\n }\n if (await consumeOutsideRescan()) {\n isReview = await isReviewSession();\n continue;\n }\n if (skillCacheReviewState.get(workspaceId) === isReview) break;\n const target = isReview;\n const pending = (async () => {\n await existing.skills?.refresh();\n skillCacheReviewState.set(workspaceId, target);\n })().finally(() => skillCacheRefreshes.delete(workspaceId));\n skillCacheRefreshes.set(workspaceId, pending);\n await pending;\n isReview = await isReviewSession();\n }\n }\n // A materialization kicked off by another caller may still be running.\n // Deliberately do NOT wait for it: a metadata-only resolution (thread\n // list, messages, activity) must not block on the clone/setup that lazy\n // materialization exists to avoid. Token reconciliation below no-ops\n // until the leader registers the injector, and the next reuse after\n // materialization completes reconciles against the live sandbox.\n return reconcileRegisteredWorkspace(existing);\n }\n\n const retiredError = () =>\n new Error(`Factory session ${session.sessionId} was retired during workspace materialization`);\n\n // The session's setup work: materialize the repo (disk-truth idempotent),\n // check out the session branch, run the configured setup command. Minted\n // tokens are fetched inside the run so a replacement VM healed mid-session\n // gets fresh credentials, not ones captured at workspace construction.\n const runSessionSetup = async (target: SessionSandbox, workdir: string, gate: SessionSetupGate): Promise<void> => {\n const access = await getRepositoryAccess();\n const token = access.authorization?.token;\n if (!token) throw new Error('Repository access did not include a bearer token for the Factory session');\n // GitHub setup commands may shell out to gh/HTTPS. Preserve its\n // role-aware PAT behavior without applying GitHub credentials to other\n // source-control providers.\n if (githubProvider) {\n const setupPatKind = await resolveGithubPatKind('default');\n const setupGhToken =\n (await getGithubPat(() => githubProvider.integrationStorage, session.orgId, setupPatKind)) ?? token;\n target.setEnv?.(env => ({ ...env, GH_TOKEN: setupGhToken }));\n }\n await materializeRepo({\n row: { id: session.id, sandboxWorkdir: workdir, materializedAt: session.materializedAt },\n repoInfo: {\n repoFullName,\n defaultBranch: repository.defaultBranch,\n cloneUrl: access.cloneUrl,\n authUsername: access.authorization?.username,\n },\n sandbox: target,\n token,\n storage: storage.sessions,\n });\n await checkoutSessionBranch(target, workdir, {\n branch: session.branch,\n baseBranch: session.baseBranch || projectRepository.branch || repository.defaultBranch,\n token,\n repoFullName,\n cloneUrl: access.cloneUrl,\n authUsername: access.authorization?.username,\n pullRequestNumber: pullRequestNumberFromBranch(session.branch),\n mergeRequestNumber: sourceControl.id === 'gitlab' ? mergeRequestNumberFromBranch(session.branch) : undefined,\n });\n if (projectRepository.setupCommand && !gate.setupDone) {\n // A setup command that already failed this session is skipped rather\n // than failing every start: the first failure surfaced loudly in the\n // tool result that triggered it, and a permanently failing onStart\n // would wedge the session — the agent could never get a shell to fix\n // the problem. Clone and checkout above still ran, so the tree is\n // real; the agent (or an edited setup command) takes it from here.\n if (hasFailedSetupCommand(session.id, projectRepository.setupCommand)) {\n console.warn('[Mastra Factory] Skipping setup command that already failed this session', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n });\n return;\n }\n try {\n await timedPhase('workspace.setup', () => runSetupCommand(target, workdir, projectRepository.setupCommand!));\n await gate.markSetupDone();\n } catch (setupError) {\n if (projectRepository.teardownCommand) {\n try {\n await runTeardownCommand(target, workdir, projectRepository.teardownCommand, {\n timeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,\n });\n } catch (teardownError) {\n console.warn('[Mastra Factory] Worktree teardown after setup failure failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: teardownError instanceof Error ? teardownError.message.slice(-2000) : String(teardownError),\n });\n }\n }\n if (setupError instanceof SetupCommandError) {\n // The command ran and exited non-zero — a config problem, not an\n // infra one. Remember it so the next start recovers, and tell the\n // agent what happens next. Infra failures (transport, clone)\n // rethrow untouched and retry in full.\n recordFailedSetupCommand(session.id, projectRepository.setupCommand);\n throw new SetupCommandError(\n `${setupError.message}. The sandbox stays usable: this setup command is skipped for the rest of the session — retry your command, then fix the setup command in the repository settings or run it manually.`,\n setupError.code,\n );\n }\n throw setupError;\n }\n }\n };\n // The session's real sandbox goes straight onto the Workspace. Providers\n // own lazy start (`ensureRunning()` inside the first command/process op)\n // and dead-VM self-healing, and the composed `onStart` hook runs the repo\n // setup + credential install inside that lifecycle. Metadata-only\n // resolutions (thread-list polling) construct but never start.\n const sessionSandbox: SessionSandbox = requireExec(sessionEntry.sandbox);\n\n const filesystem = new SandboxFilesystem({\n id: `sandbox-fs:${workspaceId}`,\n sandbox: sessionSandbox,\n // Lazy: a remote workdir is only knowable once a VM runs. The first\n // file operation resolves it (starting the VM — which materializes the\n // repo via the onStart hook — when needed) and memoizes it.\n workdir: () => resolveSessionWorkdir(session.id, sessionEntry.sandbox, repoFullName),\n });\n const projectSkillPaths = [path.join(configDir, 'skills'), '.claude/skills', '.agents/skills'];\n const guardedSkillFallback = new UnmaterializedAwareSkillSource(\n filesystem,\n () => sessionEntry.sandbox.status === 'running',\n );\n const skillPaths = [...(effectiveSkillExtension?.paths ?? []), ...projectSkillPaths];\n const workspace = new Workspace({\n id: workspaceId,\n name: 'Mastra Code Factory Session Workspace',\n filesystem,\n sandbox: sessionSandbox as unknown as ConstructorParameters<typeof Workspace>[0]['sandbox'],\n tools: MASTRACODE_WORKSPACE_TOOLS,\n skills: skillPaths,\n // Project skill roots live in the sandbox checkout; guard them so skill\n // discovery before materialization (e.g. kickoff skill resolution in the\n // start coordinator) never forces sandbox provisioning.\n skillSource:\n effectiveSkillExtension?.createSource(guardedSkillFallback, projectSkillPaths) ?? guardedSkillFallback,\n });\n // Register with the Mastra instance so sync HTTP handlers that resolve\n // the workspace via `mastra.getWorkspaceById(id)` (file tree, permissions\n // probe, MCP/tool routes) find it instead of throwing\n // `MASTRA_GET_WORKSPACE_BY_ID_NOT_FOUND`. `addWorkspace` is idempotent on\n // key collision, so concurrent first resolutions stay race-safe (start\n // itself is coalesced by the sandbox base class + the session memo).\n mastra?.addWorkspace(workspace, workspaceId, { source: 'mastra' });\n // Cache synchronously with construction: the `await` below is a suspension\n // point, and a concurrent resolution for the same session must observe this\n // workspace rather than build a second one.\n constructedWorkspaces.set(workspaceId, workspace);\n // Retirement is registered against the workspace itself rather than the\n // sandbox: construction is eager while the VM start is lazy, so a session\n // retired before its first tool call still has a workspace (and possibly a\n // token injector) that must be torn down.\n const registered = await workspaceRegistry.register(\n session.sessionId,\n workspaceId,\n workspaceGeneration,\n async () => {\n githubTokenInjectors.delete(workspaceId);\n constructedWorkspaces.delete(workspaceId);\n skillCacheReviewState.delete(workspaceId);\n skillReviewChecks.delete(workspaceId);\n // Retirement drops the memoized session sandbox so a later re-open\n // constructs (and the provider resolves) fresh instead of reusing an\n // instance whose VM the retirement path may stop or destroy.\n evictSessionSandbox(session.id);\n await mastra?.removeWorkspace?.(workspaceId);\n },\n );\n if (!registered) {\n throw new Error(`Factory session ${session.sessionId} was retired during workspace materialization`);\n }\n\n fireEagerStart();\n return workspace;\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAuDA,MAAM,sBAAsB;AAC5B,MAAM,kBAAkB,QAAQ,cAAc,OAAO,KAAK,GAAG,CAAC;AAC9D,MAAM,2BAA2B,KAAK,iBAAiB,gBAAgB;AACvE,MAAa,8BACX,CAGE,0BAGA,KAAK,iBAAiB,MAAM,gBAAgB,CAC9C,CAAC,CAAC,KAAK,UAAU,KAAK;;;;;;;;AASxB,SAAgB,8BAA8B,MAAc,QAAQ,IAAI,GAAuB;CAM7F,OAAO;EAJL,KAAK,KAAK,OAAO,UAAU,UAAU,gBAAgB;EACrD,KAAK,KAAK,UAAU,gBAAgB;EACpC,KAAK,KAAK,gBAAgB;CAEZ,CAAC,CAAC,MAChB,cAAa,KAAK,UAAU,SAAS,MAAM,KAAK,UAAU,2BAA2B,KAAK,WAAW,SAAS,CAChH;AACF;AACA,MAAM,uBAAuB,KAAK,QAAQ,KAAK,MAAM,QAAQ,IAAI,CAAC,CAAC,CAAC,MAAM,+BAA+B;AACzG,MAAa,sCAAsB,IAAI,IAAI;CACzC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;AAGD,MAAa,6CAA6B,IAAI,IAAI;CAChD;CACA;CACA;CACA;AACF,CAAC;;;;;;AAOD,MAAM,2CAA2B,IAAI,QAA+B;;;;;;AAOpE,eAAsB,oBAAoB,QAAyD;CAGjG,MAAM,UAAU,OAAO,QAAQ;CAC/B,yBAAyB,IACvB,QACA,QAAQ,YAAY,CAAC,CAAC,CACxB;CACA,MAAM;AACR;AAEA,IAAa,qBAAb,MAAuD;CAM1C;CAIA;CATX,iBAA0B,IAAI,iBAAiB,EAAE,UAAU,4BAA4B,CAAC;CACxF;CACA;CAEA,YACE,UACA,oBACA,kBAAsC,8BAA8B,GAEpE,wBAAyD,QAAQ,QAAQ,IAAI,GAC7E;EALS,KAAA,WAAA;EAIA,KAAA,kBAAA;EAET,KAAKC,eAAe,kBAAkB,IAAI,iBAAiB,EAAE,UAAU,gBAAgB,CAAC,IAAI,KAAA;EAC5F,KAAKC,sBAAsB,IAAI,IAAI,mBAAmB,KAAI,cAAa,KAAK,UAAU,SAAS,CAAC,CAAC;CACnG;CAEA,eAAe,WAA4B;EACzC,MAAM,aAAa,KAAK,UAAU,SAAS;EAC3C,OAAO,eAAe,wBAAwB,WAAW,WAAW,GAAG,uBAAuB,KAAK,KAAK;CAC1G;CAEA,aAAa,WAA2B;EACtC,OAAO,KAAK,SAAS,sBAAsB,KAAK,UAAU,SAAS,CAAC;CACtE;;CAGA,MAAMC,oBAAoB,cAAwC;EAChE,MAAM,YAAY,aAAa,MAAM,KAAK,GAAG,CAAC,CAAC;EAC/C,IAAI,CAAC,aAAa,CAAC,2BAA2B,IAAI,SAAS,GAAG,OAAO;EACrE,OAAO,CAAE,MAAM,KAAK,gBAAgB;CACtC;;CAGA,MAAMC,UAAU,cAAiD;EAC/D,IAAI,KAAKH,gBAAiB,MAAM,KAAKA,aAAa,OAAO,YAAY,GAAI,OAAO,KAAKA;EACrF,OAAO,KAAKD;CACd;CAEA,MAAM,OAAO,WAAqC;EAChD,IAAI,CAAC,KAAKK,eAAe,SAAS,GAAG,OAAO,KAAK,SAAS,OAAO,SAAS;EAC1E,MAAM,WAAW,KAAKC,aAAa,SAAS;EAC5C,IAAI,MAAM,KAAKH,oBAAoB,QAAQ,GAAG,OAAO;EACrD,IAAI,KAAKF,gBAAiB,MAAM,KAAKA,aAAa,OAAO,QAAQ,GAAI,OAAO;EAC5E,OAAO,KAAKD,eAAe,OAAO,QAAQ;CAC5C;CAEA,MAAM,KAAK,WAA6C;EACtD,IAAI,CAAC,KAAKK,eAAe,SAAS,GAAG,OAAO,KAAK,SAAS,KAAK,SAAS;EACxE,MAAM,WAAW,KAAKC,aAAa,SAAS;EAC5C,IAAI,MAAM,KAAKH,oBAAoB,QAAQ,GAAG,MAAM,kBAAkB,SAAS;EAC/E,QAAQ,MAAM,KAAKC,UAAU,QAAQ,EAAA,CAAG,KAAK,QAAQ;CACvD;CAEA,MAAM,SAAS,WAA6C;EAC1D,IAAI,CAAC,KAAKC,eAAe,SAAS,GAAG,OAAO,KAAK,SAAS,SAAS,SAAS;EAC5E,MAAM,WAAW,KAAKC,aAAa,SAAS;EAC5C,IAAI,MAAM,KAAKH,oBAAoB,QAAQ,GAAG,MAAM,kBAAkB,SAAS;EAC/E,QAAQ,MAAM,KAAKC,UAAU,QAAQ,EAAA,CAAG,SAAS,QAAQ;CAC3D;CAEA,MAAM,QAAQ,WAAgD;EAC5D,IAAI,KAAKC,eAAe,SAAS,GAAG;GAClC,MAAM,WAAW,KAAKC,aAAa,SAAS;GAC5C,IAAI,MAAM,KAAKH,oBAAoB,QAAQ,GAAG,MAAM,kBAAkB,SAAS;GAC/E,MAAM,CAAC,eAAe,eAAe,MAAM,QAAQ,IAAI,CACrD,KAAKH,eAAe,OAAO,QAAQ,GACnC,KAAKC,cAAc,OAAO,QAAQ,KAAK,QAAQ,QAAQ,KAAK,CAC9D,CAAC;GACD,IAAI,CAAC,iBAAiB,CAAC,aAAa,MAAM,kBAAkB,SAAS;GACrE,MAAM,CAAC,gBAAgB,gBAAgB,MAAM,QAAQ,IAAI,CACvD,gBAAgB,KAAKD,eAAe,QAAQ,QAAQ,IAAI,CAAC,GACzD,cAAc,KAAKC,aAAc,QAAQ,QAAQ,IAAI,CAAC,CACxD,CAAC;GACD,MAAM,yBAAS,IAAI,IAA8B;GACjD,KAAK,MAAM,SAAS,gBAAgB,OAAO,IAAI,MAAM,MAAM,KAAK;GAEhE,KAAK,MAAM,SAAS,cAAc,OAAO,IAAI,MAAM,MAAM,KAAK;GAC9D,MAAM,UAAU,CAAC,GAAG,OAAO,OAAO,CAAC;GACnC,IAAI,aAAa,MAAO,MAAM,KAAK,gBAAgB,GAAI,OAAO;GAC9D,OAAO,QAAQ,QAAO,UAAS,CAAC,2BAA2B,IAAI,MAAM,IAAI,CAAC;EAC5E;EACA,MAAM,UAAU,MAAM,KAAK,SAAS,QAAQ,SAAS;EACrD,IAAI,KAAKC,oBAAoB,IAAI,KAAK,UAAU,SAAS,CAAC,GAAG;GAC3D,MAAM,WAAW,MAAM,KAAKK,eAAe;GAC3C,OAAO,QAAQ,QAAO,UAAS,CAAC,SAAS,IAAI,MAAM,IAAI,CAAC;EAC1D;EACA,OAAO;CACT;;;;;;CAOA,MAAMA,iBAAuC;EAC3C,MAAM,WAAW,IAAI,IAAI,mBAAmB;EAC5C,IAAI,CAAC,KAAKN,cAAc,OAAO;EAC/B,IAAI;GACF,KAAK,MAAM,SAAS,MAAM,KAAKA,aAAa,QAAQ,EAAE,GACpD,IAAI,MAAM,SAAS,aAAa,SAAS,IAAI,MAAM,IAAI;EAE3D,QAAQ,CAER;EACA,OAAO;CACT;CAEA,SAAS,WAAoC;EAC3C,IAAI,KAAKI,eAAe,SAAS,GAAG,OAAO,QAAQ,QAAQ,KAAK,UAAU,SAAS,CAAC;EACpF,OAAO,KAAK,SAAS,WAAW,KAAK,SAAS,SAAS,SAAS,IAAI,QAAQ,QAAQ,SAAS;CAC/F;AACF;;AAGA,SAAS,kBAAkB,WAA0B;CACnD,MAAM,wBAAQ,IAAI,MAAM,uCAAuC,UAAU,EAAE;CAC3E,MAAM,OAAO;CACb,OAAO;AACT;;;;;;;;;;;AAYA,IAAM,iCAAN,MAA4D;CAE/C;CACA;CAFX,YACE,UACA,gBACA;EAFS,KAAA,WAAA;EACA,KAAA,iBAAA;CACR;CAEH,MAAM,OAAO,WAAqC;EAChD,OAAO,KAAK,eAAe,IAAI,KAAK,SAAS,OAAO,SAAS,IAAI;CACnE;CAEA,MAAM,KAAK,WAA6C;EACtD,IAAI,CAAC,KAAK,eAAe,GAAG,MAAM,kBAAkB,SAAS;EAC7D,OAAO,KAAK,SAAS,KAAK,SAAS;CACrC;CAEA,MAAM,SAAS,WAA6C;EAC1D,IAAI,CAAC,KAAK,eAAe,GAAG,MAAM,kBAAkB,SAAS;EAC7D,OAAO,KAAK,SAAS,SAAS,SAAS;CACzC;CAEA,MAAM,QAAQ,WAAgD;EAC5D,OAAO,KAAK,eAAe,IAAI,KAAK,SAAS,QAAQ,SAAS,IAAI,CAAC;CACrE;CAEA,SAAS,WAAoC;EAC3C,IAAI,CAAC,KAAK,eAAe,GAAG,OAAO,QAAQ,QAAQ,SAAS;EAC5D,OAAO,KAAK,SAAS,WAAW,KAAK,SAAS,SAAS,SAAS,IAAI,QAAQ,QAAQ,SAAS;CAC/F;AACF;AAEA,SAAS,4BAA4B,iBAAkE;CACrG,OAAO;EACL,IAAI;EACJ,OAAO,CAAC,oBAAoB;EAC5B,eAAe,UAAU,uBACvB,IAAI,mBAAmB,UAAU,oBAAoB,8BAA8B,GAAG,eAAe;CACzG;AACF;AAEA,SAAS,sBACP,YACA,OACS;CACT,OAAO,YAAY,SAAS,YAAY,WAAW,WAAW,YAAY,CAAC,CAAC,SAAS,WAAW,UAAU;AAC5G;;AA4CA,IAAa,2BAAb,MAAsC;CACpC,2BAAoB,IAAI,IAA8C;CACtE,+BAAwB,IAAI,IAAoB;CAEhD,WAAW,WAA2B;EACpC,OAAO,KAAKI,aAAa,IAAI,SAAS,KAAK;CAC7C;CAEA,MAAM,SACJ,WACA,aACA,YACA,YACkB;EAClB,IAAI,eAAe,KAAK,WAAW,SAAS,GAAG;GAC7C,MAAM,WAAW;GACjB,OAAO;EACT;EACA,MAAM,UAAU,KAAKD,SAAS,IAAI,SAAS,qBAAK,IAAI,IAAiC;EACrF,QAAQ,IAAI,aAAa,UAAU;EACnC,KAAKA,SAAS,IAAI,WAAW,OAAO;EACpC,OAAO;CACT;CAEA,MAAM,kBAAkB,WAAkC;EACxD,KAAKC,aAAa,IAAI,WAAW,KAAK,WAAW,SAAS,IAAI,CAAC;EAC/D,MAAM,UAAU,KAAKD,SAAS,IAAI,SAAS;EAC3C,IAAI,CAAC,SAAS;EACd,KAAKA,SAAS,OAAO,SAAS;EAE9B,MAAM,WAAU,MADM,QAAQ,WAAW,CAAC,GAAG,QAAQ,OAAO,CAAC,CAAC,CAAC,KAAI,eAAc,WAAW,CAAC,CAAC,EAAA,CACtE,MAAK,WAAU,OAAO,WAAW,UAAU;EACnE,IAAI,SAAS,WAAW,YAAY,MAAM,QAAQ;CACpD;AACF;AAEA,eAAe,4BACb,WACA,WAC6F;CAC7F,MAAM,WACJ,MAAM,QAAQ,IACZ,UAAU,IAAI,OAAM,cAAa;EAC/B;EACA,SAAS,MAAM,SAAS,QAAQ,SAAS,eAAe,SAAS;CACnE,EAAE,CACJ,EAAA,CACA,QACC,UACC,MAAM,YAAY,IACtB;CACA,IAAI,QAAQ,SAAS,GACnB,MAAM,IAAI,MAAM,mBAAmB,UAAU,8CAA8C;CAE7F,OAAO,QAAQ,MAAM;AACvB;AAEA,SAAgB,uBAAuB,UAAyC,CAAC,GAAG;CAClF,MAAM,EAAE,SAAS,eAAe,QAAQ,UAAU,cAAc;CAChE,MAAM,iBACJ,QAAQ,mBACP,SACG,CAAC;EAAE,IAAI,OAAO;EAAI,gBAAgB,OAAO;EAAgB,SAAS,OAAO;EAAsB;CAAO,CAAC,IACvG,CAAC;CACP,MAAM,oBAAoB,QAAQ,iBAAiB;CACnD,MAAM,oBAAoB,QAAQ,qBAAqB,IAAI,yBAAyB;CAapF,MAAM,uCAAuB,IAAI,IAAqC;CACtE,MAAM,6CAA6B,IAAI,IAA2B;CAIlE,MAAM,wCAAwB,IAAI,IAAuB;CAEzD,MAAM,wCAAwB,IAAI,IAAqB;CACvD,MAAM,sCAAsB,IAAI,IAA2B;CAI3D,MAAM,oCAAoB,IAAI,IAAoC;CAElE,OAAO,OAAO,EAAE,gBAAgB,QAAQ,qBAA8C;EACpF,MAAM,MAAM,eAAe,IAAI,YAAY;EAC3C,MAAM,sBAAsB,0BAA0B,KAAK,UAAU;EACrE,IAAI,qBAAqB;GACvB,MAAM,QAAQ,oBAAoB,8BAA8B,cAAc,CAAC;GAE/E,IAAI,EADY,SAAS,WAAW,MAAM,SAAS,IAAI;IAAE;IAAO,IAAI;GAAoB,CAAC,IAAI,OAC/E,MAAM,IAAI,MAAM,sBAAsB,oBAAoB,sCAAsC;GAC9G;EACF;EACA,MAAM,kBAAkB,KAAK,aAAa,MAAM,4BAA4B,gBAAgB,IAAI,UAAU,IAAI;EAC9G,MAAM,UAAU,iBAAiB,WAAW;EAC5C,MAAM,gBAAgB,iBAAiB;EAEvC,IAAI,CAAC,WAAW,CAAC,eAKf;EAGF,MAAM,OAAO,8BAA8B,cAAc;EACzD,MAAM,SAAS,qBAAqB,IAAI;EAGxC,IAAI,CAAC,MAAM,kBAAkB,CAAC,QAC5B,MAAM,IAAI,MAAM,mBAAmB,QAAQ,UAAU,wCAAwC;EAI/F,IAAI,KAAK,mBAAmB,QAAQ,SAAU,QAAQ,eAAe,aAAa,WAAW,QAAQ,QACnG,MAAM,IAAI,MAAM,mBAAmB,QAAQ,UAAU,sCAAsC;EAE7F,IAAI,CAAC,eACH,MAAM,IAAI,MAAM,sEAAsE;EAExF,MAAM,+BAA+B;EACrC,MAAM,iBAAiB,cAAc;EAErC,MAAM,UAAU,cAAc;EAC9B,MAAM,oBAAoB,MAAM,QAAQ,oBAAoB,IAAI;GAC9D,OAAO,QAAQ;GACf,IAAI,QAAQ;EACd,CAAC;EACD,IAAI,CAAC,mBAAmB,MAAM,IAAI,MAAM,mBAAmB,QAAQ,oBAAoB,eAAe;EAGtG,MAAM,CAAC,YAAY,cAAc,MAAM,QAAQ,IAAI,CACjD,QAAQ,YAAY,IAAI;GAAE,OAAO,QAAQ;GAAO,IAAI,kBAAkB;EAAa,CAAC,GACpF,QAAQ,aAAa,IAAI;GAAE,OAAO,QAAQ;GAAO,IAAI,kBAAkB;EAAa,CAAC,CACvF,CAAC;EACD,IAAI,CAAC,cAAc,CAAC,YAAY,MAAM,IAAI,MAAM,mBAAmB,QAAQ,oBAAoB,eAAe;EAE9G,IAAI,CAAC,MADsB,QAAQ,cAAc,IAAI;GAAE,OAAO,QAAQ;GAAO,IAAI,WAAW;EAAe,CAAC,GAE1G,MAAM,IAAI,MAAM,GAAG,cAAc,GAAG,gBAAgB,WAAW,eAAe,eAAe;EAE/F,MAAM,eAAe,WAAW;EAChC,IAAI,gBACF,4BAA4B,gBAAgB;GAAE,OAAO,QAAQ;GAAO,cAAc,WAAW;EAAG,CAAC;EAYnG,MAAM,cAAc,QAAiB,SAAiB,SACpD,gBAAgB,YAAY,MAA0B,GAAG,SAAS,IAAI;EACxE,MAAM,eAAe,uBACnB,YACA,QAAQ,IACR,cACA,kBAAkB,gBAAgB,KAAA,CACpC;EAQA,MAAM,YAA8B,OAAM,SAAQ;GAEhD,IAAI,kBAAkB,WAAW,QAAQ,SAAS,MAAM,qBACtD,MAAM,aAAa;GAErB,IAAI;GACJ,IAAI;IACF,MAAM,aAAa,IAAI;GACzB,SAAS,OAAO;IACd,IAAI,EAAE,iBAAiB,oBAAoB,MAAM;IACjD,aAAa;GACf;GAKA,IAAI,kBAAkB,WAAW,QAAQ,SAAS,MAAM,qBACtD,MAAM,aAAa;GAErB,MAAM,SAAyB,YAAY,KAAK,OAAO;GASvD,MAAM,0BAA0B,YAAY;IAC1C,MAAM,QAAQ,SAAS,WAAW;KAChC,IAAI,QAAQ;KAKZ,WAAW,OAAO,aAAa;KAC/B,gBAAgB,aAAa,WAAW;IAC1C,CAAC;IACD,sBACG,IAAI,WAAW,CAAC,EACf,QAAQ,QAAQ,CAAC,CAClB,YAAY,CAAC,CAAC;GACnB;GACA,MAAM,cAAc,YAAY;IAC9B,MAAM,wBAAwB;IAC9B,IAAI,YAAY,MAAM;GACxB;GACA,IAAI,CAAC,gBAAgB;IACnB,MAAM,YAAY;IAClB;GACF;GACA,MAAM,uBAAuB,qBAAqB,IAAI,WAAW;GACjE,IAAI,sBAAsB;IAQxB,qBAAqB,UAAS,eAAc;KAC1C,IAAI,CAAC,OAAO,QACV,MAAM,IAAI,MAAM,4EAA4E;KAE9F,OAAO,QAAO,SAAQ;MAAE,GAAG;MAAK,UAAU;KAAW,EAAE;KACvD,qBAAqB,UAAU;IACjC;IAIA,qBAAqB,OAAO,qBAAqB,OAAO;IACxD,MAAM,YAAY;IAClB;GACF;GAMA,MAAM,UAAU,MAAM,qBAAqB,SAAS;GACpD,MAAM,aACH,MAAM,mBAAmB,eAAe,oBAAoB,QAAQ,OAAO,OAAO,KAClF,MAAM,mBAAmB;GAC5B,OAAO,UAAS,SAAQ;IAAE,GAAG;IAAK,UAAU;GAAW,EAAE;GACzD,MAAM,oBAA6C;IACjD,SAAQ,eAAc;KACpB,IAAI,CAAC,OAAO,QACV,MAAM,IAAI,MAAM,4EAA4E;KAE9F,OAAO,QAAO,SAAQ;MAAE,GAAG;MAAK,UAAU;KAAW,EAAE;KACvD,kBAAkB,UAAU;IAC9B;IACA;IACA,SAAS;IACT,YAAY;IACZ,yBAAyB;GAC3B;GACA,qBAAqB,IAAI,aAAa,iBAAiB;GACvD,2BAA2B,iBAAiB;GAC5C,MAAM,YAAY;EACpB;EACA,MAAM,8BACJ,kBAAkB,QAAQ,IAAI,oBAAoB;GAChD,MAAM,UAAU,6BAA6B;IAC3C,WAAW,QAAQ;IAInB,WAAW,QAAQ,aAAa,KAAA;IAChC;IAEA,cAAc,kBAAkB,gBAAgB,KAAA;IAGhD,2BACE,cAAc,eAAe,oBAAoB;KAC/C,OAAO,QAAQ;KACf,cAAc,WAAW;IAC3B,CAAC;GACL,CAAC;GAKD,QAAQ,YAAW,aAAY,OAAM,SAAQ;IAC3C,MAAM,WAAW,qBAAqB,KAAK,QAAQ,IAAI,YAAY;KACjE,MAAM,UAAU,IAAI;IACtB,CAAC;IACD,MAAM,WAAW,IAAI;GACvB,CAAC;GAID,eAAe;GACf,OAAO;EACT,CAAC;EACH,IAAI,eAAe;EACnB,MAAM,uBAAuB;GAC3B,IAAI,CAAC,cAAc;GACnB,eAAe;GACf,QAAQ,QAAQ,CAAC,CACd,WAAW,aAAa,QAAQ,QAAQ,CAAC,CAAC,CAC1C,OAAM,UAAS;IACd,QAAQ,KAAK,6CAA6C,QAAQ,GAAG,WAAW,KAAK;GACvF,CAAC;EACL;EACA,MAAM,eAAe,sBAAsB;EAC3C,MAAM,UAAU,aAAa;EACN,aAAa,QAAQ;EAK5C,IAAI,OAAO,WAAW,IAAI,SAAS,CAAC,EAAE,gBAAgB,SACpD,MAAM,IAAI,SAAS;GAAE,aAAa;GAAS,aAAa;EAAa,CAAC;EAQxE,MAAM,qBAAqB,YAAY;GACrC,MAAM,UAAU,MAAM,UAAW,+BAA+B;IAC9D,OAAO,QAAQ;IACf,kBAAkB,WAAW;IAC7B,WAAW,QAAQ;GACrB,CAAC;GACD,IAAI,CAAC,WAAW,CAAC,KAAK,UAAU,OAAO;GACvC,OAAO,QAAQ,aAAa,IAAI,YAAY,QAAQ,eAAe,IAAI,aAAa,UAAU;EAChG;EAEA,MAAM,kBAAkB,YAA8B;GACpD,IAAI,CAAC,WAAW,OAAO;GACvB,IAAI;IACF,OAAO,sBAAsB,MAAM,mBAAmB,GAAG,QAAQ,KAAK;GACxE,QAAQ;IACN,OAAO;GACT;EACF;EACA,MAAM,0BACJ,kBACA,mCAAqD,kBAAkB,IAAI,WAAW,KAAK,gBAAA,CAAiB,CAAC;EAC/G,MAAM,cAAc,0BAA0B,IAAI,wBAAwB,OAAO;EACjF,MAAM,cAAsB,GAAG,oBAAoB,GAAG,kBAAkB,GAAG,GAAG,QAAQ,KAAK;EAC3F,MAAM,sBAAsB,kBAAkB,WAAW,QAAQ,SAAS;EAC1E,IAAI,CAAC,gBAAgB,kBAAkB,IAAI,aAAa,eAAe;EACvE,MAAM,YAAY;EAElB,MAAM,4BACJ,cAAc,eAAe,oBAAoB;GAC/C,OAAO,QAAQ;GACf,cAAc,WAAW;EAC3B,CAAC;EACH,MAAM,qBAAqB,YAA6B;GACtD,MAAM,SAAS,MAAM,oBAAoB,EAAA,CAAG,eAAe;GAC3D,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,0EAA0E;GACtG,OAAO;EACT;EACA,MAAM,uBAAuB,OAAO,aAAoD;GACtF,IAAI,CAAC,WAAW,OAAO;GACvB,IAAI;IACF,OAAO,sBAAsB,MAAM,mBAAmB,GAAG,QAAQ,KAAK,IAAI,aAAa;GACzF,QAAQ;IAEN,OAAO;GACT;EACF;EACA,MAAM,uBAAuB,YAAqC,gBAAwB,UAAkB;GAC1G,IAAI,qBAAqB,IAAI,WAAW,MAAM,cAAc,WAAW,eAAe,YACpF,MAAM,IAAI,MAAM,2EAA2E;GAE7F,WAAW,OAAO,KAAK;EACzB;EACA,MAAM,8BAA8B,eAA8C;GAChF,4BAA4B,gBAAgB,oBAAoB,YAAY,WAAW,UAAU,CAAC;GAClG,sBAAsB,gBAAgB,WAAW,OAAO;EAC1D;EACA,IAAI,gBAAgB;GAClB,MAAM,aAAa,qBAAqB,IAAI,WAAW;GACvD,IAAI,YACF,2BAA2B,UAAU;QAErC,oCAAoC,sBAAsB;IACxD,IAAI,kBAAkB,WAAW,QAAQ,SAAS,MAAM,qBACtD,MAAM,IAAI,MAAM,2EAA2E;IAE7F,MAAM,SAAS,qBAAqB,IAAI,WAAW;IACnD,IAAI,CAAC,QACH,MAAM,IAAI,MAAM,oEAAoE;IAEtF,sBAAsB,gBAAgB,OAAO,OAAO;IACpD,OAAO,oBAAoB,QAAQ,CAAC;GACtC,CAAC;EAEL;EACA,MAAM,uBAAuB,YAA2B;GACtD,IAAI,CAAC,gBAAgB;GAErB,MAAM,kBADW,2BAA2B,IAAI,WAAW,KAAK,QAAQ,QAAQ,EAAA,CAE7E,YAAY,CAAC,CAAC,CAAC,CACf,KAAK,YAAY;IAChB,MAAM,aAAa,qBAAqB,IAAI,WAAW;IACvD,IAAI,CAAC,YAAY;IAEjB,MAAM,kBAAkB,WAAW;IACnC,MAAM,UAAU,MAAM,qBAAqB,eAAe;IAC1D,IAAI,qBAAqB,IAAI,WAAW,MAAM,YAAY;IAE1D,IAAI,YAAY,iBAAiB;KAC/B,WAAW,UAAU;KACrB,WAAW,cAAc;IAC3B;IACA,IAAI,YAAY,YAAY,WAAW,0BAA0B;IACjE,IAAI,oBAAoB,cAAc,YAAY,WAGhD,WAAW,0BAA0B;IAGvC,IAAI,QAAQ,MAAM,mBAAmB,eAAe,oBAAoB,QAAQ,OAAO,OAAO;IAC9F,IAAI,CAAC,SAAS,WAAW,yBAAyB,QAAQ,MAAM,mBAAmB;IACnF,IAAI,qBAAqB,IAAI,WAAW,MAAM,YAAY;IAE1D,IAAI,SAAS,UAAU,WAAW,SAChC,IAAI;KACF,WAAW,OAAO,KAAK;IACzB,SAAS,OAAO;KACd,IAAI,WAAW,yBAAyB,MAAM;IAEhD;IAEF,IAAI,SAAS,UAAU,WAAW,SAAS,WAAW,0BAA0B;IAChF,2BAA2B,UAAU;GACvC,CAAC;GACH,2BAA2B,IAAI,aAAa,cAAc;GAC1D,IAAI;IACF,MAAM;GACR,UAAU;IACR,IAAI,2BAA2B,IAAI,WAAW,MAAM,gBAClD,2BAA2B,OAAO,WAAW;GAEjD;EACF;EACA,MAAM,+BAA+B,OAAO,cAA6C;GACvF,MAAM,aAAa,qBAAqB,IAAI,WAAW;GACvD,IAAI;IACF,MAAM,qBAAqB;GAC7B,SAAS,OAAO;IACd,IAAI,YAAY,2BAA2B,qBAAqB,IAAI,WAAW,MAAM,YAAY;KAI/F,IAAI,UAAU;KACd,IAAI;MACF,UAAW,MAAM,QAAQ,kBAAkB,WAAW,MAAO;KAC/D,QAAQ,CAER;KACA,IAAI;MACF,MAAM,UAAU,QAAQ;MACxB,UAAU;KACZ,QAAQ,CAER;KACA,IAAI,WAAW,qBAAqB,IAAI,WAAW,MAAM,YAAY;MACnE,qBAAqB,OAAO,WAAW;MACvC,sBAAsB,OAAO,WAAW;MACxC,sBAAsB,OAAO,WAAW;KAC1C;IACF;IACA,MAAM;GACR;GACA,IAAI,cAAc,qBAAqB,IAAI,WAAW,MAAM,YAC1D,MAAM,IAAI,MAAM,uEAAuE;GAEzF,OAAO;EACT;EAEA,IAAI;EACJ,IAAI;GACF,WAAW,QAAQ,iBAAiB,WAAW;EACjD,QAAQ;GAEN,WAAW,KAAA;EACb;EACA,aAAa,sBAAsB,IAAI,WAAW;EAClD,IAAI,UAAU;GACZ,SAAS,eAAe,0BAA0B;GAGlD,IAAI,CAAC,gBAAgB;IAInB,MAAM,uBAAuB,YAAY;KACvC,MAAM,UAAU,SAAS,UAAU,yBAAyB,IAAI,SAAS,MAAM;KAC/E,IAAI,CAAC,SAAS,OAAO;KACrB,MAAM;KACN,IAAI,yBAAyB,IAAI,SAAS,MAAO,MAAM,SACrD,yBAAyB,OAAO,SAAS,MAAO;KAClD,sBAAsB,OAAO,WAAW;KACxC,OAAO;IACT;IACA,MAAM,qBAAqB;IAC3B,IAAI,WAAW,MAAM,gBAAgB;IAIrC,SAAS;KACP,MAAM,WAAW,oBAAoB,IAAI,WAAW;KACpD,IAAI,UAAU;MACZ,MAAM;MACN,WAAW,MAAM,gBAAgB;MACjC;KACF;KACA,IAAI,MAAM,qBAAqB,GAAG;MAChC,WAAW,MAAM,gBAAgB;MACjC;KACF;KACA,IAAI,sBAAsB,IAAI,WAAW,MAAM,UAAU;KACzD,MAAM,SAAS;KACf,MAAM,WAAW,YAAY;MAC3B,MAAM,SAAS,QAAQ,QAAQ;MAC/B,sBAAsB,IAAI,aAAa,MAAM;KAC/C,EAAA,CAAG,CAAC,CAAC,cAAc,oBAAoB,OAAO,WAAW,CAAC;KAC1D,oBAAoB,IAAI,aAAa,OAAO;KAC5C,MAAM;KACN,WAAW,MAAM,gBAAgB;IACnC;GACF;GAOA,OAAO,6BAA6B,QAAQ;EAC9C;EAEA,MAAM,qCACJ,IAAI,MAAM,mBAAmB,QAAQ,UAAU,8CAA8C;EAM/F,MAAM,kBAAkB,OAAO,QAAwB,SAAiB,SAA0C;GAChH,MAAM,SAAS,MAAM,oBAAoB;GACzC,MAAM,QAAQ,OAAO,eAAe;GACpC,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,0EAA0E;GAItG,IAAI,gBAAgB;IAClB,MAAM,eAAe,MAAM,qBAAqB,SAAS;IACzD,MAAM,eACH,MAAM,mBAAmB,eAAe,oBAAoB,QAAQ,OAAO,YAAY,KAAM;IAChG,OAAO,UAAS,SAAQ;KAAE,GAAG;KAAK,UAAU;IAAa,EAAE;GAC7D;GACA,MAAM,gBAAgB;IACpB,KAAK;KAAE,IAAI,QAAQ;KAAI,gBAAgB;KAAS,gBAAgB,QAAQ;IAAe;IACvF,UAAU;KACR;KACA,eAAe,WAAW;KAC1B,UAAU,OAAO;KACjB,cAAc,OAAO,eAAe;IACtC;IACA,SAAS;IACT;IACA,SAAS,QAAQ;GACnB,CAAC;GACD,MAAM,sBAAsB,QAAQ,SAAS;IAC3C,QAAQ,QAAQ;IAChB,YAAY,QAAQ,cAAc,kBAAkB,UAAU,WAAW;IACzE;IACA;IACA,UAAU,OAAO;IACjB,cAAc,OAAO,eAAe;IACpC,mBAAmB,4BAA4B,QAAQ,MAAM;IAC7D,oBAAoB,cAAc,OAAO,WAAW,6BAA6B,QAAQ,MAAM,IAAI,KAAA;GACrG,CAAC;GACD,IAAI,kBAAkB,gBAAgB,CAAC,KAAK,WAAW;IAOrD,IAAI,sBAAsB,QAAQ,IAAI,kBAAkB,YAAY,GAAG;KACrE,QAAQ,KAAK,4EAA4E;MACvF,OAAO,QAAQ;MACf,WAAW,QAAQ;MACnB,qBAAqB,QAAQ;KAC/B,CAAC;KACD;IACF;IACA,IAAI;KACF,MAAM,WAAW,yBAAyB,gBAAgB,QAAQ,SAAS,kBAAkB,YAAa,CAAC;KAC3G,MAAM,KAAK,cAAc;IAC3B,SAAS,YAAY;KACnB,IAAI,kBAAkB,iBACpB,IAAI;MACF,MAAM,mBAAmB,QAAQ,SAAS,kBAAkB,iBAAiB,EAC3E,WAAW,2BACb,CAAC;KACH,SAAS,eAAe;MACtB,QAAQ,KAAK,iEAAiE;OAC5E,OAAO,QAAQ;OACf,WAAW,QAAQ;OACnB,qBAAqB,QAAQ;OAC7B,OAAO,yBAAyB,QAAQ,cAAc,QAAQ,MAAM,IAAK,IAAI,OAAO,aAAa;MACnG,CAAC;KACH;KAEF,IAAI,sBAAsB,mBAAmB;MAK3C,yBAAyB,QAAQ,IAAI,kBAAkB,YAAY;MACnE,MAAM,IAAI,kBACR,GAAG,WAAW,QAAQ,wLACtB,WAAW,IACb;KACF;KACA,MAAM;IACR;GACF;EACF;EAMA,MAAM,iBAAiC,YAAY,aAAa,OAAO;EAEvE,MAAM,aAAa,IAAI,kBAAkB;GACvC,IAAI,cAAc;GAClB,SAAS;GAIT,eAAe,sBAAsB,QAAQ,IAAI,aAAa,SAAS,YAAY;EACrF,CAAC;EACD,MAAM,oBAAoB;GAAC,KAAK,KAAK,WAAW,QAAQ;GAAG;GAAkB;EAAgB;EAC7F,MAAM,uBAAuB,IAAI,+BAC/B,kBACM,aAAa,QAAQ,WAAW,SACxC;EAEA,MAAM,YAAY,IAAI,UAAU;GAC9B,IAAI;GACJ,MAAM;GACN;GACA,SAAS;GACT,OAAO;GACP,QAAQ,CAPU,GAAI,yBAAyB,SAAS,CAAC,GAAI,GAAG,iBAO/C;GAIjB,aACE,yBAAyB,aAAa,sBAAsB,iBAAiB,KAAK;EACtF,CAAC;EAOD,QAAQ,aAAa,WAAW,aAAa,EAAE,QAAQ,SAAS,CAAC;EAIjE,sBAAsB,IAAI,aAAa,SAAS;EAqBhD,IAAI,CAAC,MAhBoB,kBAAkB,SACzC,QAAQ,WACR,aACA,qBACA,YAAY;GACV,qBAAqB,OAAO,WAAW;GACvC,sBAAsB,OAAO,WAAW;GACxC,sBAAsB,OAAO,WAAW;GACxC,kBAAkB,OAAO,WAAW;GAIpC,oBAAoB,QAAQ,EAAE;GAC9B,MAAM,QAAQ,kBAAkB,WAAW;EAC7C,CACF,GAEE,MAAM,IAAI,MAAM,mBAAmB,QAAQ,UAAU,8CAA8C;EAGrG,eAAe;EACf,OAAO;CACT;AACF"}
@@ -36,10 +36,10 @@ Parse the PR reference from `$ARGUMENTS`. Then:
36
36
  2. Resolve any issue that provides context for the PR's current cumulative behavior. Start with `closingIssuesReferences`; if none exist, inspect explicit PR-body references. Read each candidate with `gh issue view <issue> --json title,body,state,labels,comments`. A merely referenced but unrelated issue does not establish context. A docs-only maintenance PR may proceed without an issue when it adds no behavior and its guidance is verified against an existing public contract or implementation; record that basis in the handoff. If there is no related issue, or the issue does not cover the implemented behavior and scope—including scope introduced by the push—record an advisory issue-context gap in the handoff; it cannot by itself block approval or create a requested change.
37
37
  3. Classify the PR as feature work, bug fix, maintenance, or mixed based on its current behavior—not the author's checkbox or the prior verdict. When a related issue exists, report whether it has `status: needs triage` or `status: needs approval`; this is context for the handoff, not an independent verdict gate. Do not infer approval merely because the initial pass cleared the issue or because the feature appears useful. This policy is behavior-based, not author-based: an external contributor's docs-only maintenance correction gets the same exception as a maintainer's.
38
38
  4. Independently re-state the PR's concrete goal and expected behavior. Treat the prior pass, any issue, and the PR description as context and evidence, not established fact: identify the relevant contract from docs, types, tests, history, and analogous behavior; challenge the reporter's environmental, causal, and product assumptions; and decide whether the current cumulative PR still supports the same conclusion. For feature work, compare every material user-visible behavior and scope choice with any related issue and maintainer discussion; unresolved product decisions are findings, not reviewer assumptions.
39
- 5. Locate your previous review pass on this PR: `gh pr view <number> --json reviews --jq '.reviews[] | select(.author.login == "<factory-app[bot]>") | {state, submittedAt, body}'` (fall back to `gh pr view <number> --json reviews,comments` if the review was published as a comment instead). Identify the verdict and each requested change, finding, assumption, and open question it recorded.
39
+ 5. Locate your previous review pass on this PR: `gh api repos/<owner>/<repo>/pulls/<number>/reviews --paginate --jq '.[] | select(.user.login == "<factory-app>[bot]") | {state, submitted_at, body}'` (fall back to `gh api repos/<owner>/<repo>/issues/<number>/comments --paginate --jq '.[] | select(.user.login == "<factory-app>[bot]") | {created_at, body}'` if the review was published as a comment instead). Use the REST endpoints: `gh pr view` reports App reviewers without the `[bot]` suffix, so a `.author.login` filter on `<slug>[bot]` matches nothing. Identify the verdict and each requested change, finding, assumption, and open question it recorded.
40
40
  6. **Recover or write your own design before reading theirs.** If the prior pass recorded an **Approach** section, take the required outcome, real constraints, and simplest sufficient design from it; a push does not soften that standard. If it did not, write one now from the base branch and the problem alone, exactly as a first-time review would: the required outcome and constraints separate from the proposed solution, how the base works and where the cause lives, the simplest design that satisfies the requirements and what you would deliberately not add, the behavior a valuable test would exercise, and what evidence would make you comfortable approving. This is your design, not a forecast of the author's; it is not revised after the diff is open — later evidence goes in findings.
41
41
  7. **Load the category guidance before opening the cumulative diff.** Read `references/categories/README.md` from the `factory-review` skill (`skill_read` with skill `factory-review`) and consider every entry against the behavior, compatibility boundaries, and failure modes the problem touches, not just its headline category. Load the pages that could plausibly help, including any the prior pass named. Command recipes the pages cite live in `references/archaeology.md` in the same skill. Reading the relevant pages is required; select checks according to the actual change and its risks.
42
- 8. Now read the PR as it stands: `gh pr diff <number>` and `gh pr view <number> --json commits,files`. Reassess the categories against the actual change and the push, loading additional pages before reviewing those portions in depth. Then identify the push that triggered this pass: the commits added since your previous review submitted. `gh api repos/<owner>/<repo>/pulls/<number>/commits --paginate` lists commits with timestamps; anything after your prior review's `submittedAt` is in scope for the push. Note the base, prior-head, and current-head SHAs now — you will re-verify against exactly this current head.
42
+ 8. Now read the PR as it stands: `gh pr diff <number>` and `gh pr view <number> --json commits,files`. Reassess the categories against the actual change and the push, loading additional pages before reviewing those portions in depth. Then identify the push that triggered this pass: the commits added since your previous review submitted. `gh api repos/<owner>/<repo>/pulls/<number>/commits --paginate` lists commits with timestamps; anything after your prior review's `submitted_at` (or the comment's `created_at`) is in scope for the push. Note the base, prior-head, and current-head SHAs now — you will re-verify against exactly this current head.
43
43
 
44
44
  A prior pass you cannot locate is itself a finding: proceed as a first-time review, and record in the handoff that the previous pass could not be recovered.
45
45
 
@@ -145,7 +145,7 @@ If the tool call fails or returns an unexpected shape — **and identically if t
145
145
 
146
146
  Before drafting the handoff, run the in-run cross-check against the tool's output:
147
147
 
148
- 1. Compare `triggeredBy` with `.author.login` from your Phase 1 `gh pr view --json author` fetch. `gh pr view --json author` returns an object (`{login, name, id, is_bot}`), so the comparison must be against `.login`.
148
+ 1. Compare `triggeredBy` with the PR author from `gh api repos/<owner>/<repo>/pulls/<number> --jq .user.login` (owner, repo, and number of the Phase 1 PR) — the same REST field intake records. Do not compare it with `.author.login` from the Phase 1 `gh pr view --json author` fetch: for a GitHub App author, `gh pr view` returns `app/<slug>` while the REST API, and therefore `triggeredBy`, returns `<slug>[bot]`, so the two formats never match. Do not rewrite `app/<slug>` as `<slug>[bot]` (or the reverse) to make them agree — a rewritten string verifies no account. If the lookup fails, returns no login, or the login differs from `triggeredBy`, treat it as a mismatch: you are almost certainly reviewing a different PR than the one your session was bound to.
149
149
  2. Compare `reviewTarget.url` with the `url` you resolve for the PR under re-review. If `reviewTarget.url` is `null`, fall back to a binding-vs-checkout comparison. Phase 1's `gh pr view <number>` resolves against the session checkout's own remote, so nothing derived from that checkout (its origin URL, its PR URL) can confirm the checkout **is** the bound repository — one side of the comparison must come from the binding. That side is `boundRepository`: first compare `boundRepository.repositoryId` (the intake-stamped numeric REST repository id) with the numeric id of the checkout's repository, resolved via `gh api repos/<owner>/<repo> --jq .id` (owner/repo from the checkout's `origin` remote; `gh repo view --json id` returns the GraphQL node id, not this number). If `boundRepository` is `null` or not a `github` shape, there is no verifiable bound repository — treat that as a mismatch. If the `gh api` id resolution itself fails or returns nothing, the comparison can't run at all — same outcome: stop, do not publish, record the failure in the handoff. Only when the repository ids match, compare the trailing PR number in `reviewTarget.externalId` (`github-pr:<number>`) with the Phase 1 PR number; the externalId scopes the number to the bound repository, so a mismatch on either comparison is proof of a wrong-target review.
150
150
 
151
151
  On any mismatch, stop and record it as a blocking security finding with both values verbatim. Do **not** publish anything on the PR — a mismatch means the fetched PR may not be the session's bound target, and posting any verdict (request changes included) puts a review on a PR that may be the wrong one. Re-run the Phase 1 fetch once and re-run this cross-check; publish only if the fresh check matches. If it still mismatches, hand off without publishing: record the mismatch in the **Factory routing** block, skip the verdict step, and hand off to a human. An explanation in the handoff never authorizes publication.
@@ -184,7 +184,7 @@ Then submit a PR review matching the verdict:
184
184
  - approve → `gh pr review <number> --approve --body-file <file>`
185
185
  - request changes → `gh pr review <number> --request-changes --body-file <file>`
186
186
 
187
- **Author-identity misconfiguration must be visible, never silent.** GitHub refuses both approve and request changes from the PR's author, so a review token that authored the PR can never record a verdict in `reviewDecision` or satisfy branch protection. Before submitting, compare the reviewing identity (`gh api user --jq .login`; for an App installation token that call may fail — then treat a submission rejected with GitHub's "Can not approve/request changes on your own pull request" error as the same signal) with the PR's `.author.login`. When they match:
187
+ **Author-identity misconfiguration must be visible, never silent.** GitHub refuses both approve and request changes from the PR's author, so a review token that authored the PR can never record a verdict in `reviewDecision` or satisfy branch protection. Before submitting, compare the reviewing identity (`gh api user --jq .login`; for an App installation token that call may fail — then treat a submission rejected with GitHub's "Can not approve/request changes on your own pull request" error as the same signal) with the PR author login from the REST lookup in the in-run cross-check (`.user.login`, never `gh pr view`'s `.author.login`, which formats App authors as `app/<slug>` and would hide a match). When they match:
188
188
 
189
189
  1. Add this line to the published body immediately after the `Reviewed head:` line (the verdict line stays first and `Reviewed head:` stays second): `> ⚠️ **Factory misconfiguration:** the review token is the PR author, so GitHub cannot record this verdict as an approving or changes-requested review (it will not satisfy branch protection or workflows that require an approving or changes-requested review). Configure a separate reviewer token for Factory reviews.`
190
190
  2. Publish with `gh pr comment <number> --body-file <file>`. Do not use `gh pr review --comment`: Factory's repair loop only routes a request-changes verdict from a plain PR comment whose first line is the verdict, and ignores `COMMENTED` reviews.
@@ -138,7 +138,7 @@ If the tool call fails or returns an unexpected shape — **and identically if t
138
138
 
139
139
  Before drafting the handoff, run the in-run cross-check against the tool's output:
140
140
 
141
- 1. Compare `triggeredBy` with `.author.login` from your Phase 1 `gh pr view --json author` fetch. `gh pr view --json author` returns an object (`{login, name, id, is_bot}`), so the comparison must be against `.login`, matching what Phase 2 already does at `gh pr view --json reviews --jq '.reviews[] | {author: .author.login, …}'`. If the two disagree, you are almost certainly reviewing a different PR than the one your session was bound to.
141
+ 1. Compare `triggeredBy` with the PR author from `gh api repos/<owner>/<repo>/pulls/<number> --jq .user.login` (owner, repo, and number of the Phase 1 PR) — the same REST field intake records. Do not compare it with `.author.login` from the Phase 1 `gh pr view --json author` fetch: for a GitHub App author, `gh pr view` returns `app/<slug>` while the REST API, and therefore `triggeredBy`, returns `<slug>[bot]`, so the two formats never match. Do not rewrite `app/<slug>` as `<slug>[bot]` (or the reverse) to make them agree — a rewritten string verifies no account. If the lookup fails, returns no login, or the login differs from `triggeredBy`, treat it as a mismatch: you are almost certainly reviewing a different PR than the one your session was bound to.
142
142
  2. Compare `reviewTarget.url` with the `url` you resolve for the PR under review (typically `https://github.com/<owner>/<repo>/pull/<number>` from the Phase 1 PR). If `reviewTarget.url` is `null`, fall back to a binding-vs-checkout comparison. Phase 1's `gh pr view <number>` resolves against the session checkout's own remote, so nothing derived from that checkout (its origin URL, its PR URL) can confirm the checkout **is** the bound repository — one side of the comparison must come from the binding. That side is `boundRepository`: first compare `boundRepository.repositoryId` (the intake-stamped numeric REST repository id) with the numeric id of the checkout's repository, resolved via `gh api repos/<owner>/<repo> --jq .id` (owner/repo from the checkout's `origin` remote; `gh repo view --json id` returns the GraphQL node id, not this number). If `boundRepository` is `null` or not a `github` shape, there is no verifiable bound repository — treat that as a mismatch. If the `gh api` id resolution itself fails or returns nothing, the comparison can't run at all — same outcome: stop, do not publish, record the failure in the handoff. Only when the repository ids match, compare the trailing PR number in `reviewTarget.externalId` (`github-pr:<number>`) with the Phase 1 PR number; the externalId scopes the number to the bound repository, so a mismatch on either comparison is proof of a wrong-target review.
143
143
 
144
144
  On any mismatch, stop and record it as a blocking security finding with both values verbatim. Do **not** publish anything on the PR — a mismatch means the fetched PR may not be the session's bound target, and posting any verdict (request changes included) puts a review on a PR that may be the wrong one. Re-run the Phase 1 fetch once and re-run this cross-check; publish only if the fresh check matches. If it still mismatches, hand off without publishing: record the mismatch in the **Factory routing** block, skip the verdict step, and hand off to a human. An explanation in the handoff never authorizes publication.
@@ -176,7 +176,7 @@ Then submit a PR review matching the verdict:
176
176
  - approve → `gh pr review <number> --approve --body-file <file>`
177
177
  - request changes → `gh pr review <number> --request-changes --body-file <file>`
178
178
 
179
- **Author-identity misconfiguration must be visible, never silent.** GitHub refuses both approve and request changes from the PR's author, so a review token that authored the PR can never record a verdict in `reviewDecision` or satisfy branch protection. Before submitting, compare the reviewing identity (`gh api user --jq .login`; for an App installation token that call may fail — then treat a submission rejected with GitHub's "Can not approve/request changes on your own pull request" error as the same signal) with the PR's `.author.login`. When they match:
179
+ **Author-identity misconfiguration must be visible, never silent.** GitHub refuses both approve and request changes from the PR's author, so a review token that authored the PR can never record a verdict in `reviewDecision` or satisfy branch protection. Before submitting, compare the reviewing identity (`gh api user --jq .login`; for an App installation token that call may fail — then treat a submission rejected with GitHub's "Can not approve/request changes on your own pull request" error as the same signal) with the PR author login from the REST lookup in the in-run cross-check (`.user.login`, never `gh pr view`'s `.author.login`, which formats App authors as `app/<slug>` and would hide a match). When they match:
180
180
 
181
181
  1. Add this line to the published body immediately after the `Reviewed head:` line (the verdict line stays first and `Reviewed head:` stays second): `> ⚠️ **Factory misconfiguration:** the review token is the PR author, so GitHub cannot record this verdict as an approving or changes-requested review (it will not satisfy branch protection or workflows that require an approving or changes-requested review). Configure a separate reviewer token for Factory reviews.`
182
182
  2. Publish with `gh pr comment <number> --body-file <file>`. Do not use `gh pr review --comment`: Factory's repair loop only routes a request-changes verdict from a plain PR comment whose first line is the verdict, and ignores `COMMENTED` reviews.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/factory",
3
- "version": "0.21.0-alpha.7",
3
+ "version": "0.21.0-alpha.8",
4
4
  "description": "Mastra Software Factory module: the server core behind the Mastra Software Factory — storage domains, integrations, and surfaces for agent-powered software delivery",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -57,11 +57,11 @@
57
57
  "hono": "^4.13.7",
58
58
  "posthog-node": "^5.46.1",
59
59
  "zod": "^4.6.4",
60
+ "@mastra/code-sdk": "1.12.0-alpha.8",
60
61
  "@mastra/auth-studio": "1.3.9-alpha.0",
61
62
  "@mastra/auth-workos": "1.6.6",
62
- "@mastra/core": "1.76.0-alpha.7",
63
63
  "@mastra/slack": "1.7.2",
64
- "@mastra/code-sdk": "1.12.0-alpha.7"
64
+ "@mastra/core": "1.76.0-alpha.8"
65
65
  },
66
66
  "devDependencies": {
67
67
  "@types/node": "22.20.1",
@@ -70,10 +70,10 @@
70
70
  "typescript": "^6.0.3",
71
71
  "typescript-eslint": "^8.57.0",
72
72
  "vitest": "4.1.11",
73
- "@mastra/libsql": "1.26.0-alpha.2",
74
- "@mastra/pg": "1.31.0-alpha.3",
75
- "@internal/types-builder": "0.0.116",
76
73
  "@internal/lint": "0.0.141",
74
+ "@mastra/libsql": "1.26.0-alpha.3",
75
+ "@mastra/pg": "1.31.0-alpha.4",
76
+ "@internal/types-builder": "0.0.116",
77
77
  "@internal/workspace": "0.0.13"
78
78
  },
79
79
  "engines": {