@vercel/factory 0.0.25 → 0.0.26

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @vercel/factory
2
2
 
3
+ ## 0.0.26
4
+
5
+ ### Patch Changes
6
+
7
+ - 6f3dec5: Let managed-credential Factory demos skip unavailable GitHub PR discovery while reporting the gap and checking recorded work.
8
+
3
9
  ## 0.0.25
4
10
 
5
11
  ### Patch Changes
@@ -7,7 +7,7 @@ import { ImageAttachment } from "./image-input.mjs";
7
7
  import { AgentSession } from "./schema/session.mjs";
8
8
  import { RepositorySlug } from "./schema/repository.mjs";
9
9
  import "./tasks.mjs";
10
- import "./client.mjs";
10
+ import "./api.mjs";
11
11
  import { z } from "zod";
12
12
  //#region src/api-contracts.d.ts
13
13
  /** Validates an agent exposed by the operator API. */
@@ -1,9 +1,9 @@
1
1
  import { TaskId } from "./schema/id.mjs";
2
2
  import { AgentSessionId } from "./schema/session.mjs";
3
3
  import { TranscriptBlock } from "./schema/transcript.mjs";
4
+ import { SessionView } from "./api-contracts.mjs";
4
5
  import { Transcript } from "./client-transcript.mjs";
5
6
  import { FactoryClient } from "./client.mjs";
6
- import { SessionView } from "./api-contracts.mjs";
7
7
  //#region src/client-stream.d.ts
8
8
  /** Client operations required to hydrate and follow a Factory session stream. */
9
9
  type SessionStreamClient = Pick<FactoryClient, "getSessionTranscriptResponse" | "getSessionEventStreamResponse" | "getSessionHistory">;
package/dist/client.d.mts CHANGED
@@ -14,12 +14,12 @@ import { SessionReview, SessionReviewCheck, SessionReviewIssue, SessionReviewScr
14
14
  import { WorkspaceFileEntry, WorkspaceFiles, WorkspaceFilesRequest, workspaceFileContentSchema, workspaceFileEntrySchema, workspaceFilesRequestSchema, workspaceFilesSchema, workspacePathSchema } from "./workspace-files.mjs";
15
15
  import { ApprovalListOptions, ApprovalPage, ApprovalView } from "./approval-contracts.mjs";
16
16
  import { WorkspaceDiff, WorkspaceDiffFile, workspaceDiffFileSchema, workspaceDiffSchema } from "./workspace-diff.mjs";
17
+ import { AnswerSessionQuestionRequest, ApiAgent, ControlSessionRequest, CreateSessionRequest, PendingFollowUp, RepositoryDetails, RepositorySummary, SendSessionMessageRequest, SessionInputResponse, SessionPage, SessionPullRequest, SessionSummary, SessionView, SessionWorkGroup, SessionWorkResult, SessionWorkTask, SubmitSessionInputRequest, answerSessionQuestionRequestSchema, apiAgentSchema, controlSessionRequestSchema, createSessionRequestSchema, pendingFollowUpSchema, repositoryDetailsSchema, repositorySummarySchema, sendSessionMessageRequestSchema, sessionInputResponseSchema, sessionPageSchema, sessionPullRequestSchema, sessionPullRequestStateSchema, sessionSummarySchema, sessionViewSchema, sessionWorkGroupSchema, sessionWorkResultSchema, sessionWorkTaskSchema, submitSessionInputRequestSchema } from "./api-contracts.mjs";
17
18
  import { IntakeView, describeIntake } from "./intake-contracts.mjs";
18
19
  import { TaskGraphView } from "./task-graph-view.mjs";
19
20
  import { StreamEvent, readEvents, streamEventSchema } from "./client-events.mjs";
20
21
  import { Transcript, TranscriptHistoryPage } from "./client-transcript.mjs";
21
22
  import { FollowSessionStreamOptions, SessionStreamClient, expandTranscriptBlock, followSessionStream, loadPreviousTranscriptPage } from "./client-stream.mjs";
22
- import { AnswerSessionQuestionRequest, ApiAgent, ControlSessionRequest, CreateSessionRequest, PendingFollowUp, RepositoryDetails, RepositorySummary, SendSessionMessageRequest, SessionInputResponse, SessionPage, SessionPullRequest, SessionSummary, SessionView, SessionWorkGroup, SessionWorkResult, SessionWorkTask, SubmitSessionInputRequest, answerSessionQuestionRequestSchema, apiAgentSchema, controlSessionRequestSchema, createSessionRequestSchema, pendingFollowUpSchema, repositoryDetailsSchema, repositorySummarySchema, sendSessionMessageRequestSchema, sessionInputResponseSchema, sessionPageSchema, sessionPullRequestSchema, sessionPullRequestStateSchema, sessionSummarySchema, sessionViewSchema, sessionWorkGroupSchema, sessionWorkResultSchema, sessionWorkTaskSchema, submitSessionInputRequestSchema } from "./api-contracts.mjs";
23
23
  //#region src/client.d.ts
24
24
  /** All durable Task states in their schema-defined order. */
25
25
  declare const taskStates: readonly ("queued" | "running" | "stalled" | "verifying" | "succeeded" | "failed" | "needs_human" | "cancelled")[];
@@ -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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"}
@@ -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":";;AA6BA,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;CAEF,MAAM,CAAC,kBAAkB,SAAS,SAAS,MAAM,QAAQ,IAAI;EAC3D,MAAM,qBAAqB,WAAW,IAAI;EAC1C,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,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"}
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.25",
3
+ "version": "0.0.26",
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",