@vercel/factory 0.0.15 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +356 -0
- package/README.md +49 -261
- package/dist/agent-routes.d.mts +47 -3
- package/dist/agent-routes.mjs +28 -1
- package/dist/agent-routes.mjs.map +1 -1
- package/dist/api-contracts.d.mts +20 -1
- package/dist/api-contracts.mjs +2 -1
- package/dist/api-contracts.mjs.map +1 -1
- package/dist/api.d.mts +45 -2
- package/dist/api.mjs +199 -10
- package/dist/api.mjs.map +1 -1
- package/dist/approval-contracts.d.mts +6 -0
- package/dist/blob/index.d.mts +51 -14
- package/dist/blob/index.mjs +26 -10
- package/dist/blob/index.mjs.map +1 -1
- package/dist/budget.d.mts +7 -0
- package/dist/budget.mjs +6 -0
- package/dist/budget.mjs.map +1 -1
- package/dist/build-factory.d.mts +1 -0
- package/dist/change-verification/dispatch.d.mts +16 -3
- package/dist/change-verification/dispatch.mjs +45 -7
- package/dist/change-verification/dispatch.mjs.map +1 -1
- package/dist/change-verification/eve-tool.d.mts +2 -1
- package/dist/change-verification/eve-tool.mjs +35 -47
- package/dist/change-verification/eve-tool.mjs.map +1 -1
- package/dist/change-verification/result.mjs +130 -0
- package/dist/change-verification/result.mjs.map +1 -0
- package/dist/changes/eve-record-change.d.mts +2 -2
- package/dist/changes/eve-record-change.mjs +43 -9
- package/dist/changes/eve-record-change.mjs.map +1 -1
- package/dist/changes.d.mts +4 -3
- package/dist/changes.mjs +2 -2
- package/dist/changes.mjs.map +1 -1
- package/dist/client-events.d.mts +10 -3
- package/dist/client-events.mjs +6 -2
- package/dist/client-events.mjs.map +1 -1
- package/dist/client-stream.mjs +8 -2
- package/dist/client-stream.mjs.map +1 -1
- package/dist/client-transcript.mjs +5 -1
- package/dist/client-transcript.mjs.map +1 -1
- package/dist/client.d.mts +121 -12
- package/dist/client.mjs +117 -9
- package/dist/client.mjs.map +1 -1
- package/dist/code-review/contracts.d.mts +1 -0
- package/dist/code-review/eve-post-review.d.mts +4 -4
- package/dist/code-review/eve-post-review.mjs +29 -17
- package/dist/code-review/eve-post-review.mjs.map +1 -1
- package/dist/code-review/eve-review-comments.d.mts +13 -2
- package/dist/code-review/eve-review-comments.mjs +45 -11
- package/dist/code-review/eve-review-comments.mjs.map +1 -1
- package/dist/code-review/github-reporter.d.mts +2 -0
- package/dist/code-review/github-reporter.mjs +7 -5
- package/dist/code-review/github-reporter.mjs.map +1 -1
- package/dist/code-review.d.mts +3 -2
- package/dist/deepsec/eve-tool.mjs +3 -1
- package/dist/deepsec/eve-tool.mjs.map +1 -1
- package/dist/dispatch.d.mts +79 -8
- package/dist/dispatch.mjs +68 -9
- package/dist/dispatch.mjs.map +1 -1
- package/dist/eve/index.d.mts +70 -10
- package/dist/eve/index.mjs +93 -22
- package/dist/eve/index.mjs.map +1 -1
- package/dist/eve/invoke.mjs +24 -11
- package/dist/eve/invoke.mjs.map +1 -1
- package/dist/eve/session-client.d.mts +122 -4
- package/dist/eve/session-client.mjs +127 -13
- package/dist/eve/session-client.mjs.map +1 -1
- package/dist/eve/task-execution.d.mts +380 -0
- package/dist/eve/task-execution.mjs +57 -2
- package/dist/eve/task-execution.mjs.map +1 -1
- package/dist/eve/task-session.d.mts +44 -2
- package/dist/eve/task-session.mjs +44 -2
- package/dist/eve/task-session.mjs.map +1 -1
- package/dist/eve/transcript.mjs +5 -1
- package/dist/eve/transcript.mjs.map +1 -1
- package/dist/execution.d.mts +117 -9
- package/dist/execution.mjs +76 -6
- package/dist/execution.mjs.map +1 -1
- package/dist/finding-remediation/admission.d.mts +2 -0
- package/dist/finding-remediation/admission.mjs +4 -1
- package/dist/finding-remediation/admission.mjs.map +1 -1
- package/dist/findings.d.mts +1 -0
- package/dist/github-publication.d.mts +1 -0
- package/dist/github-publication.mjs +97 -84
- package/dist/github-publication.mjs.map +1 -1
- package/dist/github-transfer.d.mts +15 -6
- package/dist/github-transfer.mjs +214 -66
- package/dist/github-transfer.mjs.map +1 -1
- package/dist/github.d.mts +51 -11
- package/dist/github.mjs +126 -24
- package/dist/github.mjs.map +1 -1
- package/dist/inbox-activity.d.mts +53 -0
- package/dist/inbox-activity.mjs +41 -0
- package/dist/inbox-activity.mjs.map +1 -0
- package/dist/index.d.mts +3 -1
- package/dist/index.mjs +3 -2
- package/dist/intake-contracts.d.mts +0 -1
- package/dist/integrations/deepsec.d.mts +1 -0
- package/dist/integrations/github.d.mts +2 -2
- package/dist/integrations/github.mjs +2 -2
- package/dist/integrations/slack.d.mts +3 -1
- package/dist/integrations/slack.mjs +3 -1
- package/dist/integrations/vercel.d.mts +4 -2
- package/dist/integrations/vercel.mjs +3 -2
- package/dist/merge-resolution/eve-tools.d.mts +1 -0
- package/dist/merge-resolution/eve-tools.mjs +7 -2
- package/dist/merge-resolution/eve-tools.mjs.map +1 -1
- package/dist/model-settings.d.mts +41 -0
- package/dist/model-settings.mjs +35 -0
- package/dist/model-settings.mjs.map +1 -0
- package/dist/planning/reconcile.mjs +6 -0
- package/dist/planning/reconcile.mjs.map +1 -1
- package/dist/postgres/index.d.mts +43 -2
- package/dist/postgres/index.mjs +40 -2
- package/dist/postgres/index.mjs.map +1 -1
- package/dist/presets/software-development/dispatch.d.mts +4 -1
- package/dist/presets/software-development/dispatch.mjs +2 -1
- package/dist/presets/software-development/dispatch.mjs.map +1 -1
- package/dist/presets/software-development/task-communication.d.mts +1 -0
- package/dist/presets/software-development/task-communication.mjs +48 -11
- package/dist/presets/software-development/task-communication.mjs.map +1 -1
- package/dist/presets/software-development.d.mts +1 -0
- package/dist/pull-requests/github-publisher.d.mts +15 -1
- package/dist/pull-requests/github-publisher.mjs +61 -1
- package/dist/pull-requests/github-publisher.mjs.map +1 -1
- package/dist/pull-requests.d.mts +1 -0
- package/dist/sandbox/index.d.mts +1 -0
- package/dist/schema/agent-route.d.mts +19 -1
- package/dist/schema/agent-route.mjs +19 -1
- package/dist/schema/agent-route.mjs.map +1 -1
- package/dist/schema/factory-config.d.mts +27 -0
- package/dist/schema/factory-config.mjs +33 -3
- package/dist/schema/factory-config.mjs.map +1 -1
- package/dist/schema/repository.d.mts +4 -0
- package/dist/schema/repository.mjs +5 -1
- package/dist/schema/repository.mjs.map +1 -1
- package/dist/schema/session.d.mts +1 -0
- package/dist/schema/session.mjs +1 -0
- package/dist/schema/session.mjs.map +1 -1
- package/dist/schema/slack-pr-notifications.d.mts +12 -0
- package/dist/schema/slack-pr-notifications.mjs +11 -0
- package/dist/schema/slack-pr-notifications.mjs.map +1 -0
- package/dist/schema/task-graph.d.mts +39 -0
- package/dist/schema/task.d.mts +1 -0
- package/dist/schema/task.mjs +2 -1
- package/dist/schema/task.mjs.map +1 -1
- package/dist/schema/transcript.d.mts +6 -0
- package/dist/schema/transcript.mjs +2 -1
- package/dist/schema/transcript.mjs.map +1 -1
- package/dist/schema/work.d.mts +52 -3
- package/dist/schema/work.mjs.map +1 -1
- package/dist/session-previews.d.mts +76 -0
- package/dist/session-previews.mjs +55 -0
- package/dist/session-previews.mjs.map +1 -0
- package/dist/session-review.d.mts +120 -0
- package/dist/session-review.mjs +79 -0
- package/dist/session-review.mjs.map +1 -0
- package/dist/signal-triage.mjs +1 -1
- package/dist/signals.d.mts +1 -0
- package/dist/stall.d.mts +4 -1
- package/dist/stall.mjs +6 -2
- package/dist/stall.mjs.map +1 -1
- package/dist/store/driver.d.mts +1 -1
- package/dist/store/driver.mjs.map +1 -1
- package/dist/store/engine.d.mts +206 -8
- package/dist/store/engine.mjs +147 -13
- package/dist/store/engine.mjs.map +1 -1
- package/dist/store/memory.d.mts +18 -1
- package/dist/store/memory.mjs +18 -1
- package/dist/store/memory.mjs.map +1 -1
- package/dist/store/slack-pr-notifications.d.mts +44 -0
- package/dist/store/slack-pr-notifications.mjs +121 -0
- package/dist/store/slack-pr-notifications.mjs.map +1 -0
- package/dist/store/task-work.d.mts +121 -6
- package/dist/store/task-work.mjs +7 -4
- package/dist/store/task-work.mjs.map +1 -1
- package/dist/sweep.d.mts +28 -6
- package/dist/sweep.mjs +34 -6
- package/dist/sweep.mjs.map +1 -1
- package/dist/task-graph-view.d.mts +3 -0
- package/dist/tasks.d.mts +3 -3
- package/dist/tasks.mjs +3 -3
- package/dist/vercel-git.d.mts +35 -3
- package/dist/vercel-git.mjs +265 -33
- package/dist/vercel-git.mjs.map +1 -1
- package/dist/vercel-github-api.d.mts +103 -0
- package/dist/vercel-github-api.mjs +363 -0
- package/dist/vercel-github-api.mjs.map +1 -0
- package/dist/vercel.d.mts +3 -2
- package/dist/vercel.mjs +3 -2
- package/dist/vercel.mjs.map +1 -1
- package/dist/work-triage.d.mts +1 -0
- package/dist/workflows.d.mts +102 -4
- package/dist/workflows.mjs +55 -2
- package/dist/workflows.mjs.map +1 -1
- package/dist/workspace-files-git.d.mts +15 -0
- package/dist/workspace-files-git.mjs +61 -0
- package/dist/workspace-files-git.mjs.map +1 -0
- package/dist/workspace-files.d.mts +107 -0
- package/dist/workspace-files.mjs +74 -0
- package/dist/workspace-files.mjs.map +1 -0
- package/docs/getting-started.md +104 -0
- package/docs/index.md +100 -0
- package/docs/recipes/cancellation.md +215 -0
- package/docs/recipes/custom-workflow.md +153 -0
- package/docs/recipes/dependent-tasks.md +207 -0
- package/docs/recipes/eve-agent.md +277 -0
- package/docs/recipes/human-input.md +204 -0
- package/docs/recipes/persistence-recovery.md +268 -0
- package/docs/recipes/retry-recovery.md +241 -0
- package/docs/recipes/task-messaging.md +215 -0
- package/docs/recipes/typed-eve-result.md +161 -0
- package/docs/runtime-integration.md +137 -0
- package/package.json +17 -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\"
|
|
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
|
|
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
|
|
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}\
|
|
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:
|
|
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":";;;AAgFA,eAAe,oBACb,SACA,YACkC;CAClC,MAAM,QAAQ,MAAM,QAAQ,mBAAmB,WAAW,IAAI;CAC9D,QAAQ,QAAQ,gBAAgB,8BAAA,CAA+B;EAC7D,YAAY,WAAW;EACvB;EACA,OAAO,QAAQ;CACjB,CAAC;AACH;AAEA,SAAS,gBAAgB,YAAgC;CACvD,MAAM,QAAQ,WAAW,KAAK,MAAM,GAAG,CAAC,CAAC;CACzC,IAAI,UAAU,KAAA,KAAa,UAAU,IACnC,MAAM,IAAI,MAAM,4BAA4B,WAAW,MAAM;CAE/D,OAAO;AACT;AAEA,eAAe,oBACb,QACA,YACA,QAC4D;CAC5D,MAAM,SAAS,kBAAkB,MAAM;CAKvC,MAAM,QAAO,MAJM,OAAO,aAAa;EACrC,OAAO;EACP,MAAM,GAAG,gBAAgB,UAAU,EAAE,GAAG;CAC1C,CAAC,EAAA,CACiB,MAAM,cAAc,UAAU,KAAK,eAAe,WAAW,IAAI;CACnF,IAAI,SAAS,KAAA,GACX,MAAM,IAAI,MAAM,2BAA2B,OAAO,+BAA+B;CAEnF,OAAO;EAAE,GAAG;EAAM;CAAO;AAC3B;AAEA,eAAe,sBACb,QACA,YACA,QAC4D;CAC5D,MAAM,OAAO,MAAM,OAAO,YAAY,EAAE,mBAAmB,OAAO,SAAS,CAAC;CAC5E,MAAM,SAAS,KAAK,KAAK;CACzB,IACE,KAAK,UAAU,UACf,KAAK,mBAAmB,OAAO,SAC/B,KAAK,KAAK,eAAe,WAAW,QACpC,CAAC,aAAa,MAAM,GAEpB,MAAM,IAAI,MACR,wBAAwB,OAAO,SAAS,uCAC1C;CAEF,OAAO;EAAE,GAAG;EAAM;CAAO;AAC3B;AAEA,eAAe,aACb,QACA,QACA,SACA,SAAS,OAAO,MACC;CACjB,MAAM,aAAa,MAAM,OAAO,OAAO,EAAE,KAAK,OAAO,KAAK,CAAC;CAC3D,MAAM,OAAO,MAAM,OAAO,WAAW;EAAE,aAAa,WAAW;EAAS,SAAS,OAAO;CAAK,CAAC;CAC9F,OAAO,OAAO,aAAa;EAAE;EAAS,SAAS;EAAM,YAAY,CAAC,MAAM;CAAE,CAAC;AAC7E;AAEA,eAAe,mBACb,QACA,YACA,QACmD;CAKnD,MAAM,QAAO,MAJO,OAAO,aAAa;EACtC,OAAO;EACP,MAAM,GAAG,gBAAgB,UAAU,EAAE,GAAG;CAC1C,CAAC,EAAA,CACkB,MAChB,cACC,UAAU,KAAK,WAAW,UAAU,UAAU,KAAK,eAAe,WAAW,IACjF;CACA,IAAI,SAAS,KAAA,KAAa,KAAK,UAAU,QACvC,MAAM,IAAI,MACR,8FACF;CAEF,OAAO;AACT;AAEA,eAAe,YAAY,SAAsC;CAC/D,MAAM,SAAS,MAAM,QAAQ,IAAI,EAAE,SAAS,iDAAiD,CAAC;CAC9F,IAAI,OAAO,aAAa,GAAG,OAAO;CAClC,IAAI,OAAO,aAAa,KAAA,KAAa,OAAO,aAAa,GACvD,MAAM,IAAI,MAAM,8CAA8C;CAEhE,OAAO,OAAO,OAAO,KAAK;AAC5B;AAEA,eAAe,oBAAoB,SAAqB,KAA4B;CAClF,IAAI,CAAC,kBAAkB,KAAK,GAAG,GAAG,MAAM,IAAI,MAAM,wCAAwC;CAC1F,MAAM,cAAc;EAAE;EAAS,MAAM;GAAC;GAAU;GAAyB;EAAG;CAAE,CAAC;AACjF;AAEA,eAAe,sBAAsB,SAAqB,YAAuC;CAC/F,KACG,MAAM,cAAc;EAAE;EAAS,MAAM;GAAC;GAAU;GAAW;EAAQ;CAAE,CAAC,EAAA,CAAG,KAAK,MAC/E,sBAAsB,WAAW,KAAK,OAEtC,MAAM,IAAI,MAAM,sDAAsD,WAAW,MAAM;CAEzF,KAAK,MAAM,cAAc;EAAE;EAAS,MAAM,CAAC,UAAU,aAAa;CAAE,CAAC,EAAA,CAAG,KAAK,MAAM,IACjF,MAAM,IAAI,MAAM,yDAAyD;AAE7E;AAEA,eAAe,kBACb,SACA,QACA,KACkB;CAClB,MAAM,WAAW,MAAM,YAAY,OAAO;CAC1C,MAAM,SAAS,MAAM,OAAO,OAAO,EAAE,IAAI,CAAC;CAC1C,MAAM,YACJ,OAAO,aACN,MAAM,cAAc;EAAE;EAAS,MAAM,CAAC,aAAa,aAAa;CAAE,CAAC,EAAA,CAAG,KAAK;CAC9E,MAAM,YAAY,cAAc,aAAa,MAAM,OAAO,WAAW,SAAS,QAAQ;CACtF,IAAI,aAAa,OAAO,CAAC,WACvB,MAAM,IAAI,MACR,mIACF;CAEF,MAAM,oBAAoB,SAAS,GAAG;CACtC,OAAO;AACT;AAEA,SAAS,mBACP,MACA,QACA,eAAe,GACO;CACtB,OAAO;EACL,UAAU,KAAK;EACf,OAAO,KAAK;EACZ;EACA;EACA,OAAO,KAAK;CACd;AACF;;AAGA,SAAgB,iCACd,SAC4B;CAC5B,eAAe,uBACb,OAC0B;EAE1B,MAAM,OAAO,MAAM,oBAAoB,MADlB,oBAAoB,SAAS,MAAM,UAAU,GACnB,MAAM,YAAY,MAAM,MAAM;EAC7E,OAAO;GACL,UAAU,KAAK;GACf,OAAO,KAAK;GACZ,QAAQ,KAAK;EACf;CACF;CAEA,eAAe,uBACb,SACA,YACA,WACA,OAC+B;EAC/B,MAAM,SAAS,qBAAqB,SAAS;EAC7C,MAAM,sBAAsB,SAAS,UAAU;EAC/C,IAAI,QAAQ,uBAAuB,KAAA,GAAW;GAC5C,MAAM,SAAS,MAAM,uBAAuB;IAAE;IAAS,gBAAgB,WAAW;GAAK,CAAC;GACxF,MAAM,YAAY,MAAM,QAAQ,mBAAmB;IACjD,YAAY,WAAW;IACvB,YAAY,OAAO;IACnB,MAAM,OAAO;IACb,eAAe,OAAO;IACtB;IACA,OAAO,MAAM;IACb,MAAM,MAAM;IACZ,YAAY,WAAW;IACvB,OAAO;GACT,CAAC;GACD,MAAM,oBAAoB,SAAS,UAAU,SAAS;GACtD,OAAO;IACL,UAAU,UAAU;IACpB,OAAO,UAAU;IACjB;IACA,cAAc,OAAO;IACrB,OAAO,UAAU;GACnB;EACF;EACA,MAAM,SAAS,MAAM,oBAAoB,SAAS,UAAU;EAC5D,MAAM,WAAW,MAAM,mBAAmB,QAAQ,YAAY,MAAM;EACpE,IAAI,aAAa,KAAA,GAAW;GAC1B,MAAM,kBAAkB,SAAS,QAAQ,SAAS,KAAK,GAAG;GAC1D,OAAO,mBAAmB,UAAU,MAAM;EAC5C;EAEA,IAAI,MAAM,MAAM,OAAO,WAAW,EAAE,OAAO,CAAC;EAC5C,IAAI,eAAe;EACnB,IAAI,QAAQ,MAAM;GAChB,MAAM,SAAS,MAAM,uBAAuB;IAAE;IAAS,gBAAgB,WAAW;GAAK,CAAC;GACxF,eAAe,OAAO;GACtB,MAAM,MAAM,aAAa,QAAQ,QAAQ,GAAG,MAAM,MAAM,MAAM,MAAM,MAAM;GAC1E,IAAI;IACF,MAAM,OAAO,aAAa;KAAE;KAAQ;IAAI,CAAC;GAC3C,SAAS,OAAO;IACd,IAAK,MAAM,OAAO,WAAW,EAAE,OAAO,CAAC,MAAO,KAAK,MAAM;GAC3D;GACA,MAAM,oBAAoB,SAAS,GAAG;EACxC,OACE,MAAM,kBAAkB,SAAS,QAAQ,GAAG;EAE9C,IAAI;GACF,MAAM,OAAO,MAAM,OAAO,kBAAkB;IAC1C,OAAO,MAAM;IACb,MAAM,MAAM;IACZ,MAAM;IACN,MAAM,WAAW;IACjB,OAAO;GACT,CAAC;GACD,OAAO;IACL,UAAU,KAAK;IACf,OAAO,KAAK;IACZ;IACA;IACA,OAAO,KAAK;GACd;EACF,SAAS,OAAO;GACd,MAAM,UAAU,MAAM,mBAAmB,QAAQ,YAAY,MAAM;GACnE,IAAI,YAAY,KAAA,GAAW,MAAM;GACjC,OAAO,mBAAmB,SAAS,QAAQ,YAAY;EACzD;CACF;CAEA,eAAe,gBACb,OAC+B;EAC/B,IAAI,OAAO,MAAM,UAAU,UACzB,OAAO,uBACL,MAAM,SACN,MAAM,YACN,MAAM,MAAM,WACZ,MAAM,WACR;EAEF,MAAM,SAAS,MAAM,uBAAuB;GAC1C,SAAS,MAAM;GACf,gBAAgB,MAAM,WAAW;EACnC,CAAC;EACD,IAAI,QAAQ,uBAAuB,KAAA,GAAW;GAC5C,MAAM,SAAS,kBAAkB,MAAM,KAAK;GAC5C,MAAM,YAAY,MAAM,QAAQ,mBAAmB;IACjD,YAAY,MAAM,WAAW;IAC7B,YAAY,OAAO;IACnB,MAAM,OAAO;IACb,eAAe,OAAO;IACtB;IACA,OAAO,MAAM,YAAY;IACzB,MAAM,MAAM,YAAY;IACxB,YAAY,MAAM,WAAW;IAC7B,OAAO;GACT,CAAC;GACD,OAAO;IACL,UAAU,UAAU;IACpB,OAAO,UAAU;IACjB;IACA,cAAc,OAAO;IACrB,OAAO,UAAU;GACnB;EACF;EACA,MAAM,SAAS,MAAM,oBAAoB,SAAS,MAAM,UAAU;EAClE,MAAM,SAAS,kBAAkB,MAAM,KAAK;EAC5C,MAAM,YACJ,MAAM,OAAO,aAAa;GACxB,OAAO;GACP,MAAM,GAAG,gBAAgB,MAAM,UAAU,EAAE,GAAG;EAChD,CAAC,EAAA,CACD,MAAM,SAAS,KAAK,KAAK,WAAW,UAAU,KAAK,KAAK,eAAe,MAAM,WAAW,IAAI;EAC9F,IAAI,aAAa,KAAA,GAAW;GAC1B,IAAI,SAAS,UAAU,QACrB,MAAM,IAAI,MACR,sFACF;GACF,OAAO;IACL,UAAU,SAAS;IACnB,OAAO,SAAS;IAChB;IACA,cAAc;IACd,OAAO,SAAS;GAClB;EACF;EACA,MAAM,MAAM,MAAM,aAChB,QACA,QACA,GAAG,MAAM,YAAY,MAAM,MAAM,MAAM,YAAY,MACrD;EACA,MAAM,OAAO,aAAa;GAAE;GAAQ;EAAI,CAAC;EACzC,MAAM,OAAO,MAAM,OAAO,kBAAkB;GAC1C,OAAO,MAAM,YAAY;GACzB,MAAM,MAAM,YAAY;GACxB,MAAM;GACN,MAAM,MAAM,WAAW;GACvB,OAAO;EACT,CAAC;EACD,OAAO;GACL,UAAU,KAAK;GACf,OAAO,KAAK;GACZ;GACA,cAAc,OAAO;GACrB,OAAO,KAAK;EACd;CACF;CAEA,eAAe,sBACb,SACA,YACA,WACA,OAC+B;EAC/B,MAAM,SAAS,qBAAqB,SAAS;EAC7C,MAAM,sBAAsB,SAAS,UAAU;EAC/C,MAAM,SAAS,MAAM,oBAAoB,SAAS,UAAU;EAC5D,MAAM,OAAO,MAAM,mBAAmB,QAAQ,YAAY,MAAM;EAChE,IAAI,SAAS,KAAA,GACX,MAAM,IAAI,MAAM,2BAA2B,OAAO,+BAA+B;EAEnF,MAAM,YAAY,MAAM,kBAAkB,SAAS,QAAQ,KAAK,KAAK,GAAG;EACxE,IAAI,eAAe;EACnB,IAAI,CAAC,WAAW;GACd,MAAM,SAAS,MAAM,uBAAuB;IAC1C;IACA,gBAAgB,WAAW;IAC3B,YAAY;GACd,CAAC;GACD,eAAe,OAAO;GACtB,MAAM,MAAM,MAAM,aAChB,QACA,QACA,GAAG,KAAK,MAAM,MAAM,KAAK,QAAQ,MACjC,KAAK,KAAK,GACZ;GACA,MAAM,OAAO,aAAa;IAAE;IAAQ;IAAK,OAAO;GAAM,CAAC;GACvD,MAAM,oBAAoB,SAAS,GAAG;EACxC;EACA,MAAM,YAAY,mBAAmB,MAAM,QAAQ,YAAY;EAC/D,IAAI,MAAM,SAAS,UAAU,OAAO;GAClC,MAAM,OAAO,UAAU,EAAE,QAAQ,KAAK,OAAO,CAAC;GAC9C,UAAU,QAAQ;EACpB;EACA,OAAO;CACT;CAEA,eAAe,eAAe,OAAiE;EAC7F,IAAI,OAAO,MAAM,UAAU,UACzB,OAAO,sBACL,MAAM,SACN,MAAM,YACN,MAAM,MAAM,WACZ,MAAM,UACR;EAEF,MAAM,SAAS,MAAM,oBAAoB,SAAS,MAAM,UAAU;EAClE,MAAM,OACJ,MAAM,iBAAiB,KAAA,IACnB,MAAM,oBAAoB,QAAQ,MAAM,YAAY,MAAM,KAAK,IAC/D,MAAM,sBAAsB,QAAQ,MAAM,YAAY,MAAM,YAAY;EAC9E,MAAM,SAAS,MAAM,uBAAuB;GAC1C,SAAS,MAAM;GACf,gBAAgB,MAAM,WAAW;GACjC,SACE,MAAM,iBAAiB,KAAA,IACnB,gBACA,0BAA0B,MAAM,aAAa;EACrD,CAAC;EACD,IAAI,MAAM,iBAAiB,KAAA,KAAa,OAAO,SAAS,KAAK,KAAK,KAChE,MAAM,IAAI,MACR,iBAAiB,MAAM,aAAa,SAAS,8CAC/C;EAEF,MAAM,MAAM,MAAM,aAAa,QAAQ,QAAQ,GAAG,KAAK,MAAM,MAAM,KAAK,QAAQ,IAAI;EACpF,MAAM,OAAO,aAAa;GACxB,QAAQ,KAAK;GACb;GACA,OAAO,MAAM,iBAAiB,KAAA;EAChC,CAAC;EACD,IAAI,QAAQ,KAAK;EACjB,IAAI,MAAM,WAAW,SAAS,OAAO;GACnC,MAAM,OAAO,UAAU,EAAE,QAAQ,KAAK,OAAO,CAAC;GAC9C,QAAQ;EACV;EACA,OAAO;GACL,UAAU,KAAK;GACf,OAAO,KAAK;GACZ,QAAQ,KAAK;GACb,cAAc,OAAO;GACrB;EACF;CACF;CAEA,OAAO,OAAO,OAAO;EAAE;EAAwB;EAAiB;CAAe,CAAC;AAClF;;AAcA,SAAgB,uCACd,SACkC;CAClC,OAAO,OACL,UAC8C;EAC9C,IAAI;EACJ,IAAI;EACJ,IAAI;GACF,aAAa,MAAM,QAAQ,kBAAkB,MAAM,MAAM;GACzD,IAAI,WAAW,OAAO,MAAM,cAC1B,OAAO;IACL,IAAI;IACJ,MAAM;IACN,QAAQ;GACV;GAEF,MAAM,OAAO,MAAM,QAAQ,OAAO,MAAM,IAAI,MAAM,MAAM;GACxD,IAAI,SAAS,MACX,OAAO;IACL,IAAI;IACJ,MAAM;IACN,QAAQ,QAAQ,MAAM,OAAO;GAC/B;GAEF,eACE,KAAK,aAAa,KAAA,IAAY,OAAO,MAAM,QAAQ,OAAO,QAAQ,IAAI,KAAK,QAAQ;GACrF,IAAI,KAAK,aAAa,KAAA,KAAa,iBAAiB,MAClD,OAAO;IACL,IAAI;IACJ,MAAM;IACN,QAAQ,UAAU,KAAK,SAAS;GAClC;GAEF,IACE,iBAAiB,SAChB,aAAa,iBAAiB,WAAW,MACxC,aAAa,aAAa,MAAM,YAChC,aAAa,UAAU,MAAM,QAE/B,OAAO;IACL,IAAI;IACJ,MAAM;IACN,QAAQ;GACV;EAEJ,SAAS,OAAO;GACd,OAAO;IACL,IAAI;IACJ,MAAM;IACN,QAAQ,qBAAqB,KAAK;GACpC;EACF;EAEA,IAAI;EACJ,IAAI;GACF,SAAS,MAAM,oBAAoB,SAAS,UAAU;EACxD,SAAS,OAAO;GACd,OAAO;IACL,IAAI;IACJ,MAAM;IACN,QAAQ,qCAAqC,qBAAqB,KAAK;GACzE;EACF;EAEA,IAAI;EACJ,IAAI;GACF,OAAO,MAAM,OAAO,YAAY,EAAE,mBAAmB,MAAM,SAAS,CAAC;EACvE,SAAS,OAAO;GACd,OAAO;IACL,IAAI;IACJ,MACE,iBAAiB,6BAA6B,MAAM,WAAW,MAC3D,2BACA;IACN,QACE,iBAAiB,6BAA6B,MAAM,WAAW,MAC3D,iBAAiB,MAAM,SAAS,qBAAqB,WAAW,SAChE,kCAAkC,MAAM,SAAS,MAAM,WAAW;GAC1E;EACF;EACA,IAAI,KAAK,mBAAmB,MAAM,OAChC,OAAO;GACL,IAAI;GACJ,MAAM;GACN,QAAQ,sCAAsC,MAAM,SAAS,MAAM,WAAW;EAChF;EAEF,IAAI,KAAK,UAAU,QACjB,OAAO;GACL,IAAI;GACJ,MAAM;GACN,QAAQ,iBAAiB,MAAM,SAAS;EAC1C;EAEF,MAAM,SAAS,kBAAkB,MAAM,MAAM;EAC7C,IACE,KAAK,KAAK,eAAe,WAAW,QACnC,iBAAiB,QAAQ,KAAK,KAAK,WAAW,QAE/C,OAAO;GACL,IAAI;GACJ,MAAM;GACN,QACE,iBAAiB,OACb,iBAAiB,MAAM,SAAS,gCAAgC,WAChE,iBAAiB,MAAM,SAAS;EACxC;EAEF,IAAI,KAAK,OACP,OAAO;GACL,IAAI;GACJ,MAAM;GACN,QAAQ,iBAAiB,MAAM,SAAS;EAC1C;EAEF,OAAO,EAAE,IAAI,KAAK;CACpB;AACF"}
|
package/dist/pull-requests.d.mts
CHANGED
|
@@ -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";
|
package/dist/sandbox/index.d.mts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
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"}
|