@mastra/factory 0.14.1-alpha.0 → 0.14.1-alpha.3

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":["#audit","#controller","#transitionService","#boards","#storage","#ownerId","#isAutoRunEnabled","#autoApprovePlans","#reconcileToolResults","#prepareBinding","#primeCredentials","#feedReader","#resolveLinkedWorkItemParentId","#maxInFlight","#staleBindingSweepIntervalMs","#staleBindingTtlMs","#reconcileIntervalMs","#skillCompletionObservationTimeoutMs","#inFlight","#timer","#tick","#activeClaim","#claimAndStart","#maybeSweepStaleBindings","#maybeReconcileToolResults","#track","#dispatchPendingStart","#dispatchDecision","#reconcileInFlight","#lastReconcileAt","#lastStaleBindingSweepAt","#needsApproval","#supersedeProposals","#withLease","#executeDecision","#supersedeFailureOnSettledCard","#requireItem","#findBinding","#findSession","#upsertLinkedItem","#roleSuperseded","#requireOrPrepareBinding","#requireSession","#switchThread","#plansAreAutoApproved","#recordRunStart","#messageBinding","#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, Session } from '@mastra/core/agent-controller';\nimport { RequestContext } from '@mastra/core/request-context';\nimport type { SubmitPlanResumeData } from '@mastra/core/tools';\n\nimport {\n boardForWorkItem,\n createBoardRegistry,\n resolvePhaseSemantics,\n workItemPhaseSemantics,\n} from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport { recordSessionRunStart } from '../session/run-audit.js';\nimport { resolvePromptInvocation, resolveSkillInvocation } from '../skills/service.js';\nimport type { SkillSession } from '../skills/service.js';\nimport { isHumanActorId } from '../storage/domains/audit/actors.js';\nimport type { AuditRecorder } from '../storage/domains/audit/domain.js';\nimport { withWorkItemFeed } from '../storage/domains/comments/feed-context.js';\nimport type { FactoryFeedReader } from '../storage/domains/comments/feed-context.js';\nimport type {\n FactoryDeferredDecisionRecord,\n FactoryDispatchFailureCode,\n FactoryPendingStartRecord,\n FactoryRunBindingRecord,\n WorkItemRow,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport { FACTORY_RULE_MATERIALIZATION_KEY } from '../storage/domains/work-items/base.js';\nimport { FactoryDispatchError, factoryDispatchFailureCode, factoryDispatchFailureMetadata } from './dispatch-errors.js';\nimport type { FactoryTransitionService } from './transition-service.js';\nimport type { FactoryCommitDecision, FactoryRuleActor, FactoryRuleCausalEntry } from './types.js';\nimport { externallyAuthoredWorkItem, FACTORY_RULE_STAGES } from './types.js';\nimport {\n assertFactoryDecisionTarget,\n MAX_FACTORY_RULE_CAUSAL_DEPTH,\n validateFactoryRuleDecision,\n} from './validation.js';\n\nconst LEASE_MS = 30_000;\nconst POLL_MS = 1_000;\nconst BATCH_SIZE = 10;\nconst MAX_ATTEMPTS = 5;\n\n// Enough for a run that re-plans after reading its own approval, few enough\n// that an agent looping on submit_plan reaches a person instead of a bill.\nconst MAX_PLAN_APPROVALS = 3;\nconst MAX_ERROR_LENGTH = 512;\nconst MAX_BACKOFF_MS = 60_000;\n// A run the registry no longer shows never started or ended unobserved:\n// checked at this cadence, and failed for retry.\nconst RUN_REGISTRY_HEARTBEAT_MS = 10 * 60_000;\n// A run the registry still shows past this is a hang, not a slow run: failed\n// terminally so the lease and the in-flight slot come back.\nconst SKILL_COMPLETION_OBSERVATION_TIMEOUT_MS = 6 * 60 * 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// Staleness sweep: legacy/leaked active bindings (item deleted, transition\n// path bypassed, or pre-dating terminal-stage revocation) are revoked on a\n// slow cadence so the per-tick reconcile walk stays bounded.\nconst STALE_BINDING_SWEEP_INTERVAL_MS = 10 * 60_000;\nconst STALE_BINDING_TTL_MS = 24 * 60 * 60_000;\n// The bound-thread reconcile walk reads a cursor + messages per binding; it\n// exists to catch results missed at run end, so it runs on a slow cadence off\n// the claim path rather than on every 1s tick.\nconst RECONCILE_INTERVAL_MS = 30_000;\n\n// Rescheduling a failure that can never succeed only delays the moment a person sees why.\nfunction isTerminalFailure(attempts: number, failureCode: FactoryDispatchFailureCode): boolean {\n return attempts >= MAX_ATTEMPTS || !factoryDispatchFailureMetadata(failureCode).canRetry;\n}\n\nfunction watchRun(\n session: Pick<DispatcherSession, 'subscribe' | 'respondToToolSuspension'>,\n {\n timeoutMs,\n approvePlans,\n onAgentEnd,\n label,\n runStillActive,\n }: {\n timeoutMs: number;\n approvePlans: boolean;\n onAgentEnd?: () => Promise<boolean>;\n label: string;\n /** Level-triggered check against the run registry, consulted at every heartbeat the edge-triggered wait misses. */\n runStillActive: () => boolean;\n },\n) {\n let resolveAgentEnd!: () => void;\n let agentEnd!: Promise<void>;\n let endReason: 'complete' | 'aborted' | 'error' | 'suspended' | undefined;\n let supersededAtEnd: Promise<boolean> | undefined;\n let parked: { toolName: string; toolCallId: string } | undefined;\n // Re-armed before a redelivery so the second send waits on its own run's\n // ending rather than seeing the one that already resolved.\n const arm = () => {\n endReason = undefined;\n supersededAtEnd = undefined;\n agentEnd = new Promise<void>(resolve => {\n resolveAgentEnd = resolve;\n });\n };\n arm();\n const unsubscribe = session.subscribe(event => {\n if (event.type === 'agent_end') {\n endReason = event.reason;\n supersededAtEnd = onAgentEnd?.();\n resolveAgentEnd();\n return;\n }\n if (event.type === 'tool_suspended') {\n parked = { toolName: event.toolName, toolCallId: event.toolCallId };\n return;\n }\n if (event.type === 'tool_suspension_cancelled' && parked?.toolCallId === event.toolCallId) {\n parked = undefined;\n }\n });\n // Failing a run the registry still shows would kick off a duplicate into a busy session.\n const wait = async () => {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n const heartbeatMs = Math.min(RUN_REGISTRY_HEARTBEAT_MS, deadline - Date.now());\n if (await waitForAgentEndOrTimeout(agentEnd, heartbeatMs)) return true;\n if (!runStillActive()) return false;\n }\n throw new FactoryDispatchError(\n 'run_overdue',\n `${label} is still in flight after ${Math.round(timeoutMs / 3_600_000)} hours and needs a person.`,\n );\n };\n\n return {\n arm,\n wait,\n supersededAtEnd: () => supersededAtEnd,\n endReason: () => endReason,\n close: unsubscribe,\n /** The run's own verdict, thrown as what the dispatcher should record. */\n async settle(): Promise<void> {\n let observed = await wait();\n // Past the cap the plan stays parked: the person it waits for sees it in the inbox.\n if (approvePlans) {\n for (let approvals = 0; parked?.toolName === 'submit_plan' && approvals < MAX_PLAN_APPROVALS; approvals += 1) {\n const { toolCallId } = parked;\n parked = undefined;\n arm();\n await session.respondToToolSuspension({ resumeData: { action: 'approved' }, toolCallId });\n observed = await wait();\n }\n }\n if (!observed) {\n // A completed decision with no observed run end is exactly the\n // silent-stall failure mode: the card advances while nobody works it.\n // Fail non-terminally so the attempts/backoff machinery redelivers —\n // the delivery generation guarantees the retry sends a fresh kickoff\n // instead of hitting the replay guard.\n throw new Error(`${label} terminal event was not observed before timeout.`);\n }\n if (endReason === 'error') throw new Error(`${label} ended in error.`);\n if (endReason === 'aborted') {\n // Retryable, though an abort reads as deliberate. The stream does not\n // say who aborted, and in practice the dominant cause is the process\n // going away underneath the run — an operator restarting the server —\n // not anyone deciding this work should stop. Treating that as terminal\n // dead-ends the card at attempt 1 with nothing on the board to press. A\n // spurious retry is bounded by MAX_ATTEMPTS; a dead card costs a human\n // a manual nudge.\n throw new Error(`${label} was aborted before it finished.`);\n }\n },\n };\n}\n\nfunction waitForAgentEndOrTimeout(agentEnd: Promise<void>, timeoutMs: number): Promise<boolean> {\n return new Promise(resolve => {\n const timeout = setTimeout(() => resolve(false), timeoutMs);\n timeout.unref?.();\n void agentEnd.then(() => {\n clearTimeout(timeout);\n resolve(true);\n });\n });\n}\n\ninterface ThreadSwitchSession {\n thread: {\n requireId(): string;\n switch(input: { threadId: string }): Promise<unknown>;\n };\n}\n\ninterface FactoryNotificationResult {\n persisted?: Promise<unknown>;\n accepted?: Promise<{\n action?: string;\n output?: { consumeStream(): Promise<unknown> };\n }>;\n}\n\ninterface DispatcherSession extends SkillSession {\n thread: {\n switch(input: { threadId: string }): Promise<unknown>;\n listActiveMessages(): Promise<Array<{ id: string }>>;\n };\n stream: { isActive(): boolean };\n abort(): void;\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 respondToToolSuspension(input: { resumeData: SubmitPlanResumeData; toolCallId?: string }): Promise<void>;\n}\n\ntype FactoryController = Pick<AgentController<MastraCodeState>, 'getSessionByResource' | 'listActiveThreadRuns'>;\ntype BoundDispatcherSession = Session<MastraCodeState>;\n\nfunction factoryRequestContext(input: {\n session: BoundDispatcherSession;\n binding: FactoryRunBindingRecord;\n userId: string;\n orgId: string;\n}): RequestContext {\n const { session, binding, userId, orgId } = input;\n const requestContext = new RequestContext();\n requestContext.set('user', { workosId: userId, organizationId: orgId });\n const modeId = session.mode.get();\n requestContext.set('controller', {\n state: session.state.get(),\n getState: () => session.state.get(),\n threadId: binding.threadId,\n resourceId: binding.resourceId,\n session: {\n id: session.identity.getId(),\n ownerId: session.identity.getOwnerId(),\n modeId,\n modelId: session.model.get() ?? '',\n },\n workspace: session.getWorkspace(),\n });\n return requestContext;\n}\n\nexport interface FactoryBindingPreparationInput {\n record: FactoryDeferredDecisionRecord;\n item: WorkItemRow;\n role: string;\n}\n\nexport interface FactoryDecisionDispatcherOptions {\n audit?: AuditRecorder;\n controller: FactoryController;\n transitionService: Pick<FactoryTransitionService, 'transition'>;\n storage: WorkItemsStorage;\n /** Installed boards; defaults to the built-in Work and Review boards. */\n boards?: BoardRegistry;\n ownerId?: string;\n /** `false` parks `invokeSkill` effects as `proposed`; every other effect still runs. */\n isAutoRunEnabled: (tenant: { orgId: string; factoryProjectId: string }) => Promise<boolean>;\n /** `true` lets the dispatcher answer a run's plan itself, so started work carries to Done. */\n autoApprovePlans?: (tenant: { orgId: string; factoryProjectId: string }) => Promise<boolean>;\n reconcileToolResults?: () => Promise<void>;\n prepareBinding?: (input: FactoryBindingPreparationInput) => Promise<void>;\n primeCredentials?: (tenant: { orgId: string; userId: string }) => Promise<void>;\n /** Injects the work item's recent comments into skill-invocation kickoffs. */\n feedReader?: FactoryFeedReader;\n resolveLinkedWorkItemParentId?: (input: {\n orgId: string;\n factoryProjectId: string;\n decision: Extract<FactoryCommitDecision, { type: 'upsertLinkedWorkItem' }>;\n }) => Promise<string | null>;\n maxInFlight?: number;\n /** How often the stale-binding sweep runs. Defaults to 10 minutes. */\n staleBindingSweepIntervalMs?: number;\n /** Active bindings older than this are revoked by the sweep. Defaults to 24 hours. */\n staleBindingTtlMs?: number;\n /** How often the bound-thread reconcile walk runs. Defaults to 30 seconds. */\n reconcileIntervalMs?: number;\n /**\n * How long a run the registry still shows in flight is observed before it fails as overdue. Defaults to 6 hours.\n * A run the registry no longer shows fails for retry at the next ten-minute heartbeat instead.\n */\n skillCompletionObservationTimeoutMs?: number;\n}\n\nfunction positiveMs(value: number | undefined, fallback: number): number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;\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 externalActor(actor: FactoryDeferredDecisionRecord['actor']): boolean {\n return actor !== null && actor.type !== 'human' && actor.type !== 'agent' && actor.type !== 'system';\n}\n\n/** A run start asks for consent; an external event asks before pulling a card back into a working lane. */\nfunction requestsConsent(\n boards: BoardRegistry,\n record: FactoryDeferredDecisionRecord,\n decision: FactoryCommitDecision,\n): boolean {\n if (decision.type === 'invokeSkill') return true;\n if (decision.type !== 'transition' || !externalActor(record.actor)) return false;\n // Fail closed: a phase the installed board does not declare is treated as working, so consent is asked.\n return (resolvePhaseSemantics(boards, decision.board, decision.stage)?.kind ?? 'working') === 'working';\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 send: () => Promise<FactoryNotificationResult>,\n requireDelivery = false,\n): Promise<{ action?: string } | undefined> {\n try {\n const notification = await send();\n const [, accepted] = await Promise.all([notification.persisted, notification.accepted]);\n if (!accepted) {\n if (requireDelivery) {\n throw new FactoryDispatchError(\n 'notification_delivery_failed',\n 'Factory notification was persisted without agent delivery.',\n );\n }\n return undefined;\n }\n if (!requireDelivery) return accepted;\n if (accepted.action === 'wake') {\n if (!accepted.output) {\n throw new FactoryDispatchError('notification_delivery_failed', 'Factory notification wake had no output.');\n }\n await accepted.output.consumeStream();\n return accepted;\n }\n if (accepted.action !== 'deliver') {\n throw new FactoryDispatchError(\n 'notification_delivery_failed',\n `Factory notification did not reach the agent (${String(accepted.action)}).`,\n );\n }\n return accepted;\n } catch (error) {\n if (error instanceof FactoryDispatchError) throw error;\n throw new FactoryDispatchError(\n 'notification_delivery_failed',\n `Factory notification delivery failed: ${sanitizeDispatchError(error)}`,\n { cause: error },\n );\n }\n}\n\nexport class FactoryDecisionDispatcher {\n readonly #audit?: AuditRecorder;\n readonly #controller: FactoryController;\n readonly #transitionService: Pick<FactoryTransitionService, 'transition'>;\n readonly #boards: BoardRegistry;\n readonly #storage: WorkItemsStorage;\n readonly #ownerId: string;\n readonly #isAutoRunEnabled: (tenant: { orgId: string; factoryProjectId: string }) => Promise<boolean>;\n readonly #autoApprovePlans?: (tenant: { orgId: string; factoryProjectId: string }) => Promise<boolean>;\n readonly #reconcileToolResults?: () => Promise<void>;\n readonly #prepareBinding?: (input: FactoryBindingPreparationInput) => Promise<void>;\n readonly #primeCredentials?: (tenant: { orgId: string; userId: string }) => Promise<void>;\n readonly #feedReader?: FactoryFeedReader;\n readonly #resolveLinkedWorkItemParentId?: FactoryDecisionDispatcherOptions['resolveLinkedWorkItemParentId'];\n readonly #maxInFlight: number;\n readonly #staleBindingSweepIntervalMs: number;\n readonly #staleBindingTtlMs: number;\n #lastStaleBindingSweepAt?: Date;\n readonly #reconcileIntervalMs: number;\n readonly #skillCompletionObservationTimeoutMs: number;\n #lastReconcileAt?: Date;\n #reconcileInFlight?: Promise<void>;\n #timer?: ReturnType<typeof setInterval>;\n #activeClaim?: Promise<void>;\n readonly #inFlight = new Set<Promise<void>>();\n\n constructor(options: FactoryDecisionDispatcherOptions) {\n this.#audit = options.audit;\n this.#controller = options.controller;\n this.#transitionService = options.transitionService;\n this.#boards = options.boards ?? createBoardRegistry();\n this.#storage = options.storage;\n this.#ownerId = options.ownerId ?? `factory-dispatcher:${randomUUID()}`;\n this.#isAutoRunEnabled = options.isAutoRunEnabled;\n this.#autoApprovePlans = options.autoApprovePlans;\n this.#reconcileToolResults = options.reconcileToolResults;\n this.#prepareBinding = options.prepareBinding;\n this.#primeCredentials = options.primeCredentials;\n this.#feedReader = options.feedReader;\n this.#resolveLinkedWorkItemParentId = options.resolveLinkedWorkItemParentId;\n const maxInFlight = options.maxInFlight ?? MAX_IN_FLIGHT;\n this.#maxInFlight = Number.isFinite(maxInFlight) && maxInFlight > 0 ? Math.floor(maxInFlight) : MAX_IN_FLIGHT;\n this.#staleBindingSweepIntervalMs = positiveMs(\n options.staleBindingSweepIntervalMs,\n STALE_BINDING_SWEEP_INTERVAL_MS,\n );\n this.#staleBindingTtlMs = positiveMs(options.staleBindingTtlMs, STALE_BINDING_TTL_MS);\n this.#reconcileIntervalMs = positiveMs(options.reconcileIntervalMs, RECONCILE_INTERVAL_MS);\n this.#skillCompletionObservationTimeoutMs = positiveMs(\n options.skillCompletionObservationTimeoutMs,\n SKILL_COMPLETION_OBSERVATION_TIMEOUT_MS,\n );\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 // Fire-and-forget like the reconcile walk: the sweep reads every active\n // binding, so awaiting it would stretch the tick as the active set grows.\n void this.#maybeSweepStaleBindings(now);\n this.#maybeReconcileToolResults(now);\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 /**\n * Throttled, coalesced, non-blocking bound-thread reconcile: dispatch\n * claiming never waits behind cursor + message reads, and overlapping runs\n * are skipped while one is still in flight.\n */\n #maybeReconcileToolResults(now: Date): void {\n if (!this.#reconcileToolResults || this.#reconcileInFlight) return;\n if (this.#lastReconcileAt && now.getTime() - this.#lastReconcileAt.getTime() < this.#reconcileIntervalMs) return;\n this.#lastReconcileAt = now;\n const run = this.#reconcileToolResults()\n .catch(error => {\n console.error('Factory tool-result reconcile failed', sanitizeDispatchError(error));\n })\n .finally(() => {\n this.#reconcileInFlight = undefined;\n });\n this.#reconcileInFlight = run;\n this.#track(run);\n }\n\n /** Slow-cadence revocation of leaked/legacy bindings; failures never block the claim path. */\n async #maybeSweepStaleBindings(now: Date): Promise<void> {\n // The first tick only anchors the cadence: sweeping at boot would race the\n // startup reconcile that is still draining trailing tool results.\n if (!this.#lastStaleBindingSweepAt) {\n this.#lastStaleBindingSweepAt = now;\n return;\n }\n if (now.getTime() - this.#lastStaleBindingSweepAt.getTime() < this.#staleBindingSweepIntervalMs) return;\n this.#lastStaleBindingSweepAt = now;\n try {\n const revoked = await this.#storage.revokeStaleRunBindings({\n olderThan: new Date(now.getTime() - this.#staleBindingTtlMs),\n now,\n });\n if (revoked > 0) console.info(`Factory stale-binding sweep revoked ${revoked} binding(s)`);\n } catch (error) {\n console.error('Factory stale-binding sweep failed', sanitizeDispatchError(error));\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 let executionCompleted = false;\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 if (await this.#needsApproval(record, decision)) {\n const proposed = await this.#storage.proposeDeferredDecision(leaseIdentity(record, this.#ownerId), new Date());\n if (!proposed) throw new Error('Factory decision lease was lost before approval could be requested.');\n return;\n }\n await this.#supersedeProposals(record, decision);\n await this.#withLease(\n async leaseExpiresAt =>\n this.#storage.renewDeferredDecisionLease(leaseIdentity(record, this.#ownerId), leaseExpiresAt),\n async () => this.#executeDecision(record, decision),\n );\n executionCompleted = true;\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 failureCode = factoryDispatchFailureCode(error);\n const terminal = isTerminalFailure(record.attempts, failureCode);\n const failed = await this.#storage.failDeferredDecision({\n ...leaseIdentity(record, this.#ownerId),\n now: new Date(),\n availableAt: retryAt(now, record.attempts),\n lastError: sanitizeDispatchError(error),\n failureCode,\n terminal,\n advanceDeliveryGeneration: !executionCompleted,\n });\n if (terminal && failed) await this.#supersedeFailureOnSettledCard(failed);\n }\n }\n\n /**\n * Startup repair and terminal cleanup already settle a failed effect on a done or canceled\n * card as superseded; a failure landing after the card settled would otherwise page a person\n * until the next restart.\n */\n async #supersedeFailureOnSettledCard(record: FactoryDeferredDecisionRecord): Promise<void> {\n if (!record.workItemId) return;\n try {\n const item = await this.#storage.get({ orgId: record.orgId, id: record.workItemId });\n if (!item || workItemPhaseSemantics(this.#boards, item)?.kind !== 'terminal') return;\n await this.#storage.supersedeDecisionsForWorkItem({\n orgId: record.orgId,\n factoryProjectId: record.factoryProjectId,\n workItemId: record.workItemId,\n supersededAt: new Date(),\n });\n } catch (error) {\n // Best-effort: the row is already failed, and the next restart repairs it.\n console.error('Factory settled-card supersede failed', sanitizeDispatchError(error));\n }\n }\n\n /**\n * A proposal is a question: \"should this run start?\" Once that run is\n * starting anyway — because a person approved a later copy, or armed the item\n * — the question has been answered and the card must stop asking it. Left\n * alone the badge outlives the work it describes, and the one affordance that\n * means \"the loop is stopped, answer this\" cries wolf.\n */\n async #supersedeProposals(record: FactoryDeferredDecisionRecord, decision: FactoryCommitDecision): Promise<void> {\n if (decision.type !== 'invokeSkill' || !record.workItemId) return;\n try {\n await this.#storage.supersedeDecisionsForWorkItem({\n orgId: record.orgId,\n factoryProjectId: record.factoryProjectId,\n workItemId: record.workItemId,\n role: decision.role,\n supersededAt: new Date(),\n });\n } catch (error) {\n // Best-effort: a stale badge is not worth failing the run it describes.\n console.error('Factory proposal supersede failed', sanitizeDispatchError(error));\n }\n }\n\n // Effects a person owns: starting a run (compute + code execution), and an\n // external event pulling a card back into a working lane.\n async #needsApproval(record: FactoryDeferredDecisionRecord, decision: FactoryCommitDecision): Promise<boolean> {\n if (record.approvedAt !== null || !requestsConsent(this.#boards, record, decision)) return false;\n // Withholding auto-run decides what the Factory may pick up on its own, not\n // whether it may finish work a person already handed it. Once someone starts\n // an item, the runs that carry it to review are that same request continuing.\n const item = record.workItemId ? await this.#storage.get({ orgId: record.orgId, id: record.workItemId }) : null;\n // Neither arming nor auto-run is standing consent for code from outside the write-access\n // circle: only a run pre-approved by a person's gesture or its own agent's governed move passes.\n if (item && externallyAuthoredWorkItem(item)) return true;\n if (item?.autonomyArmedAt != null) return false;\n return !(await this.#isAutoRunEnabled({ orgId: record.orgId, factoryProjectId: record.factoryProjectId }));\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 replay = await this.#storage.getTransitionResultByIngress(\n record.orgId,\n record.factoryProjectId,\n `decision:${record.idempotencyKey}`,\n );\n if (!replay) assertFactoryDecisionTarget(decision, this.#boards, item.board ?? undefined);\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: record.approvedBy\n ? { type: 'human', id: record.approvedBy }\n : { type: 'system', id: 'factory-rule-dispatcher' },\n ingress: { type: 'rule', identity: `decision:${record.idempotencyKey}` },\n cause: 'rule_decision',\n causalChain: nextChain,\n ...(decision.reenter ? { reenter: true } : {}),\n });\n if (result.status === 'rejected') throw new Error(`${result.code}: ${result.reason}`);\n const transitionMessage = decision.message;\n if (!transitionMessage) 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, transitionMessage.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 session = await this.#findSession(binding);\n if (!session) return;\n const requestContext = factoryRequestContext({\n session,\n binding,\n userId: startedBy,\n orgId: record.orgId,\n });\n await awaitNotification(\n () =>\n session.sendNotificationSignal(\n {\n source: 'factory',\n kind: 'rule-message',\n summary: transitionMessage.text,\n priority: 'high',\n payload: { message: transitionMessage.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 // A retry for a role the card has already been handed past cannot win:\n // no seat can be minted for it, and the work it was for is done.\n if (await this.#roleSuperseded(record, decision.role)) return;\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 session = await this.#requireSession(binding);\n const requestContext = factoryRequestContext({\n session,\n binding,\n userId: startedBy,\n orgId: record.orgId,\n });\n const resolved =\n decision.skillName === undefined\n ? await resolvePromptInvocation(this.#controller, {\n resourceId: binding.resourceId,\n prompt: decision.prompt,\n })\n : await resolveSkillInvocation(this.#controller, {\n resourceId: binding.resourceId,\n name: decision.skillName,\n arguments: decision.arguments,\n });\n await this.#switchThread(session, binding);\n const deliveryId =\n record.deliveryGeneration === 0 ? record.id : `${record.id}:retry:${record.deliveryGeneration}`;\n const delivered = await session.thread.listActiveMessages();\n if (delivered.some(message => message.id === deliveryId)) return;\n // Safe under the replay guard above: it matches deliveryId, never prompt content.\n const kickoffContents = await withWorkItemFeed(\n this.#feedReader,\n { orgId: record.orgId, factoryProjectId: record.factoryProjectId, workItemId: record.workItemId },\n resolved.message,\n );\n if (decision.cancelInFlight && session.stream.isActive()) session.abort();\n const precedingMessage = decision.precedingMessage;\n if (precedingMessage) {\n await awaitNotification(() =>\n session.sendNotificationSignal(\n {\n source: 'factory',\n kind: 'stage-transition',\n summary: precedingMessage,\n priority: 'medium',\n payload: { message: 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 // The run's own verdict, not the delivery's. A signal can reach the\n // agent perfectly and the run still die on a provider error or be\n // cancelled mid-flight; without this the decision reports success and\n // the break is invisible on the card.\n const run = watchRun(session, {\n timeoutMs: this.#skillCompletionObservationTimeoutMs,\n approvePlans: await this.#plansAreAutoApproved(record, item),\n onAgentEnd: () => this.#roleSuperseded(record, decision.role),\n label: 'Factory skill run',\n runStillActive: () =>\n this.#controller.listActiveThreadRuns().some(active => active.threadId === binding.threadId),\n });\n\n const sendKickoff = async () => {\n const result = session.sendSignal(\n {\n id: deliveryId,\n type: 'user',\n tagName: 'user',\n contents: kickoffContents,\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 return settled;\n };\n\n try {\n let settled = await sendKickoff();\n if (settled.action === 'deliver') {\n // `deliver` means the signal was queued onto a run that was already\n // in flight. If that run ends before draining its queue the prompt\n // is dropped silently: no turn starts, no error surfaces, and the\n // decision reports success while the card sits in its new stage with\n // nobody working. Signals persist under their generation-scoped id\n // (the same identity the replay guard above reads), so confirm the\n // message actually landed in the thread rather than trusting the ack.\n const landed = await session.thread.listActiveMessages();\n if (!landed.some(message => message.id === deliveryId)) {\n // The condition that resolves this is the in-flight run ending, so\n // wait for exactly that and redeliver into the idle session. A\n // backoff cannot work here: retries are sized in seconds and a turn\n // takes minutes, so every attempt lands on the same busy run and\n // the card burns its whole budget without the session ever having\n // had a chance to be free.\n if (!(await run.wait())) {\n throw new Error('Factory skill invocation is waiting on a run that has not ended.');\n }\n run.arm();\n settled = await sendKickoff();\n if (settled.action !== 'wake') {\n throw new Error('Factory skill invocation was queued onto an ending run and never reached the agent.');\n }\n }\n }\n await this.#recordRunStart(\n session,\n binding,\n deliveryId,\n record.approvedBy ?? undefined,\n run.endReason,\n item?.title,\n );\n // A landed `deliver` still runs on the in-flight session, so the run's\n // terminal outcome matters as much as a fresh wake's: a run that ends\n // in error after accepting the prompt has still failed this decision.\n try {\n await run.settle();\n } catch (error) {\n // Roles share one session. When this role handed the card on\n // mid-turn, the next role's kickoff was delivered onto the same\n // run and the turn never ended for us — its eventual verdict is\n // the successor's to record, not ours. Capture that state when the\n // terminal event arrives so a later hand-on cannot erase our failure.\n const superseded = (await run.supersededAtEnd()) ?? (await this.#roleSuperseded(record, decision.role));\n if (!superseded) throw error;\n }\n } finally {\n run.close();\n }\n return;\n }\n case 'sendMessage': {\n const binding = await this.#messageBinding(record, decision);\n // Nobody live on the card means nobody to tell, not a failure to retry.\n if (!binding) return;\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 session = await this.#requireSession(binding);\n const requestContext = factoryRequestContext({\n session,\n binding,\n userId: startedBy,\n orgId: record.orgId,\n });\n await awaitNotification(\n () =>\n 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 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 // A lost completion acknowledgement must not reinterpret a committed\n // materialization against a replacement installation.\n const existing = await this.#storage.getByProjectSource({\n orgId: record.orgId,\n factoryProjectId: record.factoryProjectId,\n source: externalSourceForDecision(decision),\n });\n if (existing?.metadata?.[FACTORY_RULE_MATERIALIZATION_KEY] === record.idempotencyKey) {\n for (const suffix of ['destination', 'initial-entry']) {\n const replay = await this.#storage.getTransitionResultByIngress(\n record.orgId,\n record.factoryProjectId,\n `decision:${record.idempotencyKey}:${existing.id}:${suffix}`,\n );\n if (replay?.status === 'accepted' && replay.stage === decision.stage) return;\n }\n }\n assertFactoryDecisionTarget(decision, this.#boards);\n const definition = this.#boards.get(decision.board);\n if (!definition) throw new Error('Factory decision target board is not installed.');\n const initialPhase = definition.initialPhase;\n const parentWorkItemId =\n record.workItemId ??\n (await this.#resolveLinkedWorkItemParentId?.({\n orgId: record.orgId,\n factoryProjectId: record.factoryProjectId,\n decision,\n })) ??\n null;\n let 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,\n title: decision.title,\n board: decision.board,\n stages: [initialPhase],\n sessions: {},\n metadata: { ...decision.metadata, [FACTORY_RULE_MATERIALIZATION_KEY]: record.idempotencyKey },\n },\n reuseMode: 'preserve',\n });\n const itemBoard = boardForWorkItem(result.item);\n if (itemBoard !== decision.board) {\n throw new Error(`The work item belongs to board \"${itemBoard}\", not \"${decision.board}\".`);\n }\n // A re-evaluation for an already-filed card (poll/reconcile re-emitting\n // \"opened\") resolves the card itself as the triggering item; it is not\n // its own parent.\n if (!result.item.parentWorkItemId && parentWorkItemId && parentWorkItemId !== result.item.id) {\n const item = await this.#storage.setParentWorkItemIfMissing({\n orgId: record.orgId,\n id: result.item.id,\n userId: 'factory-rule-dispatcher',\n parentWorkItemId,\n });\n if (item) result = { ...result, item };\n }\n if (!result.created) {\n // Backfill source facts (e.g. sourceCreatedAt) that older cards were filed\n // without. Fill-only: never overwrite, and never adopt the card as\n // materialized by this decision.\n const missing = Object.fromEntries(\n Object.entries(decision.metadata ?? {}).filter(\n ([key]) => key !== FACTORY_RULE_MATERIALIZATION_KEY && result.item.metadata?.[key] === undefined,\n ),\n );\n if (Object.keys(missing).length > 0) {\n const filled = await this.#storage.update({\n orgId: record.orgId,\n id: result.item.id,\n userId: 'factory-rule-dispatcher',\n patch: { metadata: missing },\n });\n if (filled) result = { ...result, item: filled.item };\n }\n }\n const materializedByDecision = result.item.metadata?.[FACTORY_RULE_MATERIALIZATION_KEY] === record.idempotencyKey;\n if (!materializedByDecision && (decision.stage === initialPhase || !result.item.stages.includes(initialPhase)))\n 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: initialPhase,\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 === initialPhase) 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) {\n throw new FactoryDispatchError(\n 'session_unavailable',\n role ? `No active Factory binding for role ${role}.` : 'No active Factory binding.',\n );\n }\n return binding;\n }\n\n async #messageBinding(\n record: FactoryDeferredDecisionRecord,\n decision: Extract<FactoryCommitDecision, { type: 'sendMessage' }>,\n ): Promise<FactoryRunBindingRecord | undefined> {\n if (decision.prepareBinding && decision.role !== undefined) {\n return this.#requireOrPrepareBinding(record, decision.role);\n }\n return this.#findBinding(record, decision.role);\n }\n\n /**\n * A role is superseded when its binding was revoked by a later role taking\n * the same session (`prepareRunBinding` revokes every other active binding on\n * that session). Only a hand-on — the running agent or a person moving the\n * card — produces that shape, so the role's job is done: its decision is not\n * owed a retry, and whatever ends the shared turn afterwards belongs to the\n * successor's decision. A revoke with no successor (terminal cleanup, an\n * operator pulling the seat) is not supersession and still fails as before.\n * Only a hand-on that happened after this decision was queued counts: a\n * fresh decision for the role (the card came back to it) must still dispatch\n * even though an older revoked binding for that role is on record.\n */\n async #roleSuperseded(record: FactoryDeferredDecisionRecord, role: string): Promise<boolean> {\n if (!record.workItemId) return false;\n const bindings = await this.#storage.listRunBindings(record.orgId, record.factoryProjectId, record.workItemId);\n const own = bindings.filter(candidate => candidate.role === role);\n if (own.some(candidate => candidate.status === 'active')) return false;\n return own.some(\n revoked =>\n revoked.revokedAt !== null &&\n revoked.revokedAt.getTime() >= record.createdAt.getTime() &&\n bindings.some(\n successor =>\n successor.role !== role &&\n successor.status === 'active' &&\n successor.resourceId === revoked.resourceId &&\n successor.sessionId === revoked.sessionId &&\n successor.threadId === revoked.threadId &&\n successor.createdAt.getTime() >= revoked.revokedAt!.getTime(),\n ),\n );\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 FactoryDispatchError(\n 'session_unavailable',\n binding ? 'Bound Factory session not found.' : `No active Factory binding for role ${role}.`,\n );\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 #findSession(binding: FactoryRunBindingRecord): Promise<BoundDispatcherSession | undefined> {\n const session = await this.#controller.getSessionByResource(binding.resourceId);\n if (!session) return undefined;\n await this.#switchThread(session, binding);\n return session;\n }\n\n /** Unset means off: a plan nobody asked us to answer is a plan someone should see. */\n async #plansAreAutoApproved(\n { orgId, factoryProjectId }: { orgId: string; factoryProjectId: string },\n item?: { plansPreapprovedAt: Date | null } | null,\n ): Promise<boolean> {\n if (item?.plansPreapprovedAt) return true;\n return this.#autoApprovePlans ? await this.#autoApprovePlans({ orgId, factoryProjectId }) : false;\n }\n\n async #requireSession(binding: FactoryRunBindingRecord): Promise<BoundDispatcherSession> {\n const session = await this.#findSession(binding);\n if (!session) throw new FactoryDispatchError('session_unavailable', 'Bound Factory session not found.');\n return session;\n }\n\n async #switchThread(session: ThreadSwitchSession, binding: FactoryRunBindingRecord): Promise<void> {\n if (session.thread.requireId() === binding.threadId) return;\n await session.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 #recordRunStart(\n session: BoundDispatcherSession,\n binding: FactoryRunBindingRecord,\n kickoffId: string,\n approvedBy: string | undefined,\n observedEnd: ReturnType<typeof watchRun>['endReason'],\n workItemName: string | undefined,\n ): Promise<void> {\n if (!this.#audit) return;\n const humanApproved = isHumanActorId(approvedBy);\n await recordSessionRunStart(session, {\n audit: this.#audit,\n actorType: humanApproved ? 'human' : 'system',\n observedEnd,\n run: {\n kickoffId,\n bindingId: binding.id,\n role: binding.role,\n startedBy: humanApproved ? approvedBy : 'factory-rule-dispatcher',\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: binding.workItemId,\n workItemName,\n sessionId: binding.sessionId,\n threadId: binding.threadId,\n branch: binding.branch,\n },\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) {\n throw new FactoryDispatchError(\n 'session_unavailable',\n 'Prepared Factory binding is unavailable or revoked.',\n );\n }\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 session = await this.#requireSession(binding);\n const requestContext = factoryRequestContext({\n session,\n binding,\n userId: startedBy,\n orgId: record.orgId,\n });\n // The run's own verdict, not the delivery's: a kickoff delivered\n // into a run that is already terminating is consumed without\n // execution, and completing the pending start on the delivery ack\n // alone strands the card with a success ledger entry.\n const run = watchRun(session, {\n timeoutMs: this.#skillCompletionObservationTimeoutMs,\n approvePlans: await this.#plansAreAutoApproved(record, item),\n label: 'Factory kickoff run',\n runStillActive: () =>\n this.#controller.listActiveThreadRuns().some(active => active.threadId === binding.threadId),\n });\n const sendKickoff = (dedupeKey: string) =>\n awaitNotification(\n () =>\n 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,\n },\n { ifActive: { behavior: 'deliver' }, ifIdle: { behavior: 'wake' }, requestContext },\n ),\n true,\n );\n try {\n let settled = await sendKickoff(`factory-kickoff:${record.kickoffKey}`);\n if (settled?.action === 'deliver') {\n // `deliver` only proves the signal was queued onto a run already\n // in flight. If that run ends without draining its queue the\n // kickoff is dropped silently. There is no per-notification\n // \"processed\" signal, so wait for the in-flight run to end and\n // redeliver into the idle session unconditionally — the\n // generation-scoped dedupeKey defeats inbox dedupe and the\n // kickoff key keeps a duplicate run bounded, while a dropped\n // kickoff strands the card forever.\n if (!(await run.wait())) {\n throw new Error('Factory kickoff is waiting on a run that has not ended.');\n }\n run.arm();\n settled = await sendKickoff(`factory-kickoff:${record.kickoffKey}:retry:${record.attempts}`);\n if (settled?.action !== 'wake') {\n throw new Error('Factory kickoff was queued onto an ending run and never reached the agent.');\n }\n }\n await this.#recordRunStart(\n session,\n binding,\n `factory-kickoff:${record.kickoffKey}:${record.attempts}`,\n startedBy,\n run.endReason,\n item?.title,\n );\n await run.settle();\n } finally {\n run.close();\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 const failureCode = factoryDispatchFailureCode(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 failureCode,\n terminal: isTerminalFailure(record.attempts, failureCode),\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 runRegistryHeartbeatMs: RUN_REGISTRY_HEARTBEAT_MS,\n maxInFlight: MAX_IN_FLIGHT,\n stages: FACTORY_RULE_STAGES,\n} as const;\n"],"mappings":";;;;;;;;;;;;;;AAwCA,MAAM,WAAW;AACjB,MAAM,UAAU;AAChB,MAAM,aAAa;AACnB,MAAM,eAAe;AAIrB,MAAM,qBAAqB;AAC3B,MAAM,mBAAmB;AACzB,MAAM,iBAAiB;AAGvB,MAAM,4BAA4B,KAAK;AAGvC,MAAM,0CAA0C,MAAS;AAIzD,MAAM,gBAAgB;AAItB,MAAM,kCAAkC,KAAK;AAC7C,MAAM,uBAAuB,OAAU;AAIvC,MAAM,wBAAwB;AAG9B,SAAS,kBAAkB,UAAkB,aAAkD;CAC7F,OAAO,YAAY,gBAAgB,CAAC,+BAA+B,WAAW,CAAC,CAAC;AAClF;AAEA,SAAS,SACP,SACA,EACE,WACA,cACA,YACA,OACA,kBASF;CACA,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CAGJ,MAAM,YAAY;EAChB,YAAY,KAAA;EACZ,kBAAkB,KAAA;EAClB,WAAW,IAAI,SAAc,YAAW;GACtC,kBAAkB;EACpB,CAAC;CACH;CACA,IAAI;CACJ,MAAM,cAAc,QAAQ,WAAU,UAAS;EAC7C,IAAI,MAAM,SAAS,aAAa;GAC9B,YAAY,MAAM;GAClB,kBAAkB,aAAa;GAC/B,gBAAgB;GAChB;EACF;EACA,IAAI,MAAM,SAAS,kBAAkB;GACnC,SAAS;IAAE,UAAU,MAAM;IAAU,YAAY,MAAM;GAAW;GAClE;EACF;EACA,IAAI,MAAM,SAAS,+BAA+B,QAAQ,eAAe,MAAM,YAC7E,SAAS,KAAA;CAEb,CAAC;CAED,MAAM,OAAO,YAAY;EACvB,MAAM,WAAW,KAAK,IAAI,IAAI;EAC9B,OAAO,KAAK,IAAI,IAAI,UAAU;GAC5B,MAAM,cAAc,KAAK,IAAI,2BAA2B,WAAW,KAAK,IAAI,CAAC;GAC7E,IAAI,MAAM,yBAAyB,UAAU,WAAW,GAAG,OAAO;GAClE,IAAI,CAAC,eAAe,GAAG,OAAO;EAChC;EACA,MAAM,IAAI,qBACR,eACA,GAAG,MAAM,4BAA4B,KAAK,MAAM,YAAY,IAAS,EAAE,2BACzE;CACF;CAEA,OAAO;EACL;EACA;EACA,uBAAuB;EACvB,iBAAiB;EACjB,OAAO;;EAEP,MAAM,SAAwB;GAC5B,IAAI,WAAW,MAAM,KAAK;GAE1B,IAAI,cACF,KAAK,IAAI,YAAY,GAAG,QAAQ,aAAa,iBAAiB,YAAY,oBAAoB,aAAa,GAAG;IAC5G,MAAM,EAAE,eAAe;IACvB,SAAS,KAAA;IACT,IAAI;IACJ,MAAM,QAAQ,wBAAwB;KAAE,YAAY,EAAE,QAAQ,WAAW;KAAG;IAAW,CAAC;IACxF,WAAW,MAAM,KAAK;GACxB;GAEF,IAAI,CAAC,UAMH,MAAM,IAAI,MAAM,GAAG,MAAM,iDAAiD;GAE5E,IAAI,cAAc,SAAS,MAAM,IAAI,MAAM,GAAG,MAAM,iBAAiB;GACrE,IAAI,cAAc,WAQhB,MAAM,IAAI,MAAM,GAAG,MAAM,iCAAiC;EAE9D;CACF;AACF;AAEA,SAAS,yBAAyB,UAAyB,WAAqC;CAC9F,OAAO,IAAI,SAAQ,YAAW;EAC5B,MAAM,UAAU,iBAAiB,QAAQ,KAAK,GAAG,SAAS;EAC1D,QAAQ,QAAQ;EAChB,SAAc,WAAW;GACvB,aAAa,OAAO;GACpB,QAAQ,IAAI;EACd,CAAC;CACH,CAAC;AACH;AAmCA,SAAS,sBAAsB,OAKZ;CACjB,MAAM,EAAE,SAAS,SAAS,QAAQ,UAAU;CAC5C,MAAM,iBAAiB,IAAI,eAAe;CAC1C,eAAe,IAAI,QAAQ;EAAE,UAAU;EAAQ,gBAAgB;CAAM,CAAC;CACtE,MAAM,SAAS,QAAQ,KAAK,IAAI;CAChC,eAAe,IAAI,cAAc;EAC/B,OAAO,QAAQ,MAAM,IAAI;EACzB,gBAAgB,QAAQ,MAAM,IAAI;EAClC,UAAU,QAAQ;EAClB,YAAY,QAAQ;EACpB,SAAS;GACP,IAAI,QAAQ,SAAS,MAAM;GAC3B,SAAS,QAAQ,SAAS,WAAW;GACrC;GACA,SAAS,QAAQ,MAAM,IAAI,KAAK;EAClC;EACA,WAAW,QAAQ,aAAa;CAClC,CAAC;CACD,OAAO;AACT;AA4CA,SAAS,WAAW,OAA2B,UAA0B;CACvE,OAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,KAAK,QAAQ,IAAI,KAAK,MAAM,KAAK,IAAI;AAChG;AAEA,SAAS,sBAAsB,OAAwB;CAErD,QADgB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAA,CAElE,QAAQ,sEAAsE,YAAY,CAAC,CAC3F,MAAM,GAAG,gBAAgB;AAC9B;AAEA,SAAS,QAAQ,KAAW,UAAwB;CAClD,OAAO,IAAI,KAAK,IAAI,QAAQ,IAAI,KAAK,IAAI,MAAQ,KAAK,KAAK,IAAI,GAAG,WAAW,CAAC,GAAG,cAAc,CAAC;AAClG;AAEA,SAAS,0BAA0B,UAA4E;CAC7G,MAAM,CAAC,eAAe,QACpB,SAAS,WAAW,cAChB,CAAC,UAAU,cAAc,IACzB,SAAS,WAAW,iBAClB,CAAC,UAAU,OAAO,IAClB,SAAS,WAAW,iBAClB,CAAC,UAAU,OAAO,IAClB,CAAC,WAAW,QAAQ;CAC9B,OAAO;EAAE;EAAe;EAAM,YAAY,SAAS;EAAW,KAAK,SAAS,OAAO,KAAA;CAAU;AAC/F;AAEA,SAAS,cAAc,QAAyD;CAC9E,MAAM,QAAQ,OAAO;CACrB,IACE,OAAO,SAAS,YAChB,OAAO,MAAM,UAAU,YACvB,OAAO,MAAM,YAAY,aACzB,OAAO,MAAM,oBAAoB,WAEjC,OAAO;EACL,MAAM;EACN,OAAO,MAAM;EACb,SAAS,MAAM;EACf,iBAAiB,MAAM;CACzB;CAEF,OAAO;EAAE,MAAM;EAAU,IAAI;CAA0B;AACzD;AAEA,SAAS,cAAc,OAAwD;CAC7E,OAAO,UAAU,QAAQ,MAAM,SAAS,WAAW,MAAM,SAAS,WAAW,MAAM,SAAS;AAC9F;;AAGA,SAAS,gBACP,QACA,QACA,UACS;CACT,IAAI,SAAS,SAAS,eAAe,OAAO;CAC5C,IAAI,SAAS,SAAS,gBAAgB,CAAC,cAAc,OAAO,KAAK,GAAG,OAAO;CAE3E,QAAQ,sBAAsB,QAAQ,SAAS,OAAO,SAAS,KAAK,CAAC,EAAE,QAAQ,eAAe;AAChG;AAEA,SAAS,cACP,QACA,SACA;CACA,OAAO;EAAE,IAAI,OAAO;EAAI,OAAO,OAAO;EAAO,kBAAkB,OAAO;EAAkB;CAAQ;AAClG;AAEA,eAAe,kBACb,MACA,kBAAkB,OACwB;CAC1C,IAAI;EACF,MAAM,eAAe,MAAM,KAAK;EAChC,MAAM,GAAG,YAAY,MAAM,QAAQ,IAAI,CAAC,aAAa,WAAW,aAAa,QAAQ,CAAC;EACtF,IAAI,CAAC,UAAU;GACb,IAAI,iBACF,MAAM,IAAI,qBACR,gCACA,4DACF;GAEF;EACF;EACA,IAAI,CAAC,iBAAiB,OAAO;EAC7B,IAAI,SAAS,WAAW,QAAQ;GAC9B,IAAI,CAAC,SAAS,QACZ,MAAM,IAAI,qBAAqB,gCAAgC,0CAA0C;GAE3G,MAAM,SAAS,OAAO,cAAc;GACpC,OAAO;EACT;EACA,IAAI,SAAS,WAAW,WACtB,MAAM,IAAI,qBACR,gCACA,iDAAiD,OAAO,SAAS,MAAM,EAAE,GAC3E;EAEF,OAAO;CACT,SAAS,OAAO;EACd,IAAI,iBAAiB,sBAAsB,MAAM;EACjD,MAAM,IAAI,qBACR,gCACA,yCAAyC,sBAAsB,KAAK,KACpE,EAAE,OAAO,MAAM,CACjB;CACF;AACF;AAEA,IAAa,4BAAb,MAAuC;CACrC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,4BAAqB,IAAI,IAAmB;CAE5C,YAAY,SAA2C;EACrD,KAAKA,SAAS,QAAQ;EACtB,KAAKC,cAAc,QAAQ;EAC3B,KAAKC,qBAAqB,QAAQ;EAClC,KAAKC,UAAU,QAAQ,UAAU,oBAAoB;EACrD,KAAKC,WAAW,QAAQ;EACxB,KAAKC,WAAW,QAAQ,WAAW,sBAAsB,WAAW;EACpE,KAAKC,oBAAoB,QAAQ;EACjC,KAAKC,oBAAoB,QAAQ;EACjC,KAAKC,wBAAwB,QAAQ;EACrC,KAAKC,kBAAkB,QAAQ;EAC/B,KAAKC,oBAAoB,QAAQ;EACjC,KAAKC,cAAc,QAAQ;EAC3B,KAAKC,iCAAiC,QAAQ;EAC9C,MAAM,cAAc,QAAQ,eAAe;EAC3C,KAAKC,eAAe,OAAO,SAAS,WAAW,KAAK,cAAc,IAAI,KAAK,MAAM,WAAW,IAAI;EAChG,KAAKC,+BAA+B,WAClC,QAAQ,6BACR,+BACF;EACA,KAAKC,qBAAqB,WAAW,QAAQ,mBAAmB,oBAAoB;EACpF,KAAKC,uBAAuB,WAAW,QAAQ,qBAAqB,qBAAqB;EACzF,KAAKC,uCAAuC,WAC1C,QAAQ,qCACR,uCACF;CACF;CAEA,QAAc;EACZ,IAAI,KAAKE,QAAQ;EACjB,KAAUC,MAAM;EAChB,KAAKD,SAAS,kBAAkB,KAAK,KAAKC,MAAM,GAAG,OAAO;EAC1D,KAAKD,OAAO,QAAQ;CACtB;CAEA,MAAM,OAAsB;EAC1B,IAAI,KAAKA,QAAQ,cAAc,KAAKA,MAAM;EAC1C,KAAKA,SAAS,KAAA;EACd,MAAM,KAAKE;EACX,MAAM,QAAQ,WAAW,CAAC,GAAG,KAAKH,SAAS,CAAC;CAC9C;CAEA,MAAM,QAAQ,sBAAM,IAAI,KAAK,GAAkB;EAC7C,MAAM,QAAQ,IAAI,MAAM,KAAKI,eAAe,GAAG,CAAC;CAClD;;;;;;;;CASA,MAAMA,eAAe,KAA0C;EAG7D,KAAUC,yBAAyB,GAAG;EACtC,KAAKC,2BAA2B,GAAG;EACnC,MAAM,WAAW,KAAKX,eAAe,KAAKK,UAAU;EACpD,IAAI,YAAY,GAAG,OAAO,CAAC;EAC3B,MAAM,QAAQ,KAAK,IAAI,YAAY,QAAQ;EAC3C,MAAM,iBAAiB,IAAI,KAAK,IAAI,QAAQ,IAAI,QAAQ;EAKxD,MAAM,SAAS,MAAM,KAAKd,SAAS,mBAAmB;GACpD,SAAS,KAAKC;GACd;GACA;GACA;EACF,CAAC;EACD,MAAM,iBAAiB,QAAQ,OAAO;EACtC,MAAM,YACJ,iBAAiB,IACb,MAAM,KAAKD,SAAS,uBAAuB;GACzC,SAAS,KAAKC;GACd;GACA;GACA,OAAO;EACT,CAAC,IACD,CAAC;EACP,OAAO,CACL,GAAG,OAAO,KAAI,UAAS,KAAKoB,OAAO,KAAKC,sBAAsB,OAAO,GAAG,CAAC,CAAC,GAC1E,GAAG,UAAU,KAAI,aAAY,KAAKD,OAAO,KAAKE,kBAAkB,UAAU,GAAG,CAAC,CAAC,CACjF;CACF;;;;;;CAOA,2BAA2B,KAAiB;EAC1C,IAAI,CAAC,KAAKnB,yBAAyB,KAAKoB,oBAAoB;EAC5D,IAAI,KAAKC,oBAAoB,IAAI,QAAQ,IAAI,KAAKA,iBAAiB,QAAQ,IAAI,KAAKb,sBAAsB;EAC1G,KAAKa,mBAAmB;EACxB,MAAM,MAAM,KAAKrB,sBAAsB,CAAC,CACrC,OAAM,UAAS;GACd,QAAQ,MAAM,wCAAwC,sBAAsB,KAAK,CAAC;EACpF,CAAC,CAAC,CACD,cAAc;GACb,KAAKoB,qBAAqB,KAAA;EAC5B,CAAC;EACH,KAAKA,qBAAqB;EAC1B,KAAKH,OAAO,GAAG;CACjB;;CAGA,MAAMF,yBAAyB,KAA0B;EAGvD,IAAI,CAAC,KAAKO,0BAA0B;GAClC,KAAKA,2BAA2B;GAChC;EACF;EACA,IAAI,IAAI,QAAQ,IAAI,KAAKA,yBAAyB,QAAQ,IAAI,KAAKhB,8BAA8B;EACjG,KAAKgB,2BAA2B;EAChC,IAAI;GACF,MAAM,UAAU,MAAM,KAAK1B,SAAS,uBAAuB;IACzD,WAAW,IAAI,KAAK,IAAI,QAAQ,IAAI,KAAKW,kBAAkB;IAC3D;GACF,CAAC;GACD,IAAI,UAAU,GAAG,QAAQ,KAAK,uCAAuC,QAAQ,YAAY;EAC3F,SAAS,OAAO;GACd,QAAQ,MAAM,sCAAsC,sBAAsB,KAAK,CAAC;EAClF;CACF;CAEA,OAAO,UAAwC;EAC7C,KAAKG,UAAU,IAAI,QAAQ;EAC3B,SAAc,YAAY,CAAC,CAAC,CAAC,CAAC,WAAW,KAAKA,UAAU,OAAO,QAAQ,CAAC;EACxE,OAAO;CACT;CAEA,MAAME,QAAuB;EAC3B,IAAI,KAAKC,cAAc;EACvB,KAAKA,eAAe,KAAKC,+BAAe,IAAI,KAAK,CAAC,CAAC,CAAC,MAClD,eAAc;GACZ,KAAK,MAAM,YAAY,YACrB,SAAS,OAAM,UAAS;IACtB,QAAQ,MAAM,oCAAoC,sBAAsB,KAAK,CAAC;GAChF,CAAC;EAEL,IACA,UAAS;GACP,QAAQ,MAAM,0CAA0C,sBAAsB,KAAK,CAAC;EACtF,CACF;EACA,IAAI;GACF,MAAM,KAAKD;EACb,UAAU;GACR,KAAKA,eAAe,KAAA;EACtB;CACF;CAEA,MAAMM,kBAAkB,QAAuC,KAA0B;EACvF,IAAI,qBAAqB;EACzB,IAAI;GACF,MAAM,WAAW,4BAA4B,OAAO,UAAU,OAAO,YAAY,MAAM;GACvF,IAAI,SAAS,SAAS,UAAU,MAAM,IAAI,MAAM,2CAA2C;GAC3F,IAAI,MAAM,KAAKI,eAAe,QAAQ,QAAQ,GAAG;IAE/C,IAAI,CAAC,MADkB,KAAK3B,SAAS,wBAAwB,cAAc,QAAQ,KAAKC,QAAQ,mBAAG,IAAI,KAAK,CAAC,GAC9F,MAAM,IAAI,MAAM,qEAAqE;IACpG;GACF;GACA,MAAM,KAAK2B,oBAAoB,QAAQ,QAAQ;GAC/C,MAAM,KAAKC,WACT,OAAM,mBACJ,KAAK7B,SAAS,2BAA2B,cAAc,QAAQ,KAAKC,QAAQ,GAAG,cAAc,GAC/F,YAAY,KAAK6B,iBAAiB,QAAQ,QAAQ,CACpD;GACA,qBAAqB;GAErB,IAAI,CAAC,MADmB,KAAK9B,SAAS,yBAAyB,cAAc,QAAQ,KAAKC,QAAQ,mBAAG,IAAI,KAAK,CAAC,GAC/F,MAAM,IAAI,MAAM,oDAAoD;EACtF,SAAS,OAAO;GACd,MAAM,cAAc,2BAA2B,KAAK;GACpD,MAAM,WAAW,kBAAkB,OAAO,UAAU,WAAW;GAC/D,MAAM,SAAS,MAAM,KAAKD,SAAS,qBAAqB;IACtD,GAAG,cAAc,QAAQ,KAAKC,QAAQ;IACtC,qBAAK,IAAI,KAAK;IACd,aAAa,QAAQ,KAAK,OAAO,QAAQ;IACzC,WAAW,sBAAsB,KAAK;IACtC;IACA;IACA,2BAA2B,CAAC;GAC9B,CAAC;GACD,IAAI,YAAY,QAAQ,MAAM,KAAK8B,+BAA+B,MAAM;EAC1E;CACF;;;;;;CAOA,MAAMA,+BAA+B,QAAsD;EACzF,IAAI,CAAC,OAAO,YAAY;EACxB,IAAI;GACF,MAAM,OAAO,MAAM,KAAK/B,SAAS,IAAI;IAAE,OAAO,OAAO;IAAO,IAAI,OAAO;GAAW,CAAC;GACnF,IAAI,CAAC,QAAQ,uBAAuB,KAAKD,SAAS,IAAI,CAAC,EAAE,SAAS,YAAY;GAC9E,MAAM,KAAKC,SAAS,8BAA8B;IAChD,OAAO,OAAO;IACd,kBAAkB,OAAO;IACzB,YAAY,OAAO;IACnB,8BAAc,IAAI,KAAK;GACzB,CAAC;EACH,SAAS,OAAO;GAEd,QAAQ,MAAM,yCAAyC,sBAAsB,KAAK,CAAC;EACrF;CACF;;;;;;;;CASA,MAAM4B,oBAAoB,QAAuC,UAAgD;EAC/G,IAAI,SAAS,SAAS,iBAAiB,CAAC,OAAO,YAAY;EAC3D,IAAI;GACF,MAAM,KAAK5B,SAAS,8BAA8B;IAChD,OAAO,OAAO;IACd,kBAAkB,OAAO;IACzB,YAAY,OAAO;IACnB,MAAM,SAAS;IACf,8BAAc,IAAI,KAAK;GACzB,CAAC;EACH,SAAS,OAAO;GAEd,QAAQ,MAAM,qCAAqC,sBAAsB,KAAK,CAAC;EACjF;CACF;CAIA,MAAM2B,eAAe,QAAuC,UAAmD;EAC7G,IAAI,OAAO,eAAe,QAAQ,CAAC,gBAAgB,KAAK5B,SAAS,QAAQ,QAAQ,GAAG,OAAO;EAI3F,MAAM,OAAO,OAAO,aAAa,MAAM,KAAKC,SAAS,IAAI;GAAE,OAAO,OAAO;GAAO,IAAI,OAAO;EAAW,CAAC,IAAI;EAG3G,IAAI,QAAQ,2BAA2B,IAAI,GAAG,OAAO;EACrD,IAAI,MAAM,mBAAmB,MAAM,OAAO;EAC1C,OAAO,CAAE,MAAM,KAAKE,kBAAkB;GAAE,OAAO,OAAO;GAAO,kBAAkB,OAAO;EAAiB,CAAC;CAC1G;CAEA,MAAM4B,iBAAiB,QAAuC,UAAgD;EAC5G,MAAM,YAAsC,CAC1C,GAAI,OAAO,aACX;GAAE,WAAW,OAAO;GAAgB,cAAc,SAAS;EAAK,CAClE;EACA,IAAI,UAAU,SAAA,GAAwC,MAAM,IAAI,MAAM,qCAAqC;EAE3G,QAAQ,SAAS,MAAjB;GACE,KAAK,cAAc;IACjB,MAAM,OAAO,MAAM,KAAKE,aAAa,MAAM;IAM3C,IAAI,CAAC,MALgB,KAAKhC,SAAS,6BACjC,OAAO,OACP,OAAO,kBACP,YAAY,OAAO,gBACrB,GACa,4BAA4B,UAAU,KAAKD,SAAS,KAAK,SAAS,KAAA,CAAS;IACxF,MAAM,SAAS,MAAM,KAAKD,mBAAmB,WAAW;KACtD,OAAO,OAAO;KACd,kBAAkB,OAAO;KACzB,YAAY,KAAK;KACjB,OAAO,SAAS;KAChB,OAAO,SAAS;KAChB,kBAAkB,KAAK;KACvB,OAAO,OAAO,aACV;MAAE,MAAM;MAAS,IAAI,OAAO;KAAW,IACvC;MAAE,MAAM;MAAU,IAAI;KAA0B;KACpD,SAAS;MAAE,MAAM;MAAQ,UAAU,YAAY,OAAO;KAAiB;KACvE,OAAO;KACP,aAAa;KACb,GAAI,SAAS,UAAU,EAAE,SAAS,KAAK,IAAI,CAAC;IAC9C,CAAC;IACD,IAAI,OAAO,WAAW,YAAY,MAAM,IAAI,MAAM,GAAG,OAAO,KAAK,IAAI,OAAO,QAAQ;IACpF,MAAM,oBAAoB,SAAS;IACnC,IAAI,CAAC,mBAAmB;IAKxB,MAAM,UAAU,MAAM,KAAKmC,aAAa,QAAQ,kBAAkB,IAAI;IACtE,IAAI,CAAC,SAAS;IACd,MAAM,YAAY,KAAK,SAAS,QAAQ,KAAK,EAAE;IAC/C,IAAI,CAAC,WAAW;IAChB,MAAM,KAAK3B,oBAAoB;KAAE,OAAO,OAAO;KAAO,QAAQ;IAAU,CAAC;IACzE,MAAM,UAAU,MAAM,KAAK4B,aAAa,OAAO;IAC/C,IAAI,CAAC,SAAS;IACd,MAAM,iBAAiB,sBAAsB;KAC3C;KACA;KACA,QAAQ;KACR,OAAO,OAAO;IAChB,CAAC;IACD,MAAM,wBAEF,QAAQ,uBACN;KACE,QAAQ;KACR,MAAM;KACN,SAAS,kBAAkB;KAC3B,UAAU;KACV,SAAS,EAAE,SAAS,kBAAkB,KAAK;KAC3C,UAAU,OAAO;KACjB,WAAW,OAAO;IACpB,GACA;KACE,UAAU,EAAE,UAAU,UAAU;KAChC,QAAQ,EAAE,UAAU,OAAO;KAC3B;IACF,CACF,GACF,IACF;IACA;GACF;GACA,KAAK;IACH,MAAM,KAAKC,kBAAkB,QAAQ,UAAU,SAAS;IACxD;GAEF,KAAK,eAAe;IAGlB,IAAI,MAAM,KAAKC,gBAAgB,QAAQ,SAAS,IAAI,GAAG;IACvD,MAAM,UAAU,MAAM,KAAKC,yBAAyB,QAAQ,SAAS,IAAI;IACzE,MAAM,OAAO,OAAO,aAAa,MAAM,KAAKrC,SAAS,IAAI;KAAE,OAAO,OAAO;KAAO,IAAI,OAAO;IAAW,CAAC,IAAI;IAC3G,MAAM,YAAY,MAAM,SAAS,QAAQ,KAAK,EAAE;IAChD,IAAI,CAAC,WAAW,MAAM,IAAI,MAAM,mBAAmB,QAAQ,GAAG,qCAAqC;IACnG,MAAM,KAAKM,oBAAoB;KAAE,OAAO,OAAO;KAAO,QAAQ;IAAU,CAAC;IACzE,MAAM,UAAU,MAAM,KAAKgC,gBAAgB,OAAO;IAClD,MAAM,iBAAiB,sBAAsB;KAC3C;KACA;KACA,QAAQ;KACR,OAAO,OAAO;IAChB,CAAC;IACD,MAAM,WACJ,SAAS,cAAc,KAAA,IACnB,MAAM,wBAAwB,KAAKzC,aAAa;KAC9C,YAAY,QAAQ;KACpB,QAAQ,SAAS;IACnB,CAAC,IACD,MAAM,uBAAuB,KAAKA,aAAa;KAC7C,YAAY,QAAQ;KACpB,MAAM,SAAS;KACf,WAAW,SAAS;IACtB,CAAC;IACP,MAAM,KAAK0C,cAAc,SAAS,OAAO;IACzC,MAAM,aACJ,OAAO,uBAAuB,IAAI,OAAO,KAAK,GAAG,OAAO,GAAG,SAAS,OAAO;IAE7E,KAAI,MADoB,QAAQ,OAAO,mBAAmB,EAAA,CAC5C,MAAK,YAAW,QAAQ,OAAO,UAAU,GAAG;IAE1D,MAAM,kBAAkB,MAAM,iBAC5B,KAAKhC,aACL;KAAE,OAAO,OAAO;KAAO,kBAAkB,OAAO;KAAkB,YAAY,OAAO;IAAW,GAChG,SAAS,OACX;IACA,IAAI,SAAS,kBAAkB,QAAQ,OAAO,SAAS,GAAG,QAAQ,MAAM;IACxE,MAAM,mBAAmB,SAAS;IAClC,IAAI,kBACF,MAAM,wBACJ,QAAQ,uBACN;KACE,QAAQ;KACR,MAAM;KACN,SAAS;KACT,UAAU;KACV,SAAS,EAAE,SAAS,iBAAiB;KACrC,UAAU,GAAG,OAAO,GAAG;KACvB,WAAW,GAAG,OAAO,eAAe;IACtC,GACA;KACE,UAAU,EAAE,UAAU,UAAU;KAChC,QAAQ,EAAE,UAAU,UAAU;KAC9B;IACF,CACF,CACF;IAMF,MAAM,MAAM,SAAS,SAAS;KAC5B,WAAW,KAAKM;KAChB,cAAc,MAAM,KAAK2B,sBAAsB,QAAQ,IAAI;KAC3D,kBAAkB,KAAKJ,gBAAgB,QAAQ,SAAS,IAAI;KAC5D,OAAO;KACP,sBACE,KAAKvC,YAAY,qBAAqB,CAAC,CAAC,MAAK,WAAU,OAAO,aAAa,QAAQ,QAAQ;IAC/F,CAAC;IAED,MAAM,cAAc,YAAY;KAc9B,MAAM,UAAU,MAbD,QAAQ,WACrB;MACE,IAAI;MACJ,MAAM;MACN,SAAS;MACT,UAAU;KACZ,GAKA;MAAE;MAAgB,iBAAiB;KAAK,CAEf,CAAC,CAAC;KAC7B,IAAI,QAAQ,WAAW,UAAU,QAAQ,WAAW,WAIlD,MAAM,IAAI,MAAM,4DAA4D,OAAO,QAAQ,MAAM,EAAE,GAAG;KAExG,OAAO;IACT;IAEA,IAAI;KACF,IAAI,UAAU,MAAM,YAAY;KAChC,IAAI,QAAQ,WAAW,WASjB;UAAA,EAAC,MADgB,QAAQ,OAAO,mBAAmB,EAAA,CAC3C,MAAK,YAAW,QAAQ,OAAO,UAAU,GAAG;OAOtD,IAAI,CAAE,MAAM,IAAI,KAAK,GACnB,MAAM,IAAI,MAAM,kEAAkE;OAEpF,IAAI,IAAI;OACR,UAAU,MAAM,YAAY;OAC5B,IAAI,QAAQ,WAAW,QACrB,MAAM,IAAI,MAAM,qFAAqF;MAEzG;;KAEF,MAAM,KAAK4C,gBACT,SACA,SACA,YACA,OAAO,cAAc,KAAA,GACrB,IAAI,WACJ,MAAM,KACR;KAIA,IAAI;MACF,MAAM,IAAI,OAAO;KACnB,SAAS,OAAO;MAOd,IAAI,EADgB,MAAM,IAAI,gBAAgB,KAAO,MAAM,KAAKL,gBAAgB,QAAQ,SAAS,IAAI,IACpF,MAAM;KACzB;IACF,UAAU;KACR,IAAI,MAAM;IACZ;IACA;GACF;GACA,KAAK,eAAe;IAClB,MAAM,UAAU,MAAM,KAAKM,gBAAgB,QAAQ,QAAQ;IAE3D,IAAI,CAAC,SAAS;IAEd,MAAM,aADO,OAAO,aAAa,MAAM,KAAK1C,SAAS,IAAI;KAAE,OAAO,OAAO;KAAO,IAAI,OAAO;IAAW,CAAC,IAAI,KAAA,EACnF,SAAS,QAAQ,KAAK,EAAE;IAChD,IAAI,CAAC,WAAW,MAAM,IAAI,MAAM,mBAAmB,QAAQ,GAAG,qCAAqC;IACnG,MAAM,KAAKM,oBAAoB;KAAE,OAAO,OAAO;KAAO,QAAQ;IAAU,CAAC;IACzE,MAAM,UAAU,MAAM,KAAKgC,gBAAgB,OAAO;IAClD,MAAM,iBAAiB,sBAAsB;KAC3C;KACA;KACA,QAAQ;KACR,OAAO,OAAO;IAChB,CAAC;IACD,MAAM,wBAEF,QAAQ,uBACN;KACE,QAAQ;KACR,MAAM;KACN,SAAS,SAAS;KAClB,UAAU,SAAS,YAAY;KAC/B,SAAS,EAAE,SAAS,SAAS,QAAQ;KACrC,UAAU,OAAO;KACjB,WAAW,OAAO;IACpB,GACA;KACE,UAAU,EAAE,UAAU,UAAU;KAChC,QAAQ,EAAE,UAAU,SAAS,gBAAgB,OAAO;KACpD;IACF,CACF,GACF,IACF;IACA;GACF;GACA,KAAK,UAAU;IACb,MAAM,UAAU,MAAM,KAAKK,gBAAgB,MAAM;IACjD,MAAM,UAAU,MAAM,KAAKL,gBAAgB,OAAO;IAClD,MAAM,wBACJ,QAAQ,uBAAuB;KAC7B,QAAQ;KACR,MAAM;KACN,SAAS,SAAS;KAClB,SAAS;MAAE,MAAM,SAAS;MAAM,OAAO,SAAS;KAAM;KACtD,UAAU,OAAO;KACjB,WAAW,OAAO;IACpB,CAAC,CACH;GACF;EACF;CACF;CAEA,MAAMH,kBACJ,QACA,UACA,aACe;EAGf,MAAM,WAAW,MAAM,KAAKnC,SAAS,mBAAmB;GACtD,OAAO,OAAO;GACd,kBAAkB,OAAO;GACzB,QAAQ,0BAA0B,QAAQ;EAC5C,CAAC;EACD,IAAI,UAAU,WAAA,qCAAiD,OAAO,gBACpE,KAAK,MAAM,UAAU,CAAC,eAAe,eAAe,GAAG;GACrD,MAAM,SAAS,MAAM,KAAKA,SAAS,6BACjC,OAAO,OACP,OAAO,kBACP,YAAY,OAAO,eAAe,GAAG,SAAS,GAAG,GAAG,QACtD;GACA,IAAI,QAAQ,WAAW,cAAc,OAAO,UAAU,SAAS,OAAO;EACxE;EAEF,4BAA4B,UAAU,KAAKD,OAAO;EAClD,MAAM,aAAa,KAAKA,QAAQ,IAAI,SAAS,KAAK;EAClD,IAAI,CAAC,YAAY,MAAM,IAAI,MAAM,iDAAiD;EAClF,MAAM,eAAe,WAAW;EAChC,MAAM,mBACJ,OAAO,cACN,MAAM,KAAKS,iCAAiC;GAC3C,OAAO,OAAO;GACd,kBAAkB,OAAO;GACzB;EACF,CAAC,KACD;EACF,IAAI,SAAS,MAAM,KAAKR,SAAS,OAAO;GACtC,OAAO,OAAO;GACd,QAAQ;GACR,kBAAkB,OAAO;GACzB,OAAO;IACL,gBAAgB,0BAA0B,QAAQ;IAClD;IACA,OAAO,SAAS;IAChB,OAAO,SAAS;IAChB,QAAQ,CAAC,YAAY;IACrB,UAAU,CAAC;IACX,UAAU;KAAE,GAAG,SAAS;MAAW,mCAAmC,OAAO;IAAe;GAC9F;GACA,WAAW;EACb,CAAC;EACD,MAAM,YAAY,iBAAiB,OAAO,IAAI;EAC9C,IAAI,cAAc,SAAS,OACzB,MAAM,IAAI,MAAM,mCAAmC,UAAU,UAAU,SAAS,MAAM,GAAG;EAK3F,IAAI,CAAC,OAAO,KAAK,oBAAoB,oBAAoB,qBAAqB,OAAO,KAAK,IAAI;GAC5F,MAAM,OAAO,MAAM,KAAKA,SAAS,2BAA2B;IAC1D,OAAO,OAAO;IACd,IAAI,OAAO,KAAK;IAChB,QAAQ;IACR;GACF,CAAC;GACD,IAAI,MAAM,SAAS;IAAE,GAAG;IAAQ;GAAK;EACvC;EACA,IAAI,CAAC,OAAO,SAAS;GAInB,MAAM,UAAU,OAAO,YACrB,OAAO,QAAQ,SAAS,YAAY,CAAC,CAAC,CAAC,CAAC,QACrC,CAAC,SAAS,QAAA,mCAA4C,OAAO,KAAK,WAAW,SAAS,KAAA,CACzF,CACF;GACA,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,GAAG;IACnC,MAAM,SAAS,MAAM,KAAKA,SAAS,OAAO;KACxC,OAAO,OAAO;KACd,IAAI,OAAO,KAAK;KAChB,QAAQ;KACR,OAAO,EAAE,UAAU,QAAQ;IAC7B,CAAC;IACD,IAAI,QAAQ,SAAS;KAAE,GAAG;KAAQ,MAAM,OAAO;IAAK;GACtD;EACF;EACA,MAAM,yBAAyB,OAAO,KAAK,WAAW,sCAAsC,OAAO;EACnG,IAAI,CAAC,2BAA2B,SAAS,UAAU,gBAAgB,CAAC,OAAO,KAAK,OAAO,SAAS,YAAY,IAC1G;EAEF,MAAM,QAAQ,SAAS;EACvB,IAAI,mBAAmB,OAAO,KAAK;EACnC,IAAI,wBAAwB;GAC1B,MAAM,UAAU,MAAM,KAAKF,mBAAmB,WAAW;IACvD,OAAO,OAAO;IACd,kBAAkB,OAAO;IACzB,YAAY,OAAO,KAAK;IACxB;IACA,OAAO;IACP;IACA,OAAO,cAAc,MAAM;IAC3B,SAAS;KAAE,MAAM;KAAQ,UAAU,YAAY,OAAO,eAAe,GAAG,OAAO,KAAK,GAAG;IAAgB;IACvG,OAAO;IACP;IACA,cAAc;GAChB,CAAC;GACD,IAAI,QAAQ,WAAW,YAAY;IACjC,IAAI,OAAO,SAAS,MAAM,KAAKE,SAAS,OAAO;KAAE,OAAO,OAAO;KAAO,IAAI,OAAO,KAAK;IAAG,CAAC;IAC1F,MAAM,IAAI,MAAM,GAAG,QAAQ,KAAK,IAAI,QAAQ,QAAQ;GACtD;GACA,mBAAmB,QAAQ;EAC7B;EACA,IAAI,SAAS,UAAU,cAAc;EAErC,MAAM,QAAQ,MAAM,KAAKF,mBAAmB,WAAW;GACrD,OAAO,OAAO;GACd,kBAAkB,OAAO;GACzB,YAAY,OAAO,KAAK;GACxB;GACA,OAAO,SAAS;GAChB;GACA,OAAO;IAAE,MAAM;IAAU,IAAI;GAA0B;GACvD,SAAS;IAAE,MAAM;IAAQ,UAAU,YAAY,OAAO,eAAe,GAAG,OAAO,KAAK,GAAG;GAAc;GACrG,OAAO,yBAAyB,6BAA6B;GAC7D;EACF,CAAC;EACD,IAAI,MAAM,WAAW,YAAY,MAAM,IAAI,MAAM,GAAG,MAAM,KAAK,IAAI,MAAM,QAAQ;CACnF;CAEA,MAAMkC,aAAa,QAAuC;EACxD,IAAI,CAAC,OAAO,YAAY,MAAM,IAAI,MAAM,gDAAgD;EACxF,MAAM,OAAO,MAAM,KAAKhC,SAAS,IAAI;GAAE,OAAO,OAAO;GAAO,IAAI,OAAO;EAAW,CAAC;EACnF,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,8BAA8B;EACzD,OAAO;CACT;CAEA,MAAMiC,aACJ,QACA,MAC8C;EAC9C,IAAI,CAAC,OAAO,YAAY,MAAM,IAAI,MAAM,gDAAgD;EAExF,QAAO,MADgB,KAAKjC,SAAS,gBAAgB,OAAO,OAAO,OAAO,kBAAkB,OAAO,UAAU,EAAA,CAE1G,QAAO,cAAa,UAAU,WAAW,aAAa,SAAS,KAAA,KAAa,UAAU,SAAS,KAAK,CAAC,CACrG,MAAM,MAAM,UAAU;GACrB,IAAI,SAAS,KAAA,KAAa,KAAK,SAAS,UAAU,MAAM,SAAS,QAAQ,OAAO;GAChF,IAAI,SAAS,KAAA,KAAa,MAAM,SAAS,UAAU,KAAK,SAAS,QAAQ,OAAO;GAChF,OAAO,MAAM,UAAU,QAAQ,IAAI,KAAK,UAAU,QAAQ,KAAK,KAAK,GAAG,cAAc,MAAM,EAAE;EAC/F,CAAC,CAAC,CAAC;CACP;CAEA,MAAM2C,gBAAgB,QAAuC,MAAiD;EAC5G,MAAM,UAAU,MAAM,KAAKV,aAAa,QAAQ,IAAI;EACpD,IAAI,CAAC,SACH,MAAM,IAAI,qBACR,uBACA,OAAO,sCAAsC,KAAK,KAAK,4BACzD;EAEF,OAAO;CACT;CAEA,MAAMS,gBACJ,QACA,UAC8C;EAC9C,IAAI,SAAS,kBAAkB,SAAS,SAAS,KAAA,GAC/C,OAAO,KAAKL,yBAAyB,QAAQ,SAAS,IAAI;EAE5D,OAAO,KAAKJ,aAAa,QAAQ,SAAS,IAAI;CAChD;;;;;;;;;;;;;CAcA,MAAMG,gBAAgB,QAAuC,MAAgC;EAC3F,IAAI,CAAC,OAAO,YAAY,OAAO;EAC/B,MAAM,WAAW,MAAM,KAAKpC,SAAS,gBAAgB,OAAO,OAAO,OAAO,kBAAkB,OAAO,UAAU;EAC7G,MAAM,MAAM,SAAS,QAAO,cAAa,UAAU,SAAS,IAAI;EAChE,IAAI,IAAI,MAAK,cAAa,UAAU,WAAW,QAAQ,GAAG,OAAO;EACjE,OAAO,IAAI,MACT,YACE,QAAQ,cAAc,QACtB,QAAQ,UAAU,QAAQ,KAAK,OAAO,UAAU,QAAQ,KACxD,SAAS,MACP,cACE,UAAU,SAAS,QACnB,UAAU,WAAW,YACrB,UAAU,eAAe,QAAQ,cACjC,UAAU,cAAc,QAAQ,aAChC,UAAU,aAAa,QAAQ,YAC/B,UAAU,UAAU,QAAQ,KAAK,QAAQ,UAAW,QAAQ,CAChE,CACJ;CACF;CAEA,MAAMqC,yBACJ,QACA,MACkC;EAClC,MAAM,UAAU,MAAM,KAAKJ,aAAa,QAAQ,IAAI;EACpD,IAAI,SAEE;OAAA,MADkB,KAAKpC,YAAY,qBAAqB,QAAQ,UAAU,GACjE,OAAO;EAAA;EAEtB,IAAI,CAAC,KAAKQ,iBACR,MAAM,IAAI,qBACR,uBACA,UAAU,qCAAqC,sCAAsC,KAAK,EAC5F;EAEF,MAAM,OAAO,MAAM,KAAK2B,aAAa,MAAM;EAC3C,MAAM,KAAK3B,gBAAgB;GAAE;GAAQ;GAAM;EAAK,CAAC;EACjD,OAAO,KAAKsC,gBAAgB,QAAQ,IAAI;CAC1C;CAEA,MAAMT,aAAa,SAA+E;EAChG,MAAM,UAAU,MAAM,KAAKrC,YAAY,qBAAqB,QAAQ,UAAU;EAC9E,IAAI,CAAC,SAAS,OAAO,KAAA;EACrB,MAAM,KAAK0C,cAAc,SAAS,OAAO;EACzC,OAAO;CACT;;CAGA,MAAMC,sBACJ,EAAE,OAAO,oBACT,MACkB;EAClB,IAAI,MAAM,oBAAoB,OAAO;EACrC,OAAO,KAAKrC,oBAAoB,MAAM,KAAKA,kBAAkB;GAAE;GAAO;EAAiB,CAAC,IAAI;CAC9F;CAEA,MAAMmC,gBAAgB,SAAmE;EACvF,MAAM,UAAU,MAAM,KAAKJ,aAAa,OAAO;EAC/C,IAAI,CAAC,SAAS,MAAM,IAAI,qBAAqB,uBAAuB,kCAAkC;EACtG,OAAO;CACT;CAEA,MAAMK,cAAc,SAA8B,SAAiD;EACjG,IAAI,QAAQ,OAAO,UAAU,MAAM,QAAQ,UAAU;EACrD,MAAM,QAAQ,OAAO,OAAO,EAAE,UAAU,QAAQ,SAAS,CAAC;CAC5D;CAEA,MAAMV,WACJ,OACA,QACe;EACf,IAAI;EACJ,IAAI,UAAU,QAAQ,QAAQ;EAC9B,MAAM,QAAQ,kBACN;GACJ,UAAU,QAAQ,KAAK,YAAY;IACjC,IAAI;KAEF,IAAI,CAAC,MADiB,MAAM,IAAI,KAAK,KAAK,IAAI,IAAI,QAAQ,CAAC,GAC7C,iCAAiB,IAAI,MAAM,mDAAmD;IAC9F,SAAS,OAAO;KACd,iBAAiB;IACnB;GACF,CAAC;EACH,GACA,KAAK,MAAM,WAAW,CAAC,CACzB;EACA,MAAM,QAAQ;EACd,IAAI;GACF,MAAM,OAAO;GACb,MAAM;GACN,IAAI,gBAAgB,MAAM;EAC5B,UAAU;GACR,cAAc,KAAK;GACnB,MAAM;EACR;CACF;CAEA,MAAMY,gBACJ,SACA,SACA,WACA,YACA,aACA,cACe;EACf,IAAI,CAAC,KAAK7C,QAAQ;EAClB,MAAM,gBAAgB,eAAe,UAAU;EAC/C,MAAM,sBAAsB,SAAS;GACnC,OAAO,KAAKA;GACZ,WAAW,gBAAgB,UAAU;GACrC;GACA,KAAK;IACH;IACA,WAAW,QAAQ;IACnB,MAAM,QAAQ;IACd,WAAW,gBAAgB,aAAa;IACxC,OAAO,QAAQ;IACf,kBAAkB,QAAQ;IAC1B,YAAY,QAAQ;IACpB;IACA,WAAW,QAAQ;IACnB,UAAU,QAAQ;IAClB,QAAQ,QAAQ;GAClB;EACF,CAAC;CACH;CAEA,MAAM0B,sBAAsB,QAAmC,KAA0B;EACvF,IAAI;GACF,MAAM,KAAKO,WACT,OAAM,mBACJ,KAAK7B,SAAS,uBAAuB,cAAc,QAAQ,KAAKC,QAAQ,GAAG,cAAc,GAC3F,YAAY;IACV,IAAI,OAAO,YAAY,MAAM;IAE7B,MAAM,WAAU,MADO,KAAKD,SAAS,gBAAgB,OAAO,OAAO,OAAO,gBAAgB,EAAA,CACjE,MACvB,cAAa,UAAU,OAAO,OAAO,aAAa,UAAU,WAAW,QACzE;IACA,IAAI,CAAC,SACH,MAAM,IAAI,qBACR,uBACA,qDACF;IAIF,MAAM,OAAO,MAAM,KAAKA,SAAS,IAAI;KAAE,OAAO,OAAO;KAAO,IAAI,QAAQ;IAAW,CAAC;IACpF,MAAM,YAAY,MAAM,SAAS,QAAQ,KAAK,EAAE;IAChD,IAAI,CAAC,WAAW,MAAM,IAAI,MAAM,mBAAmB,QAAQ,GAAG,qCAAqC;IACnG,MAAM,KAAKM,oBAAoB;KAAE,OAAO,OAAO;KAAO,QAAQ;IAAU,CAAC;IACzE,MAAM,UAAU,MAAM,KAAKgC,gBAAgB,OAAO;IAClD,MAAM,iBAAiB,sBAAsB;KAC3C;KACA;KACA,QAAQ;KACR,OAAO,OAAO;IAChB,CAAC;IAKD,MAAM,MAAM,SAAS,SAAS;KAC5B,WAAW,KAAKzB;KAChB,cAAc,MAAM,KAAK2B,sBAAsB,QAAQ,IAAI;KAC3D,OAAO;KACP,sBACE,KAAK3C,YAAY,qBAAqB,CAAC,CAAC,MAAK,WAAU,OAAO,aAAa,QAAQ,QAAQ;IAC/F,CAAC;IACD,MAAM,eAAe,cACnB,wBAEI,QAAQ,uBACN;KACE,QAAQ;KACR,MAAM;KACN,SAAS,OAAO;KAChB,UAAU;KACV,SAAS,EAAE,SAAS,OAAO,QAAQ;KACnC,UAAU,OAAO;KACjB;IACF,GACA;KAAE,UAAU,EAAE,UAAU,UAAU;KAAG,QAAQ,EAAE,UAAU,OAAO;KAAG;IAAe,CACpF,GACF,IACF;IACF,IAAI;KACF,IAAI,UAAU,MAAM,YAAY,mBAAmB,OAAO,YAAY;KACtE,IAAI,SAAS,WAAW,WAAW;MASjC,IAAI,CAAE,MAAM,IAAI,KAAK,GACnB,MAAM,IAAI,MAAM,yDAAyD;MAE3E,IAAI,IAAI;MACR,UAAU,MAAM,YAAY,mBAAmB,OAAO,WAAW,SAAS,OAAO,UAAU;MAC3F,IAAI,SAAS,WAAW,QACtB,MAAM,IAAI,MAAM,4EAA4E;KAEhG;KACA,MAAM,KAAK4C,gBACT,SACA,SACA,mBAAmB,OAAO,WAAW,GAAG,OAAO,YAC/C,WACA,IAAI,WACJ,MAAM,KACR;KACA,MAAM,IAAI,OAAO;IACnB,UAAU;KACR,IAAI,MAAM;IACZ;GACF,CACF;GAEA,IAAI,CAAC,MADmB,KAAKzC,SAAS,qBAAqB,cAAc,QAAQ,KAAKC,QAAQ,mBAAG,IAAI,KAAK,CAAC,GAC3F,MAAM,IAAI,MAAM,mDAAmD;EACrF,SAAS,OAAO;GACd,MAAM,cAAc,2BAA2B,KAAK;GACpD,MAAM,KAAKD,SAAS,iBAAiB;IACnC,GAAG,cAAc,QAAQ,KAAKC,QAAQ;IACtC,qBAAK,IAAI,KAAK;IACd,aAAa,QAAQ,KAAK,OAAO,QAAQ;IACzC,WAAW,sBAAsB,KAAK;IACtC;IACA,UAAU,kBAAkB,OAAO,UAAU,WAAW;GAC1D,CAAC;EACH;CACF;AACF;AAEA,MAAa,6BAA6B;CACxC,SAAS;CACT,QAAQ;CACR,WAAW;CACX,aAAa;CACb,gBAAgB;CAChB,cAAc;CACd,qCAAqC;CACrC,wBAAwB;CACxB,aAAa;CACb,QAAQ;AACV"}
1
+ {"version":3,"file":"dispatcher.js","names":["#audit","#controller","#transitionService","#boards","#storage","#ownerId","#isAutoRunEnabled","#autoApprovePlans","#reconcileToolResults","#prepareBinding","#refreshManagedMemorySettings","#primeCredentials","#feedReader","#resolveLinkedWorkItemParentId","#maxInFlight","#staleBindingSweepIntervalMs","#staleBindingTtlMs","#reconcileIntervalMs","#skillCompletionObservationTimeoutMs","#inFlight","#timer","#tick","#activeClaim","#claimAndStart","#maybeSweepStaleBindings","#maybeReconcileToolResults","#track","#dispatchPendingStart","#dispatchDecision","#reconcileInFlight","#lastReconcileAt","#lastStaleBindingSweepAt","#needsApproval","#supersedeProposals","#withLease","#executeDecision","#supersedeFailureOnSettledCard","#requireItem","#findBinding","#findSession","#upsertLinkedItem","#roleSuperseded","#requireOrPrepareBinding","#requireSession","#switchThread","#plansAreAutoApproved","#recordRunStart","#messageBinding","#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, Session } from '@mastra/core/agent-controller';\nimport { RequestContext } from '@mastra/core/request-context';\nimport type { SubmitPlanResumeData } from '@mastra/core/tools';\n\nimport {\n boardForWorkItem,\n createBoardRegistry,\n resolvePhaseSemantics,\n workItemPhaseSemantics,\n} from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport { recordSessionRunStart } from '../session/run-audit.js';\nimport { resolvePromptInvocation, resolveSkillInvocation } from '../skills/service.js';\nimport type { SkillSession } from '../skills/service.js';\nimport { isHumanActorId } from '../storage/domains/audit/actors.js';\nimport type { AuditRecorder } from '../storage/domains/audit/domain.js';\nimport { withWorkItemFeed } from '../storage/domains/comments/feed-context.js';\nimport type { FactoryFeedReader } from '../storage/domains/comments/feed-context.js';\nimport type {\n FactoryDeferredDecisionRecord,\n FactoryDispatchFailureCode,\n FactoryPendingStartRecord,\n FactoryRunBindingRecord,\n WorkItemRow,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport { FACTORY_RULE_MATERIALIZATION_KEY } from '../storage/domains/work-items/base.js';\nimport { FactoryDispatchError, factoryDispatchFailureCode, factoryDispatchFailureMetadata } from './dispatch-errors.js';\nimport type { FactoryTransitionService } from './transition-service.js';\nimport type { FactoryCommitDecision, FactoryRuleActor, FactoryRuleCausalEntry } from './types.js';\nimport { externallyAuthoredWorkItem, FACTORY_RULE_STAGES } from './types.js';\nimport {\n assertFactoryDecisionTarget,\n MAX_FACTORY_RULE_CAUSAL_DEPTH,\n validateFactoryRuleDecision,\n} from './validation.js';\n\nconst LEASE_MS = 30_000;\nconst POLL_MS = 1_000;\nconst BATCH_SIZE = 10;\nconst MAX_ATTEMPTS = 5;\n\n// Enough for a run that re-plans after reading its own approval, few enough\n// that an agent looping on submit_plan reaches a person instead of a bill.\nconst MAX_PLAN_APPROVALS = 3;\nconst MAX_ERROR_LENGTH = 512;\nconst MAX_BACKOFF_MS = 60_000;\n// A run the registry no longer shows never started or ended unobserved:\n// checked at this cadence, and failed for retry.\nconst RUN_REGISTRY_HEARTBEAT_MS = 10 * 60_000;\n// A run the registry still shows past this is a hang, not a slow run: failed\n// terminally so the lease and the in-flight slot come back.\nconst SKILL_COMPLETION_OBSERVATION_TIMEOUT_MS = 6 * 60 * 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// Staleness sweep: legacy/leaked active bindings (item deleted, transition\n// path bypassed, or pre-dating terminal-stage revocation) are revoked on a\n// slow cadence so the per-tick reconcile walk stays bounded.\nconst STALE_BINDING_SWEEP_INTERVAL_MS = 10 * 60_000;\nconst STALE_BINDING_TTL_MS = 24 * 60 * 60_000;\n// The bound-thread reconcile walk reads a cursor + messages per binding; it\n// exists to catch results missed at run end, so it runs on a slow cadence off\n// the claim path rather than on every 1s tick.\nconst RECONCILE_INTERVAL_MS = 30_000;\n\n// Rescheduling a failure that can never succeed only delays the moment a person sees why.\nfunction isTerminalFailure(attempts: number, failureCode: FactoryDispatchFailureCode): boolean {\n return attempts >= MAX_ATTEMPTS || !factoryDispatchFailureMetadata(failureCode).canRetry;\n}\n\n/**\n * Conservative check for observational-memory failures that a retry cannot fix:\n * a provider rejecting the request outright (HTTP 400 / model unsupported /\n * authentication). Anything ambiguous (timeouts, 5xx, rate limits, network\n * blips) is intentionally left retryable to avoid dead-ending a card that would\n * have recovered on its own.\n */\nfunction isPermanentProviderRejection(error: string): boolean {\n const normalized = error.toLowerCase();\n return (\n /\\bhttp(?:\\/\\d(?:\\.\\d)?)?\\s*400\\b/.test(normalized) ||\n /\\bstatus(?:\\s*code)?\\s*[:=]?\\s*400\\b/.test(normalized) ||\n /\\b400\\s*(?:bad request|status)\\b/.test(normalized) ||\n normalized.includes('model not supported') ||\n normalized.includes('model is not supported') ||\n normalized.includes('unsupported model') ||\n normalized.includes('not supported with') ||\n normalized.includes('unauthorized') ||\n normalized.includes('authentication') ||\n (normalized.includes('invalid') && normalized.includes('model'))\n );\n}\n\nfunction watchRun(\n session: Pick<DispatcherSession, 'subscribe' | 'respondToToolSuspension'>,\n {\n timeoutMs,\n approvePlans,\n onAgentEnd,\n label,\n runStillActive,\n }: {\n timeoutMs: number;\n approvePlans: boolean;\n onAgentEnd?: () => Promise<boolean>;\n label: string;\n /** Level-triggered check against the run registry, consulted at every heartbeat the edge-triggered wait misses. */\n runStillActive: () => boolean;\n },\n) {\n let resolveAgentEnd!: () => void;\n let agentEnd!: Promise<void>;\n let endReason: 'complete' | 'aborted' | 'error' | 'suspended' | undefined;\n let supersededAtEnd: Promise<boolean> | undefined;\n let parked: { toolName: string; toolCallId: string } | undefined;\n // Latest observational-memory failure seen on this run. The stream aborts the\n // run when observation/reflection fails, but the abort itself carries no\n // reason — so without this the true cause (e.g. a provider rejecting the OM\n // model) is lost and the abort is retried blindly.\n let omFailure: string | undefined;\n // Re-armed before a redelivery so the second send waits on its own run's\n // ending rather than seeing the one that already resolved.\n const arm = () => {\n endReason = undefined;\n supersededAtEnd = undefined;\n omFailure = undefined;\n agentEnd = new Promise<void>(resolve => {\n resolveAgentEnd = resolve;\n });\n };\n arm();\n const unsubscribe = session.subscribe(event => {\n if (event.type === 'agent_end') {\n endReason = event.reason;\n supersededAtEnd = onAgentEnd?.();\n resolveAgentEnd();\n return;\n }\n if (event.type === 'om_observation_failed' || event.type === 'om_buffering_failed') {\n if (event.error) omFailure = event.error;\n return;\n }\n if (event.type === 'tool_suspended') {\n parked = { toolName: event.toolName, toolCallId: event.toolCallId };\n return;\n }\n if (event.type === 'tool_suspension_cancelled' && parked?.toolCallId === event.toolCallId) {\n parked = undefined;\n }\n });\n // Failing a run the registry still shows would kick off a duplicate into a busy session.\n const wait = async () => {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n const heartbeatMs = Math.min(RUN_REGISTRY_HEARTBEAT_MS, deadline - Date.now());\n if (await waitForAgentEndOrTimeout(agentEnd, heartbeatMs)) return true;\n if (!runStillActive()) return false;\n }\n throw new FactoryDispatchError(\n 'run_overdue',\n `${label} is still in flight after ${Math.round(timeoutMs / 3_600_000)} hours and needs a person.`,\n );\n };\n\n return {\n arm,\n wait,\n supersededAtEnd: () => supersededAtEnd,\n endReason: () => endReason,\n close: unsubscribe,\n /** The run's own verdict, thrown as what the dispatcher should record. */\n async settle(): Promise<void> {\n let observed = await wait();\n // Past the cap the plan stays parked: the person it waits for sees it in the inbox.\n if (approvePlans) {\n for (let approvals = 0; parked?.toolName === 'submit_plan' && approvals < MAX_PLAN_APPROVALS; approvals += 1) {\n const { toolCallId } = parked;\n parked = undefined;\n arm();\n await session.respondToToolSuspension({ resumeData: { action: 'approved' }, toolCallId });\n observed = await wait();\n }\n }\n if (!observed) {\n // A completed decision with no observed run end is exactly the\n // silent-stall failure mode: the card advances while nobody works it.\n // Fail non-terminally so the attempts/backoff machinery redelivers —\n // the delivery generation guarantees the retry sends a fresh kickoff\n // instead of hitting the replay guard.\n throw new Error(`${label} terminal event was not observed before timeout.`);\n }\n if (endReason === 'error') throw new Error(`${label} ended in error.`);\n if (endReason === 'aborted') {\n // An abort that follows an observational-memory failure is not the\n // usual \"process went away\" case — the OM stream deliberately aborted\n // the run because observation/reflection failed. Surface that real\n // cause instead of the generic message, and when it is a permanent\n // provider/config rejection (e.g. the OM model is not accepted by the\n // account) fail terminally so retries stop hammering a run that can\n // never succeed until the configuration changes.\n if (omFailure) {\n if (isPermanentProviderRejection(omFailure)) {\n throw new FactoryDispatchError(\n 'run_configuration_invalid',\n `${label} was aborted by an observational-memory failure that will not succeed on retry: ${omFailure}`,\n );\n }\n throw new Error(`${label} was aborted after an observational-memory failure: ${omFailure}`);\n }\n // Retryable, though an abort reads as deliberate. The stream does not\n // say who aborted, and in practice the dominant cause is the process\n // going away underneath the run — an operator restarting the server —\n // not anyone deciding this work should stop. Treating that as terminal\n // dead-ends the card at attempt 1 with nothing on the board to press. A\n // spurious retry is bounded by MAX_ATTEMPTS; a dead card costs a human\n // a manual nudge.\n throw new Error(`${label} was aborted before it finished.`);\n }\n },\n };\n}\n\nfunction waitForAgentEndOrTimeout(agentEnd: Promise<void>, timeoutMs: number): Promise<boolean> {\n return new Promise(resolve => {\n const timeout = setTimeout(() => resolve(false), timeoutMs);\n timeout.unref?.();\n void agentEnd.then(() => {\n clearTimeout(timeout);\n resolve(true);\n });\n });\n}\n\ninterface ThreadSwitchSession {\n thread: {\n requireId(): string;\n switch(input: { threadId: string }): Promise<unknown>;\n };\n}\n\ninterface FactoryNotificationResult {\n persisted?: Promise<unknown>;\n accepted?: Promise<{\n action?: string;\n output?: { consumeStream(): Promise<unknown> };\n }>;\n}\n\ninterface DispatcherSession extends SkillSession {\n thread: {\n switch(input: { threadId: string }): Promise<unknown>;\n listActiveMessages(): Promise<Array<{ id: string }>>;\n };\n stream: { isActive(): boolean };\n abort(): void;\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 respondToToolSuspension(input: { resumeData: SubmitPlanResumeData; toolCallId?: string }): Promise<void>;\n}\n\ntype FactoryController = Pick<AgentController<MastraCodeState>, 'getSessionByResource' | 'listActiveThreadRuns'>;\ntype BoundDispatcherSession = Session<MastraCodeState>;\n\nfunction factoryRequestContext(input: {\n session: BoundDispatcherSession;\n binding: FactoryRunBindingRecord;\n userId: string;\n orgId: string;\n}): RequestContext {\n const { session, binding, userId, orgId } = input;\n const requestContext = new RequestContext();\n requestContext.set('user', { workosId: userId, organizationId: orgId });\n const modeId = session.mode.get();\n requestContext.set('controller', {\n state: session.state.get(),\n getState: () => session.state.get(),\n threadId: binding.threadId,\n resourceId: binding.resourceId,\n session: {\n id: session.identity.getId(),\n ownerId: session.identity.getOwnerId(),\n modeId,\n modelId: session.model.get() ?? '',\n },\n workspace: session.getWorkspace(),\n });\n return requestContext;\n}\n\nexport interface FactoryBindingPreparationInput {\n record: FactoryDeferredDecisionRecord;\n item: WorkItemRow;\n role: string;\n}\n\nexport interface FactoryDecisionDispatcherOptions {\n audit?: AuditRecorder;\n controller: FactoryController;\n transitionService: Pick<FactoryTransitionService, 'transition'>;\n storage: WorkItemsStorage;\n /** Installed boards; defaults to the built-in Work and Review boards. */\n boards?: BoardRegistry;\n ownerId?: string;\n /** `false` parks `invokeSkill` effects as `proposed`; every other effect still runs. */\n isAutoRunEnabled: (tenant: { orgId: string; factoryProjectId: string }) => Promise<boolean>;\n /** `true` lets the dispatcher answer a run's plan itself, so started work carries to Done. */\n autoApprovePlans?: (tenant: { orgId: string; factoryProjectId: string }) => Promise<boolean>;\n reconcileToolResults?: () => Promise<void>;\n prepareBinding?: (input: FactoryBindingPreparationInput) => Promise<void>;\n /**\n * Re-applies the factory project's current observational-memory settings to a\n * reused session before it runs. Fresh preparation hydrates these settings,\n * but a reused binding keeps the models it was created with; without this a\n * project whose OM models changed keeps observing with the stale ones.\n */\n refreshManagedMemorySettings?: (input: {\n binding: FactoryRunBindingRecord;\n session: BoundDispatcherSession;\n }) => Promise<void>;\n primeCredentials?: (tenant: { orgId: string; userId: string }) => Promise<void>;\n /** Injects the work item's recent comments into skill-invocation kickoffs. */\n feedReader?: FactoryFeedReader;\n resolveLinkedWorkItemParentId?: (input: {\n orgId: string;\n factoryProjectId: string;\n decision: Extract<FactoryCommitDecision, { type: 'upsertLinkedWorkItem' }>;\n }) => Promise<string | null>;\n maxInFlight?: number;\n /** How often the stale-binding sweep runs. Defaults to 10 minutes. */\n staleBindingSweepIntervalMs?: number;\n /** Active bindings older than this are revoked by the sweep. Defaults to 24 hours. */\n staleBindingTtlMs?: number;\n /** How often the bound-thread reconcile walk runs. Defaults to 30 seconds. */\n reconcileIntervalMs?: number;\n /**\n * How long a run the registry still shows in flight is observed before it fails as overdue. Defaults to 6 hours.\n * A run the registry no longer shows fails for retry at the next ten-minute heartbeat instead.\n */\n skillCompletionObservationTimeoutMs?: number;\n}\n\nfunction positiveMs(value: number | undefined, fallback: number): number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;\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 externalActor(actor: FactoryDeferredDecisionRecord['actor']): boolean {\n return actor !== null && actor.type !== 'human' && actor.type !== 'agent' && actor.type !== 'system';\n}\n\n/** A run start asks for consent; an external event asks before pulling a card back into a working lane. */\nfunction requestsConsent(\n boards: BoardRegistry,\n record: FactoryDeferredDecisionRecord,\n decision: FactoryCommitDecision,\n): boolean {\n if (decision.type === 'invokeSkill') return true;\n if (decision.type !== 'transition' || !externalActor(record.actor)) return false;\n // Fail closed: a phase the installed board does not declare is treated as working, so consent is asked.\n return (resolvePhaseSemantics(boards, decision.board, decision.stage)?.kind ?? 'working') === 'working';\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 send: () => Promise<FactoryNotificationResult>,\n requireDelivery = false,\n): Promise<{ action?: string } | undefined> {\n try {\n const notification = await send();\n const [, accepted] = await Promise.all([notification.persisted, notification.accepted]);\n if (!accepted) {\n if (requireDelivery) {\n throw new FactoryDispatchError(\n 'notification_delivery_failed',\n 'Factory notification was persisted without agent delivery.',\n );\n }\n return undefined;\n }\n if (!requireDelivery) return accepted;\n if (accepted.action === 'wake') {\n if (!accepted.output) {\n throw new FactoryDispatchError('notification_delivery_failed', 'Factory notification wake had no output.');\n }\n await accepted.output.consumeStream();\n return accepted;\n }\n if (accepted.action !== 'deliver') {\n throw new FactoryDispatchError(\n 'notification_delivery_failed',\n `Factory notification did not reach the agent (${String(accepted.action)}).`,\n );\n }\n return accepted;\n } catch (error) {\n if (error instanceof FactoryDispatchError) throw error;\n throw new FactoryDispatchError(\n 'notification_delivery_failed',\n `Factory notification delivery failed: ${sanitizeDispatchError(error)}`,\n { cause: error },\n );\n }\n}\n\nexport class FactoryDecisionDispatcher {\n readonly #audit?: AuditRecorder;\n readonly #controller: FactoryController;\n readonly #transitionService: Pick<FactoryTransitionService, 'transition'>;\n readonly #boards: BoardRegistry;\n readonly #storage: WorkItemsStorage;\n readonly #ownerId: string;\n readonly #isAutoRunEnabled: (tenant: { orgId: string; factoryProjectId: string }) => Promise<boolean>;\n readonly #autoApprovePlans?: (tenant: { orgId: string; factoryProjectId: string }) => Promise<boolean>;\n readonly #reconcileToolResults?: () => Promise<void>;\n readonly #prepareBinding?: (input: FactoryBindingPreparationInput) => Promise<void>;\n readonly #refreshManagedMemorySettings?: (input: {\n binding: FactoryRunBindingRecord;\n session: BoundDispatcherSession;\n }) => Promise<void>;\n readonly #primeCredentials?: (tenant: { orgId: string; userId: string }) => Promise<void>;\n readonly #feedReader?: FactoryFeedReader;\n readonly #resolveLinkedWorkItemParentId?: FactoryDecisionDispatcherOptions['resolveLinkedWorkItemParentId'];\n readonly #maxInFlight: number;\n readonly #staleBindingSweepIntervalMs: number;\n readonly #staleBindingTtlMs: number;\n #lastStaleBindingSweepAt?: Date;\n readonly #reconcileIntervalMs: number;\n readonly #skillCompletionObservationTimeoutMs: number;\n #lastReconcileAt?: Date;\n #reconcileInFlight?: Promise<void>;\n #timer?: ReturnType<typeof setInterval>;\n #activeClaim?: Promise<void>;\n readonly #inFlight = new Set<Promise<void>>();\n\n constructor(options: FactoryDecisionDispatcherOptions) {\n this.#audit = options.audit;\n this.#controller = options.controller;\n this.#transitionService = options.transitionService;\n this.#boards = options.boards ?? createBoardRegistry();\n this.#storage = options.storage;\n this.#ownerId = options.ownerId ?? `factory-dispatcher:${randomUUID()}`;\n this.#isAutoRunEnabled = options.isAutoRunEnabled;\n this.#autoApprovePlans = options.autoApprovePlans;\n this.#reconcileToolResults = options.reconcileToolResults;\n this.#prepareBinding = options.prepareBinding;\n this.#refreshManagedMemorySettings = options.refreshManagedMemorySettings;\n this.#primeCredentials = options.primeCredentials;\n this.#feedReader = options.feedReader;\n this.#resolveLinkedWorkItemParentId = options.resolveLinkedWorkItemParentId;\n const maxInFlight = options.maxInFlight ?? MAX_IN_FLIGHT;\n this.#maxInFlight = Number.isFinite(maxInFlight) && maxInFlight > 0 ? Math.floor(maxInFlight) : MAX_IN_FLIGHT;\n this.#staleBindingSweepIntervalMs = positiveMs(\n options.staleBindingSweepIntervalMs,\n STALE_BINDING_SWEEP_INTERVAL_MS,\n );\n this.#staleBindingTtlMs = positiveMs(options.staleBindingTtlMs, STALE_BINDING_TTL_MS);\n this.#reconcileIntervalMs = positiveMs(options.reconcileIntervalMs, RECONCILE_INTERVAL_MS);\n this.#skillCompletionObservationTimeoutMs = positiveMs(\n options.skillCompletionObservationTimeoutMs,\n SKILL_COMPLETION_OBSERVATION_TIMEOUT_MS,\n );\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 // Fire-and-forget like the reconcile walk: the sweep reads every active\n // binding, so awaiting it would stretch the tick as the active set grows.\n void this.#maybeSweepStaleBindings(now);\n this.#maybeReconcileToolResults(now);\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 /**\n * Throttled, coalesced, non-blocking bound-thread reconcile: dispatch\n * claiming never waits behind cursor + message reads, and overlapping runs\n * are skipped while one is still in flight.\n */\n #maybeReconcileToolResults(now: Date): void {\n if (!this.#reconcileToolResults || this.#reconcileInFlight) return;\n if (this.#lastReconcileAt && now.getTime() - this.#lastReconcileAt.getTime() < this.#reconcileIntervalMs) return;\n this.#lastReconcileAt = now;\n const run = this.#reconcileToolResults()\n .catch(error => {\n console.error('Factory tool-result reconcile failed', sanitizeDispatchError(error));\n })\n .finally(() => {\n this.#reconcileInFlight = undefined;\n });\n this.#reconcileInFlight = run;\n this.#track(run);\n }\n\n /** Slow-cadence revocation of leaked/legacy bindings; failures never block the claim path. */\n async #maybeSweepStaleBindings(now: Date): Promise<void> {\n // The first tick only anchors the cadence: sweeping at boot would race the\n // startup reconcile that is still draining trailing tool results.\n if (!this.#lastStaleBindingSweepAt) {\n this.#lastStaleBindingSweepAt = now;\n return;\n }\n if (now.getTime() - this.#lastStaleBindingSweepAt.getTime() < this.#staleBindingSweepIntervalMs) return;\n this.#lastStaleBindingSweepAt = now;\n try {\n const revoked = await this.#storage.revokeStaleRunBindings({\n olderThan: new Date(now.getTime() - this.#staleBindingTtlMs),\n now,\n });\n if (revoked > 0) console.info(`Factory stale-binding sweep revoked ${revoked} binding(s)`);\n } catch (error) {\n console.error('Factory stale-binding sweep failed', sanitizeDispatchError(error));\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 let executionCompleted = false;\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 if (await this.#needsApproval(record, decision)) {\n const proposed = await this.#storage.proposeDeferredDecision(leaseIdentity(record, this.#ownerId), new Date());\n if (!proposed) throw new Error('Factory decision lease was lost before approval could be requested.');\n return;\n }\n await this.#supersedeProposals(record, decision);\n await this.#withLease(\n async leaseExpiresAt =>\n this.#storage.renewDeferredDecisionLease(leaseIdentity(record, this.#ownerId), leaseExpiresAt),\n async () => this.#executeDecision(record, decision),\n );\n executionCompleted = true;\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 failureCode = factoryDispatchFailureCode(error);\n const terminal = isTerminalFailure(record.attempts, failureCode);\n const failed = await this.#storage.failDeferredDecision({\n ...leaseIdentity(record, this.#ownerId),\n now: new Date(),\n availableAt: retryAt(now, record.attempts),\n lastError: sanitizeDispatchError(error),\n failureCode,\n terminal,\n advanceDeliveryGeneration: !executionCompleted,\n });\n if (terminal && failed) await this.#supersedeFailureOnSettledCard(failed);\n }\n }\n\n /**\n * Startup repair and terminal cleanup already settle a failed effect on a done or canceled\n * card as superseded; a failure landing after the card settled would otherwise page a person\n * until the next restart.\n */\n async #supersedeFailureOnSettledCard(record: FactoryDeferredDecisionRecord): Promise<void> {\n if (!record.workItemId) return;\n try {\n const item = await this.#storage.get({ orgId: record.orgId, id: record.workItemId });\n if (!item || workItemPhaseSemantics(this.#boards, item)?.kind !== 'terminal') return;\n await this.#storage.supersedeDecisionsForWorkItem({\n orgId: record.orgId,\n factoryProjectId: record.factoryProjectId,\n workItemId: record.workItemId,\n supersededAt: new Date(),\n });\n } catch (error) {\n // Best-effort: the row is already failed, and the next restart repairs it.\n console.error('Factory settled-card supersede failed', sanitizeDispatchError(error));\n }\n }\n\n /**\n * A proposal is a question: \"should this run start?\" Once that run is\n * starting anyway — because a person approved a later copy, or armed the item\n * — the question has been answered and the card must stop asking it. Left\n * alone the badge outlives the work it describes, and the one affordance that\n * means \"the loop is stopped, answer this\" cries wolf.\n */\n async #supersedeProposals(record: FactoryDeferredDecisionRecord, decision: FactoryCommitDecision): Promise<void> {\n if (decision.type !== 'invokeSkill' || !record.workItemId) return;\n try {\n await this.#storage.supersedeDecisionsForWorkItem({\n orgId: record.orgId,\n factoryProjectId: record.factoryProjectId,\n workItemId: record.workItemId,\n role: decision.role,\n supersededAt: new Date(),\n });\n } catch (error) {\n // Best-effort: a stale badge is not worth failing the run it describes.\n console.error('Factory proposal supersede failed', sanitizeDispatchError(error));\n }\n }\n\n // Effects a person owns: starting a run (compute + code execution), and an\n // external event pulling a card back into a working lane.\n async #needsApproval(record: FactoryDeferredDecisionRecord, decision: FactoryCommitDecision): Promise<boolean> {\n if (record.approvedAt !== null || !requestsConsent(this.#boards, record, decision)) return false;\n // Withholding auto-run decides what the Factory may pick up on its own, not\n // whether it may finish work a person already handed it. Once someone starts\n // an item, the runs that carry it to review are that same request continuing.\n const item = record.workItemId ? await this.#storage.get({ orgId: record.orgId, id: record.workItemId }) : null;\n // Neither arming nor auto-run is standing consent for code from outside the write-access\n // circle: only a run pre-approved by a person's gesture or its own agent's governed move passes.\n if (item && externallyAuthoredWorkItem(item)) return true;\n if (item?.autonomyArmedAt != null) return false;\n return !(await this.#isAutoRunEnabled({ orgId: record.orgId, factoryProjectId: record.factoryProjectId }));\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 replay = await this.#storage.getTransitionResultByIngress(\n record.orgId,\n record.factoryProjectId,\n `decision:${record.idempotencyKey}`,\n );\n if (!replay) assertFactoryDecisionTarget(decision, this.#boards, item.board ?? undefined);\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: record.approvedBy\n ? { type: 'human', id: record.approvedBy }\n : { type: 'system', id: 'factory-rule-dispatcher' },\n ingress: { type: 'rule', identity: `decision:${record.idempotencyKey}` },\n cause: 'rule_decision',\n causalChain: nextChain,\n ...(decision.reenter ? { reenter: true } : {}),\n });\n if (result.status === 'rejected') throw new Error(`${result.code}: ${result.reason}`);\n const transitionMessage = decision.message;\n if (!transitionMessage) 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, transitionMessage.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 session = await this.#findSession(binding);\n if (!session) return;\n const requestContext = factoryRequestContext({\n session,\n binding,\n userId: startedBy,\n orgId: record.orgId,\n });\n await awaitNotification(\n () =>\n session.sendNotificationSignal(\n {\n source: 'factory',\n kind: 'rule-message',\n summary: transitionMessage.text,\n priority: 'high',\n payload: { message: transitionMessage.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 // A retry for a role the card has already been handed past cannot win:\n // no seat can be minted for it, and the work it was for is done.\n if (await this.#roleSuperseded(record, decision.role)) return;\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 session = await this.#requireSession(binding);\n const requestContext = factoryRequestContext({\n session,\n binding,\n userId: startedBy,\n orgId: record.orgId,\n });\n const resolved =\n decision.skillName === undefined\n ? await resolvePromptInvocation(this.#controller, {\n resourceId: binding.resourceId,\n prompt: decision.prompt,\n })\n : await resolveSkillInvocation(this.#controller, {\n resourceId: binding.resourceId,\n name: decision.skillName,\n arguments: decision.arguments,\n });\n await this.#switchThread(session, binding);\n const deliveryId =\n record.deliveryGeneration === 0 ? record.id : `${record.id}:retry:${record.deliveryGeneration}`;\n const delivered = await session.thread.listActiveMessages();\n if (delivered.some(message => message.id === deliveryId)) return;\n // Safe under the replay guard above: it matches deliveryId, never prompt content.\n const kickoffContents = await withWorkItemFeed(\n this.#feedReader,\n { orgId: record.orgId, factoryProjectId: record.factoryProjectId, workItemId: record.workItemId },\n resolved.message,\n );\n if (decision.cancelInFlight && session.stream.isActive()) session.abort();\n const precedingMessage = decision.precedingMessage;\n if (precedingMessage) {\n await awaitNotification(() =>\n session.sendNotificationSignal(\n {\n source: 'factory',\n kind: 'stage-transition',\n summary: precedingMessage,\n priority: 'medium',\n payload: { message: 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 // The run's own verdict, not the delivery's. A signal can reach the\n // agent perfectly and the run still die on a provider error or be\n // cancelled mid-flight; without this the decision reports success and\n // the break is invisible on the card.\n const run = watchRun(session, {\n timeoutMs: this.#skillCompletionObservationTimeoutMs,\n approvePlans: await this.#plansAreAutoApproved(record, item),\n onAgentEnd: () => this.#roleSuperseded(record, decision.role),\n label: 'Factory skill run',\n runStillActive: () =>\n this.#controller.listActiveThreadRuns().some(active => active.threadId === binding.threadId),\n });\n\n const sendKickoff = async () => {\n const result = session.sendSignal(\n {\n id: deliveryId,\n type: 'user',\n tagName: 'user',\n contents: kickoffContents,\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 return settled;\n };\n\n try {\n let settled = await sendKickoff();\n if (settled.action === 'deliver') {\n // `deliver` means the signal was queued onto a run that was already\n // in flight. If that run ends before draining its queue the prompt\n // is dropped silently: no turn starts, no error surfaces, and the\n // decision reports success while the card sits in its new stage with\n // nobody working. Signals persist under their generation-scoped id\n // (the same identity the replay guard above reads), so confirm the\n // message actually landed in the thread rather than trusting the ack.\n const landed = await session.thread.listActiveMessages();\n if (!landed.some(message => message.id === deliveryId)) {\n // The condition that resolves this is the in-flight run ending, so\n // wait for exactly that and redeliver into the idle session. A\n // backoff cannot work here: retries are sized in seconds and a turn\n // takes minutes, so every attempt lands on the same busy run and\n // the card burns its whole budget without the session ever having\n // had a chance to be free.\n if (!(await run.wait())) {\n throw new Error('Factory skill invocation is waiting on a run that has not ended.');\n }\n run.arm();\n settled = await sendKickoff();\n if (settled.action !== 'wake') {\n throw new Error('Factory skill invocation was queued onto an ending run and never reached the agent.');\n }\n }\n }\n await this.#recordRunStart(\n session,\n binding,\n deliveryId,\n record.approvedBy ?? undefined,\n run.endReason,\n item?.title,\n );\n // A landed `deliver` still runs on the in-flight session, so the run's\n // terminal outcome matters as much as a fresh wake's: a run that ends\n // in error after accepting the prompt has still failed this decision.\n try {\n await run.settle();\n } catch (error) {\n // Roles share one session. When this role handed the card on\n // mid-turn, the next role's kickoff was delivered onto the same\n // run and the turn never ended for us — its eventual verdict is\n // the successor's to record, not ours. Capture that state when the\n // terminal event arrives so a later hand-on cannot erase our failure.\n const superseded = (await run.supersededAtEnd()) ?? (await this.#roleSuperseded(record, decision.role));\n if (!superseded) throw error;\n }\n } finally {\n run.close();\n }\n return;\n }\n case 'sendMessage': {\n const binding = await this.#messageBinding(record, decision);\n // Nobody live on the card means nobody to tell, not a failure to retry.\n if (!binding) return;\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 session = await this.#requireSession(binding);\n const requestContext = factoryRequestContext({\n session,\n binding,\n userId: startedBy,\n orgId: record.orgId,\n });\n await awaitNotification(\n () =>\n 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 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 // A lost completion acknowledgement must not reinterpret a committed\n // materialization against a replacement installation.\n const existing = await this.#storage.getByProjectSource({\n orgId: record.orgId,\n factoryProjectId: record.factoryProjectId,\n source: externalSourceForDecision(decision),\n });\n if (existing?.metadata?.[FACTORY_RULE_MATERIALIZATION_KEY] === record.idempotencyKey) {\n for (const suffix of ['destination', 'initial-entry']) {\n const replay = await this.#storage.getTransitionResultByIngress(\n record.orgId,\n record.factoryProjectId,\n `decision:${record.idempotencyKey}:${existing.id}:${suffix}`,\n );\n if (replay?.status === 'accepted' && replay.stage === decision.stage) return;\n }\n }\n assertFactoryDecisionTarget(decision, this.#boards);\n const definition = this.#boards.get(decision.board);\n if (!definition) throw new Error('Factory decision target board is not installed.');\n const initialPhase = definition.initialPhase;\n const parentWorkItemId =\n record.workItemId ??\n (await this.#resolveLinkedWorkItemParentId?.({\n orgId: record.orgId,\n factoryProjectId: record.factoryProjectId,\n decision,\n })) ??\n null;\n let 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,\n title: decision.title,\n board: decision.board,\n stages: [initialPhase],\n sessions: {},\n metadata: { ...decision.metadata, [FACTORY_RULE_MATERIALIZATION_KEY]: record.idempotencyKey },\n },\n reuseMode: 'preserve',\n });\n const itemBoard = boardForWorkItem(result.item);\n if (itemBoard !== decision.board) {\n throw new Error(`The work item belongs to board \"${itemBoard}\", not \"${decision.board}\".`);\n }\n // A re-evaluation for an already-filed card (poll/reconcile re-emitting\n // \"opened\") resolves the card itself as the triggering item; it is not\n // its own parent.\n if (!result.item.parentWorkItemId && parentWorkItemId && parentWorkItemId !== result.item.id) {\n const item = await this.#storage.setParentWorkItemIfMissing({\n orgId: record.orgId,\n id: result.item.id,\n userId: 'factory-rule-dispatcher',\n parentWorkItemId,\n });\n if (item) result = { ...result, item };\n }\n if (!result.created) {\n // Backfill source facts (e.g. sourceCreatedAt) that older cards were filed\n // without. Fill-only: never overwrite, and never adopt the card as\n // materialized by this decision.\n const missing = Object.fromEntries(\n Object.entries(decision.metadata ?? {}).filter(\n ([key]) => key !== FACTORY_RULE_MATERIALIZATION_KEY && result.item.metadata?.[key] === undefined,\n ),\n );\n if (Object.keys(missing).length > 0) {\n const filled = await this.#storage.update({\n orgId: record.orgId,\n id: result.item.id,\n userId: 'factory-rule-dispatcher',\n patch: { metadata: missing },\n });\n if (filled) result = { ...result, item: filled.item };\n }\n }\n const materializedByDecision = result.item.metadata?.[FACTORY_RULE_MATERIALIZATION_KEY] === record.idempotencyKey;\n if (!materializedByDecision && (decision.stage === initialPhase || !result.item.stages.includes(initialPhase)))\n 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: initialPhase,\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 === initialPhase) 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) {\n throw new FactoryDispatchError(\n 'session_unavailable',\n role ? `No active Factory binding for role ${role}.` : 'No active Factory binding.',\n );\n }\n return binding;\n }\n\n async #messageBinding(\n record: FactoryDeferredDecisionRecord,\n decision: Extract<FactoryCommitDecision, { type: 'sendMessage' }>,\n ): Promise<FactoryRunBindingRecord | undefined> {\n if (decision.prepareBinding && decision.role !== undefined) {\n return this.#requireOrPrepareBinding(record, decision.role);\n }\n return this.#findBinding(record, decision.role);\n }\n\n /**\n * A role is superseded when its binding was revoked by a later role taking\n * the same session (`prepareRunBinding` revokes every other active binding on\n * that session). Only a hand-on — the running agent or a person moving the\n * card — produces that shape, so the role's job is done: its decision is not\n * owed a retry, and whatever ends the shared turn afterwards belongs to the\n * successor's decision. A revoke with no successor (terminal cleanup, an\n * operator pulling the seat) is not supersession and still fails as before.\n * Only a hand-on that happened after this decision was queued counts: a\n * fresh decision for the role (the card came back to it) must still dispatch\n * even though an older revoked binding for that role is on record.\n */\n async #roleSuperseded(record: FactoryDeferredDecisionRecord, role: string): Promise<boolean> {\n if (!record.workItemId) return false;\n const bindings = await this.#storage.listRunBindings(record.orgId, record.factoryProjectId, record.workItemId);\n const own = bindings.filter(candidate => candidate.role === role);\n if (own.some(candidate => candidate.status === 'active')) return false;\n return own.some(\n revoked =>\n revoked.revokedAt !== null &&\n revoked.revokedAt.getTime() >= record.createdAt.getTime() &&\n bindings.some(\n successor =>\n successor.role !== role &&\n successor.status === 'active' &&\n successor.resourceId === revoked.resourceId &&\n successor.sessionId === revoked.sessionId &&\n successor.threadId === revoked.threadId &&\n successor.createdAt.getTime() >= revoked.revokedAt!.getTime(),\n ),\n );\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 FactoryDispatchError(\n 'session_unavailable',\n binding ? 'Bound Factory session not found.' : `No active Factory binding for role ${role}.`,\n );\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 #findSession(binding: FactoryRunBindingRecord): Promise<BoundDispatcherSession | undefined> {\n const session = await this.#controller.getSessionByResource(binding.resourceId);\n if (!session) return undefined;\n await this.#switchThread(session, binding);\n // A reused session keeps the OM models it was created with; refresh them\n // from the project's current settings so a managed run never observes with\n // models the project has since changed away from.\n await this.#refreshManagedMemorySettings?.({ binding, session });\n return session;\n }\n\n /** Unset means off: a plan nobody asked us to answer is a plan someone should see. */\n async #plansAreAutoApproved(\n { orgId, factoryProjectId }: { orgId: string; factoryProjectId: string },\n item?: { plansPreapprovedAt: Date | null } | null,\n ): Promise<boolean> {\n if (item?.plansPreapprovedAt) return true;\n return this.#autoApprovePlans ? await this.#autoApprovePlans({ orgId, factoryProjectId }) : false;\n }\n\n async #requireSession(binding: FactoryRunBindingRecord): Promise<BoundDispatcherSession> {\n const session = await this.#findSession(binding);\n if (!session) throw new FactoryDispatchError('session_unavailable', 'Bound Factory session not found.');\n return session;\n }\n\n async #switchThread(session: ThreadSwitchSession, binding: FactoryRunBindingRecord): Promise<void> {\n if (session.thread.requireId() === binding.threadId) return;\n await session.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 #recordRunStart(\n session: BoundDispatcherSession,\n binding: FactoryRunBindingRecord,\n kickoffId: string,\n approvedBy: string | undefined,\n observedEnd: ReturnType<typeof watchRun>['endReason'],\n workItemName: string | undefined,\n ): Promise<void> {\n if (!this.#audit) return;\n const humanApproved = isHumanActorId(approvedBy);\n await recordSessionRunStart(session, {\n audit: this.#audit,\n actorType: humanApproved ? 'human' : 'system',\n observedEnd,\n run: {\n kickoffId,\n bindingId: binding.id,\n role: binding.role,\n startedBy: humanApproved ? approvedBy : 'factory-rule-dispatcher',\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: binding.workItemId,\n workItemName,\n sessionId: binding.sessionId,\n threadId: binding.threadId,\n branch: binding.branch,\n },\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) {\n throw new FactoryDispatchError(\n 'session_unavailable',\n 'Prepared Factory binding is unavailable or revoked.',\n );\n }\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 session = await this.#requireSession(binding);\n const requestContext = factoryRequestContext({\n session,\n binding,\n userId: startedBy,\n orgId: record.orgId,\n });\n // The run's own verdict, not the delivery's: a kickoff delivered\n // into a run that is already terminating is consumed without\n // execution, and completing the pending start on the delivery ack\n // alone strands the card with a success ledger entry.\n const run = watchRun(session, {\n timeoutMs: this.#skillCompletionObservationTimeoutMs,\n approvePlans: await this.#plansAreAutoApproved(record, item),\n label: 'Factory kickoff run',\n runStillActive: () =>\n this.#controller.listActiveThreadRuns().some(active => active.threadId === binding.threadId),\n });\n const sendKickoff = (dedupeKey: string) =>\n awaitNotification(\n () =>\n 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,\n },\n { ifActive: { behavior: 'deliver' }, ifIdle: { behavior: 'wake' }, requestContext },\n ),\n true,\n );\n try {\n let settled = await sendKickoff(`factory-kickoff:${record.kickoffKey}`);\n if (settled?.action === 'deliver') {\n // `deliver` only proves the signal was queued onto a run already\n // in flight. If that run ends without draining its queue the\n // kickoff is dropped silently. There is no per-notification\n // \"processed\" signal, so wait for the in-flight run to end and\n // redeliver into the idle session unconditionally — the\n // generation-scoped dedupeKey defeats inbox dedupe and the\n // kickoff key keeps a duplicate run bounded, while a dropped\n // kickoff strands the card forever.\n if (!(await run.wait())) {\n throw new Error('Factory kickoff is waiting on a run that has not ended.');\n }\n run.arm();\n settled = await sendKickoff(`factory-kickoff:${record.kickoffKey}:retry:${record.attempts}`);\n if (settled?.action !== 'wake') {\n throw new Error('Factory kickoff was queued onto an ending run and never reached the agent.');\n }\n }\n await this.#recordRunStart(\n session,\n binding,\n `factory-kickoff:${record.kickoffKey}:${record.attempts}`,\n startedBy,\n run.endReason,\n item?.title,\n );\n await run.settle();\n } finally {\n run.close();\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 const failureCode = factoryDispatchFailureCode(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 failureCode,\n terminal: isTerminalFailure(record.attempts, failureCode),\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 runRegistryHeartbeatMs: RUN_REGISTRY_HEARTBEAT_MS,\n maxInFlight: MAX_IN_FLIGHT,\n stages: FACTORY_RULE_STAGES,\n} as const;\n"],"mappings":"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"}
@@ -3,6 +3,7 @@ import type { AgentController } from '@mastra/core/agent-controller';
3
3
  import type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';
