@mastra/factory 0.26.0-alpha.1 → 0.26.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -7
- package/dist/boards/define-board.d.ts +19 -0
- package/dist/boards/define-board.d.ts.map +1 -1
- package/dist/boards/define-board.js +41 -9
- package/dist/boards/define-board.js.map +1 -1
- package/dist/boards/project-boards.d.ts +78 -0
- package/dist/boards/project-boards.d.ts.map +1 -0
- package/dist/boards/project-boards.js +194 -0
- package/dist/boards/project-boards.js.map +1 -0
- package/dist/boards/review.js +2 -2
- package/dist/boards/review.js.map +1 -1
- package/dist/boards/stored-board.d.ts +101 -0
- package/dist/boards/stored-board.d.ts.map +1 -0
- package/dist/boards/stored-board.js +139 -0
- package/dist/boards/stored-board.js.map +1 -0
- package/dist/boards/work.d.ts.map +1 -1
- package/dist/boards/work.js +6 -3
- package/dist/boards/work.js.map +1 -1
- package/dist/capabilities/intake.d.ts +6 -0
- package/dist/capabilities/intake.d.ts.map +1 -1
- package/dist/environment/build.d.ts +1 -1
- package/dist/environment/build.d.ts.map +1 -1
- package/dist/environment/build.js +2 -0
- package/dist/environment/build.js.map +1 -1
- package/dist/factory.d.ts +15 -2
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +51 -12
- package/dist/factory.js.map +1 -1
- package/dist/integrations/github/integration.d.ts +5 -4
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +10 -8
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +2 -2
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +6 -1
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/jira/binding-scope.d.ts +30 -0
- package/dist/integrations/jira/binding-scope.d.ts.map +1 -0
- package/dist/integrations/jira/binding-scope.js +50 -0
- package/dist/integrations/jira/binding-scope.js.map +1 -0
- package/dist/integrations/jira/claim.d.ts +12 -0
- package/dist/integrations/jira/claim.d.ts.map +1 -1
- package/dist/integrations/jira/claim.js +24 -1
- package/dist/integrations/jira/claim.js.map +1 -1
- package/dist/integrations/jira/intake-jql.d.ts +32 -0
- package/dist/integrations/jira/intake-jql.d.ts.map +1 -0
- package/dist/integrations/jira/intake-jql.js +57 -0
- package/dist/integrations/jira/intake-jql.js.map +1 -0
- package/dist/integrations/jira/integration.d.ts +4 -3
- package/dist/integrations/jira/integration.d.ts.map +1 -1
- package/dist/integrations/jira/integration.js +14 -21
- package/dist/integrations/jira/integration.js.map +1 -1
- package/dist/integrations/jira/routes.d.ts +3 -0
- package/dist/integrations/jira/routes.d.ts.map +1 -1
- package/dist/integrations/jira/routes.js +16 -29
- package/dist/integrations/jira/routes.js.map +1 -1
- package/dist/integrations/jira/rules.d.ts.map +1 -1
- package/dist/integrations/jira/rules.js +8 -13
- package/dist/integrations/jira/rules.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts +5 -0
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +7 -0
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/platform/jira/integration.d.ts +7 -1
- package/dist/integrations/platform/jira/integration.d.ts.map +1 -1
- package/dist/integrations/platform/jira/integration.js +26 -24
- package/dist/integrations/platform/jira/integration.js.map +1 -1
- package/dist/integrations/platform/jira/routes.d.ts +3 -0
- package/dist/integrations/platform/jira/routes.d.ts.map +1 -1
- package/dist/integrations/platform/jira/routes.js +17 -28
- package/dist/integrations/platform/jira/routes.js.map +1 -1
- package/dist/routes/attention-providers.d.ts +2 -1
- package/dist/routes/attention-providers.d.ts.map +1 -1
- package/dist/routes/attention-providers.js +3 -2
- package/dist/routes/attention-providers.js.map +1 -1
- package/dist/routes/attention.d.ts +2 -1
- package/dist/routes/attention.d.ts.map +1 -1
- package/dist/routes/attention.js.map +1 -1
- package/dist/routes/boards.d.ts +30 -0
- package/dist/routes/boards.d.ts.map +1 -0
- package/dist/routes/boards.js +272 -0
- package/dist/routes/boards.js.map +1 -0
- package/dist/routes/contracts.d.ts +249 -0
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/contracts.js +107 -35
- package/dist/routes/contracts.js.map +1 -1
- package/dist/routes/intake.d.ts +15 -10
- package/dist/routes/intake.d.ts.map +1 -1
- package/dist/routes/intake.js +109 -22
- package/dist/routes/intake.js.map +1 -1
- package/dist/routes/projects.d.ts +3 -0
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +10 -1
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/surface.d.ts +10 -3
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +13 -1
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/tenant-credentials.d.ts +9 -0
- package/dist/routes/tenant-credentials.d.ts.map +1 -1
- package/dist/routes/tenant-credentials.js +24 -2
- package/dist/routes/tenant-credentials.js.map +1 -1
- package/dist/routes/work-items.d.ts +3 -1
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +64 -42
- 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 +7 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +70 -34
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/processor.d.ts +3 -1
- package/dist/rules/processor.d.ts.map +1 -1
- package/dist/rules/processor.js +11 -6
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/tools.d.ts +2 -1
- package/dist/rules/tools.d.ts.map +1 -1
- package/dist/rules/tools.js +2 -1
- package/dist/rules/tools.js.map +1 -1
- package/dist/rules/transition-service.d.ts +3 -1
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +12 -12
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/sandbox/local-factory-sandbox.d.ts +4 -1
- package/dist/sandbox/local-factory-sandbox.d.ts.map +1 -1
- package/dist/sandbox/local-factory-sandbox.js +1 -1
- package/dist/sandbox/local-factory-sandbox.js.map +1 -1
- package/dist/storage/domains/audit/actions.d.ts +1 -0
- package/dist/storage/domains/audit/actions.d.ts.map +1 -1
- package/dist/storage/domains/audit/actions.js +6 -1
- package/dist/storage/domains/audit/actions.js.map +1 -1
- package/dist/storage/domains/boards/base.d.ts +110 -0
- package/dist/storage/domains/boards/base.d.ts.map +1 -0
- package/dist/storage/domains/boards/base.js +292 -0
- package/dist/storage/domains/boards/base.js.map +1 -0
- package/dist/storage/domains/boards/schemas.d.ts +11 -0
- package/dist/storage/domains/boards/schemas.d.ts.map +1 -0
- package/dist/storage/domains/boards/schemas.js +106 -0
- package/dist/storage/domains/boards/schemas.js.map +1 -0
- package/dist/storage/domains/credentials/base.d.ts +4 -1
- package/dist/storage/domains/credentials/base.d.ts.map +1 -1
- package/dist/storage/domains/credentials/base.js +5 -2
- package/dist/storage/domains/credentials/base.js.map +1 -1
- package/dist/storage/domains/intake/base.d.ts +86 -5
- package/dist/storage/domains/intake/base.d.ts.map +1 -1
- package/dist/storage/domains/intake/base.js +298 -59
- package/dist/storage/domains/intake/base.js.map +1 -1
- package/dist/storage/domains/projects/base.d.ts +2 -1
- package/dist/storage/domains/projects/base.d.ts.map +1 -1
- package/dist/storage/domains/projects/base.js +15 -10
- package/dist/storage/domains/projects/base.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +16 -2
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +40 -11
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/tools/read-tools.d.ts +3 -1
- package/dist/tools/read-tools.d.ts.map +1 -1
- package/dist/tools/read-tools.js +3 -2
- package/dist/tools/read-tools.js.map +1 -1
- package/dist/tools/session-tools.d.ts +3 -1
- package/dist/tools/session-tools.d.ts.map +1 -1
- package/dist/tools/session-tools.js +2 -1
- package/dist/tools/session-tools.js.map +1 -1
- package/dist/tools/sessions-state-processor.d.ts +3 -1
- package/dist/tools/sessions-state-processor.d.ts.map +1 -1
- package/dist/tools/sessions-state-processor.js +2 -1
- package/dist/tools/sessions-state-processor.js.map +1 -1
- package/dist/tools/work-item-tools.d.ts +3 -1
- package/dist/tools/work-item-tools.d.ts.map +1 -1
- package/dist/tools/work-item-tools.js +3 -2
- package/dist/tools/work-item-tools.js.map +1 -1
- package/dist/workspace.d.ts +2 -0
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +14 -5
- package/dist/workspace.js.map +1 -1
- package/factory-skills/factory-plan/SKILL.md +1 -1
- package/factory-skills/factory-triage/SKILL.md +18 -7
- package/package.json +9 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"processor.d.ts","sourceRoot":"","sources":["../../src/rules/processor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAE7E,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAEpF,OAAO,KAAK,EACV,sBAAsB,EACtB,wBAAwB,EACxB,gBAAgB,EAChB,oBAAoB,EACpB,SAAS,EACV,MAAM,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"processor.d.ts","sourceRoot":"","sources":["../../src/rules/processor.ts"],"names":[],"mappings":"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"}
|
package/dist/rules/processor.js
CHANGED
|
@@ -2,6 +2,7 @@ import { workItemSource } from "./types.js";
|
|
|
2
2
|
import { assertFactoryDecisionTarget, normalizeFactoryRuleJsonValue, validateFactoryRuleDecisions } from "./validation.js";
|
|
3
3
|
import { boardForWorkItem, resolveBoardToolRule, workItemPhaseSemantics } from "../boards/semantics.js";
|
|
4
4
|
import "../boards/index.js";
|
|
5
|
+
import { toBoardSource } from "../boards/project-boards.js";
|
|
5
6
|
import { findSessionRunBinding, getFactorySessionCoordinates } from "./binding-context.js";
|
|
6
7
|
import { resolveRequestThinkingLevel } from "@mastra/code-sdk/agents/model";
|
|
7
8
|
// src/rules/processor.ts
|
|
@@ -158,8 +159,10 @@ var FactoryPhaseStateProcessor = class {
|
|
|
158
159
|
stateId = STATE_ID;
|
|
159
160
|
/** Request contexts whose caller row is already resolved for this request. */
|
|
160
161
|
loadedMemorySettings = /* @__PURE__ */ new WeakSet();
|
|
162
|
+
boards;
|
|
161
163
|
constructor(options) {
|
|
162
164
|
this.options = options;
|
|
165
|
+
this.boards = toBoardSource(options.boards);
|
|
163
166
|
}
|
|
164
167
|
/**
|
|
165
168
|
* Memory settings are caller state, not run state. Factory invokes this while
|
|
@@ -247,7 +250,8 @@ var FactoryPhaseStateProcessor = class {
|
|
|
247
250
|
orgId: binding.orgId,
|
|
248
251
|
id: binding.workItemId
|
|
249
252
|
});
|
|
250
|
-
const
|
|
253
|
+
const boards = await this.boards.load(binding);
|
|
254
|
+
const stage = ruleStage(boards, item);
|
|
251
255
|
if (!item || !stage) return;
|
|
252
256
|
const linked = (await this.options.storage.list({
|
|
253
257
|
orgId: binding.orgId,
|
|
@@ -274,7 +278,7 @@ var FactoryPhaseStateProcessor = class {
|
|
|
274
278
|
const cacheKey = await phaseCacheKey(value, linked);
|
|
275
279
|
if (hasBase && (args.tracking?.currentCacheKey ?? args.lastSnapshot?.metadata?.state?.cacheKey) === cacheKey) return;
|
|
276
280
|
const linkedText = linked.length ? `\nLinked items: ${linked.map((candidate) => `${workItemSource(candidate.externalSource)} ${candidate.title}`).join("; ")}` : "";
|
|
277
|
-
const phase =
|
|
281
|
+
const phase = boards.get(board)?.phases[stage];
|
|
278
282
|
const phaseLabel = phase?.name ?? stage;
|
|
279
283
|
const exitText = phase?.kind === "working" && phase.exitTool ? `This phase ends when ${escapeText(phase.exitTool.id)} succeeds. Then move the card with factory_transition_work_item to one of: ${phase.nextPhases.map(escapeText).join(", ")}.\n` : "";
|
|
280
284
|
const runtime = value.modelId && value.thinkingLevel ? {
|
|
@@ -314,7 +318,7 @@ var FactoryPhaseStateProcessor = class {
|
|
|
314
318
|
orgId: binding.orgId,
|
|
315
319
|
id: binding.workItemId
|
|
316
320
|
});
|
|
317
|
-
if (!itemInRuleStage(this.
|
|
321
|
+
if (!itemInRuleStage(await this.boards.load(binding), item)) return;
|
|
318
322
|
const cursor = await this.options.storage.getToolResultCursor(binding.orgId, binding.factoryProjectId, binding.id);
|
|
319
323
|
let page = 0;
|
|
320
324
|
while (true) {
|
|
@@ -348,7 +352,7 @@ var FactoryPhaseStateProcessor = class {
|
|
|
348
352
|
orgId: binding.orgId,
|
|
349
353
|
id: binding.workItemId
|
|
350
354
|
});
|
|
351
|
-
if (!itemInRuleStage(this.
|
|
355
|
+
if (!itemInRuleStage(await this.boards.load(binding), item)) return;
|
|
352
356
|
for (const message of messages) for (const toolResult of completedToolResults(message)) {
|
|
353
357
|
if (toolCallIds && !toolCallIds.has(toolResult.toolCallId)) continue;
|
|
354
358
|
try {
|
|
@@ -368,7 +372,8 @@ var FactoryPhaseStateProcessor = class {
|
|
|
368
372
|
}
|
|
369
373
|
async ingestToolResult(binding, item, toolResult) {
|
|
370
374
|
const board = boardForWorkItem(item);
|
|
371
|
-
const
|
|
375
|
+
const boards = await this.boards.load(binding);
|
|
376
|
+
const rule = resolveBoardToolRule(boards, board, toolResult.toolName);
|
|
372
377
|
if (!rule) return;
|
|
373
378
|
const ingressId = JSON.stringify([
|
|
374
379
|
binding.id,
|
|
@@ -428,7 +433,7 @@ var FactoryPhaseStateProcessor = class {
|
|
|
428
433
|
};
|
|
429
434
|
else if (decision) {
|
|
430
435
|
const validated = validateFactoryRuleDecisions([decision]);
|
|
431
|
-
for (const entry of validated) assertFactoryDecisionTarget(entry,
|
|
436
|
+
for (const entry of validated) assertFactoryDecisionTarget(entry, boards, board);
|
|
432
437
|
decisions = validated;
|
|
433
438
|
}
|
|
434
439
|
} catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"processor.js","names":[],"sources":["../../src/rules/processor.ts"],"sourcesContent":["import { 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 ProcessInputArgs,\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 { findSessionRunBinding, 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(\n requestContext: ComputeStateSignalArgs['requestContext'],\n defaultThinkingLevel: ThinkingLevel | undefined,\n): 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 {\n modelId,\n thinkingLevel: resolveRequestThinkingLevel({ state: context?.state }, undefined, defaultThinkingLevel),\n };\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\nasync function phaseCacheKey(value: ActivePhaseSnapshotValue, linked: WorkItemRow[]): Promise<string> {\n const serialized = JSON.stringify({\n ...value,\n linked: linked.map(item => [item.id, item.revision, item.stages[0]]),\n });\n return Buffer.from(await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(serialized))).toString(\n 'hex',\n );\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\nconst reportedUnavailableMemorySettings = new WeakSet<object>();\n\n/**\n * Marks this run's Factory memory settings as unavailable so memory falls back\n * to auto, and tells the thread once per request that saved choices were not\n * applied. The reserved request-context key keeps clients from spoofing it.\n */\nexport function reportMemorySettingsUnavailable(\n requestContext: NonNullable<ProcessInputArgs['requestContext']>,\n reason: string,\n): void {\n requestContext.set('mastra__factoryMemorySettings', { status: 'unavailable', reason });\n console.warn('[Factory Memory Settings] Failed to load settings for run', { error: reason });\n if (reportedUnavailableMemorySettings.has(requestContext)) return;\n reportedUnavailableMemorySettings.add(requestContext);\n const controller = requestContext.get('controller') as\n | Pick<AgentControllerRequestContext<MastraCodeState>, 'emitEvent'>\n | undefined;\n controller?.emitEvent?.({\n type: 'error',\n error: new Error(\n 'Memory settings could not be loaded for this run, so observational memory is using Auto models and default thresholds.',\n ),\n errorType: 'factory_memory_settings_unavailable',\n retryable: false,\n });\n}\n\nexport class FactoryPhaseStateProcessor implements Processor<'factory-phase'> {\n readonly id = STATE_ID;\n readonly stateId = STATE_ID;\n /** Request contexts whose caller row is already resolved for this request. */\n private readonly loadedMemorySettings = new WeakSet<object>();\n\n constructor(\n private readonly options: {\n configVersion: string;\n storage: WorkItemsStorage;\n boards: BoardRegistry;\n loadMemorySettings?: (args: {\n requestContext: ProcessInputStepArgs['requestContext'];\n binding: FactoryRunBindingRecord | null;\n }) => Promise<void>;\n /** The session's routed thinking default, used when the session has not picked a level. */\n defaultThinkingLevel?: (requestContext: ComputeStateSignalArgs['requestContext']) => Promise<ThinkingLevel>;\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 /**\n * Memory settings are caller state, not run state. Factory invokes this while\n * resolving its configured processor list, before Agent resolves dynamic\n * Memory and enumerates Memory's processors. This guarantees both the Memory\n * options and its model callbacks see the authoritative row for this\n * invocation. Doing this in `processInputStep` would be too late: Memory's\n * input-step hook runs before configured processors.\n */\n async prepareMemorySettings(requestContext: ProcessInputArgs['requestContext']): Promise<void> {\n await this.loadCallerMemorySettings(requestContext, undefined, true);\n }\n\n async processInput(args: ProcessInputArgs): Promise<MessageList> {\n await this.loadCallerMemorySettings(args.requestContext);\n return args.messageList;\n }\n\n async processInputStep(args: ProcessInputStepArgs): Promise<MessageList | undefined> {\n const address = getFactorySessionCoordinates(args.requestContext);\n let binding: FactoryRunBindingRecord | null | undefined;\n try {\n binding = address ? await this.options.storage.findRunBindingBySession(address) : undefined;\n } catch {\n await this.loadCallerMemorySettings(args.requestContext, undefined);\n return;\n }\n // Resumed runs skip the input-processor phase, so re-check here: it is a\n // no-op once `processInput` already placed the row for this request. Passing\n // `undefined` (not `null`) keeps the recovery lookup inside the loader, so a\n // recovered session reaches its project row from this hook too.\n await this.loadCallerMemorySettings(args.requestContext, binding);\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 /**\n * Loads the caller's row once per invocation. The input hook always refreshes\n * it in case the controller reused a request context; later step hooks reuse\n * that value (`undefined` means it has not been loaded yet; `null` means an\n * empty row).\n *\n * The WeakSet covers loaders that do not set the context key themselves, so a\n * request is still loaded once. A failed load records an unavailable sentinel\n * (memory then runs both roles on auto with default thresholds) and is not\n * marked loaded so the next step can retry.\n */\n private async loadCallerMemorySettings(\n requestContext: ProcessInputStepArgs['requestContext'] | ProcessInputArgs['requestContext'],\n binding?: FactoryRunBindingRecord | null,\n force = false,\n ): Promise<void> {\n if (!requestContext || !this.options.loadMemorySettings) return;\n const currentSettings = requestContext.get('mastra__factoryMemorySettings') as\n | { status?: unknown }\n | null\n | undefined;\n if (force) this.loadedMemorySettings.delete(requestContext);\n if (!force && currentSettings !== undefined && currentSettings?.status !== 'unavailable') return;\n if (!force && this.loadedMemorySettings.has(requestContext)) return;\n try {\n let runBinding = binding ?? null;\n if (binding === undefined) {\n const address = getFactorySessionCoordinates(requestContext);\n runBinding = address\n ? await this.options.storage.findRunBindingBySession(address)\n : // Crash-recovered sessions come back with empty state, so the binding\n // table is the last source of the project whose row applies. Without\n // this the run would silently read the caller's personal row.\n await findSessionRunBinding(requestContext, this.options.storage);\n }\n await this.options.loadMemorySettings({ requestContext, binding: runBinding });\n this.loadedMemorySettings.add(requestContext);\n } catch (error) {\n reportMemorySettingsUnavailable(requestContext, error instanceof Error ? error.message : String(error));\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 ? {\n ...baseValue,\n board,\n ...reviewRuntimeFromRequestContext(\n args.requestContext,\n await this.options.defaultThinkingLevel?.(args.requestContext),\n ),\n }\n : { ...baseValue, board };\n const cacheKey = await 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 phase = this.options.boards.get(board)?.phases[stage];\n const phaseLabel = phase?.name ?? stage;\n const exitText =\n phase?.kind === 'working' && phase.exitTool\n ? `This phase ends when ${escapeText(phase.exitTool.id)} succeeds. Then move the card with factory_transition_work_item to one of: ${phase.nextPhases.map(escapeText).join(', ')}.\\n`\n : '';\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 exitText +\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 // The bound agent's own tool call carries the same consent as its governed moves.\n consentedBy: `agent:${binding.id}`,\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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"}
|
|
1
|
+
{"version":3,"file":"processor.js","names":[],"sources":["../../src/rules/processor.ts"],"sourcesContent":["import { 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 ProcessInputArgs,\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 { toBoardSource } from '../boards/project-boards.js';\nimport type { ProjectBoardSource } from '../boards/project-boards.js';\nimport type { FactoryRunBindingRecord, WorkItemsStorage, WorkItemRow } from '../storage/domains/work-items/base.js';\nimport { findSessionRunBinding, 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(\n requestContext: ComputeStateSignalArgs['requestContext'],\n defaultThinkingLevel: ThinkingLevel | undefined,\n): 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 {\n modelId,\n thinkingLevel: resolveRequestThinkingLevel({ state: context?.state }, undefined, defaultThinkingLevel),\n };\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\nasync function phaseCacheKey(value: ActivePhaseSnapshotValue, linked: WorkItemRow[]): Promise<string> {\n const serialized = JSON.stringify({\n ...value,\n linked: linked.map(item => [item.id, item.revision, item.stages[0]]),\n });\n return Buffer.from(await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(serialized))).toString(\n 'hex',\n );\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\nconst reportedUnavailableMemorySettings = new WeakSet<object>();\n\n/**\n * Marks this run's Factory memory settings as unavailable so memory falls back\n * to auto, and tells the thread once per request that saved choices were not\n * applied. The reserved request-context key keeps clients from spoofing it.\n */\nexport function reportMemorySettingsUnavailable(\n requestContext: NonNullable<ProcessInputArgs['requestContext']>,\n reason: string,\n): void {\n requestContext.set('mastra__factoryMemorySettings', { status: 'unavailable', reason });\n console.warn('[Factory Memory Settings] Failed to load settings for run', { error: reason });\n if (reportedUnavailableMemorySettings.has(requestContext)) return;\n reportedUnavailableMemorySettings.add(requestContext);\n const controller = requestContext.get('controller') as\n | Pick<AgentControllerRequestContext<MastraCodeState>, 'emitEvent'>\n | undefined;\n controller?.emitEvent?.({\n type: 'error',\n error: new Error(\n 'Memory settings could not be loaded for this run, so observational memory is using Auto models and default thresholds.',\n ),\n errorType: 'factory_memory_settings_unavailable',\n retryable: false,\n });\n}\n\nexport class FactoryPhaseStateProcessor implements Processor<'factory-phase'> {\n readonly id = STATE_ID;\n readonly stateId = STATE_ID;\n /** Request contexts whose caller row is already resolved for this request. */\n private readonly loadedMemorySettings = new WeakSet<object>();\n private readonly boards: ProjectBoardSource;\n\n constructor(\n private readonly options: {\n configVersion: string;\n storage: WorkItemsStorage;\n boards: BoardRegistry | ProjectBoardSource;\n loadMemorySettings?: (args: {\n requestContext: ProcessInputStepArgs['requestContext'];\n binding: FactoryRunBindingRecord | null;\n }) => Promise<void>;\n /** The session's routed thinking default, used when the session has not picked a level. */\n defaultThinkingLevel?: (requestContext: ComputeStateSignalArgs['requestContext']) => Promise<ThinkingLevel>;\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 this.boards = toBoardSource(options.boards);\n }\n\n /**\n * Memory settings are caller state, not run state. Factory invokes this while\n * resolving its configured processor list, before Agent resolves dynamic\n * Memory and enumerates Memory's processors. This guarantees both the Memory\n * options and its model callbacks see the authoritative row for this\n * invocation. Doing this in `processInputStep` would be too late: Memory's\n * input-step hook runs before configured processors.\n */\n async prepareMemorySettings(requestContext: ProcessInputArgs['requestContext']): Promise<void> {\n await this.loadCallerMemorySettings(requestContext, undefined, true);\n }\n\n async processInput(args: ProcessInputArgs): Promise<MessageList> {\n await this.loadCallerMemorySettings(args.requestContext);\n return args.messageList;\n }\n\n async processInputStep(args: ProcessInputStepArgs): Promise<MessageList | undefined> {\n const address = getFactorySessionCoordinates(args.requestContext);\n let binding: FactoryRunBindingRecord | null | undefined;\n try {\n binding = address ? await this.options.storage.findRunBindingBySession(address) : undefined;\n } catch {\n await this.loadCallerMemorySettings(args.requestContext, undefined);\n return;\n }\n // Resumed runs skip the input-processor phase, so re-check here: it is a\n // no-op once `processInput` already placed the row for this request. Passing\n // `undefined` (not `null`) keeps the recovery lookup inside the loader, so a\n // recovered session reaches its project row from this hook too.\n await this.loadCallerMemorySettings(args.requestContext, binding);\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 /**\n * Loads the caller's row once per invocation. The input hook always refreshes\n * it in case the controller reused a request context; later step hooks reuse\n * that value (`undefined` means it has not been loaded yet; `null` means an\n * empty row).\n *\n * The WeakSet covers loaders that do not set the context key themselves, so a\n * request is still loaded once. A failed load records an unavailable sentinel\n * (memory then runs both roles on auto with default thresholds) and is not\n * marked loaded so the next step can retry.\n */\n private async loadCallerMemorySettings(\n requestContext: ProcessInputStepArgs['requestContext'] | ProcessInputArgs['requestContext'],\n binding?: FactoryRunBindingRecord | null,\n force = false,\n ): Promise<void> {\n if (!requestContext || !this.options.loadMemorySettings) return;\n const currentSettings = requestContext.get('mastra__factoryMemorySettings') as\n | { status?: unknown }\n | null\n | undefined;\n if (force) this.loadedMemorySettings.delete(requestContext);\n if (!force && currentSettings !== undefined && currentSettings?.status !== 'unavailable') return;\n if (!force && this.loadedMemorySettings.has(requestContext)) return;\n try {\n let runBinding = binding ?? null;\n if (binding === undefined) {\n const address = getFactorySessionCoordinates(requestContext);\n runBinding = address\n ? await this.options.storage.findRunBindingBySession(address)\n : // Crash-recovered sessions come back with empty state, so the binding\n // table is the last source of the project whose row applies. Without\n // this the run would silently read the caller's personal row.\n await findSessionRunBinding(requestContext, this.options.storage);\n }\n await this.options.loadMemorySettings({ requestContext, binding: runBinding });\n this.loadedMemorySettings.add(requestContext);\n } catch (error) {\n reportMemorySettingsUnavailable(requestContext, error instanceof Error ? error.message : String(error));\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 boards = await this.boards.load(binding);\n const stage = ruleStage(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 ? {\n ...baseValue,\n board,\n ...reviewRuntimeFromRequestContext(\n args.requestContext,\n await this.options.defaultThinkingLevel?.(args.requestContext),\n ),\n }\n : { ...baseValue, board };\n const cacheKey = await 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 phase = boards.get(board)?.phases[stage];\n const phaseLabel = phase?.name ?? stage;\n const exitText =\n phase?.kind === 'working' && phase.exitTool\n ? `This phase ends when ${escapeText(phase.exitTool.id)} succeeds. Then move the card with factory_transition_work_item to one of: ${phase.nextPhases.map(escapeText).join(', ')}.\\n`\n : '';\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 exitText +\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(await this.boards.load(binding), 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(await this.boards.load(binding), 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 boards = await this.boards.load(binding);\n const rule = resolveBoardToolRule(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, 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 // The bound agent's own tool call carries the same consent as its governed moves.\n consentedBy: `agent:${binding.id}`,\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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"}
|
package/dist/rules/tools.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { RequestContext } from '@mastra/core/request-context';
|
|
2
2
|
import type { BoardRegistry } from '../boards/index.js';
|
|
3
|
+
import type { ProjectBoardSource } from '../boards/project-boards.js';
|
|
3
4
|
import type { IntegrationTools } from '../integrations/base.js';
|
|
4
5
|
import type { WorkItemsStorage } from '../storage/domains/work-items/base.js';
|
|
5
6
|
import type { FactorySessionSourceLookup } from './binding-context.js';
|
|
@@ -9,7 +10,7 @@ export declare function createFactoryTransitionTools(options: {
|
|
|
9
10
|
storage: WorkItemsStorage;
|
|
10
11
|
transitionService: Pick<FactoryTransitionService, 'transition'>;
|
|
11
12
|
sessions?: FactorySessionSourceLookup;
|
|
12
|
-
boards?: BoardRegistry;
|
|
13
|
+
boards?: BoardRegistry | ProjectBoardSource;
|
|
13
14
|
}): Promise<IntegrationTools>;
|
|
14
15
|
export declare const REVIEW_VERDICTS: readonly ['approve', 'request changes'];
|
|
15
16
|
//# sourceMappingURL=tools.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/rules/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAKnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,OAAO,KAAK,EAAe,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC3F,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAEvE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AA8BxE,wBAAsB,4BAA4B,CAAC,OAAO,EAAE;IAC1D,cAAc,EAAE,cAAc,CAAC;IAC/B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,iBAAiB,EAAE,IAAI,CAAC,wBAAwB,EAAE,YAAY,CAAC,CAAC;IAChE,QAAQ,CAAC,EAAE,0BAA0B,CAAC;IACtC,MAAM,CAAC,EAAE,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/rules/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAKnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAEtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,OAAO,KAAK,EAAe,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC3F,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAEvE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AA8BxE,wBAAsB,4BAA4B,CAAC,OAAO,EAAE;IAC1D,cAAc,EAAE,cAAc,CAAC;IAC/B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,iBAAiB,EAAE,IAAI,CAAC,wBAAwB,EAAE,YAAY,CAAC,CAAC;IAChE,QAAQ,CAAC,EAAE,0BAA0B,CAAC;IACtC,MAAM,CAAC,EAAE,aAAa,GAAG,kBAAkB,CAAC;CAC7C,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAwF5B;AAED,eAAO,MAAM,eAAe,YAAI,SAAS,EAAE,iBAAiB,CAAU,CAAC"}
|
package/dist/rules/tools.js
CHANGED
|
@@ -3,6 +3,7 @@ import { BOARD_IDENTIFIER_RE } from "./validation.js";
|
|
|
3
3
|
import { boardForWorkItem, workItemPhaseSemantics } from "../boards/semantics.js";
|
|
4
4
|
import "../boards/work.js";
|
|
5
5
|
import "../boards/index.js";
|
|
6
|
+
import { toBoardSource } from "../boards/project-boards.js";
|
|
6
7
|
import { WorkItemUpdateConflictError } from "../storage/domains/work-items/base.js";
|
|
7
8
|
import { resolveFactorySessionAddress } from "./binding-context.js";
|
|
8
9
|
import { z } from "zod";
|
|
@@ -31,7 +32,7 @@ async function createFactoryTransitionTools(options) {
|
|
|
31
32
|
orgId: retired.orgId,
|
|
32
33
|
id: retired.workItemId
|
|
33
34
|
});
|
|
34
|
-
if (item && workItemPhaseSemantics(options.boards, item)?.kind === "terminal") return { factory_transition_work_item: createRetiredSessionTool(retired.workItemId, item.stages[0]) };
|
|
35
|
+
if (item && workItemPhaseSemantics(await toBoardSource(options.boards).load(retired), item)?.kind === "terminal") return { factory_transition_work_item: createRetiredSessionTool(retired.workItemId, item.stages[0]) };
|
|
35
36
|
}
|
|
36
37
|
return {};
|
|
37
38
|
}
|
package/dist/rules/tools.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.js","names":[],"sources":["../../src/rules/tools.ts"],"sourcesContent":["import type { RequestContext } from '@mastra/core/request-context';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport { boardForWorkItem, workItemPhaseSemantics } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport { SELF_REVIEW_ROUND_LIMIT } from '../boards/work.js';\nimport type { IntegrationTools } from '../integrations/base.js';\nimport { WorkItemUpdateConflictError } from '../storage/domains/work-items/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';\nimport type { FactoryTransitionService } from './transition-service.js';\nimport { FACTORY_TRIAGE_TYPES } from './types.js';\nimport { BOARD_IDENTIFIER_RE, MAX_BOARD_IDENTIFIER_LENGTH } from './validation.js';\n\nconst MAX_RATIONALE_LENGTH = 1_000;\n\nconst transitionInputSchema = z\n .object({\n stage: z.string().max(MAX_BOARD_IDENTIFIER_LENGTH).regex(BOARD_IDENTIFIER_RE),\n expectedRevision: z.number().int().positive(),\n // Providers strip maxLength from the JSON schema and models can't count characters, so a\n // hard cap invites overshoot-retry loops at the end of every run. Accept and clamp instead.\n rationale: z\n .string()\n .trim()\n .min(1)\n .transform(value =>\n value.length <= MAX_RATIONALE_LENGTH ? value : `${value.slice(0, MAX_RATIONALE_LENGTH - 1)}…`,\n ),\n // Sessions are shared across role rotations, so a non-triage agent sees the triage\n // agent's earlier call (with triageType) in its history and copies the shape. Accept\n // the key here and drop it in execute rather than fail the whole call on it.\n triageType: z.enum(FACTORY_TRIAGE_TYPES).optional(),\n })\n .strict();\n\nconst triageTransitionInputSchema = transitionInputSchema.extend({\n triageType: z.enum(FACTORY_TRIAGE_TYPES),\n});\n\nexport async function createFactoryTransitionTools(options: {\n requestContext: RequestContext;\n storage: WorkItemsStorage;\n transitionService: Pick<FactoryTransitionService, 'transition'>;\n sessions?: FactorySessionSourceLookup;\n boards?: BoardRegistry;\n}): Promise<IntegrationTools> {\n const resolution = await resolveFactorySessionAddress({\n requestContext: options.requestContext,\n storage: options.storage,\n sessions: options.sessions,\n });\n if (!resolution) return {};\n const availableBinding = resolution.binding ?? (await options.storage.findActiveRunBinding(resolution.address));\n if (!availableBinding) {\n // No live binding for this session. A resumed session whose binding was\n // revoked when its work item reached a terminal stage was retired out from\n // under the model: rather than silently drop the tool — which leaves the\n // resumed run to rationalize a false blocked state — surface a tool that\n // fails with the concrete reason. Threads that were never bound (or whose\n // binding was rotated to a peer role, leaving the item still in flight) get\n // no tool as before.\n const retired = await options.storage.findRunBindingBySession(resolution.address);\n if (retired && retired.status === 'revoked' && options.boards) {\n const item = await options.storage.get({ orgId: retired.orgId, id: retired.workItemId });\n if (item && workItemPhaseSemantics(options.boards, item)?.kind === 'terminal') {\n return { factory_transition_work_item: createRetiredSessionTool(retired.workItemId, item.stages[0]) };\n }\n }\n return {};\n }\n let isTriage = false;\n if (availableBinding.role === 'triage') {\n const item = await options.storage.get({ orgId: availableBinding.orgId, id: availableBinding.workItemId });\n if (!item) return {};\n isTriage = boardForWorkItem(item) === 'work';\n }\n\n return {\n ...(availableBinding.role === 'review' ? createReviewVerdictTool(options, availableBinding.workItemId) : {}),\n factory_transition_work_item: createTool({\n id: 'factory_transition_work_item',\n description: isTriage\n ? 'Report the triage classification and request a governed stage transition for the Factory work item exactly bound to this thread. Only bugs may request Planning autonomously; closure outcomes may request a terminal stage. Feature requests and other non-bug classifications that remain open must stay in their current Intake or Triage stage for maintainer approval.'\n : 'Request a governed stage transition for the Factory work item exactly bound to this thread. Use the current revision from the factory-phase signal and explain why the transition is appropriate.',\n inputSchema: isTriage ? triageTransitionInputSchema : transitionInputSchema,\n requireApproval: true,\n execute: async ({ stage, expectedRevision, rationale, triageType: requestedTriageType }, execution) => {\n const currentResolution = await resolveFactorySessionAddress({\n requestContext: execution.requestContext,\n storage: options.storage,\n sessions: options.sessions,\n });\n const currentAddress = currentResolution?.address ?? null;\n const toolCallId = execution.agent?.toolCallId;\n if (!currentAddress || !toolCallId) {\n throw new Error('Factory transitions require an authenticated bound agent tool call.');\n }\n const binding = await options.storage.findActiveRunBinding(currentAddress);\n // Authority is the work item this session is bound to, not the individual\n // binding row. Handing the next role its turn in an existing session\n // rotates the binding, and tools built for the previous role stay live\n // across that rotation; keying on row identity would strand the run that\n // the rotation exists to start. Re-pointing a session at a different item\n // is the hijack this guards against.\n if (!binding || binding.workItemId !== availableBinding.workItemId) {\n throw new Error('Factory agent binding is unavailable, revoked, or no longer matches this session.');\n }\n const item = await options.storage.get({ orgId: binding.orgId, id: binding.workItemId });\n if (!item) throw new Error('Bound Factory work item not found.');\n const board = boardForWorkItem(item);\n // Only a Work triage binding may classify; other roles can echo the key from history.\n const triageType = board === 'work' && binding.role === 'triage' ? requestedTriageType : undefined;\n\n const result = await options.transitionService.transition({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: binding.workItemId,\n board,\n stage,\n expectedRevision,\n actor: { type: 'agent', bindingId: binding.id, role: binding.role },\n ingress: { type: 'agent', identity: `${binding.id}:${toolCallId}` },\n cause: rationale,\n ...(triageType ? { triageType } : {}),\n });\n\n return result;\n },\n }),\n };\n}\n\nexport const REVIEW_VERDICTS = ['approve', 'request changes'] as const;\n\nconst MAX_SELF_REVIEW_FINDINGS_LENGTH = 20_000;\n\nconst reviewVerdictInputSchema = z.object({\n verdict: z.enum(REVIEW_VERDICTS),\n reviewedHeadSha: z\n .string()\n .regex(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i, 'Use the full commit SHA of the reviewed head.'),\n findings: z\n .string()\n .trim()\n .min(1)\n .transform(value =>\n value.length <= MAX_SELF_REVIEW_FINDINGS_LENGTH\n ? value\n : `${value.slice(0, MAX_SELF_REVIEW_FINDINGS_LENGTH - 1)}…`,\n )\n .optional()\n .describe(\n 'Self-review only: the findings and test report, in Markdown. On request changes the builder receives them; on approve they go in the change request body.',\n ),\n});\n\n/**\n * A review pass ends by recording its verdict on the card, which stays in\n * Reviewing: Done is reserved for the merge, and a verdict is not a lane. The\n * next push re-reviews from the recorded verdict.\n */\nfunction createReviewVerdictTool(\n options: {\n requestContext: RequestContext;\n storage: WorkItemsStorage;\n transitionService: Pick<FactoryTransitionService, 'transition'>;\n sessions?: FactorySessionSourceLookup;\n },\n boundWorkItemId: string,\n): IntegrationTools {\n return {\n factory_record_review_verdict: createTool({\n id: 'factory_record_review_verdict',\n description:\n 'Record the review verdict and the head SHA it covers on the card bound to this thread, ending the review pass. On a Review card it records the published PR verdict and the card stays in Reviewing. On a Work card in Review with no change request open yet (self-review), approve keeps the card in Review and wakes the builder to open the change request; request changes sends it back to Building with your findings.',\n inputSchema: reviewVerdictInputSchema,\n requireApproval: false,\n execute: async ({ verdict, reviewedHeadSha, findings }, execution) => {\n const resolution = await resolveFactorySessionAddress({\n requestContext: execution.requestContext,\n storage: options.storage,\n sessions: options.sessions,\n });\n const binding = resolution ? await options.storage.findActiveRunBinding(resolution.address) : null;\n if (!binding || binding.workItemId !== boundWorkItemId || binding.role !== 'review') {\n throw new Error('Factory review binding is unavailable, revoked, or no longer matches this session.');\n }\n const item = await options.storage.get({ orgId: binding.orgId, id: binding.workItemId });\n if (!item) throw new Error('Bound Factory work item not found.');\n if (\n boardForWorkItem(item) === 'work' &&\n item.stages[0] === 'review' &&\n typeof item.metadata?.openPullRequestNumber !== 'number'\n ) {\n return recordSelfReviewVerdict({\n storage: options.storage,\n transitionService: options.transitionService,\n binding,\n item,\n verdict,\n reviewedHeadSha: reviewedHeadSha.toLowerCase(),\n findings,\n toolCallId: execution.agent?.toolCallId,\n });\n }\n if (boardForWorkItem(item) !== 'review' || item.stages[0] !== 'review') {\n throw new Error(`Only a card in Reviewing records a verdict; this one is in ${item.stages.join(', ')}.`);\n }\n const reviewedAt = new Date().toISOString();\n // Revision-checked, so a merge that moves the card between the stage\n // check and this write fails the call instead of stamping a Done card.\n try {\n await options.storage.update({\n orgId: binding.orgId,\n id: item.id,\n userId: `agent:${binding.id}`,\n expectedRevision: item.revision,\n patch: {\n metadata: {\n ...(item.metadata ?? {}),\n reviewVerdict: verdict,\n reviewedHeadSha: reviewedHeadSha.toLowerCase(),\n reviewedAt,\n },\n },\n });\n } catch (error) {\n if (!(error instanceof WorkItemUpdateConflictError)) throw error;\n throw new Error(\n 'The Review card changed while recording the verdict; call again to record it on the current card.',\n );\n }\n // Mirror the verdict onto the Work item that authored the PR, so its\n // builder cannot close the work while changes are still requested.\n const parent = item.parentWorkItemId\n ? await options.storage.get({ orgId: binding.orgId, id: item.parentWorkItemId })\n : null;\n if (parent && boardForWorkItem(parent) === 'work') {\n await options.storage.update({\n orgId: binding.orgId,\n id: parent.id,\n userId: `agent:${binding.id}`,\n patch: {\n metadata: { reviewVerdict: verdict, reviewedHeadSha: reviewedHeadSha.toLowerCase(), reviewedAt },\n },\n });\n }\n return { status: 'recorded', verdict, reviewedHeadSha, reviewedAt };\n },\n }),\n };\n}\n\n/**\n * Self-review ends the same way a PR review does, but on the Work card before\n * any change request exists, and it moves the card itself: the verdict is the\n * only thing that decides where the work goes next.\n */\nasync function recordSelfReviewVerdict(input: {\n storage: WorkItemsStorage;\n transitionService: Pick<FactoryTransitionService, 'transition'>;\n binding: { id: string; orgId: string; factoryProjectId: string; workItemId: string; role: string };\n item: WorkItemRow;\n verdict: (typeof REVIEW_VERDICTS)[number];\n reviewedHeadSha: string;\n findings: string | undefined;\n toolCallId: string | undefined;\n}) {\n const { storage, binding, item, verdict, reviewedHeadSha, findings } = input;\n if (!input.toolCallId) throw new Error('Self-review verdicts require an authenticated bound agent tool call.');\n const reviewedAt = new Date().toISOString();\n const previousRounds = typeof item.metadata?.selfReviewRounds === 'number' ? item.metadata.selfReviewRounds : 0;\n const rounds = verdict === 'request changes' ? previousRounds + 1 : previousRounds;\n // Every SELF_REVIEW_ROUND_LIMIT rounds the card rests in Intake for a person instead of looping;\n // moving it back to Building grants another full set of rounds.\n const parked = verdict === 'request changes' && rounds % SELF_REVIEW_ROUND_LIMIT === 0;\n let updated: Awaited<ReturnType<WorkItemsStorage['update']>>;\n try {\n updated = await storage.update({\n orgId: binding.orgId,\n id: item.id,\n userId: `agent:${binding.id}`,\n expectedRevision: item.revision,\n patch: {\n metadata: {\n selfReviewVerdict: verdict,\n selfReviewedHeadSha: reviewedHeadSha,\n selfReviewedAt: reviewedAt,\n selfReviewFindings: findings ?? null,\n selfReviewRounds: rounds,\n selfReviewNeedsHuman: parked,\n },\n },\n });\n } catch (error) {\n if (!(error instanceof WorkItemUpdateConflictError)) throw error;\n throw new Error('The Work card changed while recording the verdict; call again to record it on the current card.');\n }\n if (!updated) throw new Error('Bound Factory work item not found.');\n if (verdict === 'approve')\n return { status: 'recorded', verdict, reviewedHeadSha, reviewedAt, selfReviewRounds: rounds };\n const transition = await input.transitionService.transition({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: binding.workItemId,\n board: 'work',\n stage: parked ? 'intake' : 'execute',\n expectedRevision: updated.item.revision,\n actor: { type: 'agent', bindingId: binding.id, role: binding.role },\n ingress: { type: 'agent', identity: `${binding.id}:${input.toolCallId}` },\n cause: parked\n ? `Self-review requested changes ${rounds} times; parked for a person to decide how to proceed.`\n : `Self-review requested changes on ${reviewedHeadSha} (round ${rounds}).`,\n });\n return { status: 'recorded', verdict, reviewedHeadSha, reviewedAt, selfReviewRounds: rounds, transition };\n}\n\n/**\n * A retired session's transition tool: it exists only to fail loudly. The run's\n * binding was revoked when its work item settled at a terminal stage, so there\n * is nothing to transition. Registering it (instead of dropping the tool) stops\n * a resumed model from inventing a false \"blocked\" state when the affordance it\n * expects has silently vanished.\n */\nfunction createRetiredSessionTool(workItemId: string, stage: string | undefined): IntegrationTools[string] {\n return createTool({\n id: 'factory_transition_work_item',\n description:\n 'This Factory session has been retired: its work item already reached a terminal stage and the run binding was revoked. No further transitions can be requested from this session.',\n inputSchema: transitionInputSchema,\n requireApproval: false,\n execute: async () => {\n throw new Error(\n `This Factory session has been retired: work item ${workItemId} reached a terminal stage${\n stage ? ` (${stage})` : ''\n } and its run binding was revoked. No transition can be requested from this session.`,\n );\n },\n });\n}\n"],"mappings":";;;;;;;;;;AAgBA,MAAM,uBAAuB;AAE7B,MAAM,wBAAwB,EAC3B,OAAO;CACN,OAAO,EAAE,OAAO,CAAC,CAAC,IAAA,GAA+B,CAAC,CAAC,MAAM,mBAAmB;CAC5E,kBAAkB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS;CAG5C,WAAW,EACR,OAAO,CAAC,CACR,KAAK,CAAC,CACN,IAAI,CAAC,CAAC,CACN,WAAU,UACT,MAAM,UAAU,uBAAuB,QAAQ,GAAG,MAAM,MAAM,GAAG,GAAwB,EAAE,EAC7F;CAIF,YAAY,EAAE,KAAK,oBAAoB,CAAC,CAAC,SAAS;AACpD,CAAC,CAAC,CACD,OAAO;AAEV,MAAM,8BAA8B,sBAAsB,OAAO,EAC/D,YAAY,EAAE,KAAK,oBAAoB,EACzC,CAAC;AAED,eAAsB,6BAA6B,SAMrB;CAC5B,MAAM,aAAa,MAAM,6BAA6B;EACpD,gBAAgB,QAAQ;EACxB,SAAS,QAAQ;EACjB,UAAU,QAAQ;CACpB,CAAC;CACD,IAAI,CAAC,YAAY,OAAO,CAAC;CACzB,MAAM,mBAAmB,WAAW,WAAY,MAAM,QAAQ,QAAQ,qBAAqB,WAAW,OAAO;CAC7G,IAAI,CAAC,kBAAkB;EAQrB,MAAM,UAAU,MAAM,QAAQ,QAAQ,wBAAwB,WAAW,OAAO;EAChF,IAAI,WAAW,QAAQ,WAAW,aAAa,QAAQ,QAAQ;GAC7D,MAAM,OAAO,MAAM,QAAQ,QAAQ,IAAI;IAAE,OAAO,QAAQ;IAAO,IAAI,QAAQ;GAAW,CAAC;GACvF,IAAI,QAAQ,uBAAuB,QAAQ,QAAQ,IAAI,CAAC,EAAE,SAAS,YACjE,OAAO,EAAE,8BAA8B,yBAAyB,QAAQ,YAAY,KAAK,OAAO,EAAE,EAAE;EAExG;EACA,OAAO,CAAC;CACV;CACA,IAAI,WAAW;CACf,IAAI,iBAAiB,SAAS,UAAU;EACtC,MAAM,OAAO,MAAM,QAAQ,QAAQ,IAAI;GAAE,OAAO,iBAAiB;GAAO,IAAI,iBAAiB;EAAW,CAAC;EACzG,IAAI,CAAC,MAAM,OAAO,CAAC;EACnB,WAAW,iBAAiB,IAAI,MAAM;CACxC;CAEA,OAAO;EACL,GAAI,iBAAiB,SAAS,WAAW,wBAAwB,SAAS,iBAAiB,UAAU,IAAI,CAAC;EAC1G,8BAA8B,WAAW;GACvC,IAAI;GACJ,aAAa,WACT,gXACA;GACJ,aAAa,WAAW,8BAA8B;GACtD,iBAAiB;GACjB,SAAS,OAAO,EAAE,OAAO,kBAAkB,WAAW,YAAY,uBAAuB,cAAc;IAMrG,MAAM,kBAAiB,MALS,6BAA6B;KAC3D,gBAAgB,UAAU;KAC1B,SAAS,QAAQ;KACjB,UAAU,QAAQ;IACpB,CAAC,EAAA,EACyC,WAAW;IACrD,MAAM,aAAa,UAAU,OAAO;IACpC,IAAI,CAAC,kBAAkB,CAAC,YACtB,MAAM,IAAI,MAAM,qEAAqE;IAEvF,MAAM,UAAU,MAAM,QAAQ,QAAQ,qBAAqB,cAAc;IAOzE,IAAI,CAAC,WAAW,QAAQ,eAAe,iBAAiB,YACtD,MAAM,IAAI,MAAM,mFAAmF;IAErG,MAAM,OAAO,MAAM,QAAQ,QAAQ,IAAI;KAAE,OAAO,QAAQ;KAAO,IAAI,QAAQ;IAAW,CAAC;IACvF,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,oCAAoC;IAC/D,MAAM,QAAQ,iBAAiB,IAAI;IAEnC,MAAM,aAAa,UAAU,UAAU,QAAQ,SAAS,WAAW,sBAAsB,KAAA;IAezF,OAAO,MAbc,QAAQ,kBAAkB,WAAW;KACxD,OAAO,QAAQ;KACf,kBAAkB,QAAQ;KAC1B,YAAY,QAAQ;KACpB;KACA;KACA;KACA,OAAO;MAAE,MAAM;MAAS,WAAW,QAAQ;MAAI,MAAM,QAAQ;KAAK;KAClE,SAAS;MAAE,MAAM;MAAS,UAAU,GAAG,QAAQ,GAAG,GAAG;KAAa;KAClE,OAAO;KACP,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;IACrC,CAAC;GAGH;EACF,CAAC;CACH;AACF;AAEA,MAAa,kBAAkB,CAAC,WAAW,iBAAiB;AAE5D,MAAM,kCAAkC;AAExC,MAAM,2BAA2B,EAAE,OAAO;CACxC,SAAS,EAAE,KAAK,eAAe;CAC/B,iBAAiB,EACd,OAAO,CAAC,CACR,MAAM,oCAAoC,+CAA+C;CAC5F,UAAU,EACP,OAAO,CAAC,CACR,KAAK,CAAC,CACN,IAAI,CAAC,CAAC,CACN,WAAU,UACT,MAAM,UAAU,kCACZ,QACA,GAAG,MAAM,MAAM,GAAG,KAAmC,EAAE,EAC7D,CAAC,CACA,SAAS,CAAC,CACV,SACC,2JACF;AACJ,CAAC;;;;;;AAOD,SAAS,wBACP,SAMA,iBACkB;CAClB,OAAO,EACL,+BAA+B,WAAW;EACxC,IAAI;EACJ,aACE;EACF,aAAa;EACb,iBAAiB;EACjB,SAAS,OAAO,EAAE,SAAS,iBAAiB,YAAY,cAAc;GACpE,MAAM,aAAa,MAAM,6BAA6B;IACpD,gBAAgB,UAAU;IAC1B,SAAS,QAAQ;IACjB,UAAU,QAAQ;GACpB,CAAC;GACD,MAAM,UAAU,aAAa,MAAM,QAAQ,QAAQ,qBAAqB,WAAW,OAAO,IAAI;GAC9F,IAAI,CAAC,WAAW,QAAQ,eAAe,mBAAmB,QAAQ,SAAS,UACzE,MAAM,IAAI,MAAM,oFAAoF;GAEtG,MAAM,OAAO,MAAM,QAAQ,QAAQ,IAAI;IAAE,OAAO,QAAQ;IAAO,IAAI,QAAQ;GAAW,CAAC;GACvF,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,oCAAoC;GAC/D,IACE,iBAAiB,IAAI,MAAM,UAC3B,KAAK,OAAO,OAAO,YACnB,OAAO,KAAK,UAAU,0BAA0B,UAEhD,OAAO,wBAAwB;IAC7B,SAAS,QAAQ;IACjB,mBAAmB,QAAQ;IAC3B;IACA;IACA;IACA,iBAAiB,gBAAgB,YAAY;IAC7C;IACA,YAAY,UAAU,OAAO;GAC/B,CAAC;GAEH,IAAI,iBAAiB,IAAI,MAAM,YAAY,KAAK,OAAO,OAAO,UAC5D,MAAM,IAAI,MAAM,8DAA8D,KAAK,OAAO,KAAK,IAAI,EAAE,EAAE;GAEzG,MAAM,8BAAa,IAAI,KAAK,EAAA,CAAE,YAAY;GAG1C,IAAI;IACF,MAAM,QAAQ,QAAQ,OAAO;KAC3B,OAAO,QAAQ;KACf,IAAI,KAAK;KACT,QAAQ,SAAS,QAAQ;KACzB,kBAAkB,KAAK;KACvB,OAAO,EACL,UAAU;MACR,GAAI,KAAK,YAAY,CAAC;MACtB,eAAe;MACf,iBAAiB,gBAAgB,YAAY;MAC7C;KACF,EACF;IACF,CAAC;GACH,SAAS,OAAO;IACd,IAAI,EAAE,iBAAiB,8BAA8B,MAAM;IAC3D,MAAM,IAAI,MACR,mGACF;GACF;GAGA,MAAM,SAAS,KAAK,mBAChB,MAAM,QAAQ,QAAQ,IAAI;IAAE,OAAO,QAAQ;IAAO,IAAI,KAAK;GAAiB,CAAC,IAC7E;GACJ,IAAI,UAAU,iBAAiB,MAAM,MAAM,QACzC,MAAM,QAAQ,QAAQ,OAAO;IAC3B,OAAO,QAAQ;IACf,IAAI,OAAO;IACX,QAAQ,SAAS,QAAQ;IACzB,OAAO,EACL,UAAU;KAAE,eAAe;KAAS,iBAAiB,gBAAgB,YAAY;KAAG;IAAW,EACjG;GACF,CAAC;GAEH,OAAO;IAAE,QAAQ;IAAY;IAAS;IAAiB;GAAW;EACpE;CACF,CAAC,EACH;AACF;;;;;;AAOA,eAAe,wBAAwB,OASpC;CACD,MAAM,EAAE,SAAS,SAAS,MAAM,SAAS,iBAAiB,aAAa;CACvE,IAAI,CAAC,MAAM,YAAY,MAAM,IAAI,MAAM,sEAAsE;CAC7G,MAAM,8BAAa,IAAI,KAAK,EAAA,CAAE,YAAY;CAC1C,MAAM,iBAAiB,OAAO,KAAK,UAAU,qBAAqB,WAAW,KAAK,SAAS,mBAAmB;CAC9G,MAAM,SAAS,YAAY,oBAAoB,iBAAiB,IAAI;CAGpE,MAAM,SAAS,YAAY,qBAAqB,SAAA,MAAqC;CACrF,IAAI;CACJ,IAAI;EACF,UAAU,MAAM,QAAQ,OAAO;GAC7B,OAAO,QAAQ;GACf,IAAI,KAAK;GACT,QAAQ,SAAS,QAAQ;GACzB,kBAAkB,KAAK;GACvB,OAAO,EACL,UAAU;IACR,mBAAmB;IACnB,qBAAqB;IACrB,gBAAgB;IAChB,oBAAoB,YAAY;IAChC,kBAAkB;IAClB,sBAAsB;GACxB,EACF;EACF,CAAC;CACH,SAAS,OAAO;EACd,IAAI,EAAE,iBAAiB,8BAA8B,MAAM;EAC3D,MAAM,IAAI,MAAM,iGAAiG;CACnH;CACA,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,oCAAoC;CAClE,IAAI,YAAY,WACd,OAAO;EAAE,QAAQ;EAAY;EAAS;EAAiB;EAAY,kBAAkB;CAAO;CAc9F,OAAO;EAAE,QAAQ;EAAY;EAAS;EAAiB;EAAY,kBAAkB;EAAQ,YAAA,MAbpE,MAAM,kBAAkB,WAAW;GAC1D,OAAO,QAAQ;GACf,kBAAkB,QAAQ;GAC1B,YAAY,QAAQ;GACpB,OAAO;GACP,OAAO,SAAS,WAAW;GAC3B,kBAAkB,QAAQ,KAAK;GAC/B,OAAO;IAAE,MAAM;IAAS,WAAW,QAAQ;IAAI,MAAM,QAAQ;GAAK;GAClE,SAAS;IAAE,MAAM;IAAS,UAAU,GAAG,QAAQ,GAAG,GAAG,MAAM;GAAa;GACxE,OAAO,SACH,iCAAiC,OAAO,yDACxC,oCAAoC,gBAAgB,UAAU,OAAO;EAC3E,CAAC;CACuG;AAC1G;;;;;;;;AASA,SAAS,yBAAyB,YAAoB,OAAqD;CACzG,OAAO,WAAW;EAChB,IAAI;EACJ,aACE;EACF,aAAa;EACb,iBAAiB;EACjB,SAAS,YAAY;GACnB,MAAM,IAAI,MACR,oDAAoD,WAAW,2BAC7D,QAAQ,KAAK,MAAM,KAAK,GACzB,oFACH;EACF;CACF,CAAC;AACH"}
|
|
1
|
+
{"version":3,"file":"tools.js","names":[],"sources":["../../src/rules/tools.ts"],"sourcesContent":["import type { RequestContext } from '@mastra/core/request-context';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport { boardForWorkItem, workItemPhaseSemantics } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport { toBoardSource } from '../boards/project-boards.js';\nimport type { ProjectBoardSource } from '../boards/project-boards.js';\nimport { SELF_REVIEW_ROUND_LIMIT } from '../boards/work.js';\nimport type { IntegrationTools } from '../integrations/base.js';\nimport { WorkItemUpdateConflictError } from '../storage/domains/work-items/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';\nimport type { FactoryTransitionService } from './transition-service.js';\nimport { FACTORY_TRIAGE_TYPES } from './types.js';\nimport { BOARD_IDENTIFIER_RE, MAX_BOARD_IDENTIFIER_LENGTH } from './validation.js';\n\nconst MAX_RATIONALE_LENGTH = 1_000;\n\nconst transitionInputSchema = z\n .object({\n stage: z.string().max(MAX_BOARD_IDENTIFIER_LENGTH).regex(BOARD_IDENTIFIER_RE),\n expectedRevision: z.number().int().positive(),\n // Providers strip maxLength from the JSON schema and models can't count characters, so a\n // hard cap invites overshoot-retry loops at the end of every run. Accept and clamp instead.\n rationale: z\n .string()\n .trim()\n .min(1)\n .transform(value =>\n value.length <= MAX_RATIONALE_LENGTH ? value : `${value.slice(0, MAX_RATIONALE_LENGTH - 1)}…`,\n ),\n // Sessions are shared across role rotations, so a non-triage agent sees the triage\n // agent's earlier call (with triageType) in its history and copies the shape. Accept\n // the key here and drop it in execute rather than fail the whole call on it.\n triageType: z.enum(FACTORY_TRIAGE_TYPES).optional(),\n })\n .strict();\n\nconst triageTransitionInputSchema = transitionInputSchema.extend({\n triageType: z.enum(FACTORY_TRIAGE_TYPES),\n});\n\nexport async function createFactoryTransitionTools(options: {\n requestContext: RequestContext;\n storage: WorkItemsStorage;\n transitionService: Pick<FactoryTransitionService, 'transition'>;\n sessions?: FactorySessionSourceLookup;\n boards?: BoardRegistry | ProjectBoardSource;\n}): Promise<IntegrationTools> {\n const resolution = await resolveFactorySessionAddress({\n requestContext: options.requestContext,\n storage: options.storage,\n sessions: options.sessions,\n });\n if (!resolution) return {};\n const availableBinding = resolution.binding ?? (await options.storage.findActiveRunBinding(resolution.address));\n if (!availableBinding) {\n // No live binding for this session. A resumed session whose binding was\n // revoked when its work item reached a terminal stage was retired out from\n // under the model: rather than silently drop the tool — which leaves the\n // resumed run to rationalize a false blocked state — surface a tool that\n // fails with the concrete reason. Threads that were never bound (or whose\n // binding was rotated to a peer role, leaving the item still in flight) get\n // no tool as before.\n const retired = await options.storage.findRunBindingBySession(resolution.address);\n if (retired && retired.status === 'revoked' && options.boards) {\n const item = await options.storage.get({ orgId: retired.orgId, id: retired.workItemId });\n if (\n item &&\n workItemPhaseSemantics(await toBoardSource(options.boards).load(retired), item)?.kind === 'terminal'\n ) {\n return { factory_transition_work_item: createRetiredSessionTool(retired.workItemId, item.stages[0]) };\n }\n }\n return {};\n }\n let isTriage = false;\n if (availableBinding.role === 'triage') {\n const item = await options.storage.get({ orgId: availableBinding.orgId, id: availableBinding.workItemId });\n if (!item) return {};\n isTriage = boardForWorkItem(item) === 'work';\n }\n\n return {\n ...(availableBinding.role === 'review' ? createReviewVerdictTool(options, availableBinding.workItemId) : {}),\n factory_transition_work_item: createTool({\n id: 'factory_transition_work_item',\n description: isTriage\n ? 'Report the triage classification and request a governed stage transition for the Factory work item exactly bound to this thread. Only bugs may request Planning autonomously; closure outcomes may request a terminal stage. Feature requests and other non-bug classifications that remain open must stay in their current Intake or Triage stage for maintainer approval.'\n : 'Request a governed stage transition for the Factory work item exactly bound to this thread. Use the current revision from the factory-phase signal and explain why the transition is appropriate.',\n inputSchema: isTriage ? triageTransitionInputSchema : transitionInputSchema,\n requireApproval: true,\n execute: async ({ stage, expectedRevision, rationale, triageType: requestedTriageType }, execution) => {\n const currentResolution = await resolveFactorySessionAddress({\n requestContext: execution.requestContext,\n storage: options.storage,\n sessions: options.sessions,\n });\n const currentAddress = currentResolution?.address ?? null;\n const toolCallId = execution.agent?.toolCallId;\n if (!currentAddress || !toolCallId) {\n throw new Error('Factory transitions require an authenticated bound agent tool call.');\n }\n const binding = await options.storage.findActiveRunBinding(currentAddress);\n // Authority is the work item this session is bound to, not the individual\n // binding row. Handing the next role its turn in an existing session\n // rotates the binding, and tools built for the previous role stay live\n // across that rotation; keying on row identity would strand the run that\n // the rotation exists to start. Re-pointing a session at a different item\n // is the hijack this guards against.\n if (!binding || binding.workItemId !== availableBinding.workItemId) {\n throw new Error('Factory agent binding is unavailable, revoked, or no longer matches this session.');\n }\n const item = await options.storage.get({ orgId: binding.orgId, id: binding.workItemId });\n if (!item) throw new Error('Bound Factory work item not found.');\n const board = boardForWorkItem(item);\n // Only a Work triage binding may classify; other roles can echo the key from history.\n const triageType = board === 'work' && binding.role === 'triage' ? requestedTriageType : undefined;\n\n const result = await options.transitionService.transition({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: binding.workItemId,\n board,\n stage,\n expectedRevision,\n actor: { type: 'agent', bindingId: binding.id, role: binding.role },\n ingress: { type: 'agent', identity: `${binding.id}:${toolCallId}` },\n cause: rationale,\n ...(triageType ? { triageType } : {}),\n });\n\n return result;\n },\n }),\n };\n}\n\nexport const REVIEW_VERDICTS = ['approve', 'request changes'] as const;\n\nconst MAX_SELF_REVIEW_FINDINGS_LENGTH = 20_000;\n\nconst reviewVerdictInputSchema = z.object({\n verdict: z.enum(REVIEW_VERDICTS),\n reviewedHeadSha: z\n .string()\n .regex(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i, 'Use the full commit SHA of the reviewed head.'),\n findings: z\n .string()\n .trim()\n .min(1)\n .transform(value =>\n value.length <= MAX_SELF_REVIEW_FINDINGS_LENGTH\n ? value\n : `${value.slice(0, MAX_SELF_REVIEW_FINDINGS_LENGTH - 1)}…`,\n )\n .optional()\n .describe(\n 'Self-review only: the findings and test report, in Markdown. On request changes the builder receives them; on approve they go in the change request body.',\n ),\n});\n\n/**\n * A review pass ends by recording its verdict on the card, which stays in\n * Reviewing: Done is reserved for the merge, and a verdict is not a lane. The\n * next push re-reviews from the recorded verdict.\n */\nfunction createReviewVerdictTool(\n options: {\n requestContext: RequestContext;\n storage: WorkItemsStorage;\n transitionService: Pick<FactoryTransitionService, 'transition'>;\n sessions?: FactorySessionSourceLookup;\n },\n boundWorkItemId: string,\n): IntegrationTools {\n return {\n factory_record_review_verdict: createTool({\n id: 'factory_record_review_verdict',\n description:\n 'Record the review verdict and the head SHA it covers on the card bound to this thread, ending the review pass. On a Review card it records the published PR verdict and the card stays in Reviewing. On a Work card in Review with no change request open yet (self-review), approve keeps the card in Review and wakes the builder to open the change request; request changes sends it back to Building with your findings.',\n inputSchema: reviewVerdictInputSchema,\n requireApproval: false,\n execute: async ({ verdict, reviewedHeadSha, findings }, execution) => {\n const resolution = await resolveFactorySessionAddress({\n requestContext: execution.requestContext,\n storage: options.storage,\n sessions: options.sessions,\n });\n const binding = resolution ? await options.storage.findActiveRunBinding(resolution.address) : null;\n if (!binding || binding.workItemId !== boundWorkItemId || binding.role !== 'review') {\n throw new Error('Factory review binding is unavailable, revoked, or no longer matches this session.');\n }\n const item = await options.storage.get({ orgId: binding.orgId, id: binding.workItemId });\n if (!item) throw new Error('Bound Factory work item not found.');\n if (\n boardForWorkItem(item) === 'work' &&\n item.stages[0] === 'review' &&\n typeof item.metadata?.openPullRequestNumber !== 'number'\n ) {\n return recordSelfReviewVerdict({\n storage: options.storage,\n transitionService: options.transitionService,\n binding,\n item,\n verdict,\n reviewedHeadSha: reviewedHeadSha.toLowerCase(),\n findings,\n toolCallId: execution.agent?.toolCallId,\n });\n }\n if (boardForWorkItem(item) !== 'review' || item.stages[0] !== 'review') {\n throw new Error(`Only a card in Reviewing records a verdict; this one is in ${item.stages.join(', ')}.`);\n }\n const reviewedAt = new Date().toISOString();\n // Revision-checked, so a merge that moves the card between the stage\n // check and this write fails the call instead of stamping a Done card.\n try {\n await options.storage.update({\n orgId: binding.orgId,\n id: item.id,\n userId: `agent:${binding.id}`,\n expectedRevision: item.revision,\n patch: {\n metadata: {\n ...(item.metadata ?? {}),\n reviewVerdict: verdict,\n reviewedHeadSha: reviewedHeadSha.toLowerCase(),\n reviewedAt,\n },\n },\n });\n } catch (error) {\n if (!(error instanceof WorkItemUpdateConflictError)) throw error;\n throw new Error(\n 'The Review card changed while recording the verdict; call again to record it on the current card.',\n );\n }\n // Mirror the verdict onto the Work item that authored the PR, so its\n // builder cannot close the work while changes are still requested.\n const parent = item.parentWorkItemId\n ? await options.storage.get({ orgId: binding.orgId, id: item.parentWorkItemId })\n : null;\n if (parent && boardForWorkItem(parent) === 'work') {\n await options.storage.update({\n orgId: binding.orgId,\n id: parent.id,\n userId: `agent:${binding.id}`,\n patch: {\n metadata: { reviewVerdict: verdict, reviewedHeadSha: reviewedHeadSha.toLowerCase(), reviewedAt },\n },\n });\n }\n return { status: 'recorded', verdict, reviewedHeadSha, reviewedAt };\n },\n }),\n };\n}\n\n/**\n * Self-review ends the same way a PR review does, but on the Work card before\n * any change request exists, and it moves the card itself: the verdict is the\n * only thing that decides where the work goes next.\n */\nasync function recordSelfReviewVerdict(input: {\n storage: WorkItemsStorage;\n transitionService: Pick<FactoryTransitionService, 'transition'>;\n binding: { id: string; orgId: string; factoryProjectId: string; workItemId: string; role: string };\n item: WorkItemRow;\n verdict: (typeof REVIEW_VERDICTS)[number];\n reviewedHeadSha: string;\n findings: string | undefined;\n toolCallId: string | undefined;\n}) {\n const { storage, binding, item, verdict, reviewedHeadSha, findings } = input;\n if (!input.toolCallId) throw new Error('Self-review verdicts require an authenticated bound agent tool call.');\n const reviewedAt = new Date().toISOString();\n const previousRounds = typeof item.metadata?.selfReviewRounds === 'number' ? item.metadata.selfReviewRounds : 0;\n const rounds = verdict === 'request changes' ? previousRounds + 1 : previousRounds;\n // Every SELF_REVIEW_ROUND_LIMIT rounds the card rests in Intake for a person instead of looping;\n // moving it back to Building grants another full set of rounds.\n const parked = verdict === 'request changes' && rounds % SELF_REVIEW_ROUND_LIMIT === 0;\n let updated: Awaited<ReturnType<WorkItemsStorage['update']>>;\n try {\n updated = await storage.update({\n orgId: binding.orgId,\n id: item.id,\n userId: `agent:${binding.id}`,\n expectedRevision: item.revision,\n patch: {\n metadata: {\n selfReviewVerdict: verdict,\n selfReviewedHeadSha: reviewedHeadSha,\n selfReviewedAt: reviewedAt,\n selfReviewFindings: findings ?? null,\n selfReviewRounds: rounds,\n selfReviewNeedsHuman: parked,\n },\n },\n });\n } catch (error) {\n if (!(error instanceof WorkItemUpdateConflictError)) throw error;\n throw new Error('The Work card changed while recording the verdict; call again to record it on the current card.');\n }\n if (!updated) throw new Error('Bound Factory work item not found.');\n if (verdict === 'approve')\n return { status: 'recorded', verdict, reviewedHeadSha, reviewedAt, selfReviewRounds: rounds };\n const transition = await input.transitionService.transition({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: binding.workItemId,\n board: 'work',\n stage: parked ? 'intake' : 'execute',\n expectedRevision: updated.item.revision,\n actor: { type: 'agent', bindingId: binding.id, role: binding.role },\n ingress: { type: 'agent', identity: `${binding.id}:${input.toolCallId}` },\n cause: parked\n ? `Self-review requested changes ${rounds} times; parked for a person to decide how to proceed.`\n : `Self-review requested changes on ${reviewedHeadSha} (round ${rounds}).`,\n });\n return { status: 'recorded', verdict, reviewedHeadSha, reviewedAt, selfReviewRounds: rounds, transition };\n}\n\n/**\n * A retired session's transition tool: it exists only to fail loudly. The run's\n * binding was revoked when its work item settled at a terminal stage, so there\n * is nothing to transition. Registering it (instead of dropping the tool) stops\n * a resumed model from inventing a false \"blocked\" state when the affordance it\n * expects has silently vanished.\n */\nfunction createRetiredSessionTool(workItemId: string, stage: string | undefined): IntegrationTools[string] {\n return createTool({\n id: 'factory_transition_work_item',\n description:\n 'This Factory session has been retired: its work item already reached a terminal stage and the run binding was revoked. No further transitions can be requested from this session.',\n inputSchema: transitionInputSchema,\n requireApproval: false,\n execute: async () => {\n throw new Error(\n `This Factory session has been retired: work item ${workItemId} reached a terminal stage${\n stage ? ` (${stage})` : ''\n } and its run binding was revoked. No transition can be requested from this session.`,\n );\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BoardRegistry } from '../boards/index.js';
|
|
2
|
+
import type { ProjectBoardSource } from '../boards/project-boards.js';
|
|
2
3
|
import type { AuditActorProfileInput, AuditActorType, AuditContext } from '../storage/domains/audit/base.js';
|
|
3
4
|
import type { AuditRecorder } from '../storage/domains/audit/domain.js';
|
|
4
5
|
import type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';
|
|
@@ -34,7 +35,8 @@ export interface FactoryTransitionRequest {
|
|
|
34
35
|
export interface FactoryTransitionServiceOptions {
|
|
35
36
|
configVersion: string;
|
|
36
37
|
storage: WorkItemsStorage;
|
|
37
|
-
boards
|
|
38
|
+
/** A fixed registry, or the per-project boards; each transition loads its project's boards. */
|
|
39
|
+
boards?: BoardRegistry | ProjectBoardSource;
|
|
38
40
|
/** Every commit, accepted or rejected, lands here as `stage_moved` / `transition_rejected` under the request's actor. */
|
|
39
41
|
audit?: AuditRecorder;
|
|
40
42
|
timeoutMs?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transition-service.d.ts","sourceRoot":"","sources":["../../src/rules/transition-service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"transition-service.d.ts","sourceRoot":"","sources":["../../src/rules/transition-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { externallyAuthoredWorkItem, factoryRuleSourceForWorkItem, isFactoryRuleStage, workItemSource } from "./types.js";
|
|
2
2
|
import { assertFactoryDecisionTarget, validateFactoryRuleDecision, validateFactoryRuleDecisions } from "./validation.js";
|
|
3
|
-
import {
|
|
4
|
-
import "../boards/index.js";
|
|
3
|
+
import { toBoardSource } from "../boards/project-boards.js";
|
|
5
4
|
import { isAgentActor } from "../storage/domains/work-items/base.js";
|
|
6
5
|
import { autoApprovedExitTool } from "../boards/phase-automation.js";
|
|
7
6
|
import { boardTransitionPolicyResultSchema, immutablePolicySnapshot } from "../boards/transition-policy.js";
|
|
@@ -117,7 +116,7 @@ var FactoryTransitionService = class {
|
|
|
117
116
|
#audit;
|
|
118
117
|
constructor(options) {
|
|
119
118
|
this.#configVersion = options.configVersion;
|
|
120
|
-
this.#boards = options.boards
|
|
119
|
+
this.#boards = toBoardSource(options.boards);
|
|
121
120
|
this.#storage = options.storage;
|
|
122
121
|
this.#audit = options.audit;
|
|
123
122
|
this.#timeoutMs = options.timeoutMs ?? RULE_TIMEOUT_MS;
|
|
@@ -206,16 +205,17 @@ var FactoryTransitionService = class {
|
|
|
206
205
|
});
|
|
207
206
|
}
|
|
208
207
|
async #evaluateAndCommit(request, transitionId, item) {
|
|
208
|
+
const boards = await this.#boards.load(request);
|
|
209
209
|
if (request.causalChain && request.causalChain.length > 8) return this.#commitRejection(request, transitionId, "causal_depth_exceeded", "Factory rule causal depth exceeded.");
|
|
210
210
|
const itemSource = workItemSource(item.externalSource);
|
|
211
211
|
const source = factoryRuleSourceForWorkItem(itemSource);
|
|
212
212
|
const isPullRequest = source === "pullRequest" || source === "gitlabPullRequest";
|
|
213
213
|
const legacyBoard = isPullRequest ? "review" : "work";
|
|
214
|
-
if (item.board === null && !
|
|
214
|
+
if (item.board === null && !boards.has(legacyBoard)) return this.#commitRejection(request, transitionId, "invalid_transition", "This legacy work item has no assigned board. Assign an installed board and phase through the work-item PATCH endpoint before transitioning it.");
|
|
215
215
|
const itemBoard = item.board ?? legacyBoard;
|
|
216
216
|
if (request.board !== itemBoard) return this.#commitRejection(request, transitionId, "invalid_transition", `The work item belongs to board "${itemBoard}", not "${request.board}".`);
|
|
217
217
|
if (itemBoard === "review" && !isPullRequest || itemBoard === "work" && isPullRequest) return this.#commitRejection(request, transitionId, "invalid_transition", "The work item does not belong to the requested board.");
|
|
218
|
-
const board =
|
|
218
|
+
const board = boards.get(request.board);
|
|
219
219
|
if (!board) return this.#commitRejection(request, transitionId, "invalid_transition", `Board "${request.board}" is not installed.`);
|
|
220
220
|
const fromStage = item.stages.length === 1 ? item.stages[0] : void 0;
|
|
221
221
|
if (!fromStage || !Object.prototype.hasOwnProperty.call(board.phases, fromStage)) return this.#commitRejection(request, transitionId, "invalid_transition", "The work item does not have one canonical phase on the requested board.");
|
|
@@ -260,7 +260,7 @@ var FactoryTransitionService = class {
|
|
|
260
260
|
let evaluation;
|
|
261
261
|
try {
|
|
262
262
|
evaluation = await withRuleTimeout((async () => {
|
|
263
|
-
const plansAutoApproved = autoApprovedExitTool(
|
|
263
|
+
const plansAutoApproved = autoApprovedExitTool(boards, item) === "submit_plan" || (this.#autoApprovePlans ? await this.#autoApprovePlans({
|
|
264
264
|
orgId: request.orgId,
|
|
265
265
|
factoryProjectId: request.factoryProjectId
|
|
266
266
|
}) : false);
|
|
@@ -294,7 +294,7 @@ var FactoryTransitionService = class {
|
|
|
294
294
|
factoryProjectId: request.factoryProjectId
|
|
295
295
|
}) : false;
|
|
296
296
|
const decisions = [];
|
|
297
|
-
for (const rule of resolveFactoryStageRules(
|
|
297
|
+
for (const rule of resolveFactoryStageRules(boards, {
|
|
298
298
|
board: request.board,
|
|
299
299
|
source,
|
|
300
300
|
fromStage,
|
|
@@ -315,7 +315,7 @@ var FactoryTransitionService = class {
|
|
|
315
315
|
code: decision.code,
|
|
316
316
|
reason: decision.reason
|
|
317
317
|
};
|
|
318
|
-
assertFactoryDecisionTarget(decision,
|
|
318
|
+
assertFactoryDecisionTarget(decision, boards, itemBoard);
|
|
319
319
|
if (startsRun(decision) && runAlreadyUnderway(request, decision)) continue;
|
|
320
320
|
decisions.push(decision);
|
|
321
321
|
}
|
|
@@ -357,16 +357,16 @@ var FactoryTransitionService = class {
|
|
|
357
357
|
return this.#commit(request, transitionId, evaluation, evaluation.outcome === "accepted" ? {
|
|
358
358
|
...consentEffect(request, entersWorking, humanMove),
|
|
359
359
|
...evaluation.intents
|
|
360
|
-
} : {});
|
|
360
|
+
} : {}, boards);
|
|
361
361
|
}
|
|
362
362
|
async #commitRejection(request, transitionId, code, reason) {
|
|
363
363
|
return this.#commit(request, transitionId, {
|
|
364
364
|
outcome: "rejected",
|
|
365
365
|
code,
|
|
366
366
|
reason: reason.slice(0, MAX_REJECTION_REASON)
|
|
367
|
-
});
|
|
367
|
+
}, {}, void 0);
|
|
368
368
|
}
|
|
369
|
-
async #commit(request, transitionId, evaluation, options
|
|
369
|
+
async #commit(request, transitionId, evaluation, options, boards) {
|
|
370
370
|
const committed = await this.#storage.commitTransition({
|
|
371
371
|
autonomy: options.autonomy,
|
|
372
372
|
consentedBy: options.consentedBy,
|
|
@@ -401,7 +401,7 @@ var FactoryTransitionService = class {
|
|
|
401
401
|
console.warn(`[factory] acceptance hook failed for work item ${request.workItemId}:`, error);
|
|
402
402
|
});
|
|
403
403
|
}
|
|
404
|
-
if (this.#onTerminalStage && result.status === "accepted" &&
|
|
404
|
+
if (this.#onTerminalStage && result.status === "accepted" && boards?.get(request.board)?.isTerminal(result.stage) === true) {
|
|
405
405
|
let timer;
|
|
406
406
|
try {
|
|
407
407
|
const cleanup = Promise.resolve(this.#onTerminalStage({
|