@mastra/factory 0.12.0-alpha.9 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -10
- package/dist/factory.d.ts +8 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +4 -3
- package/dist/factory.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/integrations/github/acceptance-labels.d.ts +17 -0
- package/dist/integrations/github/acceptance-labels.d.ts.map +1 -0
- package/dist/integrations/github/acceptance-labels.js +55 -0
- package/dist/integrations/github/acceptance-labels.js.map +1 -0
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +47 -10
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/reconcile-worker.d.ts.map +1 -1
- package/dist/integrations/github/reconcile-worker.js +2 -2
- package/dist/integrations/github/reconcile-worker.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +10 -13
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +74 -65
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/github/settle-or-abort.d.ts +7 -0
- package/dist/integrations/github/settle-or-abort.d.ts.map +1 -0
- package/dist/integrations/github/settle-or-abort.js +25 -0
- package/dist/integrations/github/settle-or-abort.js.map +1 -0
- package/dist/integrations/platform/github/event-worker.d.ts.map +1 -1
- package/dist/integrations/platform/github/event-worker.js +3 -3
- package/dist/integrations/platform/github/event-worker.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +37 -5
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/packages/_internals/workspace/dist/index.js +35 -0
- package/dist/packages/_internals/workspace/dist/index.js.map +1 -0
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +6 -0
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/oauth.d.ts +3 -0
- package/dist/routes/oauth.d.ts.map +1 -1
- package/dist/routes/oauth.js +10 -2
- package/dist/routes/oauth.js.map +1 -1
- package/dist/routes/om-seed.d.ts +22 -0
- package/dist/routes/om-seed.d.ts.map +1 -0
- package/dist/routes/om-seed.js +40 -0
- package/dist/routes/om-seed.js.map +1 -0
- package/dist/routes/surface.d.ts +1 -1
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +4 -1
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +7 -0
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/defaults.d.ts.map +1 -1
- package/dist/rules/defaults.js +3 -0
- package/dist/rules/defaults.js.map +1 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +16 -1
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/tools.d.ts.map +1 -1
- package/dist/rules/tools.js +1 -23
- package/dist/rules/tools.js.map +1 -1
- package/dist/rules/transition-service.d.ts +12 -1
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +28 -3
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/sandbox/session-sandbox.d.ts +22 -8
- package/dist/sandbox/session-sandbox.d.ts.map +1 -1
- package/dist/sandbox/session-sandbox.js +36 -37
- package/dist/sandbox/session-sandbox.js.map +1 -1
- package/dist/session/factory-session.js.map +1 -1
- package/dist/session/memory-settings-hydration.d.ts +3 -3
- package/dist/session/memory-settings-hydration.js +3 -3
- package/dist/session/memory-settings-hydration.js.map +1 -1
- package/dist/session/org-seed.d.ts +5 -5
- package/dist/session/org-seed.js +2 -2
- package/dist/session/org-seed.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +8 -0
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +10 -1
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/workspace.d.ts +8 -0
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +21 -6
- package/dist/workspace.js.map +1 -1
- package/package.json +11 -10
- package/dist/session/checkpoint-capture.d.ts +0 -18
- package/dist/session/checkpoint-capture.d.ts.map +0 -1
- package/dist/session/checkpoint-capture.js +0 -27
- package/dist/session/checkpoint-capture.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatcher.js","names":["#controller","#transitionService","#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","#requireItem","#findBinding","#findSession","#upsertLinkedItem","#requireOrPrepareBinding","#switchThread","#plansAreAutoApproved","#messageBinding","#requireSession","#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 { resolvePromptInvocation, resolveSkillInvocation } from '../skills/service.js';\nimport type { SkillSession } from '../skills/service.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, isWorkingFactoryRuleStage } from './types.js';\nimport { MAX_FACTORY_RULE_CAUSAL_DEPTH, validateFactoryRuleDecision } from './validation.js';\n\nconst LEASE_MS = 30_000;\nconst POLL_MS = 1_000;\nconst BATCH_SIZE = 10;\nconst MAX_ATTEMPTS = 5;\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;\nconst SKILL_COMPLETION_OBSERVATION_TIMEOUT_MS = 10 * 60_000;\n// Dispatches can legitimately run for minutes. Woken skill invocations hold\n// capacity until their agent run reaches a terminal state; binding preparation\n// also runs detached from the poll loop under this concurrency cap.\nconst MAX_IN_FLIGHT = 25;\n// 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 * `await` leaves a pause alone: a person asked for this run and is reading it.\n * `escalate` fails it loudly: nobody is watching an unattended run.\n * Plans are answered separately (`approvePlans`) — a plan has an approvable\n * default, a question does not, so the two never share a policy.\n */\ntype ParkedRunPolicy = 'escalate' | 'await';\n\nfunction watchRun(\n session: Pick<DispatcherSession, 'subscribe' | 'respondToToolSuspension'>,\n {\n timeoutMs,\n approvePlans,\n onParkedRun,\n label,\n }: { timeoutMs: number; approvePlans: boolean; onParkedRun: ParkedRunPolicy; label: string },\n) {\n let resolveAgentEnd!: () => void;\n let agentEnd!: Promise<void>;\n let endReason: 'complete' | 'aborted' | 'error' | 'suspended' | 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 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 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 const wait = () => waitForAgentEndOrTimeout(agentEnd, timeoutMs);\n\n return {\n arm,\n wait,\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 // Exhausting the cap falls through to the escalate branch below.\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 (parked !== undefined && (!observed || endReason === 'suspended')) {\n if (onParkedRun === 'await') return;\n if (parked.toolName === 'submit_plan') {\n throw new FactoryDispatchError(\n 'plan_awaiting_approval',\n 'Factory run wrote a plan and is waiting for it to be reviewed.',\n );\n }\n throw new FactoryDispatchError(\n 'run_awaiting_input',\n `Factory run is waiting on ${parked.toolName} for an answer.`,\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 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 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'>;\ntype BoundDispatcherSession = Session<MastraCodeState>;\n\nexport interface FactoryBindingPreparationInput {\n record: FactoryDeferredDecisionRecord;\n item: WorkItemRow;\n role: string;\n}\n\nexport interface FactoryDecisionDispatcherOptions {\n controller: FactoryController;\n transitionService: Pick<FactoryTransitionService, 'transition'>;\n storage: WorkItemsStorage;\n ownerId?: string;\n /** `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 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 /** How long to wait for a run's terminal event before failing for retry. Defaults to 10 minutes. */\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(record: FactoryDeferredDecisionRecord, decision: FactoryCommitDecision): boolean {\n if (decision.type === 'invokeSkill') return true;\n return decision.type === 'transition' && isWorkingFactoryRuleStage(decision.stage) && externalActor(record.actor);\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 #controller: FactoryController;\n readonly #transitionService: Pick<FactoryTransitionService, 'transition'>;\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.#controller = options.controller;\n this.#transitionService = options.transitionService;\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 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: isTerminalFailure(record.attempts, failureCode),\n advanceDeliveryGeneration: !executionCompleted,\n });\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(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\n // write-access circle: only the run a person's own gesture queued is pre-approved.\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 result = await this.#transitionService.transition({\n orgId: record.orgId,\n factoryProjectId: record.factoryProjectId,\n workItemId: item.id,\n board: decision.board,\n stage: decision.stage,\n expectedRevision: item.revision,\n actor: { type: 'system', id: 'factory-rule-dispatcher' },\n ingress: { type: 'rule', identity: `decision:${record.idempotencyKey}` },\n cause: 'rule_decision',\n causalChain: nextChain,\n ...(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 requestContext = new RequestContext();\n requestContext.set('user', { workosId: startedBy, organizationId: record.orgId });\n const session = await this.#findSession(binding);\n if (!session) return;\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 const binding = await this.#requireOrPrepareBinding(record, decision.role);\n const item = record.workItemId ? await this.#storage.get({ orgId: record.orgId, id: record.workItemId }) : null;\n const startedBy = item?.sessions[binding.role]?.startedBy;\n if (!startedBy) throw new Error(`Factory binding ${binding.id} has no authenticated session owner.`);\n await this.#primeCredentials?.({ orgId: record.orgId, userId: startedBy });\n const requestContext = new RequestContext();\n requestContext.set('user', { workosId: startedBy, organizationId: record.orgId });\n const resolved =\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 const session = resolved.session as DispatcherSession;\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.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 onParkedRun: 'escalate',\n label: 'Factory skill run',\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 // 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 await run.settle();\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 requestContext = new RequestContext();\n requestContext.set('user', { workosId: startedBy, organizationId: record.orgId });\n const session = await this.#requireSession(binding);\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 const parentWorkItemId =\n record.workItemId ??\n (await this.#resolveLinkedWorkItemParentId?.({\n orgId: record.orgId,\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 stages: ['intake'],\n sessions: {},\n metadata: { ...decision.metadata, [FACTORY_RULE_MATERIALIZATION_KEY]: record.idempotencyKey },\n },\n reuseMode: 'preserve',\n });\n if (!result.item.parentWorkItemId && parentWorkItemId) {\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 const materializedByDecision = result.item.metadata?.[FACTORY_RULE_MATERIALIZATION_KEY] === record.idempotencyKey;\n if (!materializedByDecision && (decision.stage === 'intake' || !result.item.stages.includes('intake'))) return;\n\n const board = decision.board;\n let expectedRevision = result.item.revision;\n if (materializedByDecision) {\n const initial = await this.#transitionService.transition({\n orgId: record.orgId,\n factoryProjectId: record.factoryProjectId,\n workItemId: result.item.id,\n board,\n stage: 'intake',\n expectedRevision,\n actor: deferredActor(record),\n ingress: { type: 'rule', identity: `decision:${record.idempotencyKey}:${result.item.id}:initial-entry` },\n cause: 'linked_item_materialized',\n causalChain,\n initialEntry: true,\n });\n if (initial.status === 'rejected') {\n if (result.created) await this.#storage.delete({ orgId: record.orgId, id: result.item.id });\n throw new Error(`${initial.code}: ${initial.reason}`);\n }\n expectedRevision = initial.revision;\n }\n if (decision.stage === 'intake') return;\n\n const moved = await this.#transitionService.transition({\n orgId: record.orgId,\n factoryProjectId: record.factoryProjectId,\n workItemId: result.item.id,\n board,\n stage: decision.stage,\n expectedRevision,\n actor: { type: 'system', id: 'factory-rule-dispatcher' },\n ingress: { type: 'rule', identity: `decision:${record.idempotencyKey}:${result.item.id}:destination` },\n cause: materializedByDecision ? 'linked_item_materialized' : 'linked_item_reconciled',\n causalChain,\n });\n if (moved.status === 'rejected') throw new Error(`${moved.code}: ${moved.reason}`);\n }\n\n async #requireItem(record: FactoryDeferredDecisionRecord) {\n if (!record.workItemId) throw new Error('Factory decision is not linked to a work item.');\n const item = await this.#storage.get({ orgId: record.orgId, id: record.workItemId });\n if (!item) throw new Error('Factory work item not found.');\n return item;\n }\n\n async #findBinding(\n record: FactoryDeferredDecisionRecord,\n role?: string,\n ): Promise<FactoryRunBindingRecord | undefined> {\n if (!record.workItemId) throw new Error('Factory decision is not linked to a work item.');\n const bindings = await this.#storage.listRunBindings(record.orgId, record.factoryProjectId, record.workItemId);\n return bindings\n .filter(candidate => candidate.status === 'active' && (role === undefined || candidate.role === role))\n .sort((left, right) => {\n if (role === undefined && left.role === 'work' && right.role !== 'work') return -1;\n if (role === undefined && right.role === 'work' && left.role !== 'work') return 1;\n return right.createdAt.getTime() - left.createdAt.getTime() || left.id.localeCompare(right.id);\n })[0];\n }\n\n async #requireBinding(record: FactoryDeferredDecisionRecord, role?: string): Promise<FactoryRunBindingRecord> {\n const binding = await this.#findBinding(record, role);\n if (!binding) {\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 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 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 #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 requestContext = new RequestContext();\n requestContext.set('user', { workosId: startedBy, organizationId: record.orgId });\n const session = await this.#requireSession(binding);\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 onParkedRun: 'await',\n label: 'Factory kickoff run',\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 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 maxInFlight: MAX_IN_FLIGHT,\n stages: FACTORY_RULE_STAGES,\n} as const;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dispatcher.js","names":["#controller","#transitionService","#storage","#ownerId","#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","#requireItem","#findBinding","#findSession","#upsertLinkedItem","#requireOrPrepareBinding","#switchThread","#plansAreAutoApproved","#messageBinding","#requireSession","#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 { resolvePromptInvocation, resolveSkillInvocation } from '../skills/service.js';\nimport type { SkillSession } from '../skills/service.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, isWorkingFactoryRuleStage } from './types.js';\nimport { MAX_FACTORY_RULE_CAUSAL_DEPTH, validateFactoryRuleDecision } from './validation.js';\n\nconst LEASE_MS = 30_000;\nconst POLL_MS = 1_000;\nconst BATCH_SIZE = 10;\nconst MAX_ATTEMPTS = 5;\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;\nconst SKILL_COMPLETION_OBSERVATION_TIMEOUT_MS = 10 * 60_000;\n// Dispatches can legitimately run for minutes. Woken skill invocations hold\n// capacity until their agent run reaches a terminal state; binding preparation\n// also runs detached from the poll loop under this concurrency cap.\nconst MAX_IN_FLIGHT = 25;\n// 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 * `await` leaves a pause alone: a person asked for this run and is reading it.\n * `escalate` fails it loudly: nobody is watching an unattended run.\n * Plans are answered separately (`approvePlans`) — a plan has an approvable\n * default, a question does not, so the two never share a policy.\n */\ntype ParkedRunPolicy = 'escalate' | 'await';\n\nfunction watchRun(\n session: Pick<DispatcherSession, 'subscribe' | 'respondToToolSuspension'>,\n {\n timeoutMs,\n approvePlans,\n onParkedRun,\n label,\n }: { timeoutMs: number; approvePlans: boolean; onParkedRun: ParkedRunPolicy; label: string },\n) {\n let resolveAgentEnd!: () => void;\n let agentEnd!: Promise<void>;\n let endReason: 'complete' | 'aborted' | 'error' | 'suspended' | 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 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 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 const wait = () => waitForAgentEndOrTimeout(agentEnd, timeoutMs);\n\n return {\n arm,\n wait,\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 // Exhausting the cap falls through to the escalate branch below.\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 (parked !== undefined && (!observed || endReason === 'suspended')) {\n if (onParkedRun === 'await') return;\n if (parked.toolName === 'submit_plan') {\n throw new FactoryDispatchError(\n 'plan_awaiting_approval',\n 'Factory run wrote a plan and is waiting for it to be reviewed.',\n );\n }\n throw new FactoryDispatchError(\n 'run_awaiting_input',\n `Factory run is waiting on ${parked.toolName} for an answer.`,\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 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 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'>;\ntype BoundDispatcherSession = Session<MastraCodeState>;\n\nexport interface FactoryBindingPreparationInput {\n record: FactoryDeferredDecisionRecord;\n item: WorkItemRow;\n role: string;\n}\n\nexport interface FactoryDecisionDispatcherOptions {\n controller: FactoryController;\n transitionService: Pick<FactoryTransitionService, 'transition'>;\n storage: WorkItemsStorage;\n ownerId?: string;\n /** `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 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 /** How long to wait for a run's terminal event before failing for retry. Defaults to 10 minutes. */\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(record: FactoryDeferredDecisionRecord, decision: FactoryCommitDecision): boolean {\n if (decision.type === 'invokeSkill') return true;\n return decision.type === 'transition' && isWorkingFactoryRuleStage(decision.stage) && externalActor(record.actor);\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 #controller: FactoryController;\n readonly #transitionService: Pick<FactoryTransitionService, 'transition'>;\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.#controller = options.controller;\n this.#transitionService = options.transitionService;\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 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: isTerminalFailure(record.attempts, failureCode),\n advanceDeliveryGeneration: !executionCompleted,\n });\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(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 result = await this.#transitionService.transition({\n orgId: record.orgId,\n factoryProjectId: record.factoryProjectId,\n workItemId: item.id,\n board: decision.board,\n stage: decision.stage,\n expectedRevision: item.revision,\n actor: { type: 'system', id: 'factory-rule-dispatcher' },\n ingress: { type: 'rule', identity: `decision:${record.idempotencyKey}` },\n cause: 'rule_decision',\n causalChain: nextChain,\n ...(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 requestContext = new RequestContext();\n requestContext.set('user', { workosId: startedBy, organizationId: record.orgId });\n const session = await this.#findSession(binding);\n if (!session) return;\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 const binding = await this.#requireOrPrepareBinding(record, decision.role);\n const item = record.workItemId ? await this.#storage.get({ orgId: record.orgId, id: record.workItemId }) : null;\n const startedBy = item?.sessions[binding.role]?.startedBy;\n if (!startedBy) throw new Error(`Factory binding ${binding.id} has no authenticated session owner.`);\n await this.#primeCredentials?.({ orgId: record.orgId, userId: startedBy });\n const requestContext = new RequestContext();\n requestContext.set('user', { workosId: startedBy, organizationId: record.orgId });\n const resolved =\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 const session = resolved.session as DispatcherSession;\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.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 onParkedRun: 'escalate',\n label: 'Factory skill run',\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 // 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 await run.settle();\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 requestContext = new RequestContext();\n requestContext.set('user', { workosId: startedBy, organizationId: record.orgId });\n const session = await this.#requireSession(binding);\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 const parentWorkItemId =\n record.workItemId ??\n (await this.#resolveLinkedWorkItemParentId?.({\n orgId: record.orgId,\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 stages: ['intake'],\n sessions: {},\n metadata: { ...decision.metadata, [FACTORY_RULE_MATERIALIZATION_KEY]: record.idempotencyKey },\n },\n reuseMode: 'preserve',\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 === 'intake' || !result.item.stages.includes('intake'))) return;\n\n const board = decision.board;\n let expectedRevision = result.item.revision;\n if (materializedByDecision) {\n const initial = await this.#transitionService.transition({\n orgId: record.orgId,\n factoryProjectId: record.factoryProjectId,\n workItemId: result.item.id,\n board,\n stage: 'intake',\n expectedRevision,\n actor: deferredActor(record),\n ingress: { type: 'rule', identity: `decision:${record.idempotencyKey}:${result.item.id}:initial-entry` },\n cause: 'linked_item_materialized',\n causalChain,\n initialEntry: true,\n });\n if (initial.status === 'rejected') {\n if (result.created) await this.#storage.delete({ orgId: record.orgId, id: result.item.id });\n throw new Error(`${initial.code}: ${initial.reason}`);\n }\n expectedRevision = initial.revision;\n }\n if (decision.stage === 'intake') return;\n\n const moved = await this.#transitionService.transition({\n orgId: record.orgId,\n factoryProjectId: record.factoryProjectId,\n workItemId: result.item.id,\n board,\n stage: decision.stage,\n expectedRevision,\n actor: { type: 'system', id: 'factory-rule-dispatcher' },\n ingress: { type: 'rule', identity: `decision:${record.idempotencyKey}:${result.item.id}:destination` },\n cause: materializedByDecision ? 'linked_item_materialized' : 'linked_item_reconciled',\n causalChain,\n });\n if (moved.status === 'rejected') throw new Error(`${moved.code}: ${moved.reason}`);\n }\n\n async #requireItem(record: FactoryDeferredDecisionRecord) {\n if (!record.workItemId) throw new Error('Factory decision is not linked to a work item.');\n const item = await this.#storage.get({ orgId: record.orgId, id: record.workItemId });\n if (!item) throw new Error('Factory work item not found.');\n return item;\n }\n\n async #findBinding(\n record: FactoryDeferredDecisionRecord,\n role?: string,\n ): Promise<FactoryRunBindingRecord | undefined> {\n if (!record.workItemId) throw new Error('Factory decision is not linked to a work item.');\n const bindings = await this.#storage.listRunBindings(record.orgId, record.factoryProjectId, record.workItemId);\n return bindings\n .filter(candidate => candidate.status === 'active' && (role === undefined || candidate.role === role))\n .sort((left, right) => {\n if (role === undefined && left.role === 'work' && right.role !== 'work') return -1;\n if (role === undefined && right.role === 'work' && left.role !== 'work') return 1;\n return right.createdAt.getTime() - left.createdAt.getTime() || left.id.localeCompare(right.id);\n })[0];\n }\n\n async #requireBinding(record: FactoryDeferredDecisionRecord, role?: string): Promise<FactoryRunBindingRecord> {\n const binding = await this.#findBinding(record, role);\n if (!binding) {\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 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 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 #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 requestContext = new RequestContext();\n requestContext.set('user', { workosId: startedBy, organizationId: record.orgId });\n const session = await this.#requireSession(binding);\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 onParkedRun: 'await',\n label: 'Factory kickoff run',\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 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 maxInFlight: MAX_IN_FLIGHT,\n stages: FACTORY_RULE_STAGES,\n} as const;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/rules/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAInE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAEvE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/rules/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAInE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAEvE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AA8BxE,wBAAsB,4BAA4B,CAAC,OAAO,EAAE;IAC1D,cAAc,EAAE,cAAc,CAAC;IAC/B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,iBAAiB,EAAE,IAAI,CAAC,wBAAwB,EAAE,YAAY,CAAC,CAAC;IAChE,QAAQ,CAAC,EAAE,0BAA0B,CAAC;CACvC,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA8D5B"}
|
package/dist/rules/tools.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { FACTORY_RULE_STAGES, FACTORY_TRIAGE_TYPES, isFactoryTriageType } from "./types.js";
|
|
2
|
-
import { currentStage } from "./transition-service.js";
|
|
3
2
|
import { resolveFactorySessionAddress } from "./binding-context.js";
|
|
4
3
|
import { createTool } from "@mastra/core/tools";
|
|
5
4
|
import { z } from "zod";
|
|
@@ -45,7 +44,7 @@ async function createFactoryTransitionTools(options) {
|
|
|
45
44
|
});
|
|
46
45
|
if (!item) throw new Error("Bound Factory work item not found.");
|
|
47
46
|
const triageType = "triageType" in input && isFactoryTriageType(input.triageType) ? input.triageType : void 0;
|
|
48
|
-
|
|
47
|
+
return await options.transitionService.transition({
|
|
49
48
|
orgId: binding.orgId,
|
|
50
49
|
factoryProjectId: binding.factoryProjectId,
|
|
51
50
|
workItemId: binding.workItemId,
|
|
@@ -64,27 +63,6 @@ async function createFactoryTransitionTools(options) {
|
|
|
64
63
|
cause: rationale,
|
|
65
64
|
...triageType ? { triageType } : {}
|
|
66
65
|
});
|
|
67
|
-
const memory = execution.memory;
|
|
68
|
-
if (memory?.runCuration && result.status === "accepted") {
|
|
69
|
-
const exitedStage = currentStage(item.stages) ?? stage;
|
|
70
|
-
(async () => {
|
|
71
|
-
try {
|
|
72
|
-
const threadId = execution.agent?.threadId;
|
|
73
|
-
const resourceId = execution.agent?.resourceId;
|
|
74
|
-
if (!threadId) return;
|
|
75
|
-
const { outcome } = await memory.runCuration({
|
|
76
|
-
threadId,
|
|
77
|
-
resourceId: resourceId ?? threadId,
|
|
78
|
-
requestContext: execution.requestContext,
|
|
79
|
-
prompt: `Now that the work item has left the ${exitedStage} phase: is there anything from this phase worth remembering — a durable project memory, or something worth pinning?`
|
|
80
|
-
});
|
|
81
|
-
console.debug(`[factory:transition-curate] thread=${threadId} from=${exitedStage} to=${stage} outcome=${outcome}`);
|
|
82
|
-
} catch (error) {
|
|
83
|
-
console.debug(`[factory:transition-curate] thread=${execution.agent?.threadId ?? "unknown"} from=${exitedStage} to=${stage} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
84
|
-
}
|
|
85
|
-
})();
|
|
86
|
-
}
|
|
87
|
-
return result;
|
|
88
66
|
}
|
|
89
67
|
}) };
|
|
90
68
|
}
|
package/dist/rules/tools.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.js","names":[],"sources":["../../src/rules/tools.ts"],"sourcesContent":["import type { RequestContext } from '@mastra/core/request-context';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport type { IntegrationTools } from '../integrations/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { FactorySessionSourceLookup } from './binding-context.js';\nimport { resolveFactorySessionAddress } from './binding-context.js';\nimport type { FactoryTransitionService } from './transition-service.js';\nimport {
|
|
1
|
+
{"version":3,"file":"tools.js","names":[],"sources":["../../src/rules/tools.ts"],"sourcesContent":["import type { RequestContext } from '@mastra/core/request-context';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport type { IntegrationTools } from '../integrations/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { FactorySessionSourceLookup } from './binding-context.js';\nimport { resolveFactorySessionAddress } from './binding-context.js';\nimport type { FactoryTransitionService } from './transition-service.js';\nimport { FACTORY_RULE_STAGES, FACTORY_TRIAGE_TYPES, isFactoryTriageType } from './types.js';\nimport type { FactoryRuleBoard } from './types.js';\n\nconst MAX_RATIONALE_LENGTH = 1_000;\n\nconst transitionInputSchema = z\n .object({\n stage: z.enum(FACTORY_RULE_STAGES),\n expectedRevision: z.number().int().positive(),\n // Providers strip maxLength from the JSON schema and models can't count characters, so a\n // hard cap invites overshoot-retry loops at the end of every run. Accept and clamp instead.\n rationale: z\n .string()\n .trim()\n .min(1)\n .transform(value =>\n value.length <= MAX_RATIONALE_LENGTH ? value : `${value.slice(0, MAX_RATIONALE_LENGTH - 1)}…`,\n ),\n })\n .strict();\n\nconst triageTransitionInputSchema = transitionInputSchema.extend({\n triageType: z.enum(FACTORY_TRIAGE_TYPES),\n});\n\nfunction boardForSource(type: string | undefined): FactoryRuleBoard {\n return type === 'pull-request' ? 'review' : 'work';\n}\n\nexport async function createFactoryTransitionTools(options: {\n requestContext: RequestContext;\n storage: WorkItemsStorage;\n transitionService: Pick<FactoryTransitionService, 'transition'>;\n sessions?: FactorySessionSourceLookup;\n}): Promise<IntegrationTools> {\n const resolution = await resolveFactorySessionAddress({\n requestContext: options.requestContext,\n storage: options.storage,\n sessions: options.sessions,\n });\n if (!resolution) return {};\n const availableBinding = resolution.binding ?? (await options.storage.findActiveRunBinding(resolution.address));\n if (!availableBinding) return {};\n const isTriage = availableBinding.role === 'triage';\n\n return {\n factory_transition_work_item: createTool({\n id: 'factory_transition_work_item',\n description: isTriage\n ? 'Report the triage classification and request a governed stage transition for the Factory work item exactly bound to this thread. Only bugs may request Planning autonomously; closure outcomes may request a terminal stage. Feature requests and other non-bug classifications that remain open must stay in their current Intake or Triage stage for maintainer approval.'\n : 'Request a governed stage transition for the Factory work item exactly bound to this thread. Use the current revision from the factory-phase signal and explain why the transition is appropriate.',\n inputSchema: isTriage ? triageTransitionInputSchema : transitionInputSchema,\n requireApproval: true,\n execute: async ({ stage, expectedRevision, rationale, ...input }, execution) => {\n const currentResolution = await resolveFactorySessionAddress({\n requestContext: execution.requestContext,\n storage: options.storage,\n sessions: options.sessions,\n });\n const currentAddress = currentResolution?.address ?? null;\n const toolCallId = execution.agent?.toolCallId;\n if (!currentAddress || !toolCallId) {\n throw new Error('Factory transitions require an authenticated bound agent tool call.');\n }\n const binding = await options.storage.findActiveRunBinding(currentAddress);\n // Authority is the work item this session is bound to, not the individual\n // binding row. Handing the next role its turn in an existing session\n // rotates the binding, and tools built for the previous role stay live\n // across that rotation; keying on row identity would strand the run that\n // the rotation exists to start. Re-pointing a session at a different item\n // is the hijack this guards against.\n if (!binding || binding.workItemId !== availableBinding.workItemId) {\n throw new Error('Factory agent binding is unavailable, revoked, or no longer matches this session.');\n }\n const item = await options.storage.get({ orgId: binding.orgId, id: binding.workItemId });\n if (!item) throw new Error('Bound Factory work item not found.');\n const triageType =\n 'triageType' in input && isFactoryTriageType(input.triageType) ? input.triageType : undefined;\n\n const result = await options.transitionService.transition({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: binding.workItemId,\n board: boardForSource(item.externalSource?.type),\n stage,\n expectedRevision,\n actor: { type: 'agent', bindingId: binding.id, role: binding.role },\n ingress: { type: 'agent', identity: `${binding.id}:${toolCallId}` },\n cause: rationale,\n ...(triageType ? { triageType } : {}),\n });\n\n return result;\n },\n }),\n };\n}\n"],"mappings":";;;;;AAYA,MAAM,uBAAuB;AAE7B,MAAM,wBAAwB,EAC3B,OAAO;CACN,OAAO,EAAE,KAAK,mBAAmB;CACjC,kBAAkB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS;CAG5C,WAAW,EACR,OAAO,CAAC,CACR,KAAK,CAAC,CACN,IAAI,CAAC,CAAC,CACN,WAAU,UACT,MAAM,UAAU,uBAAuB,QAAQ,GAAG,MAAM,MAAM,GAAG,uBAAuB,CAAC,EAAE,EAC7F;AACJ,CAAC,CAAC,CACD,OAAO;AAEV,MAAM,8BAA8B,sBAAsB,OAAO,EAC/D,YAAY,EAAE,KAAK,oBAAoB,EACzC,CAAC;AAED,SAAS,eAAe,MAA4C;CAClE,OAAO,SAAS,iBAAiB,WAAW;AAC9C;AAEA,eAAsB,6BAA6B,SAKrB;CAC5B,MAAM,aAAa,MAAM,6BAA6B;EACpD,gBAAgB,QAAQ;EACxB,SAAS,QAAQ;EACjB,UAAU,QAAQ;CACpB,CAAC;CACD,IAAI,CAAC,YAAY,OAAO,CAAC;CACzB,MAAM,mBAAmB,WAAW,WAAY,MAAM,QAAQ,QAAQ,qBAAqB,WAAW,OAAO;CAC7G,IAAI,CAAC,kBAAkB,OAAO,CAAC;CAC/B,MAAM,WAAW,iBAAiB,SAAS;CAE3C,OAAO,EACL,8BAA8B,WAAW;EACvC,IAAI;EACJ,aAAa,WACT,gXACA;EACJ,aAAa,WAAW,8BAA8B;EACtD,iBAAiB;EACjB,SAAS,OAAO,EAAE,OAAO,kBAAkB,WAAW,GAAG,SAAS,cAAc;GAM9E,MAAM,kBAAiB,MALS,6BAA6B;IAC3D,gBAAgB,UAAU;IAC1B,SAAS,QAAQ;IACjB,UAAU,QAAQ;GACpB,CAAC,EAAA,EACyC,WAAW;GACrD,MAAM,aAAa,UAAU,OAAO;GACpC,IAAI,CAAC,kBAAkB,CAAC,YACtB,MAAM,IAAI,MAAM,qEAAqE;GAEvF,MAAM,UAAU,MAAM,QAAQ,QAAQ,qBAAqB,cAAc;GAOzE,IAAI,CAAC,WAAW,QAAQ,eAAe,iBAAiB,YACtD,MAAM,IAAI,MAAM,mFAAmF;GAErG,MAAM,OAAO,MAAM,QAAQ,QAAQ,IAAI;IAAE,OAAO,QAAQ;IAAO,IAAI,QAAQ;GAAW,CAAC;GACvF,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,oCAAoC;GAC/D,MAAM,aACJ,gBAAgB,SAAS,oBAAoB,MAAM,UAAU,IAAI,MAAM,aAAa,KAAA;GAetF,OAAO,MAbc,QAAQ,kBAAkB,WAAW;IACxD,OAAO,QAAQ;IACf,kBAAkB,QAAQ;IAC1B,YAAY,QAAQ;IACpB,OAAO,eAAe,KAAK,gBAAgB,IAAI;IAC/C;IACA;IACA,OAAO;KAAE,MAAM;KAAS,WAAW,QAAQ;KAAI,MAAM,QAAQ;IAAK;IAClE,SAAS;KAAE,MAAM;KAAS,UAAU,GAAG,QAAQ,GAAG,GAAG;IAAa;IAClE,OAAO;IACP,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;GACrC,CAAC;EAGH;CACF,CAAC,EACH;AACF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WorkItemsStorage } from '../storage/domains/work-items/base.js';
|
|
1
|
+
import type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';
|
|
2
2
|
import type { FactoryRuleActor, FactoryRuleBoard, FactoryRuleCausalEntry, FactoryRuleStage, FactoryTriageType, FactoryRules, FactoryTransitionResult } from './types.js';
|
|
3
3
|
export interface FactoryTransitionRequest {
|
|
4
4
|
orgId: string;
|
|
@@ -43,6 +43,17 @@ export interface FactoryTransitionServiceOptions {
|
|
|
43
43
|
* `onTerminalStage` before returning (default 30s). The cleanup continues
|
|
44
44
|
* in the background past the bound. */
|
|
45
45
|
terminalCleanupTimeoutMs?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Called after a transition first records a person's acceptance of a
|
|
48
|
+
* non-bug item (see `WorkItemRow.acceptedAt`). Fire-and-forget: failures
|
|
49
|
+
* are swallowed, the committed transition never depends on it.
|
|
50
|
+
*/
|
|
51
|
+
onAccepted?: (args: {
|
|
52
|
+
orgId: string;
|
|
53
|
+
factoryProjectId: string;
|
|
54
|
+
workItemId: string;
|
|
55
|
+
item: WorkItemRow;
|
|
56
|
+
}) => Promise<void> | void;
|
|
46
57
|
}
|
|
47
58
|
export declare function currentStage(stages: readonly string[]): FactoryRuleStage | undefined;
|
|
48
59
|
export declare function roleForStage(board: FactoryRuleBoard, stage: FactoryRuleStage): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transition-service.d.ts","sourceRoot":"","sources":["../../src/rules/transition-service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;
|
|
1
|
+
{"version":3,"file":"transition-service.d.ts","sourceRoot":"","sources":["../../src/rules/transition-service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE3F,OAAO,KAAK,EAEV,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,EAEtB,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EAEZ,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAuBpB,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,gBAAgB,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,gBAAgB,CAAC;IACxB,OAAO,EAAE;QAAE,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,YAAY,GAAG,QAAQ,GAAG,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjH,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,SAAS,sBAAsB,EAAE,CAAC;IAChD,iHAAiH;IACjH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,qHAAqH;IACrH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC;AAED,MAAM,WAAW,+BAA+B;IAC9C,KAAK,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE;QACvB,KAAK,EAAE,MAAM,CAAC;QACd,gBAAgB,EAAE,MAAM,CAAC;QACzB,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,gBAAgB,CAAC;KACzB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC3B;;2CAEuC;IACvC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,gBAAgB,EAAE,MAAM,CAAC;QACzB,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,WAAW,CAAC;KACnB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC5B;AAuBD,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,gBAAgB,GAAG,SAAS,CAIpF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAKrF;AAwFD,qBAAa,wBAAwB;;gBAQvB,OAAO,EAAE,+BAA+B;IASpD,IAAI,cAAc,IAAI,MAAM,CAE3B;IAEK,UAAU,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,uBAAuB,CAAC;CAwRtF"}
|
|
@@ -48,7 +48,6 @@ function bearsConsent(actor) {
|
|
|
48
48
|
}
|
|
49
49
|
function consentEffect(request, humanBoardDrag) {
|
|
50
50
|
const autonomy = transitionConsent(request.stage, humanBoardDrag);
|
|
51
|
-
if (autonomy === void 0) return {};
|
|
52
51
|
return bearsConsent(request.actor) ? {
|
|
53
52
|
autonomy,
|
|
54
53
|
consentedBy: actorId(request.actor)
|
|
@@ -73,6 +72,15 @@ function isHumanTransition(request) {
|
|
|
73
72
|
function requiresHumanApproval(triageType) {
|
|
74
73
|
return triageType !== void 0 && triageType !== null && triageType !== "bug";
|
|
75
74
|
}
|
|
75
|
+
function isAtRest(stage) {
|
|
76
|
+
return stage === "intake" || stage === "triage";
|
|
77
|
+
}
|
|
78
|
+
function entersWork(stage) {
|
|
79
|
+
return stage === "planning" || stage === "execute";
|
|
80
|
+
}
|
|
81
|
+
function acceptsItem(request) {
|
|
82
|
+
return isHumanTransition(request) && entersWork(request.stage);
|
|
83
|
+
}
|
|
76
84
|
function ruleFailure(error) {
|
|
77
85
|
return {
|
|
78
86
|
code: "rule_error",
|
|
@@ -96,11 +104,13 @@ var FactoryTransitionService = class {
|
|
|
96
104
|
#timeoutMs;
|
|
97
105
|
#onTerminalStage;
|
|
98
106
|
#terminalCleanupTimeoutMs;
|
|
107
|
+
#onAccepted;
|
|
99
108
|
constructor(options) {
|
|
100
109
|
this.#rules = options.rules;
|
|
101
110
|
this.#storage = options.storage;
|
|
102
111
|
this.#timeoutMs = options.timeoutMs ?? RULE_TIMEOUT_MS;
|
|
103
112
|
this.#onTerminalStage = options.onTerminalStage;
|
|
113
|
+
this.#onAccepted = options.onAccepted;
|
|
104
114
|
this.#terminalCleanupTimeoutMs = options.terminalCleanupTimeoutMs ?? TERMINAL_CLEANUP_TIMEOUT_MS;
|
|
105
115
|
}
|
|
106
116
|
get ruleSetVersion() {
|
|
@@ -123,7 +133,7 @@ var FactoryTransitionService = class {
|
|
|
123
133
|
if (!fromStage) return this.#commitRejection(request, transitionId, "invalid_transition", "The work item does not have one canonical Factory stage.");
|
|
124
134
|
if (isTriageAgent(request.actor) && request.triageType === void 0) return this.#commitRejection(request, transitionId, "invalid_transition", "Triage transitions must report a structured triage classification.");
|
|
125
135
|
if (item.triageType && request.triageType && item.triageType !== request.triageType) return this.#commitRejection(request, transitionId, "forbidden", "The persisted triage classification cannot be changed by a later transition.");
|
|
126
|
-
if (requiresHumanApproval(item.triageType ?? request.triageType) && (request.stage
|
|
136
|
+
if (requiresHumanApproval(item.triageType ?? request.triageType) && entersWork(request.stage) && isAtRest(fromStage) && !isHumanTransition(request) && !item.acceptedAt) return this.#commitRejection(request, transitionId, "approval_required", "A maintainer must move this non-bug work item into Planning or Execute from the Factory UI.");
|
|
127
137
|
if (request.actor.type === "agent" && !isWorkingFactoryRuleStage(fromStage) && isWorkingFactoryRuleStage(request.stage) && externallyAuthoredWorkItem(item)) return this.#commitRejection(request, transitionId, "approval_required", "This card comes from outside the write-access circle; a person must resume it from the Factory board.");
|
|
128
138
|
const humanBoardDrag = request.actor.type === "human" && request.cause === "board_drag" && fromStage !== request.stage;
|
|
129
139
|
const contextBase = {
|
|
@@ -213,7 +223,10 @@ var FactoryTransitionService = class {
|
|
|
213
223
|
} : ruleFailure(error)
|
|
214
224
|
};
|
|
215
225
|
}
|
|
216
|
-
return this.#commit(request, transitionId, evaluation, evaluation.outcome === "accepted" ?
|
|
226
|
+
return this.#commit(request, transitionId, evaluation, evaluation.outcome === "accepted" ? {
|
|
227
|
+
...consentEffect(request, humanBoardDrag),
|
|
228
|
+
accept: acceptsItem(request) && !item.acceptedAt
|
|
229
|
+
} : {});
|
|
217
230
|
}
|
|
218
231
|
async #commitRejection(request, transitionId, code, reason) {
|
|
219
232
|
return this.#commit(request, transitionId, {
|
|
@@ -226,6 +239,7 @@ var FactoryTransitionService = class {
|
|
|
226
239
|
const committed = await this.#storage.commitTransition({
|
|
227
240
|
autonomy: options.autonomy,
|
|
228
241
|
consentedBy: options.consentedBy,
|
|
242
|
+
...options.accept ? { accept: true } : {},
|
|
229
243
|
orgId: request.orgId,
|
|
230
244
|
factoryProjectId: request.factoryProjectId,
|
|
231
245
|
workItemId: request.workItemId,
|
|
@@ -244,6 +258,17 @@ var FactoryTransitionService = class {
|
|
|
244
258
|
});
|
|
245
259
|
if (committed.status === "missing") return rejection(transitionId, request.workItemId, "invalid_transition", "Work item not found.");
|
|
246
260
|
const result = committed.result;
|
|
261
|
+
if (this.#onAccepted && options.accept && committed.status === "committed" && result.status === "accepted" && committed.item?.acceptedAt) {
|
|
262
|
+
const item = committed.item;
|
|
263
|
+
Promise.resolve(this.#onAccepted({
|
|
264
|
+
orgId: request.orgId,
|
|
265
|
+
factoryProjectId: request.factoryProjectId,
|
|
266
|
+
workItemId: request.workItemId,
|
|
267
|
+
item
|
|
268
|
+
})).catch((error) => {
|
|
269
|
+
console.warn(`[factory] acceptance hook failed for work item ${request.workItemId}:`, error);
|
|
270
|
+
});
|
|
271
|
+
}
|
|
247
272
|
if (this.#onTerminalStage && result.status === "accepted" && TERMINAL_STAGES.has(result.stage)) {
|
|
248
273
|
let timer;
|
|
249
274
|
try {
|