4
4
  import type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';
5
5
  import type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';
6
+ import { type OMConfigurableSession } from './memory-settings-hydration.js';
6
7
  type FactorySession = Awaited<ReturnType<AgentController<MastraCodeState>['createSession']>>;
7
8
  /**
8
9
  * Read the factory project's default model. Best-effort: a missing project or an
@@ -131,5 +132,23 @@ export interface HydrateFactorySessionArgs {
131
132
  * default it was created with, and the reason is logged.
132
133
  */
133
134
  export declare function hydrateFactorySession(session: FactorySession, args: HydrateFactorySessionArgs): Promise<void>;
135
+ export interface RefreshFactorySessionMemorySettingsArgs {
136
+ orgId: string;
137
+ factoryProjectId: string;
138
+ projects: Pick<FactoryProjectsStorage, 'get'>;
139
+ memorySettings: Pick<MemorySettingsStorage, 'get'>;
140
+ }
141
+ /**
142
+ * Re-apply a factory project's stored observational-memory settings to an
143
+ * already-running session that automation is about to reuse. Session creation
144
+ * hydrates these settings once (`hydrateFactorySession`), but a reused binding
145
+ * keeps whatever observer/reflector models it was created with — so a project
146
+ * whose OM models changed since would keep observing with the stale (and
147
+ * possibly since-rejected) models. This reads the project's current row with the
148
+ * same provider-aware fallback as initial hydration and applies it, mirroring
149
+ * the `GET /web/config/om` refresh. Best-effort: a settings lookup failure must
150
+ * never sink an otherwise-ready run, so it is logged and swallowed.
151
+ */
152
+ export declare function refreshFactorySessionMemorySettings(session: OMConfigurableSession, args: RefreshFactorySessionMemorySettingsArgs): Promise<void>;
134
153
  export {};
