@vercel/factory 0.0.13 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +1 -1
  2. package/dist/api.d.mts +225 -0
  3. package/dist/api.mjs +312 -0
  4. package/dist/api.mjs.map +1 -0
  5. package/dist/approval-JX2Px9IC.mjs +132 -0
  6. package/dist/approval-JX2Px9IC.mjs.map +1 -0
  7. package/dist/blob/index.d.mts +4 -3
  8. package/dist/blob/index.mjs +21 -15
  9. package/dist/blob/index.mjs.map +1 -1
  10. package/dist/budget-DzSiIA4t.mjs +42 -0
  11. package/dist/budget-DzSiIA4t.mjs.map +1 -0
  12. package/dist/build-factory.mjs +1 -1
  13. package/dist/{change-CzEAoFVE.mjs → change-D2U6v7Ek.mjs} +10 -76
  14. package/dist/change-D2U6v7Ek.mjs.map +1 -0
  15. package/dist/communication-Kd9nU5RP.mjs +30 -0
  16. package/dist/communication-Kd9nU5RP.mjs.map +1 -0
  17. package/dist/connectors/index.d.mts +3 -2
  18. package/dist/connectors/index.mjs +53 -26
  19. package/dist/connectors/index.mjs.map +1 -1
  20. package/dist/connectors/slack.d.mts +13 -2
  21. package/dist/connectors/slack.mjs +18 -2
  22. package/dist/connectors/slack.mjs.map +1 -1
  23. package/dist/coordination-Cq08hb1l.mjs +76 -0
  24. package/dist/coordination-Cq08hb1l.mjs.map +1 -0
  25. package/dist/dispatch-BaZS3PT8.d.mts +76 -0
  26. package/dist/{driver-BJutB0PE.d.mts → driver-BC0Mt5KG.d.mts} +2 -2
  27. package/dist/driver-Bbd25rNM.mjs.map +1 -1
  28. package/dist/{dispatch-B77cuPxh.d.mts → engine-DFHU2Pe-.d.mts} +356 -288
  29. package/dist/engine-WaUM2cGA.mjs +1068 -0
  30. package/dist/engine-WaUM2cGA.mjs.map +1 -0
  31. package/dist/eve/index.d.mts +44 -334
  32. package/dist/eve/index.mjs +51 -38
  33. package/dist/eve/index.mjs.map +1 -1
  34. package/dist/{factory-config-BJcjsPKl.mjs → factory-config-BoRXOw43.mjs} +5 -41
  35. package/dist/factory-config-BoRXOw43.mjs.map +1 -0
  36. package/dist/{github-DXsxboUL.d.mts → github-C5HNBN6p.d.mts} +11 -3
  37. package/dist/github.d.mts +2 -2
  38. package/dist/github.mjs +15 -1
  39. package/dist/github.mjs.map +1 -1
  40. package/dist/{id-DwCihQr4.mjs → id-BLIkCVue.mjs} +4 -2
  41. package/dist/{id-DwCihQr4.mjs.map → id-BLIkCVue.mjs.map} +1 -1
  42. package/dist/index-Bw6hggoH.d.mts +40 -0
  43. package/dist/index.d.mts +176 -6
  44. package/dist/index.mjs +50 -652
  45. package/dist/index.mjs.map +1 -1
  46. package/dist/postgres/index.d.mts +1 -1
  47. package/dist/{approval-CwhTP1XB.mjs → task-CcOyOJ3k.mjs} +18 -133
  48. package/dist/task-CcOyOJ3k.mjs.map +1 -0
  49. package/package.json +5 -1
  50. package/dist/approval-CwhTP1XB.mjs.map +0 -1
  51. package/dist/change-CzEAoFVE.mjs.map +0 -1
  52. package/dist/factory-config-BJcjsPKl.mjs.map +0 -1
  53. package/dist/index-Cy4jfXh5.d.mts +0 -67
  54. package/dist/signal-CHVEmzvI.d.mts +0 -59
