@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
package/dist/sweep.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { InvalidTransitionError } from "./schema/transitions.mjs";
|
|
|
2
2
|
import { routeKey } from "./agent-routes.mjs";
|
|
3
3
|
import { VersionConflictError } from "./store/driver.mjs";
|
|
4
4
|
import { DispatchRefusedError, dispatchTask } from "./dispatch.mjs";
|
|
5
|
+
import { TaskAttemptConflictError } from "./store/engine.mjs";
|
|
5
6
|
//#region src/sweep.ts
|
|
6
7
|
async function failUnrouteable(stores, task, reasonCode, reason, result) {
|
|
7
8
|
try {
|
|
@@ -27,12 +28,29 @@ async function failUnrouteable(stores, task, reasonCode, reason, result) {
|
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
31
|
+
* Runs one bounded pass over queued Tasks and dispatches each eligible exact route.
|
|
32
|
+
*
|
|
33
|
+
* @remarks
|
|
34
|
+
* Candidates are ordered by newest attempt first and then creation time. Route-less work remains
|
|
35
|
+
* queued for its workflow owner; unmet dependencies, approvals, and pass limits appear in
|
|
36
|
+
* `skipped`. Invalid repository scope, missing repositories or launchers, and launch failures
|
|
37
|
+
* appear in `failed`, with unrouteable Tasks transitioned to `failed` when this sweep wins the
|
|
38
|
+
* race. A result entry describes this pass rather than guaranteeing the Task's lifecycle state:
|
|
39
|
+
* an ambiguously accepted launch is reported as `launch_failed` while its Task intentionally stays
|
|
40
|
+
* running for recovery. Callers should surface every failure, reread those Tasks before deciding
|
|
41
|
+
* remediation, and schedule another pass for skipped work.
|
|
42
|
+
*
|
|
43
|
+
* Overlapping passes are safe: {@link dispatchTask} atomically admits one winner before invoking a
|
|
44
|
+
* launcher. A supplied `tasks` snapshot bounds reads and candidate selection for this pass; it can
|
|
45
|
+
* become stale, and corresponding races are returned as `task_changed` rather than hidden.
|
|
46
|
+
* `maxDispatches` counts provider launches started during the pass, including launches that later
|
|
47
|
+
* fail, so `dispatched.length` may be smaller than the limit.
|
|
48
|
+
*
|
|
49
|
+
* @param stores - Graph, receipt, repository, and Task stores used during the pass.
|
|
50
|
+
* @param options - Exact-route launchers plus optional budget, Task snapshot, and dispatch limit.
|
|
51
|
+
* @returns IDs dispatched and structured skipped/failed outcomes for every considered candidate.
|
|
52
|
+
* @throws When `maxDispatches` is not a positive integer or an unexpected persistence error occurs.
|
|
53
|
+
* @see The shipped `docs/recipes/retry-recovery.md` recipe for scheduled recovery.
|
|
36
54
|
*/
|
|
37
55
|
async function sweepQueued(stores, options) {
|
|
38
56
|
const result = {
|
|
@@ -100,12 +118,14 @@ async function sweepQueued(stores, options) {
|
|
|
100
118
|
await failUnrouteable(stores, task, "repository_not_found", "task has no repository", result);
|
|
101
119
|
continue;
|
|
102
120
|
}
|
|
121
|
+
let launchStarted = false;
|
|
103
122
|
try {
|
|
104
123
|
const dispatched = await dispatchTask(stores, {
|
|
105
124
|
taskId: task.id,
|
|
106
125
|
route,
|
|
107
126
|
launch: (dispatched) => {
|
|
108
127
|
attempted += 1;
|
|
128
|
+
launchStarted = true;
|
|
109
129
|
return launcher(dispatched, repository);
|
|
110
130
|
},
|
|
111
131
|
budget: options.budget === void 0 ? void 0 : {
|
|
@@ -128,6 +148,14 @@ async function sweepQueued(stores, options) {
|
|
|
128
148
|
});
|
|
129
149
|
continue;
|
|
130
150
|
}
|
|
151
|
+
if (!launchStarted && (error instanceof InvalidTransitionError || error instanceof TaskAttemptConflictError || error instanceof VersionConflictError)) {
|
|
152
|
+
result.skipped.push({
|
|
153
|
+
taskId: task.id,
|
|
154
|
+
reasonCode: "task_changed",
|
|
155
|
+
reason: "task changed before dispatch"
|
|
156
|
+
});
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
131
159
|
result.failed.push({
|
|
132
160
|
taskId: task.id,
|
|
133
161
|
reasonCode: "launch_failed",
|
package/dist/sweep.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sweep.mjs","names":[],"sources":["../src/sweep.ts"],"sourcesContent":["import { routeKey, type AgentRouteKey } from \"./agent-routes\";\nimport { dispatchTask, DispatchRefusedError, type DispatchBudget } from \"./dispatch\";\nimport type { DispatchRefusalCode } from \"./dispatch\";\nimport type { TaskId } from \"./schema/id\";\nimport type { Repository } from \"./schema/repository\";\nimport type { ExecutionReference, Task } from \"./schema/task\";\nimport { InvalidTransitionError } from \"./schema/transitions\";\nimport { VersionConflictError } from \"./store/driver\";\nimport type { FactoryStores } from \"./store/engine\";\n\n/** Graph, receipt, repository, and Task capabilities required by queued-work dispatch. */\nexport type SweepQueuedStores = Pick<\n FactoryStores,\n \"graphs\" | \"receipts\" | \"repositories\" | \"tasks\"\n>;\n\n/**\n * Single-repository adapter for an exact agent route. Supplied by the app, which knows its\n * agents' addresses. Returns the provider's execution reference when it has one.\n */\nexport type TaskLauncher = (task: Task, repository: Repository) => Promise<ExecutionReference>;\n\n/** Exact-route launchers, budget gate, and pass bounds for queued Task dispatch. */\nexport interface SweepQueuedOptions {\n /** Single-repository agent adapters keyed by routeKey(binding), e.g. code-change@1. */\n launchers: Readonly<Partial<Record<AgentRouteKey, TaskLauncher>>>;\n /** Reserve-before-execution limits, passed through to every dispatch. */\n budget?: DispatchBudget;\n /** Maximum task launches attempted in one pass; omitted means no limit. */\n maxDispatches?: number;\n /** A dispatcher-wide snapshot avoids another complete task read. */\n tasks?: readonly Task[];\n}\n\n/** Tasks dispatched, deferred by a gate, or failed by one queued-work pass. */\nexport interface SweepQueuedResult {\n readonly dispatched: readonly TaskId[];\n /** Tasks left in the queue because a declared gate may become eligible later. */\n readonly skipped: readonly {\n readonly taskId: TaskId;\n readonly reasonCode: SweepSkipReasonCode;\n readonly reason: string;\n }[];\n /** Tasks whose routing is invalid or whose launch threw. */\n readonly failed: readonly {\n readonly taskId: TaskId;\n readonly reasonCode: SweepFailureReasonCode;\n readonly reason: string;\n }[];\n}\n\n/** Stable reason a queued Task remained eligible for a later dispatch pass. */\nexport type SweepSkipReasonCode =\n | DispatchRefusalCode\n | \"workflow_owned\"\n | \"awaiting_approval\"\n | \"dispatch_limit\"\n | \"task_changed\";\n\n/** Stable reason a queued Task could not be routed or launched. */\nexport type SweepFailureReasonCode =\n | \"invalid_repository_scope\"\n | \"repository_not_found\"\n | \"launcher_not_registered\"\n | \"launch_failed\";\n\ntype MutableSweepQueuedResult = {\n dispatched: TaskId[];\n skipped: { taskId: TaskId; reasonCode: SweepSkipReasonCode; reason: string }[];\n failed: { taskId: TaskId; reasonCode: SweepFailureReasonCode; reason: string }[];\n};\n\nasync function failUnrouteable(\n stores: SweepQueuedStores,\n task: Task,\n reasonCode: SweepFailureReasonCode,\n reason: string,\n result: MutableSweepQueuedResult,\n): Promise<void> {\n try {\n await stores.tasks.transition(task.id, \"failed\", { reason, expectFrom: \"queued\" });\n result.failed.push({ taskId: task.id, reasonCode, reason });\n } catch (error) {\n if (error instanceof InvalidTransitionError || error instanceof VersionConflictError) {\n result.skipped.push({\n taskId: task.id,\n reasonCode: \"task_changed\",\n reason: \"task changed during routing\",\n });\n return;\n }\n throw error;\n }\n}\n\n/**\n * One pass of the factory's dispatcher, meant to run on a schedule inside the\n * deployment: every queued task that is allowed to run is started, in\n * creation order. Nothing outside the factory ever starts an agent; the CLI\n * only writes tasks and approvals, and this picks them up.\n * Idempotent by construction: dispatchTask's single-winner transition means\n * two overlapping sweeps cannot start the same task twice.\n */\nexport async function sweepQueued(\n stores: SweepQueuedStores,\n options: SweepQueuedOptions,\n): Promise<SweepQueuedResult> {\n const result: MutableSweepQueuedResult = { dispatched: [], skipped: [], failed: [] };\n const maxDispatches = options.maxDispatches ?? Number.POSITIVE_INFINITY;\n if (\n !(Number.isInteger(maxDispatches) || maxDispatches === Number.POSITIVE_INFINITY) ||\n maxDispatches < 1\n ) {\n throw new Error(\"maxDispatches must be a positive integer\");\n }\n let attempted = 0;\n const taskSnapshot = options.tasks ?? (await stores.tasks.list());\n const budgetSnapshot =\n options.budget?.tasks ??\n (options.budget && options.tasks ? await stores.tasks.list() : taskSnapshot);\n const budgetTasks = new Map(budgetSnapshot.map((task) => [task.id, task]));\n const queued = taskSnapshot\n .filter((task) => task.state === \"queued\")\n .sort((a, b) => b.attempt - a.attempt || a.createdAt.localeCompare(b.createdAt));\n\n for (const task of queued) {\n const route = task.work.route;\n if (route === undefined) {\n result.skipped.push({\n taskId: task.id,\n reasonCode: \"workflow_owned\",\n reason: \"no agent route; workflow owns execution\",\n });\n continue;\n }\n const [repositoryId] = task.repositoryIds;\n if (task.repositoryIds.length !== 1 || repositoryId === undefined) {\n await failUnrouteable(\n stores,\n task,\n \"invalid_repository_scope\",\n \"sweep launcher adapter requires exactly one repository\",\n result,\n );\n continue;\n }\n if (task.approval === \"required\") {\n result.skipped.push({\n taskId: task.id,\n reasonCode: \"awaiting_approval\",\n reason: \"awaiting human approval\",\n });\n continue;\n }\n const key = routeKey(route);\n const launcher = Object.hasOwn(options.launchers, key) ? options.launchers[key] : undefined;\n if (launcher === undefined) {\n await failUnrouteable(\n stores,\n task,\n \"launcher_not_registered\",\n `no registered launcher handles agent route ${key}`,\n result,\n );\n continue;\n }\n if (\n !(await stores.graphs.dependenciesSatisfied({\n rootTaskId: task.rootTaskId,\n taskId: task.id,\n }))\n ) {\n result.skipped.push({\n taskId: task.id,\n reasonCode: \"dependencies_unsatisfied\",\n reason: \"graph dependencies must all be succeeded before dispatch\",\n });\n continue;\n }\n if (attempted >= maxDispatches) {\n result.skipped.push({\n taskId: task.id,\n reasonCode: \"dispatch_limit\",\n reason: \"per-sweep dispatch limit reached\",\n });\n continue;\n }\n const repository = await stores.repositories.get(repositoryId);\n if (repository === null) {\n await failUnrouteable(stores, task, \"repository_not_found\", \"task has no repository\", result);\n continue;\n }\n try {\n const dispatched = await dispatchTask(stores, {\n taskId: task.id,\n route,\n launch: (dispatched) => {\n attempted += 1;\n return launcher(dispatched, repository);\n },\n budget:\n options.budget === undefined\n ? undefined\n : { ...options.budget, tasks: [...budgetTasks.values()] },\n });\n budgetTasks.set(dispatched.id, dispatched);\n result.dispatched.push(task.id);\n } catch (error) {\n if (options.budget !== undefined) {\n const current = await stores.tasks.get(task.id).catch(() => null);\n if (current !== null) budgetTasks.set(current.id, current);\n }\n if (error instanceof DispatchRefusedError) {\n // Dependencies not ready, or another sweep won the race. Leave it.\n result.skipped.push({ taskId: task.id, reasonCode: error.code, reason: error.reason });\n continue;\n }\n result.failed.push({\n taskId: task.id,\n reasonCode: \"launch_failed\",\n reason: error instanceof Error ? error.message : String(error),\n });\n }\n }\n return result;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sweep.mjs","names":[],"sources":["../src/sweep.ts"],"sourcesContent":["import { routeKey, type AgentRouteKey } from \"./agent-routes\";\nimport { dispatchTask, DispatchRefusedError, type DispatchBudget } from \"./dispatch\";\nimport type { DispatchRefusalCode } from \"./dispatch\";\nimport type { TaskId } from \"./schema/id\";\nimport type { Repository } from \"./schema/repository\";\nimport type { ExecutionReference, Task } from \"./schema/task\";\nimport { InvalidTransitionError } from \"./schema/transitions\";\nimport { VersionConflictError } from \"./store/driver\";\nimport { TaskAttemptConflictError, type FactoryStores } from \"./store/engine\";\n\n/** Graph, receipt, repository, and Task capabilities required by queued-work dispatch. */\nexport type SweepQueuedStores = Pick<\n FactoryStores,\n \"graphs\" | \"receipts\" | \"repositories\" | \"tasks\"\n>;\n\n/**\n * Single-repository adapter for an exact agent route. Supplied by the app, which knows its\n * agents' addresses. Returns the provider's execution reference when it has one.\n *\n * @param task - Running Task already admitted by the dispatch gate.\n * @param repository - Exact enabled repository selected from the Task scope.\n * @returns Provider execution reference that dispatch can persist for recovery.\n */\nexport type TaskLauncher = (task: Task, repository: Repository) => Promise<ExecutionReference>;\n\n/** Exact-route launchers, budget gate, and pass bounds for queued Task dispatch. */\nexport interface SweepQueuedOptions {\n /** Single-repository agent adapters keyed by routeKey(binding), e.g. code-change@1. */\n launchers: Readonly<Partial<Record<AgentRouteKey, TaskLauncher>>>;\n /** Reserve-before-execution limits, passed through to every dispatch. */\n budget?: DispatchBudget;\n /** Maximum task launches attempted in one pass; omitted means no limit. */\n maxDispatches?: number;\n /** A dispatcher-wide snapshot avoids another complete task read. */\n tasks?: readonly Task[];\n}\n\n/** Tasks dispatched, deferred by a gate, or failed by one queued-work pass. */\nexport interface SweepQueuedResult {\n /** Task IDs whose provider execution reference was durably recorded. */\n readonly dispatched: readonly TaskId[];\n /** Tasks left in the queue because a declared gate may become eligible later. */\n readonly skipped: readonly {\n readonly taskId: TaskId;\n readonly reasonCode: SweepSkipReasonCode;\n readonly reason: string;\n }[];\n /** Tasks whose routing is invalid or whose launch threw. */\n readonly failed: readonly {\n readonly taskId: TaskId;\n readonly reasonCode: SweepFailureReasonCode;\n readonly reason: string;\n }[];\n}\n\n/** Stable reason a queued Task remained eligible for a later dispatch pass. */\nexport type SweepSkipReasonCode =\n | DispatchRefusalCode\n | \"workflow_owned\"\n | \"awaiting_approval\"\n | \"dispatch_limit\"\n | \"task_changed\";\n\n/** Stable reason a queued Task could not be routed or launched. */\nexport type SweepFailureReasonCode =\n | \"invalid_repository_scope\"\n | \"repository_not_found\"\n | \"launcher_not_registered\"\n | \"launch_failed\";\n\ntype MutableSweepQueuedResult = {\n dispatched: TaskId[];\n skipped: { taskId: TaskId; reasonCode: SweepSkipReasonCode; reason: string }[];\n failed: { taskId: TaskId; reasonCode: SweepFailureReasonCode; reason: string }[];\n};\n\nasync function failUnrouteable(\n stores: SweepQueuedStores,\n task: Task,\n reasonCode: SweepFailureReasonCode,\n reason: string,\n result: MutableSweepQueuedResult,\n): Promise<void> {\n try {\n await stores.tasks.transition(task.id, \"failed\", { reason, expectFrom: \"queued\" });\n result.failed.push({ taskId: task.id, reasonCode, reason });\n } catch (error) {\n if (error instanceof InvalidTransitionError || error instanceof VersionConflictError) {\n result.skipped.push({\n taskId: task.id,\n reasonCode: \"task_changed\",\n reason: \"task changed during routing\",\n });\n return;\n }\n throw error;\n }\n}\n\n/**\n * Runs one bounded pass over queued Tasks and dispatches each eligible exact route.\n *\n * @remarks\n * Candidates are ordered by newest attempt first and then creation time. Route-less work remains\n * queued for its workflow owner; unmet dependencies, approvals, and pass limits appear in\n * `skipped`. Invalid repository scope, missing repositories or launchers, and launch failures\n * appear in `failed`, with unrouteable Tasks transitioned to `failed` when this sweep wins the\n * race. A result entry describes this pass rather than guaranteeing the Task's lifecycle state:\n * an ambiguously accepted launch is reported as `launch_failed` while its Task intentionally stays\n * running for recovery. Callers should surface every failure, reread those Tasks before deciding\n * remediation, and schedule another pass for skipped work.\n *\n * Overlapping passes are safe: {@link dispatchTask} atomically admits one winner before invoking a\n * launcher. A supplied `tasks` snapshot bounds reads and candidate selection for this pass; it can\n * become stale, and corresponding races are returned as `task_changed` rather than hidden.\n * `maxDispatches` counts provider launches started during the pass, including launches that later\n * fail, so `dispatched.length` may be smaller than the limit.\n *\n * @param stores - Graph, receipt, repository, and Task stores used during the pass.\n * @param options - Exact-route launchers plus optional budget, Task snapshot, and dispatch limit.\n * @returns IDs dispatched and structured skipped/failed outcomes for every considered candidate.\n * @throws When `maxDispatches` is not a positive integer or an unexpected persistence error occurs.\n * @see The shipped `docs/recipes/retry-recovery.md` recipe for scheduled recovery.\n */\nexport async function sweepQueued(\n stores: SweepQueuedStores,\n options: SweepQueuedOptions,\n): Promise<SweepQueuedResult> {\n const result: MutableSweepQueuedResult = { dispatched: [], skipped: [], failed: [] };\n const maxDispatches = options.maxDispatches ?? Number.POSITIVE_INFINITY;\n if (\n !(Number.isInteger(maxDispatches) || maxDispatches === Number.POSITIVE_INFINITY) ||\n maxDispatches < 1\n ) {\n throw new Error(\"maxDispatches must be a positive integer\");\n }\n let attempted = 0;\n const taskSnapshot = options.tasks ?? (await stores.tasks.list());\n const budgetSnapshot =\n options.budget?.tasks ??\n (options.budget && options.tasks ? await stores.tasks.list() : taskSnapshot);\n const budgetTasks = new Map(budgetSnapshot.map((task) => [task.id, task]));\n const queued = taskSnapshot\n .filter((task) => task.state === \"queued\")\n .sort((a, b) => b.attempt - a.attempt || a.createdAt.localeCompare(b.createdAt));\n\n for (const task of queued) {\n const route = task.work.route;\n if (route === undefined) {\n result.skipped.push({\n taskId: task.id,\n reasonCode: \"workflow_owned\",\n reason: \"no agent route; workflow owns execution\",\n });\n continue;\n }\n const [repositoryId] = task.repositoryIds;\n if (task.repositoryIds.length !== 1 || repositoryId === undefined) {\n await failUnrouteable(\n stores,\n task,\n \"invalid_repository_scope\",\n \"sweep launcher adapter requires exactly one repository\",\n result,\n );\n continue;\n }\n if (task.approval === \"required\") {\n result.skipped.push({\n taskId: task.id,\n reasonCode: \"awaiting_approval\",\n reason: \"awaiting human approval\",\n });\n continue;\n }\n const key = routeKey(route);\n const launcher = Object.hasOwn(options.launchers, key) ? options.launchers[key] : undefined;\n if (launcher === undefined) {\n await failUnrouteable(\n stores,\n task,\n \"launcher_not_registered\",\n `no registered launcher handles agent route ${key}`,\n result,\n );\n continue;\n }\n if (\n !(await stores.graphs.dependenciesSatisfied({\n rootTaskId: task.rootTaskId,\n taskId: task.id,\n }))\n ) {\n result.skipped.push({\n taskId: task.id,\n reasonCode: \"dependencies_unsatisfied\",\n reason: \"graph dependencies must all be succeeded before dispatch\",\n });\n continue;\n }\n if (attempted >= maxDispatches) {\n result.skipped.push({\n taskId: task.id,\n reasonCode: \"dispatch_limit\",\n reason: \"per-sweep dispatch limit reached\",\n });\n continue;\n }\n const repository = await stores.repositories.get(repositoryId);\n if (repository === null) {\n await failUnrouteable(stores, task, \"repository_not_found\", \"task has no repository\", result);\n continue;\n }\n let launchStarted = false;\n try {\n const dispatched = await dispatchTask(stores, {\n taskId: task.id,\n route,\n launch: (dispatched) => {\n attempted += 1;\n launchStarted = true;\n return launcher(dispatched, repository);\n },\n budget:\n options.budget === undefined\n ? undefined\n : { ...options.budget, tasks: [...budgetTasks.values()] },\n });\n budgetTasks.set(dispatched.id, dispatched);\n result.dispatched.push(task.id);\n } catch (error) {\n if (options.budget !== undefined) {\n const current = await stores.tasks.get(task.id).catch(() => null);\n if (current !== null) budgetTasks.set(current.id, current);\n }\n if (error instanceof DispatchRefusedError) {\n // Dependencies not ready, or another sweep won the race. Leave it.\n result.skipped.push({ taskId: task.id, reasonCode: error.code, reason: error.reason });\n continue;\n }\n if (\n !launchStarted &&\n (error instanceof InvalidTransitionError ||\n error instanceof TaskAttemptConflictError ||\n error instanceof VersionConflictError)\n ) {\n result.skipped.push({\n taskId: task.id,\n reasonCode: \"task_changed\",\n reason: \"task changed before dispatch\",\n });\n continue;\n }\n result.failed.push({\n taskId: task.id,\n reasonCode: \"launch_failed\",\n reason: error instanceof Error ? error.message : String(error),\n });\n }\n }\n return result;\n}\n"],"mappings":"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"}
|
|
@@ -13,6 +13,7 @@ declare const taskGraphViewSchema: z.ZodObject<{
|
|
|
13
13
|
id: z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
14
14
|
repositoryIds: z.ZodReadonly<z.ZodTuple<[z.ZodType<`repo_${string}`, string, z.core.$ZodTypeInternals<`repo_${string}`, string>>], z.ZodType<`repo_${string}`, string, z.core.$ZodTypeInternals<`repo_${string}`, string>>>>;
|
|
15
15
|
kind: z.ZodType<TaskKind, unknown, z.core.$ZodTypeInternals<TaskKind, unknown>>;
|
|
16
|
+
model: z.ZodOptional<z.ZodString>;
|
|
16
17
|
area: z.ZodOptional<z.ZodString>;
|
|
17
18
|
state: z.ZodEnum<{
|
|
18
19
|
queued: "queued";
|
|
@@ -129,6 +130,7 @@ declare const taskGraphViewSchema: z.ZodObject<{
|
|
|
129
130
|
attempt: number;
|
|
130
131
|
createdAt: string;
|
|
131
132
|
updatedAt: string;
|
|
133
|
+
model?: string | undefined;
|
|
132
134
|
area?: string | undefined;
|
|
133
135
|
approval?: "required" | "granted" | "denied" | undefined;
|
|
134
136
|
workResult?: {
|
|
@@ -206,6 +208,7 @@ declare const taskGraphViewSchema: z.ZodObject<{
|
|
|
206
208
|
attempt: number;
|
|
207
209
|
createdAt: string;
|
|
208
210
|
updatedAt: string;
|
|
211
|
+
model?: string | undefined;
|
|
209
212
|
area?: string | undefined;
|
|
210
213
|
approval?: "required" | "granted" | "denied" | undefined;
|
|
211
214
|
workResult?: {
|
package/dist/tasks.d.mts
CHANGED
|
@@ -7,11 +7,11 @@ import { MAX_TASK_GRAPH_DEPENDENCIES, MAX_TASK_GRAPH_NODES, TaskGraph, TaskGraph
|
|
|
7
7
|
import { AdmitTaskGraphInput, AssertTaskAdmissionInput, CreateTaskGraphStoreOptions, SetTaskGraphDependenciesInput, TaskGraphConflictError, TaskGraphNodeRef, TaskGraphStore, assertTaskAdmission, createTaskGraphStore } from "./store/task-graphs.mjs";
|
|
8
8
|
import { TaskEffect, TaskEffectKey, TaskEffectReference, taskEffectKeySchema, taskEffectReferenceSchema, taskEffectSchema } from "./schema/task-effect.mjs";
|
|
9
9
|
import { CreateTaskEffectStoreOptions, RecordTaskEffectInput, ResolveTaskEffectInput, TaskEffectLookupInput, TaskEffectStore, createTaskEffectStore } from "./store/task-effects.mjs";
|
|
10
|
-
import { CompleteTaskWorkInput, CompleteWorkflowTaskWorkInput, CreateTaskWorkStoreOptions, ResumeTaskWorkInput, TaskWorkStore, TaskWorkStores, TaskWorkUsage, WaitForTaskMessagesInput, createTaskWorkStore } from "./store/task-work.mjs";
|
|
10
|
+
import { CompleteTaskWorkInput, CompleteWorkflowTaskWorkInput, CreateTaskWorkStoreOptions, ResumeTaskWorkInput, TaskWorkCompletionFence, TaskWorkStore, TaskWorkStores, TaskWorkUsage, WaitForTaskMessagesInput, createTaskWorkStore } from "./store/task-work.mjs";
|
|
11
11
|
import { AnswerEvent, answerEventSchema } from "./schema/answer.mjs";
|
|
12
|
-
import { AgentSessionBinding, AnswerEventConflictError, AnswerQuestionInput, AnswerQuestionResult, ApprovalDecision, ApprovalError, CreateTaskInput, RejectionDecision, RootTaskOrigin, TaskPatch, TaskTransitionArguments, TaskTransitionOptions } from "./store/engine.mjs";
|
|
12
|
+
import { AgentSessionBinding, AnswerEventConflictError, AnswerQuestionInput, AnswerQuestionResult, ApprovalDecision, ApprovalError, CreateTaskInput, RejectionDecision, RootTaskOrigin, TaskAttemptConflictError, TaskPatch, TaskTransitionArguments, TaskTransitionOptions } from "./store/engine.mjs";
|
|
13
13
|
import { ApprovalAccess, ApprovalAccessDecisionInput, ApprovalAccessError, ApprovalAccessOptions, ApprovalAccessStores, createApprovalAccess } from "./approval-access.mjs";
|
|
14
14
|
import { taskInitialState, taskTransitions } from "./schema/transitions.mjs";
|
|
15
15
|
import { ProcessTaskMessageOptions, ProcessTaskMessageResult, TaskMessageProcessingStores, processTaskMessage, readTaskMessageDecision } from "./task-message-processing.mjs";
|
|
16
16
|
import { AssignmentPayload, DefaultTaskMessageProtocols, HandoffPayload, StandardTaskMessageProtocol, assignmentPayloadSchema, assignmentTaskMessageProtocol, createDefaultTaskMessageProtocols, handoffPayloadSchema, handoffTaskMessageProtocol, standardTaskMessageProtocols } from "./task-protocols.mjs";
|
|
17
|
-
export { type AdmitTaskGraphInput, type AgentSessionBinding, type AnswerEvent, AnswerEventConflictError, type AnswerId, type AnswerQuestionInput, type AnswerQuestionResult, type ApprovalAccess, type ApprovalAccessDecisionInput, ApprovalAccessError, type ApprovalAccessOptions, type ApprovalAccessStores, type ApprovalDecision, ApprovalError, type ApprovalPolicy, type ApprovalPriority, type ApprovalRule, type ApprovalSubject, type AssertTaskAdmissionInput, type AssertTaskMessageClaimInput, type AssignmentPayload, type ClaimTaskMessageInput, type ClaimTaskMessageResult, type CompleteTaskMessageInput, type CompleteTaskWorkInput, type CompleteWorkflowTaskWorkInput, type CreateTaskEffectStoreOptions, type CreateTaskGraphStoreOptions, type CreateTaskInput, type CreateTaskMessageStoreOptions, type CreateTaskWorkStoreOptions, type DefaultTaskMessageProtocols, type DiscardTaskMessageInput, type ExecutionReference, type HandoffPayload, MAX_TASK_GRAPH_DEPENDENCIES, MAX_TASK_GRAPH_NODES, MAX_TASK_INBOX_PENDING, type MessageWaitingTask, type NeedsHumanTask, type ProcessTaskMessageOptions, type ProcessTaskMessageResult, type RecordTaskEffectInput, type RejectionDecision, type ResolveTaskEffectInput, type ResumeTaskWorkInput, type RootTaskOrigin, type SendTaskMessageInput, type SetTaskGraphDependenciesInput, type StandardTaskMessageProtocol, type SucceededTask, type Task, type TaskApproval, type TaskEffect, type TaskEffectKey, type TaskEffectLookupInput, type TaskEffectReference, type TaskEffectStore, type TaskGraph, TaskGraphConflictError, type TaskGraphNode, type TaskGraphNodeRef, type TaskGraphStore, type TaskId, type TaskInbox, type TaskKind, type TaskMessage, type TaskMessageAction, TaskMessageAuthorizationError, type TaskMessageAuthorizationInput, type TaskMessageAuthorize, type TaskMessageClaim, TaskMessageConflictError, type TaskMessageDiscard, type TaskMessageEffectKeyInput, TaskMessageError, type TaskMessageForProtocol, type TaskMessageId, TaskMessageLeaseError, type TaskMessageLookupInput, type TaskMessageOperationId, type TaskMessagePrincipal, type TaskMessageProcessing, type TaskMessageProcessingStores, type TaskMessageProtocol, type TaskMessageProtocolBinding, type TaskMessageProtocolBindingInput, type TaskMessageProtocolInput, type TaskMessageProtocolInputPayload, type TaskMessageProtocolStoredPayload, type TaskMessageProtocols, type TaskMessageRecipientInput, type TaskMessageSendInput, TaskMessageSourceFenceError, type TaskMessageStore, type TaskOrigin, type TaskPatch, type TaskQuestion, type TaskState, type TaskTransitionArguments, type TaskTransitionOptions, type TaskUsage, type TaskWorkStore, type TaskWorkStores, type TaskWorkUsage, type WaitForTaskMessagesInput, allowByDefaultApprovalPolicy, answerEventSchema, answerIdSchema, approvalPolicySchema, approvalPrioritySchema, approvalRuleSchema, assertTaskAdmission, assignmentPayloadSchema, assignmentTaskMessageProtocol, createApprovalAccess, createDefaultTaskMessageProtocols, createTaskEffectStore, createTaskGraphStore, createTaskMessageStore, createTaskWorkStore, defineTaskMessageProtocol, defineTaskMessageProtocols, executionReferenceSchema, handoffPayloadSchema, handoffTaskMessageProtocol, harnessSchema, isMessageWaitingTask, isNeedsHumanTask, isSucceededTask, parseTaskMessageProtocolBinding, processTaskMessage, readTaskMessageDecision, requiresApproval, sendTaskMessageInputSchema, standardTaskKinds, standardTaskMessageProtocols, taskApprovalSchema, taskBoundMessagePolicy, taskEffectKeySchema, taskEffectReferenceSchema, taskEffectSchema, taskExecutionRepositoryIdsSchema, taskGraphAdmissionSchema, taskGraphDependenciesSchema, taskGraphNodeSchema, taskGraphSchema, taskIdSchema, taskInboxSchema, taskInitialState, taskKindSchema, taskMessageClaimSchema, taskMessageDiscardSchema, taskMessageIdSchema, taskMessageInput, taskMessageOperationIdSchema, taskMessagePrincipalSchema, taskMessageProcessingSchema, taskMessageProtocolBindingSchema, taskMessageProtocolSchema, taskMessageReservationSchema, taskMessageSchema, taskOriginSchema, taskQuestionSchema, taskSchema, taskStateSchema, taskTransitions, taskUsageSchema };
|
|
17
|
+
export { type AdmitTaskGraphInput, type AgentSessionBinding, type AnswerEvent, AnswerEventConflictError, type AnswerId, type AnswerQuestionInput, type AnswerQuestionResult, type ApprovalAccess, type ApprovalAccessDecisionInput, ApprovalAccessError, type ApprovalAccessOptions, type ApprovalAccessStores, type ApprovalDecision, ApprovalError, type ApprovalPolicy, type ApprovalPriority, type ApprovalRule, type ApprovalSubject, type AssertTaskAdmissionInput, type AssertTaskMessageClaimInput, type AssignmentPayload, type ClaimTaskMessageInput, type ClaimTaskMessageResult, type CompleteTaskMessageInput, type CompleteTaskWorkInput, type CompleteWorkflowTaskWorkInput, type CreateTaskEffectStoreOptions, type CreateTaskGraphStoreOptions, type CreateTaskInput, type CreateTaskMessageStoreOptions, type CreateTaskWorkStoreOptions, type DefaultTaskMessageProtocols, type DiscardTaskMessageInput, type ExecutionReference, type HandoffPayload, MAX_TASK_GRAPH_DEPENDENCIES, MAX_TASK_GRAPH_NODES, MAX_TASK_INBOX_PENDING, type MessageWaitingTask, type NeedsHumanTask, type ProcessTaskMessageOptions, type ProcessTaskMessageResult, type RecordTaskEffectInput, type RejectionDecision, type ResolveTaskEffectInput, type ResumeTaskWorkInput, type RootTaskOrigin, type SendTaskMessageInput, type SetTaskGraphDependenciesInput, type StandardTaskMessageProtocol, type SucceededTask, type Task, type TaskApproval, TaskAttemptConflictError, type TaskEffect, type TaskEffectKey, type TaskEffectLookupInput, type TaskEffectReference, type TaskEffectStore, type TaskGraph, TaskGraphConflictError, type TaskGraphNode, type TaskGraphNodeRef, type TaskGraphStore, type TaskId, type TaskInbox, type TaskKind, type TaskMessage, type TaskMessageAction, TaskMessageAuthorizationError, type TaskMessageAuthorizationInput, type TaskMessageAuthorize, type TaskMessageClaim, TaskMessageConflictError, type TaskMessageDiscard, type TaskMessageEffectKeyInput, TaskMessageError, type TaskMessageForProtocol, type TaskMessageId, TaskMessageLeaseError, type TaskMessageLookupInput, type TaskMessageOperationId, type TaskMessagePrincipal, type TaskMessageProcessing, type TaskMessageProcessingStores, type TaskMessageProtocol, type TaskMessageProtocolBinding, type TaskMessageProtocolBindingInput, type TaskMessageProtocolInput, type TaskMessageProtocolInputPayload, type TaskMessageProtocolStoredPayload, type TaskMessageProtocols, type TaskMessageRecipientInput, type TaskMessageSendInput, TaskMessageSourceFenceError, type TaskMessageStore, type TaskOrigin, type TaskPatch, type TaskQuestion, type TaskState, type TaskTransitionArguments, type TaskTransitionOptions, type TaskUsage, type TaskWorkCompletionFence, type TaskWorkStore, type TaskWorkStores, type TaskWorkUsage, type WaitForTaskMessagesInput, allowByDefaultApprovalPolicy, answerEventSchema, answerIdSchema, approvalPolicySchema, approvalPrioritySchema, approvalRuleSchema, assertTaskAdmission, assignmentPayloadSchema, assignmentTaskMessageProtocol, createApprovalAccess, createDefaultTaskMessageProtocols, createTaskEffectStore, createTaskGraphStore, createTaskMessageStore, createTaskWorkStore, defineTaskMessageProtocol, defineTaskMessageProtocols, executionReferenceSchema, handoffPayloadSchema, handoffTaskMessageProtocol, harnessSchema, isMessageWaitingTask, isNeedsHumanTask, isSucceededTask, parseTaskMessageProtocolBinding, processTaskMessage, readTaskMessageDecision, requiresApproval, sendTaskMessageInputSchema, standardTaskKinds, standardTaskMessageProtocols, taskApprovalSchema, taskBoundMessagePolicy, taskEffectKeySchema, taskEffectReferenceSchema, taskEffectSchema, taskExecutionRepositoryIdsSchema, taskGraphAdmissionSchema, taskGraphDependenciesSchema, taskGraphNodeSchema, taskGraphSchema, taskIdSchema, taskInboxSchema, taskInitialState, taskKindSchema, taskMessageClaimSchema, taskMessageDiscardSchema, taskMessageIdSchema, taskMessageInput, taskMessageOperationIdSchema, taskMessagePrincipalSchema, taskMessageProcessingSchema, taskMessageProtocolBindingSchema, taskMessageProtocolSchema, taskMessageReservationSchema, taskMessageSchema, taskOriginSchema, taskQuestionSchema, taskSchema, taskStateSchema, taskTransitions, taskUsageSchema };
|
package/dist/tasks.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { answerIdSchema, taskIdSchema } from "./schema/id.mjs";
|
|
2
|
-
import { allowByDefaultApprovalPolicy, approvalPolicySchema, approvalPrioritySchema, approvalRuleSchema, requiresApproval } from "./approval.mjs";
|
|
3
2
|
import { executionReferenceSchema, harnessSchema, isMessageWaitingTask, isNeedsHumanTask, isSucceededTask, standardTaskKinds, taskApprovalSchema, taskExecutionRepositoryIdsSchema, taskKindSchema, taskOriginSchema, taskQuestionSchema, taskSchema, taskStateSchema, taskUsageSchema } from "./schema/task.mjs";
|
|
3
|
+
import { allowByDefaultApprovalPolicy, approvalPolicySchema, approvalPrioritySchema, approvalRuleSchema, requiresApproval } from "./approval.mjs";
|
|
4
4
|
import { taskInitialState, taskTransitions } from "./schema/transitions.mjs";
|
|
5
5
|
import { ApprovalAccessError, createApprovalAccess } from "./approval-access.mjs";
|
|
6
6
|
import { answerEventSchema } from "./schema/answer.mjs";
|
|
@@ -13,5 +13,5 @@ import { createTaskWorkStore } from "./store/task-work.mjs";
|
|
|
13
13
|
import { createTaskEffectStore } from "./store/task-effects.mjs";
|
|
14
14
|
import { processTaskMessage, readTaskMessageDecision } from "./task-message-processing.mjs";
|
|
15
15
|
import { assignmentPayloadSchema, assignmentTaskMessageProtocol, createDefaultTaskMessageProtocols, handoffPayloadSchema, handoffTaskMessageProtocol, standardTaskMessageProtocols } from "./task-protocols.mjs";
|
|
16
|
-
import { AnswerEventConflictError, ApprovalError } from "./store/engine.mjs";
|
|
17
|
-
export { AnswerEventConflictError, ApprovalAccessError, ApprovalError, MAX_TASK_GRAPH_DEPENDENCIES, MAX_TASK_GRAPH_NODES, MAX_TASK_INBOX_PENDING, TaskGraphConflictError, TaskMessageAuthorizationError, TaskMessageConflictError, TaskMessageError, TaskMessageLeaseError, TaskMessageSourceFenceError, allowByDefaultApprovalPolicy, answerEventSchema, answerIdSchema, approvalPolicySchema, approvalPrioritySchema, approvalRuleSchema, assertTaskAdmission, assignmentPayloadSchema, assignmentTaskMessageProtocol, createApprovalAccess, createDefaultTaskMessageProtocols, createTaskEffectStore, createTaskGraphStore, createTaskMessageStore, createTaskWorkStore, defineTaskMessageProtocol, defineTaskMessageProtocols, executionReferenceSchema, handoffPayloadSchema, handoffTaskMessageProtocol, harnessSchema, isMessageWaitingTask, isNeedsHumanTask, isSucceededTask, parseTaskMessageProtocolBinding, processTaskMessage, readTaskMessageDecision, requiresApproval, sendTaskMessageInputSchema, standardTaskKinds, standardTaskMessageProtocols, taskApprovalSchema, taskBoundMessagePolicy, taskEffectKeySchema, taskEffectReferenceSchema, taskEffectSchema, taskExecutionRepositoryIdsSchema, taskGraphAdmissionSchema, taskGraphDependenciesSchema, taskGraphNodeSchema, taskGraphSchema, taskIdSchema, taskInboxSchema, taskInitialState, taskKindSchema, taskMessageClaimSchema, taskMessageDiscardSchema, taskMessageIdSchema, taskMessageInput, taskMessageOperationIdSchema, taskMessagePrincipalSchema, taskMessageProcessingSchema, taskMessageProtocolBindingSchema, taskMessageProtocolSchema, taskMessageReservationSchema, taskMessageSchema, taskOriginSchema, taskQuestionSchema, taskSchema, taskStateSchema, taskTransitions, taskUsageSchema };
|
|
16
|
+
import { AnswerEventConflictError, ApprovalError, TaskAttemptConflictError } from "./store/engine.mjs";
|
|
17
|
+
export { AnswerEventConflictError, ApprovalAccessError, ApprovalError, MAX_TASK_GRAPH_DEPENDENCIES, MAX_TASK_GRAPH_NODES, MAX_TASK_INBOX_PENDING, TaskAttemptConflictError, TaskGraphConflictError, TaskMessageAuthorizationError, TaskMessageConflictError, TaskMessageError, TaskMessageLeaseError, TaskMessageSourceFenceError, allowByDefaultApprovalPolicy, answerEventSchema, answerIdSchema, approvalPolicySchema, approvalPrioritySchema, approvalRuleSchema, assertTaskAdmission, assignmentPayloadSchema, assignmentTaskMessageProtocol, createApprovalAccess, createDefaultTaskMessageProtocols, createTaskEffectStore, createTaskGraphStore, createTaskMessageStore, createTaskWorkStore, defineTaskMessageProtocol, defineTaskMessageProtocols, executionReferenceSchema, handoffPayloadSchema, handoffTaskMessageProtocol, harnessSchema, isMessageWaitingTask, isNeedsHumanTask, isSucceededTask, parseTaskMessageProtocolBinding, processTaskMessage, readTaskMessageDecision, requiresApproval, sendTaskMessageInputSchema, standardTaskKinds, standardTaskMessageProtocols, taskApprovalSchema, taskBoundMessagePolicy, taskEffectKeySchema, taskEffectReferenceSchema, taskEffectSchema, taskExecutionRepositoryIdsSchema, taskGraphAdmissionSchema, taskGraphDependenciesSchema, taskGraphNodeSchema, taskGraphSchema, taskIdSchema, taskInboxSchema, taskInitialState, taskKindSchema, taskMessageClaimSchema, taskMessageDiscardSchema, taskMessageIdSchema, taskMessageInput, taskMessageOperationIdSchema, taskMessagePrincipalSchema, taskMessageProcessingSchema, taskMessageProtocolBindingSchema, taskMessageProtocolSchema, taskMessageReservationSchema, taskMessageSchema, taskOriginSchema, taskQuestionSchema, taskSchema, taskStateSchema, taskTransitions, taskUsageSchema };
|
package/dist/vercel-git.d.mts
CHANGED
|
@@ -1,7 +1,27 @@
|
|
|
1
1
|
import { RepositorySlug } from "./schema/repository.mjs";
|
|
2
2
|
import { GitHubGitTransport } from "./github-transfer.mjs";
|
|
3
|
-
import { Sandbox } from "@vercel/sandbox";
|
|
3
|
+
import { NetworkPolicy, Sandbox } from "@vercel/sandbox";
|
|
4
4
|
//#region src/vercel-git.d.ts
|
|
5
|
+
type TrustedGitSandbox = Pick<Sandbox, "cwd" | "runCommand" | "writeFiles" | "readFile" | "updateNetworkPolicy" | "stop">;
|
|
6
|
+
/** Exact creation request for a fresh Vercel-managed trusted Git publication Sandbox. */
|
|
7
|
+
interface VercelManagedGitHubSandboxCreateInput {
|
|
8
|
+
source: {
|
|
9
|
+
type: "git";
|
|
10
|
+
url: string;
|
|
11
|
+
};
|
|
12
|
+
__source_credentials: true;
|
|
13
|
+
__commitAs: {
|
|
14
|
+
name: string;
|
|
15
|
+
email: string;
|
|
16
|
+
};
|
|
17
|
+
networkPolicy: NetworkPolicy;
|
|
18
|
+
persistent: false;
|
|
19
|
+
resources: {
|
|
20
|
+
vcpus: 6;
|
|
21
|
+
};
|
|
22
|
+
runtime: "node24";
|
|
23
|
+
timeout: number;
|
|
24
|
+
}
|
|
5
25
|
/** Repository and access level used to mint one trusted Git transport token. */
|
|
6
26
|
interface VercelGitHubTokenInput {
|
|
7
27
|
repository: RepositorySlug;
|
|
@@ -12,10 +32,22 @@ interface VercelGitHubGitTransportOptions {
|
|
|
12
32
|
/** Reads must mint contents:read credentials; writes are confined to the supplied repository. */
|
|
13
33
|
token(input: VercelGitHubTokenInput): Promise<string>;
|
|
14
34
|
/** Creates a fresh sandbox, without a snapshot, repository, or running agent. */
|
|
15
|
-
createSandbox?: () => Promise<
|
|
35
|
+
createSandbox?: () => Promise<TrustedGitSandbox>;
|
|
36
|
+
}
|
|
37
|
+
/** Configures Vercel-managed Git reads and signed publication. */
|
|
38
|
+
interface VercelManagedGitHubGitTransportOptions {
|
|
39
|
+
/** Attribution metadata supplied for managed signed commits. */
|
|
40
|
+
commitIdentity: {
|
|
41
|
+
name: string;
|
|
42
|
+
email: string;
|
|
43
|
+
};
|
|
44
|
+
/** Creates one unnamed Sandbox from the exact managed credential request. */
|
|
45
|
+
createSandbox?: (input: VercelManagedGitHubSandboxCreateInput) => Promise<TrustedGitSandbox>;
|
|
16
46
|
}
|
|
17
47
|
/** Transfer Git objects through fresh trusted sandboxes; credentials never reach an agent sandbox. */
|
|
18
48
|
declare function createVercelGitHubGitTransport(options: VercelGitHubGitTransportOptions): GitHubGitTransport;
|
|
49
|
+
/** Use fresh Vercel-managed trusted Sandboxes for Git reads and signed publication. */
|
|
50
|
+
declare function createVercelManagedGitHubGitTransport(options: VercelManagedGitHubGitTransportOptions): GitHubGitTransport;
|
|
19
51
|
//#endregion
|
|
20
|
-
export { VercelGitHubGitTransportOptions, VercelGitHubTokenInput, createVercelGitHubGitTransport };
|
|
52
|
+
export { VercelGitHubGitTransportOptions, VercelGitHubTokenInput, VercelManagedGitHubGitTransportOptions, VercelManagedGitHubSandboxCreateInput, createVercelGitHubGitTransport, createVercelManagedGitHubGitTransport };
|
|
21
53
|
//# sourceMappingURL=vercel-git.d.mts.map
|
package/dist/vercel-git.mjs
CHANGED
|
@@ -1,7 +1,80 @@
|
|
|
1
1
|
import { repositorySlugSchema } from "./schema/repository.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { withSandboxGitBundle } from "./github-transfer.mjs";
|
|
3
3
|
import { isSafeGitRef } from "./github-publication.mjs";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { Readable } from "node:stream";
|
|
4
6
|
//#region src/vercel-git.ts
|
|
7
|
+
const gitOidPattern = /^[a-f0-9]{40}$/u;
|
|
8
|
+
const absentGitOid = "0".repeat(40);
|
|
9
|
+
const managedGitTimeoutMs = 18e4;
|
|
10
|
+
async function createManagedSandbox(input) {
|
|
11
|
+
const { Sandbox } = await import("@vercel/sandbox");
|
|
12
|
+
return Sandbox.create({ ...input });
|
|
13
|
+
}
|
|
14
|
+
function publicationBase(input) {
|
|
15
|
+
if (!isSafeGitRef(input.branch) || !gitOidPattern.test(input.head) || !gitOidPattern.test(input.expectedHead) || (input.expectedHead === absentGitOid ? input.baseHead === void 0 || !gitOidPattern.test(input.baseHead) : input.baseHead !== void 0)) throw new Error("Invalid Git publication target");
|
|
16
|
+
return input.expectedHead === absentGitOid ? input.baseHead : input.expectedHead;
|
|
17
|
+
}
|
|
18
|
+
async function trustedCommand(sandbox, input) {
|
|
19
|
+
const result = await sandbox.runCommand({
|
|
20
|
+
...input,
|
|
21
|
+
timeoutMs: 12e4
|
|
22
|
+
});
|
|
23
|
+
if (result.exitCode !== 0) throw new Error(`Trusted Git operation failed (${result.exitCode})`);
|
|
24
|
+
return (await result.stdout()).trim();
|
|
25
|
+
}
|
|
26
|
+
function bundleTransferSandbox(sandbox) {
|
|
27
|
+
return {
|
|
28
|
+
async run({ command }) {
|
|
29
|
+
const result = await sandbox.runCommand({
|
|
30
|
+
cmd: "sh",
|
|
31
|
+
args: ["-c", command]
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
exitCode: result.exitCode,
|
|
35
|
+
stdout: await result.stdout()
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
async writeBinaryFile({ path, content }) {
|
|
39
|
+
await sandbox.writeFiles([{
|
|
40
|
+
path,
|
|
41
|
+
content: Buffer.from(content.buffer, content.byteOffset, content.byteLength)
|
|
42
|
+
}]);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async function consumeSandboxBundle(sandbox, path, use) {
|
|
47
|
+
const size = await sandbox.runCommand({
|
|
48
|
+
cmd: "stat",
|
|
49
|
+
args: [
|
|
50
|
+
"-c",
|
|
51
|
+
"%s",
|
|
52
|
+
path
|
|
53
|
+
]
|
|
54
|
+
});
|
|
55
|
+
const byteLength = Number(await size.stdout());
|
|
56
|
+
if (size.exitCode !== 0 || !Number.isSafeInteger(byteLength) || byteLength < 1 || byteLength > 314572800) throw new Error("Git bundle exceeds transfer limit");
|
|
57
|
+
const source = await sandbox.readFile({ path });
|
|
58
|
+
if (!source) throw new Error("Git bundle was not created");
|
|
59
|
+
const readable = source instanceof Readable ? source : new Readable({ highWaterMark: 65536 }).wrap(source);
|
|
60
|
+
const iterator = readable[Symbol.asyncIterator]();
|
|
61
|
+
const stream = new ReadableStream({
|
|
62
|
+
async pull(controller) {
|
|
63
|
+
const { done, value } = await iterator.next();
|
|
64
|
+
if (done) controller.close();
|
|
65
|
+
else controller.enqueue(value);
|
|
66
|
+
},
|
|
67
|
+
async cancel() {
|
|
68
|
+
await iterator.return?.();
|
|
69
|
+
}
|
|
70
|
+
}, { highWaterMark: 0 });
|
|
71
|
+
try {
|
|
72
|
+
await use({ stream });
|
|
73
|
+
} finally {
|
|
74
|
+
readable.destroy();
|
|
75
|
+
if (!stream.locked) await stream.cancel();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
5
78
|
function credentialPolicy(repository, token) {
|
|
6
79
|
return { allow: { "github.com": [{
|
|
7
80
|
match: { path: { startsWith: `/${repository}.git/` } },
|
|
@@ -57,7 +130,7 @@ function createVercelGitHubGitTransport(options) {
|
|
|
57
130
|
}
|
|
58
131
|
}
|
|
59
132
|
return {
|
|
60
|
-
async fetch(input) {
|
|
133
|
+
async fetch(input, use) {
|
|
61
134
|
if (input.refs.some((ref) => !ref.startsWith("refs/") || !isSafeGitRef(ref))) throw new Error("Invalid Git fetch ref");
|
|
62
135
|
return isolated(input.repository, async (sandbox, git, slug) => {
|
|
63
136
|
await sandbox.updateNetworkPolicy(credentialPolicy(slug, await options.token({
|
|
@@ -68,45 +141,204 @@ function createVercelGitHubGitTransport(options) {
|
|
|
68
141
|
if (input.refs.length) await git("-C", "/tmp/transfer.git", "fetch", "--no-tags", "origin", ...input.refs.map((ref) => `+${ref}:${ref}`));
|
|
69
142
|
await sandbox.updateNetworkPolicy("deny-all");
|
|
70
143
|
await git("-C", "/tmp/transfer.git", "bundle", "create", "/tmp/transfer.bundle", "--all");
|
|
71
|
-
|
|
72
|
-
|
|
144
|
+
await consumeSandboxBundle(sandbox, "/tmp/transfer.bundle", use);
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
async push(input) {
|
|
148
|
+
try {
|
|
149
|
+
const baseHead = publicationBase(input);
|
|
150
|
+
return await isolated(input.repository, async (sandbox, git, slug) => {
|
|
151
|
+
await git("init", "--bare", "/tmp/transfer.git");
|
|
152
|
+
return await withSandboxGitBundle({
|
|
153
|
+
sandbox: bundleTransferSandbox(sandbox),
|
|
154
|
+
bundle: input.bundle,
|
|
155
|
+
async use(path) {
|
|
156
|
+
await git("-C", "/tmp/transfer.git", "bundle", "verify", path);
|
|
157
|
+
await git("-C", "/tmp/transfer.git", "fetch", path, "HEAD:refs/heads/candidate");
|
|
158
|
+
if (await git("-C", "/tmp/transfer.git", "rev-parse", "refs/heads/candidate") !== input.head) throw new Error("Git bundle head does not match publication target");
|
|
159
|
+
await git("-C", "/tmp/transfer.git", "merge-base", "--is-ancestor", baseHead, "refs/heads/candidate");
|
|
160
|
+
await sandbox.updateNetworkPolicy(credentialPolicy(slug, await options.token({
|
|
161
|
+
repository: slug,
|
|
162
|
+
access: "write"
|
|
163
|
+
})));
|
|
164
|
+
await input.authorize();
|
|
165
|
+
await git("-C", "/tmp/transfer.git", "push", `--force-with-lease=refs/heads/${input.branch}:${input.expectedHead}`, `https://github.com/${slug}.git`, `${input.head}:refs/heads/${input.branch}`);
|
|
166
|
+
return input.head;
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
} finally {
|
|
171
|
+
if (!input.bundle.stream.locked) await input.bundle.stream.cancel();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/** Use fresh Vercel-managed trusted Sandboxes for Git reads and signed publication. */
|
|
177
|
+
function createVercelManagedGitHubGitTransport(options) {
|
|
178
|
+
const createSandbox = options.createSandbox ?? createManagedSandbox;
|
|
179
|
+
const createInput = (repository, access) => ({
|
|
180
|
+
source: {
|
|
181
|
+
type: "git",
|
|
182
|
+
url: `https://github.com/${repository}.git`
|
|
183
|
+
},
|
|
184
|
+
__source_credentials: true,
|
|
185
|
+
__commitAs: options.commitIdentity,
|
|
186
|
+
networkPolicy: { allow: access === "read" ? ["github.com"] : ["github.com", "api.github.com"] },
|
|
187
|
+
persistent: false,
|
|
188
|
+
resources: { vcpus: 6 },
|
|
189
|
+
runtime: "node24",
|
|
190
|
+
timeout: managedGitTimeoutMs
|
|
191
|
+
});
|
|
192
|
+
return {
|
|
193
|
+
async fetch(input, use) {
|
|
194
|
+
if (input.refs.some((ref) => !ref.startsWith("refs/") || !isSafeGitRef(ref))) throw new Error("Invalid Git fetch ref");
|
|
195
|
+
const repository = repositorySlugSchema.parse(input.repository.toLowerCase());
|
|
196
|
+
const sandbox = await createSandbox(createInput(repository, "read"));
|
|
197
|
+
const repositoryPath = sandbox.cwd;
|
|
198
|
+
try {
|
|
199
|
+
await trustedCommand(sandbox, {
|
|
200
|
+
cmd: "/usr/bin/git",
|
|
73
201
|
args: [
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"
|
|
202
|
+
"clone",
|
|
203
|
+
"--bare",
|
|
204
|
+
"--no-local",
|
|
205
|
+
repositoryPath,
|
|
206
|
+
"/tmp/transfer.git"
|
|
77
207
|
]
|
|
78
208
|
});
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
209
|
+
if (input.refs.length > 0) await trustedCommand(sandbox, {
|
|
210
|
+
cmd: "git",
|
|
211
|
+
args: [
|
|
212
|
+
"-C",
|
|
213
|
+
"/tmp/transfer.git",
|
|
214
|
+
"fetch",
|
|
215
|
+
"--no-tags",
|
|
216
|
+
`https://github.com/${repository}.git`,
|
|
217
|
+
...input.refs.map((ref) => `+${ref}:${ref}`)
|
|
218
|
+
]
|
|
219
|
+
});
|
|
220
|
+
await trustedCommand(sandbox, {
|
|
221
|
+
cmd: "/usr/bin/git",
|
|
222
|
+
args: [
|
|
223
|
+
"-C",
|
|
224
|
+
"/tmp/transfer.git",
|
|
225
|
+
"bundle",
|
|
226
|
+
"create",
|
|
227
|
+
"/tmp/transfer.bundle",
|
|
228
|
+
"--all"
|
|
229
|
+
]
|
|
230
|
+
});
|
|
231
|
+
await consumeSandboxBundle(sandbox, "/tmp/transfer.bundle", use);
|
|
232
|
+
} finally {
|
|
233
|
+
await sandbox.stop();
|
|
234
|
+
}
|
|
87
235
|
},
|
|
88
236
|
async push(input) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
await
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
237
|
+
try {
|
|
238
|
+
const baseHead = publicationBase(input);
|
|
239
|
+
if (input.expectedHead === absentGitOid) throw new Error("Vercel-managed signed publication requires an existing remote branch");
|
|
240
|
+
const repository = repositorySlugSchema.parse(input.repository.toLowerCase());
|
|
241
|
+
const sandbox = await createSandbox(createInput(repository, "write"));
|
|
242
|
+
const repositoryPath = sandbox.cwd;
|
|
243
|
+
try {
|
|
244
|
+
const hooksPath = `/tmp/factory-hooks-${randomUUID()}`;
|
|
245
|
+
await trustedCommand(sandbox, {
|
|
246
|
+
cmd: "mkdir",
|
|
247
|
+
args: [
|
|
248
|
+
"-m",
|
|
249
|
+
"700",
|
|
250
|
+
hooksPath
|
|
251
|
+
]
|
|
252
|
+
});
|
|
253
|
+
await trustedCommand(sandbox, {
|
|
254
|
+
cmd: "/usr/bin/git",
|
|
255
|
+
args: [
|
|
256
|
+
"config",
|
|
257
|
+
"core.hooksPath",
|
|
258
|
+
hooksPath
|
|
259
|
+
],
|
|
260
|
+
cwd: repositoryPath
|
|
261
|
+
});
|
|
262
|
+
await withSandboxGitBundle({
|
|
263
|
+
sandbox: bundleTransferSandbox(sandbox),
|
|
264
|
+
bundle: input.bundle,
|
|
265
|
+
async use(path) {
|
|
266
|
+
await trustedCommand(sandbox, {
|
|
267
|
+
cmd: "/usr/bin/git",
|
|
268
|
+
args: [
|
|
269
|
+
"bundle",
|
|
270
|
+
"verify",
|
|
271
|
+
path
|
|
272
|
+
],
|
|
273
|
+
cwd: repositoryPath
|
|
274
|
+
});
|
|
275
|
+
await trustedCommand(sandbox, {
|
|
276
|
+
cmd: "/usr/bin/git",
|
|
277
|
+
args: [
|
|
278
|
+
"fetch",
|
|
279
|
+
"--no-tags",
|
|
280
|
+
path,
|
|
281
|
+
`${input.head}:refs/factory/candidate`
|
|
282
|
+
],
|
|
283
|
+
cwd: repositoryPath
|
|
284
|
+
});
|
|
285
|
+
if (await trustedCommand(sandbox, {
|
|
286
|
+
cmd: "/usr/bin/git",
|
|
287
|
+
args: ["rev-parse", "refs/factory/candidate"],
|
|
288
|
+
cwd: repositoryPath
|
|
289
|
+
}) !== input.head) throw new Error("Git bundle head does not match publication target");
|
|
290
|
+
await trustedCommand(sandbox, {
|
|
291
|
+
cmd: "/usr/bin/git",
|
|
292
|
+
args: [
|
|
293
|
+
"merge-base",
|
|
294
|
+
"--is-ancestor",
|
|
295
|
+
baseHead,
|
|
296
|
+
"refs/factory/candidate"
|
|
297
|
+
],
|
|
298
|
+
cwd: repositoryPath
|
|
299
|
+
});
|
|
300
|
+
await trustedCommand(sandbox, {
|
|
301
|
+
cmd: "/usr/bin/git",
|
|
302
|
+
args: [
|
|
303
|
+
"checkout",
|
|
304
|
+
"-B",
|
|
305
|
+
input.branch,
|
|
306
|
+
"refs/factory/candidate"
|
|
307
|
+
],
|
|
308
|
+
cwd: repositoryPath
|
|
309
|
+
});
|
|
310
|
+
await input.authorize();
|
|
311
|
+
await trustedCommand(sandbox, {
|
|
312
|
+
cmd: "/usr/bin/env",
|
|
313
|
+
args: [
|
|
314
|
+
`VERCEL_GIT_EXPECTED_REMOTE_OID=${input.expectedHead}`,
|
|
315
|
+
...input.baseHead === void 0 ? [] : [`VERCEL_GIT_BASE_OID=${input.baseHead}`],
|
|
316
|
+
"git",
|
|
317
|
+
"push",
|
|
318
|
+
"origin",
|
|
319
|
+
input.branch
|
|
320
|
+
],
|
|
321
|
+
cwd: repositoryPath
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
const signedHead = await trustedCommand(sandbox, {
|
|
326
|
+
cmd: "/usr/bin/git",
|
|
327
|
+
args: ["rev-parse", "HEAD"],
|
|
328
|
+
cwd: repositoryPath
|
|
329
|
+
});
|
|
330
|
+
if (!gitOidPattern.test(signedHead)) throw new Error("Managed signed push returned an invalid Git head");
|
|
331
|
+
return signedHead;
|
|
332
|
+
} finally {
|
|
333
|
+
await sandbox.stop();
|
|
334
|
+
}
|
|
335
|
+
} finally {
|
|
336
|
+
if (!input.bundle.stream.locked) await input.bundle.stream.cancel();
|
|
337
|
+
}
|
|
106
338
|
}
|
|
107
339
|
};
|
|
108
340
|
}
|
|
109
341
|
//#endregion
|
|
110
|
-
export { createVercelGitHubGitTransport };
|
|
342
|
+
export { createVercelGitHubGitTransport, createVercelManagedGitHubGitTransport };
|
|
111
343
|
|
|
112
344
|
//# sourceMappingURL=vercel-git.mjs.map
|