135
154
  //# sourceMappingURL=factory-session.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"factory-session.d.ts","sourceRoot":"","sources":["../../src/session/factory-session.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"factory-session.d.ts","sourceRoot":"","sources":["../../src/session/factory-session.ts"],"names":[],"mappings":"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"}
@@ -171,7 +171,34 @@ async function hydrateFactorySession(session, args) {
171
171
  });
172
172
  }
173
173
  }
174
+ /**
175
+ * Re-apply a factory project's stored observational-memory settings to an
176
+ * already-running session that automation is about to reuse. Session creation
177
+ * hydrates these settings once (`hydrateFactorySession`), but a reused binding
178
+ * keeps whatever observer/reflector models it was created with — so a project
179
+ * whose OM models changed since would keep observing with the stale (and
180
+ * possibly since-rejected) models. This reads the project's current row with the
181
+ * same provider-aware fallback as initial hydration and applies it, mirroring
182
+ * the `GET /web/config/om` refresh. Best-effort: a settings lookup failure must
183
+ * never sink an otherwise-ready run, so it is logged and swallowed.
184
+ */
185
+ async function refreshFactorySessionMemorySettings(session, args) {
186
+ try {
187
+ const record = await args.memorySettings.get({
188
+ orgId: args.orgId,
189
+ userId: factoryMemorySettingsUserId(args.factoryProjectId)
190
+ });
191
+ const project = await args.projects.get({
192
+ orgId: args.orgId,
193
+ id: args.factoryProjectId
194
+ });
195
+ const provider = project?.defaultModelId?.split("/")[0];
196
+ await applyStoredMemorySettings(session, record, provider ? resolveProviderOMDefault(provider, project?.defaultModelId ?? void 0).modelId : void 0);
197
+ } catch (error) {
198
+ console.warn("[Factory dispatch] Failed to reapply observational-memory settings on session reuse", { error: error instanceof Error ? error.message : String(error) });
199
+ }
200
+ }
174
201
  //#endregion
