@vercel/factory 0.0.25 → 0.0.27
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 +15 -0
- package/dist/eve/index.d.mts +10 -3
- package/dist/eve/index.mjs +33 -1
- package/dist/eve/index.mjs.map +1 -1
- package/dist/eve/install-dependencies.d.mts +28 -0
- package/dist/eve/install-dependencies.mjs +51 -0
- package/dist/eve/install-dependencies.mjs.map +1 -0
- package/dist/github-publication.mjs +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +2 -1
- package/dist/schema/factory-config.d.mts +1 -0
- package/dist/schema/factory-config.mjs +6 -0
- package/dist/schema/factory-config.mjs.map +1 -1
- package/dist/vercel-git.mjs +2 -2
- package/dist/vercel-git.mjs.map +1 -1
- package/dist/work-triage/find-existing-work.d.mts +3 -0
- package/dist/work-triage/find-existing-work.mjs +4 -1
- package/dist/work-triage/find-existing-work.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @vercel/factory
|
|
2
2
|
|
|
3
|
+
## 0.0.27
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 95b7b87: `createFactoryHooks` marks a bound Task as waiting for messages while its execution's background subagents run, and resumes it on the next turn, instead of escalating the launching turn as unresolved. `FactoryHooksStores` now includes `work`.
|
|
8
|
+
- 14371c1: add `createInstallDependenciesTool`, an Eve tool that installs a pnpm checkout's dependencies from its frozen lockfile, with lifecycle scripts off by default and a saved log.
|
|
9
|
+
- 5f8f13a: Allow managed Git commands enough time to clone and bundle large repositories.
|
|
10
|
+
- 6952430: Allow managed Git transfer sandboxes ten minutes to clone and bundle large repositories.
|
|
11
|
+
|
|
12
|
+
## 0.0.26
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 6f3dec5: Let managed-credential Factory demos skip unavailable GitHub PR discovery while reporting the gap and checking recorded work.
|
|
17
|
+
|
|
3
18
|
## 0.0.25
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
package/dist/eve/index.d.mts
CHANGED
|
@@ -9,13 +9,13 @@ import { EveTranscriptMaterializer, EveTranscriptMaterializerOptions, EveTranscr
|
|
|
9
9
|
import { ConversationBinding, FactoryConversationSessionBinding, conversationBinding, conversationBindingForSession, conversationHeaders, factoryReplyAddressAttribute, factoryReplyAddressHeader, factoryReplyChannelAttribute, factoryReplyChannelHeader, factoryRepositoryIdsAttribute, factoryRepositoryIdsHeader, factoryTaskAttemptAttribute, factoryTaskAttemptHeader, factoryTaskAttribute, factoryTaskHeader, hasFactoryTaskBinding, optionalTaskIdForSession, replyToForSession, repositoryIdsForSession, requireTaskIdForSession, taskBindingForSession, taskHeaders, taskSessionAuth, withFactoryTask, withOptionalFactoryTask } from "./task-session.mjs";
|
|
10
10
|
import { CancelEveSessionOptions, EveSessionEvent, EveSessionStream, EveSessionStreamInput, ForwardEveSessionRequestOptions, ReadCompletedEveTurnOptions, StartEveSessionOptions, cancelEveSession, forwardEveSessionRequest, readCompletedEveTurn, startEveSession } from "./session-client.mjs";
|
|
11
11
|
import { DelegateAgentLaunchInput, DelegateAgentTool, DelegateAgentToolInput, DelegateAgentToolOptions, DelegateAgentToolStores, DelegateWorkflowInput, DelegateWorkflowPolicy, DelegateWorkflowResult, InvokeAgentTool, InvokeAgentToolInput, InvokeAgentToolOptions, InvokeAgentToolResult, InvokeAgentToolStores, createDelegateAgentTool, createInvokeAgentTool } from "./invoke.mjs";
|
|
12
|
-
import { HookDefinition } from "eve/hooks";
|
|
13
12
|
import { ToolContext, ToolDefinition } from "eve/tools";
|
|
13
|
+
import { HookDefinition } from "eve/hooks";
|
|
14
14
|
//#region src/eve/index.d.ts
|
|
15
15
|
/** Receipt, Signal, and Task capabilities used by the provider-neutral Eve tool bundle. */
|
|
16
16
|
type TaskToolsStores = Pick<FactoryStores, "receipts" | "signals" | "tasks">;
|
|
17
17
|
/** Task capability used to project Eve lifecycle events onto durable Factory state. */
|
|
18
|
-
type FactoryHooksStores = Pick<FactoryStores, "tasks">;
|
|
18
|
+
type FactoryHooksStores = Pick<FactoryStores, "tasks" | "work">;
|
|
19
19
|
/** Configures the provider-neutral Task, signal, question, and receipt Eve tools. */
|
|
20
20
|
interface TaskToolsOptions {
|
|
21
21
|
/** Present a persisted human question through the application's Foreman communication boundary. */
|
|
@@ -179,7 +179,7 @@ interface FactoryHooksOptions {
|
|
|
179
179
|
declare function createTranscriptSnapshotHook(materialize: EveTranscriptMaterializer): TranscriptSnapshotHook;
|
|
180
180
|
/** Observe-only Eve hook that materializes transcript snapshots at response boundaries. */
|
|
181
181
|
type TranscriptSnapshotHook = HookDefinition<"session.waiting" | "session.completed" | "session.failed">;
|
|
182
|
-
type FactoryHookEvent = "step.completed" | "turn.failed" | "session.failed" | "session.waiting" | "session.completed" | "turn.cancelled" | "turn.completed";
|
|
182
|
+
type FactoryHookEvent = "step.completed" | "subagent.completed" | "turn.started" | "turn.failed" | "session.failed" | "session.waiting" | "session.completed" | "turn.cancelled" | "turn.completed";
|
|
183
183
|
/** The Eve lifecycle hook projected onto durable Factory Task state. */
|
|
184
184
|
interface FactoryHooks {
|
|
185
185
|
/** Hook definition to export from the target Eve agent's `hooks/` directory. */
|
|
@@ -196,6 +196,13 @@ interface FactoryHooks {
|
|
|
196
196
|
* fails it when its configured attempts are exhausted. turn.cancelled marks
|
|
197
197
|
* the current Task cancelled. Late events from prior attempts are ignored.
|
|
198
198
|
*
|
|
199
|
+
* subagent.completed/turn.started: a bound execution that starts background subagent work marks
|
|
200
|
+
* its Task as waiting for messages, so the turn that launched the work can end without escalation
|
|
201
|
+
* and the stall window does not replace the waiting attempt. The next turn of that execution, which
|
|
202
|
+
* Eve starts to deliver the settled results, resumes the Task and the turn contract applies again.
|
|
203
|
+
* Events from any other session bound to the same Task, such as a subagent's own session, are
|
|
204
|
+
* ignored, and a wait that a planner or workflow started is never resumed here.
|
|
205
|
+
*
|
|
199
206
|
* turn.completed: recovers an opted-in saved workflow result before generic escalation, and
|
|
200
207
|
* settles the bound task's recorded usage if the task is
|
|
201
208
|
* already terminal, and enforces the turn contract otherwise: a still-running
|
package/dist/eve/index.mjs
CHANGED
|
@@ -9,8 +9,8 @@ import { createEveTranscriptMaterializer } from "./transcript.mjs";
|
|
|
9
9
|
import { cancelEveSession, forwardEveSessionRequest, readCompletedEveTurn, startEveSession } from "./session-client.mjs";
|
|
10
10
|
import { createDelegateAgentTool, createInvokeAgentTool } from "./invoke.mjs";
|
|
11
11
|
import { z } from "zod";
|
|
12
|
-
import { defineHook } from "eve/hooks";
|
|
13
12
|
import { defineTool } from "eve/tools";
|
|
13
|
+
import { defineHook } from "eve/hooks";
|
|
14
14
|
//#region src/eve/index.ts
|
|
15
15
|
const decidedOutcomeSchema = z.enum([
|
|
16
16
|
"accepted",
|
|
@@ -246,6 +246,8 @@ const terminalTaskStates = /* @__PURE__ */ new Set([
|
|
|
246
246
|
* is a declared closingOptions entry: answering with it resolves the task
|
|
247
247
|
* instead of requeuing an identical retry.
|
|
248
248
|
*/
|
|
249
|
+
/** Marks a wait this hook owns, so it never resumes a planner's or workflow's own wait. */
|
|
250
|
+
const SUBAGENT_WAIT_PHASE = "subagents";
|
|
249
251
|
const UNRESOLVED_TURN_QUESTION = {
|
|
250
252
|
text: "The specialist ended its turn without recording a result for this task. Reply to retry it, or close it if the work no longer applies (for example, a newer attempt already superseded it).",
|
|
251
253
|
options: ["Retry the task", "Close the task"],
|
|
@@ -262,6 +264,13 @@ const UNRESOLVED_TURN_QUESTION = {
|
|
|
262
264
|
* fails it when its configured attempts are exhausted. turn.cancelled marks
|
|
263
265
|
* the current Task cancelled. Late events from prior attempts are ignored.
|
|
264
266
|
*
|
|
267
|
+
* subagent.completed/turn.started: a bound execution that starts background subagent work marks
|
|
268
|
+
* its Task as waiting for messages, so the turn that launched the work can end without escalation
|
|
269
|
+
* and the stall window does not replace the waiting attempt. The next turn of that execution, which
|
|
270
|
+
* Eve starts to deliver the settled results, resumes the Task and the turn contract applies again.
|
|
271
|
+
* Events from any other session bound to the same Task, such as a subagent's own session, are
|
|
272
|
+
* ignored, and a wait that a planner or workflow started is never resumed here.
|
|
273
|
+
*
|
|
265
274
|
* turn.completed: recovers an opted-in saved workflow result before generic escalation, and
|
|
266
275
|
* settles the bound task's recorded usage if the task is
|
|
267
276
|
* already terminal, and enforces the turn contract otherwise: a still-running
|
|
@@ -364,6 +373,15 @@ function createFactoryHooks(stores, options) {
|
|
|
364
373
|
reason: "measured usage settled at turn end"
|
|
365
374
|
});
|
|
366
375
|
}
|
|
376
|
+
async function ownedRunningTask(ctx) {
|
|
377
|
+
const binding = boundExecution(ctx);
|
|
378
|
+
if (binding === void 0) return void 0;
|
|
379
|
+
const task = await stores.tasks.get(binding.taskId);
|
|
380
|
+
return task?.state === "running" && task.attempt === binding.attempt && task.execution?.provider === "eve" && task.execution.sessionId === ctx.session.id ? {
|
|
381
|
+
task,
|
|
382
|
+
binding
|
|
383
|
+
} : void 0;
|
|
384
|
+
}
|
|
367
385
|
async function escalateUnresolvedTurn(task, completedSessionId) {
|
|
368
386
|
await createAndPresentQuestion(stores, task, UNRESOLVED_TURN_QUESTION, options.presentQuestion, completedSessionId).catch(() => void 0);
|
|
369
387
|
}
|
|
@@ -382,6 +400,20 @@ function createFactoryHooks(stores, options) {
|
|
|
382
400
|
...usage
|
|
383
401
|
} }).catch(() => void 0);
|
|
384
402
|
},
|
|
403
|
+
async "subagent.completed"(event, ctx) {
|
|
404
|
+
if (event.data.backgroundTask === void 0) return;
|
|
405
|
+
const owned = await ownedRunningTask(ctx).catch(() => void 0);
|
|
406
|
+
if (owned === void 0 || owned.task.waitingForMessages) return;
|
|
407
|
+
await stores.work.waitForMessages({
|
|
408
|
+
task: owned.binding,
|
|
409
|
+
phase: SUBAGENT_WAIT_PHASE
|
|
410
|
+
}).catch(() => void 0);
|
|
411
|
+
},
|
|
412
|
+
async "turn.started"(_event, ctx) {
|
|
413
|
+
const owned = await ownedRunningTask(ctx).catch(() => void 0);
|
|
414
|
+
if (!owned?.task.waitingForMessages || owned.task.workflowPhase !== SUBAGENT_WAIT_PHASE) return;
|
|
415
|
+
await stores.work.resume({ task: owned.binding }).catch(() => void 0);
|
|
416
|
+
},
|
|
385
417
|
async "turn.failed"(event, ctx) {
|
|
386
418
|
const binding = boundExecution(ctx);
|
|
387
419
|
if (binding !== void 0) {
|
package/dist/eve/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/eve/index.ts"],"sourcesContent":["import { collectFactoryRecords } from \"../store/iterate\";\nimport type { SessionContext } from \"eve/context\";\nimport { defineHook, type HookDefinition } from \"eve/hooks\";\nimport { defineTool, type ToolContext, type ToolDefinition } from \"eve/tools\";\nimport { z } from \"zod\";\nimport {\n changeIdSchema,\n repositoryIdsSchema,\n signalIdSchema,\n taskIdSchema,\n type ChangeId,\n type RepositoryIds,\n type SignalId,\n type TaskId,\n} from \"../schema/id\";\nimport {\n receiptKindSchema,\n receiptStateSchema,\n type Receipt,\n type ReceiptKind,\n type ReceiptState,\n} from \"../schema/receipt\";\nimport type { MaybePromise } from \"../schema/common\";\nimport type { TaskAttemptRef } from \"../schema/execution\";\nimport { taskQuestionSchema, type Task, type TaskQuestion } from \"../schema/task\";\nimport type { Signal, SignalOutcome } from \"../schema/signal\";\nimport { taskWork, type JsonValue } from \"../schema/work\";\nimport type { AnswerQuestionResult, FactoryStores } from \"../store/engine\";\nimport type { EveTranscriptMaterializer, TranscriptMaterializationInput } from \"./transcript\";\nimport { requireTaskExecution } from \"./task-execution\";\n\nexport {\n conversationBinding,\n conversationBindingForSession,\n conversationHeaders,\n factoryReplyAddressAttribute,\n factoryReplyAddressHeader,\n factoryReplyChannelAttribute,\n factoryReplyChannelHeader,\n factoryRepositoryIdsAttribute,\n factoryRepositoryIdsHeader,\n factoryTaskAttemptAttribute,\n factoryTaskAttemptHeader,\n factoryTaskAttribute,\n factoryTaskHeader,\n hasFactoryTaskBinding,\n optionalTaskIdForSession,\n requireTaskIdForSession,\n replyToForSession,\n repositoryIdsForSession,\n taskBindingForSession,\n taskHeaders,\n taskSessionAuth,\n withFactoryTask,\n withOptionalFactoryTask,\n type ConversationBinding,\n type FactoryConversationSessionBinding,\n} from \"./task-session\";\nexport {\n createEveTranscriptMaterializer,\n type EveTranscriptMaterializerOptions,\n type EveTranscriptMaterializer,\n type EveTranscriptRequestScope,\n type TranscriptMaterializationInput,\n} from \"./transcript\";\nexport {\n cancelEveSession,\n forwardEveSessionRequest,\n readCompletedEveTurn,\n startEveSession,\n type CancelEveSessionOptions,\n type EveSessionEvent,\n type EveSessionStream,\n type EveSessionStreamInput,\n type ForwardEveSessionRequestOptions,\n type ReadCompletedEveTurnOptions,\n type StartEveSessionOptions,\n} from \"./session-client\";\n\nconst decidedOutcomeSchema = z.enum([\n \"accepted\",\n \"duplicate\",\n \"unactionable\",\n \"low_confidence\",\n \"ignored\",\n]);\n\n/** Receipt, Signal, and Task capabilities used by the provider-neutral Eve tool bundle. */\nexport type TaskToolsStores = Pick<FactoryStores, \"receipts\" | \"signals\" | \"tasks\">;\n\n/** Task capability used to project Eve lifecycle events onto durable Factory state. */\nexport type FactoryHooksStores = Pick<FactoryStores, \"tasks\">;\n\n/** Configures the provider-neutral Task, signal, question, and receipt Eve tools. */\nexport interface TaskToolsOptions {\n /** Present a persisted human question through the application's Foreman communication boundary. */\n presentQuestion?: (task: Task) => Promise<void>;\n /** Return a refusal reason when this task must use a more specific outcome tool. */\n finishTaskPolicy?: (task: Task) => MaybePromise<string | null>;\n}\n\n/** Delivers a persisted human-question Task through an application-owned channel. */\nexport type PresentQuestion = NonNullable<TaskToolsOptions[\"presentQuestion\"]>;\n\n/** Identifies the Task to read through the provider-neutral task tool. */\nexport interface GetTaskToolInput {\n taskId: TaskId;\n}\n\n/** Explicit not-found result returned when a requested Task does not exist. */\nexport interface TaskNotFoundResult {\n readonly found: false;\n readonly taskId: TaskId;\n}\n\n/** Existing Task or explicit not-found result returned by the task lookup tool. */\nexport type GetTaskToolResult = Task | TaskNotFoundResult;\n\n/** Provider-neutral Eve tool for reading one Factory Task. */\nexport interface GetTaskTool extends ToolDefinition<GetTaskToolInput, GetTaskToolResult> {\n execute(input: GetTaskToolInput, ctx: ToolContext): Promise<GetTaskToolResult>;\n}\n\n/** One final triage decision submitted for a pending Signal. */\nexport interface DecideSignalToolInput {\n signalId: SignalId;\n outcome: Exclude<SignalOutcome, \"pending\">;\n reason: string;\n detail?: string;\n}\n\n/** Provider-neutral Eve tool for recording a Signal decision. */\nexport interface DecideSignalTool extends ToolDefinition<DecideSignalToolInput, Signal> {\n execute(input: DecideSignalToolInput, ctx: ToolContext): Promise<Signal>;\n}\n\n/** Running Task and summary supplied to the generic completion tool. */\nexport interface FinishTaskToolInput {\n taskId: TaskId;\n summary: string;\n}\n\n/** Successful generic Task completion. */\nexport interface FinishTaskSuccess {\n readonly finished: true;\n readonly taskState: \"succeeded\";\n}\n\n/** Policy or lifecycle refusal returned by the generic Task completion tool. */\nexport interface FinishTaskRefusal {\n readonly finished: false;\n readonly reason: string;\n}\n\n/** Completion or bounded refusal returned by the generic Task completion tool. */\nexport type FinishTaskToolResult = FinishTaskSuccess | FinishTaskRefusal;\n\n/** Provider-neutral Eve tool for completing a running Task. */\nexport interface FinishTaskTool extends ToolDefinition<FinishTaskToolInput, FinishTaskToolResult> {\n execute(input: FinishTaskToolInput, ctx: ToolContext): Promise<FinishTaskToolResult>;\n}\n\n/** Unanswered question shape accepted when a Task requests human input. */\nexport type HumanInputQuestion = Omit<TaskQuestion, \"answer\" | \"answeredBy\">;\n\n/** Currently running parent Task and its single human question for this execution. */\nexport interface RequestHumanInputToolInput {\n parentTaskId: TaskId;\n question: HumanInputQuestion;\n}\n\n/** Provider-neutral Eve tool for pausing one running execution on one persisted human question. */\nexport interface RequestHumanInputTool extends ToolDefinition<RequestHumanInputToolInput, Task> {\n execute(input: RequestHumanInputToolInput, ctx: ToolContext): Promise<Task>;\n}\n\n/** Question Task and authenticated answer supplied to the answer tool. */\nexport interface AnswerQuestionToolInput {\n questionTaskId: TaskId;\n answer: string;\n}\n\n/** Provider-neutral Eve tool for answering a persisted human question. */\nexport interface AnswerQuestionTool extends ToolDefinition<\n AnswerQuestionToolInput,\n AnswerQuestionResult\n> {\n execute(input: AnswerQuestionToolInput, ctx: ToolContext): Promise<AnswerQuestionResult>;\n}\n\n/** Immutable audit event accepted by the generic receipt tool. */\nexport interface AppendReceiptToolInput {\n repositoryIds: RepositoryIds;\n taskId?: TaskId;\n changeId?: ChangeId;\n kind: ReceiptKind;\n state: ReceiptState;\n reason?: string;\n dedupeKey?: string;\n}\n\n/** Provider-neutral Eve tool for appending one immutable audit Receipt. */\nexport interface AppendReceiptTool extends ToolDefinition<AppendReceiptToolInput, Receipt> {\n execute(input: AppendReceiptToolInput, ctx: ToolContext): Promise<Receipt>;\n}\n\n/** The provider-neutral Task, Signal, question, and Receipt tools exposed to an Eve agent. */\nexport interface TaskTools {\n /** Read one Task by ID; applications decide which agents may expose this cross-Task lookup. */\n readonly get_task: GetTaskTool;\n /** Record the one final decision for a pending Signal. */\n readonly decide_signal: DecideSignalTool;\n /** Complete generic `task@1` work after authenticating the current Eve execution. */\n readonly finish_task: FinishTaskTool;\n /** Persist and present one human question for the authenticated running Task. */\n readonly request_human_input: RequestHumanInputTool;\n /** Answer a question only from its authenticated reply conversation. */\n readonly answer_question: AnswerQuestionTool;\n /** Append immutable application audit evidence with optional deduplication. */\n readonly append_receipt: AppendReceiptTool;\n}\n\nasync function createAndPresentQuestion(\n stores: Pick<FactoryStores, \"tasks\">,\n parent: Task,\n question: z.infer<typeof taskQuestionSchema>,\n presentQuestion: PresentQuestion,\n completedSessionId?: string,\n): Promise<Task> {\n const needsHumanAction = question.text.trim();\n if (needsHumanAction === \"\") throw new Error(\"A human question must contain non-blank text.\");\n const task = await stores.tasks.create({\n repositoryIds: parent.repositoryIds,\n kind: \"question\",\n work: taskWork({\n title: needsHumanAction.slice(0, 256),\n input: {\n question,\n ...(completedSessionId ? { systemReason: \"unresolved_turn\", completedSessionId } : {}),\n },\n }),\n parentTaskId: parent.id,\n parentTaskAttempt: parent.attempt,\n origin: {},\n replyTo: parent.replyTo,\n question,\n });\n try {\n const waiting = await stores.tasks.transition(task.id, \"needs_human\", { needsHumanAction });\n if (parent.state === \"running\") {\n await stores.tasks.transition(parent.id, \"needs_human\", {\n expectFrom: \"running\",\n expectAttempt: parent.attempt,\n reason: `waiting for answer to question ${waiting.id}`,\n questionTaskId: waiting.id,\n needsHumanAction,\n });\n }\n await presentQuestion(waiting);\n return waiting;\n } catch (error) {\n const currentParent = await stores.tasks.get(parent.id);\n if (\n currentParent?.state === \"needs_human\" &&\n currentParent.attempt === parent.attempt &&\n currentParent.questionTaskId === task.id\n ) {\n try {\n await stores.tasks.transition(parent.id, \"queued\", {\n expectFrom: \"needs_human\",\n expectAttempt: currentParent.attempt,\n expectQuestionTaskId: task.id,\n reason: `delivery of question ${task.id} failed`,\n });\n } catch (cleanupError) {\n const latest = await stores.tasks.get(parent.id);\n if (\n latest?.state === \"needs_human\" &&\n latest.attempt === currentParent.attempt &&\n latest.questionTaskId === task.id\n )\n throw cleanupError;\n }\n }\n const currentQuestion = await stores.tasks.get(task.id);\n if (currentQuestion?.state === \"needs_human\" || currentQuestion?.state === \"queued\") {\n await stores.tasks.transition(task.id, \"cancelled\", {\n expectFrom: currentQuestion.state,\n expectAttempt: currentQuestion.attempt,\n reason: \"question delivery failed\",\n });\n }\n throw error;\n }\n}\n\n/**\n * Wraps provider-neutral Factory store operations as Eve tools.\n *\n * @remarks\n * Expose only the returned tools each agent role needs. Every write goes through the validating\n * engine, but tool visibility remains application authorization policy. `finish_task` and\n * `request_human_input` recheck the current authenticated Task attempt and Eve session before\n * mutating state. `get_task`, signal decisions, and generic receipts are broader capabilities and\n * should be mounted only for agents allowed to use them.\n *\n * The generic `finish_task` records `{ summary }` for `task@1`. Schema-specific workflows should\n * define a narrow tool that calls `requireTaskExecution` and `stores.work.completeWorkflow`, as in\n * the shipped `docs/recipes/typed-eve-result.md` recipe.\n *\n * @param stores - Receipt, Signal, and Task capabilities available to the tool bundle.\n * @param options - Optional question presentation and generic-completion policy.\n * @returns A new named Eve tool bundle ready for selective mounting under `agent/tools/`.\n *\n * @example\n * ```ts\n * import { createTaskTools, type TaskToolsStores } from \"@vercel/factory\";\n *\n * declare const stores: TaskToolsStores;\n * const tools = createTaskTools(stores);\n * export default tools.get_task;\n * ```\n */\nexport function createTaskTools(\n stores: TaskToolsStores,\n options: TaskToolsOptions = {},\n): TaskTools {\n return {\n get_task: defineTool({\n description:\n \"Read one factory task by id: its kind, state, repositories, origin, and results.\",\n inputSchema: z.object({ taskId: taskIdSchema }),\n async execute({ taskId }) {\n const task = await stores.tasks.get(taskId);\n return task ?? { found: false as const, taskId };\n },\n }),\n\n decide_signal: defineTool({\n description:\n \"Record the triage decision for a pending signal. A signal is decided exactly once.\",\n inputSchema: z.object({\n signalId: signalIdSchema,\n outcome: decidedOutcomeSchema,\n reason: z.string().min(1).describe(\"Why this outcome was chosen.\"),\n detail: z\n .string()\n .min(1)\n .optional()\n .describe(\"Severity if accepted, or the duplicate-of signal id.\"),\n }),\n execute({ signalId, outcome, reason, detail }) {\n return stores.signals.decide(signalId, { outcome: outcome, ...{ reason, detail } });\n },\n }),\n\n finish_task: defineTool({\n description:\n \"Mark your running task finished with a summary. The configured workflow policy may require a more specific outcome tool.\",\n inputSchema: z.object({\n taskId: taskIdSchema,\n summary: z.string().min(1).max(4000).describe(\"What you did and found, for the ledger.\"),\n }),\n async execute({ taskId, summary }, ctx) {\n const task = await requireTaskExecution(stores, ctx, taskId);\n const refusal = await options.finishTaskPolicy?.(task);\n if (refusal !== undefined && refusal !== null) {\n return {\n finished: false as const,\n reason: refusal,\n };\n }\n if (task.state !== \"running\") {\n return { finished: false as const, reason: `task is ${task.state}, not running` };\n }\n await stores.tasks.transition(taskId, \"succeeded\", {\n expectFrom: \"running\",\n expectAttempt: task.attempt,\n expectExecution: task.execution,\n reason: summary,\n output: { summary },\n });\n return { finished: true as const, taskState: \"succeeded\" as const };\n },\n }),\n\n request_human_input: defineTool({\n description:\n 'Request one human input or action and pause your currently running task until the human replies. A paused task cannot request another question; wait for the answer and replacement execution. The parent task determines the repositories and reply destination. Name any suggested reply in question.closingOptions when that exact answer means no further attempt can help (for example \"nothing to do, close it\"); Factory resolves the task instead of requeuing an identical retry when the human picks one.',\n inputSchema: z.object({\n parentTaskId: taskIdSchema.describe(\"Your running task, which the answer will unblock.\"),\n question: taskQuestionSchema.omit({ answer: true, answeredBy: true }),\n }),\n async execute({ parentTaskId, question }, ctx) {\n if (options.presentQuestion === undefined) {\n throw new Error(\"request_human_input requires a configured question presenter.\");\n }\n const parent = await requireTaskExecution(stores, ctx, parentTaskId);\n return createAndPresentQuestion(stores, parent, question, options.presentQuestion);\n },\n }),\n\n answer_question: defineTool({\n description:\n \"Record the authenticated user's answer to a waiting question and requeue the parent specialist task, unless the answer matches one of the question's closingOptions, which resolves the parent task instead. A blocked parent task id is also accepted and resolves its persisted QuestionTask id.\",\n inputSchema: z.object({\n questionTaskId: taskIdSchema,\n answer: z.string().min(1).max(4000),\n }),\n async execute({ questionTaskId, answer }, ctx) {\n const requestedTask = await stores.tasks.get(questionTaskId);\n const resolvedQuestionTaskId =\n requestedTask?.kind === \"question\" ? requestedTask.id : requestedTask?.questionTaskId;\n const questionTask =\n resolvedQuestionTaskId === undefined\n ? null\n : await stores.tasks.get(resolvedQuestionTaskId);\n if (\n questionTask === null ||\n questionTask.kind !== \"question\" ||\n questionTask.question === undefined\n ) {\n throw new Error(`Question task ${questionTaskId} does not exist.`);\n }\n const { initiator, current } = ctx.session.auth;\n if (\n !initiator ||\n !current ||\n [initiator, current].some(\n ({ attributes }) =>\n attributes.factoryReplyChannel !== questionTask.replyTo.channel ||\n attributes.factoryReplyAddress !== questionTask.replyTo.address,\n )\n ) {\n throw new Error(\"Question reply does not match the authenticated conversation.\");\n }\n const answeredBy = current.principalId;\n return stores.tasks.answerQuestion({\n questionTaskId: questionTask.id,\n answer,\n answeredBy,\n });\n },\n }),\n\n append_receipt: defineTool({\n description:\n \"Append one immutable audit receipt for an event worth remembering. Receipts can never be edited or deleted.\",\n inputSchema: z.object({\n repositoryIds: repositoryIdsSchema,\n taskId: taskIdSchema.optional(),\n changeId: changeIdSchema.optional(),\n kind: receiptKindSchema.describe(\"What the receipt is about, e.g. investigation.\"),\n state: receiptStateSchema.describe(\"What happened, e.g. flag_ramped.\"),\n reason: z.string().min(1).max(2000).optional(),\n dedupeKey: z\n .string()\n .min(1)\n .optional()\n .describe(\"Same key twice records one receipt instead of two.\"),\n }),\n execute(input) {\n return stores.receipts.append(input);\n },\n }),\n };\n}\n\n/** Completed conversation output, or `false` when the Task is not a conversation. */\nexport type ConversationCompletionResult = { readonly output: JsonValue } | false;\n\n/** Configures attempt recovery, conversation completion, and transcript projection for Eve hooks. */\nexport interface FactoryHooksOptions {\n /** Session auth attribute naming the task a dispatched session works on. Default `factoryTaskId`. */\n taskIdAttribute?: string;\n /** Session auth attribute naming the execution attempt. Default `factoryTaskAttempt`. */\n taskAttemptAttribute?: string;\n /** Attempts allowed before an explicit provider failure becomes terminal. Default: 3. */\n maxAttempts?: number;\n /**\n * Recover a saved workflow result for the exact bound execution before failure retry or\n * unresolved-turn escalation. Return true when handled; a throw defers generic lifecycle changes.\n */\n recoverResult?: (task: Task) => Promise<boolean>;\n /** Present the recovery question created when a task turn ends without an outcome. */\n presentQuestion: PresentQuestion;\n /** Classify discussion Tasks on failure or cancellation without collecting a successful response. */\n isConversation?: (task: Task) => MaybePromise<boolean>;\n /** Return the actual completed response for an opted-in discussion, or false for other work. */\n completeConversation?: (task: Task) => Promise<ConversationCompletionResult>;\n /** Persist the browser-safe transcript projection after Eve durably records a response boundary. */\n materializeTranscript?: EveTranscriptMaterializer;\n}\n\nfunction materializationInput(\n ctx: import(\"eve/hooks\").HookContext,\n): TranscriptMaterializationInput {\n return {\n agent: ctx.agent.name,\n execution: { provider: \"eve\", sessionId: ctx.session.id },\n attributes: ctx.session.auth.initiator?.attributes ?? {},\n };\n}\n\n/** Observe-only hook for agents that do not use the Factory task-lifecycle hook. */\nexport function createTranscriptSnapshotHook(\n materialize: EveTranscriptMaterializer,\n): TranscriptSnapshotHook {\n const snapshot = (_event: unknown, ctx: import(\"eve/hooks\").HookContext) =>\n materialize(materializationInput(ctx)).catch(() => undefined);\n return defineHook({\n events: {\n \"session.waiting\": snapshot,\n \"session.completed\": snapshot,\n \"session.failed\": snapshot,\n },\n });\n}\n\n/** Observe-only Eve hook that materializes transcript snapshots at response boundaries. */\nexport type TranscriptSnapshotHook = HookDefinition<\n \"session.waiting\" | \"session.completed\" | \"session.failed\"\n>;\n\nconst terminalTaskStates: ReadonlySet<Task[\"state\"]> = new Set([\n \"succeeded\",\n \"failed\",\n \"cancelled\",\n]);\n\ntype FactoryHookEvent =\n | \"step.completed\"\n | \"turn.failed\"\n | \"session.failed\"\n | \"session.waiting\"\n | \"session.completed\"\n | \"turn.cancelled\"\n | \"turn.completed\";\n\n/** The Eve lifecycle hook projected onto durable Factory Task state. */\nexport interface FactoryHooks {\n /** Hook definition to export from the target Eve agent's `hooks/` directory. */\n readonly factory: HookDefinition<FactoryHookEvent>;\n}\n\n/**\n * The turn contract's escalation question: a specialist turn ended cleanly\n * (eve durably recorded `turn.completed`) but the bound task is still\n * `running`, so no transition was recorded. That is never a \"worker died\n * mid-turn\" situation -- eve confirms the turn finished -- so retrying the\n * identical brief cannot succeed any differently. Escalating straight to\n * `needs_human` instead of waiting for the stall window (up to 45 minutes,\n * times `maxAttempts`) surfaces the dead end immediately. \"Close the task\"\n * is a declared closingOptions entry: answering with it resolves the task\n * instead of requeuing an identical retry.\n */\nconst UNRESOLVED_TURN_QUESTION: z.infer<typeof taskQuestionSchema> = {\n text: \"The specialist ended its turn without recording a result for this task. Reply to retry it, or close it if the work no longer applies (for example, a newer attempt already superseded it).\",\n options: [\"Retry the task\", \"Close the task\"],\n closingOptions: [\"Close the task\"],\n};\n\n/**\n * Creates observe-only Eve hooks that project session outcomes onto durable Factory state.\n *\n * @remarks\n * step.completed: records the step's tokens and AI Gateway cost on the bound\n * task. Delivery is at least once; recordUsage dedupes by (sessionId, turnId, stepIndex).\n *\n * turn.failed/session.failed: first recovers an opted-in saved workflow result, then re-queues the current attempt, or\n * fails it when its configured attempts are exhausted. turn.cancelled marks\n * the current Task cancelled. Late events from prior attempts are ignored.\n *\n * turn.completed: recovers an opted-in saved workflow result before generic escalation, and\n * settles the bound task's recorded usage if the task is\n * already terminal, and enforces the turn contract otherwise: a still-running\n * bound task (or a still-running task at this session's reply address, for\n * conversational continuations that never got their own bound session)\n * escalates immediately to a surfaced human question instead of waiting out\n * the stall window, because the agent finished without recording an outcome.\n * When the bound session matches the persisted execution, that question's work input includes\n * `systemReason: \"unresolved_turn\"` and `completedSessionId`. Application recovery can distinguish\n * this completed execution from an agent's genuine request for human input.\n *\n * Every store call is swallowed: a hook throw fails the turn.\n * Transcript projection writes at session response boundaries are likewise\n * best-effort; the API can rebuild a missing projection from durable events.\n *\n * Export the returned `factory` hook from the target agent's `hooks/` directory. The hook reads\n * the Task ID and attempt written by `withFactoryTask`; late events from replaced attempts cannot\n * advance current work. Provider failure recovery requeues below `maxAttempts` and fails the Task\n * at the limit. A clean turn that records no outcome creates a human question instead of silently\n * retrying the same brief.\n *\n * @param stores - Task store used for usage, recovery, cancellation, and completion projection.\n * @param options - Attempt limits plus application-owned question and conversation callbacks.\n * @returns An Eve hook definition under the `factory` property.\n * @see The shipped `docs/recipes/retry-recovery.md` recipe for bounded replacement attempts.\n *\n * @example\n * ```ts\n * import { createFactoryHooks, type FactoryHooksStores } from \"@vercel/factory\";\n *\n * declare const stores: FactoryHooksStores;\n * const hooks = createFactoryHooks(stores, {\n * presentQuestion: async (questionTask) => {\n * // Deliver the persisted question through the application's operator channel.\n * },\n * });\n * export default hooks.factory;\n * ```\n */\nexport function createFactoryHooks(\n stores: FactoryHooksStores,\n options: FactoryHooksOptions,\n): FactoryHooks {\n const taskIdAttribute = options.taskIdAttribute ?? \"factoryTaskId\";\n const taskAttemptAttribute = options.taskAttemptAttribute ?? \"factoryTaskAttempt\";\n const maxAttempts = z\n .number()\n .int()\n .min(1)\n .parse(options.maxAttempts ?? 3);\n\n function boundTaskId(ctx: Pick<SessionContext, \"session\">): TaskId | undefined {\n const parsed = taskIdSchema.safeParse(ctx.session.auth.initiator?.attributes[taskIdAttribute]);\n return parsed.success ? parsed.data : undefined;\n }\n\n function boundExecution(ctx: Pick<SessionContext, \"session\">): TaskAttemptRef | undefined {\n const attributes = ctx.session.auth.initiator?.attributes;\n const taskId = boundTaskId(ctx);\n const attempt = z.coerce.number().int().min(1).safeParse(attributes?.[taskAttemptAttribute]);\n return taskId !== undefined && attempt.success ? { taskId, attempt: attempt.data } : undefined;\n }\n\n async function recoverBoundResult(\n task: Task,\n ctx: Pick<SessionContext, \"session\">,\n ): Promise<boolean> {\n const binding = boundExecution(ctx);\n if (\n binding?.taskId !== task.id ||\n binding.attempt !== task.attempt ||\n task.execution?.provider !== \"eve\" ||\n task.execution.sessionId !== ctx.session.id\n )\n return false;\n return (await options.recoverResult?.(task)) ?? false;\n }\n\n async function recoverFailedAttempt(\n binding: TaskAttemptRef,\n failure: string,\n ctx: Pick<SessionContext, \"session\">,\n ): Promise<void> {\n const task = await stores.tasks.get(binding.taskId);\n if (\n !task ||\n task.attempt !== binding.attempt ||\n (options.recoverResult &&\n task.execution &&\n (task.execution.provider !== \"eve\" || task.execution.sessionId !== ctx.session.id))\n ) {\n return;\n }\n if (await recoverBoundResult(task, ctx)) {\n await settleIfTerminal(task.id);\n return;\n }\n if (task.state !== \"running\") return;\n const exhausted = task.attempt >= maxAttempts;\n await stores.tasks.transition(task.id, exhausted ? \"failed\" : \"queued\", {\n expectFrom: \"running\",\n expectAttempt: binding.attempt,\n phase: \"retry\",\n reason: exhausted\n ? `${failure} on attempt ${task.attempt} of ${maxAttempts}; attempts exhausted`\n : `${failure} on attempt ${task.attempt} of ${maxAttempts}; replacement queued`,\n });\n }\n\n async function cancelAttempt(binding: TaskAttemptRef, reason: string): Promise<void> {\n const task = await stores.tasks.get(binding.taskId);\n if (task?.state !== \"running\" || task.attempt !== binding.attempt) {\n return;\n }\n await stores.tasks.transition(task.id, \"cancelled\", {\n expectFrom: \"running\",\n expectAttempt: binding.attempt,\n phase: \"execution\",\n reason,\n });\n }\n\n async function isConversation(task: Task): Promise<boolean> {\n try {\n return (await options.isConversation?.(task)) ?? false;\n } catch {\n return false;\n }\n }\n\n async function settleIfTerminal(taskId: TaskId): Promise<void> {\n const task = await stores.tasks.get(taskId);\n if (\n task?.usage === undefined ||\n task.settledUsd !== undefined ||\n !terminalTaskStates.has(task.state)\n ) {\n return;\n }\n await stores.tasks.settle(task.id, {\n cost: { totalUsd: task.usage.costUsd },\n ...{\n reason: \"measured usage settled at turn end\",\n },\n });\n }\n\n async function escalateUnresolvedTurn(task: Task, completedSessionId?: string): Promise<void> {\n await createAndPresentQuestion(\n stores,\n task,\n UNRESOLVED_TURN_QUESTION,\n options.presentQuestion,\n completedSessionId,\n ).catch(() => undefined);\n }\n\n return {\n factory: defineHook({\n events: {\n async \"step.completed\"(event, ctx) {\n const taskId = boundTaskId(ctx);\n const usage = event.data.usage;\n if (taskId === undefined || usage === undefined) {\n return;\n }\n const binding = boundExecution(ctx);\n if (binding !== undefined) {\n const task = await stores.tasks.get(taskId).catch(() => null);\n if (task?.attempt !== binding.attempt) return;\n }\n await stores.tasks\n .recordUsage(taskId, {\n step: {\n turnId: JSON.stringify([ctx.session.id, event.data.turnId]),\n stepIndex: event.data.stepIndex,\n ...usage,\n },\n })\n .catch(() => undefined);\n },\n async \"turn.failed\"(event, ctx) {\n const binding = boundExecution(ctx);\n if (binding !== undefined) {\n await recoverFailedAttempt(\n binding,\n `provider turn failed (${event.data.code}): ${event.data.message}`,\n ctx,\n ).catch(() => undefined);\n return;\n }\n const taskId = boundTaskId(ctx);\n const task = taskId ? await stores.tasks.get(taskId).catch(() => null) : null;\n if (task?.state === \"running\" && (await isConversation(task))) {\n await stores.tasks\n .transition(task.id, \"failed\", {\n expectFrom: \"running\",\n reason: \"Conversation turn failed\",\n })\n .catch(() => undefined);\n await settleIfTerminal(task.id).catch(() => undefined);\n }\n },\n async \"session.failed\"(event, ctx) {\n const binding = boundExecution(ctx);\n if (binding !== undefined) {\n await recoverFailedAttempt(\n binding,\n `provider session failed (${event.data.code}): ${event.data.message}`,\n ctx,\n ).catch(() => undefined);\n }\n await options.materializeTranscript?.(materializationInput(ctx)).catch(() => undefined);\n },\n async \"session.waiting\"(_event, ctx) {\n await options.materializeTranscript?.(materializationInput(ctx)).catch(() => undefined);\n },\n async \"session.completed\"(_event, ctx) {\n await options.materializeTranscript?.(materializationInput(ctx)).catch(() => undefined);\n },\n async \"turn.cancelled\"(_event, ctx) {\n const binding = boundExecution(ctx);\n if (binding !== undefined) {\n await cancelAttempt(\n binding,\n `provider turn cancelled on attempt ${binding.attempt}`,\n ).catch(() => undefined);\n return;\n }\n const taskId = boundTaskId(ctx);\n const task = taskId ? await stores.tasks.get(taskId).catch(() => null) : null;\n if (task && (await isConversation(task))) {\n if (task.state === \"running\")\n await stores.tasks\n .transition(task.id, \"cancelled\", {\n expectFrom: \"running\",\n reason: \"Conversation turn interrupted\",\n })\n .catch(() => undefined);\n await settleIfTerminal(task.id).catch(() => undefined);\n }\n },\n async \"turn.completed\"(_event, ctx) {\n const taskId = boundTaskId(ctx);\n const binding = boundExecution(ctx);\n if (taskId !== undefined) {\n const task = await stores.tasks.get(taskId).catch(() => null);\n if (binding !== undefined && task?.attempt !== binding.attempt) return;\n if (\n binding &&\n options.recoverResult &&\n task?.execution &&\n (task.execution.provider !== \"eve\" || task.execution.sessionId !== ctx.session.id)\n )\n return;\n let recovered: boolean;\n try {\n recovered = task !== null && (await recoverBoundResult(task, ctx));\n } catch {\n // A saved outcome may exist; storage failure must not authorize escalation.\n return;\n }\n if (!recovered && task?.state === \"running\" && !task.waitingForMessages) {\n const completion = await options\n .completeConversation?.(task)\n .catch(() => false as const);\n if (completion) {\n await stores.tasks\n .transition(taskId, \"succeeded\", {\n expectFrom: \"running\",\n expectAttempt: task.attempt,\n reason: \"Conversation turn completed\",\n output: completion.output,\n })\n .catch(() => undefined);\n } else {\n // The turn genuinely finished (this is `turn.completed`, not a\n // crash or an interrupted turn.failed/turn.cancelled) with the\n // task still running: nothing was recorded, and an identical\n // retry cannot do better than this one did. Enforce the turn\n // contract now instead of leaving it to the 45-minute stall\n // window, which would otherwise burn up to `maxAttempts`\n // replacement attempts on a task no attempt can ever finish.\n await escalateUnresolvedTurn(\n task,\n binding &&\n task.execution?.provider === \"eve\" &&\n task.execution.sessionId === ctx.session.id\n ? ctx.session.id\n : undefined,\n );\n }\n }\n await settleIfTerminal(taskId).catch(() => undefined);\n }\n const address = ctx.channel.continuationToken;\n if (address === undefined) {\n return;\n }\n const tasks = await collectFactoryRecords(stores.tasks.list, {\n limit: 100,\n match: { state: \"running\", replyTo: { address } },\n }).catch(() => []);\n const abandoned = tasks.filter(\n (task) =>\n task.id !== taskId &&\n task.state === \"running\" &&\n !task.waitingForMessages &&\n task.replyTo.address === address,\n );\n for (const task of abandoned) {\n await escalateUnresolvedTurn(task);\n }\n },\n },\n }),\n };\n}\n\n/** The minimal command-running sandbox surface used by Eve tools. */\nexport {\n createDelegateAgentTool,\n createInvokeAgentTool,\n type DelegateAgentLaunchInput,\n type DelegateAgentTool,\n type DelegateAgentToolInput,\n type DelegateAgentToolOptions,\n type DelegateAgentToolStores,\n type DelegateWorkflowInput,\n type DelegateWorkflowPolicy,\n type DelegateWorkflowResult,\n type InvokeAgentTool,\n type InvokeAgentToolInput,\n type InvokeAgentToolOptions,\n type InvokeAgentToolResult,\n type InvokeAgentToolStores,\n} from \"./invoke\";\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/eve/index.ts"],"sourcesContent":["import { collectFactoryRecords } from \"../store/iterate\";\nimport type { SessionContext } from \"eve/context\";\nimport { defineHook, type HookDefinition } from \"eve/hooks\";\nimport { defineTool, type ToolContext, type ToolDefinition } from \"eve/tools\";\nimport { z } from \"zod\";\nimport {\n changeIdSchema,\n repositoryIdsSchema,\n signalIdSchema,\n taskIdSchema,\n type ChangeId,\n type RepositoryIds,\n type SignalId,\n type TaskId,\n} from \"../schema/id\";\nimport {\n receiptKindSchema,\n receiptStateSchema,\n type Receipt,\n type ReceiptKind,\n type ReceiptState,\n} from \"../schema/receipt\";\nimport type { MaybePromise } from \"../schema/common\";\nimport type { TaskAttemptRef } from \"../schema/execution\";\nimport { taskQuestionSchema, type Task, type TaskQuestion } from \"../schema/task\";\nimport type { Signal, SignalOutcome } from \"../schema/signal\";\nimport { taskWork, type JsonValue } from \"../schema/work\";\nimport type { AnswerQuestionResult, FactoryStores } from \"../store/engine\";\nimport type { EveTranscriptMaterializer, TranscriptMaterializationInput } from \"./transcript\";\nimport { requireTaskExecution } from \"./task-execution\";\n\nexport {\n conversationBinding,\n conversationBindingForSession,\n conversationHeaders,\n factoryReplyAddressAttribute,\n factoryReplyAddressHeader,\n factoryReplyChannelAttribute,\n factoryReplyChannelHeader,\n factoryRepositoryIdsAttribute,\n factoryRepositoryIdsHeader,\n factoryTaskAttemptAttribute,\n factoryTaskAttemptHeader,\n factoryTaskAttribute,\n factoryTaskHeader,\n hasFactoryTaskBinding,\n optionalTaskIdForSession,\n requireTaskIdForSession,\n replyToForSession,\n repositoryIdsForSession,\n taskBindingForSession,\n taskHeaders,\n taskSessionAuth,\n withFactoryTask,\n withOptionalFactoryTask,\n type ConversationBinding,\n type FactoryConversationSessionBinding,\n} from \"./task-session\";\nexport {\n createEveTranscriptMaterializer,\n type EveTranscriptMaterializerOptions,\n type EveTranscriptMaterializer,\n type EveTranscriptRequestScope,\n type TranscriptMaterializationInput,\n} from \"./transcript\";\nexport {\n cancelEveSession,\n forwardEveSessionRequest,\n readCompletedEveTurn,\n startEveSession,\n type CancelEveSessionOptions,\n type EveSessionEvent,\n type EveSessionStream,\n type EveSessionStreamInput,\n type ForwardEveSessionRequestOptions,\n type ReadCompletedEveTurnOptions,\n type StartEveSessionOptions,\n} from \"./session-client\";\n\nconst decidedOutcomeSchema = z.enum([\n \"accepted\",\n \"duplicate\",\n \"unactionable\",\n \"low_confidence\",\n \"ignored\",\n]);\n\n/** Receipt, Signal, and Task capabilities used by the provider-neutral Eve tool bundle. */\nexport type TaskToolsStores = Pick<FactoryStores, \"receipts\" | \"signals\" | \"tasks\">;\n\n/** Task capability used to project Eve lifecycle events onto durable Factory state. */\nexport type FactoryHooksStores = Pick<FactoryStores, \"tasks\" | \"work\">;\n\n/** Configures the provider-neutral Task, signal, question, and receipt Eve tools. */\nexport interface TaskToolsOptions {\n /** Present a persisted human question through the application's Foreman communication boundary. */\n presentQuestion?: (task: Task) => Promise<void>;\n /** Return a refusal reason when this task must use a more specific outcome tool. */\n finishTaskPolicy?: (task: Task) => MaybePromise<string | null>;\n}\n\n/** Delivers a persisted human-question Task through an application-owned channel. */\nexport type PresentQuestion = NonNullable<TaskToolsOptions[\"presentQuestion\"]>;\n\n/** Identifies the Task to read through the provider-neutral task tool. */\nexport interface GetTaskToolInput {\n taskId: TaskId;\n}\n\n/** Explicit not-found result returned when a requested Task does not exist. */\nexport interface TaskNotFoundResult {\n readonly found: false;\n readonly taskId: TaskId;\n}\n\n/** Existing Task or explicit not-found result returned by the task lookup tool. */\nexport type GetTaskToolResult = Task | TaskNotFoundResult;\n\n/** Provider-neutral Eve tool for reading one Factory Task. */\nexport interface GetTaskTool extends ToolDefinition<GetTaskToolInput, GetTaskToolResult> {\n execute(input: GetTaskToolInput, ctx: ToolContext): Promise<GetTaskToolResult>;\n}\n\n/** One final triage decision submitted for a pending Signal. */\nexport interface DecideSignalToolInput {\n signalId: SignalId;\n outcome: Exclude<SignalOutcome, \"pending\">;\n reason: string;\n detail?: string;\n}\n\n/** Provider-neutral Eve tool for recording a Signal decision. */\nexport interface DecideSignalTool extends ToolDefinition<DecideSignalToolInput, Signal> {\n execute(input: DecideSignalToolInput, ctx: ToolContext): Promise<Signal>;\n}\n\n/** Running Task and summary supplied to the generic completion tool. */\nexport interface FinishTaskToolInput {\n taskId: TaskId;\n summary: string;\n}\n\n/** Successful generic Task completion. */\nexport interface FinishTaskSuccess {\n readonly finished: true;\n readonly taskState: \"succeeded\";\n}\n\n/** Policy or lifecycle refusal returned by the generic Task completion tool. */\nexport interface FinishTaskRefusal {\n readonly finished: false;\n readonly reason: string;\n}\n\n/** Completion or bounded refusal returned by the generic Task completion tool. */\nexport type FinishTaskToolResult = FinishTaskSuccess | FinishTaskRefusal;\n\n/** Provider-neutral Eve tool for completing a running Task. */\nexport interface FinishTaskTool extends ToolDefinition<FinishTaskToolInput, FinishTaskToolResult> {\n execute(input: FinishTaskToolInput, ctx: ToolContext): Promise<FinishTaskToolResult>;\n}\n\n/** Unanswered question shape accepted when a Task requests human input. */\nexport type HumanInputQuestion = Omit<TaskQuestion, \"answer\" | \"answeredBy\">;\n\n/** Currently running parent Task and its single human question for this execution. */\nexport interface RequestHumanInputToolInput {\n parentTaskId: TaskId;\n question: HumanInputQuestion;\n}\n\n/** Provider-neutral Eve tool for pausing one running execution on one persisted human question. */\nexport interface RequestHumanInputTool extends ToolDefinition<RequestHumanInputToolInput, Task> {\n execute(input: RequestHumanInputToolInput, ctx: ToolContext): Promise<Task>;\n}\n\n/** Question Task and authenticated answer supplied to the answer tool. */\nexport interface AnswerQuestionToolInput {\n questionTaskId: TaskId;\n answer: string;\n}\n\n/** Provider-neutral Eve tool for answering a persisted human question. */\nexport interface AnswerQuestionTool extends ToolDefinition<\n AnswerQuestionToolInput,\n AnswerQuestionResult\n> {\n execute(input: AnswerQuestionToolInput, ctx: ToolContext): Promise<AnswerQuestionResult>;\n}\n\n/** Immutable audit event accepted by the generic receipt tool. */\nexport interface AppendReceiptToolInput {\n repositoryIds: RepositoryIds;\n taskId?: TaskId;\n changeId?: ChangeId;\n kind: ReceiptKind;\n state: ReceiptState;\n reason?: string;\n dedupeKey?: string;\n}\n\n/** Provider-neutral Eve tool for appending one immutable audit Receipt. */\nexport interface AppendReceiptTool extends ToolDefinition<AppendReceiptToolInput, Receipt> {\n execute(input: AppendReceiptToolInput, ctx: ToolContext): Promise<Receipt>;\n}\n\n/** The provider-neutral Task, Signal, question, and Receipt tools exposed to an Eve agent. */\nexport interface TaskTools {\n /** Read one Task by ID; applications decide which agents may expose this cross-Task lookup. */\n readonly get_task: GetTaskTool;\n /** Record the one final decision for a pending Signal. */\n readonly decide_signal: DecideSignalTool;\n /** Complete generic `task@1` work after authenticating the current Eve execution. */\n readonly finish_task: FinishTaskTool;\n /** Persist and present one human question for the authenticated running Task. */\n readonly request_human_input: RequestHumanInputTool;\n /** Answer a question only from its authenticated reply conversation. */\n readonly answer_question: AnswerQuestionTool;\n /** Append immutable application audit evidence with optional deduplication. */\n readonly append_receipt: AppendReceiptTool;\n}\n\nasync function createAndPresentQuestion(\n stores: Pick<FactoryStores, \"tasks\">,\n parent: Task,\n question: z.infer<typeof taskQuestionSchema>,\n presentQuestion: PresentQuestion,\n completedSessionId?: string,\n): Promise<Task> {\n const needsHumanAction = question.text.trim();\n if (needsHumanAction === \"\") throw new Error(\"A human question must contain non-blank text.\");\n const task = await stores.tasks.create({\n repositoryIds: parent.repositoryIds,\n kind: \"question\",\n work: taskWork({\n title: needsHumanAction.slice(0, 256),\n input: {\n question,\n ...(completedSessionId ? { systemReason: \"unresolved_turn\", completedSessionId } : {}),\n },\n }),\n parentTaskId: parent.id,\n parentTaskAttempt: parent.attempt,\n origin: {},\n replyTo: parent.replyTo,\n question,\n });\n try {\n const waiting = await stores.tasks.transition(task.id, \"needs_human\", { needsHumanAction });\n if (parent.state === \"running\") {\n await stores.tasks.transition(parent.id, \"needs_human\", {\n expectFrom: \"running\",\n expectAttempt: parent.attempt,\n reason: `waiting for answer to question ${waiting.id}`,\n questionTaskId: waiting.id,\n needsHumanAction,\n });\n }\n await presentQuestion(waiting);\n return waiting;\n } catch (error) {\n const currentParent = await stores.tasks.get(parent.id);\n if (\n currentParent?.state === \"needs_human\" &&\n currentParent.attempt === parent.attempt &&\n currentParent.questionTaskId === task.id\n ) {\n try {\n await stores.tasks.transition(parent.id, \"queued\", {\n expectFrom: \"needs_human\",\n expectAttempt: currentParent.attempt,\n expectQuestionTaskId: task.id,\n reason: `delivery of question ${task.id} failed`,\n });\n } catch (cleanupError) {\n const latest = await stores.tasks.get(parent.id);\n if (\n latest?.state === \"needs_human\" &&\n latest.attempt === currentParent.attempt &&\n latest.questionTaskId === task.id\n )\n throw cleanupError;\n }\n }\n const currentQuestion = await stores.tasks.get(task.id);\n if (currentQuestion?.state === \"needs_human\" || currentQuestion?.state === \"queued\") {\n await stores.tasks.transition(task.id, \"cancelled\", {\n expectFrom: currentQuestion.state,\n expectAttempt: currentQuestion.attempt,\n reason: \"question delivery failed\",\n });\n }\n throw error;\n }\n}\n\n/**\n * Wraps provider-neutral Factory store operations as Eve tools.\n *\n * @remarks\n * Expose only the returned tools each agent role needs. Every write goes through the validating\n * engine, but tool visibility remains application authorization policy. `finish_task` and\n * `request_human_input` recheck the current authenticated Task attempt and Eve session before\n * mutating state. `get_task`, signal decisions, and generic receipts are broader capabilities and\n * should be mounted only for agents allowed to use them.\n *\n * The generic `finish_task` records `{ summary }` for `task@1`. Schema-specific workflows should\n * define a narrow tool that calls `requireTaskExecution` and `stores.work.completeWorkflow`, as in\n * the shipped `docs/recipes/typed-eve-result.md` recipe.\n *\n * @param stores - Receipt, Signal, and Task capabilities available to the tool bundle.\n * @param options - Optional question presentation and generic-completion policy.\n * @returns A new named Eve tool bundle ready for selective mounting under `agent/tools/`.\n *\n * @example\n * ```ts\n * import { createTaskTools, type TaskToolsStores } from \"@vercel/factory\";\n *\n * declare const stores: TaskToolsStores;\n * const tools = createTaskTools(stores);\n * export default tools.get_task;\n * ```\n */\nexport function createTaskTools(\n stores: TaskToolsStores,\n options: TaskToolsOptions = {},\n): TaskTools {\n return {\n get_task: defineTool({\n description:\n \"Read one factory task by id: its kind, state, repositories, origin, and results.\",\n inputSchema: z.object({ taskId: taskIdSchema }),\n async execute({ taskId }) {\n const task = await stores.tasks.get(taskId);\n return task ?? { found: false as const, taskId };\n },\n }),\n\n decide_signal: defineTool({\n description:\n \"Record the triage decision for a pending signal. A signal is decided exactly once.\",\n inputSchema: z.object({\n signalId: signalIdSchema,\n outcome: decidedOutcomeSchema,\n reason: z.string().min(1).describe(\"Why this outcome was chosen.\"),\n detail: z\n .string()\n .min(1)\n .optional()\n .describe(\"Severity if accepted, or the duplicate-of signal id.\"),\n }),\n execute({ signalId, outcome, reason, detail }) {\n return stores.signals.decide(signalId, { outcome: outcome, ...{ reason, detail } });\n },\n }),\n\n finish_task: defineTool({\n description:\n \"Mark your running task finished with a summary. The configured workflow policy may require a more specific outcome tool.\",\n inputSchema: z.object({\n taskId: taskIdSchema,\n summary: z.string().min(1).max(4000).describe(\"What you did and found, for the ledger.\"),\n }),\n async execute({ taskId, summary }, ctx) {\n const task = await requireTaskExecution(stores, ctx, taskId);\n const refusal = await options.finishTaskPolicy?.(task);\n if (refusal !== undefined && refusal !== null) {\n return {\n finished: false as const,\n reason: refusal,\n };\n }\n if (task.state !== \"running\") {\n return { finished: false as const, reason: `task is ${task.state}, not running` };\n }\n await stores.tasks.transition(taskId, \"succeeded\", {\n expectFrom: \"running\",\n expectAttempt: task.attempt,\n expectExecution: task.execution,\n reason: summary,\n output: { summary },\n });\n return { finished: true as const, taskState: \"succeeded\" as const };\n },\n }),\n\n request_human_input: defineTool({\n description:\n 'Request one human input or action and pause your currently running task until the human replies. A paused task cannot request another question; wait for the answer and replacement execution. The parent task determines the repositories and reply destination. Name any suggested reply in question.closingOptions when that exact answer means no further attempt can help (for example \"nothing to do, close it\"); Factory resolves the task instead of requeuing an identical retry when the human picks one.',\n inputSchema: z.object({\n parentTaskId: taskIdSchema.describe(\"Your running task, which the answer will unblock.\"),\n question: taskQuestionSchema.omit({ answer: true, answeredBy: true }),\n }),\n async execute({ parentTaskId, question }, ctx) {\n if (options.presentQuestion === undefined) {\n throw new Error(\"request_human_input requires a configured question presenter.\");\n }\n const parent = await requireTaskExecution(stores, ctx, parentTaskId);\n return createAndPresentQuestion(stores, parent, question, options.presentQuestion);\n },\n }),\n\n answer_question: defineTool({\n description:\n \"Record the authenticated user's answer to a waiting question and requeue the parent specialist task, unless the answer matches one of the question's closingOptions, which resolves the parent task instead. A blocked parent task id is also accepted and resolves its persisted QuestionTask id.\",\n inputSchema: z.object({\n questionTaskId: taskIdSchema,\n answer: z.string().min(1).max(4000),\n }),\n async execute({ questionTaskId, answer }, ctx) {\n const requestedTask = await stores.tasks.get(questionTaskId);\n const resolvedQuestionTaskId =\n requestedTask?.kind === \"question\" ? requestedTask.id : requestedTask?.questionTaskId;\n const questionTask =\n resolvedQuestionTaskId === undefined\n ? null\n : await stores.tasks.get(resolvedQuestionTaskId);\n if (\n questionTask === null ||\n questionTask.kind !== \"question\" ||\n questionTask.question === undefined\n ) {\n throw new Error(`Question task ${questionTaskId} does not exist.`);\n }\n const { initiator, current } = ctx.session.auth;\n if (\n !initiator ||\n !current ||\n [initiator, current].some(\n ({ attributes }) =>\n attributes.factoryReplyChannel !== questionTask.replyTo.channel ||\n attributes.factoryReplyAddress !== questionTask.replyTo.address,\n )\n ) {\n throw new Error(\"Question reply does not match the authenticated conversation.\");\n }\n const answeredBy = current.principalId;\n return stores.tasks.answerQuestion({\n questionTaskId: questionTask.id,\n answer,\n answeredBy,\n });\n },\n }),\n\n append_receipt: defineTool({\n description:\n \"Append one immutable audit receipt for an event worth remembering. Receipts can never be edited or deleted.\",\n inputSchema: z.object({\n repositoryIds: repositoryIdsSchema,\n taskId: taskIdSchema.optional(),\n changeId: changeIdSchema.optional(),\n kind: receiptKindSchema.describe(\"What the receipt is about, e.g. investigation.\"),\n state: receiptStateSchema.describe(\"What happened, e.g. flag_ramped.\"),\n reason: z.string().min(1).max(2000).optional(),\n dedupeKey: z\n .string()\n .min(1)\n .optional()\n .describe(\"Same key twice records one receipt instead of two.\"),\n }),\n execute(input) {\n return stores.receipts.append(input);\n },\n }),\n };\n}\n\n/** Completed conversation output, or `false` when the Task is not a conversation. */\nexport type ConversationCompletionResult = { readonly output: JsonValue } | false;\n\n/** Configures attempt recovery, conversation completion, and transcript projection for Eve hooks. */\nexport interface FactoryHooksOptions {\n /** Session auth attribute naming the task a dispatched session works on. Default `factoryTaskId`. */\n taskIdAttribute?: string;\n /** Session auth attribute naming the execution attempt. Default `factoryTaskAttempt`. */\n taskAttemptAttribute?: string;\n /** Attempts allowed before an explicit provider failure becomes terminal. Default: 3. */\n maxAttempts?: number;\n /**\n * Recover a saved workflow result for the exact bound execution before failure retry or\n * unresolved-turn escalation. Return true when handled; a throw defers generic lifecycle changes.\n */\n recoverResult?: (task: Task) => Promise<boolean>;\n /** Present the recovery question created when a task turn ends without an outcome. */\n presentQuestion: PresentQuestion;\n /** Classify discussion Tasks on failure or cancellation without collecting a successful response. */\n isConversation?: (task: Task) => MaybePromise<boolean>;\n /** Return the actual completed response for an opted-in discussion, or false for other work. */\n completeConversation?: (task: Task) => Promise<ConversationCompletionResult>;\n /** Persist the browser-safe transcript projection after Eve durably records a response boundary. */\n materializeTranscript?: EveTranscriptMaterializer;\n}\n\nfunction materializationInput(\n ctx: import(\"eve/hooks\").HookContext,\n): TranscriptMaterializationInput {\n return {\n agent: ctx.agent.name,\n execution: { provider: \"eve\", sessionId: ctx.session.id },\n attributes: ctx.session.auth.initiator?.attributes ?? {},\n };\n}\n\n/** Observe-only hook for agents that do not use the Factory task-lifecycle hook. */\nexport function createTranscriptSnapshotHook(\n materialize: EveTranscriptMaterializer,\n): TranscriptSnapshotHook {\n const snapshot = (_event: unknown, ctx: import(\"eve/hooks\").HookContext) =>\n materialize(materializationInput(ctx)).catch(() => undefined);\n return defineHook({\n events: {\n \"session.waiting\": snapshot,\n \"session.completed\": snapshot,\n \"session.failed\": snapshot,\n },\n });\n}\n\n/** Observe-only Eve hook that materializes transcript snapshots at response boundaries. */\nexport type TranscriptSnapshotHook = HookDefinition<\n \"session.waiting\" | \"session.completed\" | \"session.failed\"\n>;\n\nconst terminalTaskStates: ReadonlySet<Task[\"state\"]> = new Set([\n \"succeeded\",\n \"failed\",\n \"cancelled\",\n]);\n\ntype FactoryHookEvent =\n | \"step.completed\"\n | \"subagent.completed\"\n | \"turn.started\"\n | \"turn.failed\"\n | \"session.failed\"\n | \"session.waiting\"\n | \"session.completed\"\n | \"turn.cancelled\"\n | \"turn.completed\";\n\n/** The Eve lifecycle hook projected onto durable Factory Task state. */\nexport interface FactoryHooks {\n /** Hook definition to export from the target Eve agent's `hooks/` directory. */\n readonly factory: HookDefinition<FactoryHookEvent>;\n}\n\n/**\n * The turn contract's escalation question: a specialist turn ended cleanly\n * (eve durably recorded `turn.completed`) but the bound task is still\n * `running`, so no transition was recorded. That is never a \"worker died\n * mid-turn\" situation -- eve confirms the turn finished -- so retrying the\n * identical brief cannot succeed any differently. Escalating straight to\n * `needs_human` instead of waiting for the stall window (up to 45 minutes,\n * times `maxAttempts`) surfaces the dead end immediately. \"Close the task\"\n * is a declared closingOptions entry: answering with it resolves the task\n * instead of requeuing an identical retry.\n */\n/** Marks a wait this hook owns, so it never resumes a planner's or workflow's own wait. */\nconst SUBAGENT_WAIT_PHASE = \"subagents\";\n\nconst UNRESOLVED_TURN_QUESTION: z.infer<typeof taskQuestionSchema> = {\n text: \"The specialist ended its turn without recording a result for this task. Reply to retry it, or close it if the work no longer applies (for example, a newer attempt already superseded it).\",\n options: [\"Retry the task\", \"Close the task\"],\n closingOptions: [\"Close the task\"],\n};\n\n/**\n * Creates observe-only Eve hooks that project session outcomes onto durable Factory state.\n *\n * @remarks\n * step.completed: records the step's tokens and AI Gateway cost on the bound\n * task. Delivery is at least once; recordUsage dedupes by (sessionId, turnId, stepIndex).\n *\n * turn.failed/session.failed: first recovers an opted-in saved workflow result, then re-queues the current attempt, or\n * fails it when its configured attempts are exhausted. turn.cancelled marks\n * the current Task cancelled. Late events from prior attempts are ignored.\n *\n * subagent.completed/turn.started: a bound execution that starts background subagent work marks\n * its Task as waiting for messages, so the turn that launched the work can end without escalation\n * and the stall window does not replace the waiting attempt. The next turn of that execution, which\n * Eve starts to deliver the settled results, resumes the Task and the turn contract applies again.\n * Events from any other session bound to the same Task, such as a subagent's own session, are\n * ignored, and a wait that a planner or workflow started is never resumed here.\n *\n * turn.completed: recovers an opted-in saved workflow result before generic escalation, and\n * settles the bound task's recorded usage if the task is\n * already terminal, and enforces the turn contract otherwise: a still-running\n * bound task (or a still-running task at this session's reply address, for\n * conversational continuations that never got their own bound session)\n * escalates immediately to a surfaced human question instead of waiting out\n * the stall window, because the agent finished without recording an outcome.\n * When the bound session matches the persisted execution, that question's work input includes\n * `systemReason: \"unresolved_turn\"` and `completedSessionId`. Application recovery can distinguish\n * this completed execution from an agent's genuine request for human input.\n *\n * Every store call is swallowed: a hook throw fails the turn.\n * Transcript projection writes at session response boundaries are likewise\n * best-effort; the API can rebuild a missing projection from durable events.\n *\n * Export the returned `factory` hook from the target agent's `hooks/` directory. The hook reads\n * the Task ID and attempt written by `withFactoryTask`; late events from replaced attempts cannot\n * advance current work. Provider failure recovery requeues below `maxAttempts` and fails the Task\n * at the limit. A clean turn that records no outcome creates a human question instead of silently\n * retrying the same brief.\n *\n * @param stores - Task store used for usage, recovery, cancellation, and completion projection.\n * @param options - Attempt limits plus application-owned question and conversation callbacks.\n * @returns An Eve hook definition under the `factory` property.\n * @see The shipped `docs/recipes/retry-recovery.md` recipe for bounded replacement attempts.\n *\n * @example\n * ```ts\n * import { createFactoryHooks, type FactoryHooksStores } from \"@vercel/factory\";\n *\n * declare const stores: FactoryHooksStores;\n * const hooks = createFactoryHooks(stores, {\n * presentQuestion: async (questionTask) => {\n * // Deliver the persisted question through the application's operator channel.\n * },\n * });\n * export default hooks.factory;\n * ```\n */\nexport function createFactoryHooks(\n stores: FactoryHooksStores,\n options: FactoryHooksOptions,\n): FactoryHooks {\n const taskIdAttribute = options.taskIdAttribute ?? \"factoryTaskId\";\n const taskAttemptAttribute = options.taskAttemptAttribute ?? \"factoryTaskAttempt\";\n const maxAttempts = z\n .number()\n .int()\n .min(1)\n .parse(options.maxAttempts ?? 3);\n\n function boundTaskId(ctx: Pick<SessionContext, \"session\">): TaskId | undefined {\n const parsed = taskIdSchema.safeParse(ctx.session.auth.initiator?.attributes[taskIdAttribute]);\n return parsed.success ? parsed.data : undefined;\n }\n\n function boundExecution(ctx: Pick<SessionContext, \"session\">): TaskAttemptRef | undefined {\n const attributes = ctx.session.auth.initiator?.attributes;\n const taskId = boundTaskId(ctx);\n const attempt = z.coerce.number().int().min(1).safeParse(attributes?.[taskAttemptAttribute]);\n return taskId !== undefined && attempt.success ? { taskId, attempt: attempt.data } : undefined;\n }\n\n async function recoverBoundResult(\n task: Task,\n ctx: Pick<SessionContext, \"session\">,\n ): Promise<boolean> {\n const binding = boundExecution(ctx);\n if (\n binding?.taskId !== task.id ||\n binding.attempt !== task.attempt ||\n task.execution?.provider !== \"eve\" ||\n task.execution.sessionId !== ctx.session.id\n )\n return false;\n return (await options.recoverResult?.(task)) ?? false;\n }\n\n async function recoverFailedAttempt(\n binding: TaskAttemptRef,\n failure: string,\n ctx: Pick<SessionContext, \"session\">,\n ): Promise<void> {\n const task = await stores.tasks.get(binding.taskId);\n if (\n !task ||\n task.attempt !== binding.attempt ||\n (options.recoverResult &&\n task.execution &&\n (task.execution.provider !== \"eve\" || task.execution.sessionId !== ctx.session.id))\n ) {\n return;\n }\n if (await recoverBoundResult(task, ctx)) {\n await settleIfTerminal(task.id);\n return;\n }\n if (task.state !== \"running\") return;\n const exhausted = task.attempt >= maxAttempts;\n await stores.tasks.transition(task.id, exhausted ? \"failed\" : \"queued\", {\n expectFrom: \"running\",\n expectAttempt: binding.attempt,\n phase: \"retry\",\n reason: exhausted\n ? `${failure} on attempt ${task.attempt} of ${maxAttempts}; attempts exhausted`\n : `${failure} on attempt ${task.attempt} of ${maxAttempts}; replacement queued`,\n });\n }\n\n async function cancelAttempt(binding: TaskAttemptRef, reason: string): Promise<void> {\n const task = await stores.tasks.get(binding.taskId);\n if (task?.state !== \"running\" || task.attempt !== binding.attempt) {\n return;\n }\n await stores.tasks.transition(task.id, \"cancelled\", {\n expectFrom: \"running\",\n expectAttempt: binding.attempt,\n phase: \"execution\",\n reason,\n });\n }\n\n async function isConversation(task: Task): Promise<boolean> {\n try {\n return (await options.isConversation?.(task)) ?? false;\n } catch {\n return false;\n }\n }\n\n async function settleIfTerminal(taskId: TaskId): Promise<void> {\n const task = await stores.tasks.get(taskId);\n if (\n task?.usage === undefined ||\n task.settledUsd !== undefined ||\n !terminalTaskStates.has(task.state)\n ) {\n return;\n }\n await stores.tasks.settle(task.id, {\n cost: { totalUsd: task.usage.costUsd },\n ...{\n reason: \"measured usage settled at turn end\",\n },\n });\n }\n\n async function ownedRunningTask(ctx: Pick<SessionContext, \"session\">) {\n const binding = boundExecution(ctx);\n if (binding === undefined) return undefined;\n const task = await stores.tasks.get(binding.taskId);\n return task?.state === \"running\" &&\n task.attempt === binding.attempt &&\n task.execution?.provider === \"eve\" &&\n task.execution.sessionId === ctx.session.id\n ? { task, binding }\n : undefined;\n }\n\n async function escalateUnresolvedTurn(task: Task, completedSessionId?: string): Promise<void> {\n await createAndPresentQuestion(\n stores,\n task,\n UNRESOLVED_TURN_QUESTION,\n options.presentQuestion,\n completedSessionId,\n ).catch(() => undefined);\n }\n\n return {\n factory: defineHook({\n events: {\n async \"step.completed\"(event, ctx) {\n const taskId = boundTaskId(ctx);\n const usage = event.data.usage;\n if (taskId === undefined || usage === undefined) {\n return;\n }\n const binding = boundExecution(ctx);\n if (binding !== undefined) {\n const task = await stores.tasks.get(taskId).catch(() => null);\n if (task?.attempt !== binding.attempt) return;\n }\n await stores.tasks\n .recordUsage(taskId, {\n step: {\n turnId: JSON.stringify([ctx.session.id, event.data.turnId]),\n stepIndex: event.data.stepIndex,\n ...usage,\n },\n })\n .catch(() => undefined);\n },\n async \"subagent.completed\"(event, ctx) {\n if (event.data.backgroundTask === undefined) return;\n const owned = await ownedRunningTask(ctx).catch(() => undefined);\n if (owned === undefined || owned.task.waitingForMessages) return;\n await stores.work\n .waitForMessages({ task: owned.binding, phase: SUBAGENT_WAIT_PHASE })\n .catch(() => undefined);\n },\n async \"turn.started\"(_event, ctx) {\n const owned = await ownedRunningTask(ctx).catch(() => undefined);\n if (!owned?.task.waitingForMessages || owned.task.workflowPhase !== SUBAGENT_WAIT_PHASE)\n return;\n await stores.work.resume({ task: owned.binding }).catch(() => undefined);\n },\n async \"turn.failed\"(event, ctx) {\n const binding = boundExecution(ctx);\n if (binding !== undefined) {\n await recoverFailedAttempt(\n binding,\n `provider turn failed (${event.data.code}): ${event.data.message}`,\n ctx,\n ).catch(() => undefined);\n return;\n }\n const taskId = boundTaskId(ctx);\n const task = taskId ? await stores.tasks.get(taskId).catch(() => null) : null;\n if (task?.state === \"running\" && (await isConversation(task))) {\n await stores.tasks\n .transition(task.id, \"failed\", {\n expectFrom: \"running\",\n reason: \"Conversation turn failed\",\n })\n .catch(() => undefined);\n await settleIfTerminal(task.id).catch(() => undefined);\n }\n },\n async \"session.failed\"(event, ctx) {\n const binding = boundExecution(ctx);\n if (binding !== undefined) {\n await recoverFailedAttempt(\n binding,\n `provider session failed (${event.data.code}): ${event.data.message}`,\n ctx,\n ).catch(() => undefined);\n }\n await options.materializeTranscript?.(materializationInput(ctx)).catch(() => undefined);\n },\n async \"session.waiting\"(_event, ctx) {\n await options.materializeTranscript?.(materializationInput(ctx)).catch(() => undefined);\n },\n async \"session.completed\"(_event, ctx) {\n await options.materializeTranscript?.(materializationInput(ctx)).catch(() => undefined);\n },\n async \"turn.cancelled\"(_event, ctx) {\n const binding = boundExecution(ctx);\n if (binding !== undefined) {\n await cancelAttempt(\n binding,\n `provider turn cancelled on attempt ${binding.attempt}`,\n ).catch(() => undefined);\n return;\n }\n const taskId = boundTaskId(ctx);\n const task = taskId ? await stores.tasks.get(taskId).catch(() => null) : null;\n if (task && (await isConversation(task))) {\n if (task.state === \"running\")\n await stores.tasks\n .transition(task.id, \"cancelled\", {\n expectFrom: \"running\",\n reason: \"Conversation turn interrupted\",\n })\n .catch(() => undefined);\n await settleIfTerminal(task.id).catch(() => undefined);\n }\n },\n async \"turn.completed\"(_event, ctx) {\n const taskId = boundTaskId(ctx);\n const binding = boundExecution(ctx);\n if (taskId !== undefined) {\n const task = await stores.tasks.get(taskId).catch(() => null);\n if (binding !== undefined && task?.attempt !== binding.attempt) return;\n if (\n binding &&\n options.recoverResult &&\n task?.execution &&\n (task.execution.provider !== \"eve\" || task.execution.sessionId !== ctx.session.id)\n )\n return;\n let recovered: boolean;\n try {\n recovered = task !== null && (await recoverBoundResult(task, ctx));\n } catch {\n // A saved outcome may exist; storage failure must not authorize escalation.\n return;\n }\n if (!recovered && task?.state === \"running\" && !task.waitingForMessages) {\n const completion = await options\n .completeConversation?.(task)\n .catch(() => false as const);\n if (completion) {\n await stores.tasks\n .transition(taskId, \"succeeded\", {\n expectFrom: \"running\",\n expectAttempt: task.attempt,\n reason: \"Conversation turn completed\",\n output: completion.output,\n })\n .catch(() => undefined);\n } else {\n // The turn genuinely finished (this is `turn.completed`, not a\n // crash or an interrupted turn.failed/turn.cancelled) with the\n // task still running: nothing was recorded, and an identical\n // retry cannot do better than this one did. Enforce the turn\n // contract now instead of leaving it to the 45-minute stall\n // window, which would otherwise burn up to `maxAttempts`\n // replacement attempts on a task no attempt can ever finish.\n await escalateUnresolvedTurn(\n task,\n binding &&\n task.execution?.provider === \"eve\" &&\n task.execution.sessionId === ctx.session.id\n ? ctx.session.id\n : undefined,\n );\n }\n }\n await settleIfTerminal(taskId).catch(() => undefined);\n }\n const address = ctx.channel.continuationToken;\n if (address === undefined) {\n return;\n }\n const tasks = await collectFactoryRecords(stores.tasks.list, {\n limit: 100,\n match: { state: \"running\", replyTo: { address } },\n }).catch(() => []);\n const abandoned = tasks.filter(\n (task) =>\n task.id !== taskId &&\n task.state === \"running\" &&\n !task.waitingForMessages &&\n task.replyTo.address === address,\n );\n for (const task of abandoned) {\n await escalateUnresolvedTurn(task);\n }\n },\n },\n }),\n };\n}\n\n/** The minimal command-running sandbox surface used by Eve tools. */\nexport {\n createDelegateAgentTool,\n createInvokeAgentTool,\n type DelegateAgentLaunchInput,\n type DelegateAgentTool,\n type DelegateAgentToolInput,\n type DelegateAgentToolOptions,\n type DelegateAgentToolStores,\n type DelegateWorkflowInput,\n type DelegateWorkflowPolicy,\n type DelegateWorkflowResult,\n type InvokeAgentTool,\n type InvokeAgentToolInput,\n type InvokeAgentToolOptions,\n type InvokeAgentToolResult,\n type InvokeAgentToolStores,\n} from \"./invoke\";\n"],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region src/eve/install-dependencies.d.ts
|
|
2
|
+
/** Where and how long the dependency install runs in the agent's sandbox. */
|
|
3
|
+
interface InstallDependenciesToolOptions {
|
|
4
|
+
/** Checkout path in the sandbox. Default: `repo`. */
|
|
5
|
+
repositoryPath?: string;
|
|
6
|
+
/** Seconds before the install is stopped. Default: 600. */
|
|
7
|
+
timeoutSeconds?: number;
|
|
8
|
+
}
|
|
9
|
+
/** Result of one dependency install; read the saved log before trusting later checks. */
|
|
10
|
+
interface InstallDependenciesResult {
|
|
11
|
+
installed: boolean;
|
|
12
|
+
exitCode: number | null;
|
|
13
|
+
logPath: string | null;
|
|
14
|
+
outputTail: string;
|
|
15
|
+
}
|
|
16
|
+
/** Creates an Eve tool that installs a pnpm checkout's dependencies from its frozen lockfile. */
|
|
17
|
+
declare function createInstallDependenciesTool(options?: InstallDependenciesToolOptions): import("eve/tools").ToolDefinition<{
|
|
18
|
+
runScripts: boolean;
|
|
19
|
+
filter?: string | undefined;
|
|
20
|
+
}, InstallDependenciesResult> & {
|
|
21
|
+
execute(input: {
|
|
22
|
+
runScripts: boolean;
|
|
23
|
+
filter?: string | undefined;
|
|
24
|
+
}, ctx: import("eve/tools").ToolContext): Promise<InstallDependenciesResult>;
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
export { InstallDependenciesResult, InstallDependenciesToolOptions, createInstallDependenciesTool };
|
|
28
|
+
//# sourceMappingURL=install-dependencies.d.mts.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { quoteShellArgument } from "../github-transfer.mjs";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { defineTool } from "eve/tools";
|
|
5
|
+
//#region src/eve/install-dependencies.ts
|
|
6
|
+
const filterSchema = z.string().regex(/^[\w@./:^!{}[\]*-]+$/u, "expected a pnpm workspace selector").max(200);
|
|
7
|
+
/** Creates an Eve tool that installs a pnpm checkout's dependencies from its frozen lockfile. */
|
|
8
|
+
function createInstallDependenciesTool(options = {}) {
|
|
9
|
+
const repositoryPath = options.repositoryPath ?? "repo";
|
|
10
|
+
const timeoutSeconds = options.timeoutSeconds ?? 600;
|
|
11
|
+
return defineTool({
|
|
12
|
+
description: `Install dependencies in the ${repositoryPath}/ checkout with pnpm from its frozen lockfile, so tests and checks can run. Lifecycle scripts are off unless runScripts is true. Pass a workspace filter to install only what a package needs. Read the returned exit code and saved log: a successful install does not prove checks pass.`,
|
|
13
|
+
inputSchema: z.object({
|
|
14
|
+
filter: filterSchema.optional().describe("pnpm selector without --filter, such as 'vercel...'. Omit for the whole workspace."),
|
|
15
|
+
runScripts: z.boolean().default(false).describe("Run lifecycle scripts, for native dependencies or generated files checks need.")
|
|
16
|
+
}),
|
|
17
|
+
async execute({ filter, runScripts }, ctx) {
|
|
18
|
+
const sandbox = await ctx.getSandbox();
|
|
19
|
+
const path = quoteShellArgument(repositoryPath);
|
|
20
|
+
if ((await sandbox.run({ command: `test -f ${path}/pnpm-lock.yaml` })).exitCode !== 0) return {
|
|
21
|
+
installed: false,
|
|
22
|
+
exitCode: null,
|
|
23
|
+
logPath: null,
|
|
24
|
+
outputTail: `${repositoryPath}/pnpm-lock.yaml not found; only pnpm checkouts are supported.`
|
|
25
|
+
};
|
|
26
|
+
const logPath = `/workspace/install-${randomUUID()}.log`;
|
|
27
|
+
const install = [
|
|
28
|
+
"node --version",
|
|
29
|
+
"corepack pnpm --version",
|
|
30
|
+
[
|
|
31
|
+
"corepack pnpm install --frozen-lockfile",
|
|
32
|
+
...runScripts ? [] : ["--ignore-scripts"],
|
|
33
|
+
...filter ? ["--filter", quoteShellArgument(filter)] : []
|
|
34
|
+
].join(" ")
|
|
35
|
+
].join("\n");
|
|
36
|
+
const result = await sandbox.run({ command: `cd ${path} && timeout ${timeoutSeconds} sh -ec ${quoteShellArgument(install)} > ${quoteShellArgument(logPath)} 2>&1` });
|
|
37
|
+
const tail = await sandbox.run({ command: `tail -n 40 ${quoteShellArgument(logPath)}` });
|
|
38
|
+
const exitCode = typeof result.exitCode === "number" ? result.exitCode : null;
|
|
39
|
+
return {
|
|
40
|
+
installed: exitCode === 0,
|
|
41
|
+
exitCode,
|
|
42
|
+
logPath,
|
|
43
|
+
outputTail: tail.stdout
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { createInstallDependenciesTool };
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=install-dependencies.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-dependencies.mjs","names":[],"sources":["../../src/eve/install-dependencies.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { defineTool } from \"eve/tools\";\nimport { z } from \"zod\";\nimport { quoteShellArgument } from \"../github-transfer\";\n\n/** Where and how long the dependency install runs in the agent's sandbox. */\nexport interface InstallDependenciesToolOptions {\n /** Checkout path in the sandbox. Default: `repo`. */\n repositoryPath?: string;\n /** Seconds before the install is stopped. Default: 600. */\n timeoutSeconds?: number;\n}\n\n/** Result of one dependency install; read the saved log before trusting later checks. */\nexport interface InstallDependenciesResult {\n installed: boolean;\n exitCode: number | null;\n logPath: string | null;\n outputTail: string;\n}\n\nconst filterSchema = z\n .string()\n .regex(/^[\\w@./:^!{}[\\]*-]+$/u, \"expected a pnpm workspace selector\")\n .max(200);\n\n/** Creates an Eve tool that installs a pnpm checkout's dependencies from its frozen lockfile. */\nexport function createInstallDependenciesTool(options: InstallDependenciesToolOptions = {}) {\n const repositoryPath = options.repositoryPath ?? \"repo\";\n const timeoutSeconds = options.timeoutSeconds ?? 600;\n return defineTool({\n description: `Install dependencies in the ${repositoryPath}/ checkout with pnpm from its frozen lockfile, so tests and checks can run. Lifecycle scripts are off unless runScripts is true. Pass a workspace filter to install only what a package needs. Read the returned exit code and saved log: a successful install does not prove checks pass.`,\n inputSchema: z.object({\n filter: filterSchema\n .optional()\n .describe(\n \"pnpm selector without --filter, such as 'vercel...'. Omit for the whole workspace.\",\n ),\n runScripts: z\n .boolean()\n .default(false)\n .describe(\"Run lifecycle scripts, for native dependencies or generated files checks need.\"),\n }),\n async execute({ filter, runScripts }, ctx): Promise<InstallDependenciesResult> {\n const sandbox = await ctx.getSandbox();\n const path = quoteShellArgument(repositoryPath);\n const lockfile = await sandbox.run({ command: `test -f ${path}/pnpm-lock.yaml` });\n if (lockfile.exitCode !== 0)\n return {\n installed: false,\n exitCode: null,\n logPath: null,\n outputTail: `${repositoryPath}/pnpm-lock.yaml not found; only pnpm checkouts are supported.`,\n };\n const logPath = `/workspace/install-${randomUUID()}.log`;\n const install = [\n \"node --version\",\n \"corepack pnpm --version\",\n [\n \"corepack pnpm install --frozen-lockfile\",\n ...(runScripts ? [] : [\"--ignore-scripts\"]),\n ...(filter ? [\"--filter\", quoteShellArgument(filter)] : []),\n ].join(\" \"),\n ].join(\"\\n\");\n const result = await sandbox.run({\n command: `cd ${path} && timeout ${timeoutSeconds} sh -ec ${quoteShellArgument(install)} > ${quoteShellArgument(logPath)} 2>&1`,\n });\n const tail = await sandbox.run({ command: `tail -n 40 ${quoteShellArgument(logPath)}` });\n const exitCode = typeof result.exitCode === \"number\" ? result.exitCode : null;\n return { installed: exitCode === 0, exitCode, logPath, outputTail: tail.stdout };\n },\n });\n}\n"],"mappings":";;;;;AAqBA,MAAM,eAAe,EAClB,OAAO,CAAC,CACR,MAAM,yBAAyB,oCAAoC,CAAC,CACpE,IAAI,GAAG;;AAGV,SAAgB,8BAA8B,UAA0C,CAAC,GAAG;CAC1F,MAAM,iBAAiB,QAAQ,kBAAkB;CACjD,MAAM,iBAAiB,QAAQ,kBAAkB;CACjD,OAAO,WAAW;EAChB,aAAa,+BAA+B,eAAe;EAC3D,aAAa,EAAE,OAAO;GACpB,QAAQ,aACL,SAAS,CAAC,CACV,SACC,oFACF;GACF,YAAY,EACT,QAAQ,CAAC,CACT,QAAQ,KAAK,CAAC,CACd,SAAS,gFAAgF;EAC9F,CAAC;EACD,MAAM,QAAQ,EAAE,QAAQ,cAAc,KAAyC;GAC7E,MAAM,UAAU,MAAM,IAAI,WAAW;GACrC,MAAM,OAAO,mBAAmB,cAAc;GAE9C,KAAI,MADmB,QAAQ,IAAI,EAAE,SAAS,WAAW,KAAK,iBAAiB,CAAC,EAAA,CACnE,aAAa,GACxB,OAAO;IACL,WAAW;IACX,UAAU;IACV,SAAS;IACT,YAAY,GAAG,eAAe;GAChC;GACF,MAAM,UAAU,sBAAsB,WAAW,EAAE;GACnD,MAAM,UAAU;IACd;IACA;IACA;KACE;KACA,GAAI,aAAa,CAAC,IAAI,CAAC,kBAAkB;KACzC,GAAI,SAAS,CAAC,YAAY,mBAAmB,MAAM,CAAC,IAAI,CAAC;IAC3D,CAAC,CAAC,KAAK,GAAG;GACZ,CAAC,CAAC,KAAK,IAAI;GACX,MAAM,SAAS,MAAM,QAAQ,IAAI,EAC/B,SAAS,MAAM,KAAK,cAAc,eAAe,UAAU,mBAAmB,OAAO,EAAE,KAAK,mBAAmB,OAAO,EAAE,OAC1H,CAAC;GACD,MAAM,OAAO,MAAM,QAAQ,IAAI,EAAE,SAAS,cAAc,mBAAmB,OAAO,IAAI,CAAC;GACvF,MAAM,WAAW,OAAO,OAAO,aAAa,WAAW,OAAO,WAAW;GACzE,OAAO;IAAE,WAAW,aAAa;IAAG;IAAU;IAAS,YAAY,KAAK;GAAO;EACjF;CACF,CAAC;AACH"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { repositorySlugSchema } from "./schema/repository.mjs";
|
|
2
|
-
import { agentSessionIdSchema } from "./schema/session.mjs";
|
|
3
2
|
import { quoteShellArgument, runSandboxCommand, withSandboxGitBundle } from "./github-transfer.mjs";
|
|
3
|
+
import { agentSessionIdSchema } from "./schema/session.mjs";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
//#region src/github-publication.ts
|
|
6
6
|
/** GitHub publication failure that preserves the provider HTTP status. */
|
package/dist/index.d.mts
CHANGED
|
@@ -8,9 +8,10 @@ import { ExecutionPolicy, FactoryConfig, FactoryConfigInput, FactoryConfigReposi
|
|
|
8
8
|
import { requireTaskExecution } from "./eve/task-execution.mjs";
|
|
9
9
|
import { EnsureConfiguredRepositoryInput, ensureConfiguredRepository, repositoryIdForSlug } from "./repositories.mjs";
|
|
10
10
|
import { RedactedErrorDetails, redactedErrorDetails, redactedErrorMessage } from "./errors.mjs";
|
|
11
|
+
import { InstallDependenciesResult, InstallDependenciesToolOptions, createInstallDependenciesTool } from "./eve/install-dependencies.mjs";
|
|
11
12
|
import { EveTranscriptMaterializer, EveTranscriptMaterializerOptions, EveTranscriptRequestScope, TranscriptMaterializationInput, createEveTranscriptMaterializer } from "./eve/transcript.mjs";
|
|
12
13
|
import { ConversationBinding, FactoryConversationSessionBinding, conversationBinding, conversationBindingForSession, conversationHeaders, factoryReplyAddressAttribute, factoryReplyAddressHeader, factoryReplyChannelAttribute, factoryReplyChannelHeader, factoryRepositoryIdsAttribute, factoryRepositoryIdsHeader, factoryTaskAttemptAttribute, factoryTaskAttemptHeader, factoryTaskAttribute, factoryTaskHeader, hasFactoryTaskBinding, optionalTaskIdForSession, replyToForSession, repositoryIdsForSession, requireTaskIdForSession, taskBindingForSession, taskHeaders, taskSessionAuth, withFactoryTask, withOptionalFactoryTask } from "./eve/task-session.mjs";
|
|
13
14
|
import { CancelEveSessionOptions, EveSessionEvent, EveSessionStream, EveSessionStreamInput, ForwardEveSessionRequestOptions, ReadCompletedEveTurnOptions, StartEveSessionOptions, cancelEveSession, forwardEveSessionRequest, readCompletedEveTurn, startEveSession } from "./eve/session-client.mjs";
|
|
14
15
|
import { DelegateAgentLaunchInput, DelegateAgentTool, DelegateAgentToolInput, DelegateAgentToolOptions, DelegateAgentToolStores, DelegateWorkflowInput, DelegateWorkflowPolicy, DelegateWorkflowResult, InvokeAgentTool, InvokeAgentToolInput, InvokeAgentToolOptions, InvokeAgentToolResult, InvokeAgentToolStores, createDelegateAgentTool, createInvokeAgentTool } from "./eve/invoke.mjs";
|
|
15
16
|
import { AnswerQuestionTool, AnswerQuestionToolInput, AppendReceiptTool, AppendReceiptToolInput, ConversationCompletionResult, DecideSignalTool, DecideSignalToolInput, FactoryHooks, FactoryHooksOptions, FactoryHooksStores, FinishTaskRefusal, FinishTaskSuccess, FinishTaskTool, FinishTaskToolInput, FinishTaskToolResult, GetTaskTool, GetTaskToolInput, GetTaskToolResult, HumanInputQuestion, PresentQuestion, RequestHumanInputTool, RequestHumanInputToolInput, TaskNotFoundResult, TaskTools, TaskToolsOptions, TaskToolsStores, TranscriptSnapshotHook, createFactoryHooks, createTaskTools, createTranscriptSnapshotHook } from "./eve/index.mjs";
|
|
16
|
-
export { type AnswerQuestionTool, type AnswerQuestionToolInput, type AppendReceiptTool, type AppendReceiptToolInput, type CancelEveSessionOptions, type ConversationBinding, type ConversationCompletionResult, type DateClock, type DecideSignalTool, type DecideSignalToolInput, type DelegateAgentLaunchInput, type DelegateAgentTool, type DelegateAgentToolInput, type DelegateAgentToolOptions, type DelegateAgentToolStores, type DelegateWorkflowInput, type DelegateWorkflowPolicy, type DelegateWorkflowResult, type EnsureConfiguredRepositoryInput, type EveSessionEvent, type EveSessionStream, type EveSessionStreamInput, type EveTranscriptMaterializer, type EveTranscriptMaterializerOptions, type EveTranscriptRequestScope, type ExecutionPolicy, type FactoryConfig, type FactoryConfigInput, type FactoryConfigRepository, type FactoryConfigRepositoryInput, type FactoryConversationSessionBinding, type FactoryHooks, type FactoryHooksOptions, type FactoryHooksStores, type FactoryId, type FinishTaskRefusal, type FinishTaskSuccess, type FinishTaskTool, type FinishTaskToolInput, type FinishTaskToolResult, type ForwardEveSessionRequestOptions, type GetTaskTool, type GetTaskToolInput, type GetTaskToolResult, type HumanInputQuestion, InvalidTransitionError, type InvokeAgentTool, type InvokeAgentToolInput, type InvokeAgentToolOptions, type InvokeAgentToolResult, type InvokeAgentToolStores, type IsoDateTimeClock, type MaybePromise, type MillisecondClock, type PresentQuestion, type ReadCompletedEveTurnOptions, type RedactedErrorDetails, type Repository, type RepositoryId, type RepositoryIds, type RepositorySlug, type RequestHumanInputTool, type RequestHumanInputToolInput, SCHEMA_VERSION, type StartEveSessionOptions, type TaskNotFoundResult, type TaskTools, type TaskToolsOptions, type TaskToolsStores, type TranscriptMaterializationInput, type TranscriptSnapshotHook, type Transition, type TransitionActor, type TransitionInput, assertTransition, cancelEveSession, conversationBinding, conversationBindingForSession, conversationHeaders, createDelegateAgentTool, createEveTranscriptMaterializer, createFactoryHooks, createInvokeAgentTool, createTaskTools, createTranscriptSnapshotHook, defaultExecutionPolicy, defineFactory, deriveFactoryId, ensureConfiguredRepository, executionPolicySchema, factoryConfigRepositorySchema, factoryConfigSchema, factoryIdSchema, factoryReplyAddressAttribute, factoryReplyAddressHeader, factoryReplyChannelAttribute, factoryReplyChannelHeader, factoryRepositoryIdsAttribute, factoryRepositoryIdsHeader, factoryTaskAttemptAttribute, factoryTaskAttemptHeader, factoryTaskAttribute, factoryTaskHeader, forwardEveSessionRequest, hasFactoryTaskBinding, isTransitionAllowed, isoDateTimeSchema, legalNextStates, optionalTaskIdForSession, readCompletedEveTurn, redactedErrorDetails, redactedErrorMessage, replyToForSession, repositoryIdForSlug, repositoryIdSchema, repositoryIdsForSession, repositoryIdsSchema, repositorySchema, repositoryScope, repositorySlugSchema, requireTaskExecution, requireTaskIdForSession, schemaVersionSchema, startEveSession, taskBindingForSession, taskHeaders, taskSessionAuth, terminalStates, usdSchema, vocabularySchema, withFactoryTask, withOptionalFactoryTask };
|
|
17
|
+
export { type AnswerQuestionTool, type AnswerQuestionToolInput, type AppendReceiptTool, type AppendReceiptToolInput, type CancelEveSessionOptions, type ConversationBinding, type ConversationCompletionResult, type DateClock, type DecideSignalTool, type DecideSignalToolInput, type DelegateAgentLaunchInput, type DelegateAgentTool, type DelegateAgentToolInput, type DelegateAgentToolOptions, type DelegateAgentToolStores, type DelegateWorkflowInput, type DelegateWorkflowPolicy, type DelegateWorkflowResult, type EnsureConfiguredRepositoryInput, type EveSessionEvent, type EveSessionStream, type EveSessionStreamInput, type EveTranscriptMaterializer, type EveTranscriptMaterializerOptions, type EveTranscriptRequestScope, type ExecutionPolicy, type FactoryConfig, type FactoryConfigInput, type FactoryConfigRepository, type FactoryConfigRepositoryInput, type FactoryConversationSessionBinding, type FactoryHooks, type FactoryHooksOptions, type FactoryHooksStores, type FactoryId, type FinishTaskRefusal, type FinishTaskSuccess, type FinishTaskTool, type FinishTaskToolInput, type FinishTaskToolResult, type ForwardEveSessionRequestOptions, type GetTaskTool, type GetTaskToolInput, type GetTaskToolResult, type HumanInputQuestion, type InstallDependenciesResult, type InstallDependenciesToolOptions, InvalidTransitionError, type InvokeAgentTool, type InvokeAgentToolInput, type InvokeAgentToolOptions, type InvokeAgentToolResult, type InvokeAgentToolStores, type IsoDateTimeClock, type MaybePromise, type MillisecondClock, type PresentQuestion, type ReadCompletedEveTurnOptions, type RedactedErrorDetails, type Repository, type RepositoryId, type RepositoryIds, type RepositorySlug, type RequestHumanInputTool, type RequestHumanInputToolInput, SCHEMA_VERSION, type StartEveSessionOptions, type TaskNotFoundResult, type TaskTools, type TaskToolsOptions, type TaskToolsStores, type TranscriptMaterializationInput, type TranscriptSnapshotHook, type Transition, type TransitionActor, type TransitionInput, assertTransition, cancelEveSession, conversationBinding, conversationBindingForSession, conversationHeaders, createDelegateAgentTool, createEveTranscriptMaterializer, createFactoryHooks, createInstallDependenciesTool, createInvokeAgentTool, createTaskTools, createTranscriptSnapshotHook, defaultExecutionPolicy, defineFactory, deriveFactoryId, ensureConfiguredRepository, executionPolicySchema, factoryConfigRepositorySchema, factoryConfigSchema, factoryIdSchema, factoryReplyAddressAttribute, factoryReplyAddressHeader, factoryReplyChannelAttribute, factoryReplyChannelHeader, factoryRepositoryIdsAttribute, factoryRepositoryIdsHeader, factoryTaskAttemptAttribute, factoryTaskAttemptHeader, factoryTaskAttribute, factoryTaskHeader, forwardEveSessionRequest, hasFactoryTaskBinding, isTransitionAllowed, isoDateTimeSchema, legalNextStates, optionalTaskIdForSession, readCompletedEveTurn, redactedErrorDetails, redactedErrorMessage, replyToForSession, repositoryIdForSlug, repositoryIdSchema, repositoryIdsForSession, repositoryIdsSchema, repositorySchema, repositoryScope, repositorySlugSchema, requireTaskExecution, requireTaskIdForSession, schemaVersionSchema, startEveSession, taskBindingForSession, taskHeaders, taskSessionAuth, terminalStates, usdSchema, vocabularySchema, withFactoryTask, withOptionalFactoryTask };
|
package/dist/index.mjs
CHANGED
|
@@ -8,8 +8,9 @@ import { ensureConfiguredRepository, repositoryIdForSlug } from "./repositories.
|
|
|
8
8
|
import { defaultExecutionPolicy, defineFactory, executionPolicySchema, factoryConfigRepositorySchema, factoryConfigSchema } from "./schema/factory-config.mjs";
|
|
9
9
|
import { InvalidTransitionError, assertTransition, isTransitionAllowed, legalNextStates, terminalStates } from "./schema/transitions.mjs";
|
|
10
10
|
import { redactedErrorDetails, redactedErrorMessage } from "./errors.mjs";
|
|
11
|
+
import { createInstallDependenciesTool } from "./eve/install-dependencies.mjs";
|
|
11
12
|
import { createEveTranscriptMaterializer } from "./eve/transcript.mjs";
|
|
12
13
|
import { cancelEveSession, forwardEveSessionRequest, readCompletedEveTurn, startEveSession } from "./eve/session-client.mjs";
|
|
13
14
|
import { createDelegateAgentTool, createInvokeAgentTool } from "./eve/invoke.mjs";
|
|
14
15
|
import { createFactoryHooks, createTaskTools, createTranscriptSnapshotHook } from "./eve/index.mjs";
|
|
15
|
-
export { InvalidTransitionError, SCHEMA_VERSION, assertTransition, cancelEveSession, conversationBinding, conversationBindingForSession, conversationHeaders, createDelegateAgentTool, createEveTranscriptMaterializer, createFactoryHooks, createInvokeAgentTool, createTaskTools, createTranscriptSnapshotHook, defaultExecutionPolicy, defineFactory, deriveFactoryId, ensureConfiguredRepository, executionPolicySchema, factoryConfigRepositorySchema, factoryConfigSchema, factoryIdSchema, factoryReplyAddressAttribute, factoryReplyAddressHeader, factoryReplyChannelAttribute, factoryReplyChannelHeader, factoryRepositoryIdsAttribute, factoryRepositoryIdsHeader, factoryTaskAttemptAttribute, factoryTaskAttemptHeader, factoryTaskAttribute, factoryTaskHeader, forwardEveSessionRequest, hasFactoryTaskBinding, isTransitionAllowed, isoDateTimeSchema, legalNextStates, optionalTaskIdForSession, readCompletedEveTurn, redactedErrorDetails, redactedErrorMessage, replyToForSession, repositoryIdForSlug, repositoryIdSchema, repositoryIdsForSession, repositoryIdsSchema, repositorySchema, repositoryScope, repositorySlugSchema, requireTaskExecution, requireTaskIdForSession, schemaVersionSchema, startEveSession, taskBindingForSession, taskHeaders, taskSessionAuth, terminalStates, usdSchema, vocabularySchema, withFactoryTask, withOptionalFactoryTask };
|
|
16
|
+
export { InvalidTransitionError, SCHEMA_VERSION, assertTransition, cancelEveSession, conversationBinding, conversationBindingForSession, conversationHeaders, createDelegateAgentTool, createEveTranscriptMaterializer, createFactoryHooks, createInstallDependenciesTool, createInvokeAgentTool, createTaskTools, createTranscriptSnapshotHook, defaultExecutionPolicy, defineFactory, deriveFactoryId, ensureConfiguredRepository, executionPolicySchema, factoryConfigRepositorySchema, factoryConfigSchema, factoryIdSchema, factoryReplyAddressAttribute, factoryReplyAddressHeader, factoryReplyChannelAttribute, factoryReplyChannelHeader, factoryRepositoryIdsAttribute, factoryRepositoryIdsHeader, factoryTaskAttemptAttribute, factoryTaskAttemptHeader, factoryTaskAttribute, factoryTaskHeader, forwardEveSessionRequest, hasFactoryTaskBinding, isTransitionAllowed, isoDateTimeSchema, legalNextStates, optionalTaskIdForSession, readCompletedEveTurn, redactedErrorDetails, redactedErrorMessage, replyToForSession, repositoryIdForSlug, repositoryIdSchema, repositoryIdsForSession, repositoryIdsSchema, repositorySchema, repositoryScope, repositorySlugSchema, requireTaskExecution, requireTaskIdForSession, schemaVersionSchema, startEveSession, taskBindingForSession, taskHeaders, taskSessionAuth, terminalStates, usdSchema, vocabularySchema, withFactoryTask, withOptionalFactoryTask };
|
|
@@ -70,6 +70,7 @@ declare const factoryConfigSchema: z.ZodReadonly<z.ZodObject<{
|
|
|
70
70
|
connect: "connect";
|
|
71
71
|
vercel: "vercel";
|
|
72
72
|
}>>;
|
|
73
|
+
enableGitHubPullRequestDiscovery: z.ZodDefault<z.ZodBoolean>;
|
|
73
74
|
sandboxGitHubActAs: z.ZodOptional<z.ZodString>;
|
|
74
75
|
commitIdentity: z.ZodDefault<z.ZodReadonly<z.ZodObject<{
|
|
75
76
|
name: z.ZodString;
|
|
@@ -53,6 +53,7 @@ const factoryConfigSchema = z.strictObject({
|
|
|
53
53
|
repositories: z.array(factoryConfigRepositorySchema).min(1).readonly().describe("Repositories this factory is allowed to inspect and change."),
|
|
54
54
|
operators: z.array(z.string().min(1)).min(1).readonly().describe("Provider-qualified identities allowed to operate this factory."),
|
|
55
55
|
sandboxGitHubCredentials: z.enum(["connect", "vercel"]).default("connect").describe("Credential source selected for trusted GitHub Sandbox operations."),
|
|
56
|
+
enableGitHubPullRequestDiscovery: z.boolean().default(true).describe("Check live GitHub pull requests before admitting repository work."),
|
|
56
57
|
sandboxGitHubActAs: z.string().regex(/^[1-9][0-9]*$/u).max(16).refine((id) => Number.isSafeInteger(Number(id))).optional().describe("GitHub user ID selected for Vercel-managed GitHub API Sandbox operations."),
|
|
57
58
|
commitIdentity: commitIdentitySchema.default({
|
|
58
59
|
name: "Vercel Factory",
|
|
@@ -85,6 +86,11 @@ const factoryConfigSchema = z.strictObject({
|
|
|
85
86
|
path: ["sandboxGitHubActAs"],
|
|
86
87
|
message: "A managed GitHub act-as identity requires Vercel-managed Sandbox credentials."
|
|
87
88
|
});
|
|
89
|
+
if (!config.enableGitHubPullRequestDiscovery && config.sandboxGitHubCredentials !== "vercel") ctx.addIssue({
|
|
90
|
+
code: "custom",
|
|
91
|
+
path: ["enableGitHubPullRequestDiscovery"],
|
|
92
|
+
message: "Skipping GitHub pull request discovery requires managed GitHub credentials."
|
|
93
|
+
});
|
|
88
94
|
}).readonly();
|
|
89
95
|
/** Validates a Factory configuration and applies its declared defaults. */
|
|
90
96
|
function defineFactory(config) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory-config.mjs","names":[],"sources":["../../src/schema/factory-config.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { allowByDefaultApprovalPolicy, approvalPolicySchema } from \"../approval\";\nimport { budgetLimitsSchema } from \"./budget\";\nimport { repositorySlugSchema, type RepositorySlug } from \"./repository\";\nimport { taskKindSchema } from \"./task\";\nimport { slackPrNotificationsSchema } from \"./slack-pr-notifications\";\n\n/** Validates one repository allowlist entry and its configured dependency ordering. */\nexport const factoryConfigRepositorySchema = z\n .strictObject({\n slug: repositorySlugSchema.describe(\"Repository this factory may work on.\"),\n defaultBranch: z\n .string()\n .min(1)\n .default(\"main\")\n .describe(\"Branch changes land on, usually main.\"),\n dependsOn: z\n .array(repositorySlugSchema)\n .readonly()\n .default([])\n .describe(\"Repositories that must land changes before this repository.\"),\n })\n .readonly();\n\nconst githubConnectorSchema = z\n .strictObject({\n owner: z\n .string()\n .regex(/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/)\n .describe(\"GitHub user or organization that owns this connector installation.\"),\n connector: z\n .string()\n .regex(/^github\\/[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/)\n .describe(\"Vercel Connect UID installed for this GitHub owner.\"),\n })\n .readonly();\n\nconst slackConnectorSchema = z\n .strictObject({\n connector: z\n .string()\n .regex(/^slack\\/[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/)\n .describe(\"Vercel Connect UID installed for this Slack app.\"),\n operatorDirectMessages: z\n .boolean()\n .default(false)\n .describe(\n \"Opt in to operator DMs for human-action requests, digests, and budget alerts. Independent of operator authorization, PR announcements, and replies in the original conversation.\",\n ),\n approvalChannel: z\n .string()\n .regex(/^[CDG][A-Z0-9]+$/u)\n .optional()\n .describe(\n \"Explicit Slack channel for approval proposals that have no originating Slack thread. Absent disables cross-channel approval notifications.\",\n ),\n autonomousPullRequests: slackPrNotificationsSchema\n .optional()\n .describe(\n \"Channel announcements for every Factory-produced PR, including human-requested work, in addition to the original conversation reply. Saved Slack settings override this default; resetting restores it. Absent disables announcements unless Slack enables them.\",\n ),\n autonomousPullRequestsByRepository: z\n .record(repositorySlugSchema, slackPrNotificationsSchema.nullable())\n .readonly()\n .optional()\n .describe(\n \"Repository-specific PR destinations, taking precedence over the factory-wide default. Null disables announcements for that repository. Saved repository settings override these entries.\",\n ),\n })\n .readonly();\n\nfunction isSupportedCommitIdentityValue(value: string, maxBytes: number): boolean {\n return (\n value.trim() === value &&\n !/[\\0\\r\\n<>]/u.test(value) &&\n new TextEncoder().encode(value).byteLength <= maxBytes\n );\n}\n\nconst commitIdentitySchema = z\n .strictObject({\n name: z\n .string()\n .min(1)\n .refine((value) => isSupportedCommitIdentityValue(value, 256))\n .describe(\"Name recorded on Factory-authored commits.\"),\n email: z\n .string()\n .email()\n .refine((value) => isSupportedCommitIdentityValue(value, 320))\n .describe(\"Email recorded on Factory-authored commits.\"),\n })\n .readonly();\n\n/** Validates stall detection and replacement-attempt limits for running Tasks. */\nexport const executionPolicySchema = z\n .strictObject({\n maxAttempts: z\n .number()\n .int()\n .min(1)\n .describe(\"Attempts a task may consume before a stall fails it for good.\"),\n stallMinutes: z\n .number()\n .int()\n .min(1)\n .describe(\"Minutes without progress before a running task is marked stalled.\"),\n stallMinutesByKind: z\n .partialRecord(taskKindSchema, z.number().int().min(1))\n .readonly()\n .default({})\n .describe(\"Per-kind stall windows for long single-step work, e.g. security_scan: 300.\"),\n })\n .readonly();\n\n/** Stall detection and retry limits applied to running Tasks. */\nexport type ExecutionPolicy = z.infer<typeof executionPolicySchema>;\n\n/** Conservative default stall window and three-attempt execution policy. */\nexport const defaultExecutionPolicy = executionPolicySchema.parse({\n maxAttempts: 3,\n stallMinutes: 45,\n stallMinutesByKind: {},\n});\n\n/** Validates the repositories, operators, connectors, budget, and policies of a Factory. */\nexport const factoryConfigSchema = z\n .strictObject({\n connectors: z\n .strictObject({\n github: z\n .array(githubConnectorSchema)\n .readonly()\n .refine(\n (connectors) =>\n new Set(connectors.map(({ owner }) => owner)).size === connectors.length,\n \"GitHub connector owners must be unique.\",\n ),\n slack: z\n .array(slackConnectorSchema)\n .max(1, \"A factory may configure one Slack connector.\")\n .readonly()\n .default([]),\n })\n .readonly()\n .default({ github: [], slack: [] })\n .describe(\"Provider connections available to this factory.\"),\n repositories: z\n .array(factoryConfigRepositorySchema)\n .min(1)\n .readonly()\n .describe(\"Repositories this factory is allowed to inspect and change.\"),\n operators: z\n .array(z.string().min(1))\n .min(1)\n .readonly()\n .describe(\"Provider-qualified identities allowed to operate this factory.\"),\n sandboxGitHubCredentials: z\n .enum([\"connect\", \"vercel\"])\n .default(\"connect\")\n .describe(\"Credential source selected for trusted GitHub Sandbox operations.\"),\n sandboxGitHubActAs: z\n .string()\n .regex(/^[1-9][0-9]*$/u)\n .max(16)\n .refine((id) => Number.isSafeInteger(Number(id)))\n .optional()\n .describe(\"GitHub user ID selected for Vercel-managed GitHub API Sandbox operations.\"),\n commitIdentity: commitIdentitySchema\n .default({ name: \"Vercel Factory\", email: \"not+real@example.org\" })\n .describe(\"Replaceable attribution metadata for Factory-authored commits.\"),\n approval: approvalPolicySchema\n .default(allowByDefaultApprovalPolicy)\n .describe(\"Which Tasks wait for a human before they run. First match wins.\"),\n budget: budgetLimitsSchema\n .default({\n totalUsd: 500,\n investigationUsd: 100,\n perTaskReservationUsd: 5,\n warningPercent: 80,\n })\n .describe(\"Monthly spending thresholds and per-task accounting reservations.\"),\n enforceBudget: z\n .boolean()\n .default(true)\n .describe(\n \"Refuse new work above the monthly budget. False keeps cost accounting without spending limits or budget alerts.\",\n ),\n execution: executionPolicySchema\n .default(defaultExecutionPolicy)\n .describe(\"Stall detection and retry limits for running tasks.\"),\n })\n .superRefine((config, ctx) => {\n const repositories = new Set<string>(config.repositories.map(({ slug }) => slug));\n for (const [index, connector] of config.connectors.slack.entries()) {\n for (const slug of Object.keys(connector.autonomousPullRequestsByRepository ?? {})) {\n if (!repositories.has(slug))\n ctx.addIssue({\n code: \"custom\",\n path: [\"connectors\", \"slack\", index, \"autonomousPullRequestsByRepository\", slug],\n message: \"PR notification destinations must name a configured repository.\",\n });\n }\n }\n if (config.sandboxGitHubActAs !== undefined && config.sandboxGitHubCredentials !== \"vercel\") {\n ctx.addIssue({\n code: \"custom\",\n path: [\"sandboxGitHubActAs\"],\n message: \"A managed GitHub act-as identity requires Vercel-managed Sandbox credentials.\",\n });\n }\n })\n .readonly();\n\n/** Fully resolved repository allowlist entry in a Factory configuration. */\nexport type FactoryConfigRepository = z.infer<typeof factoryConfigRepositorySchema>;\n\n/** Fully resolved and validated Factory configuration. */\nexport type FactoryConfig = z.infer<typeof factoryConfigSchema>;\ntype FactoryConfigSchemaInput = z.input<typeof factoryConfigSchema>;\ntype FactoryConfigConnectorsInput = Readonly<{\n github: readonly Readonly<z.input<typeof githubConnectorSchema>>[];\n slack?: readonly Readonly<z.input<typeof slackConnectorSchema>>[];\n}>;\n/** Repository allowlist entry accepted when defining a Factory, before defaults are applied. */\nexport type FactoryConfigRepositoryInput = Readonly<{\n slug: RepositorySlug;\n defaultBranch?: string;\n dependsOn?: readonly RepositorySlug[];\n}>;\n/** Caller input accepted when defining a Factory, before schema defaults are applied. */\nexport type FactoryConfigInput = Readonly<\n Omit<FactoryConfigSchemaInput, \"connectors\" | \"repositories\" | \"operators\"> & {\n connectors?: FactoryConfigConnectorsInput;\n repositories: readonly FactoryConfigRepositoryInput[];\n operators: readonly string[];\n }\n>;\n\n/** Validates a Factory configuration and applies its declared defaults. */\nexport function defineFactory(config: FactoryConfigInput): FactoryConfig {\n return factoryConfigSchema.parse(config);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"factory-config.mjs","names":[],"sources":["../../src/schema/factory-config.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { allowByDefaultApprovalPolicy, approvalPolicySchema } from \"../approval\";\nimport { budgetLimitsSchema } from \"./budget\";\nimport { repositorySlugSchema, type RepositorySlug } from \"./repository\";\nimport { taskKindSchema } from \"./task\";\nimport { slackPrNotificationsSchema } from \"./slack-pr-notifications\";\n\n/** Validates one repository allowlist entry and its configured dependency ordering. */\nexport const factoryConfigRepositorySchema = z\n .strictObject({\n slug: repositorySlugSchema.describe(\"Repository this factory may work on.\"),\n defaultBranch: z\n .string()\n .min(1)\n .default(\"main\")\n .describe(\"Branch changes land on, usually main.\"),\n dependsOn: z\n .array(repositorySlugSchema)\n .readonly()\n .default([])\n .describe(\"Repositories that must land changes before this repository.\"),\n })\n .readonly();\n\nconst githubConnectorSchema = z\n .strictObject({\n owner: z\n .string()\n .regex(/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/)\n .describe(\"GitHub user or organization that owns this connector installation.\"),\n connector: z\n .string()\n .regex(/^github\\/[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/)\n .describe(\"Vercel Connect UID installed for this GitHub owner.\"),\n })\n .readonly();\n\nconst slackConnectorSchema = z\n .strictObject({\n connector: z\n .string()\n .regex(/^slack\\/[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/)\n .describe(\"Vercel Connect UID installed for this Slack app.\"),\n operatorDirectMessages: z\n .boolean()\n .default(false)\n .describe(\n \"Opt in to operator DMs for human-action requests, digests, and budget alerts. Independent of operator authorization, PR announcements, and replies in the original conversation.\",\n ),\n approvalChannel: z\n .string()\n .regex(/^[CDG][A-Z0-9]+$/u)\n .optional()\n .describe(\n \"Explicit Slack channel for approval proposals that have no originating Slack thread. Absent disables cross-channel approval notifications.\",\n ),\n autonomousPullRequests: slackPrNotificationsSchema\n .optional()\n .describe(\n \"Channel announcements for every Factory-produced PR, including human-requested work, in addition to the original conversation reply. Saved Slack settings override this default; resetting restores it. Absent disables announcements unless Slack enables them.\",\n ),\n autonomousPullRequestsByRepository: z\n .record(repositorySlugSchema, slackPrNotificationsSchema.nullable())\n .readonly()\n .optional()\n .describe(\n \"Repository-specific PR destinations, taking precedence over the factory-wide default. Null disables announcements for that repository. Saved repository settings override these entries.\",\n ),\n })\n .readonly();\n\nfunction isSupportedCommitIdentityValue(value: string, maxBytes: number): boolean {\n return (\n value.trim() === value &&\n !/[\\0\\r\\n<>]/u.test(value) &&\n new TextEncoder().encode(value).byteLength <= maxBytes\n );\n}\n\nconst commitIdentitySchema = z\n .strictObject({\n name: z\n .string()\n .min(1)\n .refine((value) => isSupportedCommitIdentityValue(value, 256))\n .describe(\"Name recorded on Factory-authored commits.\"),\n email: z\n .string()\n .email()\n .refine((value) => isSupportedCommitIdentityValue(value, 320))\n .describe(\"Email recorded on Factory-authored commits.\"),\n })\n .readonly();\n\n/** Validates stall detection and replacement-attempt limits for running Tasks. */\nexport const executionPolicySchema = z\n .strictObject({\n maxAttempts: z\n .number()\n .int()\n .min(1)\n .describe(\"Attempts a task may consume before a stall fails it for good.\"),\n stallMinutes: z\n .number()\n .int()\n .min(1)\n .describe(\"Minutes without progress before a running task is marked stalled.\"),\n stallMinutesByKind: z\n .partialRecord(taskKindSchema, z.number().int().min(1))\n .readonly()\n .default({})\n .describe(\"Per-kind stall windows for long single-step work, e.g. security_scan: 300.\"),\n })\n .readonly();\n\n/** Stall detection and retry limits applied to running Tasks. */\nexport type ExecutionPolicy = z.infer<typeof executionPolicySchema>;\n\n/** Conservative default stall window and three-attempt execution policy. */\nexport const defaultExecutionPolicy = executionPolicySchema.parse({\n maxAttempts: 3,\n stallMinutes: 45,\n stallMinutesByKind: {},\n});\n\n/** Validates the repositories, operators, connectors, budget, and policies of a Factory. */\nexport const factoryConfigSchema = z\n .strictObject({\n connectors: z\n .strictObject({\n github: z\n .array(githubConnectorSchema)\n .readonly()\n .refine(\n (connectors) =>\n new Set(connectors.map(({ owner }) => owner)).size === connectors.length,\n \"GitHub connector owners must be unique.\",\n ),\n slack: z\n .array(slackConnectorSchema)\n .max(1, \"A factory may configure one Slack connector.\")\n .readonly()\n .default([]),\n })\n .readonly()\n .default({ github: [], slack: [] })\n .describe(\"Provider connections available to this factory.\"),\n repositories: z\n .array(factoryConfigRepositorySchema)\n .min(1)\n .readonly()\n .describe(\"Repositories this factory is allowed to inspect and change.\"),\n operators: z\n .array(z.string().min(1))\n .min(1)\n .readonly()\n .describe(\"Provider-qualified identities allowed to operate this factory.\"),\n sandboxGitHubCredentials: z\n .enum([\"connect\", \"vercel\"])\n .default(\"connect\")\n .describe(\"Credential source selected for trusted GitHub Sandbox operations.\"),\n enableGitHubPullRequestDiscovery: z\n .boolean()\n .default(true)\n .describe(\"Check live GitHub pull requests before admitting repository work.\"),\n sandboxGitHubActAs: z\n .string()\n .regex(/^[1-9][0-9]*$/u)\n .max(16)\n .refine((id) => Number.isSafeInteger(Number(id)))\n .optional()\n .describe(\"GitHub user ID selected for Vercel-managed GitHub API Sandbox operations.\"),\n commitIdentity: commitIdentitySchema\n .default({ name: \"Vercel Factory\", email: \"not+real@example.org\" })\n .describe(\"Replaceable attribution metadata for Factory-authored commits.\"),\n approval: approvalPolicySchema\n .default(allowByDefaultApprovalPolicy)\n .describe(\"Which Tasks wait for a human before they run. First match wins.\"),\n budget: budgetLimitsSchema\n .default({\n totalUsd: 500,\n investigationUsd: 100,\n perTaskReservationUsd: 5,\n warningPercent: 80,\n })\n .describe(\"Monthly spending thresholds and per-task accounting reservations.\"),\n enforceBudget: z\n .boolean()\n .default(true)\n .describe(\n \"Refuse new work above the monthly budget. False keeps cost accounting without spending limits or budget alerts.\",\n ),\n execution: executionPolicySchema\n .default(defaultExecutionPolicy)\n .describe(\"Stall detection and retry limits for running tasks.\"),\n })\n .superRefine((config, ctx) => {\n const repositories = new Set<string>(config.repositories.map(({ slug }) => slug));\n for (const [index, connector] of config.connectors.slack.entries()) {\n for (const slug of Object.keys(connector.autonomousPullRequestsByRepository ?? {})) {\n if (!repositories.has(slug))\n ctx.addIssue({\n code: \"custom\",\n path: [\"connectors\", \"slack\", index, \"autonomousPullRequestsByRepository\", slug],\n message: \"PR notification destinations must name a configured repository.\",\n });\n }\n }\n if (config.sandboxGitHubActAs !== undefined && config.sandboxGitHubCredentials !== \"vercel\") {\n ctx.addIssue({\n code: \"custom\",\n path: [\"sandboxGitHubActAs\"],\n message: \"A managed GitHub act-as identity requires Vercel-managed Sandbox credentials.\",\n });\n }\n if (!config.enableGitHubPullRequestDiscovery && config.sandboxGitHubCredentials !== \"vercel\") {\n ctx.addIssue({\n code: \"custom\",\n path: [\"enableGitHubPullRequestDiscovery\"],\n message: \"Skipping GitHub pull request discovery requires managed GitHub credentials.\",\n });\n }\n })\n .readonly();\n\n/** Fully resolved repository allowlist entry in a Factory configuration. */\nexport type FactoryConfigRepository = z.infer<typeof factoryConfigRepositorySchema>;\n\n/** Fully resolved and validated Factory configuration. */\nexport type FactoryConfig = z.infer<typeof factoryConfigSchema>;\ntype FactoryConfigSchemaInput = z.input<typeof factoryConfigSchema>;\ntype FactoryConfigConnectorsInput = Readonly<{\n github: readonly Readonly<z.input<typeof githubConnectorSchema>>[];\n slack?: readonly Readonly<z.input<typeof slackConnectorSchema>>[];\n}>;\n/** Repository allowlist entry accepted when defining a Factory, before defaults are applied. */\nexport type FactoryConfigRepositoryInput = Readonly<{\n slug: RepositorySlug;\n defaultBranch?: string;\n dependsOn?: readonly RepositorySlug[];\n}>;\n/** Caller input accepted when defining a Factory, before schema defaults are applied. */\nexport type FactoryConfigInput = Readonly<\n Omit<FactoryConfigSchemaInput, \"connectors\" | \"repositories\" | \"operators\"> & {\n connectors?: FactoryConfigConnectorsInput;\n repositories: readonly FactoryConfigRepositoryInput[];\n operators: readonly string[];\n }\n>;\n\n/** Validates a Factory configuration and applies its declared defaults. */\nexport function defineFactory(config: FactoryConfigInput): FactoryConfig {\n return factoryConfigSchema.parse(config);\n}\n"],"mappings":"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"}
|
package/dist/vercel-git.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { Readable } from "node:stream";
|
|
|
6
6
|
//#region src/vercel-git.ts
|
|
7
7
|
const gitOidPattern = /^[a-f0-9]{40}$/u;
|
|
8
8
|
const absentGitOid = "0".repeat(40);
|
|
9
|
-
const managedGitTimeoutMs =
|
|
9
|
+
const managedGitTimeoutMs = 6e5;
|
|
10
10
|
async function createManagedSandbox(input) {
|
|
11
11
|
const { Sandbox } = await import("@vercel/sandbox");
|
|
12
12
|
return Sandbox.create({ ...input });
|
|
@@ -18,7 +18,7 @@ function publicationBase(input) {
|
|
|
18
18
|
async function trustedCommand(sandbox, input) {
|
|
19
19
|
const result = await sandbox.runCommand({
|
|
20
20
|
...input,
|
|
21
|
-
timeoutMs:
|
|
21
|
+
timeoutMs: managedGitTimeoutMs
|
|
22
22
|
});
|
|
23
23
|
if (result.exitCode !== 0) throw new Error(`Trusted Git operation failed (${result.exitCode})`);
|
|
24
24
|
return (await result.stdout()).trim();
|
package/dist/vercel-git.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vercel-git.mjs","names":[],"sources":["../src/vercel-git.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { Readable } from \"node:stream\";\nimport type { Sandbox, NetworkPolicy } from \"@vercel/sandbox\";\nimport { repositorySlugSchema, type RepositorySlug } from \"./schema/repository\";\nimport { isSafeGitRef } from \"./github-publication\";\nimport {\n maxGitBundleBytes,\n withSandboxGitBundle,\n type GitHubGitTransport,\n type GitHubRepositoryBundle,\n} from \"./github-transfer\";\n\ntype TrustedGitSandbox = Pick<\n Sandbox,\n \"cwd\" | \"runCommand\" | \"writeFiles\" | \"readFile\" | \"updateNetworkPolicy\" | \"stop\"\n>;\n\n/** Exact creation request for a fresh Vercel-managed trusted Git publication Sandbox. */\nexport interface VercelManagedGitHubSandboxCreateInput {\n source: {\n type: \"git\";\n url: string;\n };\n __source_credentials: true;\n __commitAs: {\n name: string;\n email: string;\n };\n networkPolicy: NetworkPolicy;\n persistent: false;\n resources: { vcpus: 6 };\n runtime: \"node24\";\n timeout: number;\n}\n\nconst gitOidPattern = /^[a-f0-9]{40}$/u;\nconst absentGitOid = \"0\".repeat(40);\nconst managedGitTimeoutMs = 180_000;\n\n/** Repository and access level used to mint one trusted Git transport token. */\nexport interface VercelGitHubTokenInput {\n repository: RepositorySlug;\n access: \"read\" | \"write\";\n}\n\n/** Configures fresh trusted sandboxes and repository-scoped Git tokens. */\nexport interface VercelGitHubGitTransportOptions {\n /** Reads must mint contents:read credentials; writes are confined to the supplied repository. */\n token(input: VercelGitHubTokenInput): Promise<string>;\n /** Creates a fresh sandbox, without a snapshot, repository, or running agent. */\n createSandbox?: () => Promise<TrustedGitSandbox>;\n}\n\n/** Configures Vercel-managed Git reads and signed publication. */\nexport interface VercelManagedGitHubGitTransportOptions {\n /** Attribution metadata supplied for managed signed commits. */\n commitIdentity: { name: string; email: string };\n /** Creates one unnamed Sandbox from the exact managed credential request. */\n createSandbox?: (input: VercelManagedGitHubSandboxCreateInput) => Promise<TrustedGitSandbox>;\n}\n\nasync function createManagedSandbox(\n input: VercelManagedGitHubSandboxCreateInput,\n): Promise<TrustedGitSandbox> {\n const { Sandbox } = await import(\"@vercel/sandbox\");\n return Sandbox.create({ ...input });\n}\n\nfunction publicationBase(input: {\n branch: string;\n head: string;\n expectedHead: string;\n baseHead?: string;\n}): string {\n if (\n !isSafeGitRef(input.branch) ||\n !gitOidPattern.test(input.head) ||\n !gitOidPattern.test(input.expectedHead) ||\n (input.expectedHead === absentGitOid\n ? input.baseHead === undefined || !gitOidPattern.test(input.baseHead)\n : input.baseHead !== undefined)\n ) {\n throw new Error(\"Invalid Git publication target\");\n }\n return input.expectedHead === absentGitOid ? input.baseHead! : input.expectedHead;\n}\n\nasync function trustedCommand(\n sandbox: TrustedGitSandbox,\n input: { cmd: string; args: string[]; cwd?: string },\n): Promise<string> {\n const result = await sandbox.runCommand({ ...input, timeoutMs: 120_000 });\n if (result.exitCode !== 0) throw new Error(`Trusted Git operation failed (${result.exitCode})`);\n return (await result.stdout()).trim();\n}\n\nfunction bundleTransferSandbox(sandbox: TrustedGitSandbox) {\n return {\n async run({ command }: { command: string }) {\n const result = await sandbox.runCommand({ cmd: \"sh\", args: [\"-c\", command] });\n return { exitCode: result.exitCode, stdout: await result.stdout() };\n },\n async writeBinaryFile({ path, content }: { path: string; content: Uint8Array }) {\n await sandbox.writeFiles([\n {\n path,\n content: Buffer.from(content.buffer, content.byteOffset, content.byteLength),\n },\n ]);\n },\n };\n}\n\nasync function consumeSandboxBundle(\n sandbox: TrustedGitSandbox,\n path: string,\n use: (bundle: GitHubRepositoryBundle) => Promise<void>,\n): Promise<void> {\n const size = await sandbox.runCommand({ cmd: \"stat\", args: [\"-c\", \"%s\", path] });\n const byteLength = Number(await size.stdout());\n if (\n size.exitCode !== 0 ||\n !Number.isSafeInteger(byteLength) ||\n byteLength < 1 ||\n byteLength > maxGitBundleBytes\n ) {\n throw new Error(\"Git bundle exceeds transfer limit\");\n }\n const source = await sandbox.readFile({ path });\n if (!source) throw new Error(\"Git bundle was not created\");\n const readable =\n source instanceof Readable ? source : new Readable({ highWaterMark: 64 * 1024 }).wrap(source);\n const iterator = readable[Symbol.asyncIterator]();\n const stream = new ReadableStream<Uint8Array>(\n {\n async pull(controller) {\n const { done, value } = await iterator.next();\n if (done) controller.close();\n else controller.enqueue(value);\n },\n async cancel() {\n await iterator.return?.();\n },\n },\n { highWaterMark: 0 },\n );\n try {\n await use({ stream });\n } finally {\n readable.destroy();\n if (!stream.locked) await stream.cancel();\n }\n}\n\nfunction credentialPolicy(repository: RepositorySlug, token: string): NetworkPolicy {\n return {\n allow: {\n \"github.com\": [\n {\n match: { path: { startsWith: `/${repository}.git/` } },\n transform: [\n {\n headers: {\n authorization: `Basic ${Buffer.from(`x-access-token:${token}`).toString(\"base64\")}`,\n },\n },\n ],\n },\n ],\n },\n };\n}\n\n/** Transfer Git objects through fresh trusted sandboxes; credentials never reach an agent sandbox. */\nexport function createVercelGitHubGitTransport(\n options: VercelGitHubGitTransportOptions,\n): GitHubGitTransport {\n async function isolated<T>(\n repository: RepositorySlug,\n use: (\n sandbox: Awaited<ReturnType<NonNullable<VercelGitHubGitTransportOptions[\"createSandbox\"]>>>,\n git: (...args: string[]) => Promise<string>,\n repository: RepositorySlug,\n ) => Promise<T>,\n ): Promise<T> {\n const slug = repositorySlugSchema.parse(repository.toLowerCase());\n const sandbox = options.createSandbox\n ? await options.createSandbox()\n : await (\n await import(\"@vercel/sandbox\")\n ).Sandbox.create({ runtime: \"node24\", timeout: 180_000, networkPolicy: \"deny-all\" });\n async function git(...args: string[]): Promise<string> {\n const result = await sandbox.runCommand({\n cmd: \"/usr/bin/env\",\n args: [\n \"-i\",\n \"PATH=/usr/bin:/bin\",\n \"HOME=/tmp\",\n \"GIT_CONFIG_NOSYSTEM=1\",\n \"GIT_CONFIG_GLOBAL=/dev/null\",\n \"GIT_TERMINAL_PROMPT=0\",\n \"git\",\n \"-c\",\n \"core.hooksPath=/dev/null\",\n \"-c\",\n \"credential.helper=\",\n \"-c\",\n \"protocol.ext.allow=never\",\n \"-c\",\n \"protocol.file.allow=always\",\n \"-c\",\n \"http.followRedirects=false\",\n ...args,\n ],\n timeoutMs: 120_000,\n });\n if (result.exitCode !== 0)\n throw new Error(`Trusted Git operation failed (${result.exitCode})`);\n return (await result.stdout()).trim();\n }\n try {\n await sandbox.updateNetworkPolicy(\"deny-all\");\n return await use(sandbox, git, slug);\n } finally {\n try {\n await sandbox.updateNetworkPolicy(\"deny-all\");\n } finally {\n await sandbox.stop();\n }\n }\n }\n return {\n async fetch(input, use) {\n if (input.refs.some((ref) => !ref.startsWith(\"refs/\") || !isSafeGitRef(ref)))\n throw new Error(\"Invalid Git fetch ref\");\n return isolated(input.repository, async (sandbox, git, slug) => {\n await sandbox.updateNetworkPolicy(\n credentialPolicy(slug, await options.token({ repository: slug, access: \"read\" })),\n );\n await git(\n \"clone\",\n \"--bare\",\n \"--no-hardlinks\",\n `https://github.com/${slug}.git`,\n \"/tmp/transfer.git\",\n );\n if (input.refs.length)\n await git(\n \"-C\",\n \"/tmp/transfer.git\",\n \"fetch\",\n \"--no-tags\",\n \"origin\",\n ...input.refs.map((ref) => `+${ref}:${ref}`),\n );\n await sandbox.updateNetworkPolicy(\"deny-all\");\n await git(\"-C\", \"/tmp/transfer.git\", \"bundle\", \"create\", \"/tmp/transfer.bundle\", \"--all\");\n await consumeSandboxBundle(sandbox, \"/tmp/transfer.bundle\", use);\n });\n },\n async push(input) {\n try {\n const baseHead = publicationBase(input);\n return await isolated(input.repository, async (sandbox, git, slug) => {\n await git(\"init\", \"--bare\", \"/tmp/transfer.git\");\n return await withSandboxGitBundle({\n sandbox: bundleTransferSandbox(sandbox),\n bundle: input.bundle,\n async use(path) {\n await git(\"-C\", \"/tmp/transfer.git\", \"bundle\", \"verify\", path);\n await git(\"-C\", \"/tmp/transfer.git\", \"fetch\", path, \"HEAD:refs/heads/candidate\");\n if (\n (await git(\"-C\", \"/tmp/transfer.git\", \"rev-parse\", \"refs/heads/candidate\")) !==\n input.head\n )\n throw new Error(\"Git bundle head does not match publication target\");\n await git(\n \"-C\",\n \"/tmp/transfer.git\",\n \"merge-base\",\n \"--is-ancestor\",\n baseHead,\n \"refs/heads/candidate\",\n );\n await sandbox.updateNetworkPolicy(\n credentialPolicy(slug, await options.token({ repository: slug, access: \"write\" })),\n );\n await input.authorize();\n await git(\n \"-C\",\n \"/tmp/transfer.git\",\n \"push\",\n `--force-with-lease=refs/heads/${input.branch}:${input.expectedHead}`,\n `https://github.com/${slug}.git`,\n `${input.head}:refs/heads/${input.branch}`,\n );\n return input.head;\n },\n });\n });\n } finally {\n if (!input.bundle.stream.locked) await input.bundle.stream.cancel();\n }\n },\n };\n}\n\n/** Use fresh Vercel-managed trusted Sandboxes for Git reads and signed publication. */\nexport function createVercelManagedGitHubGitTransport(\n options: VercelManagedGitHubGitTransportOptions,\n): GitHubGitTransport {\n const createSandbox = options.createSandbox ?? createManagedSandbox;\n const createInput = (\n repository: RepositorySlug,\n access: \"read\" | \"write\",\n ): VercelManagedGitHubSandboxCreateInput => ({\n source: {\n type: \"git\",\n url: `https://github.com/${repository}.git`,\n },\n __source_credentials: true,\n __commitAs: options.commitIdentity,\n networkPolicy: { allow: access === \"read\" ? [\"github.com\"] : [\"github.com\", \"api.github.com\"] },\n persistent: false,\n resources: { vcpus: 6 },\n runtime: \"node24\",\n timeout: managedGitTimeoutMs,\n });\n return {\n async fetch(input, use) {\n if (input.refs.some((ref) => !ref.startsWith(\"refs/\") || !isSafeGitRef(ref))) {\n throw new Error(\"Invalid Git fetch ref\");\n }\n const repository = repositorySlugSchema.parse(input.repository.toLowerCase());\n const sandbox = await createSandbox(createInput(repository, \"read\"));\n const repositoryPath = sandbox.cwd;\n try {\n await trustedCommand(sandbox, {\n cmd: \"/usr/bin/git\",\n args: [\"clone\", \"--bare\", \"--no-local\", repositoryPath, \"/tmp/transfer.git\"],\n });\n if (input.refs.length > 0) {\n await trustedCommand(sandbox, {\n cmd: \"git\",\n args: [\n \"-C\",\n \"/tmp/transfer.git\",\n \"fetch\",\n \"--no-tags\",\n `https://github.com/${repository}.git`,\n ...input.refs.map((ref) => `+${ref}:${ref}`),\n ],\n });\n }\n await trustedCommand(sandbox, {\n cmd: \"/usr/bin/git\",\n args: [\"-C\", \"/tmp/transfer.git\", \"bundle\", \"create\", \"/tmp/transfer.bundle\", \"--all\"],\n });\n await consumeSandboxBundle(sandbox, \"/tmp/transfer.bundle\", use);\n } finally {\n await sandbox.stop();\n }\n },\n async push(input) {\n try {\n const baseHead = publicationBase(input);\n if (input.expectedHead === absentGitOid) {\n throw new Error(\"Vercel-managed signed publication requires an existing remote branch\");\n }\n const repository = repositorySlugSchema.parse(input.repository.toLowerCase());\n const sandbox = await createSandbox(createInput(repository, \"write\"));\n const repositoryPath = sandbox.cwd;\n try {\n const hooksPath = `/tmp/factory-hooks-${randomUUID()}`;\n await trustedCommand(sandbox, { cmd: \"mkdir\", args: [\"-m\", \"700\", hooksPath] });\n await trustedCommand(sandbox, {\n cmd: \"/usr/bin/git\",\n args: [\"config\", \"core.hooksPath\", hooksPath],\n cwd: repositoryPath,\n });\n await withSandboxGitBundle({\n sandbox: bundleTransferSandbox(sandbox),\n bundle: input.bundle,\n async use(path) {\n await trustedCommand(sandbox, {\n cmd: \"/usr/bin/git\",\n args: [\"bundle\", \"verify\", path],\n cwd: repositoryPath,\n });\n await trustedCommand(sandbox, {\n cmd: \"/usr/bin/git\",\n args: [\"fetch\", \"--no-tags\", path, `${input.head}:refs/factory/candidate`],\n cwd: repositoryPath,\n });\n if (\n (await trustedCommand(sandbox, {\n cmd: \"/usr/bin/git\",\n args: [\"rev-parse\", \"refs/factory/candidate\"],\n cwd: repositoryPath,\n })) !== input.head\n ) {\n throw new Error(\"Git bundle head does not match publication target\");\n }\n await trustedCommand(sandbox, {\n cmd: \"/usr/bin/git\",\n args: [\"merge-base\", \"--is-ancestor\", baseHead, \"refs/factory/candidate\"],\n cwd: repositoryPath,\n });\n await trustedCommand(sandbox, {\n cmd: \"/usr/bin/git\",\n args: [\"checkout\", \"-B\", input.branch, \"refs/factory/candidate\"],\n cwd: repositoryPath,\n });\n await input.authorize();\n await trustedCommand(sandbox, {\n cmd: \"/usr/bin/env\",\n args: [\n `VERCEL_GIT_EXPECTED_REMOTE_OID=${input.expectedHead}`,\n ...(input.baseHead === undefined\n ? []\n : [`VERCEL_GIT_BASE_OID=${input.baseHead}`]),\n \"git\",\n \"push\",\n \"origin\",\n input.branch,\n ],\n cwd: repositoryPath,\n });\n },\n });\n const signedHead = await trustedCommand(sandbox, {\n cmd: \"/usr/bin/git\",\n args: [\"rev-parse\", \"HEAD\"],\n cwd: repositoryPath,\n });\n if (!gitOidPattern.test(signedHead)) {\n throw new Error(\"Managed signed push returned an invalid Git head\");\n }\n return signedHead;\n } finally {\n await sandbox.stop();\n }\n } finally {\n if (!input.bundle.stream.locked) await input.bundle.stream.cancel();\n }\n },\n };\n}\n"],"mappings":";;;;;;AAmCA,MAAM,gBAAgB;AACtB,MAAM,eAAe,IAAI,OAAO,EAAE;AAClC,MAAM,sBAAsB;AAwB5B,eAAe,qBACb,OAC4B;CAC5B,MAAM,EAAE,YAAY,MAAM,OAAO;CACjC,OAAO,QAAQ,OAAO,EAAE,GAAG,MAAM,CAAC;AACpC;AAEA,SAAS,gBAAgB,OAKd;CACT,IACE,CAAC,aAAa,MAAM,MAAM,KAC1B,CAAC,cAAc,KAAK,MAAM,IAAI,KAC9B,CAAC,cAAc,KAAK,MAAM,YAAY,MACrC,MAAM,iBAAiB,eACpB,MAAM,aAAa,KAAA,KAAa,CAAC,cAAc,KAAK,MAAM,QAAQ,IAClE,MAAM,aAAa,KAAA,IAEvB,MAAM,IAAI,MAAM,gCAAgC;CAElD,OAAO,MAAM,iBAAiB,eAAe,MAAM,WAAY,MAAM;AACvE;AAEA,eAAe,eACb,SACA,OACiB;CACjB,MAAM,SAAS,MAAM,QAAQ,WAAW;EAAE,GAAG;EAAO,WAAW;CAAQ,CAAC;CACxE,IAAI,OAAO,aAAa,GAAG,MAAM,IAAI,MAAM,iCAAiC,OAAO,SAAS,EAAE;CAC9F,QAAQ,MAAM,OAAO,OAAO,EAAA,CAAG,KAAK;AACtC;AAEA,SAAS,sBAAsB,SAA4B;CACzD,OAAO;EACL,MAAM,IAAI,EAAE,WAAgC;GAC1C,MAAM,SAAS,MAAM,QAAQ,WAAW;IAAE,KAAK;IAAM,MAAM,CAAC,MAAM,OAAO;GAAE,CAAC;GAC5E,OAAO;IAAE,UAAU,OAAO;IAAU,QAAQ,MAAM,OAAO,OAAO;GAAE;EACpE;EACA,MAAM,gBAAgB,EAAE,MAAM,WAAkD;GAC9E,MAAM,QAAQ,WAAW,CACvB;IACE;IACA,SAAS,OAAO,KAAK,QAAQ,QAAQ,QAAQ,YAAY,QAAQ,UAAU;GAC7E,CACF,CAAC;EACH;CACF;AACF;AAEA,eAAe,qBACb,SACA,MACA,KACe;CACf,MAAM,OAAO,MAAM,QAAQ,WAAW;EAAE,KAAK;EAAQ,MAAM;GAAC;GAAM;GAAM;EAAI;CAAE,CAAC;CAC/E,MAAM,aAAa,OAAO,MAAM,KAAK,OAAO,CAAC;CAC7C,IACE,KAAK,aAAa,KAClB,CAAC,OAAO,cAAc,UAAU,KAChC,aAAa,KACb,aAAA,WAEA,MAAM,IAAI,MAAM,mCAAmC;CAErD,MAAM,SAAS,MAAM,QAAQ,SAAS,EAAE,KAAK,CAAC;CAC9C,IAAI,CAAC,QAAQ,MAAM,IAAI,MAAM,4BAA4B;CACzD,MAAM,WACJ,kBAAkB,WAAW,SAAS,IAAI,SAAS,EAAE,eAAe,MAAU,CAAC,CAAC,CAAC,KAAK,MAAM;CAC9F,MAAM,WAAW,SAAS,OAAO,cAAc,CAAC;CAChD,MAAM,SAAS,IAAI,eACjB;EACE,MAAM,KAAK,YAAY;GACrB,MAAM,EAAE,MAAM,UAAU,MAAM,SAAS,KAAK;GAC5C,IAAI,MAAM,WAAW,MAAM;QACtB,WAAW,QAAQ,KAAK;EAC/B;EACA,MAAM,SAAS;GACb,MAAM,SAAS,SAAS;EAC1B;CACF,GACA,EAAE,eAAe,EAAE,CACrB;CACA,IAAI;EACF,MAAM,IAAI,EAAE,OAAO,CAAC;CACtB,UAAU;EACR,SAAS,QAAQ;EACjB,IAAI,CAAC,OAAO,QAAQ,MAAM,OAAO,OAAO;CAC1C;AACF;AAEA,SAAS,iBAAiB,YAA4B,OAA8B;CAClF,OAAO,EACL,OAAO,EACL,cAAc,CACZ;EACE,OAAO,EAAE,MAAM,EAAE,YAAY,IAAI,WAAW,OAAO,EAAE;EACrD,WAAW,CACT,EACE,SAAS,EACP,eAAe,SAAS,OAAO,KAAK,kBAAkB,OAAO,CAAC,CAAC,SAAS,QAAQ,IAClF,EACF,CACF;CACF,CACF,EACF,EACF;AACF;;AAGA,SAAgB,+BACd,SACoB;CACpB,eAAe,SACb,YACA,KAKY;EACZ,MAAM,OAAO,qBAAqB,MAAM,WAAW,YAAY,CAAC;EAChE,MAAM,UAAU,QAAQ,gBACpB,MAAM,QAAQ,cAAc,IAC5B,OACE,MAAM,OAAO,mBAAA,CACb,QAAQ,OAAO;GAAE,SAAS;GAAU,SAAS;GAAS,eAAe;EAAW,CAAC;EACvF,eAAe,IAAI,GAAG,MAAiC;GACrD,MAAM,SAAS,MAAM,QAAQ,WAAW;IACtC,KAAK;IACL,MAAM;KACJ;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA,GAAG;IACL;IACA,WAAW;GACb,CAAC;GACD,IAAI,OAAO,aAAa,GACtB,MAAM,IAAI,MAAM,iCAAiC,OAAO,SAAS,EAAE;GACrE,QAAQ,MAAM,OAAO,OAAO,EAAA,CAAG,KAAK;EACtC;EACA,IAAI;GACF,MAAM,QAAQ,oBAAoB,UAAU;GAC5C,OAAO,MAAM,IAAI,SAAS,KAAK,IAAI;EACrC,UAAU;GACR,IAAI;IACF,MAAM,QAAQ,oBAAoB,UAAU;GAC9C,UAAU;IACR,MAAM,QAAQ,KAAK;GACrB;EACF;CACF;CACA,OAAO;EACL,MAAM,MAAM,OAAO,KAAK;GACtB,IAAI,MAAM,KAAK,MAAM,QAAQ,CAAC,IAAI,WAAW,OAAO,KAAK,CAAC,aAAa,GAAG,CAAC,GACzE,MAAM,IAAI,MAAM,uBAAuB;GACzC,OAAO,SAAS,MAAM,YAAY,OAAO,SAAS,KAAK,SAAS;IAC9D,MAAM,QAAQ,oBACZ,iBAAiB,MAAM,MAAM,QAAQ,MAAM;KAAE,YAAY;KAAM,QAAQ;IAAO,CAAC,CAAC,CAClF;IACA,MAAM,IACJ,SACA,UACA,kBACA,sBAAsB,KAAK,OAC3B,mBACF;IACA,IAAI,MAAM,KAAK,QACb,MAAM,IACJ,MACA,qBACA,SACA,aACA,UACA,GAAG,MAAM,KAAK,KAAK,QAAQ,IAAI,IAAI,GAAG,KAAK,CAC7C;IACF,MAAM,QAAQ,oBAAoB,UAAU;IAC5C,MAAM,IAAI,MAAM,qBAAqB,UAAU,UAAU,wBAAwB,OAAO;IACxF,MAAM,qBAAqB,SAAS,wBAAwB,GAAG;GACjE,CAAC;EACH;EACA,MAAM,KAAK,OAAO;GAChB,IAAI;IACF,MAAM,WAAW,gBAAgB,KAAK;IACtC,OAAO,MAAM,SAAS,MAAM,YAAY,OAAO,SAAS,KAAK,SAAS;KACpE,MAAM,IAAI,QAAQ,UAAU,mBAAmB;KAC/C,OAAO,MAAM,qBAAqB;MAChC,SAAS,sBAAsB,OAAO;MACtC,QAAQ,MAAM;MACd,MAAM,IAAI,MAAM;OACd,MAAM,IAAI,MAAM,qBAAqB,UAAU,UAAU,IAAI;OAC7D,MAAM,IAAI,MAAM,qBAAqB,SAAS,MAAM,2BAA2B;OAC/E,IACG,MAAM,IAAI,MAAM,qBAAqB,aAAa,sBAAsB,MACzE,MAAM,MAEN,MAAM,IAAI,MAAM,mDAAmD;OACrE,MAAM,IACJ,MACA,qBACA,cACA,iBACA,UACA,sBACF;OACA,MAAM,QAAQ,oBACZ,iBAAiB,MAAM,MAAM,QAAQ,MAAM;QAAE,YAAY;QAAM,QAAQ;OAAQ,CAAC,CAAC,CACnF;OACA,MAAM,MAAM,UAAU;OACtB,MAAM,IACJ,MACA,qBACA,QACA,iCAAiC,MAAM,OAAO,GAAG,MAAM,gBACvD,sBAAsB,KAAK,OAC3B,GAAG,MAAM,KAAK,cAAc,MAAM,QACpC;OACA,OAAO,MAAM;MACf;KACF,CAAC;IACH,CAAC;GACH,UAAU;IACR,IAAI,CAAC,MAAM,OAAO,OAAO,QAAQ,MAAM,MAAM,OAAO,OAAO,OAAO;GACpE;EACF;CACF;AACF;;AAGA,SAAgB,sCACd,SACoB;CACpB,MAAM,gBAAgB,QAAQ,iBAAiB;CAC/C,MAAM,eACJ,YACA,YAC2C;EAC3C,QAAQ;GACN,MAAM;GACN,KAAK,sBAAsB,WAAW;EACxC;EACA,sBAAsB;EACtB,YAAY,QAAQ;EACpB,eAAe,EAAE,OAAO,WAAW,SAAS,CAAC,YAAY,IAAI,CAAC,cAAc,gBAAgB,EAAE;EAC9F,YAAY;EACZ,WAAW,EAAE,OAAO,EAAE;EACtB,SAAS;EACT,SAAS;CACX;CACA,OAAO;EACL,MAAM,MAAM,OAAO,KAAK;GACtB,IAAI,MAAM,KAAK,MAAM,QAAQ,CAAC,IAAI,WAAW,OAAO,KAAK,CAAC,aAAa,GAAG,CAAC,GACzE,MAAM,IAAI,MAAM,uBAAuB;GAEzC,MAAM,aAAa,qBAAqB,MAAM,MAAM,WAAW,YAAY,CAAC;GAC5E,MAAM,UAAU,MAAM,cAAc,YAAY,YAAY,MAAM,CAAC;GACnE,MAAM,iBAAiB,QAAQ;GAC/B,IAAI;IACF,MAAM,eAAe,SAAS;KAC5B,KAAK;KACL,MAAM;MAAC;MAAS;MAAU;MAAc;MAAgB;KAAmB;IAC7E,CAAC;IACD,IAAI,MAAM,KAAK,SAAS,GACtB,MAAM,eAAe,SAAS;KAC5B,KAAK;KACL,MAAM;MACJ;MACA;MACA;MACA;MACA,sBAAsB,WAAW;MACjC,GAAG,MAAM,KAAK,KAAK,QAAQ,IAAI,IAAI,GAAG,KAAK;KAC7C;IACF,CAAC;IAEH,MAAM,eAAe,SAAS;KAC5B,KAAK;KACL,MAAM;MAAC;MAAM;MAAqB;MAAU;MAAU;MAAwB;KAAO;IACvF,CAAC;IACD,MAAM,qBAAqB,SAAS,wBAAwB,GAAG;GACjE,UAAU;IACR,MAAM,QAAQ,KAAK;GACrB;EACF;EACA,MAAM,KAAK,OAAO;GAChB,IAAI;IACF,MAAM,WAAW,gBAAgB,KAAK;IACtC,IAAI,MAAM,iBAAiB,cACzB,MAAM,IAAI,MAAM,sEAAsE;IAExF,MAAM,aAAa,qBAAqB,MAAM,MAAM,WAAW,YAAY,CAAC;IAC5E,MAAM,UAAU,MAAM,cAAc,YAAY,YAAY,OAAO,CAAC;IACpE,MAAM,iBAAiB,QAAQ;IAC/B,IAAI;KACF,MAAM,YAAY,sBAAsB,WAAW;KACnD,MAAM,eAAe,SAAS;MAAE,KAAK;MAAS,MAAM;OAAC;OAAM;OAAO;MAAS;KAAE,CAAC;KAC9E,MAAM,eAAe,SAAS;MAC5B,KAAK;MACL,MAAM;OAAC;OAAU;OAAkB;MAAS;MAC5C,KAAK;KACP,CAAC;KACD,MAAM,qBAAqB;MACzB,SAAS,sBAAsB,OAAO;MACtC,QAAQ,MAAM;MACd,MAAM,IAAI,MAAM;OACd,MAAM,eAAe,SAAS;QAC5B,KAAK;QACL,MAAM;SAAC;SAAU;SAAU;QAAI;QAC/B,KAAK;OACP,CAAC;OACD,MAAM,eAAe,SAAS;QAC5B,KAAK;QACL,MAAM;SAAC;SAAS;SAAa;SAAM,GAAG,MAAM,KAAK;QAAwB;QACzE,KAAK;OACP,CAAC;OACD,IACG,MAAM,eAAe,SAAS;QAC7B,KAAK;QACL,MAAM,CAAC,aAAa,wBAAwB;QAC5C,KAAK;OACP,CAAC,MAAO,MAAM,MAEd,MAAM,IAAI,MAAM,mDAAmD;OAErE,MAAM,eAAe,SAAS;QAC5B,KAAK;QACL,MAAM;SAAC;SAAc;SAAiB;SAAU;QAAwB;QACxE,KAAK;OACP,CAAC;OACD,MAAM,eAAe,SAAS;QAC5B,KAAK;QACL,MAAM;SAAC;SAAY;SAAM,MAAM;SAAQ;QAAwB;QAC/D,KAAK;OACP,CAAC;OACD,MAAM,MAAM,UAAU;OACtB,MAAM,eAAe,SAAS;QAC5B,KAAK;QACL,MAAM;SACJ,kCAAkC,MAAM;SACxC,GAAI,MAAM,aAAa,KAAA,IACnB,CAAC,IACD,CAAC,uBAAuB,MAAM,UAAU;SAC5C;SACA;SACA;SACA,MAAM;QACR;QACA,KAAK;OACP,CAAC;MACH;KACF,CAAC;KACD,MAAM,aAAa,MAAM,eAAe,SAAS;MAC/C,KAAK;MACL,MAAM,CAAC,aAAa,MAAM;MAC1B,KAAK;KACP,CAAC;KACD,IAAI,CAAC,cAAc,KAAK,UAAU,GAChC,MAAM,IAAI,MAAM,kDAAkD;KAEpE,OAAO;IACT,UAAU;KACR,MAAM,QAAQ,KAAK;IACrB;GACF,UAAU;IACR,IAAI,CAAC,MAAM,OAAO,OAAO,QAAQ,MAAM,MAAM,OAAO,OAAO,OAAO;GACpE;EACF;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"vercel-git.mjs","names":[],"sources":["../src/vercel-git.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { Readable } from \"node:stream\";\nimport type { Sandbox, NetworkPolicy } from \"@vercel/sandbox\";\nimport { repositorySlugSchema, type RepositorySlug } from \"./schema/repository\";\nimport { isSafeGitRef } from \"./github-publication\";\nimport {\n maxGitBundleBytes,\n withSandboxGitBundle,\n type GitHubGitTransport,\n type GitHubRepositoryBundle,\n} from \"./github-transfer\";\n\ntype TrustedGitSandbox = Pick<\n Sandbox,\n \"cwd\" | \"runCommand\" | \"writeFiles\" | \"readFile\" | \"updateNetworkPolicy\" | \"stop\"\n>;\n\n/** Exact creation request for a fresh Vercel-managed trusted Git publication Sandbox. */\nexport interface VercelManagedGitHubSandboxCreateInput {\n source: {\n type: \"git\";\n url: string;\n };\n __source_credentials: true;\n __commitAs: {\n name: string;\n email: string;\n };\n networkPolicy: NetworkPolicy;\n persistent: false;\n resources: { vcpus: 6 };\n runtime: \"node24\";\n timeout: number;\n}\n\nconst gitOidPattern = /^[a-f0-9]{40}$/u;\nconst absentGitOid = \"0\".repeat(40);\n// vercel/api and other large repositories can take well over three minutes to clone and bundle.\nconst managedGitTimeoutMs = 600_000;\n\n/** Repository and access level used to mint one trusted Git transport token. */\nexport interface VercelGitHubTokenInput {\n repository: RepositorySlug;\n access: \"read\" | \"write\";\n}\n\n/** Configures fresh trusted sandboxes and repository-scoped Git tokens. */\nexport interface VercelGitHubGitTransportOptions {\n /** Reads must mint contents:read credentials; writes are confined to the supplied repository. */\n token(input: VercelGitHubTokenInput): Promise<string>;\n /** Creates a fresh sandbox, without a snapshot, repository, or running agent. */\n createSandbox?: () => Promise<TrustedGitSandbox>;\n}\n\n/** Configures Vercel-managed Git reads and signed publication. */\nexport interface VercelManagedGitHubGitTransportOptions {\n /** Attribution metadata supplied for managed signed commits. */\n commitIdentity: { name: string; email: string };\n /** Creates one unnamed Sandbox from the exact managed credential request. */\n createSandbox?: (input: VercelManagedGitHubSandboxCreateInput) => Promise<TrustedGitSandbox>;\n}\n\nasync function createManagedSandbox(\n input: VercelManagedGitHubSandboxCreateInput,\n): Promise<TrustedGitSandbox> {\n const { Sandbox } = await import(\"@vercel/sandbox\");\n return Sandbox.create({ ...input });\n}\n\nfunction publicationBase(input: {\n branch: string;\n head: string;\n expectedHead: string;\n baseHead?: string;\n}): string {\n if (\n !isSafeGitRef(input.branch) ||\n !gitOidPattern.test(input.head) ||\n !gitOidPattern.test(input.expectedHead) ||\n (input.expectedHead === absentGitOid\n ? input.baseHead === undefined || !gitOidPattern.test(input.baseHead)\n : input.baseHead !== undefined)\n ) {\n throw new Error(\"Invalid Git publication target\");\n }\n return input.expectedHead === absentGitOid ? input.baseHead! : input.expectedHead;\n}\n\nasync function trustedCommand(\n sandbox: TrustedGitSandbox,\n input: { cmd: string; args: string[]; cwd?: string },\n): Promise<string> {\n const result = await sandbox.runCommand({ ...input, timeoutMs: managedGitTimeoutMs });\n if (result.exitCode !== 0) throw new Error(`Trusted Git operation failed (${result.exitCode})`);\n return (await result.stdout()).trim();\n}\n\nfunction bundleTransferSandbox(sandbox: TrustedGitSandbox) {\n return {\n async run({ command }: { command: string }) {\n const result = await sandbox.runCommand({ cmd: \"sh\", args: [\"-c\", command] });\n return { exitCode: result.exitCode, stdout: await result.stdout() };\n },\n async writeBinaryFile({ path, content }: { path: string; content: Uint8Array }) {\n await sandbox.writeFiles([\n {\n path,\n content: Buffer.from(content.buffer, content.byteOffset, content.byteLength),\n },\n ]);\n },\n };\n}\n\nasync function consumeSandboxBundle(\n sandbox: TrustedGitSandbox,\n path: string,\n use: (bundle: GitHubRepositoryBundle) => Promise<void>,\n): Promise<void> {\n const size = await sandbox.runCommand({ cmd: \"stat\", args: [\"-c\", \"%s\", path] });\n const byteLength = Number(await size.stdout());\n if (\n size.exitCode !== 0 ||\n !Number.isSafeInteger(byteLength) ||\n byteLength < 1 ||\n byteLength > maxGitBundleBytes\n ) {\n throw new Error(\"Git bundle exceeds transfer limit\");\n }\n const source = await sandbox.readFile({ path });\n if (!source) throw new Error(\"Git bundle was not created\");\n const readable =\n source instanceof Readable ? source : new Readable({ highWaterMark: 64 * 1024 }).wrap(source);\n const iterator = readable[Symbol.asyncIterator]();\n const stream = new ReadableStream<Uint8Array>(\n {\n async pull(controller) {\n const { done, value } = await iterator.next();\n if (done) controller.close();\n else controller.enqueue(value);\n },\n async cancel() {\n await iterator.return?.();\n },\n },\n { highWaterMark: 0 },\n );\n try {\n await use({ stream });\n } finally {\n readable.destroy();\n if (!stream.locked) await stream.cancel();\n }\n}\n\nfunction credentialPolicy(repository: RepositorySlug, token: string): NetworkPolicy {\n return {\n allow: {\n \"github.com\": [\n {\n match: { path: { startsWith: `/${repository}.git/` } },\n transform: [\n {\n headers: {\n authorization: `Basic ${Buffer.from(`x-access-token:${token}`).toString(\"base64\")}`,\n },\n },\n ],\n },\n ],\n },\n };\n}\n\n/** Transfer Git objects through fresh trusted sandboxes; credentials never reach an agent sandbox. */\nexport function createVercelGitHubGitTransport(\n options: VercelGitHubGitTransportOptions,\n): GitHubGitTransport {\n async function isolated<T>(\n repository: RepositorySlug,\n use: (\n sandbox: Awaited<ReturnType<NonNullable<VercelGitHubGitTransportOptions[\"createSandbox\"]>>>,\n git: (...args: string[]) => Promise<string>,\n repository: RepositorySlug,\n ) => Promise<T>,\n ): Promise<T> {\n const slug = repositorySlugSchema.parse(repository.toLowerCase());\n const sandbox = options.createSandbox\n ? await options.createSandbox()\n : await (\n await import(\"@vercel/sandbox\")\n ).Sandbox.create({ runtime: \"node24\", timeout: 180_000, networkPolicy: \"deny-all\" });\n async function git(...args: string[]): Promise<string> {\n const result = await sandbox.runCommand({\n cmd: \"/usr/bin/env\",\n args: [\n \"-i\",\n \"PATH=/usr/bin:/bin\",\n \"HOME=/tmp\",\n \"GIT_CONFIG_NOSYSTEM=1\",\n \"GIT_CONFIG_GLOBAL=/dev/null\",\n \"GIT_TERMINAL_PROMPT=0\",\n \"git\",\n \"-c\",\n \"core.hooksPath=/dev/null\",\n \"-c\",\n \"credential.helper=\",\n \"-c\",\n \"protocol.ext.allow=never\",\n \"-c\",\n \"protocol.file.allow=always\",\n \"-c\",\n \"http.followRedirects=false\",\n ...args,\n ],\n timeoutMs: 120_000,\n });\n if (result.exitCode !== 0)\n throw new Error(`Trusted Git operation failed (${result.exitCode})`);\n return (await result.stdout()).trim();\n }\n try {\n await sandbox.updateNetworkPolicy(\"deny-all\");\n return await use(sandbox, git, slug);\n } finally {\n try {\n await sandbox.updateNetworkPolicy(\"deny-all\");\n } finally {\n await sandbox.stop();\n }\n }\n }\n return {\n async fetch(input, use) {\n if (input.refs.some((ref) => !ref.startsWith(\"refs/\") || !isSafeGitRef(ref)))\n throw new Error(\"Invalid Git fetch ref\");\n return isolated(input.repository, async (sandbox, git, slug) => {\n await sandbox.updateNetworkPolicy(\n credentialPolicy(slug, await options.token({ repository: slug, access: \"read\" })),\n );\n await git(\n \"clone\",\n \"--bare\",\n \"--no-hardlinks\",\n `https://github.com/${slug}.git`,\n \"/tmp/transfer.git\",\n );\n if (input.refs.length)\n await git(\n \"-C\",\n \"/tmp/transfer.git\",\n \"fetch\",\n \"--no-tags\",\n \"origin\",\n ...input.refs.map((ref) => `+${ref}:${ref}`),\n );\n await sandbox.updateNetworkPolicy(\"deny-all\");\n await git(\"-C\", \"/tmp/transfer.git\", \"bundle\", \"create\", \"/tmp/transfer.bundle\", \"--all\");\n await consumeSandboxBundle(sandbox, \"/tmp/transfer.bundle\", use);\n });\n },\n async push(input) {\n try {\n const baseHead = publicationBase(input);\n return await isolated(input.repository, async (sandbox, git, slug) => {\n await git(\"init\", \"--bare\", \"/tmp/transfer.git\");\n return await withSandboxGitBundle({\n sandbox: bundleTransferSandbox(sandbox),\n bundle: input.bundle,\n async use(path) {\n await git(\"-C\", \"/tmp/transfer.git\", \"bundle\", \"verify\", path);\n await git(\"-C\", \"/tmp/transfer.git\", \"fetch\", path, \"HEAD:refs/heads/candidate\");\n if (\n (await git(\"-C\", \"/tmp/transfer.git\", \"rev-parse\", \"refs/heads/candidate\")) !==\n input.head\n )\n throw new Error(\"Git bundle head does not match publication target\");\n await git(\n \"-C\",\n \"/tmp/transfer.git\",\n \"merge-base\",\n \"--is-ancestor\",\n baseHead,\n \"refs/heads/candidate\",\n );\n await sandbox.updateNetworkPolicy(\n credentialPolicy(slug, await options.token({ repository: slug, access: \"write\" })),\n );\n await input.authorize();\n await git(\n \"-C\",\n \"/tmp/transfer.git\",\n \"push\",\n `--force-with-lease=refs/heads/${input.branch}:${input.expectedHead}`,\n `https://github.com/${slug}.git`,\n `${input.head}:refs/heads/${input.branch}`,\n );\n return input.head;\n },\n });\n });\n } finally {\n if (!input.bundle.stream.locked) await input.bundle.stream.cancel();\n }\n },\n };\n}\n\n/** Use fresh Vercel-managed trusted Sandboxes for Git reads and signed publication. */\nexport function createVercelManagedGitHubGitTransport(\n options: VercelManagedGitHubGitTransportOptions,\n): GitHubGitTransport {\n const createSandbox = options.createSandbox ?? createManagedSandbox;\n const createInput = (\n repository: RepositorySlug,\n access: \"read\" | \"write\",\n ): VercelManagedGitHubSandboxCreateInput => ({\n source: {\n type: \"git\",\n url: `https://github.com/${repository}.git`,\n },\n __source_credentials: true,\n __commitAs: options.commitIdentity,\n networkPolicy: { allow: access === \"read\" ? [\"github.com\"] : [\"github.com\", \"api.github.com\"] },\n persistent: false,\n resources: { vcpus: 6 },\n runtime: \"node24\",\n timeout: managedGitTimeoutMs,\n });\n return {\n async fetch(input, use) {\n if (input.refs.some((ref) => !ref.startsWith(\"refs/\") || !isSafeGitRef(ref))) {\n throw new Error(\"Invalid Git fetch ref\");\n }\n const repository = repositorySlugSchema.parse(input.repository.toLowerCase());\n const sandbox = await createSandbox(createInput(repository, \"read\"));\n const repositoryPath = sandbox.cwd;\n try {\n await trustedCommand(sandbox, {\n cmd: \"/usr/bin/git\",\n args: [\"clone\", \"--bare\", \"--no-local\", repositoryPath, \"/tmp/transfer.git\"],\n });\n if (input.refs.length > 0) {\n await trustedCommand(sandbox, {\n cmd: \"git\",\n args: [\n \"-C\",\n \"/tmp/transfer.git\",\n \"fetch\",\n \"--no-tags\",\n `https://github.com/${repository}.git`,\n ...input.refs.map((ref) => `+${ref}:${ref}`),\n ],\n });\n }\n await trustedCommand(sandbox, {\n cmd: \"/usr/bin/git\",\n args: [\"-C\", \"/tmp/transfer.git\", \"bundle\", \"create\", \"/tmp/transfer.bundle\", \"--all\"],\n });\n await consumeSandboxBundle(sandbox, \"/tmp/transfer.bundle\", use);\n } finally {\n await sandbox.stop();\n }\n },\n async push(input) {\n try {\n const baseHead = publicationBase(input);\n if (input.expectedHead === absentGitOid) {\n throw new Error(\"Vercel-managed signed publication requires an existing remote branch\");\n }\n const repository = repositorySlugSchema.parse(input.repository.toLowerCase());\n const sandbox = await createSandbox(createInput(repository, \"write\"));\n const repositoryPath = sandbox.cwd;\n try {\n const hooksPath = `/tmp/factory-hooks-${randomUUID()}`;\n await trustedCommand(sandbox, { cmd: \"mkdir\", args: [\"-m\", \"700\", hooksPath] });\n await trustedCommand(sandbox, {\n cmd: \"/usr/bin/git\",\n args: [\"config\", \"core.hooksPath\", hooksPath],\n cwd: repositoryPath,\n });\n await withSandboxGitBundle({\n sandbox: bundleTransferSandbox(sandbox),\n bundle: input.bundle,\n async use(path) {\n await trustedCommand(sandbox, {\n cmd: \"/usr/bin/git\",\n args: [\"bundle\", \"verify\", path],\n cwd: repositoryPath,\n });\n await trustedCommand(sandbox, {\n cmd: \"/usr/bin/git\",\n args: [\"fetch\", \"--no-tags\", path, `${input.head}:refs/factory/candidate`],\n cwd: repositoryPath,\n });\n if (\n (await trustedCommand(sandbox, {\n cmd: \"/usr/bin/git\",\n args: [\"rev-parse\", \"refs/factory/candidate\"],\n cwd: repositoryPath,\n })) !== input.head\n ) {\n throw new Error(\"Git bundle head does not match publication target\");\n }\n await trustedCommand(sandbox, {\n cmd: \"/usr/bin/git\",\n args: [\"merge-base\", \"--is-ancestor\", baseHead, \"refs/factory/candidate\"],\n cwd: repositoryPath,\n });\n await trustedCommand(sandbox, {\n cmd: \"/usr/bin/git\",\n args: [\"checkout\", \"-B\", input.branch, \"refs/factory/candidate\"],\n cwd: repositoryPath,\n });\n await input.authorize();\n await trustedCommand(sandbox, {\n cmd: \"/usr/bin/env\",\n args: [\n `VERCEL_GIT_EXPECTED_REMOTE_OID=${input.expectedHead}`,\n ...(input.baseHead === undefined\n ? []\n : [`VERCEL_GIT_BASE_OID=${input.baseHead}`]),\n \"git\",\n \"push\",\n \"origin\",\n input.branch,\n ],\n cwd: repositoryPath,\n });\n },\n });\n const signedHead = await trustedCommand(sandbox, {\n cmd: \"/usr/bin/git\",\n args: [\"rev-parse\", \"HEAD\"],\n cwd: repositoryPath,\n });\n if (!gitOidPattern.test(signedHead)) {\n throw new Error(\"Managed signed push returned an invalid Git head\");\n }\n return signedHead;\n } finally {\n await sandbox.stop();\n }\n } finally {\n if (!input.bundle.stream.locked) await input.bundle.stream.cancel();\n }\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -8,6 +8,7 @@ import { OpenPullRequest } from "../pull-requests/contracts.mjs";
|
|
|
8
8
|
/** Provider-neutral readers used to discover work already in progress. */
|
|
9
9
|
interface ExistingWorkLookupOptions {
|
|
10
10
|
listOpenPullRequests: (repository: RepositorySlug) => Promise<readonly OpenPullRequest[]>;
|
|
11
|
+
enableGitHubPullRequestDiscovery?: boolean;
|
|
11
12
|
}
|
|
12
13
|
/** Stores, repository identity, and provider lookup used for one existing-work query. */
|
|
13
14
|
interface FindExistingWorkInput extends ExistingWorkLookupOptions {
|
|
@@ -21,6 +22,8 @@ type ExistingWorkLookupResult = {
|
|
|
21
22
|
reason: string;
|
|
22
23
|
} | {
|
|
23
24
|
found: true;
|
|
25
|
+
githubPullRequestDiscovery: "checked" | "skipped";
|
|
26
|
+
githubPullRequestDiscoveryWarning?: string;
|
|
24
27
|
openPullRequests: readonly OpenPullRequest[];
|
|
25
28
|
openChanges: readonly Change[];
|
|
26
29
|
activeTasks: readonly Task[];
|
|
@@ -14,8 +14,9 @@ async function findExistingWork(input) {
|
|
|
14
14
|
reasonCode: "repository_not_found",
|
|
15
15
|
reason: `repository ${input.repositoryId} does not exist`
|
|
16
16
|
};
|
|
17
|
+
const githubPullRequestDiscovery = input.enableGitHubPullRequestDiscovery === false ? "skipped" : "checked";
|
|
17
18
|
const [openPullRequests, changes, tasks] = await Promise.all([
|
|
18
|
-
input.listOpenPullRequests(repository.slug),
|
|
19
|
+
githubPullRequestDiscovery === "skipped" ? Promise.resolve([]) : input.listOpenPullRequests(repository.slug),
|
|
19
20
|
collectFactoryRecords(input.stores.changes.list, {
|
|
20
21
|
limit: 100,
|
|
21
22
|
match: [...openChangeStates].map((state) => ({
|
|
@@ -30,6 +31,8 @@ async function findExistingWork(input) {
|
|
|
30
31
|
]);
|
|
31
32
|
return {
|
|
32
33
|
found: true,
|
|
34
|
+
githubPullRequestDiscovery,
|
|
35
|
+
...githubPullRequestDiscovery === "skipped" ? { githubPullRequestDiscoveryWarning: "GitHub pull requests were not checked; duplicate pull requests are possible." } : {},
|
|
33
36
|
openPullRequests,
|
|
34
37
|
openChanges: changes.filter((change) => change.repositoryId === input.repositoryId && openChangeStates.has(change.state)),
|
|
35
38
|
activeTasks: tasks.filter((task) => task.repositoryIds.includes(input.repositoryId) && activeTaskStates.has(task.state))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"find-existing-work.mjs","names":[],"sources":["../../src/work-triage/find-existing-work.ts"],"sourcesContent":["import { collectFactoryRecords } from \"../store/iterate\";\nimport type { Change } from \"../schema/change\";\nimport type { Task } from \"../schema/task\";\nimport type { FactoryStores } from \"../store/engine\";\nimport type { OpenPullRequest } from \"../pull-requests/contracts\";\nimport type { RepositoryId } from \"../schema/id\";\nimport type { RepositorySlug } from \"../schema/repository\";\n\n/** Provider-neutral readers used to discover work already in progress. */\nexport interface ExistingWorkLookupOptions {\n listOpenPullRequests: (repository: RepositorySlug) => Promise<readonly OpenPullRequest[]>;\n}\n\n/** Stores, repository identity, and provider lookup used for one existing-work query. */\nexport interface FindExistingWorkInput extends ExistingWorkLookupOptions {\n stores: Pick<FactoryStores, \"changes\" | \"repositories\" | \"tasks\">;\n repositoryId: RepositoryId;\n}\n\n/** The refusal or existing pull requests, Changes, and active Tasks found during triage. */\nexport type ExistingWorkLookupResult =\n | { found: false; reasonCode: \"repository_not_found\"; reason: string }\n | {\n found: true;\n openPullRequests: readonly OpenPullRequest[];\n openChanges: readonly Change[];\n activeTasks: readonly Task[];\n };\n\nconst openChangeStates: ReadonlySet<Change[\"state\"]> = new Set([\"verifying\", \"ready\"]);\nconst activeTaskStates: ReadonlySet<Task[\"state\"]> = new Set([\"queued\", \"running\", \"needs_human\"]);\n\n/** Find open pull requests, Changes, and active Tasks for one repository. */\nexport async function findExistingWork(\n input: FindExistingWorkInput,\n): Promise<ExistingWorkLookupResult> {\n const repository = await input.stores.repositories.get(input.repositoryId);\n if (repository === null) {\n return {\n found: false,\n reasonCode: \"repository_not_found\",\n reason: `repository ${input.repositoryId} does not exist`,\n };\n }\n const [openPullRequests, changes, tasks] = await Promise.all([\n input.listOpenPullRequests(repository.slug),\n collectFactoryRecords(input.stores.changes.list, {\n limit: 100,\n match: [...openChangeStates].map((state) => ({ repositoryId: input.repositoryId, state })),\n }),\n collectFactoryRecords(input.stores.tasks.list, {\n limit: 100,\n match: { repositoryIds: [input.repositoryId] },\n }),\n ]);\n return {\n found: true,\n openPullRequests,\n openChanges: changes.filter(\n (change) => change.repositoryId === input.repositoryId && openChangeStates.has(change.state),\n ),\n activeTasks: tasks.filter(\n (task) => task.repositoryIds.includes(input.repositoryId) && activeTaskStates.has(task.state),\n ),\n };\n}\n"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"find-existing-work.mjs","names":[],"sources":["../../src/work-triage/find-existing-work.ts"],"sourcesContent":["import { collectFactoryRecords } from \"../store/iterate\";\nimport type { Change } from \"../schema/change\";\nimport type { Task } from \"../schema/task\";\nimport type { FactoryStores } from \"../store/engine\";\nimport type { OpenPullRequest } from \"../pull-requests/contracts\";\nimport type { RepositoryId } from \"../schema/id\";\nimport type { RepositorySlug } from \"../schema/repository\";\n\n/** Provider-neutral readers used to discover work already in progress. */\nexport interface ExistingWorkLookupOptions {\n listOpenPullRequests: (repository: RepositorySlug) => Promise<readonly OpenPullRequest[]>;\n enableGitHubPullRequestDiscovery?: boolean;\n}\n\n/** Stores, repository identity, and provider lookup used for one existing-work query. */\nexport interface FindExistingWorkInput extends ExistingWorkLookupOptions {\n stores: Pick<FactoryStores, \"changes\" | \"repositories\" | \"tasks\">;\n repositoryId: RepositoryId;\n}\n\n/** The refusal or existing pull requests, Changes, and active Tasks found during triage. */\nexport type ExistingWorkLookupResult =\n | { found: false; reasonCode: \"repository_not_found\"; reason: string }\n | {\n found: true;\n githubPullRequestDiscovery: \"checked\" | \"skipped\";\n githubPullRequestDiscoveryWarning?: string;\n openPullRequests: readonly OpenPullRequest[];\n openChanges: readonly Change[];\n activeTasks: readonly Task[];\n };\n\nconst openChangeStates: ReadonlySet<Change[\"state\"]> = new Set([\"verifying\", \"ready\"]);\nconst activeTaskStates: ReadonlySet<Task[\"state\"]> = new Set([\"queued\", \"running\", \"needs_human\"]);\n\n/** Find open pull requests, Changes, and active Tasks for one repository. */\nexport async function findExistingWork(\n input: FindExistingWorkInput,\n): Promise<ExistingWorkLookupResult> {\n const repository = await input.stores.repositories.get(input.repositoryId);\n if (repository === null) {\n return {\n found: false,\n reasonCode: \"repository_not_found\",\n reason: `repository ${input.repositoryId} does not exist`,\n };\n }\n // Temporary managed-credential demo mode: recorded work is still checked, but GitHub PRs may be missed (PLAT-9026).\n const githubPullRequestDiscovery =\n input.enableGitHubPullRequestDiscovery === false ? \"skipped\" : \"checked\";\n const [openPullRequests, changes, tasks] = await Promise.all([\n githubPullRequestDiscovery === \"skipped\"\n ? Promise.resolve([])\n : input.listOpenPullRequests(repository.slug),\n collectFactoryRecords(input.stores.changes.list, {\n limit: 100,\n match: [...openChangeStates].map((state) => ({ repositoryId: input.repositoryId, state })),\n }),\n collectFactoryRecords(input.stores.tasks.list, {\n limit: 100,\n match: { repositoryIds: [input.repositoryId] },\n }),\n ]);\n return {\n found: true,\n githubPullRequestDiscovery,\n ...(githubPullRequestDiscovery === \"skipped\"\n ? {\n githubPullRequestDiscoveryWarning:\n \"GitHub pull requests were not checked; duplicate pull requests are possible.\",\n }\n : {}),\n openPullRequests,\n openChanges: changes.filter(\n (change) => change.repositoryId === input.repositoryId && openChangeStates.has(change.state),\n ),\n activeTasks: tasks.filter(\n (task) => task.repositoryIds.includes(input.repositoryId) && activeTaskStates.has(task.state),\n ),\n };\n}\n"],"mappings":";;AAgCA,MAAM,mCAAiD,IAAI,IAAI,CAAC,aAAa,OAAO,CAAC;AACrF,MAAM,mCAA+C,IAAI,IAAI;CAAC;CAAU;CAAW;AAAa,CAAC;;AAGjG,eAAsB,iBACpB,OACmC;CACnC,MAAM,aAAa,MAAM,MAAM,OAAO,aAAa,IAAI,MAAM,YAAY;CACzE,IAAI,eAAe,MACjB,OAAO;EACL,OAAO;EACP,YAAY;EACZ,QAAQ,cAAc,MAAM,aAAa;CAC3C;CAGF,MAAM,6BACJ,MAAM,qCAAqC,QAAQ,YAAY;CACjE,MAAM,CAAC,kBAAkB,SAAS,SAAS,MAAM,QAAQ,IAAI;EAC3D,+BAA+B,YAC3B,QAAQ,QAAQ,CAAC,CAAC,IAClB,MAAM,qBAAqB,WAAW,IAAI;EAC9C,sBAAsB,MAAM,OAAO,QAAQ,MAAM;GAC/C,OAAO;GACP,OAAO,CAAC,GAAG,gBAAgB,CAAC,CAAC,KAAK,WAAW;IAAE,cAAc,MAAM;IAAc;GAAM,EAAE;EAC3F,CAAC;EACD,sBAAsB,MAAM,OAAO,MAAM,MAAM;GAC7C,OAAO;GACP,OAAO,EAAE,eAAe,CAAC,MAAM,YAAY,EAAE;EAC/C,CAAC;CACH,CAAC;CACD,OAAO;EACL,OAAO;EACP;EACA,GAAI,+BAA+B,YAC/B,EACE,mCACE,+EACJ,IACA,CAAC;EACL;EACA,aAAa,QAAQ,QAClB,WAAW,OAAO,iBAAiB,MAAM,gBAAgB,iBAAiB,IAAI,OAAO,KAAK,CAC7F;EACA,aAAa,MAAM,QAChB,SAAS,KAAK,cAAc,SAAS,MAAM,YAAY,KAAK,iBAAiB,IAAI,KAAK,KAAK,CAC9F;CACF;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/factory",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.27",
|
|
4
4
|
"description": "Schema, state machines, and kernel primitives for Agent Factory.",
|
|
5
5
|
"homepage": "https://github.com/vercel-labs/agent-factory/tree/main/packages/factory",
|
|
6
6
|
"bugs": "https://github.com/vercel-labs/agent-factory/issues",
|