@@ -1,6 +1,8 @@
1
- import { i as repositoryIdSchema, o as signalIdSchema, p as vocabularySchema, s as taskIdSchema, t as changeIdSchema } from "../id-DwCihQr4.mjs";
2
- import { b as taskQuestionSchema, c as findingSchema, i as requiresApproval, o as findingFingerprint, r as defaultApprovalPolicy } from "../approval-CwhTP1XB.mjs";
3
- import { b as createCoordination, c as dispatchTask, i as levelSchema, t as changeEvidenceSchema } from "../change-CzEAoFVE.mjs";
1
+ import { c as signalIdSchema, h as vocabularySchema, l as taskIdSchema, n as changeIdSchema, o as repositoryIdSchema } from "../id-BLIkCVue.mjs";
2
+ import { l as taskQuestionSchema } from "../task-CcOyOJ3k.mjs";
3
+ import { c as findingSchema, i as requiresApproval, o as findingFingerprint, r as defaultApprovalPolicy } from "../approval-JX2Px9IC.mjs";
4
+ import { c as dispatchTask, i as levelSchema, t as changeEvidenceSchema } from "../change-D2U6v7Ek.mjs";
5
+ import { t as createCoordination } from "../coordination-Cq08hb1l.mjs";
4
6
  import { answerReviewComment, readPullRequestHead, readReviewComment, submitPullRequestReview } from "../github.mjs";
5
7
  import { z } from "zod";
6
8
  import { createHash } from "node:crypto";