175
- export { FactorySourceSessionResolutionError, ensureFactorySourceSession, hydrateFactorySession, resolveFactoryDefaultModelId, resolveFactoryProjectForSession, resolveFactorySourceRepository };
202
+ export { FactorySourceSessionResolutionError, ensureFactorySourceSession, hydrateFactorySession, refreshFactorySessionMemorySettings, resolveFactoryDefaultModelId, resolveFactoryProjectForSession, resolveFactorySourceRepository };
176
203
 
177
204
  //# sourceMappingURL=factory-session.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"factory-session.js","names":[],"sources":["../../src/session/factory-session.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport { resolveProviderOMDefault } from '@mastra/code-sdk/onboarding/packs';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\n\nimport { factoryMemorySettingsUserId } from '../storage/domains/memory-settings/base.js';\nimport type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport { applyStoredMemorySettings } from './memory-settings-hydration.js';\nimport { seedSessionOrg } from './org-seed.js';\n\ntype FactorySession = Awaited<ReturnType<AgentController<MastraCodeState>['createSession']>>;\n\n/**\n * Read the factory project's default model. Best-effort: a missing project or an\n * uninitialized storage domain means \"no default\", never a failed run.\n */\nexport async function resolveFactoryDefaultModelId(\n projects: FactoryProjectsStorage | undefined,\n factoryProjectId: string | undefined,\n): Promise<string | undefined> {\n if (!projects || !factoryProjectId) return undefined;\n try {\n const project = await projects.getById({ id: factoryProjectId });\n return project?.defaultModelId ?? undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface EnsureFactorySourceSessionArgs {\n /**\n * Storage handle of the integration that owns source control. Nothing here is\n * provider-specific: the connection is matched by the handle's own\n * `integrationId`, so GitHub, Slack-on-behalf-of-GitHub, or any future owner\n * all resolve through the same traversal.\n */\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n branch: string;\n /** Pick a specific linked repository by slug. Defaults to the first linked repository. */\n repositorySlug?: string;\n /**\n * Attribute the run to this user instead of the repo connector. Set when the\n * run has an interactive user — e.g. the person who approved a proposed run.\n */\n attributeToUserId?: string;\n}\n\nexport interface EnsuredFactorySourceSession {\n sessionId: string;\n userId: string;\n projectRepositoryId: string;\n branch: string;\n baseBranch: string;\n}\n\nexport class FactorySourceSessionResolutionError extends Error {\n constructor(readonly reason: 'connection' | 'repository') {\n super(\n reason === 'connection'\n ? 'Factory source-control connection not found.'\n : 'Factory source-control repository not found.',\n );\n this.name = 'FactorySourceSessionResolutionError';\n }\n}\n\nexport interface ResolvedFactorySourceRepository {\n projectRepositoryId: string;\n /** The repository's pinned branch, else its default branch. */\n baseBranch: string;\n /** Who connected the repository. The attribution for runs with no interactive user. */\n connectedByUserId: string;\n}\n\n/**\n * Outcome of {@link resolveFactorySourceRepository}. A miss carries which step\n * failed: callers differ on whether that is an error (an autonomous run cannot\n * proceed) or a routine fallback (a chat integration drops to a chat-only\n * session), and the two steps fail for different reasons worth reporting apart.\n */\nexport type FactorySourceRepositoryResult =\n | ({ found: true } & ResolvedFactorySourceRepository)\n | { found: false; reason: 'connection' | 'repository' };\n\n/**\n * Resolve which repository a factory project's source-control runs act on: the\n * owner's connection on the project, then one of its linked repositories.\n *\n * The owner is whichever integration owns source control, matched by the\n * handle's own `integrationId` — nothing here is provider-specific.\n */\nexport async function resolveFactorySourceRepository(args: {\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n /** Pick a specific linked repository by slug. Defaults to the first linked repository. */\n repositorySlug?: string;\n}): Promise<FactorySourceRepositoryResult> {\n const { sourceControl, orgId, factoryProjectId, repositorySlug } = args;\n\n const connections = await sourceControl.connections.list({ orgId, factoryProjectId });\n const candidates = connections.filter(candidate => candidate.integrationId === sourceControl.integrationId);\n if (candidates.length === 0) return { found: false, reason: 'connection' };\n\n // A project can carry stale connections: a provider-app reinstall leaves the\n // old connection pointing at an installation that no longer exists, and that\n // row can sit ahead of the healthy one. Try every candidate and skip the ones\n // that no longer resolve rather than failing on the first.\n for (const connection of candidates) {\n let resolved;\n try {\n const projectRepositories = await sourceControl.projectRepositories.list({ orgId, connectionId: connection.id });\n const resolvedRepositories = await Promise.all(\n projectRepositories.map(async projectRepository => ({\n projectRepository,\n repository: await sourceControl.repositories.get({ orgId, id: projectRepository.repositoryId }),\n })),\n );\n resolved = resolvedRepositories.find(\n candidate => candidate.repository && (!repositorySlug || candidate.repository.slug === repositorySlug),\n );\n } catch {\n // The connection no longer resolves (e.g. its installation was deleted).\n continue;\n }\n if (!resolved?.repository) continue;\n\n return {\n found: true,\n projectRepositoryId: resolved.projectRepository.id,\n baseBranch: resolved.projectRepository.branch ?? resolved.repository.defaultBranch,\n connectedByUserId: connection.createdByUserId,\n };\n }\n\n return { found: false, reason: 'repository' };\n}\n\n/**\n * Walk a Factory user-session id back to the project it belongs to.\n *\n * Repo-backed channel threads are keyed by their Factory session id, which is\n * the only handle a session-start hook gets. This turns that id back into the\n * project whose configuration the session should adopt. Durable by\n * construction — it reads the same rows the session was created from, so it\n * survives restarts without any in-memory mapping.\n */\nexport async function resolveFactoryProjectForSession(args: {\n sourceControl: SourceControlStorageHandle;\n sessionId: string;\n}): Promise<{ factoryProjectId: string; orgId: string; userId: string } | null> {\n const { sourceControl, sessionId } = args;\n\n const session = await sourceControl.sessions.getBySessionId(sessionId);\n if (!session) return null;\n const projectRepository = await sourceControl.projectRepositories.get({\n orgId: session.orgId,\n id: session.projectRepositoryId,\n });\n if (!projectRepository) return null;\n const connection = await sourceControl.connections.get({ orgId: session.orgId, id: projectRepository.connectionId });\n if (!connection) return null;\n\n return { factoryProjectId: connection.factoryProjectId, orgId: session.orgId, userId: session.userId };\n}\n\n/**\n * Create the source-control session a repo-backed factory run needs.\n *\n * `FactoryStartCoordinator.prepare` requires this record to already exist —\n * `resolveSourceSession` throws `Factory session not found` otherwise — so every\n * autonomous entry point has to produce one before it can start a run. This is\n * that step, in one place: the owner's connection on the factory project, one of\n * its linked repositories, and a session on the requested branch with the\n * repository's pinned or default branch as the base.\n *\n * The run is attributed to `attributeToUserId` when the caller has an\n * interactive user (e.g. the approver of a proposed run), and otherwise falls\n * back to whoever connected the repository (`connection.createdByUserId`),\n * because a genuinely autonomous run has no interactive user of its own.\n */\nexport async function ensureFactorySourceSession(\n args: EnsureFactorySourceSessionArgs,\n): Promise<EnsuredFactorySourceSession> {\n const { sourceControl, orgId, factoryProjectId, branch, repositorySlug } = args;\n\n const resolved = await resolveFactorySourceRepository({ sourceControl, orgId, factoryProjectId, repositorySlug });\n if (!resolved.found) throw new FactorySourceSessionResolutionError(resolved.reason);\n\n const userId = args.attributeToUserId ?? resolved.connectedByUserId;\n const session = await sourceControl.sessions.create({\n sessionId: randomUUID(),\n projectRepositoryId: resolved.projectRepositoryId,\n orgId,\n userId,\n branch,\n baseBranch: resolved.baseBranch,\n visibility: 'org',\n });\n return {\n sessionId: session.sessionId,\n userId,\n projectRepositoryId: resolved.projectRepositoryId,\n branch: session.branch,\n baseBranch: resolved.baseBranch,\n };\n}\n\nexport interface HydrateFactorySessionArgs {\n orgId: string;\n /**\n * The factory project whose shared memory settings apply. Factory sessions\n * never read an individual user's personal memory settings — the project's\n * own row (or the built-in defaults) is what they run with.\n */\n factoryProjectId?: string;\n /** The factory project's default model. Without it the session keeps the SDK's built-in mode default. */\n defaultModelId?: string;\n /**\n * When provided, the factory project's stored memory-settings row is\n * applied. When omitted (or no row exists) the session is reset to the\n * built-in memory defaults.\n */\n memorySettings?: MemorySettingsStorage;\n}\n\n/**\n * Apply a factory project's configuration to a freshly created session:\n * observational-memory settings, then the project's default model.\n *\n * Both steps are best-effort. A retired model id or an unreachable settings row\n * must not sink a run that is otherwise ready — the session simply keeps the\n * default it was created with, and the reason is logged.\n */\nexport async function hydrateFactorySession(session: FactorySession, args: HydrateFactorySessionArgs): Promise<void> {\n // The org rung knowledge curation scopes on. Seeded first so it lands even if\n // a later best-effort step fails; an empty org marks the session unresolved.\n await seedSessionOrg(session, args.orgId);\n try {\n const record =\n args.memorySettings && args.factoryProjectId\n ? await args.memorySettings.get({\n orgId: args.orgId,\n userId: factoryMemorySettingsUserId(args.factoryProjectId),\n })\n : null;\n // Without a stored row, fall back to the low-cost OM model of the factory\n // default model's provider — a factory connected only to Anthropic should\n // not observe with the (uncredentialed) built-in Google default.\n const provider = args.defaultModelId?.split('/')[0];\n const fallbackOmModelId = provider ? resolveProviderOMDefault(provider, args.defaultModelId).modelId : undefined;\n await applyStoredMemorySettings(session, record, fallbackOmModelId);\n } catch (error) {\n console.warn('[Factory Start] Failed to apply observational-memory settings', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n if (args.defaultModelId) {\n try {\n await session.model.switch({ modelId: args.defaultModelId });\n } catch (error) {\n console.warn('[Factory Start] Failed to apply factory default model', {\n modelId: args.defaultModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"factory-session.js","names":[],"sources":["../../src/session/factory-session.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport { resolveProviderOMDefault } from '@mastra/code-sdk/onboarding/packs';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\n\nimport { factoryMemorySettingsUserId } from '../storage/domains/memory-settings/base.js';\nimport type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport { applyStoredMemorySettings, type OMConfigurableSession } from './memory-settings-hydration.js';\nimport { seedSessionOrg } from './org-seed.js';\n\ntype FactorySession = Awaited<ReturnType<AgentController<MastraCodeState>['createSession']>>;\n\n/**\n * Read the factory project's default model. Best-effort: a missing project or an\n * uninitialized storage domain means \"no default\", never a failed run.\n */\nexport async function resolveFactoryDefaultModelId(\n projects: FactoryProjectsStorage | undefined,\n factoryProjectId: string | undefined,\n): Promise<string | undefined> {\n if (!projects || !factoryProjectId) return undefined;\n try {\n const project = await projects.getById({ id: factoryProjectId });\n return project?.defaultModelId ?? undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface EnsureFactorySourceSessionArgs {\n /**\n * Storage handle of the integration that owns source control. Nothing here is\n * provider-specific: the connection is matched by the handle's own\n * `integrationId`, so GitHub, Slack-on-behalf-of-GitHub, or any future owner\n * all resolve through the same traversal.\n */\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n branch: string;\n /** Pick a specific linked repository by slug. Defaults to the first linked repository. */\n repositorySlug?: string;\n /**\n * Attribute the run to this user instead of the repo connector. Set when the\n * run has an interactive user — e.g. the person who approved a proposed run.\n */\n attributeToUserId?: string;\n}\n\nexport interface EnsuredFactorySourceSession {\n sessionId: string;\n userId: string;\n projectRepositoryId: string;\n branch: string;\n baseBranch: string;\n}\n\nexport class FactorySourceSessionResolutionError extends Error {\n constructor(readonly reason: 'connection' | 'repository') {\n super(\n reason === 'connection'\n ? 'Factory source-control connection not found.'\n : 'Factory source-control repository not found.',\n );\n this.name = 'FactorySourceSessionResolutionError';\n }\n}\n\nexport interface ResolvedFactorySourceRepository {\n projectRepositoryId: string;\n /** The repository's pinned branch, else its default branch. */\n baseBranch: string;\n /** Who connected the repository. The attribution for runs with no interactive user. */\n connectedByUserId: string;\n}\n\n/**\n * Outcome of {@link resolveFactorySourceRepository}. A miss carries which step\n * failed: callers differ on whether that is an error (an autonomous run cannot\n * proceed) or a routine fallback (a chat integration drops to a chat-only\n * session), and the two steps fail for different reasons worth reporting apart.\n */\nexport type FactorySourceRepositoryResult =\n | ({ found: true } & ResolvedFactorySourceRepository)\n | { found: false; reason: 'connection' | 'repository' };\n\n/**\n * Resolve which repository a factory project's source-control runs act on: the\n * owner's connection on the project, then one of its linked repositories.\n *\n * The owner is whichever integration owns source control, matched by the\n * handle's own `integrationId` — nothing here is provider-specific.\n */\nexport async function resolveFactorySourceRepository(args: {\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n /** Pick a specific linked repository by slug. Defaults to the first linked repository. */\n repositorySlug?: string;\n}): Promise<FactorySourceRepositoryResult> {\n const { sourceControl, orgId, factoryProjectId, repositorySlug } = args;\n\n const connections = await sourceControl.connections.list({ orgId, factoryProjectId });\n const candidates = connections.filter(candidate => candidate.integrationId === sourceControl.integrationId);\n if (candidates.length === 0) return { found: false, reason: 'connection' };\n\n // A project can carry stale connections: a provider-app reinstall leaves the\n // old connection pointing at an installation that no longer exists, and that\n // row can sit ahead of the healthy one. Try every candidate and skip the ones\n // that no longer resolve rather than failing on the first.\n for (const connection of candidates) {\n let resolved;\n try {\n const projectRepositories = await sourceControl.projectRepositories.list({ orgId, connectionId: connection.id });\n const resolvedRepositories = await Promise.all(\n projectRepositories.map(async projectRepository => ({\n projectRepository,\n repository: await sourceControl.repositories.get({ orgId, id: projectRepository.repositoryId }),\n })),\n );\n resolved = resolvedRepositories.find(\n candidate => candidate.repository && (!repositorySlug || candidate.repository.slug === repositorySlug),\n );\n } catch {\n // The connection no longer resolves (e.g. its installation was deleted).\n continue;\n }\n if (!resolved?.repository) continue;\n\n return {\n found: true,\n projectRepositoryId: resolved.projectRepository.id,\n baseBranch: resolved.projectRepository.branch ?? resolved.repository.defaultBranch,\n connectedByUserId: connection.createdByUserId,\n };\n }\n\n return { found: false, reason: 'repository' };\n}\n\n/**\n * Walk a Factory user-session id back to the project it belongs to.\n *\n * Repo-backed channel threads are keyed by their Factory session id, which is\n * the only handle a session-start hook gets. This turns that id back into the\n * project whose configuration the session should adopt. Durable by\n * construction — it reads the same rows the session was created from, so it\n * survives restarts without any in-memory mapping.\n */\nexport async function resolveFactoryProjectForSession(args: {\n sourceControl: SourceControlStorageHandle;\n sessionId: string;\n}): Promise<{ factoryProjectId: string; orgId: string; userId: string } | null> {\n const { sourceControl, sessionId } = args;\n\n const session = await sourceControl.sessions.getBySessionId(sessionId);\n if (!session) return null;\n const projectRepository = await sourceControl.projectRepositories.get({\n orgId: session.orgId,\n id: session.projectRepositoryId,\n });\n if (!projectRepository) return null;\n const connection = await sourceControl.connections.get({ orgId: session.orgId, id: projectRepository.connectionId });\n if (!connection) return null;\n\n return { factoryProjectId: connection.factoryProjectId, orgId: session.orgId, userId: session.userId };\n}\n\n/**\n * Create the source-control session a repo-backed factory run needs.\n *\n * `FactoryStartCoordinator.prepare` requires this record to already exist —\n * `resolveSourceSession` throws `Factory session not found` otherwise — so every\n * autonomous entry point has to produce one before it can start a run. This is\n * that step, in one place: the owner's connection on the factory project, one of\n * its linked repositories, and a session on the requested branch with the\n * repository's pinned or default branch as the base.\n *\n * The run is attributed to `attributeToUserId` when the caller has an\n * interactive user (e.g. the approver of a proposed run), and otherwise falls\n * back to whoever connected the repository (`connection.createdByUserId`),\n * because a genuinely autonomous run has no interactive user of its own.\n */\nexport async function ensureFactorySourceSession(\n args: EnsureFactorySourceSessionArgs,\n): Promise<EnsuredFactorySourceSession> {\n const { sourceControl, orgId, factoryProjectId, branch, repositorySlug } = args;\n\n const resolved = await resolveFactorySourceRepository({ sourceControl, orgId, factoryProjectId, repositorySlug });\n if (!resolved.found) throw new FactorySourceSessionResolutionError(resolved.reason);\n\n const userId = args.attributeToUserId ?? resolved.connectedByUserId;\n const session = await sourceControl.sessions.create({\n sessionId: randomUUID(),\n projectRepositoryId: resolved.projectRepositoryId,\n orgId,\n userId,\n branch,\n baseBranch: resolved.baseBranch,\n visibility: 'org',\n });\n return {\n sessionId: session.sessionId,\n userId,\n projectRepositoryId: resolved.projectRepositoryId,\n branch: session.branch,\n baseBranch: resolved.baseBranch,\n };\n}\n\nexport interface HydrateFactorySessionArgs {\n orgId: string;\n /**\n * The factory project whose shared memory settings apply. Factory sessions\n * never read an individual user's personal memory settings — the project's\n * own row (or the built-in defaults) is what they run with.\n */\n factoryProjectId?: string;\n /** The factory project's default model. Without it the session keeps the SDK's built-in mode default. */\n defaultModelId?: string;\n /**\n * When provided, the factory project's stored memory-settings row is\n * applied. When omitted (or no row exists) the session is reset to the\n * built-in memory defaults.\n */\n memorySettings?: MemorySettingsStorage;\n}\n\n/**\n * Apply a factory project's configuration to a freshly created session:\n * observational-memory settings, then the project's default model.\n *\n * Both steps are best-effort. A retired model id or an unreachable settings row\n * must not sink a run that is otherwise ready — the session simply keeps the\n * default it was created with, and the reason is logged.\n */\nexport async function hydrateFactorySession(session: FactorySession, args: HydrateFactorySessionArgs): Promise<void> {\n // The org rung knowledge curation scopes on. Seeded first so it lands even if\n // a later best-effort step fails; an empty org marks the session unresolved.\n await seedSessionOrg(session, args.orgId);\n try {\n const record =\n args.memorySettings && args.factoryProjectId\n ? await args.memorySettings.get({\n orgId: args.orgId,\n userId: factoryMemorySettingsUserId(args.factoryProjectId),\n })\n : null;\n // Without a stored row, fall back to the low-cost OM model of the factory\n // default model's provider — a factory connected only to Anthropic should\n // not observe with the (uncredentialed) built-in Google default.\n const provider = args.defaultModelId?.split('/')[0];\n const fallbackOmModelId = provider ? resolveProviderOMDefault(provider, args.defaultModelId).modelId : undefined;\n await applyStoredMemorySettings(session, record, fallbackOmModelId);\n } catch (error) {\n console.warn('[Factory Start] Failed to apply observational-memory settings', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n if (args.defaultModelId) {\n try {\n await session.model.switch({ modelId: args.defaultModelId });\n } catch (error) {\n console.warn('[Factory Start] Failed to apply factory default model', {\n modelId: args.defaultModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n}\n\nexport interface RefreshFactorySessionMemorySettingsArgs {\n orgId: string;\n factoryProjectId: string;\n projects: Pick<FactoryProjectsStorage, 'get'>;\n memorySettings: Pick<MemorySettingsStorage, 'get'>;\n}\n\n/**\n * Re-apply a factory project's stored observational-memory settings to an\n * already-running session that automation is about to reuse. Session creation\n * hydrates these settings once (`hydrateFactorySession`), but a reused binding\n * keeps whatever observer/reflector models it was created with — so a project\n * whose OM models changed since would keep observing with the stale (and\n * possibly since-rejected) models. This reads the project's current row with the\n * same provider-aware fallback as initial hydration and applies it, mirroring\n * the `GET /web/config/om` refresh. Best-effort: a settings lookup failure must\n * never sink an otherwise-ready run, so it is logged and swallowed.\n */\nexport async function refreshFactorySessionMemorySettings(\n session: OMConfigurableSession,\n args: RefreshFactorySessionMemorySettingsArgs,\n): Promise<void> {\n try {\n const record = await args.memorySettings.get({\n orgId: args.orgId,\n userId: factoryMemorySettingsUserId(args.factoryProjectId),\n });\n const project = await args.projects.get({ orgId: args.orgId, id: args.factoryProjectId });\n const provider = project?.defaultModelId?.split('/')[0];\n const fallbackOmModelId = provider\n ? resolveProviderOMDefault(provider, project?.defaultModelId ?? undefined).modelId\n : undefined;\n await applyStoredMemorySettings(session, record, fallbackOmModelId);\n } catch (error) {\n console.warn('[Factory dispatch] Failed to reapply observational-memory settings on session reuse', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n}\n"],"mappings":"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"}
