@vercel/factory 0.0.15 → 0.0.17

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.
Files changed (214) hide show
  1. package/CHANGELOG.md +362 -0
  2. package/README.md +49 -261
  3. package/dist/agent-routes.d.mts +47 -3
  4. package/dist/agent-routes.mjs +28 -1
  5. package/dist/agent-routes.mjs.map +1 -1
  6. package/dist/api-contracts.d.mts +20 -1
  7. package/dist/api-contracts.mjs +2 -1
  8. package/dist/api-contracts.mjs.map +1 -1
  9. package/dist/api.d.mts +45 -2
  10. package/dist/api.mjs +199 -10
  11. package/dist/api.mjs.map +1 -1
  12. package/dist/approval-contracts.d.mts +6 -0
  13. package/dist/blob/index.d.mts +51 -14
  14. package/dist/blob/index.mjs +26 -10
  15. package/dist/blob/index.mjs.map +1 -1
  16. package/dist/budget.d.mts +7 -0
  17. package/dist/budget.mjs +6 -0
  18. package/dist/budget.mjs.map +1 -1
  19. package/dist/build-factory.d.mts +1 -0
  20. package/dist/change-verification/dispatch.d.mts +16 -3
  21. package/dist/change-verification/dispatch.mjs +45 -7
  22. package/dist/change-verification/dispatch.mjs.map +1 -1
  23. package/dist/change-verification/eve-tool.d.mts +2 -1
  24. package/dist/change-verification/eve-tool.mjs +35 -47
  25. package/dist/change-verification/eve-tool.mjs.map +1 -1
  26. package/dist/change-verification/result.mjs +130 -0
  27. package/dist/change-verification/result.mjs.map +1 -0
  28. package/dist/changes/eve-record-change.d.mts +2 -2
  29. package/dist/changes/eve-record-change.mjs +43 -9
  30. package/dist/changes/eve-record-change.mjs.map +1 -1
  31. package/dist/changes.d.mts +4 -3
  32. package/dist/changes.mjs +2 -2
  33. package/dist/changes.mjs.map +1 -1
  34. package/dist/client-events.d.mts +10 -3
  35. package/dist/client-events.mjs +6 -2
  36. package/dist/client-events.mjs.map +1 -1
  37. package/dist/client-stream.mjs +8 -2
  38. package/dist/client-stream.mjs.map +1 -1
  39. package/dist/client-transcript.mjs +5 -1
  40. package/dist/client-transcript.mjs.map +1 -1
  41. package/dist/client.d.mts +121 -12
  42. package/dist/client.mjs +117 -9
  43. package/dist/client.mjs.map +1 -1
  44. package/dist/code-review/contracts.d.mts +1 -0
  45. package/dist/code-review/eve-post-review.d.mts +4 -4
  46. package/dist/code-review/eve-post-review.mjs +29 -17
  47. package/dist/code-review/eve-post-review.mjs.map +1 -1
  48. package/dist/code-review/eve-review-comments.d.mts +13 -2
  49. package/dist/code-review/eve-review-comments.mjs +45 -11
  50. package/dist/code-review/eve-review-comments.mjs.map +1 -1
  51. package/dist/code-review/github-reporter.d.mts +2 -0
  52. package/dist/code-review/github-reporter.mjs +7 -5
  53. package/dist/code-review/github-reporter.mjs.map +1 -1
  54. package/dist/code-review.d.mts +3 -2
  55. package/dist/deepsec/eve-tool.mjs +3 -1
  56. package/dist/deepsec/eve-tool.mjs.map +1 -1
  57. package/dist/dispatch.d.mts +79 -8
  58. package/dist/dispatch.mjs +68 -9
  59. package/dist/dispatch.mjs.map +1 -1
  60. package/dist/eve/index.d.mts +70 -10
  61. package/dist/eve/index.mjs +93 -22
  62. package/dist/eve/index.mjs.map +1 -1
  63. package/dist/eve/invoke.mjs +24 -11
  64. package/dist/eve/invoke.mjs.map +1 -1
  65. package/dist/eve/session-client.d.mts +122 -4
  66. package/dist/eve/session-client.mjs +127 -13
  67. package/dist/eve/session-client.mjs.map +1 -1
  68. package/dist/eve/task-execution.d.mts +380 -0
  69. package/dist/eve/task-execution.mjs +57 -2
  70. package/dist/eve/task-execution.mjs.map +1 -1
  71. package/dist/eve/task-session.d.mts +44 -2
  72. package/dist/eve/task-session.mjs +44 -2
  73. package/dist/eve/task-session.mjs.map +1 -1
  74. package/dist/eve/transcript.mjs +5 -1
  75. package/dist/eve/transcript.mjs.map +1 -1
  76. package/dist/execution.d.mts +117 -9
  77. package/dist/execution.mjs +76 -6
  78. package/dist/execution.mjs.map +1 -1
  79. package/dist/finding-remediation/admission.d.mts +2 -0
  80. package/dist/finding-remediation/admission.mjs +4 -1
  81. package/dist/finding-remediation/admission.mjs.map +1 -1
  82. package/dist/findings.d.mts +1 -0
  83. package/dist/github-publication.d.mts +1 -0
  84. package/dist/github-publication.mjs +97 -84
  85. package/dist/github-publication.mjs.map +1 -1
  86. package/dist/github-transfer.d.mts +15 -6
  87. package/dist/github-transfer.mjs +214 -66
  88. package/dist/github-transfer.mjs.map +1 -1
  89. package/dist/github.d.mts +51 -11
  90. package/dist/github.mjs +126 -24
  91. package/dist/github.mjs.map +1 -1
  92. package/dist/inbox-activity.d.mts +53 -0
  93. package/dist/inbox-activity.mjs +41 -0
  94. package/dist/inbox-activity.mjs.map +1 -0
  95. package/dist/index.d.mts +3 -1
  96. package/dist/index.mjs +3 -2
  97. package/dist/intake-contracts.d.mts +0 -1
  98. package/dist/integrations/deepsec.d.mts +1 -0
  99. package/dist/integrations/github.d.mts +2 -2
  100. package/dist/integrations/github.mjs +2 -2
  101. package/dist/integrations/slack.d.mts +3 -1
  102. package/dist/integrations/slack.mjs +3 -1
  103. package/dist/integrations/vercel.d.mts +4 -2
  104. package/dist/integrations/vercel.mjs +3 -2
  105. package/dist/merge-resolution/eve-tools.d.mts +1 -0
  106. package/dist/merge-resolution/eve-tools.mjs +7 -2
  107. package/dist/merge-resolution/eve-tools.mjs.map +1 -1
  108. package/dist/model-settings.d.mts +41 -0
  109. package/dist/model-settings.mjs +35 -0
  110. package/dist/model-settings.mjs.map +1 -0
  111. package/dist/planning/reconcile.mjs +6 -0
  112. package/dist/planning/reconcile.mjs.map +1 -1
  113. package/dist/postgres/index.d.mts +43 -2
  114. package/dist/postgres/index.mjs +40 -2
  115. package/dist/postgres/index.mjs.map +1 -1
  116. package/dist/presets/software-development/dispatch.d.mts +4 -1
  117. package/dist/presets/software-development/dispatch.mjs +2 -1
  118. package/dist/presets/software-development/dispatch.mjs.map +1 -1
  119. package/dist/presets/software-development/task-communication.d.mts +1 -0
  120. package/dist/presets/software-development/task-communication.mjs +48 -11
  121. package/dist/presets/software-development/task-communication.mjs.map +1 -1
  122. package/dist/presets/software-development.d.mts +1 -0
  123. package/dist/pull-requests/github-publisher.d.mts +15 -1
  124. package/dist/pull-requests/github-publisher.mjs +61 -1
  125. package/dist/pull-requests/github-publisher.mjs.map +1 -1
  126. package/dist/pull-requests.d.mts +1 -0
  127. package/dist/sandbox/index.d.mts +1 -0
  128. package/dist/schema/agent-route.d.mts +19 -1
  129. package/dist/schema/agent-route.mjs +19 -1
  130. package/dist/schema/agent-route.mjs.map +1 -1
  131. package/dist/schema/factory-config.d.mts +27 -0
  132. package/dist/schema/factory-config.mjs +33 -3
  133. package/dist/schema/factory-config.mjs.map +1 -1
  134. package/dist/schema/repository.d.mts +4 -0
  135. package/dist/schema/repository.mjs +5 -1
  136. package/dist/schema/repository.mjs.map +1 -1
  137. package/dist/schema/session.d.mts +1 -0
  138. package/dist/schema/session.mjs +1 -0
  139. package/dist/schema/session.mjs.map +1 -1
  140. package/dist/schema/slack-pr-notifications.d.mts +12 -0
  141. package/dist/schema/slack-pr-notifications.mjs +11 -0
  142. package/dist/schema/slack-pr-notifications.mjs.map +1 -0
  143. package/dist/schema/task-graph.d.mts +39 -0
  144. package/dist/schema/task.d.mts +1 -0
  145. package/dist/schema/task.mjs +2 -1
  146. package/dist/schema/task.mjs.map +1 -1
  147. package/dist/schema/transcript.d.mts +6 -0
  148. package/dist/schema/transcript.mjs +2 -1
  149. package/dist/schema/transcript.mjs.map +1 -1
  150. package/dist/schema/work.d.mts +52 -3
  151. package/dist/schema/work.mjs.map +1 -1
  152. package/dist/session-previews.d.mts +76 -0
  153. package/dist/session-previews.mjs +55 -0
  154. package/dist/session-previews.mjs.map +1 -0
  155. package/dist/session-review.d.mts +120 -0
  156. package/dist/session-review.mjs +79 -0
  157. package/dist/session-review.mjs.map +1 -0
  158. package/dist/signal-triage.mjs +1 -1
  159. package/dist/signals.d.mts +1 -0
  160. package/dist/stall.d.mts +4 -1
  161. package/dist/stall.mjs +6 -2
  162. package/dist/stall.mjs.map +1 -1
  163. package/dist/store/driver.d.mts +1 -1
  164. package/dist/store/driver.mjs.map +1 -1
  165. package/dist/store/engine.d.mts +206 -8
  166. package/dist/store/engine.mjs +147 -13
  167. package/dist/store/engine.mjs.map +1 -1
  168. package/dist/store/memory.d.mts +18 -1
  169. package/dist/store/memory.mjs +18 -1
  170. package/dist/store/memory.mjs.map +1 -1
  171. package/dist/store/slack-pr-notifications.d.mts +44 -0
  172. package/dist/store/slack-pr-notifications.mjs +121 -0
  173. package/dist/store/slack-pr-notifications.mjs.map +1 -0
  174. package/dist/store/task-work.d.mts +121 -6
  175. package/dist/store/task-work.mjs +7 -4
  176. package/dist/store/task-work.mjs.map +1 -1
  177. package/dist/sweep.d.mts +28 -6
  178. package/dist/sweep.mjs +34 -6
  179. package/dist/sweep.mjs.map +1 -1
  180. package/dist/task-graph-view.d.mts +3 -0
  181. package/dist/tasks.d.mts +3 -3
  182. package/dist/tasks.mjs +3 -3
  183. package/dist/vercel-git.d.mts +35 -3
  184. package/dist/vercel-git.mjs +265 -33
  185. package/dist/vercel-git.mjs.map +1 -1
  186. package/dist/vercel-github-api.d.mts +103 -0
  187. package/dist/vercel-github-api.mjs +363 -0
  188. package/dist/vercel-github-api.mjs.map +1 -0
  189. package/dist/vercel.d.mts +3 -2
  190. package/dist/vercel.mjs +3 -2
  191. package/dist/vercel.mjs.map +1 -1
  192. package/dist/work-triage.d.mts +1 -0
  193. package/dist/workflows.d.mts +102 -4
  194. package/dist/workflows.mjs +55 -2
  195. package/dist/workflows.mjs.map +1 -1
  196. package/dist/workspace-files-git.d.mts +15 -0
  197. package/dist/workspace-files-git.mjs +61 -0
  198. package/dist/workspace-files-git.mjs.map +1 -0
  199. package/dist/workspace-files.d.mts +107 -0
  200. package/dist/workspace-files.mjs +74 -0
  201. package/dist/workspace-files.mjs.map +1 -0
  202. package/docs/getting-started.md +104 -0
  203. package/docs/index.md +100 -0
  204. package/docs/recipes/cancellation.md +215 -0
  205. package/docs/recipes/custom-workflow.md +153 -0
  206. package/docs/recipes/dependent-tasks.md +207 -0
  207. package/docs/recipes/eve-agent.md +277 -0
  208. package/docs/recipes/human-input.md +204 -0
  209. package/docs/recipes/persistence-recovery.md +268 -0
  210. package/docs/recipes/retry-recovery.md +241 -0
  211. package/docs/recipes/task-messaging.md +215 -0
  212. package/docs/recipes/typed-eve-result.md +161 -0
  213. package/docs/runtime-integration.md +137 -0
  214. package/package.json +20 -6
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch.mjs","names":[],"sources":["../../../src/presets/software-development/dispatch.ts"],"sourcesContent":["import { sweepSettlements } from \"../../budget\";\nimport type { AgentRouteBinding } from \"../../agent-routes\";\nimport { ensureChangeVerificationTasks } from \"../../change-verification/dispatch\";\nimport type { DispatchBudget } from \"../../dispatch\";\nimport { reconcileMultiRepositoryChangeTasks } from \"../../multi-repository-changes/coordination\";\nimport type { PlannerReconcileResult } from \"../../planning/runtime\";\nimport type { MillisecondClock } from \"../../schema/common\";\nimport type { RepositoryId } from \"../../schema/id\";\nimport { agentSessionIdSchema } from \"../../schema/session\";\nimport type { Task } from \"../../schema/task\";\nimport type { FactoryStores } from \"../../store/engine\";\nimport type { WorkNotification } from \"../../store/work-notifications\";\nimport { sweepQueued, type SweepQueuedOptions, type SweepQueuedResult } from \"../../sweep\";\nimport type { TaskCommunicationReconciler } from \"../../task-communication\";\nimport {\n createSoftwareDevelopmentTaskCommunication,\n type SoftwareDevelopmentTaskCommunicationStores,\n} from \"./task-communication\";\n\nconst defaultReconcileTaskCommunications = createSoftwareDevelopmentTaskCommunication().reconcile;\n\n/** Factory capabilities composed by the supplied software-development dispatcher. */\nexport type SoftwareDevelopmentDispatchStores = Pick<\n FactoryStores,\n \"changes\" | \"graphs\" | \"outbox\" | \"receipts\" | \"repositories\" | \"sessions\" | \"tasks\" | \"work\"\n>;\n\n/** Application policies and adapters for reconciling one reference-work root graph. */\nexport interface DispatchSoftwareDevelopmentWorkOptions {\n /** Durable wakeup identifying the canonical Task graph to reconcile. */\n readonly event: WorkNotification;\n /** Provider delivery identity used to deduplicate continuation wakeups. */\n readonly deliveryId: string;\n stores: SoftwareDevelopmentDispatchStores;\n planning: {\n isPlanner(task: Task): boolean;\n reconcile(tasks: readonly Task[]): Promise<PlannerReconcileResult>;\n };\n launchers: SweepQueuedOptions[\"launchers\"];\n budget: DispatchBudget;\n verificationRoute?: AgentRouteBinding;\n canUseRepository(id: RepositoryId): Promise<boolean>;\n publish(event: WorkNotification, delaySeconds?: number): Promise<void>;\n /**\n * Tasks a workflow owns and reconciles itself. The reference dispatcher skips\n * them so the two never drive the same execution.\n */\n ownedByWorkflow?(tasks: readonly Task[]): readonly Task[];\n now?: MillisecondClock;\n retryDelayMs?: number;\n maxDispatches?: number;\n plannerBatchSize?: number;\n /** Application-selected projection from task state to outbound communication. */\n reconcileCommunications?: TaskCommunicationReconciler<SoftwareDevelopmentTaskCommunicationStores>;\n}\n\n/** Queued-dispatch and planner reconciliation results for one notified root graph. */\nexport interface DispatchSoftwareDevelopmentWorkResult extends SweepQueuedResult {\n readonly planner: PlannerReconcileResult;\n}\n\n/** Reconcile one notified root graph without scanning or scheduling unrelated work. */\nexport async function dispatchSoftwareDevelopmentWork(\n options: DispatchSoftwareDevelopmentWorkOptions,\n): Promise<DispatchSoftwareDevelopmentWorkResult> {\n const { deliveryId, event, stores, planning } = options;\n const recover = async () => {\n const graph = await stores.work.recover(event.rootTaskId);\n const owned = new Set((options.ownedByWorkflow?.(graph) ?? []).map((task) => task.id));\n return graph.filter((task) => !owned.has(task.id));\n };\n let tasks = await recover();\n for (const id of new Set(tasks.flatMap((task) => task.repositoryIds))) {\n if (!(await options.canUseRepository(id)))\n throw new Error(`Work repository ${id} is no longer enabled and configured`);\n }\n // Admission may publish before the API finishes binding its session or continuation.\n for (const task of tasks.filter(\n (task) => task.state === \"queued\" && task.replyTo.channel === \"api\",\n )) {\n const session = await stores.sessions.get(agentSessionIdSchema.parse(task.replyTo.address));\n if (\n !session ||\n (session.route !== undefined && session.taskId === undefined) ||\n (task.work.route?.id === \"conversation\" &&\n !session.continuations?.some((entry) => entry.taskId === task.id))\n ) {\n throw new Error(`Task ${task.id} is waiting for its durable API session binding`);\n }\n }\n for (const task of tasks) {\n if (task.kind === \"coordination\" && task.state === \"running\")\n await reconcileMultiRepositoryChangeTasks(stores, task.id);\n }\n if (options.verificationRoute)\n await ensureChangeVerificationTasks({\n stores,\n tasks: await recover(),\n route: options.verificationRoute,\n });\n await sweepSettlements(stores, { tasks: await recover() });\n const planner = await planning.reconcile(await recover());\n tasks = await recover();\n const ready: Task[] = [];\n const now = (options.now ?? Date.now)();\n for (const task of tasks.filter((task) => !planning.isPlanner(task))) {\n const retryAt = Date.parse(task.updatedAt) + (options.retryDelayMs ?? 60_000);\n if (task.state === \"queued\" && task.attempt > 1 && retryAt > now) {\n await options.publish(\n { rootTaskId: event.rootTaskId, key: `retry:${task.id}:${task.attempt}` },\n Math.ceil((retryAt - now) / 1000),\n );\n } else ready.push(task);\n }\n const result = await sweepQueued(stores, {\n tasks: ready,\n launchers: options.launchers,\n budget: options.budget,\n maxDispatches: options.maxDispatches ?? 1,\n });\n await (options.reconcileCommunications ?? defaultReconcileTaskCommunications)(\n stores,\n await recover(),\n );\n // Continue bounded batches through the queue, never through the next recovery interval.\n if (\n result.skipped.some((entry) => entry.reason === \"per-sweep dispatch limit reached\") ||\n planner.dispatched.length ||\n planner.delivered >= (options.plannerBatchSize ?? 32) ||\n planner.recovered + planner.discarded >= (options.plannerBatchSize ?? 32)\n ) {\n await options.publish({ rootTaskId: event.rootTaskId, key: `continue:${deliveryId}` });\n }\n if (planner.errors.length || planner.cancellationPending?.length || result.failed.length) {\n throw new Error(\n `Work reconciliation needs retry: ${JSON.stringify({ planner: planner.errors, cancellationPending: planner.cancellationPending, failed: result.failed })}`,\n );\n }\n return { ...result, planner };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"dispatch.mjs","names":[],"sources":["../../../src/presets/software-development/dispatch.ts"],"sourcesContent":["import { sweepSettlements } from \"../../budget\";\nimport type { AgentRouteBinding } from \"../../agent-routes\";\nimport { ensureChangeVerificationTasks } from \"../../change-verification/dispatch\";\nimport type { PresentVerificationResultInput } from \"../../change-verification/eve-tool\";\nimport type { DispatchBudget } from \"../../dispatch\";\nimport { reconcileMultiRepositoryChangeTasks } from \"../../multi-repository-changes/coordination\";\nimport type { PlannerReconcileResult } from \"../../planning/runtime\";\nimport type { MillisecondClock } from \"../../schema/common\";\nimport type { RepositoryId } from \"../../schema/id\";\nimport { agentSessionIdSchema } from \"../../schema/session\";\nimport type { Task } from \"../../schema/task\";\nimport type { FactoryStores } from \"../../store/engine\";\nimport type { WorkNotification } from \"../../store/work-notifications\";\nimport { sweepQueued, type SweepQueuedOptions, type SweepQueuedResult } from \"../../sweep\";\nimport type { TaskCommunicationReconciler } from \"../../task-communication\";\nimport {\n createSoftwareDevelopmentTaskCommunication,\n type SoftwareDevelopmentTaskCommunicationStores,\n} from \"./task-communication\";\n\nconst defaultReconcileTaskCommunications = createSoftwareDevelopmentTaskCommunication().reconcile;\n\n/** Factory capabilities composed by the supplied software-development dispatcher. */\nexport type SoftwareDevelopmentDispatchStores = Pick<\n FactoryStores,\n | \"changes\"\n | \"graphs\"\n | \"outbox\"\n | \"receipts\"\n | \"repositories\"\n | \"sessions\"\n | \"signals\"\n | \"tasks\"\n | \"work\"\n>;\n\n/** Application policies and adapters for reconciling one reference-work root graph. */\nexport interface DispatchSoftwareDevelopmentWorkOptions {\n /** Durable wakeup identifying the canonical Task graph to reconcile. */\n readonly event: WorkNotification;\n /** Provider delivery identity used to deduplicate continuation wakeups. */\n readonly deliveryId: string;\n stores: SoftwareDevelopmentDispatchStores;\n planning: {\n isPlanner(task: Task): boolean;\n reconcile(tasks: readonly Task[]): Promise<PlannerReconcileResult>;\n };\n launchers: SweepQueuedOptions[\"launchers\"];\n budget: DispatchBudget;\n verificationRoute?: AgentRouteBinding;\n /** Idempotent application presenter for recorded verification outcomes recovered by the queue. */\n presentVerificationResult?: (input: PresentVerificationResultInput) => Promise<void>;\n canUseRepository(id: RepositoryId): Promise<boolean>;\n publish(event: WorkNotification, delaySeconds?: number): Promise<void>;\n /**\n * Tasks a workflow owns and reconciles itself. The reference dispatcher skips\n * them so the two never drive the same execution.\n */\n ownedByWorkflow?(tasks: readonly Task[]): readonly Task[];\n now?: MillisecondClock;\n retryDelayMs?: number;\n maxDispatches?: number;\n plannerBatchSize?: number;\n /** Application-selected projection from task state to outbound communication. */\n reconcileCommunications?: TaskCommunicationReconciler<SoftwareDevelopmentTaskCommunicationStores>;\n}\n\n/** Queued-dispatch and planner reconciliation results for one notified root graph. */\nexport interface DispatchSoftwareDevelopmentWorkResult extends SweepQueuedResult {\n readonly planner: PlannerReconcileResult;\n}\n\n/** Reconcile one notified root graph without scanning or scheduling unrelated work. */\nexport async function dispatchSoftwareDevelopmentWork(\n options: DispatchSoftwareDevelopmentWorkOptions,\n): Promise<DispatchSoftwareDevelopmentWorkResult> {\n const { deliveryId, event, stores, planning } = options;\n const recover = async () => {\n const graph = await stores.work.recover(event.rootTaskId);\n const owned = new Set((options.ownedByWorkflow?.(graph) ?? []).map((task) => task.id));\n return graph.filter((task) => !owned.has(task.id));\n };\n let tasks = await recover();\n for (const id of new Set(tasks.flatMap((task) => task.repositoryIds))) {\n if (!(await options.canUseRepository(id)))\n throw new Error(`Work repository ${id} is no longer enabled and configured`);\n }\n // Admission may publish before the API finishes binding its session or continuation.\n for (const task of tasks.filter(\n (task) => task.state === \"queued\" && task.replyTo.channel === \"api\",\n )) {\n const session = await stores.sessions.get(agentSessionIdSchema.parse(task.replyTo.address));\n if (\n !session ||\n (session.route !== undefined && session.taskId === undefined) ||\n (task.work.route?.id === \"conversation\" &&\n !session.continuations?.some((entry) => entry.taskId === task.id))\n ) {\n throw new Error(`Task ${task.id} is waiting for its durable API session binding`);\n }\n }\n for (const task of tasks) {\n if (task.kind === \"coordination\" && task.state === \"running\")\n await reconcileMultiRepositoryChangeTasks(stores, task.id);\n }\n if (options.verificationRoute)\n await ensureChangeVerificationTasks({\n stores,\n tasks: await recover(),\n route: options.verificationRoute,\n presentResult: options.presentVerificationResult,\n });\n await sweepSettlements(stores, { tasks: await recover() });\n const planner = await planning.reconcile(await recover());\n tasks = await recover();\n const ready: Task[] = [];\n const now = (options.now ?? Date.now)();\n for (const task of tasks.filter((task) => !planning.isPlanner(task))) {\n const retryAt = Date.parse(task.updatedAt) + (options.retryDelayMs ?? 60_000);\n if (task.state === \"queued\" && task.attempt > 1 && retryAt > now) {\n await options.publish(\n { rootTaskId: event.rootTaskId, key: `retry:${task.id}:${task.attempt}` },\n Math.ceil((retryAt - now) / 1000),\n );\n } else ready.push(task);\n }\n const result = await sweepQueued(stores, {\n tasks: ready,\n launchers: options.launchers,\n budget: options.budget,\n maxDispatches: options.maxDispatches ?? 1,\n });\n await (options.reconcileCommunications ?? defaultReconcileTaskCommunications)(\n stores,\n await recover(),\n );\n // Continue bounded batches through the queue, never through the next recovery interval.\n if (\n result.skipped.some((entry) => entry.reason === \"per-sweep dispatch limit reached\") ||\n planner.dispatched.length ||\n planner.delivered >= (options.plannerBatchSize ?? 32) ||\n planner.recovered + planner.discarded >= (options.plannerBatchSize ?? 32)\n ) {\n await options.publish({ rootTaskId: event.rootTaskId, key: `continue:${deliveryId}` });\n }\n if (planner.errors.length || planner.cancellationPending?.length || result.failed.length) {\n throw new Error(\n `Work reconciliation needs retry: ${JSON.stringify({ planner: planner.errors, cancellationPending: planner.cancellationPending, failed: result.failed })}`,\n );\n }\n return { ...result, planner };\n}\n"],"mappings":"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"}
@@ -9,6 +9,7 @@ type SoftwareDevelopmentTaskCommunicationStores = Pick<FactoryStores, "changes"
9
9
  /** Connector presentation settings for supplied software Task communications. */
