@mastra/factory 0.12.0-alpha.0 → 0.12.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +37 -15
- package/dist/factory.js.map +1 -1
- package/dist/feed-events.d.ts +13 -0
- package/dist/feed-events.d.ts.map +1 -0
- package/dist/feed-events.js +25 -0
- package/dist/feed-events.js.map +1 -0
- package/dist/integrations/base.d.ts +9 -0
- package/dist/integrations/base.d.ts.map +1 -1
- package/dist/integrations/github/commits.d.ts +29 -0
- package/dist/integrations/github/commits.d.ts.map +1 -0
- package/dist/integrations/github/commits.js +29 -0
- package/dist/integrations/github/commits.js.map +1 -0
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +47 -10
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/issue-reconciler.d.ts.map +1 -1
- package/dist/integrations/github/issue-reconciler.js +22 -14
- package/dist/integrations/github/issue-reconciler.js.map +1 -1
- package/dist/integrations/github/reconcile-worker.d.ts.map +1 -1
- package/dist/integrations/github/reconcile-worker.js +3 -3
- package/dist/integrations/github/reconcile-worker.js.map +1 -1
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +35 -0
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/rules.d.ts +6 -0
- package/dist/integrations/github/rules.d.ts.map +1 -1
- package/dist/integrations/github/rules.js +58 -12
- package/dist/integrations/github/rules.js.map +1 -1
- package/dist/integrations/github/settle-or-abort.d.ts +7 -0
- package/dist/integrations/github/settle-or-abort.d.ts.map +1 -0
- package/dist/integrations/github/settle-or-abort.js +25 -0
- package/dist/integrations/github/settle-or-abort.js.map +1 -0
- package/dist/integrations/issue-reconcile-worker.js +1 -1
- package/dist/integrations/platform/github/event-worker.d.ts.map +1 -1
- package/dist/integrations/platform/github/event-worker.js +4 -4
- package/dist/integrations/platform/github/event-worker.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +37 -5
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/platform/linear/event-worker.js +1 -1
- package/dist/integrations/slack/feed-publisher.d.ts +26 -0
- package/dist/integrations/slack/feed-publisher.d.ts.map +1 -0
- package/dist/integrations/slack/feed-publisher.js +44 -0
- package/dist/integrations/slack/feed-publisher.js.map +1 -0
- package/dist/integrations/slack/integration.d.ts +2 -0
- package/dist/integrations/slack/integration.d.ts.map +1 -1
- package/dist/integrations/slack/integration.js +5 -0
- package/dist/integrations/slack/integration.js.map +1 -1
- package/dist/integrations/slack/slack.d.ts +13 -1
- package/dist/integrations/slack/slack.d.ts.map +1 -1
- package/dist/integrations/slack/slack.js +78 -6
- package/dist/integrations/slack/slack.js.map +1 -1
- package/dist/routes/attention-activity.d.ts +24 -0
- package/dist/routes/attention-activity.d.ts.map +1 -0
- package/dist/routes/attention-activity.js +143 -0
- package/dist/routes/attention-activity.js.map +1 -0
- package/dist/routes/attention-providers.d.ts +31 -1
- package/dist/routes/attention-providers.d.ts.map +1 -1
- package/dist/routes/attention-providers.js +4 -4
- package/dist/routes/attention-providers.js.map +1 -1
- package/dist/routes/attention.d.ts.map +1 -1
- package/dist/routes/attention.js +39 -11
- package/dist/routes/attention.js.map +1 -1
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +4 -0
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/skills.d.ts.map +1 -1
- package/dist/routes/skills.js +9 -3
- package/dist/routes/skills.js.map +1 -1
- package/dist/routes/surface.d.ts +5 -0
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +34 -6
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/tenant-credentials.d.ts +4 -4
- package/dist/routes/tenant-credentials.d.ts.map +1 -1
- package/dist/routes/tenant-credentials.js +22 -7
- package/dist/routes/tenant-credentials.js.map +1 -1
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +12 -1
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/defaults.d.ts.map +1 -1
- package/dist/rules/defaults.js +20 -8
- package/dist/rules/defaults.js.map +1 -1
- package/dist/rules/dispatch-errors.d.ts.map +1 -1
- package/dist/rules/dispatch-errors.js +8 -0
- package/dist/rules/dispatch-errors.js.map +1 -1
- package/dist/rules/dispatcher.d.ts +5 -0
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +125 -62
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/processor.d.ts.map +1 -1
- package/dist/rules/processor.js +2 -3
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/start-coordinator.d.ts +2 -0
- package/dist/rules/start-coordinator.d.ts.map +1 -1
- package/dist/rules/start-coordinator.js +6 -1
- package/dist/rules/start-coordinator.js.map +1 -1
- package/dist/rules/transition-service.d.ts +2 -2
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +33 -12
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.d.ts +27 -1
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js +37 -1
- package/dist/rules/types.js.map +1 -1
- package/dist/rules/validation.d.ts.map +1 -1
- package/dist/rules/validation.js +3 -1
- package/dist/rules/validation.js.map +1 -1
- package/dist/sandbox/session-sandbox.d.ts.map +1 -1
- package/dist/sandbox/session-sandbox.js +2 -2
- package/dist/sandbox/session-sandbox.js.map +1 -1
- package/dist/sandbox/workdir.d.ts +7 -2
- package/dist/sandbox/workdir.d.ts.map +1 -1
- package/dist/sandbox/workdir.js +11 -4
- package/dist/sandbox/workdir.js.map +1 -1
- package/dist/skills/catalog.d.ts +4 -1
- package/dist/skills/catalog.d.ts.map +1 -1
- package/dist/skills/catalog.js +11 -4
- package/dist/skills/catalog.js.map +1 -1
- package/dist/skills/service.d.ts +2 -1
- package/dist/skills/service.d.ts.map +1 -1
- package/dist/skills/service.js +5 -2
- package/dist/skills/service.js.map +1 -1
- package/dist/storage/domains/comments/activity.d.ts +40 -0
- package/dist/storage/domains/comments/activity.d.ts.map +1 -0
- package/dist/storage/domains/comments/activity.js +94 -0
- package/dist/storage/domains/comments/activity.js.map +1 -0
- package/dist/storage/domains/comments/base.d.ts +13 -1
- package/dist/storage/domains/comments/base.d.ts.map +1 -1
- package/dist/storage/domains/comments/base.js +43 -7
- package/dist/storage/domains/comments/base.js.map +1 -1
- package/dist/storage/domains/comments/domain.d.ts +13 -3
- package/dist/storage/domains/comments/domain.d.ts.map +1 -1
- package/dist/storage/domains/comments/domain.js +31 -16
- package/dist/storage/domains/comments/domain.js.map +1 -1
- package/dist/storage/domains/comments/feed-context.d.ts +2 -0
- package/dist/storage/domains/comments/feed-context.d.ts.map +1 -1
- package/dist/storage/domains/comments/feed-context.js +6 -4
- package/dist/storage/domains/comments/feed-context.js.map +1 -1
- package/dist/storage/domains/comments/feed-sync.d.ts +9 -26
- package/dist/storage/domains/comments/feed-sync.d.ts.map +1 -1
- package/dist/storage/domains/comments/parse.d.ts +1 -0
- package/dist/storage/domains/comments/parse.d.ts.map +1 -1
- package/dist/storage/domains/comments/parse.js +2 -2
- package/dist/storage/domains/comments/parse.js.map +1 -1
- package/dist/storage/domains/comments/routes.d.ts +2 -0
- package/dist/storage/domains/comments/routes.d.ts.map +1 -1
- package/dist/storage/domains/comments/routes.js +49 -3
- package/dist/storage/domains/comments/routes.js.map +1 -1
- package/dist/storage/domains/comments/schema.d.ts +1 -0
- package/dist/storage/domains/comments/schema.d.ts.map +1 -1
- package/dist/storage/domains/comments/schema.js +45 -1
- package/dist/storage/domains/comments/schema.js.map +1 -1
- package/dist/storage/domains/projects/base.d.ts +3 -0
- package/dist/storage/domains/projects/base.d.ts.map +1 -1
- package/dist/storage/domains/projects/base.js +7 -0
- package/dist/storage/domains/projects/base.js.map +1 -1
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/source-control/inmemory.js +5 -1
- package/dist/storage/domains/source-control/inmemory.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +46 -4
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +159 -61
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/workspace.d.ts +20 -2
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +49 -15
- package/dist/workspace.js.map +1 -1
- package/factory-skills/factory-rereview/SKILL.md +13 -12
- package/factory-skills/factory-review/SKILL.md +13 -11
- package/factory-skills/factory-triage/SKILL.md +27 -6
- package/package.json +11 -12
- package/CHANGELOG.md +0 -3330
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.js","names":[],"sources":["../../src/rules/defaults.ts"],"sourcesContent":["import type {\n FactoryBoardRuleLeaf,\n FactoryBoardRules,\n FactoryGithubRuleLeaf,\n FactoryGithubEventName,\n FactoryGithubRuleContext,\n FactoryLinearEventName,\n FactoryLinearRuleContext,\n FactoryLinearRuleLeaf,\n FactoryRules,\n FactoryRulesOverrides,\n FactoryRuleSource,\n FactoryRuleStage,\n FactoryStageRuleContext,\n FactoryToolResultRuleContext,\n FactoryToolRuleLeaf,\n} from './types.js';\nimport { assertFactoryRules, FactoryRuleValidationError } from './validation.js';\n\nexport const DEFAULT_FACTORY_RULE_VERSION = 'factory-default-v1';\n\nfunction trustedGithubActor(context: Pick<FactoryStageRuleContext, 'actor'>): boolean {\n return context.actor.type === 'github' && context.actor.trusted;\n}\n\nfunction githubActorLogin(context: Pick<FactoryStageRuleContext, 'actor'>): string | undefined {\n return context.actor.type === 'github' ? context.actor.login : undefined;\n}\n\nfunction invokeIssueInvestigation(context: FactoryStageRuleContext) {\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-triage`,\n role: 'triage',\n skillName: 'factory-triage',\n arguments: context.item.url ? `GitHub issue (${context.item.url})` : context.item.title,\n } as const;\n}\n\nfunction investigateTriagedIssue(context: FactoryStageRuleContext) {\n if (\n context.cause === 'run_start' ||\n (context.cause === 'linked_item_materialized' && context.fromStage === 'intake' && context.toStage === 'triage')\n ) {\n return;\n }\n return invokeIssueInvestigation(context);\n}\n\nfunction retriageGithubIssue(context: FactoryGithubRuleContext) {\n if (!context.item || context.item.source !== 'github-issue' || !context.item.url) return;\n if (context.actor.type === 'github' && context.actor.factoryAuthored) return;\n\n const reason =\n context.event === 'issueEdited'\n ? context.issueChange?.title && context.issueChange.body\n ? 'issue title and body edited'\n : context.issueChange?.title\n ? 'issue title edited'\n : 'issue body edited'\n : context.event === 'issueCommentDeleted'\n ? 'comment deleted'\n : context.event === 'issueCommentEdited'\n ? 'comment edited'\n : 'comment created';\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-triage`,\n role: 'triage',\n skillName: 'factory-triage',\n arguments: `Re-triage GitHub issue (${context.item.url}) after ${reason}.`,\n } as const;\n}\n\nfunction investigateTriagedLinearIssue(context: FactoryStageRuleContext) {\n const identifier = context.item.sourceKey?.startsWith('linear:')\n ? context.item.sourceKey.slice('linear:'.length)\n : context.item.title;\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-triage-linear`,\n role: 'triage',\n skillName: 'factory-triage',\n arguments: `Linear issue ${identifier}${context.item.url ? ` (${context.item.url})` : ''}`,\n } as const;\n}\n\nfunction planWorkItem(context: FactoryStageRuleContext) {\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-plan`,\n role: 'plan',\n skillName: 'factory-plan',\n arguments: context.item.url ? `Work item (${context.item.url})` : context.item.title,\n } as const;\n}\n\n// A GitHub login is alphanumeric with interior hyphens — no underscores, no\n// spaces. Checking the grammar rejects the placeholder the issue poller stamps\n// when the reporter's account is gone (`__unknown__`), which would otherwise\n// become a trailer crediting an account that does not exist.\nconst GITHUB_LOGIN = /^[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38}$/;\n\n/**\n * The reporter earns a `Co-Authored-By` trailer on the work their report caused.\n * Only a GitHub issue qualifies: Linear stamps a display name and a manual card\n * stamps nothing, and neither resolves to the GitHub identity a trailer needs.\n * Factory's own reports are skipped — crediting ourselves is noise.\n */\nfunction reporterCoAuthor(context: FactoryStageRuleContext) {\n if (context.source !== 'issue') return undefined;\n const author = context.item.metadata?.author;\n if (typeof author !== 'string' || !author) return undefined;\n if (author.endsWith('[bot]') || !GITHUB_LOGIN.test(author)) return undefined;\n return author;\n}\n\n/**\n * Building carries a prompt rather than a skill. The approved plan is already\n * the specification, so there is nothing for a skill document to add, and the\n * handoff a skill would define is unnecessary here: Building ends by opening a\n * pull request, which arrives as its own event and raises the Review card.\n */\nfunction buildWorkItem(context: FactoryStageRuleContext) {\n const subject = context.item.url ? `the approved plan for ${context.item.url}` : 'the approved plan';\n const reporter = reporterCoAuthor(context);\n // The trailer needs the reporter's numeric id, which intake does not stamp, so\n // the agent resolves it from the same issue it is already reading.\n const credit = reporter\n ? ` The work was reported by @${reporter}: credit them on every commit with a ` +\n `\\`Co-Authored-By: ${reporter} <ID+${reporter}@users.noreply.github.com>\\` trailer, ` +\n `resolving ID with \\`gh api users/${reporter} --jq .id\\`.`\n : '';\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:build`,\n role: 'work',\n prompt: `Implement ${subject}. Open a pull request when the work is ready for review.${credit}`,\n } as const;\n}\n\nfunction completeIssue(context: FactoryStageRuleContext) {\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-complete-issue`,\n role: 'triage',\n skillName: 'factory-complete-issue',\n arguments: context.item.url ? `GitHub issue (${context.item.url})` : context.item.title,\n } as const;\n}\n\nfunction reviewPullRequest(context: FactoryStageRuleContext) {\n // A re-entry into Review (from any post-intake stage) supersedes whichever\n // review pass previously ran on this card: cancel any in-flight run before\n // dispatching a fresh one so we don't burn tokens on the stale pass and race\n // two agents on the same card. Cancellation is safe when nothing is in flight.\n const supersedes = context.fromStage !== 'intake';\n // The re-review skill only applies when a prior review pass actually completed\n // (the card is returning from `done`). A cancelled first-time review that\n // re-enters Review from `review` itself still has no prior pass to reconcile —\n // it gets the regular factory-review skill.\n const priorReviewCompleted = context.fromStage === 'done';\n const skillName = priorReviewCompleted ? 'factory-rereview' : 'factory-review';\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:${skillName}`,\n role: 'review',\n skillName,\n arguments: context.item.url ? `GitHub pull request (${context.item.url})` : context.item.title,\n ...(supersedes ? { cancelInFlight: true } : {}),\n } as const;\n}\n\nfunction resultContent(value: unknown): string | undefined {\n if (typeof value === 'string') return value;\n if (!value || typeof value !== 'object' || Array.isArray(value)) return;\n const content = (value as { content?: unknown }).content;\n return typeof content === 'string' ? content : undefined;\n}\n\n// Interactive-session path only: factory-plan never calls submit_plan — it\n// advances planning → execute via factory_transition_work_item directly.\nfunction advanceApprovedPlan(context: FactoryToolResultRuleContext) {\n if (\n context.result.status !== 'success' ||\n context.board !== 'work' ||\n context.item.stages.length !== 1 ||\n context.item.stages[0] !== 'planning' ||\n context.actor.type !== 'agent' ||\n context.actor.role !== 'plan' ||\n !resultContent(context.result.value)?.startsWith('Plan approved.')\n ) {\n return;\n }\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:approved-plan`,\n board: 'work',\n stage: 'execute',\n } as const;\n}\n\nfunction createdAfterFactory(createdAt: string | undefined, factoryCreatedAt: string): boolean {\n if (!createdAt) return false;\n const sourceCreatedAt = Date.parse(createdAt);\n const projectCreatedAt = Date.parse(factoryCreatedAt);\n return Number.isFinite(sourceCreatedAt) && Number.isFinite(projectCreatedAt) && sourceCreatedAt > projectCreatedAt;\n}\n\nfunction issueOpened(context: FactoryGithubRuleContext) {\n if (!context.issue) return;\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:issue-intake`,\n board: 'work',\n source: 'github-issue',\n sourceKey: `github-issue:${context.issue.number}`,\n title: context.issue.title,\n url: context.issue.url,\n stage:\n trustedGithubActor(context) && createdAfterFactory(context.issue.createdAt, context.factory.createdAt)\n ? 'triage'\n : 'intake',\n metadata: {\n githubRepositoryId: context.repository.id,\n githubIssueNumber: context.issue.number,\n ...(githubActorLogin(context) ? { author: githubActorLogin(context) } : {}),\n assignees: context.issue.assignees ?? [],\n labels: context.issue.labels ?? [],\n },\n } as const;\n}\n\nfunction issueClosed(context: FactoryGithubRuleContext) {\n if (!context.item || context.item.source !== 'github-issue' || !context.issue) return;\n if (context.board !== 'work') return;\n // Already off the board: nothing to reconcile.\n if (context.item.stages.some(stage => stage === 'done' || stage === 'canceled')) return;\n // Issue closure is a repository fact, not third-party input — no actor trust\n // gate. `not_planned` (and `duplicate`) means abandoned, everything else is\n // completed work.\n const canceled = context.issue.stateReason === 'not_planned' || context.issue.stateReason === 'duplicate';\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:issue-closed`,\n board: 'work',\n stage: canceled ? 'canceled' : 'done',\n message: {\n text:\n `GitHub issue #${context.issue.number} was closed` +\n `${context.issue.stateReason ? ` (${context.issue.stateReason})` : ''}; ` +\n `this Work card was moved to ${canceled ? 'Canceled' : 'Done'}.`,\n },\n } as const;\n}\n\nfunction pullRequestOpened(context: FactoryGithubRuleContext) {\n if (!context.pullRequest) return;\n // Trust is a repository-collaborator permission lookup, and a GitHub App bot\n // is never a collaborator — so a PR Factory opened itself scores as untrusted\n // and parks in Intake, the one class of PR whose provenance we know best.\n // Factory authorship is its own trust signal: the branch came from a Work run\n // this Factory dispatched.\n const factoryAuthored = context.actor.type === 'github' && context.actor.factoryAuthored;\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:pull-request-intake`,\n board: 'review',\n source: 'github-pr',\n sourceKey: `github-pr:${context.pullRequest.number}`,\n title: context.pullRequest.title,\n url: context.pullRequest.url,\n stage:\n (trustedGithubActor(context) || factoryAuthored) &&\n createdAfterFactory(context.pullRequest.createdAt, context.factory.createdAt)\n ? 'review'\n : 'intake',\n metadata: {\n githubRepositoryId: context.repository.id,\n githubPullRequestNumber: context.pullRequest.number,\n factoryAuthored,\n state: context.pullRequest.state,\n draft: context.pullRequest.draft,\n merged: context.pullRequest.merged,\n assignees: context.pullRequest.assignees ?? [],\n requestedReviewers: context.pullRequest.requestedReviewers ?? [],\n labels: context.pullRequest.labels ?? [],\n headBranch: context.pullRequest.headBranch,\n baseBranch: context.pullRequest.baseBranch,\n ...(githubActorLogin(context) ? { author: githubActorLogin(context) } : {}),\n },\n } as const;\n}\n\nfunction pullRequestMerged(context: FactoryGithubRuleContext) {\n if (!context.item || !context.pullRequest?.merged) return;\n if (context.board === 'review') {\n // The event is bound to the PR's own Review card: a merged PR is finished\n // review work, so always move the card to Done. The message only reaches\n // an active session (if any) — cards without one just move, instead of\n // failing retries against a binding that never existed.\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:pull-request-merged`,\n board: 'review',\n stage: 'done',\n message: {\n text:\n `Pull request #${context.pullRequest.number} merged; this Review card was moved to Done. ` +\n 'No further review is needed unless follow-up work was requested.',\n },\n } as const;\n }\n // Provenance bound the event to the originating Work item instead: remind\n // its agent to assess completion — never auto-complete the Work item.\n return {\n type: 'sendMessage',\n idempotencyKey: `${context.ingress.id}:assess-work-completion`,\n role: 'work',\n message:\n `Pull request #${context.pullRequest.number} merged. Assess whether the linked Work item is complete. ` +\n 'Do not mark it Done solely because this PR merged; use factory_transition_work_item only after verifying the work.',\n } as const;\n}\n\nfunction addressReviewFeedback(context: FactoryGithubRuleContext) {\n if (!context.item || !context.pullRequest || !context.review) return;\n // Only the Work item that authored the PR can act on the feedback. Provenance\n // binds the event there; a Review card seeing its own posted review must not\n // react to it (that would loop the reviewer against itself).\n if (context.board !== 'work') return;\n // A closed or merged pull request has no branch left to push fixes to.\n if (context.pullRequest.state !== 'open' || context.pullRequest.merged) return;\n // `approved` needs no work, and `commented` (a review body with no verdict)\n // is how a reviewer leaves notes without blocking — only a verdict that asks\n // for changes should pull the author back in.\n if (context.review.state.toLowerCase() !== 'changes_requested') return;\n // The authoring thread is already subscribed to this PR (`gh pr create`\n // subscribes automatically), so it can read the individual line comments\n // from its own notification inbox — the message only has to wake it and\n // point at the review.\n return {\n type: 'sendMessage',\n idempotencyKey: `${context.ingress.id}:address-review-feedback`,\n role: 'work',\n priority: 'high',\n message:\n `Changes were requested on pull request #${context.pullRequest.number} (${context.review.url}). ` +\n 'Read the review comments on this PR, address the ones you agree with, and push the fixes to the PR branch. ' +\n 'Reply on GitHub to anything you are deliberately not changing, explaining why.',\n } as const;\n}\n\n/**\n * Detects the `factory-review` handoff verdict in a comment body.\n *\n * GitHub forbids an app from reviewing a pull request it authored, so on\n * Factory-authored PRs the review skill falls back to posting its verdict as a\n * plain comment. That comment is the only signal the authoring agent gets, so\n * it has to be readable back out. The skill's handoff contract puts the verdict\n * on the first line (`Verdict: request changes`), so only that line is\n * inspected — a verdict quoted later in the findings must not count.\n */\nfunction requestsChangesVerdict(body: string | undefined): boolean {\n const firstLine = body\n ?.split('\\n')\n .map(line => line.trim())\n .find(line => line.length > 0);\n if (!firstLine) return false;\n // Tolerate the markdown the skill wraps the line in (`**Verdict: ...**`).\n const normalized = firstLine\n .replaceAll(/[*_`#>\\s]+/g, ' ')\n .trim()\n .toLowerCase();\n // Match the verdict exactly so negated phrasings (\"Verdict: do not request\n // changes\") cannot wake the author.\n return /^verdict: ?(request changes|changes requested)$/.test(normalized);\n}\n\nfunction addressPullRequestComment(context: FactoryGithubRuleContext) {\n if (!context.item || !context.pullRequest || !context.issueComment) return;\n // Provenance binds the comment to the Work item that authored the PR — the\n // only session that can act on it. A Review card must not react to comments\n // on the PR it is reviewing.\n if (context.board !== 'work') return;\n if (context.pullRequest.state !== 'open' || context.pullRequest.merged) return;\n // `factoryAuthored` is one bit for the whole Factory, so Factory's own\n // comments are indistinguishable between roles — waking on all of them would\n // let the Work agent's own progress comments wake itself in a loop. The one\n // exception is the review verdict the Review run had to post as a comment\n // because GitHub refused a self-review: that is the handoff, and it only ever\n // asks for changes once per review, so it cannot sustain a loop.\n if (\n context.actor.type === 'github' &&\n context.actor.factoryAuthored &&\n !requestsChangesVerdict(context.issueComment.body)\n ) {\n return;\n }\n return {\n type: 'sendMessage',\n idempotencyKey: `${context.ingress.id}:address-pull-request-comment`,\n role: 'work',\n priority: 'high',\n message:\n `${context.issueComment.author ?? 'Someone'} commented on pull request #${context.pullRequest.number} ` +\n `(${context.issueComment.url ?? context.pullRequest.url}). Read the comment, address it if you agree, and push ` +\n 'the fixes to the PR branch. Reply on GitHub to anything you are deliberately not changing, explaining why.',\n } as const;\n}\n\nfunction pullRequestClosed(context: FactoryGithubRuleContext) {\n if (!context.item || !context.pullRequest || context.pullRequest.merged) return;\n if (context.board !== 'review') return;\n // A PR closed without merging is abandoned review work: clear the card off\n // the board instead of leaving it in Reviewing forever.\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:pull-request-closed`,\n board: 'review',\n stage: 'canceled',\n message: {\n text:\n `Pull request #${context.pullRequest.number} was closed without merging; ` +\n 'this Review card was moved to Canceled.',\n },\n } as const;\n}\n\nfunction reReviewRequestedPullRequest(context: FactoryGithubRuleContext) {\n // Only a review re-requested *from Factory's own bot* restarts the review —\n // requesting a human reviewer is not Factory's signal.\n if (!context.item || context.board !== 'review' || !context.reviewRequest?.factoryReviewer) return;\n if (!context.pullRequest || context.pullRequest.state !== 'open' || context.pullRequest.merged) return;\n // Trusted (write/admin) requesters only: re-entering review checks out and\n // executes PR code, the same bar pullRequestOpened applies to auto-review.\n if (!trustedGithubActor(context)) return;\n if (context.actor.type === 'github' && context.actor.factoryAuthored) return;\n // Already in Reviewing: a review pass is pending or running; re-entering\n // would be a same-stage no-op anyway (stage rules only fire on change).\n if (context.item.stages.length === 1 && context.item.stages[0] === 'review') return;\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:re-review-requested`,\n board: 'review',\n stage: 'review',\n } as const;\n}\n\nfunction reReviewUpdatedPullRequest(context: FactoryGithubRuleContext) {\n if (!context.item || context.board !== 'review') return;\n if (!context.pullRequest || context.pullRequest.state !== 'open' || context.pullRequest.merged) return;\n // Intake has not started a review pass yet, so a push there is just more of\n // the code the first pass will read. A push to a card sitting in Reviewing is\n // different: it invalidates whatever that pass is reading, so re-enter the\n // stage to supersede it. `reviewPullRequest` cancels the stale run and picks\n // the right skill for the entry it sees.\n if (context.item.stages.some(stage => stage === 'intake')) return;\n const alreadyReviewing = context.item.stages.some(stage => stage === 'review');\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:re-review-updated`,\n board: 'review',\n stage: 'review',\n // Re-entry is the point when the card is already Reviewing: the stage's\n // entry rule is what cancels the superseded pass and starts one on the code\n // that just landed.\n ...(alreadyReviewing ? { reenter: true } : {}),\n } as const;\n}\n\nfunction linearIssueObserved(context: FactoryLinearRuleContext) {\n if (context.item) return;\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:issue-triage`,\n board: 'work',\n source: 'linear-issue',\n sourceKey: `linear:${context.issue.identifier}`,\n title: `${context.issue.identifier}: ${context.issue.title}`,\n url: context.issue.url,\n stage: 'triage',\n metadata: {\n linearIssueId: context.issue.id,\n identifier: context.issue.identifier,\n linearState: context.issue.state,\n linearStateType: context.issue.stateType,\n linearPriority: context.issue.priorityLabel,\n linearAssignee: context.issue.assignee,\n linearCreator: context.issue.creator,\n linearTeam: context.issue.team,\n labels: [...context.issue.labels] as string[],\n ...(context.issue.assignee ? { assignee: context.issue.assignee } : {}),\n ...(context.issue.creator ? { creator: context.issue.creator, author: context.issue.creator } : {}),\n },\n } as const;\n}\n\nfunction linearIssueClosed(context: FactoryLinearRuleContext) {\n if (!context.item || context.item.source !== 'linear-issue') return;\n if (context.board !== 'work') return;\n // Already off the board: nothing to reconcile.\n if (context.item.stages.some(stage => stage === 'done' || stage === 'canceled')) return;\n // Only terminal state types trigger close.\n const stateType = context.issue.stateType;\n if (stateType !== 'completed' && stateType !== 'canceled') return;\n const canceled = stateType === 'canceled';\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:issue-closed`,\n board: 'work',\n stage: canceled ? 'canceled' : 'done',\n message: {\n text: `Linear issue ${context.issue.identifier} was ${canceled ? 'canceled' : 'completed'}; this Work card was moved to ${canceled ? 'Canceled' : 'Done'}.`,\n },\n } as const;\n}\n\nconst BUILT_IN_DEFAULTS: FactoryRulesOverrides = {\n work: {\n triage: {\n issue: { onEnter: investigateTriagedIssue },\n linearIssue: { onEnter: investigateTriagedLinearIssue },\n },\n planning: {\n issue: { onEnter: planWorkItem },\n linearIssue: { onEnter: planWorkItem },\n manual: { onEnter: planWorkItem },\n },\n execute: {\n issue: { onEnter: buildWorkItem },\n linearIssue: { onEnter: buildWorkItem },\n manual: { onEnter: buildWorkItem },\n },\n done: {\n issue: { onEnter: completeIssue },\n },\n },\n review: { review: { pullRequest: { onEnter: reviewPullRequest } } },\n tools: { submit_plan: { onResult: advanceApprovedPlan } },\n github: {\n issueOpened: { onEvent: issueOpened },\n issueEdited: { onEvent: retriageGithubIssue },\n issueClosed: { onEvent: issueClosed },\n issueCommentCreated: { onEvent: retriageGithubIssue },\n issueCommentEdited: { onEvent: retriageGithubIssue },\n issueCommentDeleted: { onEvent: retriageGithubIssue },\n pullRequestOpened: { onEvent: pullRequestOpened },\n pullRequestUpdated: { onEvent: reReviewUpdatedPullRequest },\n pullRequestCommentCreated: { onEvent: addressPullRequestComment },\n pullRequestReviewRequested: { onEvent: reReviewRequestedPullRequest },\n pullRequestReviewSubmitted: { onEvent: addressReviewFeedback },\n pullRequestMerged: { onEvent: pullRequestMerged },\n pullRequestClosed: { onEvent: pullRequestClosed },\n },\n linear: { issueObserved: { onEvent: linearIssueObserved }, issueClosed: { onEvent: linearIssueClosed } },\n};\n\nfunction mergeBoardRules(\n base: FactoryBoardRules | undefined,\n overrides: FactoryBoardRules | undefined,\n): FactoryBoardRules {\n const result: FactoryBoardRules = {};\n const stages = new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]) as Set<FactoryRuleStage>;\n for (const stage of stages) {\n const baseSources = base?.[stage];\n const overrideSources = overrides?.[stage];\n const sources = new Set([\n ...Object.keys(baseSources ?? {}),\n ...Object.keys(overrideSources ?? {}),\n ]) as Set<FactoryRuleSource>;\n const mergedSources: Partial<Record<FactoryRuleSource, FactoryBoardRuleLeaf>> = {};\n for (const source of sources) {\n const baseLeaf = baseSources?.[source];\n const overrideLeaf = overrideSources?.[source];\n mergedSources[source] = {\n ...(baseLeaf && 'onEnter' in baseLeaf ? { onEnter: baseLeaf.onEnter } : {}),\n ...(baseLeaf && 'onExit' in baseLeaf ? { onExit: baseLeaf.onExit } : {}),\n ...(overrideLeaf && 'onEnter' in overrideLeaf ? { onEnter: overrideLeaf.onEnter } : {}),\n ...(overrideLeaf && 'onExit' in overrideLeaf ? { onExit: overrideLeaf.onExit } : {}),\n };\n }\n result[stage] = mergedSources;\n }\n return result;\n}\n\nfunction mergeToolRules(\n base: Record<string, FactoryToolRuleLeaf> | undefined,\n overrides: Record<string, FactoryToolRuleLeaf> | undefined,\n): Record<string, FactoryToolRuleLeaf> {\n const result: Record<string, FactoryToolRuleLeaf> = {};\n for (const name of new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})])) {\n const baseLeaf = base?.[name];\n const overrideLeaf = overrides?.[name];\n result[name] = {\n ...(baseLeaf && 'onResult' in baseLeaf ? { onResult: baseLeaf.onResult } : {}),\n ...(overrideLeaf && 'onResult' in overrideLeaf ? { onResult: overrideLeaf.onResult } : {}),\n };\n }\n return result;\n}\n\nfunction mergeGithubRules(\n base: FactoryRulesOverrides['github'],\n overrides: FactoryRulesOverrides['github'],\n): NonNullable<FactoryRulesOverrides['github']> {\n const result: Partial<Record<FactoryGithubEventName, FactoryGithubRuleLeaf>> = {};\n const events = new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]) as Set<FactoryGithubEventName>;\n for (const event of events) {\n const baseLeaf = base?.[event];\n const overrideLeaf = overrides?.[event];\n result[event] = {\n ...(baseLeaf && 'onEvent' in baseLeaf ? { onEvent: baseLeaf.onEvent } : {}),\n ...(overrideLeaf && 'onEvent' in overrideLeaf ? { onEvent: overrideLeaf.onEvent } : {}),\n };\n }\n return result;\n}\n\nfunction mergeLinearRules(\n base: FactoryRulesOverrides['linear'],\n overrides: FactoryRulesOverrides['linear'],\n): NonNullable<FactoryRulesOverrides['linear']> {\n const result: Partial<Record<FactoryLinearEventName, FactoryLinearRuleLeaf>> = {};\n const events = new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]) as Set<FactoryLinearEventName>;\n for (const event of events) {\n const baseLeaf = base?.[event];\n const overrideLeaf = overrides?.[event];\n result[event] = {\n ...(baseLeaf && 'onEvent' in baseLeaf ? { onEvent: baseLeaf.onEvent } : {}),\n ...(overrideLeaf && 'onEvent' in overrideLeaf ? { onEvent: overrideLeaf.onEvent } : {}),\n };\n }\n return result;\n}\n\nexport function mergeFactoryRuleOverrides(\n base: FactoryRulesOverrides,\n overrides: FactoryRulesOverrides = {},\n): Omit<FactoryRules, 'version'> {\n return {\n work: mergeBoardRules(base.work, overrides.work),\n review: mergeBoardRules(base.review, overrides.review),\n tools: mergeToolRules(base.tools, overrides.tools),\n github: mergeGithubRules(base.github, overrides.github),\n linear: mergeLinearRules(base.linear, overrides.linear),\n };\n}\n\nexport function defaultFactoryRules(input: { version: string; overrides?: FactoryRulesOverrides }): FactoryRules {\n if (typeof input?.version !== 'string' || input.version.trim().length === 0) {\n throw new FactoryRuleValidationError('Factory rule version is required.');\n }\n\n const rules: FactoryRules = {\n version: input.version.trim(),\n ...mergeFactoryRuleOverrides(BUILT_IN_DEFAULTS, input.overrides),\n };\n assertFactoryRules(rules);\n return rules;\n}\n\nexport function builtInFactoryRules(): FactoryRules {\n return defaultFactoryRules({ version: DEFAULT_FACTORY_RULE_VERSION });\n}\n"],"mappings":";;AAmBA,MAAa,+BAA+B;AAE5C,SAAS,mBAAmB,SAA0D;CACpF,OAAO,QAAQ,MAAM,SAAS,YAAY,QAAQ,MAAM;AAC1D;AAEA,SAAS,iBAAiB,SAAqE;CAC7F,OAAO,QAAQ,MAAM,SAAS,WAAW,QAAQ,MAAM,QAAQ,KAAA;AACjE;AAEA,SAAS,yBAAyB,SAAkC;CAClE,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,MAAM;EACN,WAAW;EACX,WAAW,QAAQ,KAAK,MAAM,iBAAiB,QAAQ,KAAK,IAAI,KAAK,QAAQ,KAAK;CACpF;AACF;AAEA,SAAS,wBAAwB,SAAkC;CACjE,IACE,QAAQ,UAAU,eACjB,QAAQ,UAAU,8BAA8B,QAAQ,cAAc,YAAY,QAAQ,YAAY,UAEvG;CAEF,OAAO,yBAAyB,OAAO;AACzC;AAEA,SAAS,oBAAoB,SAAmC;CAC9D,IAAI,CAAC,QAAQ,QAAQ,QAAQ,KAAK,WAAW,kBAAkB,CAAC,QAAQ,KAAK,KAAK;CAClF,IAAI,QAAQ,MAAM,SAAS,YAAY,QAAQ,MAAM,iBAAiB;CAEtE,MAAM,SACJ,QAAQ,UAAU,gBACd,QAAQ,aAAa,SAAS,QAAQ,YAAY,OAChD,gCACA,QAAQ,aAAa,QACnB,uBACA,sBACJ,QAAQ,UAAU,wBAChB,oBACA,QAAQ,UAAU,uBAChB,mBACA;CACV,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,MAAM;EACN,WAAW;EACX,WAAW,2BAA2B,QAAQ,KAAK,IAAI,UAAU,OAAO;CAC1E;AACF;AAEA,SAAS,8BAA8B,SAAkC;CACvE,MAAM,aAAa,QAAQ,KAAK,WAAW,WAAW,SAAS,IAC3D,QAAQ,KAAK,UAAU,MAAM,CAAgB,IAC7C,QAAQ,KAAK;CACjB,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,MAAM;EACN,WAAW;EACX,WAAW,gBAAgB,aAAa,QAAQ,KAAK,MAAM,KAAK,QAAQ,KAAK,IAAI,KAAK;CACxF;AACF;AAEA,SAAS,aAAa,SAAkC;CACtD,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,MAAM;EACN,WAAW;EACX,WAAW,QAAQ,KAAK,MAAM,cAAc,QAAQ,KAAK,IAAI,KAAK,QAAQ,KAAK;CACjF;AACF;AAMA,MAAM,eAAe;;;;;;;AAQrB,SAAS,iBAAiB,SAAkC;CAC1D,IAAI,QAAQ,WAAW,SAAS,OAAO,KAAA;CACvC,MAAM,SAAS,QAAQ,KAAK,UAAU;CACtC,IAAI,OAAO,WAAW,YAAY,CAAC,QAAQ,OAAO,KAAA;CAClD,IAAI,OAAO,SAAS,OAAO,KAAK,CAAC,aAAa,KAAK,MAAM,GAAG,OAAO,KAAA;CACnE,OAAO;AACT;;;;;;;AAQA,SAAS,cAAc,SAAkC;CACvD,MAAM,UAAU,QAAQ,KAAK,MAAM,yBAAyB,QAAQ,KAAK,QAAQ;CACjF,MAAM,WAAW,iBAAiB,OAAO;CAGzC,MAAM,SAAS,WACX,8BAA8B,SAAS,yDAClB,SAAS,OAAO,SAAS,yEACV,SAAS,gBAC7C;CACJ,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,MAAM;EACN,QAAQ,aAAa,QAAQ,0DAA0D;CACzF;AACF;AAEA,SAAS,cAAc,SAAkC;CACvD,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,MAAM;EACN,WAAW;EACX,WAAW,QAAQ,KAAK,MAAM,iBAAiB,QAAQ,KAAK,IAAI,KAAK,QAAQ,KAAK;CACpF;AACF;AAEA,SAAS,kBAAkB,SAAkC;CAK3D,MAAM,aAAa,QAAQ,cAAc;CAMzC,MAAM,YADuB,QAAQ,cAAc,SACV,qBAAqB;CAC9D,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG,GAAG;EACzC,MAAM;EACN;EACA,WAAW,QAAQ,KAAK,MAAM,wBAAwB,QAAQ,KAAK,IAAI,KAAK,QAAQ,KAAK;EACzF,GAAI,aAAa,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC/C;AACF;AAEA,SAAS,cAAc,OAAoC;CACzD,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;CACjE,MAAM,UAAW,MAAgC;CACjD,OAAO,OAAO,YAAY,WAAW,UAAU,KAAA;AACjD;AAIA,SAAS,oBAAoB,SAAuC;CAClE,IACE,QAAQ,OAAO,WAAW,aAC1B,QAAQ,UAAU,UAClB,QAAQ,KAAK,OAAO,WAAW,KAC/B,QAAQ,KAAK,OAAO,OAAO,cAC3B,QAAQ,MAAM,SAAS,WACvB,QAAQ,MAAM,SAAS,UACvB,CAAC,cAAc,QAAQ,OAAO,KAAK,CAAC,EAAE,WAAW,gBAAgB,GAEjE;CAEF,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,OAAO;CACT;AACF;AAEA,SAAS,oBAAoB,WAA+B,kBAAmC;CAC7F,IAAI,CAAC,WAAW,OAAO;CACvB,MAAM,kBAAkB,KAAK,MAAM,SAAS;CAC5C,MAAM,mBAAmB,KAAK,MAAM,gBAAgB;CACpD,OAAO,OAAO,SAAS,eAAe,KAAK,OAAO,SAAS,gBAAgB,KAAK,kBAAkB;AACpG;AAEA,SAAS,YAAY,SAAmC;CACtD,IAAI,CAAC,QAAQ,OAAO;CACpB,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,QAAQ;EACR,WAAW,gBAAgB,QAAQ,MAAM;EACzC,OAAO,QAAQ,MAAM;EACrB,KAAK,QAAQ,MAAM;EACnB,OACE,mBAAmB,OAAO,KAAK,oBAAoB,QAAQ,MAAM,WAAW,QAAQ,QAAQ,SAAS,IACjG,WACA;EACN,UAAU;GACR,oBAAoB,QAAQ,WAAW;GACvC,mBAAmB,QAAQ,MAAM;GACjC,GAAI,iBAAiB,OAAO,IAAI,EAAE,QAAQ,iBAAiB,OAAO,EAAE,IAAI,CAAC;GACzE,WAAW,QAAQ,MAAM,aAAa,CAAC;GACvC,QAAQ,QAAQ,MAAM,UAAU,CAAC;EACnC;CACF;AACF;AAEA,SAAS,YAAY,SAAmC;CACtD,IAAI,CAAC,QAAQ,QAAQ,QAAQ,KAAK,WAAW,kBAAkB,CAAC,QAAQ,OAAO;CAC/E,IAAI,QAAQ,UAAU,QAAQ;CAE9B,IAAI,QAAQ,KAAK,OAAO,MAAK,UAAS,UAAU,UAAU,UAAU,UAAU,GAAG;CAIjF,MAAM,WAAW,QAAQ,MAAM,gBAAgB,iBAAiB,QAAQ,MAAM,gBAAgB;CAC9F,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,OAAO,WAAW,aAAa;EAC/B,SAAS,EACP,MACE,iBAAiB,QAAQ,MAAM,OAAO,aACnC,QAAQ,MAAM,cAAc,KAAK,QAAQ,MAAM,YAAY,KAAK,GAAG,gCACvC,WAAW,aAAa,OAAO,GAClE;CACF;AACF;AAEA,SAAS,kBAAkB,SAAmC;CAC5D,IAAI,CAAC,QAAQ,aAAa;CAM1B,MAAM,kBAAkB,QAAQ,MAAM,SAAS,YAAY,QAAQ,MAAM;CACzE,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,QAAQ;EACR,WAAW,aAAa,QAAQ,YAAY;EAC5C,OAAO,QAAQ,YAAY;EAC3B,KAAK,QAAQ,YAAY;EACzB,QACG,mBAAmB,OAAO,KAAK,oBAChC,oBAAoB,QAAQ,YAAY,WAAW,QAAQ,QAAQ,SAAS,IACxE,WACA;EACN,UAAU;GACR,oBAAoB,QAAQ,WAAW;GACvC,yBAAyB,QAAQ,YAAY;GAC7C;GACA,OAAO,QAAQ,YAAY;GAC3B,OAAO,QAAQ,YAAY;GAC3B,QAAQ,QAAQ,YAAY;GAC5B,WAAW,QAAQ,YAAY,aAAa,CAAC;GAC7C,oBAAoB,QAAQ,YAAY,sBAAsB,CAAC;GAC/D,QAAQ,QAAQ,YAAY,UAAU,CAAC;GACvC,YAAY,QAAQ,YAAY;GAChC,YAAY,QAAQ,YAAY;GAChC,GAAI,iBAAiB,OAAO,IAAI,EAAE,QAAQ,iBAAiB,OAAO,EAAE,IAAI,CAAC;EAC3E;CACF;AACF;AAEA,SAAS,kBAAkB,SAAmC;CAC5D,IAAI,CAAC,QAAQ,QAAQ,CAAC,QAAQ,aAAa,QAAQ;CACnD,IAAI,QAAQ,UAAU,UAKpB,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,OAAO;EACP,SAAS,EACP,MACE,iBAAiB,QAAQ,YAAY,OAAO,+GAEhD;CACF;CAIF,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,MAAM;EACN,SACE,iBAAiB,QAAQ,YAAY,OAAO;CAEhD;AACF;AAEA,SAAS,sBAAsB,SAAmC;CAChE,IAAI,CAAC,QAAQ,QAAQ,CAAC,QAAQ,eAAe,CAAC,QAAQ,QAAQ;CAI9D,IAAI,QAAQ,UAAU,QAAQ;CAE9B,IAAI,QAAQ,YAAY,UAAU,UAAU,QAAQ,YAAY,QAAQ;CAIxE,IAAI,QAAQ,OAAO,MAAM,YAAY,MAAM,qBAAqB;CAKhE,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,MAAM;EACN,UAAU;EACV,SACE,2CAA2C,QAAQ,YAAY,OAAO,IAAI,QAAQ,OAAO,IAAI;CAGjG;AACF;;;;;;;;;;;AAYA,SAAS,uBAAuB,MAAmC;CACjE,MAAM,YAAY,MACd,MAAM,IAAI,CAAC,CACZ,KAAI,SAAQ,KAAK,KAAK,CAAC,CAAC,CACxB,MAAK,SAAQ,KAAK,SAAS,CAAC;CAC/B,IAAI,CAAC,WAAW,OAAO;CAEvB,MAAM,aAAa,UAChB,WAAW,eAAe,GAAG,CAAC,CAC9B,KAAK,CAAC,CACN,YAAY;CAGf,OAAO,kDAAkD,KAAK,UAAU;AAC1E;AAEA,SAAS,0BAA0B,SAAmC;CACpE,IAAI,CAAC,QAAQ,QAAQ,CAAC,QAAQ,eAAe,CAAC,QAAQ,cAAc;CAIpE,IAAI,QAAQ,UAAU,QAAQ;CAC9B,IAAI,QAAQ,YAAY,UAAU,UAAU,QAAQ,YAAY,QAAQ;CAOxE,IACE,QAAQ,MAAM,SAAS,YACvB,QAAQ,MAAM,mBACd,CAAC,uBAAuB,QAAQ,aAAa,IAAI,GAEjD;CAEF,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,MAAM;EACN,UAAU;EACV,SACE,GAAG,QAAQ,aAAa,UAAU,UAAU,8BAA8B,QAAQ,YAAY,OAAO,IACjG,QAAQ,aAAa,OAAO,QAAQ,YAAY,IAAI;CAE5D;AACF;AAEA,SAAS,kBAAkB,SAAmC;CAC5D,IAAI,CAAC,QAAQ,QAAQ,CAAC,QAAQ,eAAe,QAAQ,YAAY,QAAQ;CACzE,IAAI,QAAQ,UAAU,UAAU;CAGhC,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,OAAO;EACP,SAAS,EACP,MACE,iBAAiB,QAAQ,YAAY,OAAO,sEAEhD;CACF;AACF;AAEA,SAAS,6BAA6B,SAAmC;CAGvE,IAAI,CAAC,QAAQ,QAAQ,QAAQ,UAAU,YAAY,CAAC,QAAQ,eAAe,iBAAiB;CAC5F,IAAI,CAAC,QAAQ,eAAe,QAAQ,YAAY,UAAU,UAAU,QAAQ,YAAY,QAAQ;CAGhG,IAAI,CAAC,mBAAmB,OAAO,GAAG;CAClC,IAAI,QAAQ,MAAM,SAAS,YAAY,QAAQ,MAAM,iBAAiB;CAGtE,IAAI,QAAQ,KAAK,OAAO,WAAW,KAAK,QAAQ,KAAK,OAAO,OAAO,UAAU;CAC7E,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,OAAO;CACT;AACF;AAEA,SAAS,2BAA2B,SAAmC;CACrE,IAAI,CAAC,QAAQ,QAAQ,QAAQ,UAAU,UAAU;CACjD,IAAI,CAAC,QAAQ,eAAe,QAAQ,YAAY,UAAU,UAAU,QAAQ,YAAY,QAAQ;CAMhG,IAAI,QAAQ,KAAK,OAAO,MAAK,UAAS,UAAU,QAAQ,GAAG;CAC3D,MAAM,mBAAmB,QAAQ,KAAK,OAAO,MAAK,UAAS,UAAU,QAAQ;CAC7E,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,OAAO;EAIP,GAAI,mBAAmB,EAAE,SAAS,KAAK,IAAI,CAAC;CAC9C;AACF;AAEA,SAAS,oBAAoB,SAAmC;CAC9D,IAAI,QAAQ,MAAM;CAClB,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,QAAQ;EACR,WAAW,UAAU,QAAQ,MAAM;EACnC,OAAO,GAAG,QAAQ,MAAM,WAAW,IAAI,QAAQ,MAAM;EACrD,KAAK,QAAQ,MAAM;EACnB,OAAO;EACP,UAAU;GACR,eAAe,QAAQ,MAAM;GAC7B,YAAY,QAAQ,MAAM;GAC1B,aAAa,QAAQ,MAAM;GAC3B,iBAAiB,QAAQ,MAAM;GAC/B,gBAAgB,QAAQ,MAAM;GAC9B,gBAAgB,QAAQ,MAAM;GAC9B,eAAe,QAAQ,MAAM;GAC7B,YAAY,QAAQ,MAAM;GAC1B,QAAQ,CAAC,GAAG,QAAQ,MAAM,MAAM;GAChC,GAAI,QAAQ,MAAM,WAAW,EAAE,UAAU,QAAQ,MAAM,SAAS,IAAI,CAAC;GACrE,GAAI,QAAQ,MAAM,UAAU;IAAE,SAAS,QAAQ,MAAM;IAAS,QAAQ,QAAQ,MAAM;GAAQ,IAAI,CAAC;EACnG;CACF;AACF;AAEA,SAAS,kBAAkB,SAAmC;CAC5D,IAAI,CAAC,QAAQ,QAAQ,QAAQ,KAAK,WAAW,gBAAgB;CAC7D,IAAI,QAAQ,UAAU,QAAQ;CAE9B,IAAI,QAAQ,KAAK,OAAO,MAAK,UAAS,UAAU,UAAU,UAAU,UAAU,GAAG;CAEjF,MAAM,YAAY,QAAQ,MAAM;CAChC,IAAI,cAAc,eAAe,cAAc,YAAY;CAC3D,MAAM,WAAW,cAAc;CAC/B,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,OAAO,WAAW,aAAa;EAC/B,SAAS,EACP,MAAM,gBAAgB,QAAQ,MAAM,WAAW,OAAO,WAAW,aAAa,YAAY,gCAAgC,WAAW,aAAa,OAAO,GAC3J;CACF;AACF;AAEA,MAAM,oBAA2C;CAC/C,MAAM;EACJ,QAAQ;GACN,OAAO,EAAE,SAAS,wBAAwB;GAC1C,aAAa,EAAE,SAAS,8BAA8B;EACxD;EACA,UAAU;GACR,OAAO,EAAE,SAAS,aAAa;GAC/B,aAAa,EAAE,SAAS,aAAa;GACrC,QAAQ,EAAE,SAAS,aAAa;EAClC;EACA,SAAS;GACP,OAAO,EAAE,SAAS,cAAc;GAChC,aAAa,EAAE,SAAS,cAAc;GACtC,QAAQ,EAAE,SAAS,cAAc;EACnC;EACA,MAAM,EACJ,OAAO,EAAE,SAAS,cAAc,EAClC;CACF;CACA,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,kBAAkB,EAAE,EAAE;CAClE,OAAO,EAAE,aAAa,EAAE,UAAU,oBAAoB,EAAE;CACxD,QAAQ;EACN,aAAa,EAAE,SAAS,YAAY;EACpC,aAAa,EAAE,SAAS,oBAAoB;EAC5C,aAAa,EAAE,SAAS,YAAY;EACpC,qBAAqB,EAAE,SAAS,oBAAoB;EACpD,oBAAoB,EAAE,SAAS,oBAAoB;EACnD,qBAAqB,EAAE,SAAS,oBAAoB;EACpD,mBAAmB,EAAE,SAAS,kBAAkB;EAChD,oBAAoB,EAAE,SAAS,2BAA2B;EAC1D,2BAA2B,EAAE,SAAS,0BAA0B;EAChE,4BAA4B,EAAE,SAAS,6BAA6B;EACpE,4BAA4B,EAAE,SAAS,sBAAsB;EAC7D,mBAAmB,EAAE,SAAS,kBAAkB;EAChD,mBAAmB,EAAE,SAAS,kBAAkB;CAClD;CACA,QAAQ;EAAE,eAAe,EAAE,SAAS,oBAAoB;EAAG,aAAa,EAAE,SAAS,kBAAkB;CAAE;AACzG;AAEA,SAAS,gBACP,MACA,WACmB;CACnB,MAAM,SAA4B,CAAC;CACnC,MAAM,yBAAS,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,QAAQ,CAAC,CAAC,GAAG,GAAG,OAAO,KAAK,aAAa,CAAC,CAAC,CAAC,CAAC;CACpF,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,cAAc,OAAO;EAC3B,MAAM,kBAAkB,YAAY;EACpC,MAAM,0BAAU,IAAI,IAAI,CACtB,GAAG,OAAO,KAAK,eAAe,CAAC,CAAC,GAChC,GAAG,OAAO,KAAK,mBAAmB,CAAC,CAAC,CACtC,CAAC;EACD,MAAM,gBAA0E,CAAC;EACjF,KAAK,MAAM,UAAU,SAAS;GAC5B,MAAM,WAAW,cAAc;GAC/B,MAAM,eAAe,kBAAkB;GACvC,cAAc,UAAU;IACtB,GAAI,YAAY,aAAa,WAAW,EAAE,SAAS,SAAS,QAAQ,IAAI,CAAC;IACzE,GAAI,YAAY,YAAY,WAAW,EAAE,QAAQ,SAAS,OAAO,IAAI,CAAC;IACtE,GAAI,gBAAgB,aAAa,eAAe,EAAE,SAAS,aAAa,QAAQ,IAAI,CAAC;IACrF,GAAI,gBAAgB,YAAY,eAAe,EAAE,QAAQ,aAAa,OAAO,IAAI,CAAC;GACpF;EACF;EACA,OAAO,SAAS;CAClB;CACA,OAAO;AACT;AAEA,SAAS,eACP,MACA,WACqC;CACrC,MAAM,SAA8C,CAAC;CACrD,KAAK,MAAM,wBAAQ,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,QAAQ,CAAC,CAAC,GAAG,GAAG,OAAO,KAAK,aAAa,CAAC,CAAC,CAAC,CAAC,GAAG;EACzF,MAAM,WAAW,OAAO;EACxB,MAAM,eAAe,YAAY;EACjC,OAAO,QAAQ;GACb,GAAI,YAAY,cAAc,WAAW,EAAE,UAAU,SAAS,SAAS,IAAI,CAAC;GAC5E,GAAI,gBAAgB,cAAc,eAAe,EAAE,UAAU,aAAa,SAAS,IAAI,CAAC;EAC1F;CACF;CACA,OAAO;AACT;AAEA,SAAS,iBACP,MACA,WAC8C;CAC9C,MAAM,SAAyE,CAAC;CAChF,MAAM,yBAAS,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,QAAQ,CAAC,CAAC,GAAG,GAAG,OAAO,KAAK,aAAa,CAAC,CAAC,CAAC,CAAC;CACpF,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,WAAW,OAAO;EACxB,MAAM,eAAe,YAAY;EACjC,OAAO,SAAS;GACd,GAAI,YAAY,aAAa,WAAW,EAAE,SAAS,SAAS,QAAQ,IAAI,CAAC;GACzE,GAAI,gBAAgB,aAAa,eAAe,EAAE,SAAS,aAAa,QAAQ,IAAI,CAAC;EACvF;CACF;CACA,OAAO;AACT;AAEA,SAAS,iBACP,MACA,WAC8C;CAC9C,MAAM,SAAyE,CAAC;CAChF,MAAM,yBAAS,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,QAAQ,CAAC,CAAC,GAAG,GAAG,OAAO,KAAK,aAAa,CAAC,CAAC,CAAC,CAAC;CACpF,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,WAAW,OAAO;EACxB,MAAM,eAAe,YAAY;EACjC,OAAO,SAAS;GACd,GAAI,YAAY,aAAa,WAAW,EAAE,SAAS,SAAS,QAAQ,IAAI,CAAC;GACzE,GAAI,gBAAgB,aAAa,eAAe,EAAE,SAAS,aAAa,QAAQ,IAAI,CAAC;EACvF;CACF;CACA,OAAO;AACT;AAEA,SAAgB,0BACd,MACA,YAAmC,CAAC,GACL;CAC/B,OAAO;EACL,MAAM,gBAAgB,KAAK,MAAM,UAAU,IAAI;EAC/C,QAAQ,gBAAgB,KAAK,QAAQ,UAAU,MAAM;EACrD,OAAO,eAAe,KAAK,OAAO,UAAU,KAAK;EACjD,QAAQ,iBAAiB,KAAK,QAAQ,UAAU,MAAM;EACtD,QAAQ,iBAAiB,KAAK,QAAQ,UAAU,MAAM;CACxD;AACF;AAEA,SAAgB,oBAAoB,OAA6E;CAC/G,IAAI,OAAO,OAAO,YAAY,YAAY,MAAM,QAAQ,KAAK,CAAC,CAAC,WAAW,GACxE,MAAM,IAAI,2BAA2B,mCAAmC;CAG1E,MAAM,QAAsB;EAC1B,SAAS,MAAM,QAAQ,KAAK;EAC5B,GAAG,0BAA0B,mBAAmB,MAAM,SAAS;CACjE;CACA,mBAAmB,KAAK;CACxB,OAAO;AACT;AAEA,SAAgB,sBAAoC;CAClD,OAAO,oBAAoB,EAAE,SAAS,6BAA6B,CAAC;AACtE"}
|
|
1
|
+
{"version":3,"file":"defaults.js","names":[],"sources":["../../src/rules/defaults.ts"],"sourcesContent":["import type {\n FactoryBoardRuleLeaf,\n FactoryBoardRules,\n FactoryGithubRuleLeaf,\n FactoryGithubEventName,\n FactoryGithubRuleContext,\n FactoryLinearEventName,\n FactoryLinearRuleContext,\n FactoryLinearRuleLeaf,\n FactoryRules,\n FactoryRulesOverrides,\n FactoryRuleSource,\n FactoryRuleStage,\n FactoryStageRuleContext,\n FactoryToolResultRuleContext,\n FactoryToolRuleLeaf,\n} from './types.js';\nimport { assertFactoryRules, FactoryRuleValidationError } from './validation.js';\n\nexport const DEFAULT_FACTORY_RULE_VERSION = 'factory-default-v1';\n\nfunction trustedGithubActor(context: Pick<FactoryStageRuleContext, 'actor'>): boolean {\n return context.actor.type === 'github' && context.actor.trusted;\n}\n\nfunction githubActorLogin(context: Pick<FactoryStageRuleContext, 'actor'>): string | undefined {\n return context.actor.type === 'github' ? context.actor.login : undefined;\n}\n\nfunction invokeIssueInvestigation(context: FactoryStageRuleContext) {\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-triage`,\n role: 'triage',\n skillName: 'factory-triage',\n arguments: context.item.url ? `GitHub issue (${context.item.url})` : context.item.title,\n } as const;\n}\n\nfunction retriageGithubIssue(context: FactoryGithubRuleContext) {\n if (!context.item || context.item.source !== 'github-issue' || !context.item.url) return;\n if (context.actor.type === 'github' && context.actor.factoryAuthored) return;\n\n const reason =\n context.event === 'issueEdited'\n ? context.issueChange?.title && context.issueChange.body\n ? 'issue title and body edited'\n : context.issueChange?.title\n ? 'issue title edited'\n : 'issue body edited'\n : context.event === 'issueCommentDeleted'\n ? 'comment deleted'\n : context.event === 'issueCommentEdited'\n ? 'comment edited'\n : 'comment created';\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-triage`,\n role: 'triage',\n skillName: 'factory-triage',\n arguments: `Re-triage GitHub issue (${context.item.url}) after ${reason}.`,\n } as const;\n}\n\nfunction investigateTriagedLinearIssue(context: FactoryStageRuleContext) {\n const identifier = context.item.sourceKey?.startsWith('linear:')\n ? context.item.sourceKey.slice('linear:'.length)\n : context.item.title;\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-triage-linear`,\n role: 'triage',\n skillName: 'factory-triage',\n arguments: `Linear issue ${identifier}${context.item.url ? ` (${context.item.url})` : ''}`,\n } as const;\n}\n\nfunction planWorkItem(context: FactoryStageRuleContext) {\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-plan`,\n role: 'plan',\n skillName: 'factory-plan',\n arguments: context.item.url ? `Work item (${context.item.url})` : context.item.title,\n } as const;\n}\n\n// A GitHub login is alphanumeric with interior hyphens — no underscores, no\n// spaces. Checking the grammar rejects the placeholder the issue poller stamps\n// when the reporter's account is gone (`__unknown__`), which would otherwise\n// become a trailer crediting an account that does not exist.\nconst GITHUB_LOGIN = /^[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38}$/;\n\n/**\n * The reporter earns a `Co-Authored-By` trailer on the work their report caused.\n * Only a GitHub issue qualifies: Linear stamps a display name and a manual card\n * stamps nothing, and neither resolves to the GitHub identity a trailer needs.\n * Factory's own reports are skipped — crediting ourselves is noise.\n */\nfunction reporterCoAuthor(context: FactoryStageRuleContext) {\n if (context.source !== 'issue') return undefined;\n const author = context.item.metadata?.author;\n if (typeof author !== 'string' || !author) return undefined;\n if (author.endsWith('[bot]') || !GITHUB_LOGIN.test(author)) return undefined;\n return author;\n}\n\n/**\n * Building carries a prompt rather than a skill. The approved plan is already\n * the specification, so there is nothing for a skill document to add, and the\n * handoff a skill would define is unnecessary here: Building ends by opening a\n * pull request, which arrives as its own event and raises the Review card.\n */\nfunction buildWorkItem(context: FactoryStageRuleContext) {\n const subject = context.item.url ? `the approved plan for ${context.item.url}` : 'the approved plan';\n const reporter = reporterCoAuthor(context);\n // The trailer needs the reporter's numeric id, which intake does not stamp, so\n // the agent resolves it from the same issue it is already reading.\n const credit = reporter\n ? ` The work was reported by @${reporter}: credit them on every commit with a ` +\n `\\`Co-Authored-By: ${reporter} <ID+${reporter}@users.noreply.github.com>\\` trailer, ` +\n `resolving ID with \\`gh api users/${reporter} --jq .id\\`.`\n : '';\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:build`,\n role: 'work',\n prompt: `Implement ${subject}. Open a pull request when the work is ready for review.${credit}`,\n } as const;\n}\n\nfunction completeIssue(context: FactoryStageRuleContext) {\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-complete-issue`,\n role: 'triage',\n skillName: 'factory-complete-issue',\n arguments: context.item.url ? `GitHub issue (${context.item.url})` : context.item.title,\n } as const;\n}\n\nfunction reviewPullRequest(context: FactoryStageRuleContext) {\n // A re-entry into Review (from any post-intake stage) supersedes whichever\n // review pass previously ran on this card: cancel any in-flight run before\n // dispatching a fresh one so we don't burn tokens on the stale pass and race\n // two agents on the same card. Cancellation is safe when nothing is in flight.\n const supersedes = context.fromStage !== 'intake';\n // The re-review skill only applies when a prior review pass actually completed\n // (the card is returning from `done`). A cancelled first-time review that\n // re-enters Review from `review` itself still has no prior pass to reconcile —\n // it gets the regular factory-review skill.\n const priorReviewCompleted = context.fromStage === 'done';\n const skillName = priorReviewCompleted ? 'factory-rereview' : 'factory-review';\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:${skillName}`,\n role: 'review',\n skillName,\n arguments: context.item.url ? `GitHub pull request (${context.item.url})` : context.item.title,\n ...(supersedes ? { cancelInFlight: true } : {}),\n } as const;\n}\n\n// Fires only on webhook materialization, so an item filed by hand or re-synced\n// from source never suggests its own run.\nfunction onArrival<Effect>(rule: (context: FactoryStageRuleContext) => Effect) {\n return (context: FactoryStageRuleContext): Effect | undefined => {\n if (context.cause !== 'linked_item_materialized') return;\n if (context.item.metadata?.autoStartCandidate !== true) return;\n return rule(context);\n };\n}\n\nfunction resultContent(value: unknown): string | undefined {\n if (typeof value === 'string') return value;\n if (!value || typeof value !== 'object' || Array.isArray(value)) return;\n const content = (value as { content?: unknown }).content;\n return typeof content === 'string' ? content : undefined;\n}\n\n// Interactive-session path only: factory-plan never calls submit_plan — it\n// advances planning → execute via factory_transition_work_item directly.\nfunction advanceApprovedPlan(context: FactoryToolResultRuleContext) {\n if (\n context.result.status !== 'success' ||\n context.board !== 'work' ||\n context.item.stages.length !== 1 ||\n context.item.stages[0] !== 'planning' ||\n context.actor.type !== 'agent' ||\n context.actor.role !== 'plan' ||\n !resultContent(context.result.value)?.startsWith('Plan approved.')\n ) {\n return;\n }\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:approved-plan`,\n board: 'work',\n stage: 'execute',\n } as const;\n}\n\nfunction createdAfterFactory(createdAt: string | undefined, factoryCreatedAt: string): boolean {\n if (!createdAt) return false;\n const sourceCreatedAt = Date.parse(createdAt);\n const projectCreatedAt = Date.parse(factoryCreatedAt);\n return Number.isFinite(sourceCreatedAt) && Number.isFinite(projectCreatedAt) && sourceCreatedAt > projectCreatedAt;\n}\n\nfunction issueOpened(context: FactoryGithubRuleContext) {\n if (!context.issue) return;\n // Everything arrives in Intake; arrival only stamps whether `onArrival` may\n // suggest this card's run without a person.\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:issue-intake`,\n board: 'work',\n source: 'github-issue',\n sourceKey: `github-issue:${context.issue.number}`,\n title: context.issue.title,\n url: context.issue.url,\n stage: 'intake',\n metadata: {\n githubRepositoryId: context.repository.id,\n githubIssueNumber: context.issue.number,\n ...(githubActorLogin(context) ? { author: githubActorLogin(context) } : {}),\n authorTrusted: trustedGithubActor(context),\n autoStartCandidate:\n trustedGithubActor(context) && createdAfterFactory(context.issue.createdAt, context.factory.createdAt),\n assignees: context.issue.assignees ?? [],\n labels: context.issue.labels ?? [],\n },\n } as const;\n}\n\nfunction issueClosed(context: FactoryGithubRuleContext) {\n if (!context.item || context.item.source !== 'github-issue' || !context.issue) return;\n if (context.board !== 'work') return;\n // Already off the board: nothing to reconcile.\n if (context.item.stages.some(stage => stage === 'done' || stage === 'canceled')) return;\n // Issue closure is a repository fact, not third-party input — no actor trust\n // gate. `not_planned` (and `duplicate`) means abandoned, everything else is\n // completed work.\n const canceled = context.issue.stateReason === 'not_planned' || context.issue.stateReason === 'duplicate';\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:issue-closed`,\n board: 'work',\n stage: canceled ? 'canceled' : 'done',\n message: {\n text:\n `GitHub issue #${context.issue.number} was closed` +\n `${context.issue.stateReason ? ` (${context.issue.stateReason})` : ''}; ` +\n `this Work card was moved to ${canceled ? 'Canceled' : 'Done'}.`,\n },\n } as const;\n}\n\nfunction pullRequestOpened(context: FactoryGithubRuleContext) {\n if (!context.pullRequest) return;\n // A GitHub App bot is never a collaborator, so Factory's own PRs score\n // untrusted; their authorship is the trust signal.\n const factoryAuthored = context.actor.type === 'github' && context.actor.factoryAuthored;\n const autoStartCandidate =\n (trustedGithubActor(context) || factoryAuthored) &&\n createdAfterFactory(context.pullRequest.createdAt, context.factory.createdAt);\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:pull-request-intake`,\n board: 'review',\n source: 'github-pr',\n sourceKey: `github-pr:${context.pullRequest.number}`,\n title: context.pullRequest.title,\n url: context.pullRequest.url,\n stage: 'intake',\n metadata: {\n githubRepositoryId: context.repository.id,\n githubPullRequestNumber: context.pullRequest.number,\n factoryAuthored,\n authorTrusted: trustedGithubActor(context),\n autoStartCandidate,\n state: context.pullRequest.state,\n draft: context.pullRequest.draft,\n merged: context.pullRequest.merged,\n assignees: context.pullRequest.assignees ?? [],\n requestedReviewers: context.pullRequest.requestedReviewers ?? [],\n labels: context.pullRequest.labels ?? [],\n headBranch: context.pullRequest.headBranch,\n baseBranch: context.pullRequest.baseBranch,\n ...(githubActorLogin(context) ? { author: githubActorLogin(context) } : {}),\n },\n } as const;\n}\n\nfunction pullRequestMerged(context: FactoryGithubRuleContext) {\n if (!context.item || !context.pullRequest?.merged) return;\n if (context.board === 'review') {\n // The event is bound to the PR's own Review card: a merged PR is finished\n // review work, so always move the card to Done. The message only reaches\n // an active session (if any) — cards without one just move, instead of\n // failing retries against a binding that never existed.\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:pull-request-merged`,\n board: 'review',\n stage: 'done',\n message: {\n text:\n `Pull request #${context.pullRequest.number} merged; this Review card was moved to Done. ` +\n 'No further review is needed unless follow-up work was requested.',\n },\n } as const;\n }\n // Provenance bound the event to the originating Work item instead: remind\n // its agent to assess completion — never auto-complete the Work item.\n return {\n type: 'sendMessage',\n idempotencyKey: `${context.ingress.id}:assess-work-completion`,\n role: 'work',\n message:\n `Pull request #${context.pullRequest.number} merged. Assess whether the linked Work item is complete. ` +\n 'Do not mark it Done solely because this PR merged; use factory_transition_work_item only after verifying the work.',\n } as const;\n}\n\nfunction addressReviewFeedback(context: FactoryGithubRuleContext) {\n if (!context.item || !context.pullRequest || !context.review) return;\n // Only the Work item that authored the PR can act on the feedback. Provenance\n // binds the event there; a Review card seeing its own posted review must not\n // react to it (that would loop the reviewer against itself).\n if (context.board !== 'work') return;\n // A closed or merged pull request has no branch left to push fixes to.\n if (context.pullRequest.state !== 'open' || context.pullRequest.merged) return;\n // `approved` needs no work, and `commented` (a review body with no verdict)\n // is how a reviewer leaves notes without blocking — only a verdict that asks\n // for changes should pull the author back in.\n if (context.review.state.toLowerCase() !== 'changes_requested') return;\n // The authoring thread is already subscribed to this PR (`gh pr create`\n // subscribes automatically), so it can read the individual line comments\n // from its own notification inbox — the message only has to wake it and\n // point at the review.\n return {\n type: 'sendMessage',\n idempotencyKey: `${context.ingress.id}:address-review-feedback`,\n role: 'work',\n priority: 'high',\n message:\n `Changes were requested on pull request #${context.pullRequest.number} (${context.review.url}). ` +\n 'Read the review comments on this PR, address the ones you agree with, and push the fixes to the PR branch. ' +\n 'Reply on GitHub to anything you are deliberately not changing, explaining why.',\n } as const;\n}\n\n/**\n * Detects the `factory-review` handoff verdict in a comment body.\n *\n * GitHub forbids an app from reviewing a pull request it authored, so on\n * Factory-authored PRs the review skill falls back to posting its verdict as a\n * plain comment. That comment is the only signal the authoring agent gets, so\n * it has to be readable back out. The skill's handoff contract puts the verdict\n * on the first line (`Verdict: request changes`), so only that line is\n * inspected — a verdict quoted later in the findings must not count.\n */\nfunction requestsChangesVerdict(body: string | undefined): boolean {\n const firstLine = body\n ?.split('\\n')\n .map(line => line.trim())\n .find(line => line.length > 0);\n if (!firstLine) return false;\n // Tolerate the markdown the skill wraps the line in (`**Verdict: ...**`).\n const normalized = firstLine\n .replaceAll(/[*_`#>\\s]+/g, ' ')\n .trim()\n .toLowerCase();\n // Match the verdict exactly so negated phrasings (\"Verdict: do not request\n // changes\") cannot wake the author.\n return /^verdict: ?(request changes|changes requested)$/.test(normalized);\n}\n\nfunction addressPullRequestComment(context: FactoryGithubRuleContext) {\n if (!context.item || !context.pullRequest || !context.issueComment) return;\n // Provenance binds the comment to the Work item that authored the PR — the\n // only session that can act on it. A Review card must not react to comments\n // on the PR it is reviewing.\n if (context.board !== 'work') return;\n if (context.pullRequest.state !== 'open' || context.pullRequest.merged) return;\n // `factoryAuthored` is one bit for the whole Factory, so Factory's own\n // comments are indistinguishable between roles — waking on all of them would\n // let the Work agent's own progress comments wake itself in a loop. The one\n // exception is the review verdict the Review run had to post as a comment\n // because GitHub refused a self-review: that is the handoff, and it only ever\n // asks for changes once per review, so it cannot sustain a loop.\n if (\n context.actor.type === 'github' &&\n context.actor.factoryAuthored &&\n !requestsChangesVerdict(context.issueComment.body)\n ) {\n return;\n }\n return {\n type: 'sendMessage',\n idempotencyKey: `${context.ingress.id}:address-pull-request-comment`,\n role: 'work',\n priority: 'high',\n message:\n `${context.issueComment.author ?? 'Someone'} commented on pull request #${context.pullRequest.number} ` +\n `(${context.issueComment.url ?? context.pullRequest.url}). Read the comment, address it if you agree, and push ` +\n 'the fixes to the PR branch. Reply on GitHub to anything you are deliberately not changing, explaining why.',\n } as const;\n}\n\nfunction pullRequestClosed(context: FactoryGithubRuleContext) {\n if (!context.item || !context.pullRequest || context.pullRequest.merged) return;\n if (context.board !== 'review') return;\n // A PR closed without merging is abandoned review work: clear the card off\n // the board instead of leaving it in Reviewing forever.\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:pull-request-closed`,\n board: 'review',\n stage: 'canceled',\n message: {\n text:\n `Pull request #${context.pullRequest.number} was closed without merging; ` +\n 'this Review card was moved to Canceled.',\n },\n } as const;\n}\n\nfunction reReviewRequestedPullRequest(context: FactoryGithubRuleContext) {\n // Only a review re-requested *from Factory's own bot* restarts the review —\n // requesting a human reviewer is not Factory's signal.\n if (!context.item || context.board !== 'review' || !context.reviewRequest?.factoryReviewer) return;\n if (!context.pullRequest || context.pullRequest.state !== 'open' || context.pullRequest.merged) return;\n // Trusted (write/admin) requesters only: re-entering review checks out and\n // executes PR code, the same bar pullRequestOpened applies to auto-review.\n if (!trustedGithubActor(context)) return;\n if (context.actor.type === 'github' && context.actor.factoryAuthored) return;\n // Already in Reviewing: a review pass is pending or running; re-entering\n // would be a same-stage no-op anyway (stage rules only fire on change).\n if (context.item.stages.length === 1 && context.item.stages[0] === 'review') return;\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:re-review-requested`,\n board: 'review',\n stage: 'review',\n } as const;\n}\n\nfunction reReviewUpdatedPullRequest(context: FactoryGithubRuleContext) {\n if (!context.item || context.board !== 'review') return;\n if (!context.pullRequest || context.pullRequest.state !== 'open' || context.pullRequest.merged) return;\n // Intake has not started a review pass yet, so a push there is just more of\n // the code the first pass will read. A push to a card sitting in Reviewing is\n // different: it invalidates whatever that pass is reading, so re-enter the\n // stage to supersede it. `reviewPullRequest` cancels the stale run and picks\n // the right skill for the entry it sees.\n if (context.item.stages.some(stage => stage === 'intake')) return;\n const alreadyReviewing = context.item.stages.some(stage => stage === 'review');\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:re-review-updated`,\n board: 'review',\n stage: 'review',\n // Re-entry is the point when the card is already Reviewing: the stage's\n // entry rule is what cancels the superseded pass and starts one on the code\n // that just landed.\n ...(alreadyReviewing ? { reenter: true } : {}),\n } as const;\n}\n\nfunction linearIssueObserved(context: FactoryLinearRuleContext) {\n if (context.item) return;\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:issue-triage`,\n board: 'work',\n source: 'linear-issue',\n sourceKey: `linear:${context.issue.identifier}`,\n title: `${context.issue.identifier}: ${context.issue.title}`,\n url: context.issue.url,\n stage: 'triage',\n metadata: {\n linearIssueId: context.issue.id,\n identifier: context.issue.identifier,\n linearState: context.issue.state,\n linearStateType: context.issue.stateType,\n linearPriority: context.issue.priorityLabel,\n linearAssignee: context.issue.assignee,\n linearCreator: context.issue.creator,\n linearTeam: context.issue.team,\n labels: [...context.issue.labels] as string[],\n ...(context.issue.assignee ? { assignee: context.issue.assignee } : {}),\n ...(context.issue.creator ? { creator: context.issue.creator, author: context.issue.creator } : {}),\n },\n } as const;\n}\n\nfunction linearIssueClosed(context: FactoryLinearRuleContext) {\n if (!context.item || context.item.source !== 'linear-issue') return;\n if (context.board !== 'work') return;\n // Already off the board: nothing to reconcile.\n if (context.item.stages.some(stage => stage === 'done' || stage === 'canceled')) return;\n // Only terminal state types trigger close.\n const stateType = context.issue.stateType;\n if (stateType !== 'completed' && stateType !== 'canceled') return;\n const canceled = stateType === 'canceled';\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:issue-closed`,\n board: 'work',\n stage: canceled ? 'canceled' : 'done',\n message: {\n text: `Linear issue ${context.issue.identifier} was ${canceled ? 'canceled' : 'completed'}; this Work card was moved to ${canceled ? 'Canceled' : 'Done'}.`,\n },\n } as const;\n}\n\nconst BUILT_IN_DEFAULTS: FactoryRulesOverrides = {\n work: {\n intake: { issue: { onEnter: onArrival(invokeIssueInvestigation) } },\n triage: {\n issue: { onEnter: invokeIssueInvestigation },\n linearIssue: { onEnter: investigateTriagedLinearIssue },\n },\n planning: {\n issue: { onEnter: planWorkItem },\n linearIssue: { onEnter: planWorkItem },\n manual: { onEnter: planWorkItem },\n },\n execute: {\n issue: { onEnter: buildWorkItem },\n linearIssue: { onEnter: buildWorkItem },\n manual: { onEnter: buildWorkItem },\n },\n done: {\n issue: { onEnter: completeIssue },\n },\n },\n review: {\n intake: { pullRequest: { onEnter: onArrival(reviewPullRequest) } },\n review: { pullRequest: { onEnter: reviewPullRequest } },\n },\n tools: { submit_plan: { onResult: advanceApprovedPlan } },\n github: {\n issueOpened: { onEvent: issueOpened },\n issueEdited: { onEvent: retriageGithubIssue },\n issueClosed: { onEvent: issueClosed },\n issueCommentCreated: { onEvent: retriageGithubIssue },\n issueCommentEdited: { onEvent: retriageGithubIssue },\n issueCommentDeleted: { onEvent: retriageGithubIssue },\n pullRequestOpened: { onEvent: pullRequestOpened },\n pullRequestUpdated: { onEvent: reReviewUpdatedPullRequest },\n pullRequestCommentCreated: { onEvent: addressPullRequestComment },\n pullRequestReviewRequested: { onEvent: reReviewRequestedPullRequest },\n pullRequestReviewSubmitted: { onEvent: addressReviewFeedback },\n pullRequestMerged: { onEvent: pullRequestMerged },\n pullRequestClosed: { onEvent: pullRequestClosed },\n },\n linear: { issueObserved: { onEvent: linearIssueObserved }, issueClosed: { onEvent: linearIssueClosed } },\n};\n\nfunction mergeBoardRules(\n base: FactoryBoardRules | undefined,\n overrides: FactoryBoardRules | undefined,\n): FactoryBoardRules {\n const result: FactoryBoardRules = {};\n const stages = new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]) as Set<FactoryRuleStage>;\n for (const stage of stages) {\n const baseSources = base?.[stage];\n const overrideSources = overrides?.[stage];\n const sources = new Set([\n ...Object.keys(baseSources ?? {}),\n ...Object.keys(overrideSources ?? {}),\n ]) as Set<FactoryRuleSource>;\n const mergedSources: Partial<Record<FactoryRuleSource, FactoryBoardRuleLeaf>> = {};\n for (const source of sources) {\n const baseLeaf = baseSources?.[source];\n const overrideLeaf = overrideSources?.[source];\n mergedSources[source] = {\n ...(baseLeaf && 'onEnter' in baseLeaf ? { onEnter: baseLeaf.onEnter } : {}),\n ...(baseLeaf && 'onExit' in baseLeaf ? { onExit: baseLeaf.onExit } : {}),\n ...(overrideLeaf && 'onEnter' in overrideLeaf ? { onEnter: overrideLeaf.onEnter } : {}),\n ...(overrideLeaf && 'onExit' in overrideLeaf ? { onExit: overrideLeaf.onExit } : {}),\n };\n }\n result[stage] = mergedSources;\n }\n return result;\n}\n\nfunction mergeToolRules(\n base: Record<string, FactoryToolRuleLeaf> | undefined,\n overrides: Record<string, FactoryToolRuleLeaf> | undefined,\n): Record<string, FactoryToolRuleLeaf> {\n const result: Record<string, FactoryToolRuleLeaf> = {};\n for (const name of new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})])) {\n const baseLeaf = base?.[name];\n const overrideLeaf = overrides?.[name];\n result[name] = {\n ...(baseLeaf && 'onResult' in baseLeaf ? { onResult: baseLeaf.onResult } : {}),\n ...(overrideLeaf && 'onResult' in overrideLeaf ? { onResult: overrideLeaf.onResult } : {}),\n };\n }\n return result;\n}\n\nfunction mergeGithubRules(\n base: FactoryRulesOverrides['github'],\n overrides: FactoryRulesOverrides['github'],\n): NonNullable<FactoryRulesOverrides['github']> {\n const result: Partial<Record<FactoryGithubEventName, FactoryGithubRuleLeaf>> = {};\n const events = new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]) as Set<FactoryGithubEventName>;\n for (const event of events) {\n const baseLeaf = base?.[event];\n const overrideLeaf = overrides?.[event];\n result[event] = {\n ...(baseLeaf && 'onEvent' in baseLeaf ? { onEvent: baseLeaf.onEvent } : {}),\n ...(overrideLeaf && 'onEvent' in overrideLeaf ? { onEvent: overrideLeaf.onEvent } : {}),\n };\n }\n return result;\n}\n\nfunction mergeLinearRules(\n base: FactoryRulesOverrides['linear'],\n overrides: FactoryRulesOverrides['linear'],\n): NonNullable<FactoryRulesOverrides['linear']> {\n const result: Partial<Record<FactoryLinearEventName, FactoryLinearRuleLeaf>> = {};\n const events = new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]) as Set<FactoryLinearEventName>;\n for (const event of events) {\n const baseLeaf = base?.[event];\n const overrideLeaf = overrides?.[event];\n result[event] = {\n ...(baseLeaf && 'onEvent' in baseLeaf ? { onEvent: baseLeaf.onEvent } : {}),\n ...(overrideLeaf && 'onEvent' in overrideLeaf ? { onEvent: overrideLeaf.onEvent } : {}),\n };\n }\n return result;\n}\n\nexport function mergeFactoryRuleOverrides(\n base: FactoryRulesOverrides,\n overrides: FactoryRulesOverrides = {},\n): Omit<FactoryRules, 'version'> {\n return {\n work: mergeBoardRules(base.work, overrides.work),\n review: mergeBoardRules(base.review, overrides.review),\n tools: mergeToolRules(base.tools, overrides.tools),\n github: mergeGithubRules(base.github, overrides.github),\n linear: mergeLinearRules(base.linear, overrides.linear),\n };\n}\n\nexport function defaultFactoryRules(input: { version: string; overrides?: FactoryRulesOverrides }): FactoryRules {\n if (typeof input?.version !== 'string' || input.version.trim().length === 0) {\n throw new FactoryRuleValidationError('Factory rule version is required.');\n }\n\n const rules: FactoryRules = {\n version: input.version.trim(),\n ...mergeFactoryRuleOverrides(BUILT_IN_DEFAULTS, input.overrides),\n };\n assertFactoryRules(rules);\n return rules;\n}\n\nexport function builtInFactoryRules(): FactoryRules {\n return defaultFactoryRules({ version: DEFAULT_FACTORY_RULE_VERSION });\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch-errors.d.ts","sourceRoot":"","sources":["../../src/rules/dispatch-errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AAExF,UAAU,8BAA8B;IACtC,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;
|
|
1
|
+
{"version":3,"file":"dispatch-errors.d.ts","sourceRoot":"","sources":["../../src/rules/dispatch-errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AAExF,UAAU,8BAA8B;IACtC,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAqBD,qBAAa,oBAAqB,SAAQ,KAAK;IAE3C,QAAQ,CAAC,IAAI,EAAE,0BAA0B;gBAAhC,IAAI,EAAE,0BAA0B,EACzC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,YAAY;CAKzB;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,0BAA0B,CAErF;AAED,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,0BAA0B,GAAG,IAAI,GACtC,8BAA8B,CAEhC"}
|
|
@@ -20,6 +20,14 @@ const FAILURE_METADATA = {
|
|
|
20
20
|
canRetry: true,
|
|
21
21
|
label: "Factory message delivery failed"
|
|
22
22
|
},
|
|
23
|
+
plan_awaiting_approval: {
|
|
24
|
+
canRetry: false,
|
|
25
|
+
label: "Plan waiting for review"
|
|
26
|
+
},
|
|
27
|
+
run_awaiting_input: {
|
|
28
|
+
canRetry: false,
|
|
29
|
+
label: "Agent is waiting for an answer"
|
|
30
|
+
},
|
|
23
31
|
repository_git_missing: {
|
|
24
32
|
canRetry: false,
|
|
25
33
|
label: "Git is unavailable in the workspace"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch-errors.js","names":[],"sources":["../../src/rules/dispatch-errors.ts"],"sourcesContent":["import type { FactoryDispatchFailureCode } from '../storage/domains/work-items/base.js';\n\ninterface FactoryDispatchFailureMetadata {\n canRetry: boolean;\n label: string;\n}\n\nconst FAILURE_METADATA = {\n session_unavailable: { canRetry: true, label: 'Factory session unavailable' },\n source_control_missing: { canRetry: true, label: 'Source-control connection unavailable' },\n source_repository_missing: { canRetry: true, label: 'Source repository unavailable' },\n unsupported_provider_item: { canRetry: false, label: 'Unsupported provider work item' },\n notification_delivery_failed: { canRetry: true, label: 'Factory message delivery failed' },\n repository_git_missing: { canRetry: false, label: 'Git is unavailable in the workspace' },\n repository_egress_blocked: { canRetry: false, label: 'Repository network access is blocked' },\n repository_clone_failed: { canRetry: true, label: 'Repository clone failed' },\n repository_pull_failed: { canRetry: true, label: 'Repository update failed' },\n repository_push_failed: { canRetry: true, label: 'Repository push failed' },\n repository_commit_failed: { canRetry: true, label: 'Repository commit failed' },\n repository_cli_missing: { canRetry: false, label: 'GitHub CLI is unavailable in the workspace' },\n repository_pr_failed: { canRetry: true, label: 'Pull request creation failed' },\n unknown: { canRetry: true, label: 'Factory automation failed' },\n} satisfies Record<FactoryDispatchFailureCode, FactoryDispatchFailureMetadata>;\n\nexport class FactoryDispatchError extends Error {\n constructor(\n readonly code: FactoryDispatchFailureCode,\n message: string,\n options?: ErrorOptions,\n ) {\n super(message, options);\n this.name = 'FactoryDispatchError';\n }\n}\n\nexport function factoryDispatchFailureCode(error: unknown): FactoryDispatchFailureCode {\n return error instanceof FactoryDispatchError ? error.code : 'unknown';\n}\n\nexport function factoryDispatchFailureMetadata(\n code: FactoryDispatchFailureCode | null,\n): FactoryDispatchFailureMetadata {\n return code === null ? FAILURE_METADATA.unknown : FAILURE_METADATA[code];\n}\n"],"mappings":";AAOA,MAAM,mBAAmB;CACvB,qBAAqB;EAAE,UAAU;EAAM,OAAO;CAA8B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAAwC;CACzF,2BAA2B;EAAE,UAAU;EAAM,OAAO;CAAgC;CACpF,2BAA2B;EAAE,UAAU;EAAO,OAAO;CAAiC;CACtF,8BAA8B;EAAE,UAAU;EAAM,OAAO;CAAkC;CACzF,wBAAwB;EAAE,UAAU;EAAO,OAAO;CAAsC;CACxF,2BAA2B;EAAE,UAAU;EAAO,OAAO;CAAuC;CAC5F,yBAAyB;EAAE,UAAU;EAAM,OAAO;CAA0B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAA2B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAAyB;CAC1E,0BAA0B;EAAE,UAAU;EAAM,OAAO;CAA2B;CAC9E,wBAAwB;EAAE,UAAU;EAAO,OAAO;CAA6C;CAC/F,sBAAsB;EAAE,UAAU;EAAM,OAAO;CAA+B;CAC9E,SAAS;EAAE,UAAU;EAAM,OAAO;CAA4B;AAChE;AAEA,IAAa,uBAAb,cAA0C,MAAM;CAEnC;CADX,YACE,MACA,SACA,SACA;EACA,MAAM,SAAS,OAAO;EAJb,KAAA,OAAA;EAKT,KAAK,OAAO;CACd;AACF;AAEA,SAAgB,2BAA2B,OAA4C;CACrF,OAAO,iBAAiB,uBAAuB,MAAM,OAAO;AAC9D;AAEA,SAAgB,+BACd,MACgC;CAChC,OAAO,SAAS,OAAO,iBAAiB,UAAU,iBAAiB;AACrE"}
|
|
1
|
+
{"version":3,"file":"dispatch-errors.js","names":[],"sources":["../../src/rules/dispatch-errors.ts"],"sourcesContent":["import type { FactoryDispatchFailureCode } from '../storage/domains/work-items/base.js';\n\ninterface FactoryDispatchFailureMetadata {\n canRetry: boolean;\n label: string;\n}\n\nconst FAILURE_METADATA = {\n session_unavailable: { canRetry: true, label: 'Factory session unavailable' },\n source_control_missing: { canRetry: true, label: 'Source-control connection unavailable' },\n source_repository_missing: { canRetry: true, label: 'Source repository unavailable' },\n unsupported_provider_item: { canRetry: false, label: 'Unsupported provider work item' },\n notification_delivery_failed: { canRetry: true, label: 'Factory message delivery failed' },\n plan_awaiting_approval: { canRetry: false, label: 'Plan waiting for review' },\n run_awaiting_input: { canRetry: false, label: 'Agent is waiting for an answer' },\n repository_git_missing: { canRetry: false, label: 'Git is unavailable in the workspace' },\n repository_egress_blocked: { canRetry: false, label: 'Repository network access is blocked' },\n repository_clone_failed: { canRetry: true, label: 'Repository clone failed' },\n repository_pull_failed: { canRetry: true, label: 'Repository update failed' },\n repository_push_failed: { canRetry: true, label: 'Repository push failed' },\n repository_commit_failed: { canRetry: true, label: 'Repository commit failed' },\n repository_cli_missing: { canRetry: false, label: 'GitHub CLI is unavailable in the workspace' },\n repository_pr_failed: { canRetry: true, label: 'Pull request creation failed' },\n unknown: { canRetry: true, label: 'Factory automation failed' },\n} satisfies Record<FactoryDispatchFailureCode, FactoryDispatchFailureMetadata>;\n\nexport class FactoryDispatchError extends Error {\n constructor(\n readonly code: FactoryDispatchFailureCode,\n message: string,\n options?: ErrorOptions,\n ) {\n super(message, options);\n this.name = 'FactoryDispatchError';\n }\n}\n\nexport function factoryDispatchFailureCode(error: unknown): FactoryDispatchFailureCode {\n return error instanceof FactoryDispatchError ? error.code : 'unknown';\n}\n\nexport function factoryDispatchFailureMetadata(\n code: FactoryDispatchFailureCode | null,\n): FactoryDispatchFailureMetadata {\n return code === null ? FAILURE_METADATA.unknown : FAILURE_METADATA[code];\n}\n"],"mappings":";AAOA,MAAM,mBAAmB;CACvB,qBAAqB;EAAE,UAAU;EAAM,OAAO;CAA8B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAAwC;CACzF,2BAA2B;EAAE,UAAU;EAAM,OAAO;CAAgC;CACpF,2BAA2B;EAAE,UAAU;EAAO,OAAO;CAAiC;CACtF,8BAA8B;EAAE,UAAU;EAAM,OAAO;CAAkC;CACzF,wBAAwB;EAAE,UAAU;EAAO,OAAO;CAA0B;CAC5E,oBAAoB;EAAE,UAAU;EAAO,OAAO;CAAiC;CAC/E,wBAAwB;EAAE,UAAU;EAAO,OAAO;CAAsC;CACxF,2BAA2B;EAAE,UAAU;EAAO,OAAO;CAAuC;CAC5F,yBAAyB;EAAE,UAAU;EAAM,OAAO;CAA0B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAA2B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAAyB;CAC1E,0BAA0B;EAAE,UAAU;EAAM,OAAO;CAA2B;CAC9E,wBAAwB;EAAE,UAAU;EAAO,OAAO;CAA6C;CAC/F,sBAAsB;EAAE,UAAU;EAAM,OAAO;CAA+B;CAC9E,SAAS;EAAE,UAAU;EAAM,OAAO;CAA4B;AAChE;AAEA,IAAa,uBAAb,cAA0C,MAAM;CAEnC;CADX,YACE,MACA,SACA,SACA;EACA,MAAM,SAAS,OAAO;EAJb,KAAA,OAAA;EAKT,KAAK,OAAO;CACd;AACF;AAEA,SAAgB,2BAA2B,OAA4C;CACrF,OAAO,iBAAiB,uBAAuB,MAAM,OAAO;AAC9D;AAEA,SAAgB,+BACd,MACgC;CAChC,OAAO,SAAS,OAAO,iBAAiB,UAAU,iBAAiB;AACrE"}
|
|
@@ -20,6 +20,11 @@ export interface FactoryDecisionDispatcherOptions {
|
|
|
20
20
|
orgId: string;
|
|
21
21
|
factoryProjectId: string;
|
|
22
22
|
}) => Promise<boolean>;
|
|
23
|
+
/** `true` lets the dispatcher answer a run's plan itself, so started work carries to Done. */
|
|
24
|
+
autoApprovePlans?: (tenant: {
|
|
25
|
+
orgId: string;
|
|
26
|
+
factoryProjectId: string;
|
|
27
|
+
}) => Promise<boolean>;
|
|
23
28
|
reconcileToolResults?: () => Promise<void>;
|
|
24
29
|
prepareBinding?: (input: FactoryBindingPreparationInput) => Promise<void>;
|
|
25
30
|
primeCredentials?: (tenant: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/rules/dispatcher.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAyC,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/rules/dispatcher.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAyC,MAAM,+BAA+B,CAAC;AAO5G,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6CAA6C,CAAC;AACrF,OAAO,KAAK,EACV,6BAA6B,EAI7B,WAAW,EACX,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAG/C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,KAAK,EAAE,qBAAqB,EAA4C,MAAM,YAAY,CAAC;AA4KlG,KAAK,iBAAiB,GAAG,IAAI,CAAC,eAAe,CAAC,eAAe,CAAC,EAAE,sBAAsB,CAAC,CAAC;AAGxF,MAAM,WAAW,8BAA8B;IAC7C,MAAM,EAAE,6BAA6B,CAAC;IACtC,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gCAAgC;IAC/C,UAAU,EAAE,iBAAiB,CAAC;IAC9B,iBAAiB,EAAE,IAAI,CAAC,wBAAwB,EAAE,YAAY,CAAC,CAAC;IAChE,OAAO,EAAE,gBAAgB,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,gBAAgB,EAAE,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5F,8FAA8F;IAC9F,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7F,oBAAoB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,8BAA8B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChF,8EAA8E;IAC9E,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,6BAA6B,CAAC,EAAE,CAAC,KAAK,EAAE;QACtC,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,OAAO,CAAC,qBAAqB,EAAE;YAAE,IAAI,EAAE,sBAAsB,CAAA;SAAE,CAAC,CAAC;KAC5E,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,sFAAsF;IACtF,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,8EAA8E;IAC9E,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oGAAoG;IACpG,mCAAmC,CAAC,EAAE,MAAM,CAAC;CAC9C;AAyGD,qBAAa,yBAAyB;;gBAwBxB,OAAO,EAAE,gCAAgC;IA0BrD,KAAK,IAAI,IAAI;IAOP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAOrB,OAAO,CAAC,GAAG,OAAa,GAAG,OAAO,CAAC,IAAI,CAAC;CA0sB/C;AAED,eAAO,MAAM,0BAA0B;;;;;;;;;;CAU7B,CAAC"}
|
package/dist/rules/dispatcher.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { FACTORY_RULE_STAGES } from "./types.js";
|
|
1
|
+
import { FACTORY_RULE_STAGES, externallyAuthoredWorkItem, isWorkingFactoryRuleStage } from "./types.js";
|
|
2
2
|
import { validateFactoryRuleDecision } from "./validation.js";
|
|
3
3
|
import { FACTORY_RULE_MATERIALIZATION_KEY } from "../storage/domains/work-items/base.js";
|
|
4
|
-
import { FactoryDispatchError, factoryDispatchFailureCode } from "./dispatch-errors.js";
|
|
4
|
+
import { FactoryDispatchError, factoryDispatchFailureCode, factoryDispatchFailureMetadata } from "./dispatch-errors.js";
|
|
5
5
|
import { withWorkItemFeed } from "../storage/domains/comments/feed-context.js";
|
|
6
6
|
import { resolvePromptInvocation, resolveSkillInvocation } from "../skills/service.js";
|
|
7
7
|
import { randomUUID } from "crypto";
|
|
@@ -11,6 +11,7 @@ const LEASE_MS = 3e4;
|
|
|
11
11
|
const POLL_MS = 1e3;
|
|
12
12
|
const BATCH_SIZE = 10;
|
|
13
13
|
const MAX_ATTEMPTS = 5;
|
|
14
|
+
const MAX_PLAN_APPROVALS = 3;
|
|
14
15
|
const MAX_ERROR_LENGTH = 512;
|
|
15
16
|
const MAX_BACKOFF_MS = 6e4;
|
|
16
17
|
const SKILL_COMPLETION_OBSERVATION_TIMEOUT_MS = 10 * 6e4;
|
|
@@ -18,6 +19,65 @@ const MAX_IN_FLIGHT = 25;
|
|
|
18
19
|
const STALE_BINDING_SWEEP_INTERVAL_MS = 10 * 6e4;
|
|
19
20
|
const STALE_BINDING_TTL_MS = 1440 * 6e4;
|
|
20
21
|
const RECONCILE_INTERVAL_MS = 3e4;
|
|
22
|
+
function isTerminalFailure(attempts, failureCode) {
|
|
23
|
+
return attempts >= MAX_ATTEMPTS || !factoryDispatchFailureMetadata(failureCode).canRetry;
|
|
24
|
+
}
|
|
25
|
+
function watchRun(session, { timeoutMs, approvePlans, onParkedRun, label }) {
|
|
26
|
+
let resolveAgentEnd;
|
|
27
|
+
let agentEnd;
|
|
28
|
+
let endReason;
|
|
29
|
+
let parked;
|
|
30
|
+
const arm = () => {
|
|
31
|
+
endReason = void 0;
|
|
32
|
+
agentEnd = new Promise((resolve) => {
|
|
33
|
+
resolveAgentEnd = resolve;
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
arm();
|
|
37
|
+
const unsubscribe = session.subscribe((event) => {
|
|
38
|
+
if (event.type === "agent_end") {
|
|
39
|
+
endReason = event.reason;
|
|
40
|
+
resolveAgentEnd();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (event.type === "tool_suspended") {
|
|
44
|
+
parked = {
|
|
45
|
+
toolName: event.toolName,
|
|
46
|
+
toolCallId: event.toolCallId
|
|
47
|
+
};
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (event.type === "tool_suspension_cancelled" && parked?.toolCallId === event.toolCallId) parked = void 0;
|
|
51
|
+
});
|
|
52
|
+
const wait = () => waitForAgentEndOrTimeout(agentEnd, timeoutMs);
|
|
53
|
+
return {
|
|
54
|
+
arm,
|
|
55
|
+
wait,
|
|
56
|
+
close: unsubscribe,
|
|
57
|
+
/** The run's own verdict, thrown as what the dispatcher should record. */
|
|
58
|
+
async settle() {
|
|
59
|
+
let observed = await wait();
|
|
60
|
+
if (approvePlans) for (let approvals = 0; parked?.toolName === "submit_plan" && approvals < MAX_PLAN_APPROVALS; approvals += 1) {
|
|
61
|
+
const { toolCallId } = parked;
|
|
62
|
+
parked = void 0;
|
|
63
|
+
arm();
|
|
64
|
+
await session.respondToToolSuspension({
|
|
65
|
+
resumeData: { action: "approved" },
|
|
66
|
+
toolCallId
|
|
67
|
+
});
|
|
68
|
+
observed = await wait();
|
|
69
|
+
}
|
|
70
|
+
if (parked !== void 0 && (!observed || endReason === "suspended")) {
|
|
71
|
+
if (onParkedRun === "await") return;
|
|
72
|
+
if (parked.toolName === "submit_plan") throw new FactoryDispatchError("plan_awaiting_approval", "Factory run wrote a plan and is waiting for it to be reviewed.");
|
|
73
|
+
throw new FactoryDispatchError("run_awaiting_input", `Factory run is waiting on ${parked.toolName} for an answer.`);
|
|
74
|
+
}
|
|
75
|
+
if (!observed) throw new Error(`${label} terminal event was not observed before timeout.`);
|
|
76
|
+
if (endReason === "error") throw new Error(`${label} ended in error.`);
|
|
77
|
+
if (endReason === "aborted") throw new Error(`${label} was aborted before it finished.`);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
21
81
|
function waitForAgentEndOrTimeout(agentEnd, timeoutMs) {
|
|
22
82
|
return new Promise((resolve) => {
|
|
23
83
|
const timeout = setTimeout(() => resolve(false), timeoutMs);
|
|
@@ -59,6 +119,14 @@ function deferredActor(record) {
|
|
|
59
119
|
id: "factory-rule-dispatcher"
|
|
60
120
|
};
|
|
61
121
|
}
|
|
122
|
+
function externalActor(actor) {
|
|
123
|
+
return actor !== null && actor.type !== "human" && actor.type !== "agent" && actor.type !== "system";
|
|
124
|
+
}
|
|
125
|
+
/** A run start asks for consent; an external event asks before pulling a card back into a working lane. */
|
|
126
|
+
function requestsConsent(record, decision) {
|
|
127
|
+
if (decision.type === "invokeSkill") return true;
|
|
128
|
+
return decision.type === "transition" && isWorkingFactoryRuleStage(decision.stage) && externalActor(record.actor);
|
|
129
|
+
}
|
|
62
130
|
function leaseIdentity(record, ownerId) {
|
|
63
131
|
return {
|
|
64
132
|
id: record.id,
|
|
@@ -94,6 +162,7 @@ var FactoryDecisionDispatcher = class {
|
|
|
94
162
|
#storage;
|
|
95
163
|
#ownerId;
|
|
96
164
|
#isAutoRunEnabled;
|
|
165
|
+
#autoApprovePlans;
|
|
97
166
|
#reconcileToolResults;
|
|
98
167
|
#prepareBinding;
|
|
99
168
|
#primeCredentials;
|
|
@@ -116,6 +185,7 @@ var FactoryDecisionDispatcher = class {
|
|
|
116
185
|
this.#storage = options.storage;
|
|
117
186
|
this.#ownerId = options.ownerId ?? `factory-dispatcher:${randomUUID()}`;
|
|
118
187
|
this.#isAutoRunEnabled = options.isAutoRunEnabled;
|
|
188
|
+
this.#autoApprovePlans = options.autoApprovePlans;
|
|
119
189
|
this.#reconcileToolResults = options.reconcileToolResults;
|
|
120
190
|
this.#prepareBinding = options.prepareBinding;
|
|
121
191
|
this.#primeCredentials = options.primeCredentials;
|
|
@@ -241,14 +311,14 @@ var FactoryDecisionDispatcher = class {
|
|
|
241
311
|
executionCompleted = true;
|
|
242
312
|
if (!await this.#storage.completeDeferredDecision(leaseIdentity(record, this.#ownerId), /* @__PURE__ */ new Date())) throw new Error("Factory decision lease was lost before completion.");
|
|
243
313
|
} catch (error) {
|
|
244
|
-
const
|
|
314
|
+
const failureCode = factoryDispatchFailureCode(error);
|
|
245
315
|
await this.#storage.failDeferredDecision({
|
|
246
316
|
...leaseIdentity(record, this.#ownerId),
|
|
247
317
|
now: /* @__PURE__ */ new Date(),
|
|
248
318
|
availableAt: retryAt(now, record.attempts),
|
|
249
319
|
lastError: sanitizeDispatchError(error),
|
|
250
|
-
failureCode
|
|
251
|
-
terminal,
|
|
320
|
+
failureCode,
|
|
321
|
+
terminal: isTerminalFailure(record.attempts, failureCode),
|
|
252
322
|
advanceDeliveryGeneration: !executionCompleted
|
|
253
323
|
});
|
|
254
324
|
}
|
|
@@ -274,17 +344,18 @@ var FactoryDecisionDispatcher = class {
|
|
|
274
344
|
console.error("Factory proposal supersede failed", sanitizeDispatchError(error));
|
|
275
345
|
}
|
|
276
346
|
}
|
|
277
|
-
/** Starting an agent run spends the project's compute and executes its code — the one effect a human owns. */
|
|
278
347
|
async #needsApproval(record, decision) {
|
|
279
|
-
if (
|
|
280
|
-
|
|
281
|
-
orgId: record.orgId,
|
|
282
|
-
factoryProjectId: record.factoryProjectId
|
|
283
|
-
})) return false;
|
|
284
|
-
return (record.workItemId ? await this.#storage.get({
|
|
348
|
+
if (record.approvedAt !== null || !requestsConsent(record, decision)) return false;
|
|
349
|
+
const item = record.workItemId ? await this.#storage.get({
|
|
285
350
|
orgId: record.orgId,
|
|
286
351
|
id: record.workItemId
|
|
287
|
-
}) : null
|
|
352
|
+
}) : null;
|
|
353
|
+
if (item && externallyAuthoredWorkItem(item)) return true;
|
|
354
|
+
if (item?.autonomyArmedAt != null) return false;
|
|
355
|
+
return !await this.#isAutoRunEnabled({
|
|
356
|
+
orgId: record.orgId,
|
|
357
|
+
factoryProjectId: record.factoryProjectId
|
|
358
|
+
});
|
|
288
359
|
}
|
|
289
360
|
async #executeDecision(record, decision) {
|
|
290
361
|
const nextChain = [...record.causalChain, {
|
|
@@ -352,10 +423,11 @@ var FactoryDecisionDispatcher = class {
|
|
|
352
423
|
return;
|
|
353
424
|
case "invokeSkill": {
|
|
354
425
|
const binding = await this.#requireOrPrepareBinding(record, decision.role);
|
|
355
|
-
const
|
|
426
|
+
const item = record.workItemId ? await this.#storage.get({
|
|
356
427
|
orgId: record.orgId,
|
|
357
428
|
id: record.workItemId
|
|
358
|
-
}) : null
|
|
429
|
+
}) : null;
|
|
430
|
+
const startedBy = item?.sessions[binding.role]?.startedBy;
|
|
359
431
|
if (!startedBy) throw new Error(`Factory binding ${binding.id} has no authenticated session owner.`);
|
|
360
432
|
await this.#primeCredentials?.({
|
|
361
433
|
orgId: record.orgId,
|
|
@@ -398,21 +470,11 @@ var FactoryDecisionDispatcher = class {
|
|
|
398
470
|
ifIdle: { behavior: "persist" },
|
|
399
471
|
requestContext
|
|
400
472
|
}));
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
agentEnd = new Promise((resolve) => {
|
|
407
|
-
resolveAgentEnd = resolve;
|
|
408
|
-
});
|
|
409
|
-
};
|
|
410
|
-
armAgentEnd();
|
|
411
|
-
const unsubscribe = session.subscribe((event) => {
|
|
412
|
-
if (event.type === "agent_end") {
|
|
413
|
-
endReason = event.reason;
|
|
414
|
-
resolveAgentEnd();
|
|
415
|
-
}
|
|
473
|
+
const run = watchRun(session, {
|
|
474
|
+
timeoutMs: this.#skillCompletionObservationTimeoutMs,
|
|
475
|
+
approvePlans: await this.#plansAreAutoApproved(record, item),
|
|
476
|
+
onParkedRun: "escalate",
|
|
477
|
+
label: "Factory skill run"
|
|
416
478
|
});
|
|
417
479
|
const sendKickoff = async () => {
|
|
418
480
|
const settled = await session.sendSignal({
|
|
@@ -431,22 +493,21 @@ var FactoryDecisionDispatcher = class {
|
|
|
431
493
|
let settled = await sendKickoff();
|
|
432
494
|
if (settled.action === "deliver") {
|
|
433
495
|
if (!(await session.thread.listActiveMessages()).some((message) => message.id === deliveryId)) {
|
|
434
|
-
if (!await
|
|
435
|
-
|
|
496
|
+
if (!await run.wait()) throw new Error("Factory skill invocation is waiting on a run that has not ended.");
|
|
497
|
+
run.arm();
|
|
436
498
|
settled = await sendKickoff();
|
|
437
499
|
if (settled.action !== "wake") throw new Error("Factory skill invocation was queued onto an ending run and never reached the agent.");
|
|
438
500
|
}
|
|
439
501
|
}
|
|
440
|
-
|
|
441
|
-
else if (endReason === "error") throw new Error("Factory skill run ended in error.");
|
|
442
|
-
else if (endReason === "aborted") throw new Error("Factory skill run was aborted before it finished.");
|
|
502
|
+
await run.settle();
|
|
443
503
|
} finally {
|
|
444
|
-
|
|
504
|
+
run.close();
|
|
445
505
|
}
|
|
446
506
|
return;
|
|
447
507
|
}
|
|
448
508
|
case "sendMessage": {
|
|
449
|
-
const binding =
|
|
509
|
+
const binding = await this.#messageBinding(record, decision);
|
|
510
|
+
if (!binding) return;
|
|
450
511
|
const startedBy = (record.workItemId ? await this.#storage.get({
|
|
451
512
|
orgId: record.orgId,
|
|
452
513
|
id: record.workItemId
|
|
@@ -601,6 +662,10 @@ var FactoryDecisionDispatcher = class {
|
|
|
601
662
|
if (!binding) throw new FactoryDispatchError("session_unavailable", role ? `No active Factory binding for role ${role}.` : "No active Factory binding.");
|
|
602
663
|
return binding;
|
|
603
664
|
}
|
|
665
|
+
async #messageBinding(record, decision) {
|
|
666
|
+
if (decision.prepareBinding && decision.role !== void 0) return this.#requireOrPrepareBinding(record, decision.role);
|
|
667
|
+
return this.#findBinding(record, decision.role);
|
|
668
|
+
}
|
|
604
669
|
async #requireOrPrepareBinding(record, role) {
|
|
605
670
|
const binding = await this.#findBinding(record, role);
|
|
606
671
|
if (binding) {
|
|
@@ -621,6 +686,14 @@ var FactoryDecisionDispatcher = class {
|
|
|
621
686
|
await this.#switchThread(session, binding);
|
|
622
687
|
return session;
|
|
623
688
|
}
|
|
689
|
+
/** Unset means off: a plan nobody asked us to answer is a plan someone should see. */
|
|
690
|
+
async #plansAreAutoApproved({ orgId, factoryProjectId }, item) {
|
|
691
|
+
if (item?.plansPreapprovedAt) return true;
|
|
692
|
+
return this.#autoApprovePlans ? await this.#autoApprovePlans({
|
|
693
|
+
orgId,
|
|
694
|
+
factoryProjectId
|
|
695
|
+
}) : false;
|
|
696
|
+
}
|
|
624
697
|
async #requireSession(binding) {
|
|
625
698
|
const session = await this.#findSession(binding);
|
|
626
699
|
if (!session) throw new FactoryDispatchError("session_unavailable", "Bound Factory session not found.");
|
|
@@ -657,10 +730,11 @@ var FactoryDecisionDispatcher = class {
|
|
|
657
730
|
if (record.message === null) return;
|
|
658
731
|
const binding = (await this.#storage.listRunBindings(record.orgId, record.factoryProjectId)).find((candidate) => candidate.id === record.bindingId && candidate.status === "active");
|
|
659
732
|
if (!binding) throw new FactoryDispatchError("session_unavailable", "Prepared Factory binding is unavailable or revoked.");
|
|
660
|
-
const
|
|
733
|
+
const item = await this.#storage.get({
|
|
661
734
|
orgId: record.orgId,
|
|
662
735
|
id: binding.workItemId
|
|
663
|
-
})
|
|
736
|
+
});
|
|
737
|
+
const startedBy = item?.sessions[binding.role]?.startedBy;
|
|
664
738
|
if (!startedBy) throw new Error(`Factory binding ${binding.id} has no authenticated session owner.`);
|
|
665
739
|
await this.#primeCredentials?.({
|
|
666
740
|
orgId: record.orgId,
|
|
@@ -672,21 +746,11 @@ var FactoryDecisionDispatcher = class {
|
|
|
672
746
|
organizationId: record.orgId
|
|
673
747
|
});
|
|
674
748
|
const session = await this.#requireSession(binding);
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
agentEnd = new Promise((resolve) => {
|
|
681
|
-
resolveAgentEnd = resolve;
|
|
682
|
-
});
|
|
683
|
-
};
|
|
684
|
-
armAgentEnd();
|
|
685
|
-
const unsubscribe = session.subscribe((event) => {
|
|
686
|
-
if (event.type === "agent_end") {
|
|
687
|
-
endReason = event.reason;
|
|
688
|
-
resolveAgentEnd();
|
|
689
|
-
}
|
|
749
|
+
const run = watchRun(session, {
|
|
750
|
+
timeoutMs: this.#skillCompletionObservationTimeoutMs,
|
|
751
|
+
approvePlans: await this.#plansAreAutoApproved(record, item),
|
|
752
|
+
onParkedRun: "await",
|
|
753
|
+
label: "Factory kickoff run"
|
|
690
754
|
});
|
|
691
755
|
const sendKickoff = (dedupeKey) => awaitNotification(() => session.sendNotificationSignal({
|
|
692
756
|
source: "factory",
|
|
@@ -704,27 +768,26 @@ var FactoryDecisionDispatcher = class {
|
|
|
704
768
|
try {
|
|
705
769
|
let settled = await sendKickoff(`factory-kickoff:${record.kickoffKey}`);
|
|
706
770
|
if (settled?.action === "deliver") {
|
|
707
|
-
if (!await
|
|
708
|
-
|
|
771
|
+
if (!await run.wait()) throw new Error("Factory kickoff is waiting on a run that has not ended.");
|
|
772
|
+
run.arm();
|
|
709
773
|
settled = await sendKickoff(`factory-kickoff:${record.kickoffKey}:retry:${record.attempts}`);
|
|
710
774
|
if (settled?.action !== "wake") throw new Error("Factory kickoff was queued onto an ending run and never reached the agent.");
|
|
711
775
|
}
|
|
712
|
-
|
|
713
|
-
else if (endReason === "error") throw new Error("Factory kickoff run ended in error.");
|
|
714
|
-
else if (endReason === "aborted") throw new Error("Factory kickoff run was aborted before it finished.");
|
|
776
|
+
await run.settle();
|
|
715
777
|
} finally {
|
|
716
|
-
|
|
778
|
+
run.close();
|
|
717
779
|
}
|
|
718
780
|
});
|
|
719
781
|
if (!await this.#storage.completePendingStart(leaseIdentity(record, this.#ownerId), /* @__PURE__ */ new Date())) throw new Error("Factory kickoff lease was lost before completion.");
|
|
720
782
|
} catch (error) {
|
|
783
|
+
const failureCode = factoryDispatchFailureCode(error);
|
|
721
784
|
await this.#storage.failPendingStart({
|
|
722
785
|
...leaseIdentity(record, this.#ownerId),
|
|
723
786
|
now: /* @__PURE__ */ new Date(),
|
|
724
787
|
availableAt: retryAt(now, record.attempts),
|
|
725
788
|
lastError: sanitizeDispatchError(error),
|
|
726
|
-
failureCode
|
|
727
|
-
terminal: record.attempts
|
|
789
|
+
failureCode,
|
|
790
|
+
terminal: isTerminalFailure(record.attempts, failureCode)
|
|
728
791
|
});
|
|
729
792
|
}
|
|
730
793
|
}
|