@@ -123,8 +123,8 @@ export interface FactoryRuleEvaluationRecord {
123
123
  }
124
124
  /** `proposed` is parked awaiting approval; `dismissed` is human, `superseded` is automatic. */
125
125
  export type FactoryDispatchStatus = 'pending' | 'proposed' | 'dismissed' | 'superseded' | 'leased' | 'retry' | 'succeeded' | 'failed';
126
- declare const FACTORY_DISPATCH_FAILURE_CODES: readonly ["session_unavailable", "source_control_missing", "source_repository_missing", "unsupported_provider_item", "notification_delivery_failed", "run_overdue", "repository_git_missing", "repository_egress_blocked", "repository_clone_failed", "repository_pull_failed", "repository_push_failed", "repository_commit_failed", "repository_cli_missing", "repository_pr_failed", "unknown"];
127
- declare const STORED_FACTORY_DISPATCH_FAILURE_CODES: readonly ["session_unavailable", "source_control_missing", "source_repository_missing", "unsupported_provider_item", "notification_delivery_failed", "run_overdue", "repository_git_missing", "repository_egress_blocked", "repository_clone_failed", "repository_pull_failed", "repository_push_failed", "repository_commit_failed", "repository_cli_missing", "repository_pr_failed", "unknown", "plan_awaiting_approval", "run_awaiting_input"];
126
+ declare const FACTORY_DISPATCH_FAILURE_CODES: readonly ["session_unavailable", "source_control_missing", "source_repository_missing", "unsupported_provider_item", "notification_delivery_failed", "run_overdue", "repository_git_missing", "repository_egress_blocked", "repository_clone_failed", "repository_pull_failed", "repository_push_failed", "repository_commit_failed", "repository_cli_missing", "repository_pr_failed", "run_configuration_invalid", "unknown"];
127
+ declare const STORED_FACTORY_DISPATCH_FAILURE_CODES: readonly ["session_unavailable", "source_control_missing", "source_repository_missing", "unsupported_provider_item", "notification_delivery_failed", "run_overdue", "repository_git_missing", "repository_egress_blocked", "repository_clone_failed", "repository_pull_failed", "repository_push_failed", "repository_commit_failed", "repository_cli_missing", "repository_pr_failed", "run_configuration_invalid", "unknown", "plan_awaiting_approval", "run_awaiting_input"];
128
128
  export type FactoryDispatchFailureCode = (typeof FACTORY_DISPATCH_FAILURE_CODES)[number];
129
129
  export type StoredFactoryDispatchFailureCode = (typeof STORED_FACTORY_DISPATCH_FAILURE_CODES)[number];
130
130
  export interface FactoryDeferredDecisionPageInput {