@@ -371,8 +373,20 @@ function fetchPullRequestTool(stores, options) {
371
373
  const depth = options.depth ?? 50;
372
374
  return defineTool({
373
375
  description: "Fetch the pull request and the repository's current default branch, then check out the pull request as local branch `pr`. Call this after get_task, before inspecting or testing the change. Returns an exact diff command based on their merge base; use it instead of origin/HEAD.",
374
- inputSchema: z.object({ changeId: changeIdSchema.describe("The change under review, from your starting message.") }),
376
+ inputSchema: z.object({ changeId: changeIdSchema.describe("The Change linked to your Task, returned by get_task.") }),
375
377
  async execute({ changeId }, ctx) {
378
+ const boundTaskId = taskIdSchema.safeParse(ctx.session?.auth.initiator?.attributes.factoryTaskId);
379
+ if (boundTaskId.success) {
380
+ const task = await stores.tasks.get(boundTaskId.data);
381
+ if (task === null) return {
382
+ fetched: false,
383
+ reason: `task ${boundTaskId.data} does not exist`
384
+ };
385
+ if (task.changeId !== void 0 && task.changeId !== changeId) return {
386
+ fetched: false,
387
+ reason: `change ${changeId} is not linked to task ${boundTaskId.data}`
388
+ };
389
+ }
376
390
  const change = await stores.changes.get(changeId);
377
391
  if (change === null) return {
378
392
  fetched: false,
@@ -524,6 +538,15 @@ function delegateAgentTool(stores, options) {
524
538
  const repository = await stores.repositories.get(repositoryId);
525
539
  if (repository === null || !repository.enabled) throw new Error(`Repository ${repositoryId} is not enabled in this factory.`);
526
540
  }
541
+ const boundChange = changeIdSchema.safeParse(attributes?.factoryChangeId);
542
+ if (attributes?.factoryChangeId !== void 0 && !boundChange.success) throw new Error("Connector-authenticated change context is invalid.");
543
+ if (input.changeId !== void 0 && boundChange.success && input.changeId !== boundChange.data) throw new Error("Delegated change does not match the connector-authenticated context.");
544
+ const changeId = input.changeId ?? (boundChange.success ? boundChange.data : void 0);
545
+ if (changeId !== void 0) {
546
+ if (input.repositoryIds.length !== 1) throw new Error("Delegating work on an existing Change requires exactly one repository.");
547
+ const change = await stores.changes.get(changeId);
548
+ if (change === null || change.repositoryId !== input.repositoryIds[0] || change.state !== "verifying" && change.state !== "ready") throw new Error(`Change ${changeId} is not an open pull request in the selected repository.`);
549
+ }
527
550
  if (input.signalId !== void 0) {
528
551
  const signal = await stores.signals.get(input.signalId);
529
552
  if (signal === null) throw new Error(`Signal ${input.signalId} does not exist.`);
@@ -545,7 +568,7 @@ function delegateAgentTool(stores, options) {
545
568
  ...input.signalId === void 0 ? {} : { dedupeKey: `route:${input.signalId}:${route.id}` },
546
569
  ...input.signalId === void 0 ? { operator } : { signalId: input.signalId }
547
570
  });
548
- await Promise.all(coordination.children.filter((child) => child.state === "queued").map((child) => dispatchRouteTask(stores, options, doFetch, route, child, input.changeId, input.brief)));
571
+ await Promise.all(coordination.children.filter((child) => child.state === "queued").map((child) => dispatchRouteTask(stores, options, doFetch, route, child, changeId, input.brief)));
549
572
  return {
550
573
  taskId: coordination.parent.id,
551
574
  childTaskIds: coordination.children.map(({ id }) => id),
@@ -559,10 +582,10 @@ function delegateAgentTool(stores, options) {
559
582
  kind: route.taskKind,
560
583
  origin: input.signalId !== void 0 ? { signalId: input.signalId } : input.parentTaskId !== void 0 ? { parentTaskId: input.parentTaskId } : { operator },
561
584
  replyTo,
562
- changeId: input.changeId,
585
+ changeId,
563
586
  ...input.signalId === void 0 ? {} : { dedupeKey: `route:${input.signalId}:${route.id}` }
564
587
  });
565
- const running = task.state === "queued" ? await dispatchRouteTask(stores, options, doFetch, route, task, input.changeId, input.brief) : task;
588
+ const running = task.state === "queued" ? await dispatchRouteTask(stores, options, doFetch, route, task, changeId, input.brief) : task;
566
589
  return {
567
590
  taskId: running.id,
568
591
  routeId: route.id,
@@ -876,9 +899,9 @@ function recordVerificationTool(stores, options = {}) {
876
899
  recorded: false,
877
900
  reason: `change ${changeId} does not exist`
878
901
  };
879
- if (change.state !== "verifying") return {
902
+ if (change.state !== "verifying" && change.state !== "ready") return {
880
903
  recorded: false,
881
- reason: `change is ${change.state}, not verifying; nothing to record`
904
+ reason: `change is ${change.state}, not open for verification; nothing to record`
882
905
  };
883
906
  const task = await stores.tasks.get(taskId);
884
907
  if (task === null || task.kind !== "verification") return {
@@ -886,20 +909,22 @@ function recordVerificationTool(stores, options = {}) {
886
909
  reason: `${taskId} is not a verification task`
887
910
  };
888
911
  let updatedChange = change;
889
- if (verdict === "pass") updatedChange = await stores.changes.transition(changeId, "ready", {
912
+ if (verdict === "pass" && change.state === "verifying") updatedChange = await stores.changes.transition(changeId, "ready", {
890
913
  patch: {
891
914
  evidence,
892
915
  confidence
893
916
  },
894
917
  reason: summary
895
918
  });
919
+ else if (verdict === "fail" && change.state === "ready") updatedChange = await stores.changes.transition(changeId, "verifying", { reason: summary });
896
920
  let finished;
897
921
  if (verdict === "pass") {
898
922
  await stores.tasks.transition(taskId, "verifying", { reason: summary });
899
923
  finished = await stores.tasks.transition(taskId, "succeeded");
900
924
  } else finished = await stores.tasks.transition(taskId, "failed", { reason: summary });
901
- if (change.taskId !== void 0) {
902
- if ((await stores.tasks.get(change.taskId))?.state === "verifying") await stores.tasks.transition(change.taskId, verdict === "pass" ? "succeeded" : "failed", verdict === "pass" ? {} : { reason: `verification failed: ${summary}` }).catch(() => void 0);
925
+ const sourceTaskId = task.origin.parentTaskId ?? change.taskId;
926
+ if (sourceTaskId !== void 0) {
927
+ if ((await stores.tasks.get(sourceTaskId))?.state === "verifying") await stores.tasks.transition(sourceTaskId, verdict === "pass" ? "succeeded" : "failed", verdict === "pass" ? {} : { reason: `verification failed: ${summary}` }).catch(() => void 0);
903
928
  }
904
929
  await options.presentResult?.({
905
930
  taskId,
@@ -944,7 +969,10 @@ async function createAndPresentQuestion(stores, parent, question, presentQuestio
944
969
  });
945
970
  const waiting = await stores.tasks.transition(task.id, "needs_human");
946
971
  try {
947
- if (parent.state === "running") await stores.tasks.transition(parent.id, "needs_human", { reason: `waiting for answer to question ${waiting.id}` });
972
+ if (parent.state === "running") await stores.tasks.transition(parent.id, "needs_human", {
973
+ reason: `waiting for answer to question ${waiting.id}`,
974
+ questionTaskId: waiting.id
975
+ });
948
976
  await presentQuestion(waiting);
949
977
  } catch (error) {
950
978
  if ((await stores.tasks.get(parent.id))?.state === "needs_human") await stores.tasks.transition(parent.id, "queued", { reason: `delivery of question ${waiting.id} failed` });
@@ -1097,39 +1125,24 @@ function factoryTools(stores, options = {}) {
1097
1125
  }
1098
1126
  }),
1099
1127
  answer_question: defineTool({
1100
- description: "Record the authenticated user's answer to a waiting question and requeue the parent specialist task.",
1128
+ description: "Record the authenticated user's answer to a waiting question and requeue the parent specialist task. A blocked parent task id is also accepted and resolves its persisted QuestionTask id.",
1101
1129
  inputSchema: z.object({
1102
1130
  questionTaskId: taskIdSchema,
1103
1131
  answer: z.string().min(1).max(4e3)
1104
1132
  }),
1105
1133
  async execute({ questionTaskId, answer }, ctx) {
1106
- const questionTask = await stores.tasks.get(questionTaskId);
1107
- if (questionTask === null || questionTask.kind !== "question" || questionTask.state !== "needs_human" || questionTask.question === void 0) throw new Error(`Question task ${questionTaskId} is not waiting for an answer.`);
1134
+ const requestedTask = await stores.tasks.get(questionTaskId);
1135
+ const resolvedQuestionTaskId = requestedTask?.kind === "question" ? requestedTask.id : requestedTask?.questionTaskId;
1136
+ const questionTask = resolvedQuestionTaskId === void 0 ? null : await stores.tasks.get(resolvedQuestionTaskId);
1137
+ if (questionTask === null || questionTask.kind !== "question" || questionTask.question === void 0) throw new Error(`Question task ${questionTaskId} does not exist.`);
1108
1138
  const attributes = ctx.session.auth.initiator?.attributes;
1109
1139
  if (attributes?.factoryReplyChannel !== questionTask.replyTo.channel || attributes.factoryReplyAddress !== questionTask.replyTo.address) throw new Error("Question reply does not match the authenticated conversation.");
1110
- const repositoryAttribute = attributes.factoryRepositoryIds;
1111
- const boundRepositoryIds = typeof repositoryAttribute === "string" ? repositoryAttribute.split(",").filter(Boolean) : repositoryAttribute;
1112
- if (boundRepositoryIds === void 0 || boundRepositoryIds.length !== questionTask.repositoryIds.length || boundRepositoryIds.some((id) => !questionTask.repositoryIds.includes(id))) throw new Error("Question repositories do not match the authenticated conversation.");
1113
1140
  const answeredBy = ctx.session.auth.initiator?.principalId ?? "operator";
1114
- const answered = await stores.tasks.transition(questionTask.id, "succeeded", {
1115
- patch: { question: {
1116
- ...questionTask.question,
1117
- answer,
1118
- answeredBy
1119
- } },
1120
- reason: "question answered through Foreman"
1141
+ return stores.tasks.answerQuestion({
1142
+ questionTaskId: questionTask.id,
1143
+ answer,
1144
+ answeredBy
1121
1145
  });
1122
- const parentTaskId = questionTask.origin.parentTaskId;
1123
- if (parentTaskId === void 0) return {
1124
- questionTask: answered,
1125
- parentTask: null
1126
- };
1127
- const parent = await stores.tasks.get(parentTaskId);
1128
- if (parent === null) throw new Error(`Parent task ${parentTaskId} does not exist.`);
1129
- return {
1130
- questionTask: answered,
1131
- parentTask: parent.state === "needs_human" ? await stores.tasks.transition(parent.id, "queued", { reason: `question ${questionTask.id} answered by ${answeredBy}` }) : parent
1132
- };
1133
1146
  }
1134
1147
  }),
1135
1148
  append_receipt: defineTool({
@@ -1196,7 +1209,7 @@ function factoryHooks(stores, options) {
1196
1209
  if (taskId !== void 0) await settleIfTerminal(taskId).catch(() => void 0);
1197
1210
  const address = ctx.channel.continuationToken;
1198
1211
  if (address === void 0) return;
1199
- const abandoned = (await stores.tasks.list()).filter((task) => task.state === "running" && task.replyTo.address === address);
1212
+ const abandoned = (await stores.tasks.list().catch(() => [])).filter((task) => task.state === "running" && task.replyTo.address === address);
1200
1213
  for (const task of abandoned) await createAndPresentQuestion(stores, task, {
1201
1214
  text: "The worker stopped without recording an outcome. Please review the task and reply when Factory should retry it.",
1202
1215
  options: ["Retry the task"]