@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
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Getting started with `@vercel/factory`
|
|
2
|
+
|
|
3
|
+
This guide isolates Factory's durable Task state with process-local storage. It calls no model,
|
|
4
|
+
Eve agent, provider, sandbox, HTTP endpoint, or planner, so it is safe to run as a local smoke
|
|
5
|
+
test. For the primary agent path, continue with
|
|
6
|
+
[Run an Eve agent as a Factory Task](recipes/eve-agent.md).
|
|
7
|
+
|
|
8
|
+
## Requirements
|
|
9
|
+
|
|
10
|
+
- Node.js 24 or newer
|
|
11
|
+
- An ESM project (`"type": "module"` in `package.json`) or an `.mts` file
|
|
12
|
+
- `@vercel/factory` and its required `eve` and `zod` peers
|
|
13
|
+
|
|
14
|
+
Install them with your package manager:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
pnpm add @vercel/factory eve zod
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Optional storage, sandbox, and harness integrations have additional peer dependencies. Install
|
|
21
|
+
those only when you import the corresponding capability path; the package manifest declares their
|
|
22
|
+
supported ranges.
|
|
23
|
+
|
|
24
|
+
## Run a local task
|
|
25
|
+
|
|
26
|
+
Save this as `factory-quickstart.mts`. The example uses only public capability entry points from
|
|
27
|
+
the installed package.
|
|
28
|
+
|
|
29
|
+
<!-- runnable-example:start -->
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { repositoryIdSchema, repositoryScope } from "@vercel/factory";
|
|
33
|
+
import { createInMemoryDriver, createStores } from "@vercel/factory/storage";
|
|
34
|
+
import { taskWork } from "@vercel/factory/workflows";
|
|
35
|
+
|
|
36
|
+
const repositoryId = repositoryIdSchema.parse("repo_example");
|
|
37
|
+
const stores = createStores({ driver: createInMemoryDriver() });
|
|
38
|
+
|
|
39
|
+
const task = await stores.tasks.create({
|
|
40
|
+
repositoryIds: repositoryScope(repositoryId),
|
|
41
|
+
kind: "analysis",
|
|
42
|
+
origin: { operator: "local:quickstart" },
|
|
43
|
+
replyTo: { channel: "local", address: "quickstart" },
|
|
44
|
+
work: {
|
|
45
|
+
...taskWork({
|
|
46
|
+
title: "Return a greeting",
|
|
47
|
+
input: { name: "Factory" },
|
|
48
|
+
}),
|
|
49
|
+
completionCriteria: ["Return a greeting for the supplied name"],
|
|
50
|
+
},
|
|
51
|
+
dedupeKey: "quickstart:greeting",
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const running = await stores.tasks.transition(task.id, "running");
|
|
55
|
+
const completed = await stores.work.complete({
|
|
56
|
+
task: { taskId: running.id, attempt: running.attempt },
|
|
57
|
+
output: { greeting: "Hello, Factory!" },
|
|
58
|
+
});
|
|
59
|
+
const stored = await stores.tasks.get(task.id);
|
|
60
|
+
|
|
61
|
+
if (
|
|
62
|
+
completed.state !== "succeeded" ||
|
|
63
|
+
stored === null ||
|
|
64
|
+
stored.state !== "succeeded" ||
|
|
65
|
+
stored.workResult === undefined
|
|
66
|
+
) {
|
|
67
|
+
throw new Error("Expected the Task to succeed");
|
|
68
|
+
}
|
|
69
|
+
if (JSON.stringify(stored.workResult.output) !== '{"greeting":"Hello, Factory!"}') {
|
|
70
|
+
throw new Error("Expected the completed greeting");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log(
|
|
74
|
+
JSON.stringify({ taskId: stored.id, state: stored.state, output: stored.workResult.output }),
|
|
75
|
+
);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
<!-- runnable-example:end -->
|
|
79
|
+
|
|
80
|
+
Run it directly with Node.js 24:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
node factory-quickstart.mts
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The command prints a generated Task ID, the `succeeded` state, and the validated output:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{ "taskId": "task_<generated>", "state": "succeeded", "output": { "greeting": "Hello, Factory!" } }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`createInMemoryDriver` is non-durable and intended for development and tests. A production factory
|
|
93
|
+
supplies a persistent driver and owns its authorization, workflow policy, credentials, and
|
|
94
|
+
provider configuration.
|
|
95
|
+
|
|
96
|
+
## What happened
|
|
97
|
+
|
|
98
|
+
`taskWork` selected the built-in `task@1` workflow, which accepts bounded JSON input and output.
|
|
99
|
+
Task creation persisted a queued Task and its graph root. The explicit transition started the
|
|
100
|
+
Task, and `stores.work.complete` validated and recorded its output before moving it to `succeeded`.
|
|
101
|
+
|
|
102
|
+
Task kinds are descriptive. Workflow bindings select input and output contracts, and exact agent
|
|
103
|
+
routes select Eve agent execution. See the [documentation index](index.md) for typed workflows,
|
|
104
|
+
persistent storage, execution providers, Task messages, and operator clients.
|
package/docs/index.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# `@vercel/factory` consumer documentation
|
|
2
|
+
|
|
3
|
+
These guides and declaration files ship with the installed package. They describe the same version
|
|
4
|
+
of `@vercel/factory` that your project resolves.
|
|
5
|
+
|
|
6
|
+
Start with [Run an Eve agent as a Factory Task](recipes/eve-agent.md) for the primary integration
|
|
7
|
+
path. The [local Task quickstart](getting-started.md) isolates the durable state model without
|
|
8
|
+
running an agent. Read [runtime composition](runtime-integration.md) when assembling a complete
|
|
9
|
+
factory.
|
|
10
|
+
|
|
11
|
+
## Runnable recipes
|
|
12
|
+
|
|
13
|
+
Each recipe contains a complete, self-checking TypeScript program. The programs use local storage
|
|
14
|
+
or injected local boundaries, require no credentials, and are compiled and executed against both
|
|
15
|
+
the built package and the packed release artifact.
|
|
16
|
+
|
|
17
|
+
| Goal | Recipe | Main APIs |
|
|
18
|
+
| ------------------------------------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
|
19
|
+
| Run and complete an Eve agent in one durable attempt | [Eve agent integration](recipes/eve-agent.md) | `startEveSession`, `withFactoryTask`, Task tools and hooks |
|
|
20
|
+
| Submit a schema-specific result from an Eve tool | [Typed Eve result](recipes/typed-eve-result.md) | `defineWorkflow`, `requireTaskExecution`, `completeWorkflow` |
|
|
21
|
+
| Pause for an authenticated answer and resume Eve | [Human input](recipes/human-input.md) | `request_human_input`, `answer_question`, `sweepQueued` |
|
|
22
|
+
| Enforce execution order and hand results between agents | [Dependent Eve agents](recipes/dependent-tasks.md) | dependencies, `sweepQueued`, authenticated Task tools |
|
|
23
|
+
| Define typed work and connect a non-Eve provider | [Custom workflow and local execution](recipes/custom-workflow.md) | `defineWorkflow`, `dispatchTask`, `launchExecution`, `completeWorkflow` |
|
|
24
|
+
| Reopen durable storage and recover workflow-owned work | [Persistence and restart recovery](recipes/persistence-recovery.md) | `applyFactoryMigrations`, `createPostgresDriver`, `recover` |
|
|
25
|
+
| Stop provider or Eve work and fence late output | [Cancellation](recipes/cancellation.md) | `cancelExecution`, `cancelEveSession`, attempt fences |
|
|
26
|
+
| Recover Eve failures and exhaust bounded retries | [Retry and recovery](recipes/retry-recovery.md) | `createFactoryHooks`, `sweepQueued`, authenticated completion |
|
|
27
|
+
| Send and process an authenticated typed message | [Task messaging](recipes/task-messaging.md) | `requireTaskExecution`, message protocols, `processTaskMessage` |
|
|
28
|
+
|
|
29
|
+
Read them in table order when building an Eve-based factory. They progress from one authenticated
|
|
30
|
+
session through typed results, human pauses, graph ordering, alternative execution, persistence,
|
|
31
|
+
lifecycle recovery, and inter-Task communication.
|
|
32
|
+
|
|
33
|
+
## Behavioral contracts
|
|
34
|
+
|
|
35
|
+
The installed declarations are the source of truth for operation semantics. In particular:
|
|
36
|
+
|
|
37
|
+
- `createStores` and `FactoryStores.tasks` document persistence, deduplication, transition fences,
|
|
38
|
+
and when a mutation is durable.
|
|
39
|
+
- `dispatchTask` and `sweepQueued` distinguish accepted execution, completed work, retryable gates,
|
|
40
|
+
terminal routing failures, and provider acceptance that remains ambiguous.
|
|
41
|
+
- `FactoryClient` documents idempotency keys, optimistic execution matching, request completion,
|
|
42
|
+
redirects, timeouts, and validated responses.
|
|
43
|
+
- `createBlobDriver` and `createPostgresDriver` document concurrency, retry, migration, and
|
|
44
|
+
compare-and-swap responsibilities.
|
|
45
|
+
|
|
46
|
+
Search the linked declaration files below for the exact installed signatures and JSDoc. Full
|
|
47
|
+
recipes remain the preferred starting point when several operations must be composed.
|
|
48
|
+
|
|
49
|
+
## Find the right capability
|
|
50
|
+
|
|
51
|
+
| Goal | Import path | Start with | Version-matched declarations |
|
|
52
|
+
| ------------------------------------------------------ | ------------------------------------------------------------------ | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
53
|
+
| Configure a factory and validate repository identity | `@vercel/factory` | `defineFactory`, `repositoryIdSchema`, `repositoryIdForSlug` | [root declarations](../dist/index.d.mts) |
|
|
54
|
+
| Create Tasks, graphs, messages, approvals, and effects | `@vercel/factory/tasks` | `Task`, `createDefaultTaskMessageProtocols`, `taskBoundMessagePolicy` | [Task declarations](../dist/tasks.d.mts) |
|
|
55
|
+
| Define versioned work and exact agent routes | `@vercel/factory/workflows` | `defineWorkflow`, `defineWorkflows`, `taskWork`, `defineAgentRoutes` | [workflow declarations](../dist/workflows.d.mts) |
|
|
56
|
+
| Create a store engine or local in-memory store | `@vercel/factory/storage` | `createStores`, `createInMemoryDriver`, `StoreDriver` | [storage declarations](../dist/storage.d.mts) |
|
|
57
|
+
| Use Blob or Postgres persistence | `@vercel/factory/storage/blob`, `@vercel/factory/storage/postgres` | `createBlobDriver`, `createPostgresDriver` | [Blob](../dist/blob/index.d.mts), [Postgres](../dist/postgres/index.d.mts) |
|
|
58
|
+
| Dispatch work through execution providers | `@vercel/factory/execution` | `dispatchTask`, `ExecutionAdapter` | [execution declarations](../dist/execution-public.d.mts) |
|
|
59
|
+
| Run bounded recursive planning | `@vercel/factory/planning` | `createPlannerRuntime`, `createPlannerMessageProtocols` | [planning declarations](../dist/planning/runtime.d.mts) |
|
|
60
|
+
| Apply budgets and inspect immutable receipts | `@vercel/factory/budgets`, `@vercel/factory/receipts` | `canReserve`, `sweepBudget`, `receiptSchema` | [budgets](../dist/budgets.d.mts), [receipts](../dist/receipts.d.mts) |
|
|
61
|
+
| Normalize intake and deliver replies | `@vercel/factory/signals`, `@vercel/factory/communication` | `signalSchema`, `createTaskCommunicationReconciler` | [signals](../dist/signals.d.mts), [communication](../dist/communication.d.mts) |
|
|
62
|
+
| Build browser or terminal operator clients | `@vercel/factory/client` | `FactoryClient`, `followSessionStream`, `Transcript` | [client declarations](../dist/client.d.mts) |
|
|
63
|
+
| Mount server handlers or a Next.js factory | `@vercel/factory/api`, `@vercel/factory/next` | `createFactoryApi`, `buildFactory` | [API](../dist/api.d.mts), [Next.js](../dist/build-factory.d.mts) |
|
|
64
|
+
| Compose the supplied software-development policy | `@vercel/factory/presets/software-development` | `createSoftwareDevelopmentTools`, `dispatchSoftwareDevelopmentWork` | [preset declarations](../dist/presets/software-development.d.mts) |
|
|
65
|
+
| Add sandbox execution | `@vercel/factory/sandbox` | `createSandboxExecutionAdapter`, `createTaskSandboxEnvironment` | [sandbox declarations](../dist/sandbox/index.d.mts) |
|
|
66
|
+
| Add provider adapters | `@vercel/factory/integrations/<provider>` | Select GitHub, Slack, DeepSec, or Vercel explicitly | [GitHub](../dist/integrations/github.d.mts), [Slack](../dist/integrations/slack.d.mts), [DeepSec](../dist/integrations/deepsec.d.mts), [Vercel](../dist/integrations/vercel.d.mts) |
|
|
67
|
+
|
|
68
|
+
Use the narrowest capability path that owns the API. This avoids loading server-only or optional
|
|
69
|
+
integrations accidentally and gives each declaration one stable import path.
|
|
70
|
+
|
|
71
|
+
## Domain capabilities
|
|
72
|
+
|
|
73
|
+
Software-development records and operations are available from:
|
|
74
|
+
|
|
75
|
+
- `@vercel/factory/changes` for Change lifecycle and verification
|
|
76
|
+
- `@vercel/factory/findings` for findings and remediation
|
|
77
|
+
- `@vercel/factory/code-review` for review policy and tools
|
|
78
|
+
- `@vercel/factory/pull-requests` for publication and merge-conflict operations
|
|
79
|
+
- `@vercel/factory/work-triage` for existing-work lookup
|
|
80
|
+
- `@vercel/factory/delivery-metrics` for delivery ledger metrics
|
|
81
|
+
|
|
82
|
+
Each path has a matching declaration entry under `dist/`. Provider-specific implementations remain
|
|
83
|
+
under `@vercel/factory/integrations/<provider>`.
|
|
84
|
+
|
|
85
|
+
## Reading the API
|
|
86
|
+
|
|
87
|
+
Public declarations carry concise JSDoc summaries, and public schemas expose field descriptions at
|
|
88
|
+
runtime. Search the linked `.d.mts` files for a symbol to inspect the exact installed signature.
|
|
89
|
+
Configuration inputs use `*Options`, operation results use `*Result`, and errors use `*Error`.
|
|
90
|
+
Factories use `create*`; declarative validators and registries use `define*`.
|
|
91
|
+
|
|
92
|
+
The package is pre-1.0. Replaced names and import paths can be removed in a patch release. Review
|
|
93
|
+
the [installed changelog](../CHANGELOG.md) before upgrading, and pin a version when a deployment
|
|
94
|
+
requires a stable contract.
|
|
95
|
+
|
|
96
|
+
The declarations are validated with TypeScript 5.9.3 and 7.0.2. Core workflows and the Blob adapter
|
|
97
|
+
use `nodeNext` module resolution with library checking enabled. The `/client` entry point uses
|
|
98
|
+
`bundler` resolution in a browser-only fixture that does not load Node or server-integration types.
|
|
99
|
+
The Postgres adapter is checked with its Drizzle peer and `skipLibCheck` because Drizzle's
|
|
100
|
+
declaration graph includes declarations for optional database drivers.
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# Cancel an execution safely
|
|
2
|
+
|
|
3
|
+
Cancellation records canonical Task state before asking the provider to stop. A provider stop may
|
|
4
|
+
be retried, and any final measured usage is retained and settled once.
|
|
5
|
+
|
|
6
|
+
The program covers both the provider-neutral `ExecutionAdapter` contract and the Eve session
|
|
7
|
+
boundary. In both cases the application records canonical cancellation before asking the provider
|
|
8
|
+
to stop.
|
|
9
|
+
|
|
10
|
+
## Run the recipe
|
|
11
|
+
|
|
12
|
+
Save this as `cancellation.mts` in an ESM project with `@vercel/factory`, `eve`, and `zod`
|
|
13
|
+
installed.
|
|
14
|
+
|
|
15
|
+
<!-- runnable-example:start -->
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import type { ToolContext } from "eve/tools";
|
|
19
|
+
import {
|
|
20
|
+
cancelEveSession,
|
|
21
|
+
createTaskTools,
|
|
22
|
+
factoryTaskAttemptAttribute,
|
|
23
|
+
factoryTaskAttribute,
|
|
24
|
+
startEveSession,
|
|
25
|
+
taskHeaders,
|
|
26
|
+
} from "@vercel/factory";
|
|
27
|
+
import {
|
|
28
|
+
cancelExecution,
|
|
29
|
+
dispatchTask,
|
|
30
|
+
ExecutionFenceError,
|
|
31
|
+
inspectExecution,
|
|
32
|
+
launchExecution,
|
|
33
|
+
type ExecutionAdapter,
|
|
34
|
+
type ExecutionResult,
|
|
35
|
+
} from "@vercel/factory/execution";
|
|
36
|
+
import { createInMemoryDriver, createStores } from "@vercel/factory/storage";
|
|
37
|
+
import { parseAgentRouteBinding, taskWork } from "@vercel/factory/workflows";
|
|
38
|
+
|
|
39
|
+
const stores = createStores({ driver: createInMemoryDriver() });
|
|
40
|
+
const repository = await stores.repositories.put({
|
|
41
|
+
schemaVersion: 1,
|
|
42
|
+
id: "repo_recipe03",
|
|
43
|
+
slug: "example/project",
|
|
44
|
+
defaultBranch: "main",
|
|
45
|
+
enabled: true,
|
|
46
|
+
dependsOn: [],
|
|
47
|
+
connectedAt: new Date().toISOString(),
|
|
48
|
+
});
|
|
49
|
+
const task = await stores.tasks.create({
|
|
50
|
+
repositoryIds: [repository.id],
|
|
51
|
+
kind: "example",
|
|
52
|
+
origin: { operator: "local:recipe" },
|
|
53
|
+
replyTo: { channel: "local", address: "cancellation" },
|
|
54
|
+
work: taskWork({ title: "Run cancellable work" }),
|
|
55
|
+
});
|
|
56
|
+
const running = await stores.tasks.transition(task.id, "running");
|
|
57
|
+
const execution = { provider: "local", sessionId: "cancel-1" };
|
|
58
|
+
const cancelledResult: ExecutionResult = {
|
|
59
|
+
status: "cancelled",
|
|
60
|
+
execution,
|
|
61
|
+
artifacts: [],
|
|
62
|
+
usage: { computeMs: 5, costUsd: 0.05 },
|
|
63
|
+
reason: "Stopped by the operator",
|
|
64
|
+
};
|
|
65
|
+
let providerObservedCancellation = false;
|
|
66
|
+
const adapter: ExecutionAdapter = {
|
|
67
|
+
async start() {
|
|
68
|
+
return execution;
|
|
69
|
+
},
|
|
70
|
+
async inspect() {
|
|
71
|
+
return { status: "finished", result: cancelledResult };
|
|
72
|
+
},
|
|
73
|
+
async stop() {
|
|
74
|
+
providerObservedCancellation = (await stores.tasks.get(task.id))?.state === "cancelled";
|
|
75
|
+
return cancelledResult;
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
await launchExecution({
|
|
79
|
+
stores,
|
|
80
|
+
adapter,
|
|
81
|
+
request: {
|
|
82
|
+
taskId: running.id,
|
|
83
|
+
attempt: running.attempt,
|
|
84
|
+
repository: { slug: repository.slug, baseSha: "b".repeat(40) },
|
|
85
|
+
harness: "codex",
|
|
86
|
+
prompt: "Perform cancellable work",
|
|
87
|
+
allowedConstructs: [],
|
|
88
|
+
environment: { timeoutMs: 1_000 },
|
|
89
|
+
limits: { maxCostUsd: 1 },
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const cancelled = await cancelExecution({
|
|
94
|
+
stores,
|
|
95
|
+
task: { taskId: running.id, attempt: running.attempt },
|
|
96
|
+
adapter,
|
|
97
|
+
reason: "Operator cancelled the example",
|
|
98
|
+
});
|
|
99
|
+
let lateResultRejected = false;
|
|
100
|
+
try {
|
|
101
|
+
await inspectExecution({
|
|
102
|
+
stores,
|
|
103
|
+
task: { taskId: running.id, attempt: running.attempt },
|
|
104
|
+
adapter,
|
|
105
|
+
});
|
|
106
|
+
} catch (error) {
|
|
107
|
+
lateResultRejected = error instanceof ExecutionFenceError;
|
|
108
|
+
}
|
|
109
|
+
if (
|
|
110
|
+
cancelled.state !== "cancelled" ||
|
|
111
|
+
cancelled.settledUsd !== 0.05 ||
|
|
112
|
+
!providerObservedCancellation ||
|
|
113
|
+
!lateResultRejected ||
|
|
114
|
+
cancelled.workResult !== undefined
|
|
115
|
+
) {
|
|
116
|
+
throw new Error("Expected cancellation, settlement, and late-result fencing");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const eveRoute = parseAgentRouteBinding({ id: "worker", version: 1 });
|
|
120
|
+
const eveTask = await stores.tasks.create({
|
|
121
|
+
repositoryIds: [repository.id],
|
|
122
|
+
kind: "example",
|
|
123
|
+
origin: { operator: "local:recipe" },
|
|
124
|
+
replyTo: { channel: "local", address: "cancellation" },
|
|
125
|
+
work: { ...taskWork({ title: "Cancel an Eve agent" }), route: eveRoute },
|
|
126
|
+
});
|
|
127
|
+
const runningEve = await dispatchTask(stores, {
|
|
128
|
+
taskId: eveTask.id,
|
|
129
|
+
route: eveRoute,
|
|
130
|
+
launch: (claimed) =>
|
|
131
|
+
startEveSession({
|
|
132
|
+
agentUrl: "https://factory.example/eve/agents/worker",
|
|
133
|
+
message: `Work Task ${claimed.id}`,
|
|
134
|
+
operationId: `factory-task:${claimed.id}:${claimed.attempt}`,
|
|
135
|
+
headers: taskHeaders({ taskId: claimed.id, attempt: claimed.attempt }),
|
|
136
|
+
fetch: async () => Response.json({ sessionId: "ses_cancel_eve" }),
|
|
137
|
+
}),
|
|
138
|
+
});
|
|
139
|
+
if (runningEve.execution?.provider !== "eve") throw new Error("Expected an Eve execution");
|
|
140
|
+
const eveContext = {
|
|
141
|
+
session: {
|
|
142
|
+
id: runningEve.execution.sessionId,
|
|
143
|
+
auth: {
|
|
144
|
+
initiator: {
|
|
145
|
+
attributes: {
|
|
146
|
+
[factoryTaskAttribute]: runningEve.id,
|
|
147
|
+
[factoryTaskAttemptAttribute]: String(runningEve.attempt),
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
} as unknown as ToolContext;
|
|
153
|
+
|
|
154
|
+
await stores.tasks.transition(runningEve.id, "cancelled", {
|
|
155
|
+
expectFrom: "running",
|
|
156
|
+
expectAttempt: runningEve.attempt,
|
|
157
|
+
expectExecution: runningEve.execution,
|
|
158
|
+
reason: "Operator cancelled the Eve Task",
|
|
159
|
+
});
|
|
160
|
+
let eveObservedCancellation = false;
|
|
161
|
+
let confirmedSession = "";
|
|
162
|
+
await cancelEveSession({
|
|
163
|
+
agentUrl: "https://factory.example/eve/agents/worker",
|
|
164
|
+
sessionId: runningEve.execution.sessionId,
|
|
165
|
+
headers: taskHeaders({ taskId: runningEve.id, attempt: runningEve.attempt }),
|
|
166
|
+
fetch: async (input, init) => {
|
|
167
|
+
const request = new Request(input, init);
|
|
168
|
+
eveObservedCancellation =
|
|
169
|
+
request.url.endsWith("/eve/v1/session/ses_cancel_eve/cancel") &&
|
|
170
|
+
(await stores.tasks.get(runningEve.id))?.state === "cancelled";
|
|
171
|
+
return Response.json({ status: "cancelling" });
|
|
172
|
+
},
|
|
173
|
+
stream: async function* (input) {
|
|
174
|
+
confirmedSession = input.sessionId;
|
|
175
|
+
yield { type: "session.waiting" };
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
let lateEveCompletionRejected = false;
|
|
179
|
+
try {
|
|
180
|
+
await createTaskTools(stores).finish_task.execute(
|
|
181
|
+
{ taskId: runningEve.id, summary: "Late agent output" },
|
|
182
|
+
eveContext,
|
|
183
|
+
);
|
|
184
|
+
} catch {
|
|
185
|
+
lateEveCompletionRejected = true;
|
|
186
|
+
}
|
|
187
|
+
if (
|
|
188
|
+
!eveObservedCancellation ||
|
|
189
|
+
confirmedSession !== runningEve.execution.sessionId ||
|
|
190
|
+
!lateEveCompletionRejected
|
|
191
|
+
) {
|
|
192
|
+
throw new Error("Expected task-first Eve cancellation and late-tool fencing");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
console.log(
|
|
196
|
+
JSON.stringify({
|
|
197
|
+
adapter: { state: cancelled.state, settledUsd: cancelled.settledUsd },
|
|
198
|
+
eve: { state: (await stores.tasks.get(runningEve.id))?.state, confirmedSession },
|
|
199
|
+
}),
|
|
200
|
+
);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
<!-- runnable-example:end -->
|
|
204
|
+
|
|
205
|
+
## What it proves
|
|
206
|
+
|
|
207
|
+
`cancelExecution` persists cancellation before invoking `adapter.stop`. It records the provider's
|
|
208
|
+
final usage and settles measured cost, while the attempt fence prevents a late inspection from
|
|
209
|
+
completing cancelled work. If stopping is ambiguous, retry `cancelExecution` with the same Task and
|
|
210
|
+
attempt; adapters must make `stop` idempotent.
|
|
211
|
+
|
|
212
|
+
For Eve, transition the Task with its attempt and execution fences, then pass the persisted session
|
|
213
|
+
ID to `cancelEveSession`. The client waits for Eve's stream to confirm that the session is no longer
|
|
214
|
+
running. A late tool call fails because the Task is already terminal. Provider cancellation and
|
|
215
|
+
Task persistence are separate writes, so recovery should retry the idempotent provider request.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Custom workflow with a local execution adapter
|
|
2
|
+
|
|
3
|
+
Use this recipe when a Task needs typed input and output plus an execution provider. The local
|
|
4
|
+
adapter stands in for a model or sandbox provider, so the complete dispatch, inspection, and
|
|
5
|
+
completion path runs without credentials or network access.
|
|
6
|
+
|
|
7
|
+
This is the provider-adapter path used for coding harnesses, sandboxes, and other execution systems
|
|
8
|
+
that return bounded evidence. It is not the usual Eve-agent setup. For an Eve agent, start with
|
|
9
|
+
[Run an Eve agent as a Factory Task](./eve-agent.md), then expose the workflow output through an
|
|
10
|
+
[authenticated typed result tool](./typed-eve-result.md).
|
|
11
|
+
|
|
12
|
+
## Run the recipe
|
|
13
|
+
|
|
14
|
+
Save this as `custom-workflow.mts` in an ESM project with `@vercel/factory`, `eve`, and `zod`
|
|
15
|
+
installed.
|
|
16
|
+
|
|
17
|
+
<!-- runnable-example:start -->
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import {
|
|
22
|
+
dispatchTask,
|
|
23
|
+
inspectExecution,
|
|
24
|
+
launchExecution,
|
|
25
|
+
type ExecutionAdapter,
|
|
26
|
+
type ExecutionResult,
|
|
27
|
+
} from "@vercel/factory/execution";
|
|
28
|
+
import { createInMemoryDriver, createStores } from "@vercel/factory/storage";
|
|
29
|
+
import {
|
|
30
|
+
defineWorkflow,
|
|
31
|
+
defineWorkflows,
|
|
32
|
+
parseAgentRouteBinding,
|
|
33
|
+
taskWork,
|
|
34
|
+
} from "@vercel/factory/workflows";
|
|
35
|
+
|
|
36
|
+
const greetingWorkflow = defineWorkflow({
|
|
37
|
+
id: "greeting",
|
|
38
|
+
version: 1,
|
|
39
|
+
input: z.strictObject({ name: z.string().min(1) }),
|
|
40
|
+
output: z.strictObject({ greeting: z.string().min(1) }),
|
|
41
|
+
});
|
|
42
|
+
const route = parseAgentRouteBinding({ id: "local-worker", version: 1 });
|
|
43
|
+
const stores = createStores({
|
|
44
|
+
driver: createInMemoryDriver(),
|
|
45
|
+
workflows: defineWorkflows([greetingWorkflow]),
|
|
46
|
+
});
|
|
47
|
+
const repository = await stores.repositories.put({
|
|
48
|
+
schemaVersion: 1,
|
|
49
|
+
id: "repo_recipe01",
|
|
50
|
+
slug: "example/project",
|
|
51
|
+
defaultBranch: "main",
|
|
52
|
+
enabled: true,
|
|
53
|
+
dependsOn: [],
|
|
54
|
+
connectedAt: new Date().toISOString(),
|
|
55
|
+
});
|
|
56
|
+
const task = await stores.tasks.create({
|
|
57
|
+
repositoryIds: [repository.id],
|
|
58
|
+
kind: "example",
|
|
59
|
+
origin: { operator: "local:recipe" },
|
|
60
|
+
replyTo: { channel: "local", address: "custom-workflow" },
|
|
61
|
+
work: {
|
|
62
|
+
...taskWork({
|
|
63
|
+
title: "Create a greeting",
|
|
64
|
+
input: { name: "Factory" },
|
|
65
|
+
workflow: greetingWorkflow,
|
|
66
|
+
}),
|
|
67
|
+
route,
|
|
68
|
+
completionCriteria: ["Return a greeting for the supplied name"],
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const execution = { provider: "local", sessionId: "greeting-1" };
|
|
73
|
+
const result: ExecutionResult = {
|
|
74
|
+
status: "completed",
|
|
75
|
+
execution,
|
|
76
|
+
artifacts: [{ kind: "report", ref: "memory:greeting-1" }],
|
|
77
|
+
usage: { computeMs: 1, costUsd: 0 },
|
|
78
|
+
};
|
|
79
|
+
const adapter: ExecutionAdapter = {
|
|
80
|
+
async start({ launchKey }) {
|
|
81
|
+
if (launchKey !== `factory-task:${task.id}:1`) throw new Error("Unexpected launch key");
|
|
82
|
+
return execution;
|
|
83
|
+
},
|
|
84
|
+
async inspect() {
|
|
85
|
+
return { status: "finished", result };
|
|
86
|
+
},
|
|
87
|
+
async stop() {
|
|
88
|
+
return { ...result, status: "cancelled" };
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const dispatched = await dispatchTask(stores, {
|
|
93
|
+
taskId: task.id,
|
|
94
|
+
route,
|
|
95
|
+
launch: (running) =>
|
|
96
|
+
launchExecution({
|
|
97
|
+
stores,
|
|
98
|
+
adapter,
|
|
99
|
+
request: {
|
|
100
|
+
taskId: running.id,
|
|
101
|
+
attempt: running.attempt,
|
|
102
|
+
repository: { slug: repository.slug, baseSha: "a".repeat(40) },
|
|
103
|
+
harness: "codex",
|
|
104
|
+
prompt: `Greet ${greetingWorkflow.input.parse(running.work.input).name}`,
|
|
105
|
+
allowedConstructs: [],
|
|
106
|
+
environment: { timeoutMs: 1_000 },
|
|
107
|
+
limits: { maxCostUsd: 1 },
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
110
|
+
});
|
|
111
|
+
const observed = await inspectExecution({
|
|
112
|
+
stores,
|
|
113
|
+
task: { taskId: dispatched.id, attempt: dispatched.attempt },
|
|
114
|
+
adapter,
|
|
115
|
+
});
|
|
116
|
+
if (observed.status !== "finished" || observed.result.status !== "completed") {
|
|
117
|
+
throw new Error("Expected a completed local execution");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let invalidOutputRejected = false;
|
|
121
|
+
try {
|
|
122
|
+
await stores.work.complete({
|
|
123
|
+
task: { taskId: dispatched.id, attempt: dispatched.attempt },
|
|
124
|
+
output: { greeting: 42 },
|
|
125
|
+
});
|
|
126
|
+
} catch {
|
|
127
|
+
invalidOutputRejected = true;
|
|
128
|
+
}
|
|
129
|
+
if (!invalidOutputRejected) throw new Error("Expected invalid workflow output to be rejected");
|
|
130
|
+
|
|
131
|
+
const completed = await stores.work.completeWorkflow({
|
|
132
|
+
task: { taskId: dispatched.id, attempt: dispatched.attempt },
|
|
133
|
+
workflow: greetingWorkflow.binding,
|
|
134
|
+
output: { greeting: "Hello, Factory!" },
|
|
135
|
+
});
|
|
136
|
+
const output = greetingWorkflow.output.parse(completed.workResult?.output);
|
|
137
|
+
if (completed.state !== "succeeded" || output.greeting !== "Hello, Factory!") {
|
|
138
|
+
throw new Error("Expected validated workflow completion");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
console.log(JSON.stringify({ state: completed.state, output }));
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
<!-- runnable-example:end -->
|
|
145
|
+
|
|
146
|
+
## What it proves
|
|
147
|
+
|
|
148
|
+
`dispatchTask` owns the queued-to-running admission and exact route check. `launchExecution` gives
|
|
149
|
+
the adapter a stable per-attempt key and records its provider reference. Inspection returns
|
|
150
|
+
evidence but does not complete the Task; workflow code explicitly validates and records the output.
|
|
151
|
+
|
|
152
|
+
Replace the local adapter with a provider adapter in production. Keep the workflow definition and
|
|
153
|
+
its exact version registered anywhere stored Tasks from that version may be read or completed.
|