10
10
  interface SoftwareDevelopmentTaskCommunicationOptions {
11
11
  githubMention?: string;
12
+ /** Optional additional notification destination; omitted keeps questions in their original conversation. */
12
13
  needsHumanReplyTo?: ReplyTo;
13
14
  }
14
15
  /** Supplied question projection and durable lifecycle communication reconciler. */
@@ -1,24 +1,35 @@
1
+ import { agentSessionIdSchema } from "../../schema/session.mjs";
1
2
  import { createTaskCommunicationReconciler } from "../../task-communication.mjs";
3
+ import { z } from "zod";
2
4
  //#region src/presets/software-development/task-communication.ts
5
+ const reviewOutputSchema = z.object({
6
+ verdict: z.enum([
7
+ "approve",
8
+ "request_changes",
9
+ "comment"
10
+ ]),
11
+ findingCount: z.number().int().nonnegative(),
12
+ review: z.object({
13
+ url: z.url(),
14
+ state: z.string().optional()
15
+ }).optional()
16
+ });
3
17
  /** Supplied Slack, GitHub, and API presentation for a task question. */
4
18
  function createSoftwareDevelopmentTaskCommunication(options = {}) {
5
19
  const githubMention = options.githubMention ?? "@agent-factory";
6
20
  if (githubMention.trim().length === 0) throw new Error("githubMention must not be empty");
7
- const needsHumanReplyTo = options.needsHumanReplyTo ?? {
8
- channel: "slack",
9
- address: "owner"
10
- };
21
+ const needsHumanReplyTo = options.needsHumanReplyTo;
11
22
  function questionEvents(task) {
12
23
  if (task.kind !== "question" || task.question === void 0) throw new Error(`Task ${task.id} has no question.`);
13
24
  const reference = `Reference: ${task.id}`;
14
- const instructions = task.replyTo.channel === "slack" ? "To answer, reply in this thread and mention the Factory app." : task.replyTo.channel === "github" && task.replyTo.address.includes("#") ? `To answer, comment on this issue starting with \`${githubMention}\`.` : "Answer in the original conversation.";
25
+ const session = task.replyTo.channel === "api" && agentSessionIdSchema.safeParse(task.replyTo.address);
26
+ const instructions = session && session.success ? `Open Factory session ${session.data}: factory-tui --config REPLACE_WITH_TUI_CONFIG.json --session ${session.data}\nReplace REPLACE_WITH_TUI_CONFIG.json with an authenticated TUI config for the factory that sent this notice. Review the current question before answering; this notice may be stale.` : task.replyTo.channel === "slack" ? "To answer, reply in the original Slack thread and mention the Factory app." : task.replyTo.channel === "github" && /^[^/]+\/[^/#]+(?:#|\/pull\/)[1-9][0-9]*$/u.test(task.replyTo.address) ? `To answer, comment on ${task.replyTo.address} starting with \`${githubMention}\`.` : `No Factory conversation link recorded. Reply destination: ${task.replyTo.channel} ${task.replyTo.address}. Ask an operator to locate the original conversation using the question reference; do not use approve or retry-intake to answer it.`;
15
27
  const common = {
16
28
  repositoryIds: task.repositoryIds,
17
29
  taskId: task.id
18
30
  };
19
31
  const action = task.needsHumanAction ?? task.question.text;
20
- const apiInstructions = task.replyTo.channel === "api" ? `\nOpen session ${task.replyTo.address} in your Factory client to answer.${/^ses_[a-z0-9]{6,64}$/u.test(task.replyTo.address) ? ` In Factory TUI: factory-tui --session ${task.replyTo.address}` : ""}` : "";
21
- return [{
32
+ const events = [{
22
33
  ...common,
23
34
  eventId: `question:${task.id}:${task.attempt}`,
24
35
  replyTo: task.replyTo,
@@ -27,32 +38,58 @@ function createSoftwareDevelopmentTaskCommunication(options = {}) {
27
38
  question: `${reference}\n\n${instructions}\n\n${task.question.text}`,
28
39
  options: task.question.options
29
40
  }
30
- }, {
41
+ }];
42
+ if (needsHumanReplyTo !== void 0) events.push({
31
43
  ...common,
32
44
  eventId: `needs-human:${task.id}:${task.attempt}`,
33
45
  replyTo: needsHumanReplyTo,
34
46
  outcome: "needs_human",
35
47
  data: {
36
48
  requestedAction: action.length > 2e3 ? `${action.slice(0, 2e3)}…` : action,
37
- message: `${reference}\nOpen the original conversation to answer.${apiInstructions}\nReplies in this notification DM do not answer the question.`,
49
+ message: `${reference}\nReply destination: ${task.replyTo.channel} ${task.replyTo.address}\n${instructions}\nReplies in this notification DM do not answer the question.`,
38
50
  options: task.question.options,
39
51
  questionAttempt: task.attempt,
40
52
  ...task.parentTaskId === void 0 ? {} : { parentTaskId: task.parentTaskId },
41
53
  replyChannel: task.replyTo.channel,
42
54
  replyAddress: task.replyTo.address
43
55
  }
44
- }];
56
+ });
57
+ return events;
45
58
  }
46
59
  return {
47
60
  questionEvents,
48
61
  reconcile: createTaskCommunicationReconciler({
49
62
  question: questionEvents,
50
63
  async lifecycle({ stores, tasks, task, receipt }) {
64
+ if (receipt.state === "succeeded" && task.changeId !== void 0) {
65
+ if (task.work.route?.id !== "code-review" || task.work.route.version !== 1 || task.origin.operator === void 0) return [];
66
+ const output = reviewOutputSchema.parse(task.workResult?.output);
67
+ const change = await stores.changes.get(task.changeId);
68
+ if (change === null) throw new Error(`Review ${task.id} references a missing Change.`);
69
+ const findings = output.findingCount === 0 ? output.verdict === "request_changes" ? "Changes requested." : output.review?.state === "APPROVED" ? "Approved. No issues found." : "No issues found." : `${output.findingCount} finding${output.findingCount === 1 ? "" : "s"}.${output.verdict === "request_changes" ? " Changes requested." : ""}`;
70
+ const event = eventFor(task, receipt);
71
+ return [{
72
+ ...event,
73
+ data: {
74
+ ...event.data,
75
+ title: `Review complete for PR #${change.prNumber}`,
76
+ summary: findings,
77
+ url: output.review?.url ?? change.prUrl
78
+ }
79
+ }];
80
+ }
51
81
  return [task.kind === "coordination" && (receipt.state === "succeeded" || receipt.state === "failed") ? await coordinationEvent(stores, tasks, task, receipt) : eventFor(task, receipt)];
52
82
  }
53
83
  })
54
84
  };
55
85
  }
86
+ function completionSummary(task, receipt) {
87
+ const output = task.workResult?.output;
88
+ const summary = [...typeof output === "object" && output !== null && !Array.isArray(output) ? ["summary" in output ? output.summary : void 0, "result" in output ? output.result : void 0] : [output], receipt.reason].find((value) => typeof value === "string" && value.trim().length > 0);
89
+ if (!summary) return "Completed. No result summary was recorded; open the chat for details.";
90
+ const text = summary.trim();
91
+ return text.length > 1e3 ? `${text.slice(0, 997)}…` : text;
92
+ }
56
93
  function eventFor(task, receipt) {
57
94
  const common = {
58
95
  eventId: `task-lifecycle:${receipt.receiptId}`,
@@ -91,7 +128,7 @@ function eventFor(task, receipt) {
91
128
  ...common,
92
129
  outcome: "succeeded",
93
130
  data: {
94
- summary: receipt.reason ?? "The task completed successfully.",
131
+ summary: completionSummary(task, receipt),
95
132
  output: task.workResult?.output
96
133
  }
97
134
  };
@@ -1 +1 @@
1
- {"version":3,"file":"task-communication.mjs","names":[],"sources":["../../../src/presets/software-development/task-communication.ts"],"sourcesContent":["import type { CommunicationEvent } from \"../../schema/communication\";\nimport type { ReplyTo } from \"../../schema/common\";\nimport type { Receipt } from \"../../schema/receipt\";\nimport type { Task } from \"../../schema/task\";\nimport type { FactoryStores } from \"../../store/engine\";\nimport {\n createTaskCommunicationReconciler,\n type TaskCommunicationReconciler,\n} from \"../../task-communication\";\n\n/** Store capabilities required by supplied software-development communication projections. */\nexport type SoftwareDevelopmentTaskCommunicationStores = Pick<\n FactoryStores,\n \"changes\" | \"outbox\" | \"receipts\" | \"repositories\" | \"tasks\"\n>;\n\n/** Connector presentation settings for supplied software Task communications. */\nexport interface SoftwareDevelopmentTaskCommunicationOptions {\n githubMention?: string;\n needsHumanReplyTo?: ReplyTo;\n}\n\n/** Supplied question projection and durable lifecycle communication reconciler. */\nexport interface SoftwareDevelopmentTaskCommunication {\n questionEvents(task: Task): readonly CommunicationEvent[];\n reconcile: TaskCommunicationReconciler<SoftwareDevelopmentTaskCommunicationStores>;\n}\n\n/** Supplied Slack, GitHub, and API presentation for a task question. */\nexport function createSoftwareDevelopmentTaskCommunication(\n options: SoftwareDevelopmentTaskCommunicationOptions = {},\n): SoftwareDevelopmentTaskCommunication {\n const githubMention = options.githubMention ?? \"@agent-factory\";\n if (githubMention.trim().length === 0) throw new Error(\"githubMention must not be empty\");\n const needsHumanReplyTo = options.needsHumanReplyTo ?? {\n channel: \"slack\",\n address: \"owner\",\n };\n\n function questionEvents(task: Task): readonly CommunicationEvent[] {\n if (task.kind !== \"question\" || task.question === undefined)\n throw new Error(`Task ${task.id} has no question.`);\n const reference = `Reference: ${task.id}`;\n const instructions =\n task.replyTo.channel === \"slack\"\n ? \"To answer, reply in this thread and mention the Factory app.\"\n : task.replyTo.channel === \"github\" && task.replyTo.address.includes(\"#\")\n ? `To answer, comment on this issue starting with \\`${githubMention}\\`.`\n : \"Answer in the original conversation.\";\n const common = { repositoryIds: task.repositoryIds, taskId: task.id };\n const action = task.needsHumanAction ?? task.question.text;\n const apiInstructions =\n task.replyTo.channel === \"api\"\n ? `\\nOpen session ${task.replyTo.address} in your Factory client to answer.${/^ses_[a-z0-9]{6,64}$/u.test(task.replyTo.address) ? ` In Factory TUI: factory-tui --session ${task.replyTo.address}` : \"\"}`\n : \"\";\n return [\n {\n ...common,\n eventId: `question:${task.id}:${task.attempt}`,\n replyTo: task.replyTo,\n outcome: \"question\",\n data: {\n question: `${reference}\\n\\n${instructions}\\n\\n${task.question.text}`,\n options: task.question.options,\n },\n },\n {\n ...common,\n eventId: `needs-human:${task.id}:${task.attempt}`,\n replyTo: needsHumanReplyTo,\n outcome: \"needs_human\",\n data: {\n requestedAction: action.length > 2000 ? `${action.slice(0, 2000)}…` : action,\n message: `${reference}\\nOpen the original conversation to answer.${apiInstructions}\\nReplies in this notification DM do not answer the question.`,\n options: task.question.options,\n questionAttempt: task.attempt,\n ...(task.parentTaskId === undefined ? {} : { parentTaskId: task.parentTaskId }),\n replyChannel: task.replyTo.channel,\n replyAddress: task.replyTo.address,\n },\n },\n ];\n }\n\n return {\n questionEvents,\n reconcile: createTaskCommunicationReconciler<SoftwareDevelopmentTaskCommunicationStores>({\n question: questionEvents,\n async lifecycle({ stores, tasks, task, receipt }) {\n const event =\n task.kind === \"coordination\" &&\n (receipt.state === \"succeeded\" || receipt.state === \"failed\")\n ? await coordinationEvent(stores, tasks, task, receipt)\n : eventFor(task, receipt);\n return [event];\n },\n }),\n };\n}\n\nfunction eventFor(task: Task, receipt: Receipt): CommunicationEvent {\n const common = {\n eventId: `task-lifecycle:${receipt.receiptId}`,\n repositoryIds: task.repositoryIds,\n taskId: task.id,\n ...(task.changeId === undefined ? {} : { changeId: task.changeId }),\n replyTo: task.replyTo,\n };\n if (receipt.state === \"dispatched\" && receipt.phase === \"retry\") {\n return {\n ...common,\n outcome: \"started\",\n data: {\n message: `Retry attempt ${receipt.attempt} started.${receipt.reason === undefined ? \"\" : ` ${receipt.reason}`}`,\n },\n };\n }\n if (receipt.state === \"failed\") {\n let prefix = \"Work failed.\";\n if (receipt.reason?.startsWith(\"launch failed\")) prefix = \"Work failed to start.\";\n else if (receipt.phase === \"retry\") prefix = \"Retries exhausted.\";\n return {\n ...common,\n outcome: \"failed\",\n data: { reason: `${prefix}${receipt.reason === undefined ? \"\" : ` ${receipt.reason}`}` },\n };\n }\n if (receipt.state === \"cancelled\") {\n const prefix = receipt.phase === \"approval\" ? \"Request rejected.\" : \"Work cancelled.\";\n return {\n ...common,\n outcome: \"cancelled\",\n data: {\n message: `${prefix}${receipt.reason === undefined ? \"\" : ` ${receipt.reason}`}`,\n ...(receipt.phase === \"approval\" ? { approvalDecision: true } : {}),\n },\n };\n }\n if (receipt.state === \"succeeded\") {\n return {\n ...common,\n outcome: \"succeeded\",\n data: {\n summary: receipt.reason ?? \"The task completed successfully.\",\n output: task.workResult?.output,\n },\n };\n }\n throw new Error(`Receipt ${receipt.receiptId} is not a communicable task lifecycle event.`);\n}\n\nasync function coordinationEvent(\n stores: SoftwareDevelopmentTaskCommunicationStores,\n tasks: readonly Task[],\n parent: Task,\n receipt: Receipt,\n): Promise<CommunicationEvent> {\n const children = tasks.filter((task) => task.parentTaskId === parent.id);\n const items = await Promise.all(\n children.map(async (child) => {\n const repository = await stores.repositories.get(child.repositoryIds[0]!);\n const change = child.changeId === undefined ? null : await stores.changes.get(child.changeId);\n return `${repository?.slug ?? child.repositoryIds[0]}: ${change?.prUrl ?? child.state}`;\n }),\n );\n const event = eventFor(parent, receipt);\n return {\n ...event,\n data: {\n ...event.data,\n summary:\n receipt.state === \"succeeded\"\n ? \"Coordinated work completed.\"\n : \"Coordinated work failed in one or more repositories.\",\n items,\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"task-communication.mjs","names":[],"sources":["../../../src/presets/software-development/task-communication.ts"],"sourcesContent":["import { z } from \"zod\";\nimport type { CommunicationEvent } from \"../../schema/communication\";\nimport type { ReplyTo } from \"../../schema/common\";\nimport type { Receipt } from \"../../schema/receipt\";\nimport type { Task } from \"../../schema/task\";\nimport { agentSessionIdSchema } from \"../../schema/session\";\nimport type { FactoryStores } from \"../../store/engine\";\nimport {\n createTaskCommunicationReconciler,\n type TaskCommunicationReconciler,\n} from \"../../task-communication\";\n\n/** Store capabilities required by supplied software-development communication projections. */\nexport type SoftwareDevelopmentTaskCommunicationStores = Pick<\n FactoryStores,\n \"changes\" | \"outbox\" | \"receipts\" | \"repositories\" | \"tasks\"\n>;\n\n/** Connector presentation settings for supplied software Task communications. */\nexport interface SoftwareDevelopmentTaskCommunicationOptions {\n githubMention?: string;\n /** Optional additional notification destination; omitted keeps questions in their original conversation. */\n needsHumanReplyTo?: ReplyTo;\n}\n\n/** Supplied question projection and durable lifecycle communication reconciler. */\nexport interface SoftwareDevelopmentTaskCommunication {\n questionEvents(task: Task): readonly CommunicationEvent[];\n reconcile: TaskCommunicationReconciler<SoftwareDevelopmentTaskCommunicationStores>;\n}\n\nconst reviewOutputSchema = z.object({\n verdict: z.enum([\"approve\", \"request_changes\", \"comment\"]),\n findingCount: z.number().int().nonnegative(),\n review: z.object({ url: z.url(), state: z.string().optional() }).optional(),\n});\n\n/** Supplied Slack, GitHub, and API presentation for a task question. */\nexport function createSoftwareDevelopmentTaskCommunication(\n options: SoftwareDevelopmentTaskCommunicationOptions = {},\n): SoftwareDevelopmentTaskCommunication {\n const githubMention = options.githubMention ?? \"@agent-factory\";\n if (githubMention.trim().length === 0) throw new Error(\"githubMention must not be empty\");\n const needsHumanReplyTo = options.needsHumanReplyTo;\n\n function questionEvents(task: Task): readonly CommunicationEvent[] {\n if (task.kind !== \"question\" || task.question === undefined)\n throw new Error(`Task ${task.id} has no question.`);\n const reference = `Reference: ${task.id}`;\n const session =\n task.replyTo.channel === \"api\" && agentSessionIdSchema.safeParse(task.replyTo.address);\n const instructions =\n session && session.success\n ? `Open Factory session ${session.data}: factory-tui --config REPLACE_WITH_TUI_CONFIG.json --session ${session.data}\\nReplace REPLACE_WITH_TUI_CONFIG.json with an authenticated TUI config for the factory that sent this notice. Review the current question before answering; this notice may be stale.`\n : task.replyTo.channel === \"slack\"\n ? \"To answer, reply in the original Slack thread and mention the Factory app.\"\n : task.replyTo.channel === \"github\" &&\n /^[^/]+\\/[^/#]+(?:#|\\/pull\\/)[1-9][0-9]*$/u.test(task.replyTo.address)\n ? `To answer, comment on ${task.replyTo.address} starting with \\`${githubMention}\\`.`\n : `No Factory conversation link recorded. Reply destination: ${task.replyTo.channel} ${task.replyTo.address}. Ask an operator to locate the original conversation using the question reference; do not use approve or retry-intake to answer it.`;\n const common = { repositoryIds: task.repositoryIds, taskId: task.id };\n const action = task.needsHumanAction ?? task.question.text;\n const events: CommunicationEvent[] = [\n {\n ...common,\n eventId: `question:${task.id}:${task.attempt}`,\n replyTo: task.replyTo,\n outcome: \"question\",\n data: {\n question: `${reference}\\n\\n${instructions}\\n\\n${task.question.text}`,\n options: task.question.options,\n },\n },\n ];\n if (needsHumanReplyTo !== undefined) {\n events.push({\n ...common,\n eventId: `needs-human:${task.id}:${task.attempt}`,\n replyTo: needsHumanReplyTo,\n outcome: \"needs_human\",\n data: {\n requestedAction: action.length > 2000 ? `${action.slice(0, 2000)}…` : action,\n message: `${reference}\\nReply destination: ${task.replyTo.channel} ${task.replyTo.address}\\n${instructions}\\nReplies in this notification DM do not answer the question.`,\n options: task.question.options,\n questionAttempt: task.attempt,\n ...(task.parentTaskId === undefined ? {} : { parentTaskId: task.parentTaskId }),\n replyChannel: task.replyTo.channel,\n replyAddress: task.replyTo.address,\n },\n });\n }\n return events;\n }\n\n return {\n questionEvents,\n reconcile: createTaskCommunicationReconciler<SoftwareDevelopmentTaskCommunicationStores>({\n question: questionEvents,\n async lifecycle({ stores, tasks, task, receipt }) {\n if (receipt.state === \"succeeded\" && task.changeId !== undefined) {\n if (\n task.work.route?.id !== \"code-review\" ||\n task.work.route.version !== 1 ||\n task.origin.operator === undefined\n )\n return [];\n const output = reviewOutputSchema.parse(task.workResult?.output);\n const change = await stores.changes.get(task.changeId);\n if (change === null) throw new Error(`Review ${task.id} references a missing Change.`);\n const findings =\n output.findingCount === 0\n ? output.verdict === \"request_changes\"\n ? \"Changes requested.\"\n : output.review?.state === \"APPROVED\"\n ? \"Approved. No issues found.\"\n : \"No issues found.\"\n : `${output.findingCount} finding${output.findingCount === 1 ? \"\" : \"s\"}.${output.verdict === \"request_changes\" ? \" Changes requested.\" : \"\"}`;\n const event = eventFor(task, receipt);\n return [\n {\n ...event,\n data: {\n ...event.data,\n title: `Review complete for PR #${change.prNumber}`,\n summary: findings,\n url: output.review?.url ?? change.prUrl,\n },\n },\n ];\n }\n const event =\n task.kind === \"coordination\" &&\n (receipt.state === \"succeeded\" || receipt.state === \"failed\")\n ? await coordinationEvent(stores, tasks, task, receipt)\n : eventFor(task, receipt);\n return [event];\n },\n }),\n };\n}\n\nfunction completionSummary(task: Task, receipt: Receipt): string {\n const output = task.workResult?.output;\n const fields =\n typeof output === \"object\" && output !== null && !Array.isArray(output)\n ? [\n \"summary\" in output ? output.summary : undefined,\n \"result\" in output ? output.result : undefined,\n ]\n : [output];\n const summary = [...fields, receipt.reason].find(\n (value): value is string => typeof value === \"string\" && value.trim().length > 0,\n );\n if (!summary) return \"Completed. No result summary was recorded; open the chat for details.\";\n const text = summary.trim();\n return text.length > 1000 ? `${text.slice(0, 997)}…` : text;\n}\n\nfunction eventFor(task: Task, receipt: Receipt): CommunicationEvent {\n const common = {\n eventId: `task-lifecycle:${receipt.receiptId}`,\n repositoryIds: task.repositoryIds,\n taskId: task.id,\n ...(task.changeId === undefined ? {} : { changeId: task.changeId }),\n replyTo: task.replyTo,\n };\n if (receipt.state === \"dispatched\" && receipt.phase === \"retry\") {\n return {\n ...common,\n outcome: \"started\",\n data: {\n message: `Retry attempt ${receipt.attempt} started.${receipt.reason === undefined ? \"\" : ` ${receipt.reason}`}`,\n },\n };\n }\n if (receipt.state === \"failed\") {\n let prefix = \"Work failed.\";\n if (receipt.reason?.startsWith(\"launch failed\")) prefix = \"Work failed to start.\";\n else if (receipt.phase === \"retry\") prefix = \"Retries exhausted.\";\n return {\n ...common,\n outcome: \"failed\",\n data: { reason: `${prefix}${receipt.reason === undefined ? \"\" : ` ${receipt.reason}`}` },\n };\n }\n if (receipt.state === \"cancelled\") {\n const prefix = receipt.phase === \"approval\" ? \"Request rejected.\" : \"Work cancelled.\";\n return {\n ...common,\n outcome: \"cancelled\",\n data: {\n message: `${prefix}${receipt.reason === undefined ? \"\" : ` ${receipt.reason}`}`,\n ...(receipt.phase === \"approval\" ? { approvalDecision: true } : {}),\n },\n };\n }\n if (receipt.state === \"succeeded\") {\n return {\n ...common,\n outcome: \"succeeded\",\n data: {\n summary: completionSummary(task, receipt),\n output: task.workResult?.output,\n },\n };\n }\n throw new Error(`Receipt ${receipt.receiptId} is not a communicable task lifecycle event.`);\n}\n\nasync function coordinationEvent(\n stores: SoftwareDevelopmentTaskCommunicationStores,\n tasks: readonly Task[],\n parent: Task,\n receipt: Receipt,\n): Promise<CommunicationEvent> {\n const children = tasks.filter((task) => task.parentTaskId === parent.id);\n const items = await Promise.all(\n children.map(async (child) => {\n const repository = await stores.repositories.get(child.repositoryIds[0]!);\n const change = child.changeId === undefined ? null : await stores.changes.get(child.changeId);\n return `${repository?.slug ?? child.repositoryIds[0]}: ${change?.prUrl ?? child.state}`;\n }),\n );\n const event = eventFor(parent, receipt);\n return {\n ...event,\n data: {\n ...event.data,\n summary:\n receipt.state === \"succeeded\"\n ? \"Coordinated work completed.\"\n : \"Coordinated work failed in one or more repositories.\",\n items,\n },\n };\n}\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" />
1
2
  import { formatDeepsecScanBrief } from "./software-development/deepsec-brief.mjs";
2
3
  import { SoftwareDevelopmentRecoveryOptions, SoftwareDevelopmentRecoveryStores, StartSoftwareDevelopmentRecovery, StartSoftwareDevelopmentRecoveryInput, createSoftwareDevelopmentRecoveryLaunchers } from "./software-development/recovery.mjs";
3
4
  import { defaultSoftwareDevelopmentApprovalPolicy } from "./software-development/approval.mjs";
@@ -3,13 +3,27 @@ import { Repository, RepositorySlug } from "../schema/repository.mjs";
3
3
  import { Change } from "../schema/change.mjs";
4
4
  import { FactoryStores } from "../store/engine.mjs";
5
5
  import { OpenPullRequestInput, PublishedPullRequest, PullRequestPublicationGateInput, PullRequestPublicationGateResult, PullRequestPublicationOwner, PushCheckpointInput, TaskPullRequest } from "./contracts.mjs";
6
- import { GitHubPublicationClient, GitHubPublicationClientOptions, SandboxGit } from "../github-publication.mjs";
6
+ import { CommittedGitChange, CreatedGitHubPublicationPullRequest, GitHubPublicationClient, GitHubPublicationClientOptions, SandboxGit } from "../github-publication.mjs";
7
7
  //#region src/pull-requests/github-publisher.d.ts
8
8
  /** Repository credential and client factories required by software publication. */
9
9
  interface GitHubPullRequestPublisherOptions {
10
10
  tokenForRepository(repositorySlug: RepositorySlug): Promise<string>;
11
11
  fetch?: typeof globalThis.fetch;
12
12
  createClient?: (options: GitHubPublicationClientOptions) => GitHubPublicationClient;
13
+ /** Publishes the initial commit, branch, and pull request through an application credential boundary. */
14
+ publishPullRequest?: (input: {
15
+ repository: RepositorySlug;
16
+ baseCommit: string;
17
+ tree: CommittedGitChange["tree"];
18
+ commitMessage: string;
19
+ branch: string;
20
+ title: string;
21
+ body: string;
22
+ baseBranch: string;
23
+ draft: boolean;
24
+ }) => Promise<CreatedGitHubPublicationPullRequest & {
25
+ commitSha: string;
26
+ }>;
13
27
  }
14
28
  /** Task- and session-aware operations for idempotent GitHub pull-request publication. */
15
29
  interface GitHubPullRequestPublisher {
@@ -120,6 +120,31 @@ function createGitHubPullRequestPublisher(options) {
120
120
  async function openSessionPullRequest(sandbox, repository, sessionId, input) {
121
121
  const branch = factorySessionBranch(sessionId);
122
122
  await assertSessionCheckout(sandbox, repository);
123
+ if (options.publishPullRequest !== void 0) {
124
+ const change = await readCommittedGitChange({
125
+ sandbox,
126
+ repositorySlug: repository.slug
127
+ });
128
+ const published = await options.publishPullRequest({
129
+ repository: repository.slug,
130
+ baseCommit: change.base,
131
+ tree: change.tree,
132
+ commitMessage: change.commitMessage,
133
+ branch,
134
+ title: input.title,
135
+ body: input.body,
136
+ baseBranch: repository.defaultBranch,
137
+ draft: true
138
+ });
139
+ await rememberSessionHead(sandbox, published.commitSha);
140
+ return {
141
+ prNumber: published.pullRequestNumber,
142
+ prUrl: published.pullRequestUrl,
143
+ branch,
144
+ changedFiles: change.changedFiles,
145
+ draft: published.draft
146
+ };
147
+ }
123
148
  const client = await clientForRepository(options, repository);
124
149
  const existing = await sessionPullRequest(client, repository, branch);
125
150
  if (existing !== void 0) {
@@ -172,9 +197,44 @@ function createGitHubPullRequestPublisher(options) {
172
197
  sandbox: input.sandbox,
173
198
  repositorySlug: input.repository.slug
174
199
  });
200
+ if (options.publishPullRequest !== void 0) {
201
+ const branch = factoryTaskBranch(input.owner);
202
+ const published = await options.publishPullRequest({
203
+ repository: input.repository.slug,
204
+ baseCommit: change.base,
205
+ tree: change.tree,
206
+ commitMessage: change.commitMessage,
207
+ branch,
208
+ title: input.pullRequest.title,
209
+ body: input.pullRequest.body,
210
+ baseBranch: input.repository.defaultBranch,
211
+ draft: true
212
+ });
213
+ return {
214
+ prNumber: published.pullRequestNumber,
215
+ prUrl: published.pullRequestUrl,
216
+ branch,
217
+ changedFiles: change.changedFiles,
218
+ draft: published.draft
219
+ };
220
+ }
175
221
  const client = await clientForRepository(options, input.repository);
176
- const sha = await createCommit(client, change, `${input.pullRequest.title}\n\n${input.pullRequest.body}`);
177
222
  const branch = factoryTaskBranch(input.owner);
223
+ const existing = (await client.pullRequests({
224
+ state: "all",
225
+ head: `${repositoryOwner(input.repository)}:${branch}`
226
+ })).find((pull) => pull.head.branch === branch && pull.head.repository === input.repository.slug);
227
+ if (existing !== void 0) {
228
+ if (existing.state !== "open") throw new Error("This Task's pull request is merged or closed. Start new work for a new pull request.");
229
+ return {
230
+ prNumber: existing.pullRequestNumber,
231
+ prUrl: existing.pullRequestUrl,
232
+ branch,
233
+ changedFiles: 0,
234
+ draft: existing.draft
235
+ };
236
+ }
237
+ const sha = await createCommit(client, change, `${input.pullRequest.title}\n\n${input.pullRequest.body}`);
178
238
  await client.createBranch({
179
239
  branch,
180
240
  sha
@@ -1 +1 @@
1
- {"version":3,"file":"github-publisher.mjs","names":[],"sources":["../../src/pull-requests/github-publisher.ts"],"sourcesContent":["import { redactedErrorMessage } from \"../errors\";\nimport {\n createGitHubPublicationClient,\n factorySessionBranch,\n factoryTaskBranch,\n GitHubPublicationApiError,\n isSafeGitRef,\n readCommittedGitChange,\n runSandboxGit,\n type CommittedGitChange,\n type GitHubPublicationClient,\n type GitHubPublicationClientOptions,\n type GitHubPublicationPullRequest,\n type SandboxGit,\n} from \"../github-publication\";\nimport type { Change } from \"../schema/change\";\nimport type { TaskId } from \"../schema/id\";\nimport type { Repository, RepositorySlug } from \"../schema/repository\";\nimport type { AgentSessionId } from \"../schema/session\";\nimport type { FactoryStores } from \"../store/engine\";\nimport type {\n OpenPullRequestInput,\n PublishedPullRequest,\n PullRequestPublicationGateInput,\n PullRequestPublicationGateResult,\n PullRequestPublicationOwner,\n PushCheckpointInput,\n TaskPullRequest,\n} from \"./contracts\";\n\n/** Repository credential and client factories required by software publication. */\nexport interface GitHubPullRequestPublisherOptions {\n tokenForRepository(repositorySlug: RepositorySlug): Promise<string>;\n fetch?: typeof globalThis.fetch;\n createClient?: (options: GitHubPublicationClientOptions) => GitHubPublicationClient;\n}\n\n/** Task- and session-aware operations for idempotent GitHub pull-request publication. */\nexport interface GitHubPullRequestPublisher {\n requireTaskPullRequest(input: RequireTaskPullRequestInput): Promise<TaskPullRequest>;\n openPullRequest(input: PublishGitHubPullRequestInput): Promise<PublishedPullRequest>;\n pushCheckpoint(input: PushGitHubCheckpointInput): Promise<PublishedPullRequest>;\n}\n\n/** Repository and Task whose owned pull request must already exist. */\nexport interface RequireTaskPullRequestInput {\n repository: Repository;\n taskId: TaskId;\n}\n\n/** Sandbox checkout and publication request used to open a GitHub pull request. */\nexport interface PublishGitHubPullRequestInput {\n sandbox: SandboxGit;\n repository: Repository;\n owner: PullRequestPublicationOwner;\n pullRequest: OpenPullRequestInput;\n}\n\n/** Sandbox checkout and optional linked Change used to publish a checkpoint. */\nexport interface PushGitHubCheckpointInput {\n sandbox: SandboxGit;\n repository: Repository;\n owner: PullRequestPublicationOwner;\n checkpoint: PushCheckpointInput;\n linkedChange?: Pick<Change, \"prNumber\" | \"prUrl\">;\n}\n\nasync function clientForRepository(\n options: GitHubPullRequestPublisherOptions,\n repository: Repository,\n): Promise<GitHubPublicationClient> {\n const token = await options.tokenForRepository(repository.slug);\n return (options.createClient ?? createGitHubPublicationClient)({\n repository: repository.slug,\n token,\n fetch: options.fetch,\n });\n}\n\nfunction repositoryOwner(repository: Repository): string {\n const owner = repository.slug.split(\"/\")[0];\n if (owner === undefined || owner === \"\") {\n throw new Error(`invalid repository slug: ${repository.slug}`);\n }\n return owner;\n}\n\nasync function findOpenPullRequest(\n client: GitHubPublicationClient,\n repository: Repository,\n taskId: TaskId,\n): Promise<GitHubPublicationPullRequest & { branch: string }> {\n const branch = factoryTaskBranch(taskId);\n const open = await client.pullRequests({\n state: \"open\",\n head: `${repositoryOwner(repository)}:${branch}`,\n });\n const pull = open.find((candidate) => candidate.head.repository === repository.slug);\n if (pull === undefined) {\n throw new Error(`no open pull request on ${branch}; call open_pull_request first`);\n }\n return { ...pull, branch };\n}\n\nasync function findLinkedPullRequest(\n client: GitHubPublicationClient,\n repository: Repository,\n change: Pick<Change, \"prNumber\" | \"prUrl\">,\n): Promise<GitHubPublicationPullRequest & { branch: string }> {\n const pull = await client.pullRequest({ pullRequestNumber: change.prNumber });\n const branch = pull.head.branch;\n if (\n pull.state !== \"open\" ||\n pull.pullRequestUrl !== change.prUrl ||\n pull.head.repository !== repository.slug ||\n !isSafeGitRef(branch)\n ) {\n throw new Error(\n `Change pull request #${change.prNumber} is not an open same-repository branch`,\n );\n }\n return { ...pull, branch };\n}\n\nasync function createCommit(\n client: GitHubPublicationClient,\n change: CommittedGitChange,\n message: string,\n parent = change.base,\n): Promise<string> {\n const baseCommit = await client.commit({ sha: change.base });\n const tree = await client.createTree({ baseTreeSha: baseCommit.treeSha, entries: change.tree });\n return client.createCommit({ message, treeSha: tree, parentShas: [parent] });\n}\n\nasync function sessionPullRequest(\n client: GitHubPublicationClient,\n repository: Repository,\n branch: string,\n): Promise<GitHubPublicationPullRequest | undefined> {\n const pulls = await client.pullRequests({\n state: \"all\",\n head: `${repositoryOwner(repository)}:${branch}`,\n });\n const pull = pulls.find(\n (candidate) =>\n candidate.head.branch === branch && candidate.head.repository === repository.slug,\n );\n if (pull !== undefined && pull.state !== \"open\") {\n throw new Error(\n \"This session's pull request is merged or closed. Start a new session for a new pull request.\",\n );\n }\n return pull;\n}\n\nasync function sessionHead(sandbox: SandboxGit): Promise<string> {\n const result = await sandbox.run({ command: \"git -C repo config --get factory.publishedHead\" });\n if (result.exitCode === 1) return \"\";\n if (result.exitCode !== undefined && result.exitCode !== 0) {\n throw new Error(\"Could not read the session's published head.\");\n }\n return result.stdout.trim();\n}\n\nasync function rememberSessionHead(sandbox: SandboxGit, sha: string): Promise<void> {\n if (!/^[0-9a-f]{40}$/u.test(sha)) throw new Error(\"GitHub returned an invalid commit SHA.\");\n await runSandboxGit({ sandbox, args: [\"config\", \"factory.publishedHead\", sha] });\n}\n\nasync function assertSessionCheckout(sandbox: SandboxGit, repository: Repository): Promise<void> {\n if (\n (await runSandboxGit({ sandbox, args: [\"remote\", \"get-url\", \"origin\"] })).trim() !==\n `https://github.com/${repository.slug}.git`\n ) {\n throw new Error(`sandbox checkout does not match session repository ${repository.slug}`);\n }\n if ((await runSandboxGit({ sandbox, args: [\"status\", \"--porcelain\"] })).trim() !== \"\") {\n throw new Error(\"Commit your changes before publishing the pull request.\");\n }\n}\n\nasync function acceptSessionHead(\n sandbox: SandboxGit,\n client: GitHubPublicationClient,\n sha: string,\n): Promise<boolean> {\n const expected = await sessionHead(sandbox);\n const commit = await client.commit({ sha });\n const unchanged =\n commit.treeSha ===\n (await runSandboxGit({ sandbox, args: [\"rev-parse\", \"HEAD^{tree}\"] })).trim();\n const recovered = unchanged && (expected === \"\" || commit.parentShas.includes(expected));\n if (expected !== sha && !recovered) {\n throw new Error(\n \"The session PR changed since checkout or publication. Refusing to overwrite newer commits; reload its checkout before continuing.\",\n );\n }\n await rememberSessionHead(sandbox, sha);\n return unchanged;\n}\n\nfunction sessionPublication(\n pull: Pick<GitHubPublicationPullRequest, \"pullRequestNumber\" | \"pullRequestUrl\" | \"draft\">,\n branch: string,\n changedFiles = 0,\n): PublishedPullRequest {\n return {\n prNumber: pull.pullRequestNumber,\n prUrl: pull.pullRequestUrl,\n branch,\n changedFiles,\n draft: pull.draft,\n };\n}\n\n/** Supplied software workflow for safe Task- and session-owned GitHub publication. */\nexport function createGitHubPullRequestPublisher(\n options: GitHubPullRequestPublisherOptions,\n): GitHubPullRequestPublisher {\n async function requireTaskPullRequest(\n input: RequireTaskPullRequestInput,\n ): Promise<TaskPullRequest> {\n const client = await clientForRepository(options, input.repository);\n const pull = await findOpenPullRequest(client, input.repository, input.taskId);\n return {\n prNumber: pull.pullRequestNumber,\n prUrl: pull.pullRequestUrl,\n branch: pull.branch,\n };\n }\n\n async function openSessionPullRequest(\n sandbox: SandboxGit,\n repository: Repository,\n sessionId: AgentSessionId,\n input: OpenPullRequestInput,\n ): Promise<PublishedPullRequest> {\n const branch = factorySessionBranch(sessionId);\n await assertSessionCheckout(sandbox, repository);\n const client = await clientForRepository(options, repository);\n const existing = await sessionPullRequest(client, repository, branch);\n if (existing !== undefined) {\n await acceptSessionHead(sandbox, client, existing.head.sha);\n return sessionPublication(existing, branch);\n }\n\n let sha = await client.branchHead({ branch });\n let changedFiles = 0;\n if (sha === null) {\n const change = await readCommittedGitChange({ sandbox, repositorySlug: repository.slug });\n changedFiles = change.changedFiles;\n sha = await createCommit(client, change, `${input.title}\\n\\n${input.body}`);\n try {\n await client.createBranch({ branch, sha });\n } catch (error) {\n if ((await client.branchHead({ branch })) !== sha) throw error;\n }\n await rememberSessionHead(sandbox, sha);\n } else {\n await acceptSessionHead(sandbox, client, sha);\n }\n try {\n const pull = await client.createPullRequest({\n title: input.title,\n body: input.body,\n head: branch,\n base: repository.defaultBranch,\n draft: true,\n });\n return {\n prNumber: pull.pullRequestNumber,\n prUrl: pull.pullRequestUrl,\n branch,\n changedFiles,\n draft: pull.draft,\n };\n } catch (error) {\n const created = await sessionPullRequest(client, repository, branch);\n if (created === undefined) throw error;\n return sessionPublication(created, branch, changedFiles);\n }\n }\n\n async function openPullRequest(\n input: PublishGitHubPullRequestInput,\n ): Promise<PublishedPullRequest> {\n if (typeof input.owner !== \"string\") {\n return openSessionPullRequest(\n input.sandbox,\n input.repository,\n input.owner.sessionId,\n input.pullRequest,\n );\n }\n const change = await readCommittedGitChange({\n sandbox: input.sandbox,\n repositorySlug: input.repository.slug,\n });\n const client = await clientForRepository(options, input.repository);\n const sha = await createCommit(\n client,\n change,\n `${input.pullRequest.title}\\n\\n${input.pullRequest.body}`,\n );\n const branch = factoryTaskBranch(input.owner);\n await client.createBranch({ branch, sha });\n const pull = await client.createPullRequest({\n title: input.pullRequest.title,\n body: input.pullRequest.body,\n head: branch,\n base: input.repository.defaultBranch,\n draft: true,\n });\n return {\n prNumber: pull.pullRequestNumber,\n prUrl: pull.pullRequestUrl,\n branch,\n changedFiles: change.changedFiles,\n draft: pull.draft,\n };\n }\n\n async function pushSessionCheckpoint(\n sandbox: SandboxGit,\n repository: Repository,\n sessionId: AgentSessionId,\n input: PushCheckpointInput,\n ): Promise<PublishedPullRequest> {\n const branch = factorySessionBranch(sessionId);\n await assertSessionCheckout(sandbox, repository);\n const client = await clientForRepository(options, repository);\n const pull = await sessionPullRequest(client, repository, branch);\n if (pull === undefined) {\n throw new Error(`no open pull request on ${branch}; call open_pull_request first`);\n }\n const unchanged = await acceptSessionHead(sandbox, client, pull.head.sha);\n let changedFiles = 0;\n if (!unchanged) {\n const change = await readCommittedGitChange({\n sandbox,\n repositorySlug: repository.slug,\n allowEmpty: true,\n });\n changedFiles = change.changedFiles;\n const sha = await createCommit(\n client,\n change,\n `${pull.title}\\n\\n${pull.body ?? \"\"}`,\n pull.head.sha,\n );\n await client.updateBranch({ branch, sha, force: false });\n await rememberSessionHead(sandbox, sha);\n }\n const published = sessionPublication(pull, branch, changedFiles);\n if (input.ready && published.draft) {\n await client.markReady({ nodeId: pull.nodeId });\n published.draft = false;\n }\n return published;\n }\n\n async function pushCheckpoint(input: PushGitHubCheckpointInput): Promise<PublishedPullRequest> {\n if (typeof input.owner !== \"string\") {\n return pushSessionCheckpoint(\n input.sandbox,\n input.repository,\n input.owner.sessionId,\n input.checkpoint,\n );\n }\n const client = await clientForRepository(options, input.repository);\n const pull =\n input.linkedChange === undefined\n ? await findOpenPullRequest(client, input.repository, input.owner)\n : await findLinkedPullRequest(client, input.repository, input.linkedChange);\n const change = await readCommittedGitChange({\n sandbox: input.sandbox,\n repositorySlug: input.repository.slug,\n baseRef:\n input.linkedChange === undefined\n ? \"origin/HEAD\"\n : `refs/remotes/origin/pr-${input.linkedChange.prNumber}`,\n });\n if (input.linkedChange !== undefined && change.base !== pull.head.sha) {\n throw new Error(\n `pull request #${input.linkedChange.prNumber} changed since it was fetched; fetch it again`,\n );\n }\n const sha = await createCommit(client, change, `${pull.title}\\n\\n${pull.body ?? \"\"}`);\n await client.updateBranch({\n branch: pull.branch,\n sha,\n force: input.linkedChange === undefined,\n });\n let draft = pull.draft;\n if (input.checkpoint.ready && draft) {\n await client.markReady({ nodeId: pull.nodeId });\n draft = false;\n }\n return {\n prNumber: pull.pullRequestNumber,\n prUrl: pull.pullRequestUrl,\n branch: pull.branch,\n changedFiles: change.changedFiles,\n draft,\n };\n }\n\n return Object.freeze({ requireTaskPullRequest, openPullRequest, pushCheckpoint });\n}\n\n/** Store and repository lookup dependencies for the pull-request publication gate. */\nexport interface GitHubPullRequestPublicationGateOptions extends GitHubPullRequestPublisherOptions {\n stores: Pick<FactoryStores, \"tasks\" | \"changes\">;\n repositoryForTask(taskId: TaskId): Promise<Repository>;\n}\n\n/** Validates that one pull request belongs to its exact GitHub Task and Change context. */\nexport type GitHubPullRequestPublicationGate = (\n input: PullRequestPublicationGateInput,\n) => Promise<PullRequestPublicationGateResult>;\n\n/** Fail-closed gate for the pull request a supplied code-change workflow records. */\nexport function createGitHubPullRequestPublicationGate(\n options: GitHubPullRequestPublicationGateOptions,\n): GitHubPullRequestPublicationGate {\n return async (\n input: PullRequestPublicationGateInput,\n ): Promise<PullRequestPublicationGateResult> => {\n let repository: Repository;\n let linkedChange: Awaited<ReturnType<FactoryStores[\"changes\"][\"get\"]>>;\n try {\n repository = await options.repositoryForTask(input.taskId);\n if (repository.id !== input.repositoryId) {\n return {\n ok: false,\n code: \"repository_mismatch\",\n reason: \"repositoryId does not match the task repository\",\n };\n }\n const task = await options.stores.tasks.get(input.taskId);\n if (task === null) {\n return {\n ok: false,\n code: \"task_not_found\",\n reason: `task ${input.taskId} does not exist`,\n };\n }\n linkedChange =\n task.changeId === undefined ? null : await options.stores.changes.get(task.changeId);\n if (task.changeId !== undefined && linkedChange === null) {\n return {\n ok: false,\n code: \"change_not_found\",\n reason: `change ${task.changeId} does not exist`,\n };\n }\n if (\n linkedChange !== null &&\n (linkedChange.repositoryId !== repository.id ||\n linkedChange.prNumber !== input.prNumber ||\n linkedChange.prUrl !== input.prUrl)\n ) {\n return {\n ok: false,\n code: \"linked_change_mismatch\",\n reason: \"pull request does not match the Change linked to this task\",\n };\n }\n } catch (error) {\n return {\n ok: false,\n code: \"repository_lookup_failed\",\n reason: redactedErrorMessage(error),\n };\n }\n\n let client: GitHubPublicationClient;\n try {\n client = await clientForRepository(options, repository);\n } catch (error) {\n return {\n ok: false,\n code: \"authentication_failed\",\n reason: `could not authenticate to GitHub: ${redactedErrorMessage(error)}`,\n };\n }\n\n let pull: GitHubPublicationPullRequest;\n try {\n pull = await client.pullRequest({ pullRequestNumber: input.prNumber });\n } catch (error) {\n return {\n ok: false,\n code:\n error instanceof GitHubPublicationApiError && error.status === 404\n ? \"pull_request_not_found\"\n : \"pull_request_lookup_failed\",\n reason:\n error instanceof GitHubPublicationApiError && error.status === 404\n ? `pull request #${input.prNumber} does not exist on ${repository.slug}`\n : `could not verify pull request #${input.prNumber} on ${repository.slug}`,\n };\n }\n if (pull.pullRequestUrl !== input.prUrl) {\n return {\n ok: false,\n code: \"pull_request_url_mismatch\",\n reason: `prUrl does not match pull request #${input.prNumber} on ${repository.slug}`,\n };\n }\n if (pull.state !== \"open\") {\n return {\n ok: false,\n code: \"pull_request_closed\",\n reason: `pull request #${input.prNumber} is not open`,\n };\n }\n const branch = factoryTaskBranch(input.taskId);\n if (\n pull.head.repository !== repository.slug ||\n (linkedChange === null && pull.head.branch !== branch)\n ) {\n return {\n ok: false,\n code: \"pull_request_branch_mismatch\",\n reason:\n linkedChange === null\n ? `pull request #${input.prNumber} is not on this task's branch ${branch}`\n : `pull request #${input.prNumber} is not a same-repository branch`,\n };\n }\n if (pull.draft) {\n return {\n ok: false,\n code: \"pull_request_draft\",\n reason: `pull request #${input.prNumber} is still a draft; call push_checkpoint with ready: true first`,\n };\n }\n return { ok: true };\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"github-publisher.mjs","names":[],"sources":["../../src/pull-requests/github-publisher.ts"],"sourcesContent":["import { redactedErrorMessage } from \"../errors\";\nimport {\n createGitHubPublicationClient,\n factorySessionBranch,\n factoryTaskBranch,\n GitHubPublicationApiError,\n isSafeGitRef,\n readCommittedGitChange,\n runSandboxGit,\n type CommittedGitChange,\n type CreatedGitHubPublicationPullRequest,\n type GitHubPublicationClient,\n type GitHubPublicationClientOptions,\n type GitHubPublicationPullRequest,\n type SandboxGit,\n} from \"../github-publication\";\nimport type { Change } from \"../schema/change\";\nimport type { TaskId } from \"../schema/id\";\nimport type { Repository, RepositorySlug } from \"../schema/repository\";\nimport type { AgentSessionId } from \"../schema/session\";\nimport type { FactoryStores } from \"../store/engine\";\nimport type {\n OpenPullRequestInput,\n PublishedPullRequest,\n PullRequestPublicationGateInput,\n PullRequestPublicationGateResult,\n PullRequestPublicationOwner,\n PushCheckpointInput,\n TaskPullRequest,\n} from \"./contracts\";\n\n/** Repository credential and client factories required by software publication. */\nexport interface GitHubPullRequestPublisherOptions {\n tokenForRepository(repositorySlug: RepositorySlug): Promise<string>;\n fetch?: typeof globalThis.fetch;\n createClient?: (options: GitHubPublicationClientOptions) => GitHubPublicationClient;\n /** Publishes the initial commit, branch, and pull request through an application credential boundary. */\n publishPullRequest?: (input: {\n repository: RepositorySlug;\n baseCommit: string;\n tree: CommittedGitChange[\"tree\"];\n commitMessage: string;\n branch: string;\n title: string;\n body: string;\n baseBranch: string;\n draft: boolean;\n }) => Promise<CreatedGitHubPublicationPullRequest & { commitSha: string }>;\n}\n\n/** Task- and session-aware operations for idempotent GitHub pull-request publication. */\nexport interface GitHubPullRequestPublisher {\n requireTaskPullRequest(input: RequireTaskPullRequestInput): Promise<TaskPullRequest>;\n openPullRequest(input: PublishGitHubPullRequestInput): Promise<PublishedPullRequest>;\n pushCheckpoint(input: PushGitHubCheckpointInput): Promise<PublishedPullRequest>;\n}\n\n/** Repository and Task whose owned pull request must already exist. */\nexport interface RequireTaskPullRequestInput {\n repository: Repository;\n taskId: TaskId;\n}\n\n/** Sandbox checkout and publication request used to open a GitHub pull request. */\nexport interface PublishGitHubPullRequestInput {\n sandbox: SandboxGit;\n repository: Repository;\n owner: PullRequestPublicationOwner;\n pullRequest: OpenPullRequestInput;\n}\n\n/** Sandbox checkout and optional linked Change used to publish a checkpoint. */\nexport interface PushGitHubCheckpointInput {\n sandbox: SandboxGit;\n repository: Repository;\n owner: PullRequestPublicationOwner;\n checkpoint: PushCheckpointInput;\n linkedChange?: Pick<Change, \"prNumber\" | \"prUrl\">;\n}\n\nasync function clientForRepository(\n options: GitHubPullRequestPublisherOptions,\n repository: Repository,\n): Promise<GitHubPublicationClient> {\n const token = await options.tokenForRepository(repository.slug);\n return (options.createClient ?? createGitHubPublicationClient)({\n repository: repository.slug,\n token,\n fetch: options.fetch,\n });\n}\n\nfunction repositoryOwner(repository: Repository): string {\n const owner = repository.slug.split(\"/\")[0];\n if (owner === undefined || owner === \"\") {\n throw new Error(`invalid repository slug: ${repository.slug}`);\n }\n return owner;\n}\n\nasync function findOpenPullRequest(\n client: GitHubPublicationClient,\n repository: Repository,\n taskId: TaskId,\n): Promise<GitHubPublicationPullRequest & { branch: string }> {\n const branch = factoryTaskBranch(taskId);\n const open = await client.pullRequests({\n state: \"open\",\n head: `${repositoryOwner(repository)}:${branch}`,\n });\n const pull = open.find((candidate) => candidate.head.repository === repository.slug);\n if (pull === undefined) {\n throw new Error(`no open pull request on ${branch}; call open_pull_request first`);\n }\n return { ...pull, branch };\n}\n\nasync function findLinkedPullRequest(\n client: GitHubPublicationClient,\n repository: Repository,\n change: Pick<Change, \"prNumber\" | \"prUrl\">,\n): Promise<GitHubPublicationPullRequest & { branch: string }> {\n const pull = await client.pullRequest({ pullRequestNumber: change.prNumber });\n const branch = pull.head.branch;\n if (\n pull.state !== \"open\" ||\n pull.pullRequestUrl !== change.prUrl ||\n pull.head.repository !== repository.slug ||\n !isSafeGitRef(branch)\n ) {\n throw new Error(\n `Change pull request #${change.prNumber} is not an open same-repository branch`,\n );\n }\n return { ...pull, branch };\n}\n\nasync function createCommit(\n client: GitHubPublicationClient,\n change: CommittedGitChange,\n message: string,\n parent = change.base,\n): Promise<string> {\n const baseCommit = await client.commit({ sha: change.base });\n const tree = await client.createTree({ baseTreeSha: baseCommit.treeSha, entries: change.tree });\n return client.createCommit({ message, treeSha: tree, parentShas: [parent] });\n}\n\nasync function sessionPullRequest(\n client: GitHubPublicationClient,\n repository: Repository,\n branch: string,\n): Promise<GitHubPublicationPullRequest | undefined> {\n const pulls = await client.pullRequests({\n state: \"all\",\n head: `${repositoryOwner(repository)}:${branch}`,\n });\n const pull = pulls.find(\n (candidate) =>\n candidate.head.branch === branch && candidate.head.repository === repository.slug,\n );\n if (pull !== undefined && pull.state !== \"open\") {\n throw new Error(\n \"This session's pull request is merged or closed. Start a new session for a new pull request.\",\n );\n }\n return pull;\n}\n\nasync function sessionHead(sandbox: SandboxGit): Promise<string> {\n const result = await sandbox.run({ command: \"git -C repo config --get factory.publishedHead\" });\n if (result.exitCode === 1) return \"\";\n if (result.exitCode !== undefined && result.exitCode !== 0) {\n throw new Error(\"Could not read the session's published head.\");\n }\n return result.stdout.trim();\n}\n\nasync function rememberSessionHead(sandbox: SandboxGit, sha: string): Promise<void> {\n if (!/^[0-9a-f]{40}$/u.test(sha)) throw new Error(\"GitHub returned an invalid commit SHA.\");\n await runSandboxGit({ sandbox, args: [\"config\", \"factory.publishedHead\", sha] });\n}\n\nasync function assertSessionCheckout(sandbox: SandboxGit, repository: Repository): Promise<void> {\n if (\n (await runSandboxGit({ sandbox, args: [\"remote\", \"get-url\", \"origin\"] })).trim() !==\n `https://github.com/${repository.slug}.git`\n ) {\n throw new Error(`sandbox checkout does not match session repository ${repository.slug}`);\n }\n if ((await runSandboxGit({ sandbox, args: [\"status\", \"--porcelain\"] })).trim() !== \"\") {\n throw new Error(\"Commit your changes before publishing the pull request.\");\n }\n}\n\nasync function acceptSessionHead(\n sandbox: SandboxGit,\n client: GitHubPublicationClient,\n sha: string,\n): Promise<boolean> {\n const expected = await sessionHead(sandbox);\n const commit = await client.commit({ sha });\n const unchanged =\n commit.treeSha ===\n (await runSandboxGit({ sandbox, args: [\"rev-parse\", \"HEAD^{tree}\"] })).trim();\n const recovered = unchanged && (expected === \"\" || commit.parentShas.includes(expected));\n if (expected !== sha && !recovered) {\n throw new Error(\n \"The session PR changed since checkout or publication. Refusing to overwrite newer commits; reload its checkout before continuing.\",\n );\n }\n await rememberSessionHead(sandbox, sha);\n return unchanged;\n}\n\nfunction sessionPublication(\n pull: Pick<GitHubPublicationPullRequest, \"pullRequestNumber\" | \"pullRequestUrl\" | \"draft\">,\n branch: string,\n changedFiles = 0,\n): PublishedPullRequest {\n return {\n prNumber: pull.pullRequestNumber,\n prUrl: pull.pullRequestUrl,\n branch,\n changedFiles,\n draft: pull.draft,\n };\n}\n\n/** Supplied software workflow for safe Task- and session-owned GitHub publication. */\nexport function createGitHubPullRequestPublisher(\n options: GitHubPullRequestPublisherOptions,\n): GitHubPullRequestPublisher {\n async function requireTaskPullRequest(\n input: RequireTaskPullRequestInput,\n ): Promise<TaskPullRequest> {\n const client = await clientForRepository(options, input.repository);\n const pull = await findOpenPullRequest(client, input.repository, input.taskId);\n return {\n prNumber: pull.pullRequestNumber,\n prUrl: pull.pullRequestUrl,\n branch: pull.branch,\n };\n }\n\n async function openSessionPullRequest(\n sandbox: SandboxGit,\n repository: Repository,\n sessionId: AgentSessionId,\n input: OpenPullRequestInput,\n ): Promise<PublishedPullRequest> {\n const branch = factorySessionBranch(sessionId);\n await assertSessionCheckout(sandbox, repository);\n if (options.publishPullRequest !== undefined) {\n const change = await readCommittedGitChange({ sandbox, repositorySlug: repository.slug });\n const published = await options.publishPullRequest({\n repository: repository.slug,\n baseCommit: change.base,\n tree: change.tree,\n commitMessage: change.commitMessage,\n branch,\n title: input.title,\n body: input.body,\n baseBranch: repository.defaultBranch,\n draft: true,\n });\n await rememberSessionHead(sandbox, published.commitSha);\n return {\n prNumber: published.pullRequestNumber,\n prUrl: published.pullRequestUrl,\n branch,\n changedFiles: change.changedFiles,\n draft: published.draft,\n };\n }\n const client = await clientForRepository(options, repository);\n const existing = await sessionPullRequest(client, repository, branch);\n if (existing !== undefined) {\n await acceptSessionHead(sandbox, client, existing.head.sha);\n return sessionPublication(existing, branch);\n }\n\n let sha = await client.branchHead({ branch });\n let changedFiles = 0;\n if (sha === null) {\n const change = await readCommittedGitChange({ sandbox, repositorySlug: repository.slug });\n changedFiles = change.changedFiles;\n sha = await createCommit(client, change, `${input.title}\\n\\n${input.body}`);\n try {\n await client.createBranch({ branch, sha });\n } catch (error) {\n if ((await client.branchHead({ branch })) !== sha) throw error;\n }\n await rememberSessionHead(sandbox, sha);\n } else {\n await acceptSessionHead(sandbox, client, sha);\n }\n try {\n const pull = await client.createPullRequest({\n title: input.title,\n body: input.body,\n head: branch,\n base: repository.defaultBranch,\n draft: true,\n });\n return {\n prNumber: pull.pullRequestNumber,\n prUrl: pull.pullRequestUrl,\n branch,\n changedFiles,\n draft: pull.draft,\n };\n } catch (error) {\n const created = await sessionPullRequest(client, repository, branch);\n if (created === undefined) throw error;\n return sessionPublication(created, branch, changedFiles);\n }\n }\n\n async function openPullRequest(\n input: PublishGitHubPullRequestInput,\n ): Promise<PublishedPullRequest> {\n if (typeof input.owner !== \"string\") {\n return openSessionPullRequest(\n input.sandbox,\n input.repository,\n input.owner.sessionId,\n input.pullRequest,\n );\n }\n const change = await readCommittedGitChange({\n sandbox: input.sandbox,\n repositorySlug: input.repository.slug,\n });\n if (options.publishPullRequest !== undefined) {\n const branch = factoryTaskBranch(input.owner);\n const published = await options.publishPullRequest({\n repository: input.repository.slug,\n baseCommit: change.base,\n tree: change.tree,\n commitMessage: change.commitMessage,\n branch,\n title: input.pullRequest.title,\n body: input.pullRequest.body,\n baseBranch: input.repository.defaultBranch,\n draft: true,\n });\n return {\n prNumber: published.pullRequestNumber,\n prUrl: published.pullRequestUrl,\n branch,\n changedFiles: change.changedFiles,\n draft: published.draft,\n };\n }\n const client = await clientForRepository(options, input.repository);\n const branch = factoryTaskBranch(input.owner);\n const existing = (\n await client.pullRequests({\n state: \"all\",\n head: `${repositoryOwner(input.repository)}:${branch}`,\n })\n ).find((pull) => pull.head.branch === branch && pull.head.repository === input.repository.slug);\n if (existing !== undefined) {\n if (existing.state !== \"open\")\n throw new Error(\n \"This Task's pull request is merged or closed. Start new work for a new pull request.\",\n );\n return {\n prNumber: existing.pullRequestNumber,\n prUrl: existing.pullRequestUrl,\n branch,\n changedFiles: 0,\n draft: existing.draft,\n };\n }\n const sha = await createCommit(\n client,\n change,\n `${input.pullRequest.title}\\n\\n${input.pullRequest.body}`,\n );\n await client.createBranch({ branch, sha });\n const pull = await client.createPullRequest({\n title: input.pullRequest.title,\n body: input.pullRequest.body,\n head: branch,\n base: input.repository.defaultBranch,\n draft: true,\n });\n return {\n prNumber: pull.pullRequestNumber,\n prUrl: pull.pullRequestUrl,\n branch,\n changedFiles: change.changedFiles,\n draft: pull.draft,\n };\n }\n\n async function pushSessionCheckpoint(\n sandbox: SandboxGit,\n repository: Repository,\n sessionId: AgentSessionId,\n input: PushCheckpointInput,\n ): Promise<PublishedPullRequest> {\n const branch = factorySessionBranch(sessionId);\n await assertSessionCheckout(sandbox, repository);\n const client = await clientForRepository(options, repository);\n const pull = await sessionPullRequest(client, repository, branch);\n if (pull === undefined) {\n throw new Error(`no open pull request on ${branch}; call open_pull_request first`);\n }\n const unchanged = await acceptSessionHead(sandbox, client, pull.head.sha);\n let changedFiles = 0;\n if (!unchanged) {\n const change = await readCommittedGitChange({\n sandbox,\n repositorySlug: repository.slug,\n allowEmpty: true,\n });\n changedFiles = change.changedFiles;\n const sha = await createCommit(\n client,\n change,\n `${pull.title}\\n\\n${pull.body ?? \"\"}`,\n pull.head.sha,\n );\n await client.updateBranch({ branch, sha, force: false });\n await rememberSessionHead(sandbox, sha);\n }\n const published = sessionPublication(pull, branch, changedFiles);\n if (input.ready && published.draft) {\n await client.markReady({ nodeId: pull.nodeId });\n published.draft = false;\n }\n return published;\n }\n\n async function pushCheckpoint(input: PushGitHubCheckpointInput): Promise<PublishedPullRequest> {\n if (typeof input.owner !== \"string\") {\n return pushSessionCheckpoint(\n input.sandbox,\n input.repository,\n input.owner.sessionId,\n input.checkpoint,\n );\n }\n const client = await clientForRepository(options, input.repository);\n const pull =\n input.linkedChange === undefined\n ? await findOpenPullRequest(client, input.repository, input.owner)\n : await findLinkedPullRequest(client, input.repository, input.linkedChange);\n const change = await readCommittedGitChange({\n sandbox: input.sandbox,\n repositorySlug: input.repository.slug,\n baseRef:\n input.linkedChange === undefined\n ? \"origin/HEAD\"\n : `refs/remotes/origin/pr-${input.linkedChange.prNumber}`,\n });\n if (input.linkedChange !== undefined && change.base !== pull.head.sha) {\n throw new Error(\n `pull request #${input.linkedChange.prNumber} changed since it was fetched; fetch it again`,\n );\n }\n const sha = await createCommit(client, change, `${pull.title}\\n\\n${pull.body ?? \"\"}`);\n await client.updateBranch({\n branch: pull.branch,\n sha,\n force: input.linkedChange === undefined,\n });\n let draft = pull.draft;\n if (input.checkpoint.ready && draft) {\n await client.markReady({ nodeId: pull.nodeId });\n draft = false;\n }\n return {\n prNumber: pull.pullRequestNumber,\n prUrl: pull.pullRequestUrl,\n branch: pull.branch,\n changedFiles: change.changedFiles,\n draft,\n };\n }\n\n return Object.freeze({ requireTaskPullRequest, openPullRequest, pushCheckpoint });\n}\n\n/** Store and repository lookup dependencies for the pull-request publication gate. */\nexport interface GitHubPullRequestPublicationGateOptions extends GitHubPullRequestPublisherOptions {\n stores: Pick<FactoryStores, \"tasks\" | \"changes\">;\n repositoryForTask(taskId: TaskId): Promise<Repository>;\n}\n\n/** Validates that one pull request belongs to its exact GitHub Task and Change context. */\nexport type GitHubPullRequestPublicationGate = (\n input: PullRequestPublicationGateInput,\n) => Promise<PullRequestPublicationGateResult>;\n\n/** Fail-closed gate for the pull request a supplied code-change workflow records. */\nexport function createGitHubPullRequestPublicationGate(\n options: GitHubPullRequestPublicationGateOptions,\n): GitHubPullRequestPublicationGate {\n return async (\n input: PullRequestPublicationGateInput,\n ): Promise<PullRequestPublicationGateResult> => {\n let repository: Repository;\n let linkedChange: Awaited<ReturnType<FactoryStores[\"changes\"][\"get\"]>>;\n try {\n repository = await options.repositoryForTask(input.taskId);\n if (repository.id !== input.repositoryId) {\n return {\n ok: false,\n code: \"repository_mismatch\",\n reason: \"repositoryId does not match the task repository\",\n };\n }\n const task = await options.stores.tasks.get(input.taskId);\n if (task === null) {\n return {\n ok: false,\n code: \"task_not_found\",\n reason: `task ${input.taskId} does not exist`,\n };\n }\n linkedChange =\n task.changeId === undefined ? null : await options.stores.changes.get(task.changeId);\n if (task.changeId !== undefined && linkedChange === null) {\n return {\n ok: false,\n code: \"change_not_found\",\n reason: `change ${task.changeId} does not exist`,\n };\n }\n if (\n linkedChange !== null &&\n (linkedChange.repositoryId !== repository.id ||\n linkedChange.prNumber !== input.prNumber ||\n linkedChange.prUrl !== input.prUrl)\n ) {\n return {\n ok: false,\n code: \"linked_change_mismatch\",\n reason: \"pull request does not match the Change linked to this task\",\n };\n }\n } catch (error) {\n return {\n ok: false,\n code: \"repository_lookup_failed\",\n reason: redactedErrorMessage(error),\n };\n }\n\n let client: GitHubPublicationClient;\n try {\n client = await clientForRepository(options, repository);\n } catch (error) {\n return {\n ok: false,\n code: \"authentication_failed\",\n reason: `could not authenticate to GitHub: ${redactedErrorMessage(error)}`,\n };\n }\n\n let pull: GitHubPublicationPullRequest;\n try {\n pull = await client.pullRequest({ pullRequestNumber: input.prNumber });\n } catch (error) {\n return {\n ok: false,\n code:\n error instanceof GitHubPublicationApiError && error.status === 404\n ? \"pull_request_not_found\"\n : \"pull_request_lookup_failed\",\n reason:\n error instanceof GitHubPublicationApiError && error.status === 404\n ? `pull request #${input.prNumber} does not exist on ${repository.slug}`\n : `could not verify pull request #${input.prNumber} on ${repository.slug}`,\n };\n }\n if (pull.pullRequestUrl !== input.prUrl) {\n return {\n ok: false,\n code: \"pull_request_url_mismatch\",\n reason: `prUrl does not match pull request #${input.prNumber} on ${repository.slug}`,\n };\n }\n if (pull.state !== \"open\") {\n return {\n ok: false,\n code: \"pull_request_closed\",\n reason: `pull request #${input.prNumber} is not open`,\n };\n }\n const branch = factoryTaskBranch(input.taskId);\n if (\n pull.head.repository !== repository.slug ||\n (linkedChange === null && pull.head.branch !== branch)\n ) {\n return {\n ok: false,\n code: \"pull_request_branch_mismatch\",\n reason:\n linkedChange === null\n ? `pull request #${input.prNumber} is not on this task's branch ${branch}`\n : `pull request #${input.prNumber} is not a same-repository branch`,\n };\n }\n if (pull.draft) {\n return {\n ok: false,\n code: \"pull_request_draft\",\n reason: `pull request #${input.prNumber} is still a draft; call push_checkpoint with ready: true first`,\n };\n }\n return { ok: true };\n };\n}\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" />
1
2
  import { OpenPullRequest, OpenPullRequestInput, PublishedPullRequest, PullRequestLabelAction, PullRequestLabelResult, PullRequestPublicationGateFailure, PullRequestPublicationGateFailureCode, PullRequestPublicationGateInput, PullRequestPublicationGateResult, PullRequestPublicationGateSuccess, PullRequestPublicationOwner, PushCheckpointInput, TaskPullRequest } from "./pull-requests/contracts.mjs";
2
3
  import { GitHubFetchPullRequestFailure, GitHubFetchPullRequestFailureCode, GitHubFetchPullRequestInput, GitHubFetchPullRequestResult, GitHubFetchPullRequestSuccess, GitHubFetchPullRequestTool, GitHubFetchPullRequestToolOptions, GitHubFetchPullRequestToolStores, createGitHubFetchPullRequestTool, gitHubFetchPullRequestInputSchema } from "./pull-requests/eve-fetch.mjs";
3
4
  import { MergeConflictPayload, mergeConflictPayloadSchema } from "./merge-resolution/contracts.mjs";
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" />
1
2
  import { CommandSandbox, CommandSandboxRunInput, CommandSandboxRunResult } from "./command.mjs";
2
3
  import { HarnessWorkspace, SandboxOutputPath, SandboxSession, SandboxWorkspaceHandle, defaultHarnessWorkDir, harnessWorkspace, parseSandboxOutputPath } from "./workspace.mjs";
3
4
  import { ConfiguredHarnessRunner, HarnessAgentRunnerOptions, HarnessRunner, HarnessSession, HarnessTurn, HarnessTurnUsage, OpenHarnessSessionOptions, SandboxHandle, SandboxRunCommandInput, SandboxRunCommandResult, createHarnessAgentRunner } from "./harness-runner.mjs";
@@ -15,7 +15,25 @@ interface AgentRouteBindingInput<Id extends string = string, Version extends num
15
15
  readonly id: Id;
16
16
  readonly version: Version;
17
17
  }
18
- /** Validates and brands an exact agent-route binding while preserving literal identity. */
18
+ /**
19
+ * Validates and brands an exact agent-route binding while preserving literal identity.
20
+ *
21
+ * @remarks
22
+ * This creates an identity value only; it does not prove that the route is registered. Resolve it
23
+ * through `defineAgentRoutes(...).get(binding)` or let dispatch compare it with the Task's stored
24
+ * route. Both ID and version are required, so recovery never selects an implicit latest version.
25
+ *
26
+ * @param input - Plain route ID and positive exact version.
27
+ * @returns A validated binding retaining the input's literal ID and version types.
28
+ * @throws A Zod validation error when either field is invalid.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * import { parseAgentRouteBinding } from "@vercel/factory/workflows";
33
+ *
34
+ * const route = parseAgentRouteBinding({ id: "incident-worker", version: 1 });
35
+ * ```
36
+ */
19
37
  declare function parseAgentRouteBinding<const Input extends AgentRouteBindingInput>(input: Input): AgentRouteBinding<Input["id"], Input["version"]>;
20
38
  //#endregion
21
39
  export { AgentRouteBinding, AgentRouteBindingInput, agentRouteBindingSchema, parseAgentRouteBinding };
@@ -5,7 +5,25 @@ const agentRouteBindingSchema = z.strictObject({
5
5
  id: z.string().regex(/^[a-z][a-z0-9-]{0,63}$/u).describe("Stable route identifier selected by a workflow."),
6
6
  version: z.number().int().positive().describe("Exact positive version of the selected route.")
7
7
  }).describe("Exact agent-route identity; lookup never defaults or selects a latest version.").brand();
8
- /** Validates and brands an exact agent-route binding while preserving literal identity. */
8
+ /**
9
+ * Validates and brands an exact agent-route binding while preserving literal identity.
10
+ *
11
+ * @remarks
12
+ * This creates an identity value only; it does not prove that the route is registered. Resolve it
13
+ * through `defineAgentRoutes(...).get(binding)` or let dispatch compare it with the Task's stored
14
+ * route. Both ID and version are required, so recovery never selects an implicit latest version.
15
+ *
16
+ * @param input - Plain route ID and positive exact version.
17
+ * @returns A validated binding retaining the input's literal ID and version types.
18
+ * @throws A Zod validation error when either field is invalid.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * import { parseAgentRouteBinding } from "@vercel/factory/workflows";
23
+ *
24
+ * const route = parseAgentRouteBinding({ id: "incident-worker", version: 1 });
25
+ * ```
26
+ */
9
27
  function parseAgentRouteBinding(input) {
10
28
  return agentRouteBindingSchema.parse(input);
11
29
  }
@@ -1 +1 @@
1
- {"version":3,"file":"agent-route.mjs","names":[],"sources":["../../src/schema/agent-route.ts"],"sourcesContent":["import { z } from \"zod\";\n\n/** Validates an exact agent-route identity used for lookup and persisted recovery. */\nexport const agentRouteBindingSchema = z\n .strictObject({\n id: z\n .string()\n .regex(/^[a-z][a-z0-9-]{0,63}$/u)\n .describe(\"Stable route identifier selected by a workflow.\"),\n version: z.number().int().positive().describe(\"Exact positive version of the selected route.\"),\n })\n .describe(\"Exact agent-route identity; lookup never defaults or selects a latest version.\")\n .brand<\"AgentRouteBinding\">();\n\n/** Exact identifier and version selecting one registered agent route. */\nexport type AgentRouteBinding<\n Id extends string = string,\n Version extends number = number,\n> = z.infer<typeof agentRouteBindingSchema> & {\n readonly id: Id;\n readonly version: Version;\n};\n\n/** Plain identifier and version accepted when constructing an agent-route binding. */\nexport interface AgentRouteBindingInput<\n Id extends string = string,\n Version extends number = number,\n> {\n readonly id: Id;\n readonly version: Version;\n}\n\n/** Validates and brands an exact agent-route binding while preserving literal identity. */\nexport function parseAgentRouteBinding<const Input extends AgentRouteBindingInput>(\n input: Input,\n): AgentRouteBinding<Input[\"id\"], Input[\"version\"]> {\n return agentRouteBindingSchema.parse(input) as AgentRouteBinding<Input[\"id\"], Input[\"version\"]>;\n}\n"],"mappings":";;;AAGA,MAAa,0BAA0B,EACpC,aAAa;CACZ,IAAI,EACD,OAAO,CAAC,CACR,MAAM,yBAAyB,CAAC,CAChC,SAAS,iDAAiD;CAC7D,SAAS,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,+CAA+C;AAC/F,CAAC,CAAC,CACD,SAAS,gFAAgF,CAAC,CAC1F,MAA2B;;AAqB9B,SAAgB,uBACd,OACkD;CAClD,OAAO,wBAAwB,MAAM,KAAK;AAC5C"}
1
+ {"version":3,"file":"agent-route.mjs","names":[],"sources":["../../src/schema/agent-route.ts"],"sourcesContent":["import { z } from \"zod\";\n\n/** Validates an exact agent-route identity used for lookup and persisted recovery. */\nexport const agentRouteBindingSchema = z\n .strictObject({\n id: z\n .string()\n .regex(/^[a-z][a-z0-9-]{0,63}$/u)\n .describe(\"Stable route identifier selected by a workflow.\"),\n version: z.number().int().positive().describe(\"Exact positive version of the selected route.\"),\n })\n .describe(\"Exact agent-route identity; lookup never defaults or selects a latest version.\")\n .brand<\"AgentRouteBinding\">();\n\n/** Exact identifier and version selecting one registered agent route. */\nexport type AgentRouteBinding<\n Id extends string = string,\n Version extends number = number,\n> = z.infer<typeof agentRouteBindingSchema> & {\n readonly id: Id;\n readonly version: Version;\n};\n\n/** Plain identifier and version accepted when constructing an agent-route binding. */\nexport interface AgentRouteBindingInput<\n Id extends string = string,\n Version extends number = number,\n> {\n readonly id: Id;\n readonly version: Version;\n}\n\n/**\n * Validates and brands an exact agent-route binding while preserving literal identity.\n *\n * @remarks\n * This creates an identity value only; it does not prove that the route is registered. Resolve it\n * through `defineAgentRoutes(...).get(binding)` or let dispatch compare it with the Task's stored\n * route. Both ID and version are required, so recovery never selects an implicit latest version.\n *\n * @param input - Plain route ID and positive exact version.\n * @returns A validated binding retaining the input's literal ID and version types.\n * @throws A Zod validation error when either field is invalid.\n *\n * @example\n * ```ts\n * import { parseAgentRouteBinding } from \"@vercel/factory/workflows\";\n *\n * const route = parseAgentRouteBinding({ id: \"incident-worker\", version: 1 });\n * ```\n */\nexport function parseAgentRouteBinding<const Input extends AgentRouteBindingInput>(\n input: Input,\n): AgentRouteBinding<Input[\"id\"], Input[\"version\"]> {\n return agentRouteBindingSchema.parse(input) as AgentRouteBinding<Input[\"id\"], Input[\"version\"]>;\n}\n"],"mappings":";;;AAGA,MAAa,0BAA0B,EACpC,aAAa;CACZ,IAAI,EACD,OAAO,CAAC,CACR,MAAM,yBAAyB,CAAC,CAChC,SAAS,iDAAiD;CAC7D,SAAS,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,+CAA+C;AAC/F,CAAC,CAAC,CACD,SAAS,gFAAgF,CAAC,CAC1F,MAA2B;;;;;;;;;;;;;;;;;;;;AAuC9B,SAAgB,uBACd,OACkD;CAClD,OAAO,wBAAwB,MAAM,KAAK;AAC5C"}