@mastra/factory 0.17.3-alpha.5 → 0.18.0-alpha.7
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/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +15 -4
- package/dist/auth.js.map +1 -1
- package/dist/boards/transition-policy.d.ts +3 -3
- package/dist/boards/transition-policy.d.ts.map +1 -1
- package/dist/boards/transition-policy.js.map +1 -1
- package/dist/boards/work-transition-policy.d.ts.map +1 -1
- package/dist/boards/work-transition-policy.js +3 -3
- package/dist/boards/work-transition-policy.js.map +1 -1
- package/dist/capabilities/intake.d.ts +2 -0
- package/dist/capabilities/intake.d.ts.map +1 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +9 -1
- package/dist/factory.js.map +1 -1
- package/dist/integrations/github/default-rules.d.ts.map +1 -1
- package/dist/integrations/github/default-rules.js +2 -0
- package/dist/integrations/github/default-rules.js.map +1 -1
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +1 -0
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/rules.d.ts.map +1 -1
- package/dist/integrations/github/rules.js +20 -1
- package/dist/integrations/github/rules.js.map +1 -1
- package/dist/integrations/gitlab/routes.d.ts.map +1 -1
- package/dist/integrations/gitlab/routes.js +1 -0
- package/dist/integrations/gitlab/routes.js.map +1 -1
- package/dist/integrations/linear/default-rules.d.ts +2 -1
- package/dist/integrations/linear/default-rules.d.ts.map +1 -1
- package/dist/integrations/linear/default-rules.js +1 -0
- package/dist/integrations/linear/default-rules.js.map +1 -1
- package/dist/integrations/linear/integration.d.ts.map +1 -1
- package/dist/integrations/linear/integration.js +2 -0
- package/dist/integrations/linear/integration.js.map +1 -1
- package/dist/integrations/linear/issue-reconciler.d.ts.map +1 -1
- package/dist/integrations/linear/issue-reconciler.js +3 -0
- package/dist/integrations/linear/issue-reconciler.js.map +1 -1
- package/dist/integrations/linear/routes.d.ts.map +1 -1
- package/dist/integrations/linear/routes.js +2 -1
- package/dist/integrations/linear/routes.js.map +1 -1
- package/dist/integrations/linear/rules.d.ts +1 -0
- package/dist/integrations/linear/rules.d.ts.map +1 -1
- package/dist/integrations/linear/rules.js.map +1 -1
- package/dist/integrations/slack/slack.d.ts +7 -7
- package/dist/integrations/slack/slack.d.ts.map +1 -1
- package/dist/integrations/slack/slack.js +13 -11
- package/dist/integrations/slack/slack.js.map +1 -1
- package/dist/platform-auth-host.d.ts +4 -0
- package/dist/platform-auth-host.d.ts.map +1 -0
- package/dist/platform-auth-host.js +18 -0
- package/dist/platform-auth-host.js.map +1 -0
- package/dist/routes/intake.d.ts.map +1 -1
- package/dist/routes/intake.js +15 -1
- package/dist/routes/intake.js.map +1 -1
- package/dist/routes/surface.d.ts +1 -1
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +47 -6
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +6 -1
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/dispatch-errors.d.ts.map +1 -1
- package/dist/rules/dispatch-errors.js +4 -0
- package/dist/rules/dispatch-errors.js.map +1 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +8 -6
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/processor.d.ts.map +1 -1
- package/dist/rules/processor.js +9 -1
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/review-source-tool.d.ts +103 -0
- package/dist/rules/review-source-tool.d.ts.map +1 -0
- package/dist/rules/review-source-tool.js +109 -0
- package/dist/rules/review-source-tool.js.map +1 -0
- package/dist/rules/start-coordinator.d.ts +7 -1
- package/dist/rules/start-coordinator.d.ts.map +1 -1
- package/dist/rules/start-coordinator.js +56 -2
- package/dist/rules/start-coordinator.js.map +1 -1
- package/dist/rules/transition-service.d.ts +4 -4
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +3 -2
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.d.ts +2 -0
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js.map +1 -1
- package/dist/session/factory-session.d.ts +8 -4
- package/dist/session/factory-session.d.ts.map +1 -1
- package/dist/session/factory-session.js +4 -2
- package/dist/session/factory-session.js.map +1 -1
- package/dist/session/work-item-repository.d.ts +21 -0
- package/dist/session/work-item-repository.d.ts.map +1 -0
- package/dist/session/work-item-repository.js +91 -0
- package/dist/session/work-item-repository.js.map +1 -0
- package/dist/skills/service.d.ts.map +1 -1
- package/dist/skills/service.js +16 -6
- package/dist/skills/service.js.map +1 -1
- package/dist/storage/domains/intake/base.d.ts +1 -0
- package/dist/storage/domains/intake/base.d.ts.map +1 -1
- package/dist/storage/domains/intake/base.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +13 -2
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +17 -0
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/supervisor/session-messaging.d.ts +4 -1
- package/dist/supervisor/session-messaging.d.ts.map +1 -1
- package/dist/supervisor/session-messaging.js +8 -2
- package/dist/supervisor/session-messaging.js.map +1 -1
- package/dist/supervisor/write-tools.d.ts +2 -1
- package/dist/supervisor/write-tools.d.ts.map +1 -1
- package/dist/supervisor/write-tools.js +9 -2
- package/dist/supervisor/write-tools.js.map +1 -1
- package/dist/workspace.d.ts +7 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +45 -7
- package/dist/workspace.js.map +1 -1
- package/factory-skills/factory-complete-issue/SKILL.md +4 -2
- package/factory-skills/factory-gitlab-rereview/SKILL.md +3 -2
- package/factory-skills/factory-gitlab-review/SKILL.md +17 -1
- package/factory-skills/factory-plan/SKILL.md +11 -0
- package/factory-skills/factory-rereview/SKILL.md +25 -4
- package/factory-skills/factory-review/SKILL.md +25 -4
- package/package.json +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"processor.js","names":[],"sources":["../../src/rules/processor.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\n\nimport { resolveRequestThinkingLevel } from '@mastra/code-sdk/agents/model';\nimport type { ThinkingLevel } from '@mastra/code-sdk/providers/openai-codex';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { MastraDBMessage, MessageList } from '@mastra/core/agent/message-list';\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type {\n ComputeStateSignalArgs,\n ComputeStateSignalResult,\n ProcessInputStepArgs,\n Processor,\n} from '@mastra/core/processors';\n\nimport { boardForWorkItem, resolveBoardToolRule, workItemPhaseSemantics } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport type { FactoryRunBindingRecord, WorkItemsStorage, WorkItemRow } from '../storage/domains/work-items/base.js';\nimport { getFactorySessionCoordinates } from './binding-context.js';\nimport type { FactoryTransitionService } from './transition-service.js';\nimport { workItemSource } from './types.js';\nimport type {\n FactoryCommitDecision,\n FactoryRuleDecision,\n FactoryRuleJsonValue,\n FactoryRuleStage,\n FactoryToolResultRuleContext,\n} from './types.js';\nimport {\n assertFactoryDecisionTarget,\n normalizeFactoryRuleJsonValue,\n validateFactoryRuleDecisions,\n} from './validation.js';\n\nconst STATE_ID = 'factory-phase';\nconst RULE_TIMEOUT_MS = 5_000;\nconst TRANSCRIPT_PAGE_SIZE = 50;\nconst MAX_LINKED_ITEMS = 5;\nfunction ruleStage(boards: BoardRegistry, item: WorkItemRow | null | undefined): string | undefined {\n return item && workItemPhaseSemantics(boards, item) ? item.stages[0] : undefined;\n}\n\nfunction itemInRuleStage(boards: BoardRegistry, item: WorkItemRow | null | undefined): item is WorkItemRow {\n return ruleStage(boards, item) !== undefined;\n}\n\ntype PersistedMessageReader = {\n listMessages(input: {\n threadId: string;\n resourceId?: string;\n page: number;\n perPage: number;\n filter?: { dateRange?: { start?: Date } };\n orderBy: { field: 'createdAt'; direction: 'ASC' };\n }): Promise<{ messages: MastraDBMessage[]; hasMore: boolean }>;\n};\n\ntype CompletedToolResult = {\n assistantMessageId: string;\n messageCreatedAt: Date;\n toolCallId: string;\n toolName: string;\n input: FactoryRuleJsonValue;\n status: 'success' | 'error';\n value: FactoryRuleJsonValue;\n};\n\ntype RuntimeSnapshot = {\n modelId: string;\n thinkingLevel: ThinkingLevel;\n};\n\ntype ActivePhaseSnapshotBase = {\n bindingId: string;\n itemId: string;\n revision: number;\n stage: FactoryRuleStage;\n role: string;\n configVersion: string;\n status: 'active';\n};\n\ntype ActivePhaseSnapshotValue = ActivePhaseSnapshotBase & {\n board: string;\n modelId?: string;\n thinkingLevel?: ThinkingLevel;\n};\n\ntype PhaseSnapshotValue = ActivePhaseSnapshotValue | { bindingId?: string; status: 'none' };\n\nfunction reviewRuntimeFromRequestContext(requestContext: ComputeStateSignalArgs['requestContext']): RuntimeSnapshot {\n if (!requestContext || typeof requestContext.get !== 'function') {\n throw new Error('Factory review phase requires a controller request context.');\n }\n const context = requestContext.get<'controller', AgentControllerRequestContext<MastraCodeState>>('controller');\n const modelId = context?.session?.modelId.trim();\n if (!modelId) {\n throw new Error('Factory review phase requires a selected session model.');\n }\n return { modelId, thinkingLevel: resolveRequestThinkingLevel(context) };\n}\n\nfunction workItemSourceKey(item: WorkItemRow): string | null {\n const source = item.externalSource;\n return source ? `${source.integrationId}:${source.type}:${source.externalId}` : null;\n}\n\nfunction boundedError(value: unknown): FactoryRuleJsonValue {\n const message = value instanceof Error ? value.message : typeof value === 'string' ? value : 'Tool execution failed.';\n return { message: message.slice(0, 2_000) };\n}\n\nfunction boundedResult(value: unknown): FactoryRuleJsonValue {\n try {\n return normalizeFactoryRuleJsonValue(value);\n } catch {\n return { message: 'Tool result was not serializable.' };\n }\n}\n\nfunction messageParts(message: MastraDBMessage): unknown[] {\n const content = message.content as { parts?: unknown[]; toolInvocations?: unknown[] } | unknown[] | undefined;\n if (Array.isArray(content)) return content;\n if (Array.isArray(content?.parts)) return content.parts;\n return Array.isArray(content?.toolInvocations) ? content.toolInvocations : [];\n}\n\nfunction completedStepToolCallIds(steps: unknown[]): Set<string> {\n const ids = new Set<string>();\n for (const rawStep of steps) {\n if (!rawStep || typeof rawStep !== 'object') continue;\n const toolResults = (rawStep as { toolResults?: unknown[] }).toolResults;\n if (!Array.isArray(toolResults)) continue;\n for (const rawResult of toolResults) {\n if (!rawResult || typeof rawResult !== 'object') continue;\n const toolCallId = (rawResult as { toolCallId?: unknown }).toolCallId;\n if (typeof toolCallId === 'string') ids.add(toolCallId);\n }\n }\n return ids;\n}\n\nfunction completedToolResults(message: MastraDBMessage): CompletedToolResult[] {\n if (message.role !== 'assistant') return [];\n const createdAt = message.createdAt instanceof Date ? message.createdAt : new Date(message.createdAt);\n const completed: CompletedToolResult[] = [];\n for (const rawPart of messageParts(message)) {\n if (!rawPart || typeof rawPart !== 'object') continue;\n const part = rawPart as Record<string, unknown>;\n const invocation =\n part.type === 'tool-invocation' && part.toolInvocation && typeof part.toolInvocation === 'object'\n ? (part.toolInvocation as Record<string, unknown>)\n : part;\n const state = invocation.state;\n if (state !== 'result' && state !== 'error') continue;\n const toolCallId = invocation.toolCallId;\n const toolName = invocation.toolName ?? invocation.name;\n if (typeof toolCallId !== 'string' || typeof toolName !== 'string') continue;\n completed.push({\n assistantMessageId: message.id,\n messageCreatedAt: createdAt,\n toolCallId,\n toolName: toolName.slice(0, 256),\n input: boundedResult(invocation.args ?? {}),\n status: state === 'error' ? 'error' : 'success',\n value: state === 'error' ? boundedError(invocation.result ?? invocation.error) : boundedResult(invocation.result),\n });\n }\n return completed;\n}\n\nfunction currentCompletedToolMessage(\n messages: MastraDBMessage[],\n toolCallIds: ReadonlySet<string>,\n): MastraDBMessage | undefined {\n for (let index = messages.length - 1; index >= 0; index -= 1) {\n const message = messages[index]!;\n if (completedToolResults(message).some(result => toolCallIds.has(result.toolCallId))) return message;\n }\n return undefined;\n}\n\nfunction phaseCacheKey(value: ActivePhaseSnapshotValue, linked: WorkItemRow[]): string {\n return createHash('sha256')\n .update(\n JSON.stringify({\n ...value,\n linked: linked.map(item => [item.id, item.revision, item.stages[0]]),\n }),\n )\n .digest('hex');\n}\n\nfunction escapeText(value: string): string {\n return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');\n}\n\nfunction phaseFromSignal(signal: { metadata?: Record<string, unknown> } | undefined): PhaseSnapshotValue | undefined {\n return (signal?.metadata?.value as { phase?: PhaseSnapshotValue } | undefined)?.phase;\n}\n\nfunction latestPhase(args: ComputeStateSignalArgs): PhaseSnapshotValue | undefined {\n for (const signal of [...args.activeStateSignals].reverse()) {\n const phase = phaseFromSignal(signal);\n if (phase) return phase;\n }\n return phaseFromSignal(args.lastSnapshot);\n}\n\nasync function withRuleTimeout<T>(operation: Promise<T>): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n operation,\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error('FACTORY_RULE_TIMEOUT')), RULE_TIMEOUT_MS);\n }),\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\nexport class FactoryPhaseStateProcessor implements Processor<'factory-phase'> {\n readonly id = STATE_ID;\n readonly stateId = STATE_ID;\n\n constructor(\n private readonly options: {\n configVersion: string;\n storage: WorkItemsStorage;\n boards: BoardRegistry;\n transitionService?: Pick<FactoryTransitionService, 'transition'>;\n messageReader?: PersistedMessageReader;\n recordPullRequestProvenance?: (input: {\n binding: FactoryRunBindingRecord;\n item: WorkItemRow;\n assistantMessageId: string;\n toolCallId: string;\n toolName: string;\n toolInput: FactoryRuleJsonValue;\n toolResult: FactoryRuleJsonValue;\n status: 'success' | 'error';\n }) => Promise<void>;\n },\n ) {}\n\n async processInputStep(args: ProcessInputStepArgs): Promise<MessageList | undefined> {\n const address = getFactorySessionCoordinates(args.requestContext);\n if (!address) return;\n const binding = await this.options.storage.findRunBindingBySession(address);\n if (!binding || binding.status !== 'active') return;\n const completedToolCallIds = completedStepToolCallIds(args.steps);\n const completedMessage = currentCompletedToolMessage(args.messages, completedToolCallIds);\n if (completedMessage) {\n await this.ingestMessages(binding, [completedMessage], completedToolCallIds);\n }\n }\n\n async computeStateSignal(args: ComputeStateSignalArgs): Promise<ComputeStateSignalResult> {\n const address = getFactorySessionCoordinates(args.requestContext);\n if (!address) return;\n const binding = await this.options.storage.findRunBindingBySession(address);\n const prior = latestPhase(args);\n const hasBase = Boolean(args.lastSnapshot) && args.contextWindow.hasSnapshot;\n\n if (!binding) return;\n if (binding.status !== 'active') {\n if (!hasBase || prior?.status !== 'active') return;\n return {\n id: STATE_ID,\n cacheKey: `factory:none:${prior.bindingId ?? 'revoked'}`,\n mode: 'snapshot',\n tagName: 'factory-phase',\n contents: '\\n',\n value: { phase: { status: 'none' } },\n attributes: { status: 'none' },\n metadata: { value: { phase: { status: 'none' } } },\n };\n }\n\n const item = await this.options.storage.get({ orgId: binding.orgId, id: binding.workItemId });\n const stage = ruleStage(this.options.boards, item);\n if (!item || !stage) return;\n const allItems = await this.options.storage.list({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n });\n const linked = allItems\n .filter(candidate => candidate.parentWorkItemId === item.id || item.parentWorkItemId === candidate.id)\n .slice(0, MAX_LINKED_ITEMS);\n const board = boardForWorkItem(item);\n const baseValue: ActivePhaseSnapshotBase = {\n status: 'active',\n bindingId: binding.id,\n itemId: item.id,\n revision: item.revision,\n stage,\n role: binding.role,\n configVersion: this.options.configVersion,\n };\n const value: ActivePhaseSnapshotValue =\n board === 'review'\n ? { ...baseValue, board, ...reviewRuntimeFromRequestContext(args.requestContext) }\n : { ...baseValue, board };\n const cacheKey = phaseCacheKey(value, linked);\n if (hasBase && (args.tracking?.currentCacheKey ?? args.lastSnapshot?.metadata?.state?.cacheKey) === cacheKey)\n return;\n\n const linkedText = linked.length\n ? `\\nLinked items: ${linked.map(candidate => `${workItemSource(candidate.externalSource)} ${candidate.title}`).join('; ')}`\n : '';\n const phaseLabel = this.options.boards.get(board)?.phases[stage]?.title ?? stage;\n const runtime =\n value.modelId && value.thinkingLevel ? { modelId: value.modelId, thinkingLevel: value.thinkingLevel } : null;\n const snapshotContents =\n `Factory ${board} phase: ${escapeText(phaseLabel)} (${escapeText(stage)})\\n` +\n `Work item: ${escapeText(item.title)} (${item.id})\\n` +\n `Role: ${escapeText(binding.role)}\\nRevision: ${item.revision}\\nConfig: ${escapeText(this.options.configVersion)}\\n` +\n (runtime\n ? `Runtime: model=${escapeText(runtime.modelId)}, reasoning-setting=${escapeText(runtime.thinkingLevel)}\\n`\n : '') +\n ((board === 'work' || board === 'review') && stage === 'intake'\n ? 'This card rests in Intake: its work is paused. Answer questions without moving it; when the user asks to resume, request the transition into the working stage first, then continue the work in this session.\\n'\n : '') +\n `Use factory_transition_work_item with expectedRevision ${item.revision} to request a phase change.${escapeText(linkedText)}`;\n const isDelta = hasBase && prior?.status === 'active';\n return {\n id: STATE_ID,\n cacheKey,\n mode: isDelta ? 'delta' : 'snapshot',\n tagName: 'factory-phase',\n contents: isDelta ? `Factory phase update:\\n${snapshotContents}` : snapshotContents,\n value: { phase: value },\n ...(isDelta ? { delta: { phase: value } } : {}),\n attributes: {\n status: 'active',\n board,\n stage,\n role: binding.role,\n revision: item.revision,\n ...(runtime ?? {}),\n },\n metadata: { value: { phase: value } },\n };\n }\n\n async reconcileAllBoundThreads(): Promise<void> {\n if (!this.options.messageReader) return;\n const bindings = await this.options.storage.listActiveRunBindings();\n for (const binding of bindings) await this.reconcileBinding(binding);\n }\n\n async reconcileBinding(binding: FactoryRunBindingRecord): Promise<void> {\n const reader = this.options.messageReader;\n if (!reader || binding.status !== 'active') return;\n // One keyed read up front: a binding whose item is gone or no longer in a\n // single rule stage would ingest nothing, so skip the cursor and message\n // reads entirely instead of paying them on every walk.\n const item = await this.options.storage.get({ orgId: binding.orgId, id: binding.workItemId });\n if (!itemInRuleStage(this.options.boards, item)) return;\n const cursor = await this.options.storage.getToolResultCursor(binding.orgId, binding.factoryProjectId, binding.id);\n let page = 0;\n while (true) {\n const result = await reader.listMessages({\n threadId: binding.threadId,\n resourceId: binding.resourceId,\n page,\n perPage: TRANSCRIPT_PAGE_SIZE,\n ...(cursor ? { filter: { dateRange: { start: cursor.lastMessageCreatedAt } } } : {}),\n orderBy: { field: 'createdAt', direction: 'ASC' },\n });\n await this.ingestMessages(binding, result.messages, undefined, item);\n const last = result.messages.at(-1);\n if (last) {\n await this.options.storage.advanceToolResultCursor({\n bindingId: binding.id,\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n lastMessageId: last.id,\n lastMessageCreatedAt: last.createdAt instanceof Date ? last.createdAt : new Date(last.createdAt),\n updatedAt: new Date(),\n });\n }\n if (!result.hasMore) break;\n page += 1;\n }\n }\n\n private async ingestMessages(\n binding: FactoryRunBindingRecord,\n messages: MastraDBMessage[],\n toolCallIds?: ReadonlySet<string>,\n preloadedItem?: WorkItemRow,\n ): Promise<void> {\n const item = preloadedItem ?? (await this.options.storage.get({ orgId: binding.orgId, id: binding.workItemId }));\n if (!itemInRuleStage(this.options.boards, item)) return;\n for (const message of messages) {\n for (const toolResult of completedToolResults(message)) {\n if (toolCallIds && !toolCallIds.has(toolResult.toolCallId)) continue;\n try {\n await this.options.recordPullRequestProvenance?.({\n binding,\n item,\n assistantMessageId: toolResult.assistantMessageId,\n toolCallId: toolResult.toolCallId,\n toolName: toolResult.toolName,\n toolInput: toolResult.input,\n toolResult: toolResult.value,\n status: toolResult.status,\n });\n } catch {\n // Provenance is supporting evidence and must not block authoritative rule ingress.\n }\n await this.ingestToolResult(binding, item, toolResult);\n }\n }\n }\n\n private async ingestToolResult(\n binding: FactoryRunBindingRecord,\n item: WorkItemRow,\n toolResult: CompletedToolResult,\n ): Promise<void> {\n const board = boardForWorkItem(item);\n const rule = resolveBoardToolRule(this.options.boards, board, toolResult.toolName);\n if (!rule) return;\n const ingressId = JSON.stringify([\n binding.id,\n binding.threadId,\n toolResult.assistantMessageId,\n toolResult.toolCallId,\n ]);\n const prior = await this.options.storage.getTransitionResultByIngress(\n binding.orgId,\n binding.factoryProjectId,\n ingressId,\n );\n if (prior) return;\n const context: FactoryToolResultRuleContext = {\n tenant: { orgId: binding.orgId, projectId: binding.factoryProjectId },\n actor: { type: 'agent', bindingId: binding.id, role: binding.role },\n ingress: { type: 'toolResult', id: ingressId },\n cause: `Completed ${toolResult.toolName}`,\n causalChain: [],\n configVersion: this.options.configVersion,\n item: {\n id: item.id,\n source: workItemSource(item.externalSource),\n sourceKey: workItemSourceKey(item),\n parentWorkItemId: item.parentWorkItemId,\n title: item.title,\n url: item.externalSource?.url ?? null,\n stages: item.stages,\n acceptedAt: item.acceptedAt,\n metadata: item.metadata,\n },\n board,\n itemRevision: item.revision,\n toolName: toolResult.toolName,\n threadId: binding.threadId,\n assistantMessageId: toolResult.assistantMessageId,\n toolCallId: toolResult.toolCallId,\n result: { status: toolResult.status, value: toolResult.value },\n };\n\n let decision: FactoryRuleDecision | void = undefined;\n let decisions: FactoryCommitDecision[] = [];\n let outcome: { status: 'accepted' | 'rejected'; code?: string; reason?: string } = { status: 'accepted' };\n try {\n decision = await withRuleTimeout(Promise.resolve(rule(Object.freeze(context))));\n if (decision?.type === 'reject') {\n outcome = { status: 'rejected', code: decision.code, reason: decision.reason };\n } else if (decision) {\n const validated = validateFactoryRuleDecisions([decision]);\n for (const entry of validated) assertFactoryDecisionTarget(entry, this.options.boards, board);\n decisions = validated;\n }\n } catch (error) {\n const timedOut = error instanceof Error && error.message === 'FACTORY_RULE_TIMEOUT';\n outcome = {\n status: 'rejected',\n code: timedOut ? 'timeout' : 'rule_error',\n reason: timedOut\n ? 'Factory rule evaluation timed out.'\n : error instanceof Error\n ? error.message.slice(0, 2_000)\n : 'Factory tool-result rule failed.',\n };\n }\n const committed = await this.options.storage.commitRuleEvaluation({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: item.id,\n ingress: { identity: ingressId, triggerType: 'tool.result' },\n configVersion: this.options.configVersion,\n expectedRevision: item.revision,\n actor: { ...context.actor },\n outcome,\n decisions: decisions.map(entry => ({ ...entry })),\n causalChain: [],\n now: new Date(),\n });\n if (committed.status !== 'committed' || !this.options.transitionService) return;\n for (const entry of decisions) {\n if (entry.type !== 'transition') continue;\n await this.options.transitionService.transition({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: item.id,\n board: entry.board,\n stage: entry.stage,\n expectedRevision: item.revision,\n actor: { type: 'system', id: 'factory-tool-result-rule' },\n ingress: { type: 'rule', identity: `decision:${entry.idempotencyKey}` },\n cause: 'tool_result_rule',\n causalChain: [{ ingressId, decisionType: entry.type }],\n });\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"processor.js","names":[],"sources":["../../src/rules/processor.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\n\nimport { resolveRequestThinkingLevel } from '@mastra/code-sdk/agents/model';\nimport type { ThinkingLevel } from '@mastra/code-sdk/providers/openai-codex';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { MastraDBMessage, MessageList } from '@mastra/core/agent/message-list';\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type {\n ComputeStateSignalArgs,\n ComputeStateSignalResult,\n ProcessInputStepArgs,\n Processor,\n} from '@mastra/core/processors';\n\nimport { boardForWorkItem, resolveBoardToolRule, workItemPhaseSemantics } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport type { FactoryRunBindingRecord, WorkItemsStorage, WorkItemRow } from '../storage/domains/work-items/base.js';\nimport { getFactorySessionCoordinates } from './binding-context.js';\nimport type { FactoryTransitionService } from './transition-service.js';\nimport { workItemSource } from './types.js';\nimport type {\n FactoryCommitDecision,\n FactoryRuleDecision,\n FactoryRuleJsonValue,\n FactoryRuleStage,\n FactoryToolResultRuleContext,\n} from './types.js';\nimport {\n assertFactoryDecisionTarget,\n normalizeFactoryRuleJsonValue,\n validateFactoryRuleDecisions,\n} from './validation.js';\n\nconst STATE_ID = 'factory-phase';\nconst RULE_TIMEOUT_MS = 5_000;\nconst TRANSCRIPT_PAGE_SIZE = 50;\nconst MAX_LINKED_ITEMS = 5;\nfunction ruleStage(boards: BoardRegistry, item: WorkItemRow | null | undefined): string | undefined {\n return item && workItemPhaseSemantics(boards, item) ? item.stages[0] : undefined;\n}\n\nfunction itemInRuleStage(boards: BoardRegistry, item: WorkItemRow | null | undefined): item is WorkItemRow {\n return ruleStage(boards, item) !== undefined;\n}\n\ntype PersistedMessageReader = {\n listMessages(input: {\n threadId: string;\n resourceId?: string;\n page: number;\n perPage: number;\n filter?: { dateRange?: { start?: Date } };\n orderBy: { field: 'createdAt'; direction: 'ASC' };\n }): Promise<{ messages: MastraDBMessage[]; hasMore: boolean }>;\n};\n\ntype CompletedToolResult = {\n assistantMessageId: string;\n messageCreatedAt: Date;\n toolCallId: string;\n toolName: string;\n input: FactoryRuleJsonValue;\n status: 'success' | 'error';\n value: FactoryRuleJsonValue;\n};\n\nfunction isApprovedPlanResult(result: CompletedToolResult): boolean {\n if (result.toolName !== 'submit_plan' || result.status !== 'success') return false;\n if (typeof result.value === 'string') return result.value.startsWith('Plan approved.');\n if (!result.value || typeof result.value !== 'object' || Array.isArray(result.value)) return false;\n const content = result.value.content;\n return typeof content === 'string' && content.startsWith('Plan approved.');\n}\n\ntype RuntimeSnapshot = {\n modelId: string;\n thinkingLevel: ThinkingLevel;\n};\n\ntype ActivePhaseSnapshotBase = {\n bindingId: string;\n itemId: string;\n revision: number;\n stage: FactoryRuleStage;\n role: string;\n configVersion: string;\n status: 'active';\n};\n\ntype ActivePhaseSnapshotValue = ActivePhaseSnapshotBase & {\n board: string;\n modelId?: string;\n thinkingLevel?: ThinkingLevel;\n};\n\ntype PhaseSnapshotValue = ActivePhaseSnapshotValue | { bindingId?: string; status: 'none' };\n\nfunction reviewRuntimeFromRequestContext(requestContext: ComputeStateSignalArgs['requestContext']): RuntimeSnapshot {\n if (!requestContext || typeof requestContext.get !== 'function') {\n throw new Error('Factory review phase requires a controller request context.');\n }\n const context = requestContext.get<'controller', AgentControllerRequestContext<MastraCodeState>>('controller');\n const modelId = context?.session?.modelId.trim();\n if (!modelId) {\n throw new Error('Factory review phase requires a selected session model.');\n }\n return { modelId, thinkingLevel: resolveRequestThinkingLevel(context) };\n}\n\nfunction workItemSourceKey(item: WorkItemRow): string | null {\n const source = item.externalSource;\n return source ? `${source.integrationId}:${source.type}:${source.externalId}` : null;\n}\n\nfunction boundedError(value: unknown): FactoryRuleJsonValue {\n const message = value instanceof Error ? value.message : typeof value === 'string' ? value : 'Tool execution failed.';\n return { message: message.slice(0, 2_000) };\n}\n\nfunction boundedResult(value: unknown): FactoryRuleJsonValue {\n try {\n return normalizeFactoryRuleJsonValue(value);\n } catch {\n return { message: 'Tool result was not serializable.' };\n }\n}\n\nfunction messageParts(message: MastraDBMessage): unknown[] {\n const content = message.content as { parts?: unknown[]; toolInvocations?: unknown[] } | unknown[] | undefined;\n if (Array.isArray(content)) return content;\n if (Array.isArray(content?.parts)) return content.parts;\n return Array.isArray(content?.toolInvocations) ? content.toolInvocations : [];\n}\n\nfunction completedStepToolCallIds(steps: unknown[]): Set<string> {\n const ids = new Set<string>();\n for (const rawStep of steps) {\n if (!rawStep || typeof rawStep !== 'object') continue;\n const toolResults = (rawStep as { toolResults?: unknown[] }).toolResults;\n if (!Array.isArray(toolResults)) continue;\n for (const rawResult of toolResults) {\n if (!rawResult || typeof rawResult !== 'object') continue;\n const toolCallId = (rawResult as { toolCallId?: unknown }).toolCallId;\n if (typeof toolCallId === 'string') ids.add(toolCallId);\n }\n }\n return ids;\n}\n\nfunction completedToolResults(message: MastraDBMessage): CompletedToolResult[] {\n if (message.role !== 'assistant') return [];\n const createdAt = message.createdAt instanceof Date ? message.createdAt : new Date(message.createdAt);\n const completed: CompletedToolResult[] = [];\n for (const rawPart of messageParts(message)) {\n if (!rawPart || typeof rawPart !== 'object') continue;\n const part = rawPart as Record<string, unknown>;\n const invocation =\n part.type === 'tool-invocation' && part.toolInvocation && typeof part.toolInvocation === 'object'\n ? (part.toolInvocation as Record<string, unknown>)\n : part;\n const state = invocation.state;\n if (state !== 'result' && state !== 'error') continue;\n const toolCallId = invocation.toolCallId;\n const toolName = invocation.toolName ?? invocation.name;\n if (typeof toolCallId !== 'string' || typeof toolName !== 'string') continue;\n completed.push({\n assistantMessageId: message.id,\n messageCreatedAt: createdAt,\n toolCallId,\n toolName: toolName.slice(0, 256),\n input: boundedResult(invocation.args ?? {}),\n status: state === 'error' ? 'error' : 'success',\n value: state === 'error' ? boundedError(invocation.result ?? invocation.error) : boundedResult(invocation.result),\n });\n }\n return completed;\n}\n\nfunction currentCompletedToolMessage(\n messages: MastraDBMessage[],\n toolCallIds: ReadonlySet<string>,\n): MastraDBMessage | undefined {\n for (let index = messages.length - 1; index >= 0; index -= 1) {\n const message = messages[index]!;\n if (completedToolResults(message).some(result => toolCallIds.has(result.toolCallId))) return message;\n }\n return undefined;\n}\n\nfunction phaseCacheKey(value: ActivePhaseSnapshotValue, linked: WorkItemRow[]): string {\n return createHash('sha256')\n .update(\n JSON.stringify({\n ...value,\n linked: linked.map(item => [item.id, item.revision, item.stages[0]]),\n }),\n )\n .digest('hex');\n}\n\nfunction escapeText(value: string): string {\n return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');\n}\n\nfunction phaseFromSignal(signal: { metadata?: Record<string, unknown> } | undefined): PhaseSnapshotValue | undefined {\n return (signal?.metadata?.value as { phase?: PhaseSnapshotValue } | undefined)?.phase;\n}\n\nfunction latestPhase(args: ComputeStateSignalArgs): PhaseSnapshotValue | undefined {\n for (const signal of [...args.activeStateSignals].reverse()) {\n const phase = phaseFromSignal(signal);\n if (phase) return phase;\n }\n return phaseFromSignal(args.lastSnapshot);\n}\n\nasync function withRuleTimeout<T>(operation: Promise<T>): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n operation,\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error('FACTORY_RULE_TIMEOUT')), RULE_TIMEOUT_MS);\n }),\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\nexport class FactoryPhaseStateProcessor implements Processor<'factory-phase'> {\n readonly id = STATE_ID;\n readonly stateId = STATE_ID;\n\n constructor(\n private readonly options: {\n configVersion: string;\n storage: WorkItemsStorage;\n boards: BoardRegistry;\n transitionService?: Pick<FactoryTransitionService, 'transition'>;\n messageReader?: PersistedMessageReader;\n recordPullRequestProvenance?: (input: {\n binding: FactoryRunBindingRecord;\n item: WorkItemRow;\n assistantMessageId: string;\n toolCallId: string;\n toolName: string;\n toolInput: FactoryRuleJsonValue;\n toolResult: FactoryRuleJsonValue;\n status: 'success' | 'error';\n }) => Promise<void>;\n },\n ) {}\n\n async processInputStep(args: ProcessInputStepArgs): Promise<MessageList | undefined> {\n const address = getFactorySessionCoordinates(args.requestContext);\n if (!address) return;\n const binding = await this.options.storage.findRunBindingBySession(address);\n if (!binding || binding.status !== 'active') return;\n const completedToolCallIds = completedStepToolCallIds(args.steps);\n const completedMessage = currentCompletedToolMessage(args.messages, completedToolCallIds);\n if (completedMessage) {\n await this.ingestMessages(binding, [completedMessage], completedToolCallIds);\n }\n }\n\n async computeStateSignal(args: ComputeStateSignalArgs): Promise<ComputeStateSignalResult> {\n const address = getFactorySessionCoordinates(args.requestContext);\n if (!address) return;\n const binding = await this.options.storage.findRunBindingBySession(address);\n const prior = latestPhase(args);\n const hasBase = Boolean(args.lastSnapshot) && args.contextWindow.hasSnapshot;\n\n if (!binding) return;\n if (binding.status !== 'active') {\n if (!hasBase || prior?.status !== 'active') return;\n return {\n id: STATE_ID,\n cacheKey: `factory:none:${prior.bindingId ?? 'revoked'}`,\n mode: 'snapshot',\n tagName: 'factory-phase',\n contents: '\\n',\n value: { phase: { status: 'none' } },\n attributes: { status: 'none' },\n metadata: { value: { phase: { status: 'none' } } },\n };\n }\n\n const item = await this.options.storage.get({ orgId: binding.orgId, id: binding.workItemId });\n const stage = ruleStage(this.options.boards, item);\n if (!item || !stage) return;\n const allItems = await this.options.storage.list({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n });\n const linked = allItems\n .filter(candidate => candidate.parentWorkItemId === item.id || item.parentWorkItemId === candidate.id)\n .slice(0, MAX_LINKED_ITEMS);\n const board = boardForWorkItem(item);\n const baseValue: ActivePhaseSnapshotBase = {\n status: 'active',\n bindingId: binding.id,\n itemId: item.id,\n revision: item.revision,\n stage,\n role: binding.role,\n configVersion: this.options.configVersion,\n };\n const value: ActivePhaseSnapshotValue =\n board === 'review'\n ? { ...baseValue, board, ...reviewRuntimeFromRequestContext(args.requestContext) }\n : { ...baseValue, board };\n const cacheKey = phaseCacheKey(value, linked);\n if (hasBase && (args.tracking?.currentCacheKey ?? args.lastSnapshot?.metadata?.state?.cacheKey) === cacheKey)\n return;\n\n const linkedText = linked.length\n ? `\\nLinked items: ${linked.map(candidate => `${workItemSource(candidate.externalSource)} ${candidate.title}`).join('; ')}`\n : '';\n const phaseLabel = this.options.boards.get(board)?.phases[stage]?.title ?? stage;\n const runtime =\n value.modelId && value.thinkingLevel ? { modelId: value.modelId, thinkingLevel: value.thinkingLevel } : null;\n const snapshotContents =\n `Factory ${board} phase: ${escapeText(phaseLabel)} (${escapeText(stage)})\\n` +\n `Work item: ${escapeText(item.title)} (${item.id})\\n` +\n `Role: ${escapeText(binding.role)}\\nRevision: ${item.revision}\\nConfig: ${escapeText(this.options.configVersion)}\\n` +\n (runtime\n ? `Runtime: model=${escapeText(runtime.modelId)}, reasoning-setting=${escapeText(runtime.thinkingLevel)}\\n`\n : '') +\n ((board === 'work' || board === 'review') && stage === 'intake'\n ? 'This card rests in Intake: its work is paused. Answer questions without moving it; when the user asks to resume, request the transition into the working stage first, then continue the work in this session.\\n'\n : '') +\n `Use factory_transition_work_item with expectedRevision ${item.revision} to request a phase change.${escapeText(linkedText)}`;\n const isDelta = hasBase && prior?.status === 'active';\n return {\n id: STATE_ID,\n cacheKey,\n mode: isDelta ? 'delta' : 'snapshot',\n tagName: 'factory-phase',\n contents: isDelta ? `Factory phase update:\\n${snapshotContents}` : snapshotContents,\n value: { phase: value },\n ...(isDelta ? { delta: { phase: value } } : {}),\n attributes: {\n status: 'active',\n board,\n stage,\n role: binding.role,\n revision: item.revision,\n ...(runtime ?? {}),\n },\n metadata: { value: { phase: value } },\n };\n }\n\n async reconcileAllBoundThreads(): Promise<void> {\n if (!this.options.messageReader) return;\n const bindings = await this.options.storage.listActiveRunBindings();\n for (const binding of bindings) await this.reconcileBinding(binding);\n }\n\n async reconcileBinding(binding: FactoryRunBindingRecord): Promise<void> {\n const reader = this.options.messageReader;\n if (!reader || binding.status !== 'active') return;\n // One keyed read up front: a binding whose item is gone or no longer in a\n // single rule stage would ingest nothing, so skip the cursor and message\n // reads entirely instead of paying them on every walk.\n const item = await this.options.storage.get({ orgId: binding.orgId, id: binding.workItemId });\n if (!itemInRuleStage(this.options.boards, item)) return;\n const cursor = await this.options.storage.getToolResultCursor(binding.orgId, binding.factoryProjectId, binding.id);\n let page = 0;\n while (true) {\n const result = await reader.listMessages({\n threadId: binding.threadId,\n resourceId: binding.resourceId,\n page,\n perPage: TRANSCRIPT_PAGE_SIZE,\n ...(cursor ? { filter: { dateRange: { start: cursor.lastMessageCreatedAt } } } : {}),\n orderBy: { field: 'createdAt', direction: 'ASC' },\n });\n await this.ingestMessages(binding, result.messages, undefined, item);\n const last = result.messages.at(-1);\n if (last) {\n await this.options.storage.advanceToolResultCursor({\n bindingId: binding.id,\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n lastMessageId: last.id,\n lastMessageCreatedAt: last.createdAt instanceof Date ? last.createdAt : new Date(last.createdAt),\n updatedAt: new Date(),\n });\n }\n if (!result.hasMore) break;\n page += 1;\n }\n }\n\n private async ingestMessages(\n binding: FactoryRunBindingRecord,\n messages: MastraDBMessage[],\n toolCallIds?: ReadonlySet<string>,\n preloadedItem?: WorkItemRow,\n ): Promise<void> {\n const item = preloadedItem ?? (await this.options.storage.get({ orgId: binding.orgId, id: binding.workItemId }));\n if (!itemInRuleStage(this.options.boards, item)) return;\n for (const message of messages) {\n for (const toolResult of completedToolResults(message)) {\n if (toolCallIds && !toolCallIds.has(toolResult.toolCallId)) continue;\n try {\n await this.options.recordPullRequestProvenance?.({\n binding,\n item,\n assistantMessageId: toolResult.assistantMessageId,\n toolCallId: toolResult.toolCallId,\n toolName: toolResult.toolName,\n toolInput: toolResult.input,\n toolResult: toolResult.value,\n status: toolResult.status,\n });\n } catch {\n // Provenance is supporting evidence and must not block authoritative rule ingress.\n }\n await this.ingestToolResult(binding, item, toolResult);\n }\n }\n }\n\n private async ingestToolResult(\n binding: FactoryRunBindingRecord,\n item: WorkItemRow,\n toolResult: CompletedToolResult,\n ): Promise<void> {\n const board = boardForWorkItem(item);\n const rule = resolveBoardToolRule(this.options.boards, board, toolResult.toolName);\n if (!rule) return;\n const ingressId = JSON.stringify([\n binding.id,\n binding.threadId,\n toolResult.assistantMessageId,\n toolResult.toolCallId,\n ]);\n const prior = await this.options.storage.getTransitionResultByIngress(\n binding.orgId,\n binding.factoryProjectId,\n ingressId,\n );\n if (prior) return;\n const context: FactoryToolResultRuleContext = {\n tenant: { orgId: binding.orgId, projectId: binding.factoryProjectId },\n actor: { type: 'agent', bindingId: binding.id, role: binding.role },\n ingress: { type: 'toolResult', id: ingressId },\n cause: `Completed ${toolResult.toolName}`,\n causalChain: [],\n configVersion: this.options.configVersion,\n item: {\n id: item.id,\n source: workItemSource(item.externalSource),\n sourceKey: workItemSourceKey(item),\n parentWorkItemId: item.parentWorkItemId,\n title: item.title,\n url: item.externalSource?.url ?? null,\n stages: item.stages,\n acceptedAt: item.acceptedAt,\n metadata: item.metadata,\n },\n board,\n itemRevision: item.revision,\n toolName: toolResult.toolName,\n threadId: binding.threadId,\n assistantMessageId: toolResult.assistantMessageId,\n toolCallId: toolResult.toolCallId,\n result: { status: toolResult.status, value: toolResult.value },\n };\n\n let decision: FactoryRuleDecision | void = undefined;\n let decisions: FactoryCommitDecision[] = [];\n let outcome: { status: 'accepted' | 'rejected'; code?: string; reason?: string } = { status: 'accepted' };\n try {\n decision = await withRuleTimeout(Promise.resolve(rule(Object.freeze(context))));\n if (decision?.type === 'reject') {\n outcome = { status: 'rejected', code: decision.code, reason: decision.reason };\n } else if (decision) {\n const validated = validateFactoryRuleDecisions([decision]);\n for (const entry of validated) assertFactoryDecisionTarget(entry, this.options.boards, board);\n decisions = validated;\n }\n } catch (error) {\n const timedOut = error instanceof Error && error.message === 'FACTORY_RULE_TIMEOUT';\n outcome = {\n status: 'rejected',\n code: timedOut ? 'timeout' : 'rule_error',\n reason: timedOut\n ? 'Factory rule evaluation timed out.'\n : error instanceof Error\n ? error.message.slice(0, 2_000)\n : 'Factory tool-result rule failed.',\n };\n }\n const committed = await this.options.storage.commitRuleEvaluation({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: item.id,\n ingress: { identity: ingressId, triggerType: 'tool.result' },\n configVersion: this.options.configVersion,\n expectedRevision: item.revision,\n actor: { ...context.actor },\n outcome,\n decisions: decisions.map(entry => ({ ...entry })),\n causalChain: [],\n now: new Date(),\n });\n if (committed.status !== 'committed' || !this.options.transitionService) return;\n for (const entry of decisions) {\n if (entry.type !== 'transition') continue;\n await this.options.transitionService.transition({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: item.id,\n board: entry.board,\n stage: entry.stage,\n expectedRevision: item.revision,\n actor: { type: 'system', id: 'factory-tool-result-rule' },\n ingress: { type: 'rule', identity: `decision:${entry.idempotencyKey}` },\n cause: 'tool_result_rule',\n causalChain: [{ ingressId, decisionType: entry.type }],\n ...(isApprovedPlanResult(toolResult) ? { planApproved: true } : {}),\n });\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { RequestContext } from '@mastra/core/request-context';
|
|
2
|
+
import type { IntegrationTools } from '../integrations/base.js';
|
|
3
|
+
import type { WorkItemsStorage } from '../storage/domains/work-items/base.js';
|
|
4
|
+
import type { FactorySessionSourceLookup } from './binding-context.js';
|
|
5
|
+
export interface ReviewSourceOutput {
|
|
6
|
+
/**
|
|
7
|
+
* Browser-facing deep-link back to the Factory session that produced this
|
|
8
|
+
* review — the only field a review body publishes. Everything else on this
|
|
9
|
+
* shape is an input to in-run cross-checks and the session handoff, never a
|
|
10
|
+
* published field, so the GitHub/GitLab audience never sees the upstream
|
|
11
|
+
* Linear/Jira slug through the review comment.
|
|
12
|
+
*/
|
|
13
|
+
sessionUrl: string;
|
|
14
|
+
/**
|
|
15
|
+
* PR/MR author recorded on the review card at intake — for GitHub this is
|
|
16
|
+
* `pullRequest.user.login`, for GitLab it is the GitLab username. The skill
|
|
17
|
+
* cross-checks it against `gh pr view --json author` (`.author.login`) or
|
|
18
|
+
* the equivalent MR fetch; a mismatch means the session is bound to a
|
|
19
|
+
* different PR/MR than the one being reviewed.
|
|
20
|
+
*/
|
|
21
|
+
triggeredBy: string | null;
|
|
22
|
+
/**
|
|
23
|
+
* The externalSource recorded on the review card itself — the PR/MR the
|
|
24
|
+
* session was intake-bound to. The skill cross-checks the reviewed PR/MR
|
|
25
|
+
* against this shape so a review can never quietly land on the wrong target.
|
|
26
|
+
*/
|
|
27
|
+
reviewTarget: {
|
|
28
|
+
integrationId: string;
|
|
29
|
+
type: string;
|
|
30
|
+
externalId: string;
|
|
31
|
+
url: string | null;
|
|
32
|
+
} | null;
|
|
33
|
+
/**
|
|
34
|
+
* Repository identity stamped on the review card at intake — the
|
|
35
|
+
* binding-side value for the null-`url` cross-check fallback. The skill must
|
|
36
|
+
* compare this against an identity resolved independently of the card (e.g.
|
|
37
|
+
* `gh api repos/<owner>/<repo> --jq .id` — the numeric REST id — for the
|
|
38
|
+
* session checkout's repository);
|
|
39
|
+
* comparing two values derived from the same checkout proves nothing.
|
|
40
|
+
* `null` when intake recorded no repository identity — in that case there
|
|
41
|
+
* is no verifiable bound repository and the fallback must not publish.
|
|
42
|
+
*/
|
|
43
|
+
boundRepository: {
|
|
44
|
+
provider: 'github';
|
|
45
|
+
repositoryId: number;
|
|
46
|
+
} | {
|
|
47
|
+
provider: 'gitlab';
|
|
48
|
+
host: string | null;
|
|
49
|
+
projectId: number;
|
|
50
|
+
} | null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Normalize the operator-provided browser-facing UI origin (the
|
|
54
|
+
* `MASTRACODE_PUBLIC_URL` env var) into the `uiOrigin` the tool accepts.
|
|
55
|
+
*
|
|
56
|
+
* Blank counts as unset: `mastracode/web/.env.schema` ships
|
|
57
|
+
* `MASTRACODE_PUBLIC_URL=` as the operator-facing line, so an empty or
|
|
58
|
+
* whitespace-only value is the expected "not configured" state — it must map
|
|
59
|
+
* to `null` (tool omitted) rather than register the tool and publish a
|
|
60
|
+
* hostless `sessionUrl` into a public review body.
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveReviewSourceUiOrigin(raw: string | undefined): string | null;
|
|
63
|
+
/**
|
|
64
|
+
* `factory_review_source` — read the routing facts behind the current review
|
|
65
|
+
* session (session deep-link, PR/MR author, the review target) so the review
|
|
66
|
+
* agent can attribute its verdict to a concrete originating run and detect a
|
|
67
|
+
* mis-targeted review before it publishes.
|
|
68
|
+
*
|
|
69
|
+
* The tool exists to make cross-PR misattribution detectable both in-run and
|
|
70
|
+
* after the fact: a review lands on GitHub/GitLab with the Factory session URL
|
|
71
|
+
* that produced it, and the human who investigates a suspicious review can
|
|
72
|
+
* jump straight to the run.
|
|
73
|
+
*
|
|
74
|
+
* All fields are derived server-side from the bound work item — nothing enters
|
|
75
|
+
* from the agent's tool arguments — so the session URL always points at the
|
|
76
|
+
* caller's own session, never one it was asked to name.
|
|
77
|
+
*
|
|
78
|
+
* The tool is intentionally omitted (returns `{}`) whenever it cannot produce
|
|
79
|
+
* a complete, correct provenance shape: no resolvable session address, no
|
|
80
|
+
* active binding, a non-review-role binding, no bound work item, or no
|
|
81
|
+
* configured browser-facing UI origin. Skills treat tool-absent identically to
|
|
82
|
+
* tool-failed: stop-don't-publish.
|
|
83
|
+
*/
|
|
84
|
+
export declare function createReviewSourceTool(options: {
|
|
85
|
+
requestContext: RequestContext;
|
|
86
|
+
storage: Pick<WorkItemsStorage, 'findActiveRunBindingByThread' | 'findActiveRunBinding' | 'get'>;
|
|
87
|
+
sessions?: FactorySessionSourceLookup;
|
|
88
|
+
/**
|
|
89
|
+
* Browser-facing UI origin, no trailing slash. In a separate-SPA deployment
|
|
90
|
+
* (API host distinct from UI host) this is the UI host — the origin the
|
|
91
|
+
* emitted session link opens in. `MastraFactoryConfig.publicUrl` is the API
|
|
92
|
+
* origin and must not be used here; the caller resolves the UI origin (see
|
|
93
|
+
* `factory.ts`) via the `MASTRACODE_PUBLIC_URL` env var.
|
|
94
|
+
*
|
|
95
|
+
* `null` means no browser-facing origin is configured. Match the Slack
|
|
96
|
+
* session-link surface (`integrations/slack/slack.ts:168-171`, `:809-812`),
|
|
97
|
+
* which omits the deep-link entirely rather than publishing an API/localhost
|
|
98
|
+
* URL into a public body: the tool is dropped from the toolset, and the
|
|
99
|
+
* skill's tool-not-available branch stops publication.
|
|
100
|
+
*/
|
|
101
|
+
uiOrigin: string | null;
|
|
102
|
+
}): Promise<IntegrationTools>;
|
|
103
|
+
//# sourceMappingURL=review-source-tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-source-tool.d.ts","sourceRoot":"","sources":["../../src/rules/review-source-tool.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;AAC3F,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAGvE,MAAM,WAAW,kBAAkB;IACjC;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;;;OAIG;IACH,YAAY,EAAE;QACZ,aAAa,EAAE,MAAM,CAAC;QACtB,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;KACpB,GAAG,IAAI,CAAC;IACT;;;;;;;;;OASG;IACH,eAAe,EACX;QAAE,QAAQ,EAAE,QAAQ,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,GAC5C;QAAE,QAAQ,EAAE,QAAQ,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAC9D,IAAI,CAAC;CACV;AAED;;;;;;;;;GASG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAElF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAAE;IACpD,cAAc,EAAE,cAAc,CAAC;IAC/B,OAAO,EAAE,IAAI,CAAC,gBAAgB,EAAE,8BAA8B,GAAG,sBAAsB,GAAG,KAAK,CAAC,CAAC;IACjG,QAAQ,CAAC,EAAE,0BAA0B,CAAC;IACtC;;;;;;;;;;;;OAYG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAyC5B"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { resolveFactorySessionAddress } from "./binding-context.js";
|
|
2
|
+
import { createTool } from "@mastra/core/tools";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
//#region src/rules/review-source-tool.ts
|
|
5
|
+
/**
|
|
6
|
+
* Normalize the operator-provided browser-facing UI origin (the
|
|
7
|
+
* `MASTRACODE_PUBLIC_URL` env var) into the `uiOrigin` the tool accepts.
|
|
8
|
+
*
|
|
9
|
+
* Blank counts as unset: `mastracode/web/.env.schema` ships
|
|
10
|
+
* `MASTRACODE_PUBLIC_URL=` as the operator-facing line, so an empty or
|
|
11
|
+
* whitespace-only value is the expected "not configured" state — it must map
|
|
12
|
+
* to `null` (tool omitted) rather than register the tool and publish a
|
|
13
|
+
* hostless `sessionUrl` into a public review body.
|
|
14
|
+
*/
|
|
15
|
+
function resolveReviewSourceUiOrigin(raw) {
|
|
16
|
+
return raw?.trim().replace(/\/+$/, "") || null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* `factory_review_source` — read the routing facts behind the current review
|
|
20
|
+
* session (session deep-link, PR/MR author, the review target) so the review
|
|
21
|
+
* agent can attribute its verdict to a concrete originating run and detect a
|
|
22
|
+
* mis-targeted review before it publishes.
|
|
23
|
+
*
|
|
24
|
+
* The tool exists to make cross-PR misattribution detectable both in-run and
|
|
25
|
+
* after the fact: a review lands on GitHub/GitLab with the Factory session URL
|
|
26
|
+
* that produced it, and the human who investigates a suspicious review can
|
|
27
|
+
* jump straight to the run.
|
|
28
|
+
*
|
|
29
|
+
* All fields are derived server-side from the bound work item — nothing enters
|
|
30
|
+
* from the agent's tool arguments — so the session URL always points at the
|
|
31
|
+
* caller's own session, never one it was asked to name.
|
|
32
|
+
*
|
|
33
|
+
* The tool is intentionally omitted (returns `{}`) whenever it cannot produce
|
|
34
|
+
* a complete, correct provenance shape: no resolvable session address, no
|
|
35
|
+
* active binding, a non-review-role binding, no bound work item, or no
|
|
36
|
+
* configured browser-facing UI origin. Skills treat tool-absent identically to
|
|
37
|
+
* tool-failed: stop-don't-publish.
|
|
38
|
+
*/
|
|
39
|
+
async function createReviewSourceTool(options) {
|
|
40
|
+
if (options.uiOrigin === null) return {};
|
|
41
|
+
const resolution = await resolveFactorySessionAddress({
|
|
42
|
+
requestContext: options.requestContext,
|
|
43
|
+
storage: options.storage,
|
|
44
|
+
sessions: options.sessions
|
|
45
|
+
});
|
|
46
|
+
if (!resolution) return {};
|
|
47
|
+
const binding = resolution.binding ?? await options.storage.findActiveRunBinding(resolution.address);
|
|
48
|
+
if (!binding || binding.role !== "review") return {};
|
|
49
|
+
const item = await options.storage.get({
|
|
50
|
+
orgId: binding.orgId,
|
|
51
|
+
id: binding.workItemId
|
|
52
|
+
});
|
|
53
|
+
if (!item) return {};
|
|
54
|
+
const output = {
|
|
55
|
+
sessionUrl: buildSessionUrl({
|
|
56
|
+
uiOrigin: options.uiOrigin,
|
|
57
|
+
factoryProjectId: binding.factoryProjectId,
|
|
58
|
+
sessionId: binding.sessionId,
|
|
59
|
+
threadId: binding.threadId
|
|
60
|
+
}),
|
|
61
|
+
triggeredBy: readPullRequestAuthor(item),
|
|
62
|
+
reviewTarget: readReviewTarget(item),
|
|
63
|
+
boundRepository: readBoundRepository(item)
|
|
64
|
+
};
|
|
65
|
+
return { factory_review_source: createTool({
|
|
66
|
+
id: "factory_review_source",
|
|
67
|
+
description: "Read the routing facts behind this review session: a deep-link back to the Factory session that produced the review, the PR/MR author recorded on the review card, and the review target (the PR/MR the session was intake-bound to). Publish the session URL in the required `Factory Session` block of every review body so a misattributed review can always be traced back to the run that produced it. Cross-check `triggeredBy` and `reviewTarget` against the PR/MR you fetched in Phase 1, using `boundRepository` as the intake-stamped repository identity when `reviewTarget.url` is null; do not publish the `triggeredBy`, `reviewTarget`, or `boundRepository` fields.",
|
|
68
|
+
inputSchema: z.object({}),
|
|
69
|
+
execute: async () => output
|
|
70
|
+
}) };
|
|
71
|
+
}
|
|
72
|
+
function buildSessionUrl(input) {
|
|
73
|
+
return `${input.uiOrigin.replace(/\/+$/, "")}/factories/${encodeURIComponent(input.factoryProjectId)}/workspaces/${encodeURIComponent(input.sessionId)}/threads/${encodeURIComponent(input.threadId)}`;
|
|
74
|
+
}
|
|
75
|
+
function readReviewTarget(item) {
|
|
76
|
+
const source = item.externalSource;
|
|
77
|
+
if (!source) return null;
|
|
78
|
+
return {
|
|
79
|
+
integrationId: source.integrationId,
|
|
80
|
+
type: source.type,
|
|
81
|
+
externalId: source.externalId,
|
|
82
|
+
url: typeof source.url === "string" && source.url ? source.url : null
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function readBoundRepository(item) {
|
|
86
|
+
const metadata = item.metadata;
|
|
87
|
+
if (!metadata) return null;
|
|
88
|
+
if (typeof metadata.githubRepositoryId === "number") return {
|
|
89
|
+
provider: "github",
|
|
90
|
+
repositoryId: metadata.githubRepositoryId
|
|
91
|
+
};
|
|
92
|
+
if (typeof metadata.gitlabProjectId === "number") return {
|
|
93
|
+
provider: "gitlab",
|
|
94
|
+
host: typeof metadata.gitlabHost === "string" && metadata.gitlabHost ? metadata.gitlabHost : null,
|
|
95
|
+
projectId: metadata.gitlabProjectId
|
|
96
|
+
};
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
function readPullRequestAuthor(item) {
|
|
100
|
+
const metadata = item.metadata;
|
|
101
|
+
if (!metadata) return null;
|
|
102
|
+
const author = metadata.author;
|
|
103
|
+
if (typeof author === "string" && author.trim()) return author.trim();
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { createReviewSourceTool, resolveReviewSourceUiOrigin };
|
|
108
|
+
|
|
109
|
+
//# sourceMappingURL=review-source-tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-source-tool.js","names":[],"sources":["../../src/rules/review-source-tool.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 { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { FactorySessionSourceLookup } from './binding-context.js';\nimport { resolveFactorySessionAddress } from './binding-context.js';\n\nexport interface ReviewSourceOutput {\n /**\n * Browser-facing deep-link back to the Factory session that produced this\n * review — the only field a review body publishes. Everything else on this\n * shape is an input to in-run cross-checks and the session handoff, never a\n * published field, so the GitHub/GitLab audience never sees the upstream\n * Linear/Jira slug through the review comment.\n */\n sessionUrl: string;\n /**\n * PR/MR author recorded on the review card at intake — for GitHub this is\n * `pullRequest.user.login`, for GitLab it is the GitLab username. The skill\n * cross-checks it against `gh pr view --json author` (`.author.login`) or\n * the equivalent MR fetch; a mismatch means the session is bound to a\n * different PR/MR than the one being reviewed.\n */\n triggeredBy: string | null;\n /**\n * The externalSource recorded on the review card itself — the PR/MR the\n * session was intake-bound to. The skill cross-checks the reviewed PR/MR\n * against this shape so a review can never quietly land on the wrong target.\n */\n reviewTarget: {\n integrationId: string;\n type: string;\n externalId: string;\n url: string | null;\n } | null;\n /**\n * Repository identity stamped on the review card at intake — the\n * binding-side value for the null-`url` cross-check fallback. The skill must\n * compare this against an identity resolved independently of the card (e.g.\n * `gh api repos/<owner>/<repo> --jq .id` — the numeric REST id — for the\n * session checkout's repository);\n * comparing two values derived from the same checkout proves nothing.\n * `null` when intake recorded no repository identity — in that case there\n * is no verifiable bound repository and the fallback must not publish.\n */\n boundRepository:\n | { provider: 'github'; repositoryId: number }\n | { provider: 'gitlab'; host: string | null; projectId: number }\n | null;\n}\n\n/**\n * Normalize the operator-provided browser-facing UI origin (the\n * `MASTRACODE_PUBLIC_URL` env var) into the `uiOrigin` the tool accepts.\n *\n * Blank counts as unset: `mastracode/web/.env.schema` ships\n * `MASTRACODE_PUBLIC_URL=` as the operator-facing line, so an empty or\n * whitespace-only value is the expected \"not configured\" state — it must map\n * to `null` (tool omitted) rather than register the tool and publish a\n * hostless `sessionUrl` into a public review body.\n */\nexport function resolveReviewSourceUiOrigin(raw: string | undefined): string | null {\n return raw?.trim().replace(/\\/+$/, '') || null;\n}\n\n/**\n * `factory_review_source` — read the routing facts behind the current review\n * session (session deep-link, PR/MR author, the review target) so the review\n * agent can attribute its verdict to a concrete originating run and detect a\n * mis-targeted review before it publishes.\n *\n * The tool exists to make cross-PR misattribution detectable both in-run and\n * after the fact: a review lands on GitHub/GitLab with the Factory session URL\n * that produced it, and the human who investigates a suspicious review can\n * jump straight to the run.\n *\n * All fields are derived server-side from the bound work item — nothing enters\n * from the agent's tool arguments — so the session URL always points at the\n * caller's own session, never one it was asked to name.\n *\n * The tool is intentionally omitted (returns `{}`) whenever it cannot produce\n * a complete, correct provenance shape: no resolvable session address, no\n * active binding, a non-review-role binding, no bound work item, or no\n * configured browser-facing UI origin. Skills treat tool-absent identically to\n * tool-failed: stop-don't-publish.\n */\nexport async function createReviewSourceTool(options: {\n requestContext: RequestContext;\n storage: Pick<WorkItemsStorage, 'findActiveRunBindingByThread' | 'findActiveRunBinding' | 'get'>;\n sessions?: FactorySessionSourceLookup;\n /**\n * Browser-facing UI origin, no trailing slash. In a separate-SPA deployment\n * (API host distinct from UI host) this is the UI host — the origin the\n * emitted session link opens in. `MastraFactoryConfig.publicUrl` is the API\n * origin and must not be used here; the caller resolves the UI origin (see\n * `factory.ts`) via the `MASTRACODE_PUBLIC_URL` env var.\n *\n * `null` means no browser-facing origin is configured. Match the Slack\n * session-link surface (`integrations/slack/slack.ts:168-171`, `:809-812`),\n * which omits the deep-link entirely rather than publishing an API/localhost\n * URL into a public body: the tool is dropped from the toolset, and the\n * skill's tool-not-available branch stops publication.\n */\n uiOrigin: string | null;\n}): Promise<IntegrationTools> {\n if (options.uiOrigin === null) return {};\n const resolution = await resolveFactorySessionAddress({\n requestContext: options.requestContext,\n storage: options.storage,\n sessions: options.sessions,\n });\n if (!resolution) return {};\n const binding = resolution.binding ?? (await options.storage.findActiveRunBinding(resolution.address));\n // The tool exists only for review-role sessions. Work/plan/triage runs never\n // publish reviews on GitHub, so exposing them the tool would just clutter\n // their prompt.\n if (!binding || binding.role !== 'review') return {};\n\n const item = await options.storage.get({ orgId: binding.orgId, id: binding.workItemId });\n if (!item) return {};\n\n const sessionUrl = buildSessionUrl({\n uiOrigin: options.uiOrigin,\n factoryProjectId: binding.factoryProjectId,\n sessionId: binding.sessionId,\n threadId: binding.threadId,\n });\n const triggeredBy = readPullRequestAuthor(item);\n const reviewTarget = readReviewTarget(item);\n const boundRepository = readBoundRepository(item);\n // Every field is pre-resolved server-side and returned as a plain object.\n // The tool takes no arguments and does no further I/O, so a review-role\n // session either sees a complete provenance shape or the tool is absent —\n // the skill treats those branches identically (stop-don't-publish).\n const output: ReviewSourceOutput = { sessionUrl, triggeredBy, reviewTarget, boundRepository };\n\n return {\n factory_review_source: createTool({\n id: 'factory_review_source',\n description:\n 'Read the routing facts behind this review session: a deep-link back to the Factory session that produced the review, the PR/MR author recorded on the review card, and the review target (the PR/MR the session was intake-bound to). Publish the session URL in the required `Factory Session` block of every review body so a misattributed review can always be traced back to the run that produced it. Cross-check `triggeredBy` and `reviewTarget` against the PR/MR you fetched in Phase 1, using `boundRepository` as the intake-stamped repository identity when `reviewTarget.url` is null; do not publish the `triggeredBy`, `reviewTarget`, or `boundRepository` fields.',\n inputSchema: z.object({}),\n execute: async (): Promise<ReviewSourceOutput> => output,\n }),\n };\n}\n\nfunction buildSessionUrl(input: {\n uiOrigin: string;\n factoryProjectId: string;\n sessionId: string;\n threadId: string;\n}): string {\n const origin = input.uiOrigin.replace(/\\/+$/, '');\n return `${origin}/factories/${encodeURIComponent(input.factoryProjectId)}/workspaces/${encodeURIComponent(input.sessionId)}/threads/${encodeURIComponent(input.threadId)}`;\n}\n\nfunction readReviewTarget(item: WorkItemRow): ReviewSourceOutput['reviewTarget'] {\n const source = item.externalSource;\n if (!source) return null;\n return {\n integrationId: source.integrationId,\n type: source.type,\n externalId: source.externalId,\n url: typeof source.url === 'string' && source.url ? source.url : null,\n };\n}\n\nfunction readBoundRepository(item: WorkItemRow): ReviewSourceOutput['boundRepository'] {\n const metadata = item.metadata;\n if (!metadata) return null;\n // Intake stamps repository identity on every PR/MR card:\n // `github/default-rules.ts` writes `githubRepositoryId`, and\n // `gitlab/default-rules.ts` writes `gitlabHost` + `gitlabProjectId`.\n if (typeof metadata.githubRepositoryId === 'number') {\n return { provider: 'github', repositoryId: metadata.githubRepositoryId };\n }\n if (typeof metadata.gitlabProjectId === 'number') {\n return {\n provider: 'gitlab',\n host: typeof metadata.gitlabHost === 'string' && metadata.gitlabHost ? metadata.gitlabHost : null,\n projectId: metadata.gitlabProjectId,\n };\n }\n return null;\n}\n\nfunction readPullRequestAuthor(item: WorkItemRow): string | null {\n const metadata = item.metadata;\n if (!metadata) return null;\n const author = metadata.author;\n if (typeof author === 'string' && author.trim()) return author.trim();\n return null;\n}\n"],"mappings":";;;;;;;;;;;;;;AA+DA,SAAgB,4BAA4B,KAAwC;CAClF,OAAO,KAAK,KAAK,CAAC,CAAC,QAAQ,QAAQ,EAAE,KAAK;AAC5C;;;;;;;;;;;;;;;;;;;;;;AAuBA,eAAsB,uBAAuB,SAkBf;CAC5B,IAAI,QAAQ,aAAa,MAAM,OAAO,CAAC;CACvC,MAAM,aAAa,MAAM,6BAA6B;EACpD,gBAAgB,QAAQ;EACxB,SAAS,QAAQ;EACjB,UAAU,QAAQ;CACpB,CAAC;CACD,IAAI,CAAC,YAAY,OAAO,CAAC;CACzB,MAAM,UAAU,WAAW,WAAY,MAAM,QAAQ,QAAQ,qBAAqB,WAAW,OAAO;CAIpG,IAAI,CAAC,WAAW,QAAQ,SAAS,UAAU,OAAO,CAAC;CAEnD,MAAM,OAAO,MAAM,QAAQ,QAAQ,IAAI;EAAE,OAAO,QAAQ;EAAO,IAAI,QAAQ;CAAW,CAAC;CACvF,IAAI,CAAC,MAAM,OAAO,CAAC;CAenB,MAAM,SAA6B;EAAE,YAblB,gBAAgB;GACjC,UAAU,QAAQ;GAClB,kBAAkB,QAAQ;GAC1B,WAAW,QAAQ;GACnB,UAAU,QAAQ;EACpB,CAQ8C;EAAG,aAP7B,sBAAsB,IAOiB;EAAG,cANzC,iBAAiB,IAMmC;EAAG,iBALpD,oBAAoB,IAK8C;CAAE;CAE5F,OAAO,EACL,uBAAuB,WAAW;EAChC,IAAI;EACJ,aACE;EACF,aAAa,EAAE,OAAO,CAAC,CAAC;EACxB,SAAS,YAAyC;CACpD,CAAC,EACH;AACF;AAEA,SAAS,gBAAgB,OAKd;CAET,OAAO,GADQ,MAAM,SAAS,QAAQ,QAAQ,EAC/B,EAAE,aAAa,mBAAmB,MAAM,gBAAgB,EAAE,cAAc,mBAAmB,MAAM,SAAS,EAAE,WAAW,mBAAmB,MAAM,QAAQ;AACzK;AAEA,SAAS,iBAAiB,MAAuD;CAC/E,MAAM,SAAS,KAAK;CACpB,IAAI,CAAC,QAAQ,OAAO;CACpB,OAAO;EACL,eAAe,OAAO;EACtB,MAAM,OAAO;EACb,YAAY,OAAO;EACnB,KAAK,OAAO,OAAO,QAAQ,YAAY,OAAO,MAAM,OAAO,MAAM;CACnE;AACF;AAEA,SAAS,oBAAoB,MAA0D;CACrF,MAAM,WAAW,KAAK;CACtB,IAAI,CAAC,UAAU,OAAO;CAItB,IAAI,OAAO,SAAS,uBAAuB,UACzC,OAAO;EAAE,UAAU;EAAU,cAAc,SAAS;CAAmB;CAEzE,IAAI,OAAO,SAAS,oBAAoB,UACtC,OAAO;EACL,UAAU;EACV,MAAM,OAAO,SAAS,eAAe,YAAY,SAAS,aAAa,SAAS,aAAa;EAC7F,WAAW,SAAS;CACtB;CAEF,OAAO;AACT;AAEA,SAAS,sBAAsB,MAAkC;CAC/D,MAAM,WAAW,KAAK;CACtB,IAAI,CAAC,UAAU,OAAO;CACtB,MAAM,SAAS,SAAS;CACxB,IAAI,OAAO,WAAW,YAAY,OAAO,KAAK,GAAG,OAAO,OAAO,KAAK;CACpE,OAAO;AACT"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { MastraCodeState } from '@mastra/code-sdk/schema';
|
|
2
2
|
import type { AgentController } from '@mastra/core/agent-controller';
|
|
3
3
|
import { RequestContext } from '@mastra/core/request-context';
|
|
4
|
+
import type { IntakeStorage } from '../storage/domains/intake/base.js';
|
|
4
5
|
import type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';
|
|
5
6
|
import type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';
|
|
6
7
|
import type { CreateWorkItemInput, WorkItemsStorage } from '../storage/domains/work-items/base.js';
|
|
@@ -23,6 +24,11 @@ export interface FactoryStartRequest {
|
|
|
23
24
|
};
|
|
24
25
|
requestContext?: RequestContext;
|
|
25
26
|
}
|
|
27
|
+
export declare class WorkItemRepositoryError extends Error {
|
|
28
|
+
readonly code: 'repository_mismatch' | 'repository_ambiguous' | 'repository_unlinked';
|
|
29
|
+
readonly candidates: string[];
|
|
30
|
+
constructor(code: 'repository_mismatch' | 'repository_ambiguous' | 'repository_unlinked', message: string, candidates?: string[]);
|
|
31
|
+
}
|
|
26
32
|
export declare class FactoryStartTransitionError extends Error {
|
|
27
33
|
readonly result: Extract<FactoryTransitionResult, {
|
|
28
34
|
status: 'rejected';
|
|
@@ -45,7 +51,7 @@ export interface FactoryStartPreparedResult {
|
|
|
45
51
|
type FactoryController = AgentController<MastraCodeState>;
|
|
46
52
|
export declare class FactoryStartCoordinator {
|
|
47
53
|
#private;
|
|
48
|
-
constructor(controller: FactoryController, storage: WorkItemsStorage, transitionService?: Pick<FactoryTransitionService, 'transition'>, sourceControl?: SourceControlStorageHandle | ((request: FactoryStartRequest) => SourceControlStorageHandle | undefined | Promise<SourceControlStorageHandle | undefined>), memorySettings?: MemorySettingsStorage);
|
|
54
|
+
constructor(controller: FactoryController, storage: WorkItemsStorage, transitionService?: Pick<FactoryTransitionService, 'transition'>, sourceControl?: SourceControlStorageHandle | ((request: FactoryStartRequest) => SourceControlStorageHandle | undefined | Promise<SourceControlStorageHandle | undefined>), memorySettings?: MemorySettingsStorage, intake?: IntakeStorage);
|
|
49
55
|
prepare(request: FactoryStartRequest): Promise<FactoryStartPreparedResult>;
|
|
50
56
|
}
|
|
51
57
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start-coordinator.d.ts","sourceRoot":"","sources":["../../src/rules/start-coordinator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"start-coordinator.d.ts","sourceRoot":"","sources":["../../src/rules/start-coordinator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAK9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AACxF,OAAO,KAAK,EAAwB,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AAClH,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AACnG,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE5E,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,8FAA8F;IAC9F,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE;QACR,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,mBAAmB,CAAC;KAC5B,CAAC;IACF,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,qBAAa,uBAAwB,SAAQ,KAAK;IAE9C,QAAQ,CAAC,IAAI,EAAE,qBAAqB,GAAG,sBAAsB,GAAG,qBAAqB;IAErF,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE;gBAFpB,IAAI,EAAE,qBAAqB,GAAG,sBAAsB,GAAG,qBAAqB,EACrF,OAAO,EAAE,MAAM,EACN,UAAU,GAAE,MAAM,EAAO;CAKrC;AAED,qBAAa,2BAA4B,SAAQ,KAAK;IACpD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,uBAAuB,EAAE;QAAE,MAAM,EAAE,UAAU,CAAA;KAAE,CAAC,CAAC;gBAE9D,MAAM,EAAE,OAAO,CAAC,uBAAuB,EAAE;QAAE,MAAM,EAAE,UAAU,CAAA;KAAE,CAAC;CAK7E;AAED,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IAClE,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,KAAK,iBAAiB,GAAG,eAAe,CAAC,eAAe,CAAC,CAAC;AAgC1D,qBAAa,uBAAuB;;gBAahC,UAAU,EAAE,iBAAiB,EAC7B,OAAO,EAAE,gBAAgB,EACzB,iBAAiB,CAAC,EAAE,IAAI,CAAC,wBAAwB,EAAE,YAAY,CAAC,EAChE,aAAa,CAAC,EACV,0BAA0B,GAC1B,CAAC,CACC,OAAO,EAAE,mBAAmB,KACzB,0BAA0B,GAAG,SAAS,GAAG,OAAO,CAAC,0BAA0B,GAAG,SAAS,CAAC,CAAC,EAClG,cAAc,CAAC,EAAE,qBAAqB,EACtC,MAAM,CAAC,EAAE,aAAa;IAUlB,OAAO,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,0BAA0B,CAAC;CAoLjF"}
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import { boardForWorkItem } from "../boards/semantics.js";
|
|
2
2
|
import "../boards/index.js";
|
|
3
3
|
import { hydrateFactorySession } from "../session/factory-session.js";
|
|
4
|
+
import { resolveWorkItemRepository } from "../session/work-item-repository.js";
|
|
4
5
|
import { RequestContext } from "@mastra/core/request-context";
|
|
5
6
|
//#region src/rules/start-coordinator.ts
|
|
7
|
+
var WorkItemRepositoryError = class extends Error {
|
|
8
|
+
code;
|
|
9
|
+
candidates;
|
|
10
|
+
constructor(code, message, candidates = []) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.candidates = candidates;
|
|
14
|
+
this.name = "WorkItemRepositoryError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
6
17
|
var FactoryStartTransitionError = class extends Error {
|
|
7
18
|
result;
|
|
8
19
|
constructor(result) {
|
|
@@ -44,18 +55,58 @@ var FactoryStartCoordinator = class {
|
|
|
44
55
|
#transitionService;
|
|
45
56
|
#sourceControl;
|
|
46
57
|
#memorySettings;
|
|
47
|
-
|
|
58
|
+
#intake;
|
|
59
|
+
constructor(controller, storage, transitionService, sourceControl, memorySettings, intake) {
|
|
48
60
|
this.#controller = controller;
|
|
49
61
|
this.#storage = storage;
|
|
50
62
|
this.#transitionService = transitionService;
|
|
51
63
|
this.#sourceControl = sourceControl;
|
|
52
64
|
this.#memorySettings = memorySettings;
|
|
65
|
+
this.#intake = intake;
|
|
53
66
|
}
|
|
54
67
|
async prepare(request) {
|
|
55
68
|
const storage = this.#storage;
|
|
56
69
|
const sourceControl = typeof this.#sourceControl === "function" ? await this.#sourceControl(request) : this.#sourceControl;
|
|
57
70
|
if (!sourceControl) throw new Error("Factory source control storage is unavailable");
|
|
58
71
|
const sourceSession = await resolveSourceSession(sourceControl, request);
|
|
72
|
+
const storedItem = request.workItem.id ? await storage.get({
|
|
73
|
+
orgId: request.orgId,
|
|
74
|
+
id: request.workItem.id
|
|
75
|
+
}) : null;
|
|
76
|
+
const intakeConfig = await this.#intake?.getConfig({
|
|
77
|
+
orgId: request.orgId,
|
|
78
|
+
integrationIds: ["linear"]
|
|
79
|
+
});
|
|
80
|
+
let repository = await resolveWorkItemRepository({
|
|
81
|
+
sourceControl,
|
|
82
|
+
orgId: request.orgId,
|
|
83
|
+
factoryProjectId: request.factoryProjectId,
|
|
84
|
+
item: storedItem ?? { metadata: request.workItem.input.metadata ?? null },
|
|
85
|
+
linearRepositoryMap: intakeConfig?.linear?.repositoryByLinearProject
|
|
86
|
+
});
|
|
87
|
+
if (repository.status === "ambiguous") {
|
|
88
|
+
if (storedItem?.sessions[request.workItem.role]?.sessionId !== sourceSession.sessionId) throw new WorkItemRepositoryError("repository_ambiguous", `Choose a repository for this work item: ${repository.candidates.join(", ")}.`, repository.candidates);
|
|
89
|
+
const link = await sourceControl.projectRepositories.get({
|
|
90
|
+
orgId: request.orgId,
|
|
91
|
+
id: sourceSession.projectRepositoryId
|
|
92
|
+
});
|
|
93
|
+
const linked = link && await sourceControl.repositories.get({
|
|
94
|
+
orgId: request.orgId,
|
|
95
|
+
id: link.repositoryId
|
|
96
|
+
});
|
|
97
|
+
const currentLinks = link ? await sourceControl.projectRepositories.list({
|
|
98
|
+
orgId: request.orgId,
|
|
99
|
+
connectionId: link.connectionId
|
|
100
|
+
}) : [];
|
|
101
|
+
if (!linked || !repository.candidates.includes(linked.slug) || !currentLinks.some((candidate) => candidate.id === sourceSession.projectRepositoryId)) throw new WorkItemRepositoryError("repository_unlinked", "The existing session repository is not linked to this Factory.");
|
|
102
|
+
repository = {
|
|
103
|
+
status: "resolved",
|
|
104
|
+
slug: linked.slug,
|
|
105
|
+
projectRepositoryId: sourceSession.projectRepositoryId
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (repository.status === "unlinked") throw new WorkItemRepositoryError("repository_unlinked", repository.hint);
|
|
109
|
+
if (repository.projectRepositoryId !== sourceSession.projectRepositoryId) throw new WorkItemRepositoryError("repository_mismatch", `This run session is bound to a different repository than ${repository.slug}.`);
|
|
59
110
|
const requestContext = request.requestContext ?? new RequestContext();
|
|
60
111
|
const existingUser = requestContext.get("user");
|
|
61
112
|
if (existingUser && typeof existingUser === "object") requestContext.set("user", {
|
|
@@ -82,9 +133,12 @@ var FactoryStartCoordinator = class {
|
|
|
82
133
|
requestContext,
|
|
83
134
|
tags: sessionTags
|
|
84
135
|
});
|
|
136
|
+
const targetRepositoryInstruction = `Target repository: ${repository.slug}. Before editing files or creating a pull request, verify this session's checkout matches the target repository.`;
|
|
137
|
+
const existingPluginInstructions = session.state.get()?.pluginInstructions ?? [];
|
|
85
138
|
await session.state.set({
|
|
86
139
|
...sessionTags,
|
|
87
140
|
factoryOrgId: request.orgId,
|
|
141
|
+
pluginInstructions: [...existingPluginInstructions.filter((instruction) => instruction !== targetRepositoryInstruction), targetRepositoryInstruction],
|
|
88
142
|
...untrustedCheckout ? {
|
|
89
143
|
untrustedCheckout: true,
|
|
90
144
|
...baseRef ? { baseRef } : {}
|
|
@@ -166,6 +220,6 @@ var FactoryStartCoordinator = class {
|
|
|
166
220
|
}
|
|
167
221
|
};
|
|
168
222
|
//#endregion
|
|
169
|
-
export { FactoryStartCoordinator, FactoryStartTransitionError };
|
|
223
|
+
export { FactoryStartCoordinator, FactoryStartTransitionError, WorkItemRepositoryError };
|
|
170
224
|
|
|
171
225
|
//# sourceMappingURL=start-coordinator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start-coordinator.js","names":["#controller","#storage","#transitionService","#sourceControl","#memorySettings"],"sources":["../../src/rules/start-coordinator.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport { RequestContext } from '@mastra/core/request-context';\n\nimport { boardForWorkItem } from '../boards/index.js';\nimport { hydrateFactorySession } from '../session/factory-session.js';\nimport type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { SourceControlSession, SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport type { CreateWorkItemInput, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { FactoryTransitionService } from './transition-service.js';\nimport type { FactoryRuleStage, FactoryTransitionResult } from './types.js';\n\nexport interface FactoryStartRequest {\n orgId: string;\n userId: string;\n factoryProjectId: string;\n sessionId: string;\n threadTitle: string;\n kickoffKey: string;\n /** Where the arrival path lands the card; a session opened on an existing card names none. */\n destinationStage?: FactoryRuleStage;\n defaultModelId?: string;\n workItem: {\n id?: string;\n role: string;\n input: CreateWorkItemInput;\n };\n requestContext?: RequestContext;\n}\n\nexport class FactoryStartTransitionError extends Error {\n readonly result: Extract<FactoryTransitionResult, { status: 'rejected' }>;\n\n constructor(result: Extract<FactoryTransitionResult, { status: 'rejected' }>) {\n super(result.reason);\n this.name = 'FactoryStartTransitionError';\n this.result = result;\n }\n}\n\nexport interface FactoryStartPreparedResult {\n workItemId: string;\n bindingId: string;\n threadId: string;\n resourceId: string;\n sessionId: string;\n branch: string;\n revision: number;\n kickoffStatus: 'pending' | 'leased' | 'retry' | 'sent' | 'failed';\n replayed: boolean;\n}\n\ntype FactoryController = AgentController<MastraCodeState>;\ntype FactorySession = Awaited<ReturnType<FactoryController['createSession']>>;\n\nasync function resolveSourceSession(\n storage: SourceControlStorageHandle,\n request: FactoryStartRequest,\n): Promise<SourceControlSession> {\n const session = await storage.sessions.getBySessionId(request.sessionId);\n if (!session || session.orgId !== request.orgId || session.userId !== request.userId) {\n throw new Error('Factory session not found');\n }\n const projectRepository = await storage.projectRepositories.get({\n orgId: request.orgId,\n id: session.projectRepositoryId,\n });\n if (!projectRepository) throw new Error('Factory session repository not found');\n const connection = await storage.connections.get({ orgId: request.orgId, id: projectRepository.connectionId });\n if (!connection || connection.factoryProjectId !== request.factoryProjectId) {\n throw new Error('Factory session does not belong to this project');\n }\n return session;\n}\n\nasync function configureThread(session: FactorySession, request: FactoryStartRequest): Promise<string> {\n const threadId = session.thread.requireId();\n // Derived from the work item, not a user rename, so leave the title\n // unpinned and let auto-naming keep refining it.\n await session.thread.rename({ title: request.threadTitle, pin: false });\n await session.thread.setSetting({ key: 'factorySessionId', value: request.sessionId });\n return threadId;\n}\n\nexport class FactoryStartCoordinator {\n readonly #controller: FactoryController;\n readonly #storage: WorkItemsStorage;\n readonly #transitionService?: Pick<FactoryTransitionService, 'transition'>;\n readonly #sourceControl?:\n | SourceControlStorageHandle\n | ((\n request: FactoryStartRequest,\n ) => SourceControlStorageHandle | undefined | Promise<SourceControlStorageHandle | undefined>);\n readonly #memorySettings?: MemorySettingsStorage;\n\n constructor(\n controller: FactoryController,\n storage: WorkItemsStorage,\n transitionService?: Pick<FactoryTransitionService, 'transition'>,\n sourceControl?:\n | SourceControlStorageHandle\n | ((\n request: FactoryStartRequest,\n ) => SourceControlStorageHandle | undefined | Promise<SourceControlStorageHandle | undefined>),\n memorySettings?: MemorySettingsStorage,\n ) {\n this.#controller = controller;\n this.#storage = storage;\n this.#transitionService = transitionService;\n this.#sourceControl = sourceControl;\n this.#memorySettings = memorySettings;\n }\n\n async prepare(request: FactoryStartRequest): Promise<FactoryStartPreparedResult> {\n const storage = this.#storage;\n const sourceControl =\n typeof this.#sourceControl === 'function' ? await this.#sourceControl(request) : this.#sourceControl;\n if (!sourceControl) throw new Error('Factory source control storage is unavailable');\n const sourceSession = await resolveSourceSession(sourceControl, request);\n const requestContext = request.requestContext ?? new RequestContext();\n // Factory runs resolve model credentials org > user: the org's shared keys\n // win, with the acting user's personal credentials as a fallback — a board\n // run should never silently prefer whoever kicked it off. The flag rides\n // the stashed user even when a caller-provided context already has one.\n const existingUser = requestContext.get('user');\n if (existingUser && typeof existingUser === 'object') {\n requestContext.set('user', { ...existingUser, orgFirstCredentials: true });\n } else {\n requestContext.set('user', {\n workosId: request.userId,\n organizationId: request.orgId,\n orgFirstCredentials: true,\n });\n }\n // Sessions kicked off against third-party content (a PR under review, or\n // any pull-request-sourced work item) get `untrustedCheckout` so the SDK\n // never ingests the checkout's AGENTS.md/CLAUDE.md into the system prompt\n // or reminders — those files are attacker-writable in a PR branch.\n const untrustedCheckout = request.workItem.input.externalSource?.type === 'pull-request';\n // The trusted ref the SDK may serve project instruction files from on an\n // untrusted checkout (the PR's base branch). Prefer the session record's\n // base branch; fall back to the intake metadata captured from the PR.\n const metadataBaseBranch = request.workItem.input.metadata?.baseBranch;\n const baseRef =\n (sourceSession.baseBranch || undefined) ??\n (typeof metadataBaseBranch === 'string' && metadataBaseBranch ? metadataBaseBranch : undefined);\n const sessionTags = {\n factoryProjectId: request.factoryProjectId,\n projectRepositoryId: sourceSession.projectRepositoryId,\n };\n const session = await this.#controller.createSession({\n id: sourceSession.sessionId,\n ownerId: request.userId,\n resourceId: sourceSession.sessionId,\n threadId: sourceSession.sessionId,\n requestContext,\n tags: sessionTags,\n });\n // Bound-agent authority gates (the transition tool, the factory-phase\n // processor, workspace token selection) resolve the session address from\n // controller state. Seed it server-side — `tags` covers fresh creation,\n // the explicit setState covers get-or-create returning a session another\n // caller created without them — so autonomous runs never depend on a\n // browser connecting to populate the state. `untrustedCheckout` is a\n // boolean so it rides only on state (tags are string-valued).\n await session.state.set({\n ...sessionTags,\n // The authoritative org id for every downstream identity read (the\n // memory seam's organizationId): the session owner is a USER id, not an\n // org, so it must never be improvised from ownerId.\n factoryOrgId: request.orgId,\n ...(untrustedCheckout ? { untrustedCheckout: true, ...(baseRef ? { baseRef } : {}) } : {}),\n });\n // Board runs are org-shared: hydrate with the factory's default model and\n // the project's shared memory settings (falling back to the built-in\n // defaults), never any individual user's stored settings.\n await hydrateFactorySession(session, {\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n defaultModelId: request.defaultModelId,\n memorySettings: this.#memorySettings,\n });\n // The tool is `requireApproval`, and that prompt parks the run whether or not\n // someone pressed Start — a person is reading the plan, not an approval queue.\n // Starting the run was already the say-so; the rules engine still governs.\n await session.permissions.setForTool({ toolName: 'factory_transition_work_item', policy: 'allow' });\n const threadId = await configureThread(session, request);\n const prepared = await storage.prepareRunStart({\n orgId: request.orgId,\n userId: request.userId,\n factoryProjectId: request.factoryProjectId,\n workItem: { id: request.workItem.id, input: request.workItem.input },\n role: request.workItem.role,\n session: { sessionId: sourceSession.sessionId, branch: sourceSession.branch, threadId },\n resourceId: sourceSession.sessionId,\n kickoffKey: request.kickoffKey,\n kickoffMessage: null,\n });\n await session.thread.setSetting({ key: 'factoryWorkItemId', value: prepared.item.id });\n\n let revision = prepared.item.revision;\n const destinationStage = request.destinationStage;\n if (destinationStage && (prepared.item.stages.length !== 1 || prepared.item.stages[0] !== destinationStage)) {\n if (!this.#transitionService) throw new Error('Factory transition service is unavailable.');\n const transition = await this.#transitionService.transition({\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: prepared.item.id,\n board: boardForWorkItem(prepared.item),\n stage: destinationStage,\n expectedRevision: prepared.item.revision,\n actor: { type: 'human', id: request.userId },\n ingress: { type: 'human', identity: `start:${request.kickoffKey}:transition` },\n cause: 'run_start',\n });\n if (transition.status === 'rejected') {\n await storage.markPendingStart(prepared.binding.id, 'failed', transition.reason);\n throw new FactoryStartTransitionError(transition);\n }\n revision = transition.revision;\n }\n\n await storage.markPendingStart(prepared.binding.id, 'sent');\n prepared.pendingStart.status = 'sent';\n\n return {\n workItemId: prepared.item.id,\n bindingId: prepared.binding.id,\n threadId,\n resourceId: sourceSession.sessionId,\n sessionId: sourceSession.sessionId,\n branch: sourceSession.branch,\n revision,\n kickoffStatus: prepared.pendingStart.status,\n replayed: prepared.replayed,\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"start-coordinator.js","names":["#controller","#storage","#transitionService","#sourceControl","#memorySettings","#intake"],"sources":["../../src/rules/start-coordinator.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport { RequestContext } from '@mastra/core/request-context';\n\nimport { boardForWorkItem } from '../boards/index.js';\nimport { hydrateFactorySession } from '../session/factory-session.js';\nimport { resolveWorkItemRepository } from '../session/work-item-repository.js';\nimport type { IntakeStorage } from '../storage/domains/intake/base.js';\nimport type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { SourceControlSession, SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport type { CreateWorkItemInput, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { FactoryTransitionService } from './transition-service.js';\nimport type { FactoryRuleStage, FactoryTransitionResult } from './types.js';\n\nexport interface FactoryStartRequest {\n orgId: string;\n userId: string;\n factoryProjectId: string;\n sessionId: string;\n threadTitle: string;\n kickoffKey: string;\n /** Where the arrival path lands the card; a session opened on an existing card names none. */\n destinationStage?: FactoryRuleStage;\n defaultModelId?: string;\n workItem: {\n id?: string;\n role: string;\n input: CreateWorkItemInput;\n };\n requestContext?: RequestContext;\n}\n\nexport class WorkItemRepositoryError extends Error {\n constructor(\n readonly code: 'repository_mismatch' | 'repository_ambiguous' | 'repository_unlinked',\n message: string,\n readonly candidates: string[] = [],\n ) {\n super(message);\n this.name = 'WorkItemRepositoryError';\n }\n}\n\nexport class FactoryStartTransitionError extends Error {\n readonly result: Extract<FactoryTransitionResult, { status: 'rejected' }>;\n\n constructor(result: Extract<FactoryTransitionResult, { status: 'rejected' }>) {\n super(result.reason);\n this.name = 'FactoryStartTransitionError';\n this.result = result;\n }\n}\n\nexport interface FactoryStartPreparedResult {\n workItemId: string;\n bindingId: string;\n threadId: string;\n resourceId: string;\n sessionId: string;\n branch: string;\n revision: number;\n kickoffStatus: 'pending' | 'leased' | 'retry' | 'sent' | 'failed';\n replayed: boolean;\n}\n\ntype FactoryController = AgentController<MastraCodeState>;\ntype FactorySession = Awaited<ReturnType<FactoryController['createSession']>>;\n\nasync function resolveSourceSession(\n storage: SourceControlStorageHandle,\n request: FactoryStartRequest,\n): Promise<SourceControlSession> {\n const session = await storage.sessions.getBySessionId(request.sessionId);\n if (!session || session.orgId !== request.orgId || session.userId !== request.userId) {\n throw new Error('Factory session not found');\n }\n const projectRepository = await storage.projectRepositories.get({\n orgId: request.orgId,\n id: session.projectRepositoryId,\n });\n if (!projectRepository) throw new Error('Factory session repository not found');\n const connection = await storage.connections.get({ orgId: request.orgId, id: projectRepository.connectionId });\n if (!connection || connection.factoryProjectId !== request.factoryProjectId) {\n throw new Error('Factory session does not belong to this project');\n }\n return session;\n}\n\nasync function configureThread(session: FactorySession, request: FactoryStartRequest): Promise<string> {\n const threadId = session.thread.requireId();\n // Derived from the work item, not a user rename, so leave the title\n // unpinned and let auto-naming keep refining it.\n await session.thread.rename({ title: request.threadTitle, pin: false });\n await session.thread.setSetting({ key: 'factorySessionId', value: request.sessionId });\n return threadId;\n}\n\nexport class FactoryStartCoordinator {\n readonly #controller: FactoryController;\n readonly #storage: WorkItemsStorage;\n readonly #transitionService?: Pick<FactoryTransitionService, 'transition'>;\n readonly #sourceControl?:\n | SourceControlStorageHandle\n | ((\n request: FactoryStartRequest,\n ) => SourceControlStorageHandle | undefined | Promise<SourceControlStorageHandle | undefined>);\n readonly #memorySettings?: MemorySettingsStorage;\n readonly #intake?: IntakeStorage;\n\n constructor(\n controller: FactoryController,\n storage: WorkItemsStorage,\n transitionService?: Pick<FactoryTransitionService, 'transition'>,\n sourceControl?:\n | SourceControlStorageHandle\n | ((\n request: FactoryStartRequest,\n ) => SourceControlStorageHandle | undefined | Promise<SourceControlStorageHandle | undefined>),\n memorySettings?: MemorySettingsStorage,\n intake?: IntakeStorage,\n ) {\n this.#controller = controller;\n this.#storage = storage;\n this.#transitionService = transitionService;\n this.#sourceControl = sourceControl;\n this.#memorySettings = memorySettings;\n this.#intake = intake;\n }\n\n async prepare(request: FactoryStartRequest): Promise<FactoryStartPreparedResult> {\n const storage = this.#storage;\n const sourceControl =\n typeof this.#sourceControl === 'function' ? await this.#sourceControl(request) : this.#sourceControl;\n if (!sourceControl) throw new Error('Factory source control storage is unavailable');\n const sourceSession = await resolveSourceSession(sourceControl, request);\n const storedItem = request.workItem.id\n ? await storage.get({ orgId: request.orgId, id: request.workItem.id })\n : null;\n const intakeConfig = await this.#intake?.getConfig({ orgId: request.orgId, integrationIds: ['linear'] });\n let repository = await resolveWorkItemRepository({\n sourceControl,\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n item: storedItem ?? { metadata: request.workItem.input.metadata ?? null },\n linearRepositoryMap: intakeConfig?.linear?.repositoryByLinearProject,\n });\n if (repository.status === 'ambiguous') {\n // A retry of this role's existing session keeps its already-chosen\n // repository; a newly minted session must not choose for the card.\n if (storedItem?.sessions[request.workItem.role]?.sessionId !== sourceSession.sessionId) {\n throw new WorkItemRepositoryError(\n 'repository_ambiguous',\n `Choose a repository for this work item: ${repository.candidates.join(', ')}.`,\n repository.candidates,\n );\n }\n const link = await sourceControl.projectRepositories.get({\n orgId: request.orgId,\n id: sourceSession.projectRepositoryId,\n });\n const linked = link && (await sourceControl.repositories.get({ orgId: request.orgId, id: link.repositoryId }));\n const currentLinks = link\n ? await sourceControl.projectRepositories.list({ orgId: request.orgId, connectionId: link.connectionId })\n : [];\n if (\n !linked ||\n !repository.candidates.includes(linked.slug) ||\n !currentLinks.some(candidate => candidate.id === sourceSession.projectRepositoryId)\n ) {\n throw new WorkItemRepositoryError(\n 'repository_unlinked',\n 'The existing session repository is not linked to this Factory.',\n );\n }\n repository = { status: 'resolved', slug: linked.slug, projectRepositoryId: sourceSession.projectRepositoryId };\n }\n if (repository.status === 'unlinked') {\n throw new WorkItemRepositoryError('repository_unlinked', repository.hint);\n }\n if (repository.projectRepositoryId !== sourceSession.projectRepositoryId) {\n throw new WorkItemRepositoryError(\n 'repository_mismatch',\n `This run session is bound to a different repository than ${repository.slug}.`,\n );\n }\n const requestContext = request.requestContext ?? new RequestContext();\n // Factory runs resolve model credentials org > user: the org's shared keys\n // win, with the acting user's personal credentials as a fallback — a board\n // run should never silently prefer whoever kicked it off. The flag rides\n // the stashed user even when a caller-provided context already has one.\n const existingUser = requestContext.get('user');\n if (existingUser && typeof existingUser === 'object') {\n requestContext.set('user', { ...existingUser, orgFirstCredentials: true });\n } else {\n requestContext.set('user', {\n workosId: request.userId,\n organizationId: request.orgId,\n orgFirstCredentials: true,\n });\n }\n // Sessions kicked off against third-party content (a PR under review, or\n // any pull-request-sourced work item) get `untrustedCheckout` so the SDK\n // never ingests the checkout's AGENTS.md/CLAUDE.md into the system prompt\n // or reminders — those files are attacker-writable in a PR branch.\n const untrustedCheckout = request.workItem.input.externalSource?.type === 'pull-request';\n // The trusted ref the SDK may serve project instruction files from on an\n // untrusted checkout (the PR's base branch). Prefer the session record's\n // base branch; fall back to the intake metadata captured from the PR.\n const metadataBaseBranch = request.workItem.input.metadata?.baseBranch;\n const baseRef =\n (sourceSession.baseBranch || undefined) ??\n (typeof metadataBaseBranch === 'string' && metadataBaseBranch ? metadataBaseBranch : undefined);\n const sessionTags = {\n factoryProjectId: request.factoryProjectId,\n projectRepositoryId: sourceSession.projectRepositoryId,\n };\n const session = await this.#controller.createSession({\n id: sourceSession.sessionId,\n ownerId: request.userId,\n resourceId: sourceSession.sessionId,\n threadId: sourceSession.sessionId,\n requestContext,\n tags: sessionTags,\n });\n // Bound-agent authority gates (the transition tool, the factory-phase\n // processor, workspace token selection) resolve the session address from\n // controller state. Seed it server-side — `tags` covers fresh creation,\n // the explicit setState covers get-or-create returning a session another\n // caller created without them — so autonomous runs never depend on a\n // browser connecting to populate the state. `untrustedCheckout` is a\n // boolean so it rides only on state (tags are string-valued).\n const targetRepositoryInstruction = `Target repository: ${repository.slug}. Before editing files or creating a pull request, verify this session's checkout matches the target repository.`;\n const existingPluginInstructions = session.state.get()?.pluginInstructions ?? [];\n await session.state.set({\n ...sessionTags,\n // The authoritative org id for every downstream identity read (the\n // memory seam's organizationId): the session owner is a USER id, not an\n // org, so it must never be improvised from ownerId.\n factoryOrgId: request.orgId,\n pluginInstructions: [\n ...existingPluginInstructions.filter(instruction => instruction !== targetRepositoryInstruction),\n targetRepositoryInstruction,\n ],\n ...(untrustedCheckout ? { untrustedCheckout: true, ...(baseRef ? { baseRef } : {}) } : {}),\n });\n // Board runs are org-shared: hydrate with the factory's default model and\n // the project's shared memory settings (falling back to the built-in\n // defaults), never any individual user's stored settings.\n await hydrateFactorySession(session, {\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n defaultModelId: request.defaultModelId,\n memorySettings: this.#memorySettings,\n });\n // The tool is `requireApproval`, and that prompt parks the run whether or not\n // someone pressed Start — a person is reading the plan, not an approval queue.\n // Starting the run was already the say-so; the rules engine still governs.\n await session.permissions.setForTool({ toolName: 'factory_transition_work_item', policy: 'allow' });\n const threadId = await configureThread(session, request);\n const prepared = await storage.prepareRunStart({\n orgId: request.orgId,\n userId: request.userId,\n factoryProjectId: request.factoryProjectId,\n workItem: { id: request.workItem.id, input: request.workItem.input },\n role: request.workItem.role,\n session: { sessionId: sourceSession.sessionId, branch: sourceSession.branch, threadId },\n resourceId: sourceSession.sessionId,\n kickoffKey: request.kickoffKey,\n kickoffMessage: null,\n });\n await session.thread.setSetting({ key: 'factoryWorkItemId', value: prepared.item.id });\n\n let revision = prepared.item.revision;\n const destinationStage = request.destinationStage;\n if (destinationStage && (prepared.item.stages.length !== 1 || prepared.item.stages[0] !== destinationStage)) {\n if (!this.#transitionService) throw new Error('Factory transition service is unavailable.');\n const transition = await this.#transitionService.transition({\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: prepared.item.id,\n board: boardForWorkItem(prepared.item),\n stage: destinationStage,\n expectedRevision: prepared.item.revision,\n actor: { type: 'human', id: request.userId },\n ingress: { type: 'human', identity: `start:${request.kickoffKey}:transition` },\n cause: 'run_start',\n });\n if (transition.status === 'rejected') {\n await storage.markPendingStart(prepared.binding.id, 'failed', transition.reason);\n throw new FactoryStartTransitionError(transition);\n }\n revision = transition.revision;\n }\n\n await storage.markPendingStart(prepared.binding.id, 'sent');\n prepared.pendingStart.status = 'sent';\n\n return {\n workItemId: prepared.item.id,\n bindingId: prepared.binding.id,\n threadId,\n resourceId: sourceSession.sessionId,\n sessionId: sourceSession.sessionId,\n branch: sourceSession.branch,\n revision,\n kickoffStatus: prepared.pendingStart.status,\n replayed: prepared.replayed,\n };\n }\n}\n"],"mappings":"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"}
|