@mastra/factory 0.6.0-alpha.16 → 0.6.0-alpha.17

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.
@@ -1 +1 @@
1
- {"version":3,"file":"dispatcher.js","names":["#controller","#transitionService","#storage","#ownerId","#reconcileToolResults","#prepareBinding","#primeCredentials","#maxInFlight","#inFlight","#timer","#tick","#activeClaim","#claimAndStart","#track","#dispatchDecision","#dispatchPendingStart","#withLease","#executeDecision","#requireItem","#findBinding","#requireSession","#upsertLinkedItem","#requireOrPrepareBinding","#switchThread","#requireBinding"],"sources":["../../src/rules/dispatcher.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController, AgentControllerEventListener } from '@mastra/core/agent-controller';\nimport { RequestContext } from '@mastra/core/request-context';\n\nimport { resolveSkillInvocation } from '../skills/service.js';\nimport type { SkillSession } from '../skills/service.js';\nimport type {\n FactoryDeferredDecisionRecord,\n FactoryPendingStartRecord,\n FactoryRunBindingRecord,\n WorkItemRow,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport type { FactoryTransitionService } from './transition-service.js';\nimport type { FactoryCommitDecision, FactoryRuleActor, FactoryRuleCausalEntry } from './types.js';\nimport { FACTORY_RULE_STAGES } from './types.js';\nimport { MAX_FACTORY_RULE_CAUSAL_DEPTH, validateFactoryRuleDecision } from './validation.js';\n\nconst LEASE_MS = 30_000;\nconst POLL_MS = 1_000;\nconst BATCH_SIZE = 10;\nconst MAX_ATTEMPTS = 5;\nconst MAX_ERROR_LENGTH = 512;\nconst MAX_BACKOFF_MS = 60_000;\nconst SKILL_COMPLETION_OBSERVATION_TIMEOUT_MS = 10 * 60_000;\n// Dispatches can legitimately run for minutes. Woken skill invocations hold\n// capacity until their agent run reaches a terminal state; binding preparation\n// also runs detached from the poll loop under this concurrency cap.\nconst MAX_IN_FLIGHT = 25;\n\nfunction waitForAgentEndOrTimeout(agentEnd: Promise<void>): Promise<boolean> {\n return new Promise(resolve => {\n const timeout = setTimeout(() => resolve(false), SKILL_COMPLETION_OBSERVATION_TIMEOUT_MS);\n timeout.unref?.();\n void agentEnd.then(() => {\n clearTimeout(timeout);\n resolve(true);\n });\n });\n}\n\ninterface DispatcherSession extends SkillSession {\n thread: {\n switch(input: { threadId: string }): Promise<unknown>;\n listActiveMessages(): Promise<Array<{ id: string }>>;\n };\n sendSignal(\n input: { id: string; type: 'user'; tagName: 'user'; contents: string },\n options: { requestContext: RequestContext; requireDelivery?: boolean },\n ): { accepted: Promise<{ accepted: true; runId?: string; action?: string }> };\n subscribe(listener: AgentControllerEventListener): () => void;\n}\n\ntype FactoryController = Pick<AgentController<MastraCodeState>, 'getSessionByResource'>;\n\nexport interface FactoryBindingPreparationInput {\n record: FactoryDeferredDecisionRecord;\n item: WorkItemRow;\n role: string;\n}\n\nexport interface FactoryDecisionDispatcherOptions {\n controller: FactoryController;\n transitionService: Pick<FactoryTransitionService, 'transition'>;\n storage: WorkItemsStorage;\n ownerId?: string;\n reconcileToolResults?: () => Promise<void>;\n prepareBinding?: (input: FactoryBindingPreparationInput) => Promise<void>;\n primeCredentials?: (tenant: { orgId: string; userId: string }) => Promise<void>;\n maxInFlight?: number;\n}\n\nfunction sanitizeDispatchError(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n return message\n .replace(/\\b(?:bearer|token|api[-_ ]?key|authorization)\\s*[:=]?\\s*[^\\s,;]+/gi, '[redacted]')\n .slice(0, MAX_ERROR_LENGTH);\n}\n\nfunction retryAt(now: Date, attempts: number): Date {\n return new Date(now.getTime() + Math.min(1_000 * 2 ** Math.max(0, attempts - 1), MAX_BACKOFF_MS));\n}\n\nfunction externalSourceForDecision(decision: Extract<FactoryCommitDecision, { type: 'upsertLinkedWorkItem' }>) {\n const [integrationId, type] =\n decision.source === 'github-pr'\n ? ['github', 'pull-request']\n : decision.source === 'github-issue'\n ? ['github', 'issue']\n : decision.source === 'linear-issue'\n ? ['linear', 'issue']\n : ['factory', 'manual'];\n return { integrationId, type, externalId: decision.sourceKey, url: decision.url ?? undefined };\n}\n\nfunction deferredActor(record: FactoryDeferredDecisionRecord): FactoryRuleActor {\n const actor = record.actor;\n if (\n actor?.type === 'github' &&\n typeof actor.login === 'string' &&\n typeof actor.trusted === 'boolean' &&\n typeof actor.factoryAuthored === 'boolean'\n ) {\n return {\n type: 'github',\n login: actor.login,\n trusted: actor.trusted,\n factoryAuthored: actor.factoryAuthored,\n };\n }\n return { type: 'system', id: 'factory-rule-dispatcher' };\n}\n\nfunction leaseIdentity(\n record: Pick<FactoryDeferredDecisionRecord | FactoryPendingStartRecord, 'id' | 'orgId' | 'factoryProjectId'>,\n ownerId: string,\n) {\n return { id: record.id, orgId: record.orgId, factoryProjectId: record.factoryProjectId, ownerId };\n}\n\nasync function awaitNotification(\n result: Awaited<ReturnType<SkillSession['sendNotificationSignal']>>,\n requireDelivery = false,\n): Promise<void> {\n await result.persisted;\n if (!result.accepted) {\n if (requireDelivery) throw new Error('Factory notification was persisted without agent delivery.');\n return;\n }\n const accepted = await result.accepted;\n if (!requireDelivery) return;\n if (accepted.action === 'wake') {\n await accepted.output.consumeStream();\n return;\n }\n if (accepted.action !== 'deliver') {\n throw new Error(`Factory notification did not reach the agent (${String(accepted.action)}).`);\n }\n}\n\nexport class FactoryDecisionDispatcher {\n readonly #controller: FactoryController;\n readonly #transitionService: Pick<FactoryTransitionService, 'transition'>;\n readonly #storage: WorkItemsStorage;\n readonly #ownerId: string;\n readonly #reconcileToolResults?: () => Promise<void>;\n readonly #prepareBinding?: (input: FactoryBindingPreparationInput) => Promise<void>;\n readonly #primeCredentials?: (tenant: { orgId: string; userId: string }) => Promise<void>;\n readonly #maxInFlight: number;\n #timer?: ReturnType<typeof setInterval>;\n #activeClaim?: Promise<void>;\n readonly #inFlight = new Set<Promise<void>>();\n\n constructor(options: FactoryDecisionDispatcherOptions) {\n this.#controller = options.controller;\n this.#transitionService = options.transitionService;\n this.#storage = options.storage;\n this.#ownerId = options.ownerId ?? `factory-dispatcher:${randomUUID()}`;\n this.#reconcileToolResults = options.reconcileToolResults;\n this.#prepareBinding = options.prepareBinding;\n this.#primeCredentials = options.primeCredentials;\n const maxInFlight = options.maxInFlight ?? MAX_IN_FLIGHT;\n this.#maxInFlight = Number.isFinite(maxInFlight) && maxInFlight > 0 ? Math.floor(maxInFlight) : MAX_IN_FLIGHT;\n }\n\n start(): void {\n if (this.#timer) return;\n void this.#tick();\n this.#timer = setInterval(() => void this.#tick(), POLL_MS);\n this.#timer.unref?.();\n }\n\n async stop(): Promise<void> {\n if (this.#timer) clearInterval(this.#timer);\n this.#timer = undefined;\n await this.#activeClaim;\n await Promise.allSettled([...this.#inFlight]);\n }\n\n async runOnce(now = new Date()): Promise<void> {\n await Promise.all(await this.#claimAndStart(now));\n }\n\n /**\n * Claims a batch and starts dispatches without awaiting their completion.\n * Dispatches can legitimately take minutes (skill kickoffs consume the\n * agent's run stream; binding preparation provisions sandboxes), so awaiting\n * them here would freeze the poll loop and starve every other queued\n * decision. In-flight records stay protected from re-claim by lease renewal.\n */\n async #claimAndStart(now: Date): Promise<Array<Promise<void>>> {\n await this.#reconcileToolResults?.();\n const capacity = this.#maxInFlight - this.#inFlight.size;\n if (capacity <= 0) return [];\n const limit = Math.min(BATCH_SIZE, capacity);\n const leaseExpiresAt = new Date(now.getTime() + LEASE_MS);\n const decisions = await this.#storage.claimDeferredDecisions({\n ownerId: this.#ownerId,\n now,\n leaseExpiresAt,\n limit,\n });\n const startsLimit = limit - decisions.length;\n const starts =\n startsLimit > 0\n ? await this.#storage.claimPendingStarts({\n ownerId: this.#ownerId,\n now,\n leaseExpiresAt,\n limit: startsLimit,\n })\n : [];\n return [\n ...decisions.map(decision => this.#track(this.#dispatchDecision(decision, now))),\n ...starts.map(start => this.#track(this.#dispatchPendingStart(start, now))),\n ];\n }\n\n #track(dispatch: Promise<void>): Promise<void> {\n this.#inFlight.add(dispatch);\n void dispatch.catch(() => {}).then(() => this.#inFlight.delete(dispatch));\n return dispatch;\n }\n\n async #tick(): Promise<void> {\n if (this.#activeClaim) return;\n this.#activeClaim = this.#claimAndStart(new Date()).then(\n dispatches => {\n for (const dispatch of dispatches) {\n dispatch.catch(error => {\n console.error('Factory decision dispatch failed', sanitizeDispatchError(error));\n });\n }\n },\n error => {\n console.error('Factory decision dispatch cycle failed', sanitizeDispatchError(error));\n },\n );\n try {\n await this.#activeClaim;\n } finally {\n this.#activeClaim = undefined;\n }\n }\n\n async #dispatchDecision(record: FactoryDeferredDecisionRecord, now: Date): Promise<void> {\n try {\n const decision = validateFactoryRuleDecision(record.decision, record.causalChain.length);\n if (decision.type === 'reject') throw new Error('Deferred Factory decisions cannot reject.');\n await this.#withLease(\n async leaseExpiresAt =>\n this.#storage.renewDeferredDecisionLease(leaseIdentity(record, this.#ownerId), leaseExpiresAt),\n async () => this.#executeDecision(record, decision),\n );\n const completed = await this.#storage.completeDeferredDecision(leaseIdentity(record, this.#ownerId), new Date());\n if (!completed) throw new Error('Factory decision lease was lost before completion.');\n } catch (error) {\n const terminal = record.attempts >= MAX_ATTEMPTS;\n await this.#storage.failDeferredDecision({\n ...leaseIdentity(record, this.#ownerId),\n now: new Date(),\n availableAt: retryAt(now, record.attempts),\n lastError: sanitizeDispatchError(error),\n terminal,\n });\n }\n }\n\n async #executeDecision(record: FactoryDeferredDecisionRecord, decision: FactoryCommitDecision): Promise<void> {\n const nextChain: FactoryRuleCausalEntry[] = [\n ...(record.causalChain as FactoryRuleCausalEntry[]),\n { ingressId: record.idempotencyKey, decisionType: decision.type },\n ];\n if (nextChain.length > MAX_FACTORY_RULE_CAUSAL_DEPTH) throw new Error('Factory rule causal depth exceeded.');\n\n switch (decision.type) {\n case 'transition': {\n const item = await this.#requireItem(record);\n const result = await this.#transitionService.transition({\n orgId: record.orgId,\n factoryProjectId: record.factoryProjectId,\n workItemId: item.id,\n board: decision.board,\n stage: decision.stage,\n expectedRevision: item.revision,\n actor: { type: 'system', id: 'factory-rule-dispatcher' },\n ingress: { type: 'rule', identity: `decision:${record.idempotencyKey}` },\n cause: 'rule_decision',\n causalChain: nextChain,\n });\n if (result.status === 'rejected') throw new Error(`${result.code}: ${result.reason}`);\n if (!decision.message) return;\n // Best-effort recipient lookup: no active binding (or no authenticated\n // session owner) means nobody is engaged with this item, so the\n // transition itself is the whole effect. A retry after a delivery\n // failure is safe because the transition replays by ingress identity.\n const binding = await this.#findBinding(record, decision.message.role);\n if (!binding) return;\n const startedBy = item.sessions[binding.role]?.startedBy;\n if (!startedBy) return;\n await this.#primeCredentials?.({ orgId: record.orgId, userId: startedBy });\n const requestContext = new RequestContext();\n requestContext.set('user', { workosId: startedBy, organizationId: record.orgId });\n const session = await this.#requireSession(binding);\n await awaitNotification(\n await session.sendNotificationSignal(\n {\n source: 'factory',\n kind: 'rule-message',\n summary: decision.message.text,\n priority: 'high',\n payload: { message: decision.message.text },\n sourceId: record.id,\n dedupeKey: record.idempotencyKey,\n },\n {\n ifActive: { behavior: 'deliver' },\n ifIdle: { behavior: 'wake' },\n requestContext,\n },\n ),\n true,\n );\n return;\n }\n case 'upsertLinkedWorkItem': {\n await this.#upsertLinkedItem(record, decision, nextChain);\n return;\n }\n case 'invokeSkill': {\n const binding = await this.#requireOrPrepareBinding(record, decision.role);\n const item = record.workItemId ? await this.#storage.get({ orgId: record.orgId, id: record.workItemId }) : null;\n const startedBy = item?.sessions[binding.role]?.startedBy;\n if (!startedBy) throw new Error(`Factory binding ${binding.id} has no authenticated session owner.`);\n await this.#primeCredentials?.({ orgId: record.orgId, userId: startedBy });\n const requestContext = new RequestContext();\n requestContext.set('user', { workosId: startedBy, organizationId: record.orgId });\n const resolved = await resolveSkillInvocation(this.#controller, {\n resourceId: binding.resourceId,\n name: decision.skillName,\n arguments: decision.arguments,\n });\n const session = resolved.session as DispatcherSession;\n await this.#switchThread(session, binding);\n const delivered = await session.thread.listActiveMessages();\n if (delivered.some(message => message.id === record.id)) return;\n if (decision.precedingMessage) {\n await awaitNotification(\n await session.sendNotificationSignal(\n {\n source: 'factory',\n kind: 'stage-transition',\n summary: decision.precedingMessage,\n priority: 'medium',\n payload: { message: decision.precedingMessage },\n sourceId: `${record.id}:stage-transition`,\n dedupeKey: `${record.idempotencyKey}:stage-transition`,\n },\n {\n ifActive: { behavior: 'deliver' },\n ifIdle: { behavior: 'persist' },\n requestContext,\n },\n ),\n );\n }\n let resolveAgentEnd!: () => void;\n const agentEnd = new Promise<void>(resolve => {\n resolveAgentEnd = resolve;\n });\n const unsubscribe = session.subscribe(event => {\n if (event.type === 'agent_end') {\n resolveAgentEnd();\n }\n });\n\n try {\n const result = session.sendSignal(\n {\n id: record.id,\n type: 'user',\n tagName: 'user',\n contents: resolved.message,\n },\n // Without `requireDelivery` the session resolves `accepted` on the\n // next tick and swallows wake failures, so a kickoff that never\n // reached the agent would be marked succeeded and the thread would\n // stay empty forever.\n { requestContext, requireDelivery: true },\n );\n const settled = await result.accepted;\n if (settled.action !== 'wake' && settled.action !== 'deliver') {\n // An undefined action means the session did not verify delivery at\n // all — with `requireDelivery` set that is a contract violation, not\n // a success.\n throw new Error(`Factory skill invocation signal did not reach the agent (${String(settled.action)}).`);\n }\n if (settled.action === 'wake') {\n const observed = await waitForAgentEndOrTimeout(agentEnd);\n if (!observed) {\n console.warn('Factory skill run terminal event was not observed before timeout', {\n decisionId: record.id,\n runId: settled.runId,\n });\n }\n }\n } finally {\n unsubscribe();\n }\n return;\n }\n case 'sendMessage': {\n const binding = decision.prepareBinding\n ? await this.#requireOrPrepareBinding(record, decision.role)\n : await this.#requireBinding(record, decision.role);\n const item = record.workItemId ? await this.#storage.get({ orgId: record.orgId, id: record.workItemId }) : null;\n const startedBy = item?.sessions[binding.role]?.startedBy;\n if (!startedBy) throw new Error(`Factory binding ${binding.id} has no authenticated session owner.`);\n await this.#primeCredentials?.({ orgId: record.orgId, userId: startedBy });\n const requestContext = new RequestContext();\n requestContext.set('user', { workosId: startedBy, organizationId: record.orgId });\n const session = await this.#requireSession(binding);\n await awaitNotification(\n await session.sendNotificationSignal(\n {\n source: 'factory',\n kind: 'rule-message',\n summary: decision.message,\n priority: decision.priority ?? 'high',\n payload: { message: decision.message },\n sourceId: record.id,\n dedupeKey: record.idempotencyKey,\n },\n {\n ifActive: { behavior: 'deliver' },\n ifIdle: { behavior: decision.idleBehavior ?? 'wake' },\n requestContext,\n },\n ),\n true,\n );\n return;\n }\n case 'notify': {\n const binding = await this.#requireBinding(record);\n const session = await this.#requireSession(binding);\n await awaitNotification(\n await session.sendNotificationSignal({\n source: 'factory',\n kind: 'rule-notification',\n summary: decision.title,\n payload: { body: decision.body, level: decision.level },\n sourceId: record.id,\n dedupeKey: record.idempotencyKey,\n }),\n );\n }\n }\n }\n\n async #upsertLinkedItem(\n record: FactoryDeferredDecisionRecord,\n decision: Extract<FactoryCommitDecision, { type: 'upsertLinkedWorkItem' }>,\n causalChain: FactoryRuleCausalEntry[],\n ): Promise<void> {\n const result = await this.#storage.upsert({\n orgId: record.orgId,\n userId: 'factory-rule-dispatcher',\n factoryProjectId: record.factoryProjectId,\n input: {\n externalSource: externalSourceForDecision(decision),\n parentWorkItemId: record.workItemId,\n title: decision.title,\n stages: ['intake'],\n sessions: {},\n metadata: { ...decision.metadata, factoryRuleMaterializationKey: record.idempotencyKey },\n },\n reuseMode: 'preserve',\n });\n const materializedByDecision = result.item.metadata?.factoryRuleMaterializationKey === record.idempotencyKey;\n if (!materializedByDecision && (decision.stage === 'intake' || !result.item.stages.includes('intake'))) return;\n\n const board = decision.board;\n let expectedRevision = result.item.revision;\n if (materializedByDecision) {\n const initial = await this.#transitionService.transition({\n orgId: record.orgId,\n factoryProjectId: record.factoryProjectId,\n workItemId: result.item.id,\n board,\n stage: 'intake',\n expectedRevision,\n actor: deferredActor(record),\n ingress: { type: 'rule', identity: `decision:${record.idempotencyKey}:${result.item.id}:initial-entry` },\n cause: 'linked_item_materialized',\n causalChain,\n initialEntry: true,\n });\n if (initial.status === 'rejected') {\n if (result.created) await this.#storage.delete({ orgId: record.orgId, id: result.item.id });\n throw new Error(`${initial.code}: ${initial.reason}`);\n }\n expectedRevision = initial.revision;\n }\n if (decision.stage === 'intake') return;\n\n const moved = await this.#transitionService.transition({\n orgId: record.orgId,\n factoryProjectId: record.factoryProjectId,\n workItemId: result.item.id,\n board,\n stage: decision.stage,\n expectedRevision,\n actor: { type: 'system', id: 'factory-rule-dispatcher' },\n ingress: { type: 'rule', identity: `decision:${record.idempotencyKey}:${result.item.id}:destination` },\n cause: materializedByDecision ? 'linked_item_materialized' : 'linked_item_reconciled',\n causalChain,\n });\n if (moved.status === 'rejected') throw new Error(`${moved.code}: ${moved.reason}`);\n }\n\n async #requireItem(record: FactoryDeferredDecisionRecord) {\n if (!record.workItemId) throw new Error('Factory decision is not linked to a work item.');\n const item = await this.#storage.get({ orgId: record.orgId, id: record.workItemId });\n if (!item) throw new Error('Factory work item not found.');\n return item;\n }\n\n async #findBinding(\n record: FactoryDeferredDecisionRecord,\n role?: string,\n ): Promise<FactoryRunBindingRecord | undefined> {\n if (!record.workItemId) throw new Error('Factory decision is not linked to a work item.');\n const bindings = await this.#storage.listRunBindings(record.orgId, record.factoryProjectId, record.workItemId);\n return bindings\n .filter(candidate => candidate.status === 'active' && (role === undefined || candidate.role === role))\n .sort((left, right) => {\n if (role === undefined && left.role === 'work' && right.role !== 'work') return -1;\n if (role === undefined && right.role === 'work' && left.role !== 'work') return 1;\n return right.createdAt.getTime() - left.createdAt.getTime() || left.id.localeCompare(right.id);\n })[0];\n }\n\n async #requireBinding(record: FactoryDeferredDecisionRecord, role?: string): Promise<FactoryRunBindingRecord> {\n const binding = await this.#findBinding(record, role);\n if (!binding) throw new Error(role ? `No active Factory binding for role ${role}.` : 'No active Factory binding.');\n return binding;\n }\n\n async #requireOrPrepareBinding(\n record: FactoryDeferredDecisionRecord,\n role: string,\n ): Promise<FactoryRunBindingRecord> {\n const binding = await this.#findBinding(record, role);\n if (binding) {\n const session = await this.#controller.getSessionByResource(binding.resourceId);\n if (session) return binding;\n }\n if (!this.#prepareBinding) {\n throw new Error(binding ? 'Bound Factory session not found.' : `No active Factory binding for role ${role}.`);\n }\n const item = await this.#requireItem(record);\n await this.#prepareBinding({ record, item, role });\n return this.#requireBinding(record, role);\n }\n\n async #requireSession(binding: FactoryRunBindingRecord): Promise<DispatcherSession> {\n const session = (await this.#controller.getSessionByResource(binding.resourceId)) as DispatcherSession | undefined;\n if (!session) throw new Error('Bound Factory session not found.');\n await this.#switchThread(session, binding);\n return session;\n }\n\n async #switchThread(session: SkillSession, binding: FactoryRunBindingRecord): Promise<void> {\n await (session as DispatcherSession).thread.switch({ threadId: binding.threadId });\n }\n\n async #withLease(\n renew: (leaseExpiresAt: Date) => Promise<unknown | null>,\n effect: () => Promise<void>,\n ): Promise<void> {\n let renewalFailure: unknown;\n let renewal = Promise.resolve();\n const timer = setInterval(\n () => {\n renewal = renewal.then(async () => {\n try {\n const renewed = await renew(new Date(Date.now() + LEASE_MS));\n if (!renewed) renewalFailure = new Error('Factory dispatch lease was lost during execution.');\n } catch (error) {\n renewalFailure = error;\n }\n });\n },\n Math.floor(LEASE_MS / 3),\n );\n timer.unref?.();\n try {\n await effect();\n await renewal;\n if (renewalFailure) throw renewalFailure;\n } finally {\n clearInterval(timer);\n await renewal;\n }\n }\n\n async #dispatchPendingStart(record: FactoryPendingStartRecord, now: Date): Promise<void> {\n try {\n await this.#withLease(\n async leaseExpiresAt =>\n this.#storage.renewPendingStartLease(leaseIdentity(record, this.#ownerId), leaseExpiresAt),\n async () => {\n if (record.message === null) return;\n const bindings = await this.#storage.listRunBindings(record.orgId, record.factoryProjectId);\n const binding = bindings.find(\n candidate => candidate.id === record.bindingId && candidate.status === 'active',\n );\n if (!binding) throw new Error('Prepared Factory binding is unavailable or revoked.');\n // Wake runs build the Factory workspace, which requires the\n // authenticated session owner on the request context.\n const item = await this.#storage.get({ orgId: record.orgId, id: binding.workItemId });\n const startedBy = item?.sessions[binding.role]?.startedBy;\n if (!startedBy) throw new Error(`Factory binding ${binding.id} has no authenticated session owner.`);\n await this.#primeCredentials?.({ orgId: record.orgId, userId: startedBy });\n const requestContext = new RequestContext();\n requestContext.set('user', { workosId: startedBy, organizationId: record.orgId });\n const session = await this.#requireSession(binding);\n await awaitNotification(\n await session.sendNotificationSignal(\n {\n source: 'factory',\n kind: 'run-kickoff',\n summary: record.message!,\n priority: 'high',\n payload: { message: record.message },\n sourceId: record.id,\n dedupeKey: `factory-kickoff:${record.kickoffKey}`,\n },\n { ifActive: { behavior: 'deliver' }, ifIdle: { behavior: 'wake' }, requestContext },\n ),\n true,\n );\n },\n );\n const completed = await this.#storage.completePendingStart(leaseIdentity(record, this.#ownerId), new Date());\n if (!completed) throw new Error('Factory kickoff lease was lost before completion.');\n } catch (error) {\n await this.#storage.failPendingStart({\n ...leaseIdentity(record, this.#ownerId),\n now: new Date(),\n availableAt: retryAt(now, record.attempts),\n lastError: sanitizeDispatchError(error),\n terminal: record.attempts >= MAX_ATTEMPTS,\n });\n }\n }\n}\n\nexport const FACTORY_DISPATCH_CONSTANTS = {\n leaseMs: LEASE_MS,\n pollMs: POLL_MS,\n batchSize: BATCH_SIZE,\n maxAttempts: MAX_ATTEMPTS,\n maxErrorLength: MAX_ERROR_LENGTH,\n maxBackoffMs: MAX_BACKOFF_MS,\n skillCompletionObservationTimeoutMs: SKILL_COMPLETION_OBSERVATION_TIMEOUT_MS,\n maxInFlight: MAX_IN_FLIGHT,\n stages: FACTORY_RULE_STAGES,\n} as const;\n"],"mappings":"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"}
1
+ {"version":3,"file":"dispatcher.js","names":["#controller","#transitionService","#storage","#ownerId","#reconcileToolResults","#prepareBinding","#primeCredentials","#maxInFlight","#inFlight","#timer","#tick","#activeClaim","#claimAndStart","#track","#dispatchPendingStart","#dispatchDecision","#withLease","#executeDecision","#requireItem","#findBinding","#requireSession","#upsertLinkedItem","#requireOrPrepareBinding","#switchThread","#requireBinding"],"sources":["../../src/rules/dispatcher.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController, AgentControllerEventListener } from '@mastra/core/agent-controller';\nimport { RequestContext } from '@mastra/core/request-context';\n\nimport { resolveSkillInvocation } from '../skills/service.js';\nimport type { SkillSession } from '../skills/service.js';\nimport type {\n FactoryDeferredDecisionRecord,\n FactoryPendingStartRecord,\n FactoryRunBindingRecord,\n WorkItemRow,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport type { FactoryTransitionService } from './transition-service.js';\nimport type { FactoryCommitDecision, FactoryRuleActor, FactoryRuleCausalEntry } from './types.js';\nimport { FACTORY_RULE_STAGES } from './types.js';\nimport { MAX_FACTORY_RULE_CAUSAL_DEPTH, validateFactoryRuleDecision } from './validation.js';\n\nconst LEASE_MS = 30_000;\nconst POLL_MS = 1_000;\nconst BATCH_SIZE = 10;\nconst MAX_ATTEMPTS = 5;\nconst MAX_ERROR_LENGTH = 512;\nconst MAX_BACKOFF_MS = 60_000;\nconst SKILL_COMPLETION_OBSERVATION_TIMEOUT_MS = 10 * 60_000;\n// Dispatches can legitimately run for minutes. Woken skill invocations hold\n// capacity until their agent run reaches a terminal state; binding preparation\n// also runs detached from the poll loop under this concurrency cap.\nconst MAX_IN_FLIGHT = 25;\n\nfunction waitForAgentEndOrTimeout(agentEnd: Promise<void>): Promise<boolean> {\n return new Promise(resolve => {\n const timeout = setTimeout(() => resolve(false), SKILL_COMPLETION_OBSERVATION_TIMEOUT_MS);\n timeout.unref?.();\n void agentEnd.then(() => {\n clearTimeout(timeout);\n resolve(true);\n });\n });\n}\n\ninterface DispatcherSession extends SkillSession {\n thread: {\n switch(input: { threadId: string }): Promise<unknown>;\n listActiveMessages(): Promise<Array<{ id: string }>>;\n };\n sendSignal(\n input: { id: string; type: 'user'; tagName: 'user'; contents: string },\n options: { requestContext: RequestContext; requireDelivery?: boolean },\n ): { accepted: Promise<{ accepted: true; runId?: string; action?: string }> };\n subscribe(listener: AgentControllerEventListener): () => void;\n}\n\ntype FactoryController = Pick<AgentController<MastraCodeState>, 'getSessionByResource'>;\n\nexport interface FactoryBindingPreparationInput {\n record: FactoryDeferredDecisionRecord;\n item: WorkItemRow;\n role: string;\n}\n\nexport interface FactoryDecisionDispatcherOptions {\n controller: FactoryController;\n transitionService: Pick<FactoryTransitionService, 'transition'>;\n storage: WorkItemsStorage;\n ownerId?: string;\n reconcileToolResults?: () => Promise<void>;\n prepareBinding?: (input: FactoryBindingPreparationInput) => Promise<void>;\n primeCredentials?: (tenant: { orgId: string; userId: string }) => Promise<void>;\n maxInFlight?: number;\n}\n\nfunction sanitizeDispatchError(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n return message\n .replace(/\\b(?:bearer|token|api[-_ ]?key|authorization)\\s*[:=]?\\s*[^\\s,;]+/gi, '[redacted]')\n .slice(0, MAX_ERROR_LENGTH);\n}\n\nfunction retryAt(now: Date, attempts: number): Date {\n return new Date(now.getTime() + Math.min(1_000 * 2 ** Math.max(0, attempts - 1), MAX_BACKOFF_MS));\n}\n\nfunction externalSourceForDecision(decision: Extract<FactoryCommitDecision, { type: 'upsertLinkedWorkItem' }>) {\n const [integrationId, type] =\n decision.source === 'github-pr'\n ? ['github', 'pull-request']\n : decision.source === 'github-issue'\n ? ['github', 'issue']\n : decision.source === 'linear-issue'\n ? ['linear', 'issue']\n : ['factory', 'manual'];\n return { integrationId, type, externalId: decision.sourceKey, url: decision.url ?? undefined };\n}\n\nfunction deferredActor(record: FactoryDeferredDecisionRecord): FactoryRuleActor {\n const actor = record.actor;\n if (\n actor?.type === 'github' &&\n typeof actor.login === 'string' &&\n typeof actor.trusted === 'boolean' &&\n typeof actor.factoryAuthored === 'boolean'\n ) {\n return {\n type: 'github',\n login: actor.login,\n trusted: actor.trusted,\n factoryAuthored: actor.factoryAuthored,\n };\n }\n return { type: 'system', id: 'factory-rule-dispatcher' };\n}\n\nfunction leaseIdentity(\n record: Pick<FactoryDeferredDecisionRecord | FactoryPendingStartRecord, 'id' | 'orgId' | 'factoryProjectId'>,\n ownerId: string,\n) {\n return { id: record.id, orgId: record.orgId, factoryProjectId: record.factoryProjectId, ownerId };\n}\n\nasync function awaitNotification(\n result: Awaited<ReturnType<SkillSession['sendNotificationSignal']>>,\n requireDelivery = false,\n): Promise<void> {\n await result.persisted;\n if (!result.accepted) {\n if (requireDelivery) throw new Error('Factory notification was persisted without agent delivery.');\n return;\n }\n const accepted = await result.accepted;\n if (!requireDelivery) return;\n if (accepted.action === 'wake') {\n await accepted.output.consumeStream();\n return;\n }\n if (accepted.action !== 'deliver') {\n throw new Error(`Factory notification did not reach the agent (${String(accepted.action)}).`);\n }\n}\n\nexport class FactoryDecisionDispatcher {\n readonly #controller: FactoryController;\n readonly #transitionService: Pick<FactoryTransitionService, 'transition'>;\n readonly #storage: WorkItemsStorage;\n readonly #ownerId: string;\n readonly #reconcileToolResults?: () => Promise<void>;\n readonly #prepareBinding?: (input: FactoryBindingPreparationInput) => Promise<void>;\n readonly #primeCredentials?: (tenant: { orgId: string; userId: string }) => Promise<void>;\n readonly #maxInFlight: number;\n #timer?: ReturnType<typeof setInterval>;\n #activeClaim?: Promise<void>;\n readonly #inFlight = new Set<Promise<void>>();\n\n constructor(options: FactoryDecisionDispatcherOptions) {\n this.#controller = options.controller;\n this.#transitionService = options.transitionService;\n this.#storage = options.storage;\n this.#ownerId = options.ownerId ?? `factory-dispatcher:${randomUUID()}`;\n this.#reconcileToolResults = options.reconcileToolResults;\n this.#prepareBinding = options.prepareBinding;\n this.#primeCredentials = options.primeCredentials;\n const maxInFlight = options.maxInFlight ?? MAX_IN_FLIGHT;\n this.#maxInFlight = Number.isFinite(maxInFlight) && maxInFlight > 0 ? Math.floor(maxInFlight) : MAX_IN_FLIGHT;\n }\n\n start(): void {\n if (this.#timer) return;\n void this.#tick();\n this.#timer = setInterval(() => void this.#tick(), POLL_MS);\n this.#timer.unref?.();\n }\n\n async stop(): Promise<void> {\n if (this.#timer) clearInterval(this.#timer);\n this.#timer = undefined;\n await this.#activeClaim;\n await Promise.allSettled([...this.#inFlight]);\n }\n\n async runOnce(now = new Date()): Promise<void> {\n await Promise.all(await this.#claimAndStart(now));\n }\n\n /**\n * Claims a batch and starts dispatches without awaiting their completion.\n * Dispatches can legitimately take minutes (skill kickoffs consume the\n * agent's run stream; binding preparation provisions sandboxes), so awaiting\n * them here would freeze the poll loop and starve every other queued\n * decision. In-flight records stay protected from re-claim by lease renewal.\n */\n async #claimAndStart(now: Date): Promise<Array<Promise<void>>> {\n await this.#reconcileToolResults?.();\n const capacity = this.#maxInFlight - this.#inFlight.size;\n if (capacity <= 0) return [];\n const limit = Math.min(BATCH_SIZE, capacity);\n const leaseExpiresAt = new Date(now.getTime() + LEASE_MS);\n // Starts are claimed before deferred decisions: a pending start is a user\n // waiting on a brand-new session, while a deferred decision is a background\n // continuation of one that is already running. A deep decision queue must\n // never starve new sessions out of the tick.\n const starts = await this.#storage.claimPendingStarts({\n ownerId: this.#ownerId,\n now,\n leaseExpiresAt,\n limit,\n });\n const decisionsLimit = limit - starts.length;\n const decisions =\n decisionsLimit > 0\n ? await this.#storage.claimDeferredDecisions({\n ownerId: this.#ownerId,\n now,\n leaseExpiresAt,\n limit: decisionsLimit,\n })\n : [];\n return [\n ...starts.map(start => this.#track(this.#dispatchPendingStart(start, now))),\n ...decisions.map(decision => this.#track(this.#dispatchDecision(decision, now))),\n ];\n }\n\n #track(dispatch: Promise<void>): Promise<void> {\n this.#inFlight.add(dispatch);\n void dispatch.catch(() => {}).then(() => this.#inFlight.delete(dispatch));\n return dispatch;\n }\n\n async #tick(): Promise<void> {\n if (this.#activeClaim) return;\n this.#activeClaim = this.#claimAndStart(new Date()).then(\n dispatches => {\n for (const dispatch of dispatches) {\n dispatch.catch(error => {\n console.error('Factory decision dispatch failed', sanitizeDispatchError(error));\n });\n }\n },\n error => {\n console.error('Factory decision dispatch cycle failed', sanitizeDispatchError(error));\n },\n );\n try {\n await this.#activeClaim;\n } finally {\n this.#activeClaim = undefined;\n }\n }\n\n async #dispatchDecision(record: FactoryDeferredDecisionRecord, now: Date): Promise<void> {\n try {\n const decision = validateFactoryRuleDecision(record.decision, record.causalChain.length);\n if (decision.type === 'reject') throw new Error('Deferred Factory decisions cannot reject.');\n await this.#withLease(\n async leaseExpiresAt =>\n this.#storage.renewDeferredDecisionLease(leaseIdentity(record, this.#ownerId), leaseExpiresAt),\n async () => this.#executeDecision(record, decision),\n );\n const completed = await this.#storage.completeDeferredDecision(leaseIdentity(record, this.#ownerId), new Date());\n if (!completed) throw new Error('Factory decision lease was lost before completion.');\n } catch (error) {\n const terminal = record.attempts >= MAX_ATTEMPTS;\n await this.#storage.failDeferredDecision({\n ...leaseIdentity(record, this.#ownerId),\n now: new Date(),\n availableAt: retryAt(now, record.attempts),\n lastError: sanitizeDispatchError(error),\n terminal,\n });\n }\n }\n\n async #executeDecision(record: FactoryDeferredDecisionRecord, decision: FactoryCommitDecision): Promise<void> {\n const nextChain: FactoryRuleCausalEntry[] = [\n ...(record.causalChain as FactoryRuleCausalEntry[]),\n { ingressId: record.idempotencyKey, decisionType: decision.type },\n ];\n if (nextChain.length > MAX_FACTORY_RULE_CAUSAL_DEPTH) throw new Error('Factory rule causal depth exceeded.');\n\n switch (decision.type) {\n case 'transition': {\n const item = await this.#requireItem(record);\n const result = await this.#transitionService.transition({\n orgId: record.orgId,\n factoryProjectId: record.factoryProjectId,\n workItemId: item.id,\n board: decision.board,\n stage: decision.stage,\n expectedRevision: item.revision,\n actor: { type: 'system', id: 'factory-rule-dispatcher' },\n ingress: { type: 'rule', identity: `decision:${record.idempotencyKey}` },\n cause: 'rule_decision',\n causalChain: nextChain,\n });\n if (result.status === 'rejected') throw new Error(`${result.code}: ${result.reason}`);\n if (!decision.message) return;\n // Best-effort recipient lookup: no active binding (or no authenticated\n // session owner) means nobody is engaged with this item, so the\n // transition itself is the whole effect. A retry after a delivery\n // failure is safe because the transition replays by ingress identity.\n const binding = await this.#findBinding(record, decision.message.role);\n if (!binding) return;\n const startedBy = item.sessions[binding.role]?.startedBy;\n if (!startedBy) return;\n await this.#primeCredentials?.({ orgId: record.orgId, userId: startedBy });\n const requestContext = new RequestContext();\n requestContext.set('user', { workosId: startedBy, organizationId: record.orgId });\n const session = await this.#requireSession(binding);\n await awaitNotification(\n await session.sendNotificationSignal(\n {\n source: 'factory',\n kind: 'rule-message',\n summary: decision.message.text,\n priority: 'high',\n payload: { message: decision.message.text },\n sourceId: record.id,\n dedupeKey: record.idempotencyKey,\n },\n {\n ifActive: { behavior: 'deliver' },\n ifIdle: { behavior: 'wake' },\n requestContext,\n },\n ),\n true,\n );\n return;\n }\n case 'upsertLinkedWorkItem': {\n await this.#upsertLinkedItem(record, decision, nextChain);\n return;\n }\n case 'invokeSkill': {\n const binding = await this.#requireOrPrepareBinding(record, decision.role);\n const item = record.workItemId ? await this.#storage.get({ orgId: record.orgId, id: record.workItemId }) : null;\n const startedBy = item?.sessions[binding.role]?.startedBy;\n if (!startedBy) throw new Error(`Factory binding ${binding.id} has no authenticated session owner.`);\n await this.#primeCredentials?.({ orgId: record.orgId, userId: startedBy });\n const requestContext = new RequestContext();\n requestContext.set('user', { workosId: startedBy, organizationId: record.orgId });\n const resolved = await resolveSkillInvocation(this.#controller, {\n resourceId: binding.resourceId,\n name: decision.skillName,\n arguments: decision.arguments,\n });\n const session = resolved.session as DispatcherSession;\n await this.#switchThread(session, binding);\n const delivered = await session.thread.listActiveMessages();\n if (delivered.some(message => message.id === record.id)) return;\n if (decision.precedingMessage) {\n await awaitNotification(\n await session.sendNotificationSignal(\n {\n source: 'factory',\n kind: 'stage-transition',\n summary: decision.precedingMessage,\n priority: 'medium',\n payload: { message: decision.precedingMessage },\n sourceId: `${record.id}:stage-transition`,\n dedupeKey: `${record.idempotencyKey}:stage-transition`,\n },\n {\n ifActive: { behavior: 'deliver' },\n ifIdle: { behavior: 'persist' },\n requestContext,\n },\n ),\n );\n }\n let resolveAgentEnd!: () => void;\n const agentEnd = new Promise<void>(resolve => {\n resolveAgentEnd = resolve;\n });\n const unsubscribe = session.subscribe(event => {\n if (event.type === 'agent_end') {\n resolveAgentEnd();\n }\n });\n\n try {\n const result = session.sendSignal(\n {\n id: record.id,\n type: 'user',\n tagName: 'user',\n contents: resolved.message,\n },\n // Without `requireDelivery` the session resolves `accepted` on the\n // next tick and swallows wake failures, so a kickoff that never\n // reached the agent would be marked succeeded and the thread would\n // stay empty forever.\n { requestContext, requireDelivery: true },\n );\n const settled = await result.accepted;\n if (settled.action !== 'wake' && settled.action !== 'deliver') {\n // An undefined action means the session did not verify delivery at\n // all — with `requireDelivery` set that is a contract violation, not\n // a success.\n throw new Error(`Factory skill invocation signal did not reach the agent (${String(settled.action)}).`);\n }\n if (settled.action === 'wake') {\n const observed = await waitForAgentEndOrTimeout(agentEnd);\n if (!observed) {\n console.warn('Factory skill run terminal event was not observed before timeout', {\n decisionId: record.id,\n runId: settled.runId,\n });\n }\n }\n } finally {\n unsubscribe();\n }\n return;\n }\n case 'sendMessage': {\n const binding = decision.prepareBinding\n ? await this.#requireOrPrepareBinding(record, decision.role)\n : await this.#requireBinding(record, decision.role);\n const item = record.workItemId ? await this.#storage.get({ orgId: record.orgId, id: record.workItemId }) : null;\n const startedBy = item?.sessions[binding.role]?.startedBy;\n if (!startedBy) throw new Error(`Factory binding ${binding.id} has no authenticated session owner.`);\n await this.#primeCredentials?.({ orgId: record.orgId, userId: startedBy });\n const requestContext = new RequestContext();\n requestContext.set('user', { workosId: startedBy, organizationId: record.orgId });\n const session = await this.#requireSession(binding);\n await awaitNotification(\n await session.sendNotificationSignal(\n {\n source: 'factory',\n kind: 'rule-message',\n summary: decision.message,\n priority: decision.priority ?? 'high',\n payload: { message: decision.message },\n sourceId: record.id,\n dedupeKey: record.idempotencyKey,\n },\n {\n ifActive: { behavior: 'deliver' },\n ifIdle: { behavior: decision.idleBehavior ?? 'wake' },\n requestContext,\n },\n ),\n true,\n );\n return;\n }\n case 'notify': {\n const binding = await this.#requireBinding(record);\n const session = await this.#requireSession(binding);\n await awaitNotification(\n await session.sendNotificationSignal({\n source: 'factory',\n kind: 'rule-notification',\n summary: decision.title,\n payload: { body: decision.body, level: decision.level },\n sourceId: record.id,\n dedupeKey: record.idempotencyKey,\n }),\n );\n }\n }\n }\n\n async #upsertLinkedItem(\n record: FactoryDeferredDecisionRecord,\n decision: Extract<FactoryCommitDecision, { type: 'upsertLinkedWorkItem' }>,\n causalChain: FactoryRuleCausalEntry[],\n ): Promise<void> {\n const result = await this.#storage.upsert({\n orgId: record.orgId,\n userId: 'factory-rule-dispatcher',\n factoryProjectId: record.factoryProjectId,\n input: {\n externalSource: externalSourceForDecision(decision),\n parentWorkItemId: record.workItemId,\n title: decision.title,\n stages: ['intake'],\n sessions: {},\n metadata: { ...decision.metadata, factoryRuleMaterializationKey: record.idempotencyKey },\n },\n reuseMode: 'preserve',\n });\n const materializedByDecision = result.item.metadata?.factoryRuleMaterializationKey === record.idempotencyKey;\n if (!materializedByDecision && (decision.stage === 'intake' || !result.item.stages.includes('intake'))) return;\n\n const board = decision.board;\n let expectedRevision = result.item.revision;\n if (materializedByDecision) {\n const initial = await this.#transitionService.transition({\n orgId: record.orgId,\n factoryProjectId: record.factoryProjectId,\n workItemId: result.item.id,\n board,\n stage: 'intake',\n expectedRevision,\n actor: deferredActor(record),\n ingress: { type: 'rule', identity: `decision:${record.idempotencyKey}:${result.item.id}:initial-entry` },\n cause: 'linked_item_materialized',\n causalChain,\n initialEntry: true,\n });\n if (initial.status === 'rejected') {\n if (result.created) await this.#storage.delete({ orgId: record.orgId, id: result.item.id });\n throw new Error(`${initial.code}: ${initial.reason}`);\n }\n expectedRevision = initial.revision;\n }\n if (decision.stage === 'intake') return;\n\n const moved = await this.#transitionService.transition({\n orgId: record.orgId,\n factoryProjectId: record.factoryProjectId,\n workItemId: result.item.id,\n board,\n stage: decision.stage,\n expectedRevision,\n actor: { type: 'system', id: 'factory-rule-dispatcher' },\n ingress: { type: 'rule', identity: `decision:${record.idempotencyKey}:${result.item.id}:destination` },\n cause: materializedByDecision ? 'linked_item_materialized' : 'linked_item_reconciled',\n causalChain,\n });\n if (moved.status === 'rejected') throw new Error(`${moved.code}: ${moved.reason}`);\n }\n\n async #requireItem(record: FactoryDeferredDecisionRecord) {\n if (!record.workItemId) throw new Error('Factory decision is not linked to a work item.');\n const item = await this.#storage.get({ orgId: record.orgId, id: record.workItemId });\n if (!item) throw new Error('Factory work item not found.');\n return item;\n }\n\n async #findBinding(\n record: FactoryDeferredDecisionRecord,\n role?: string,\n ): Promise<FactoryRunBindingRecord | undefined> {\n if (!record.workItemId) throw new Error('Factory decision is not linked to a work item.');\n const bindings = await this.#storage.listRunBindings(record.orgId, record.factoryProjectId, record.workItemId);\n return bindings\n .filter(candidate => candidate.status === 'active' && (role === undefined || candidate.role === role))\n .sort((left, right) => {\n if (role === undefined && left.role === 'work' && right.role !== 'work') return -1;\n if (role === undefined && right.role === 'work' && left.role !== 'work') return 1;\n return right.createdAt.getTime() - left.createdAt.getTime() || left.id.localeCompare(right.id);\n })[0];\n }\n\n async #requireBinding(record: FactoryDeferredDecisionRecord, role?: string): Promise<FactoryRunBindingRecord> {\n const binding = await this.#findBinding(record, role);\n if (!binding) throw new Error(role ? `No active Factory binding for role ${role}.` : 'No active Factory binding.');\n return binding;\n }\n\n async #requireOrPrepareBinding(\n record: FactoryDeferredDecisionRecord,\n role: string,\n ): Promise<FactoryRunBindingRecord> {\n const binding = await this.#findBinding(record, role);\n if (binding) {\n const session = await this.#controller.getSessionByResource(binding.resourceId);\n if (session) return binding;\n }\n if (!this.#prepareBinding) {\n throw new Error(binding ? 'Bound Factory session not found.' : `No active Factory binding for role ${role}.`);\n }\n const item = await this.#requireItem(record);\n await this.#prepareBinding({ record, item, role });\n return this.#requireBinding(record, role);\n }\n\n async #requireSession(binding: FactoryRunBindingRecord): Promise<DispatcherSession> {\n const session = (await this.#controller.getSessionByResource(binding.resourceId)) as DispatcherSession | undefined;\n if (!session) throw new Error('Bound Factory session not found.');\n await this.#switchThread(session, binding);\n return session;\n }\n\n async #switchThread(session: SkillSession, binding: FactoryRunBindingRecord): Promise<void> {\n await (session as DispatcherSession).thread.switch({ threadId: binding.threadId });\n }\n\n async #withLease(\n renew: (leaseExpiresAt: Date) => Promise<unknown | null>,\n effect: () => Promise<void>,\n ): Promise<void> {\n let renewalFailure: unknown;\n let renewal = Promise.resolve();\n const timer = setInterval(\n () => {\n renewal = renewal.then(async () => {\n try {\n const renewed = await renew(new Date(Date.now() + LEASE_MS));\n if (!renewed) renewalFailure = new Error('Factory dispatch lease was lost during execution.');\n } catch (error) {\n renewalFailure = error;\n }\n });\n },\n Math.floor(LEASE_MS / 3),\n );\n timer.unref?.();\n try {\n await effect();\n await renewal;\n if (renewalFailure) throw renewalFailure;\n } finally {\n clearInterval(timer);\n await renewal;\n }\n }\n\n async #dispatchPendingStart(record: FactoryPendingStartRecord, now: Date): Promise<void> {\n try {\n await this.#withLease(\n async leaseExpiresAt =>\n this.#storage.renewPendingStartLease(leaseIdentity(record, this.#ownerId), leaseExpiresAt),\n async () => {\n if (record.message === null) return;\n const bindings = await this.#storage.listRunBindings(record.orgId, record.factoryProjectId);\n const binding = bindings.find(\n candidate => candidate.id === record.bindingId && candidate.status === 'active',\n );\n if (!binding) throw new Error('Prepared Factory binding is unavailable or revoked.');\n // Wake runs build the Factory workspace, which requires the\n // authenticated session owner on the request context.\n const item = await this.#storage.get({ orgId: record.orgId, id: binding.workItemId });\n const startedBy = item?.sessions[binding.role]?.startedBy;\n if (!startedBy) throw new Error(`Factory binding ${binding.id} has no authenticated session owner.`);\n await this.#primeCredentials?.({ orgId: record.orgId, userId: startedBy });\n const requestContext = new RequestContext();\n requestContext.set('user', { workosId: startedBy, organizationId: record.orgId });\n const session = await this.#requireSession(binding);\n await awaitNotification(\n await session.sendNotificationSignal(\n {\n source: 'factory',\n kind: 'run-kickoff',\n summary: record.message!,\n priority: 'high',\n payload: { message: record.message },\n sourceId: record.id,\n dedupeKey: `factory-kickoff:${record.kickoffKey}`,\n },\n { ifActive: { behavior: 'deliver' }, ifIdle: { behavior: 'wake' }, requestContext },\n ),\n true,\n );\n },\n );\n const completed = await this.#storage.completePendingStart(leaseIdentity(record, this.#ownerId), new Date());\n if (!completed) throw new Error('Factory kickoff lease was lost before completion.');\n } catch (error) {\n await this.#storage.failPendingStart({\n ...leaseIdentity(record, this.#ownerId),\n now: new Date(),\n availableAt: retryAt(now, record.attempts),\n lastError: sanitizeDispatchError(error),\n terminal: record.attempts >= MAX_ATTEMPTS,\n });\n }\n }\n}\n\nexport const FACTORY_DISPATCH_CONSTANTS = {\n leaseMs: LEASE_MS,\n pollMs: POLL_MS,\n batchSize: BATCH_SIZE,\n maxAttempts: MAX_ATTEMPTS,\n maxErrorLength: MAX_ERROR_LENGTH,\n maxBackoffMs: MAX_BACKOFF_MS,\n skillCompletionObservationTimeoutMs: SKILL_COMPLETION_OBSERVATION_TIMEOUT_MS,\n maxInFlight: MAX_IN_FLIGHT,\n stages: FACTORY_RULE_STAGES,\n} as const;\n"],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { SessionBeforeAgentEndListener } from '@mastra/core/agent-controller';
2
+ import type { WorkspaceSandbox } from '@mastra/core/workspace';
3
+ export interface CheckpointCaptureSession {
4
+ getWorkspace(): {
5
+ sandbox?: Pick<WorkspaceSandbox, 'snapshot'>;
6
+ } | undefined;
7
+ onBeforeAgentEnd(listener: SessionBeforeAgentEndListener): () => void;
8
+ }
9
+ /**
10
+ * Snapshot the session's sandbox before every agent-end event so providers
11
+ * with checkpoint support (for example Railway-backed sandboxes) persist the
12
+ * last completed turn's writes. Captures are chained sequentially so a slow
13
+ * snapshot never overlaps the next one, and failures are logged rather than
14
+ * thrown so they never break the agent turn.
15
+ */
16
+ export declare function observeSessionCheckpoint(session: CheckpointCaptureSession): () => void;
17
+ //# sourceMappingURL=checkpoint-capture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoint-capture.d.ts","sourceRoot":"","sources":["../../src/session/checkpoint-capture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,+BAA+B,CAAC;AACnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,MAAM,WAAW,wBAAwB;IACvC,YAAY,IAAI;QAAE,OAAO,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAA;KAAE,GAAG,SAAS,CAAC;IAC7E,gBAAgB,CAAC,QAAQ,EAAE,6BAA6B,GAAG,MAAM,IAAI,CAAC;CACvE;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,wBAAwB,GAAG,MAAM,IAAI,CAetF"}
@@ -0,0 +1,27 @@
1
+ //#region src/session/checkpoint-capture.ts
2
+ /**
3
+ * Snapshot the session's sandbox before every agent-end event so providers
4
+ * with checkpoint support (for example Railway-backed sandboxes) persist the
5
+ * last completed turn's writes. Captures are chained sequentially so a slow
6
+ * snapshot never overlaps the next one, and failures are logged rather than
7
+ * thrown so they never break the agent turn.
8
+ */
9
+ function observeSessionCheckpoint(session) {
10
+ let capture = Promise.resolve();
11
+ return session.onBeforeAgentEnd(() => {
12
+ capture = capture.then(async () => {
13
+ const sandbox = session.getWorkspace()?.sandbox;
14
+ if (!sandbox) return;
15
+ try {
16
+ await sandbox.snapshot();
17
+ } catch (error) {
18
+ console.warn("[Factory checkpoint capture] Unable to snapshot sandbox.", error);
19
+ }
20
+ });
21
+ return capture;
22
+ });
23
+ }
24
+ //#endregion
25
+ export { observeSessionCheckpoint };
26
+
27
+ //# sourceMappingURL=checkpoint-capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoint-capture.js","names":[],"sources":["../../src/session/checkpoint-capture.ts"],"sourcesContent":["import type { SessionBeforeAgentEndListener } from '@mastra/core/agent-controller';\nimport type { WorkspaceSandbox } from '@mastra/core/workspace';\n\nexport interface CheckpointCaptureSession {\n getWorkspace(): { sandbox?: Pick<WorkspaceSandbox, 'snapshot'> } | undefined;\n onBeforeAgentEnd(listener: SessionBeforeAgentEndListener): () => void;\n}\n\n/**\n * Snapshot the session's sandbox before every agent-end event so providers\n * with checkpoint support (for example Railway-backed sandboxes) persist the\n * last completed turn's writes. Captures are chained sequentially so a slow\n * snapshot never overlaps the next one, and failures are logged rather than\n * thrown so they never break the agent turn.\n */\nexport function observeSessionCheckpoint(session: CheckpointCaptureSession): () => void {\n let capture = Promise.resolve();\n return session.onBeforeAgentEnd(() => {\n capture = capture.then(async () => {\n // Chat-only sessions run without a workspace; there is nothing to snapshot.\n const sandbox = session.getWorkspace()?.sandbox;\n if (!sandbox) return;\n try {\n await sandbox.snapshot();\n } catch (error) {\n console.warn('[Factory checkpoint capture] Unable to snapshot sandbox.', error);\n }\n });\n return capture;\n });\n}\n"],"mappings":";;;;;;;;AAeA,SAAgB,yBAAyB,SAA+C;CACtF,IAAI,UAAU,QAAQ,QAAQ;CAC9B,OAAO,QAAQ,uBAAuB;EACpC,UAAU,QAAQ,KAAK,YAAY;GAEjC,MAAM,UAAU,QAAQ,aAAa,CAAC,EAAE;GACxC,IAAI,CAAC,SAAS;GACd,IAAI;IACF,MAAM,QAAQ,SAAS;GACzB,SAAS,OAAO;IACd,QAAQ,KAAK,4DAA4D,KAAK;GAChF;EACF,CAAC;EACD,OAAO;CACT,CAAC;AACH"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/factory",
3
- "version": "0.6.0-alpha.16",
3
+ "version": "0.6.0-alpha.17",
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": {
@@ -53,8 +53,8 @@
53
53
  "zod": "^4.3.6",
54
54
  "@mastra/auth-studio": "1.3.3",
55
55
  "@mastra/auth-workos": "1.6.4",
56
- "@mastra/code-sdk": "1.2.0-alpha.15",
57
- "@mastra/core": "1.58.0-alpha.13",
56
+ "@mastra/code-sdk": "1.2.0-alpha.16",
57
+ "@mastra/core": "1.58.0-alpha.14",
58
58
  "@mastra/slack": "1.6.1-alpha.0"
59
59
  },
60
60
  "devDependencies": {
@@ -64,10 +64,10 @@
64
64
  "typescript": "^6.0.3",
65
65
  "typescript-eslint": "^8.57.0",
66
66
  "vitest": "4.1.10",
67
- "@internal/lint": "0.0.121",
68
67
  "@mastra/libsql": "1.20.0-alpha.2",
69
68
  "@mastra/pg": "1.20.0-alpha.3",
70
- "@internal/types-builder": "0.0.96"
69
+ "@internal/types-builder": "0.0.96",
70
+ "@internal/lint": "0.0.121"
71
71
  },
72
72
  "engines": {
73
73
  "node": ">=22.19.0"