@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,29 +1,56 @@
1
- import { a as repositoryIdsSchema, s as taskIdSchema, t as changeIdSchema, u as replyToSchema } from "../id-DwCihQr4.mjs";
2
- import { z } from "zod";
3
- //#region src/connectors/index.ts
4
- const communicationOutcomes = [
5
- "accepted",
6
- "started",
7
- "succeeded",
8
- "failed",
9
- "needs_human",
10
- "question",
11
- "digest",
12
- "rolled_back",
13
- "budget_warning",
14
- "budget_exhausted",
15
- "message"
1
+ import { n as communicationOutcomeSchema, r as communicationOutcomes, t as communicationEventSchema } from "../communication-Kd9nU5RP.mjs";
2
+ //#region src/connectors/outbox.ts
3
+ const defaultRetryDelayMs = (attempt) => Math.min(6e4 * 2 ** Math.max(0, attempt - 1), 36e5);
4
+ const credentialPatterns = [
5
+ /github_pat_[A-Za-z0-9_]+/gu,
6
+ /gh[psoru]_[A-Za-z0-9_]+/gu,
7
+ /xox[baprs]-[A-Za-z0-9-]+/gu,
8
+ /(?:Basic|Bearer) [A-Za-z0-9+/_=.:-]+/gu
16
9
  ];
17
- const communicationOutcomeSchema = z.enum(communicationOutcomes).describe("Lifecycle outcome a connector should render and deliver.");
18
- const communicationEventSchema = z.strictObject({
19
- eventId: z.string().min(1).describe("Stable idempotency key for this delivery."),
20
- repositoryIds: repositoryIdsSchema,
21
- taskId: taskIdSchema.optional(),
22
- changeId: changeIdSchema.optional(),
23
- replyTo: replyToSchema,
24
- outcome: communicationOutcomeSchema,
25
- data: z.record(z.string(), z.unknown()).describe("Outcome-specific data rendered by the selected connector.")
26
- });
10
+ function errorMessage(error) {
11
+ let message = error instanceof Error ? error.message : "Unknown communication delivery error";
12
+ for (const pattern of credentialPatterns) message = message.replace(pattern, (match) => {
13
+ return `${match.startsWith("Basic ") ? "Basic " : match.startsWith("Bearer ") ? "Bearer " : ""}[REDACTED]`;
14
+ });
15
+ return message;
16
+ }
17
+ async function deliverOne(stores, router, id, options) {
18
+ const claimed = await stores.outbox.claim(id, options.leaseMs);
19
+ if (claimed === null) return "skipped";
20
+ try {
21
+ await router.deliver(claimed.event);
22
+ return await stores.outbox.complete(claimed.id, claimed.attempt) === null ? "skipped" : "delivered";
23
+ } catch (error) {
24
+ const delay = (options.retryDelayMs ?? defaultRetryDelayMs)(claimed.attempt);
25
+ return await stores.outbox.retry(claimed.id, claimed.attempt, errorMessage(error), delay) === null ? "skipped" : "retried";
26
+ }
27
+ }
28
+ async function sweepCommunicationOutbox(stores, router, options = {}) {
29
+ const limit = options.limit ?? 100;
30
+ const messages = await stores.outbox.pending(limit);
31
+ const result = {
32
+ delivered: 0,
33
+ retried: 0,
34
+ skipped: 0
35
+ };
36
+ for (const message of messages) {
37
+ const outcome = await deliverOne(stores, router, message.id, options);
38
+ result[outcome] += 1;
39
+ }
40
+ return result;
41
+ }
42
+ function createOutboxCommunicationRouter(stores, router, options = {}) {
43
+ return Object.freeze({
44
+ channels: router.channels,
45
+ async deliver(event) {
46
+ const message = await stores.outbox.enqueue(event);
47
+ if (message.state === "delivered") return;
48
+ await deliverOne(stores, router, message.id, options);
49
+ }
50
+ });
51
+ }
52
+ //#endregion
53
+ //#region src/connectors/index.ts
27
54
  function createCommunicationRouter(adapters) {
28
55
  const byChannel = /* @__PURE__ */ new Map();
29
56
  for (const adapter of adapters) {
@@ -44,6 +71,6 @@ function createCommunicationRouter(adapters) {
44
71
  });
45
72
  }
46
73
  //#endregion
47
- export { communicationEventSchema, communicationOutcomeSchema, communicationOutcomes, createCommunicationRouter };
74
+ export { communicationEventSchema, communicationOutcomeSchema, communicationOutcomes, createCommunicationRouter, createOutboxCommunicationRouter, sweepCommunicationOutbox };
48
75
 
49
76
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/connectors/index.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { changeIdSchema, repositoryIdsSchema, taskIdSchema } from \"../schema/id\";\nimport { replyToSchema } from \"../schema/common\";\nimport type { ReplyTo } from \"../schema/common\";\nimport type { SignalSource } from \"../schema/signal\";\n\nexport interface WebhookEvent {\n deliveryId: string;\n event: string;\n payload: unknown;\n}\n\nexport interface NormalizedSignal<TSource extends SignalSource = SignalSource> {\n source: TSource;\n deliveryId: string;\n replyTo: ReplyTo;\n payload: unknown;\n}\n\nexport type SignalNormalizer<TSignal extends NormalizedSignal = NormalizedSignal> = (\n event: WebhookEvent,\n) => TSignal | null;\n\nexport const communicationOutcomes = [\n \"accepted\",\n \"started\",\n \"succeeded\",\n \"failed\",\n \"needs_human\",\n \"question\",\n \"digest\",\n \"rolled_back\",\n \"budget_warning\",\n \"budget_exhausted\",\n \"message\",\n] as const;\n\nexport const communicationOutcomeSchema = z\n .enum(communicationOutcomes)\n .describe(\"Lifecycle outcome a connector should render and deliver.\");\n\nexport const communicationEventSchema = z.strictObject({\n eventId: z.string().min(1).describe(\"Stable idempotency key for this delivery.\"),\n repositoryIds: repositoryIdsSchema,\n taskId: taskIdSchema.optional(),\n changeId: changeIdSchema.optional(),\n replyTo: replyToSchema,\n outcome: communicationOutcomeSchema,\n data: z\n .record(z.string(), z.unknown())\n .describe(\"Outcome-specific data rendered by the selected connector.\"),\n});\n\nexport type CommunicationOutcome = z.infer<typeof communicationOutcomeSchema>;\nexport type CommunicationEvent = z.infer<typeof communicationEventSchema>;\n\nexport interface CommunicationAdapter {\n readonly channel: string;\n deliver(event: CommunicationEvent): Promise<void>;\n}\n\nexport interface CommunicationRouter {\n readonly channels: readonly string[];\n deliver(event: CommunicationEvent): Promise<void>;\n}\n\nexport function createCommunicationRouter(\n adapters: readonly CommunicationAdapter[],\n): CommunicationRouter {\n const byChannel = new Map<string, CommunicationAdapter>();\n for (const adapter of adapters) {\n const channel = adapter.channel.trim();\n if (channel.length === 0) {\n throw new Error(\"Communication adapter channel is required.\");\n }\n if (byChannel.has(channel)) {\n throw new Error(`Communication adapter ${channel} is registered more than once.`);\n }\n byChannel.set(channel, adapter);\n }\n const channels = Object.freeze([...byChannel.keys()]);\n\n return Object.freeze({\n channels,\n async deliver(input: CommunicationEvent): Promise<void> {\n const event = communicationEventSchema.parse(input);\n const adapter = byChannel.get(event.replyTo.channel);\n if (adapter === undefined) {\n throw new Error(`No communication adapter is registered for ${event.replyTo.channel}.`);\n }\n await adapter.deliver(event);\n },\n });\n}\n"],"mappings":";;;AAuBA,MAAa,wBAAwB;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,MAAa,6BAA6B,EACvC,KAAK,qBAAqB,CAAC,CAC3B,SAAS,0DAA0D;AAEtE,MAAa,2BAA2B,EAAE,aAAa;CACrD,SAAS,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,2CAA2C;CAC/E,eAAe;CACf,QAAQ,aAAa,SAAS;CAC9B,UAAU,eAAe,SAAS;CAClC,SAAS;CACT,SAAS;CACT,MAAM,EACH,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,CAAC,CAC/B,SAAS,2DAA2D;AACzE,CAAC;AAeD,SAAgB,0BACd,UACqB;CACrB,MAAM,4BAAY,IAAI,IAAkC;CACxD,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,UAAU,QAAQ,QAAQ,KAAK;EACrC,IAAI,QAAQ,WAAW,GACrB,MAAM,IAAI,MAAM,4CAA4C;EAE9D,IAAI,UAAU,IAAI,OAAO,GACvB,MAAM,IAAI,MAAM,yBAAyB,QAAQ,+BAA+B;EAElF,UAAU,IAAI,SAAS,OAAO;CAChC;CACA,MAAM,WAAW,OAAO,OAAO,CAAC,GAAG,UAAU,KAAK,CAAC,CAAC;CAEpD,OAAO,OAAO,OAAO;EACnB;EACA,MAAM,QAAQ,OAA0C;GACtD,MAAM,QAAQ,yBAAyB,MAAM,KAAK;GAClD,MAAM,UAAU,UAAU,IAAI,MAAM,QAAQ,OAAO;GACnD,IAAI,YAAY,KAAA,GACd,MAAM,IAAI,MAAM,8CAA8C,MAAM,QAAQ,QAAQ,EAAE;GAExF,MAAM,QAAQ,QAAQ,KAAK;EAC7B;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/connectors/outbox.ts","../../src/connectors/index.ts"],"sourcesContent":["import type { OutboxId } from \"../schema/id\";\nimport type { FactoryStores } from \"../store/engine\";\nimport type { CommunicationEvent, CommunicationRouter } from \"./index\";\n\nexport interface OutboxSweepResult {\n delivered: number;\n retried: number;\n skipped: number;\n}\n\nexport interface OutboxDeliveryOptions {\n leaseMs?: number;\n limit?: number;\n retryDelayMs?: (attempt: number) => number;\n}\n\nconst defaultRetryDelayMs = (attempt: number) =>\n Math.min(60_000 * 2 ** Math.max(0, attempt - 1), 60 * 60_000);\n\nconst credentialPatterns = [\n /github_pat_[A-Za-z0-9_]+/gu,\n /gh[psoru]_[A-Za-z0-9_]+/gu,\n /xox[baprs]-[A-Za-z0-9-]+/gu,\n /(?:Basic|Bearer) [A-Za-z0-9+/_=.:-]+/gu,\n] as const;\n\nfunction errorMessage(error: unknown): string {\n let message = error instanceof Error ? error.message : \"Unknown communication delivery error\";\n for (const pattern of credentialPatterns) {\n message = message.replace(pattern, (match) => {\n const scheme = match.startsWith(\"Basic \")\n ? \"Basic \"\n : match.startsWith(\"Bearer \")\n ? \"Bearer \"\n : \"\";\n return `${scheme}[REDACTED]`;\n });\n }\n return message;\n}\n\nasync function deliverOne(\n stores: FactoryStores,\n router: CommunicationRouter,\n id: OutboxId,\n options: OutboxDeliveryOptions,\n): Promise<\"delivered\" | \"retried\" | \"skipped\"> {\n const claimed = await stores.outbox.claim(id, options.leaseMs);\n if (claimed === null) return \"skipped\";\n try {\n await router.deliver(claimed.event);\n const completed = await stores.outbox.complete(claimed.id, claimed.attempt);\n return completed === null ? \"skipped\" : \"delivered\";\n } catch (error) {\n const delay = (options.retryDelayMs ?? defaultRetryDelayMs)(claimed.attempt);\n const retried = await stores.outbox.retry(\n claimed.id,\n claimed.attempt,\n errorMessage(error),\n delay,\n );\n return retried === null ? \"skipped\" : \"retried\";\n }\n}\n\nexport async function sweepCommunicationOutbox(\n stores: FactoryStores,\n router: CommunicationRouter,\n options: OutboxDeliveryOptions = {},\n): Promise<OutboxSweepResult> {\n const limit = options.limit ?? 100;\n const messages = await stores.outbox.pending(limit);\n const result: OutboxSweepResult = { delivered: 0, retried: 0, skipped: 0 };\n for (const message of messages) {\n const outcome = await deliverOne(stores, router, message.id, options);\n result[outcome] += 1;\n }\n return result;\n}\n\nexport function createOutboxCommunicationRouter(\n stores: FactoryStores,\n router: CommunicationRouter,\n options: OutboxDeliveryOptions = {},\n): CommunicationRouter {\n return Object.freeze({\n channels: router.channels,\n async deliver(event: CommunicationEvent): Promise<void> {\n const message = await stores.outbox.enqueue(event);\n if (message.state === \"delivered\") return;\n await deliverOne(stores, router, message.id, options);\n },\n });\n}\n","import { communicationEventSchema, type CommunicationEvent } from \"../schema/communication\";\nimport type { ReplyTo } from \"../schema/common\";\nimport type { SignalSource } from \"../schema/signal\";\n\nexport {\n communicationEventSchema,\n communicationOutcomeSchema,\n communicationOutcomes,\n type CommunicationEvent,\n type CommunicationOutcome,\n} from \"../schema/communication\";\n\nexport interface WebhookEvent {\n deliveryId: string;\n event: string;\n payload: unknown;\n}\n\nexport interface NormalizedSignal<TSource extends SignalSource = SignalSource> {\n source: TSource;\n deliveryId: string;\n replyTo: ReplyTo;\n payload: unknown;\n}\n\nexport type SignalNormalizer<TSignal extends NormalizedSignal = NormalizedSignal> = (\n event: WebhookEvent,\n) => TSignal | null;\n\nexport interface CommunicationAdapter {\n readonly channel: string;\n deliver(event: CommunicationEvent): Promise<void>;\n}\n\nexport interface CommunicationRouter {\n readonly channels: readonly string[];\n deliver(event: CommunicationEvent): Promise<void>;\n}\n\nexport function createCommunicationRouter(\n adapters: readonly CommunicationAdapter[],\n): CommunicationRouter {\n const byChannel = new Map<string, CommunicationAdapter>();\n for (const adapter of adapters) {\n const channel = adapter.channel.trim();\n if (channel.length === 0) {\n throw new Error(\"Communication adapter channel is required.\");\n }\n if (byChannel.has(channel)) {\n throw new Error(`Communication adapter ${channel} is registered more than once.`);\n }\n byChannel.set(channel, adapter);\n }\n const channels = Object.freeze([...byChannel.keys()]);\n\n return Object.freeze({\n channels,\n async deliver(input: CommunicationEvent): Promise<void> {\n const event = communicationEventSchema.parse(input);\n const adapter = byChannel.get(event.replyTo.channel);\n if (adapter === undefined) {\n throw new Error(`No communication adapter is registered for ${event.replyTo.channel}.`);\n }\n await adapter.deliver(event);\n },\n });\n}\n\nexport * from \"./outbox\";\n"],"mappings":";;AAgBA,MAAM,uBAAuB,YAC3B,KAAK,IAAI,MAAS,KAAK,KAAK,IAAI,GAAG,UAAU,CAAC,GAAG,IAAW;AAE9D,MAAM,qBAAqB;CACzB;CACA;CACA;CACA;AACF;AAEA,SAAS,aAAa,OAAwB;CAC5C,IAAI,UAAU,iBAAiB,QAAQ,MAAM,UAAU;CACvD,KAAK,MAAM,WAAW,oBACpB,UAAU,QAAQ,QAAQ,UAAU,UAAU;EAM5C,OAAO,GALQ,MAAM,WAAW,QAAQ,IACpC,WACA,MAAM,WAAW,SAAS,IACxB,YACA,GACW;CACnB,CAAC;CAEH,OAAO;AACT;AAEA,eAAe,WACb,QACA,QACA,IACA,SAC8C;CAC9C,MAAM,UAAU,MAAM,OAAO,OAAO,MAAM,IAAI,QAAQ,OAAO;CAC7D,IAAI,YAAY,MAAM,OAAO;CAC7B,IAAI;EACF,MAAM,OAAO,QAAQ,QAAQ,KAAK;EAElC,OAAO,MADiB,OAAO,OAAO,SAAS,QAAQ,IAAI,QAAQ,OAAO,MACrD,OAAO,YAAY;CAC1C,SAAS,OAAO;EACd,MAAM,SAAS,QAAQ,gBAAgB,oBAAA,CAAqB,QAAQ,OAAO;EAO3E,OAAO,MANe,OAAO,OAAO,MAClC,QAAQ,IACR,QAAQ,SACR,aAAa,KAAK,GAClB,KACF,MACmB,OAAO,YAAY;CACxC;AACF;AAEA,eAAsB,yBACpB,QACA,QACA,UAAiC,CAAC,GACN;CAC5B,MAAM,QAAQ,QAAQ,SAAS;CAC/B,MAAM,WAAW,MAAM,OAAO,OAAO,QAAQ,KAAK;CAClD,MAAM,SAA4B;EAAE,WAAW;EAAG,SAAS;EAAG,SAAS;CAAE;CACzE,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,UAAU,MAAM,WAAW,QAAQ,QAAQ,QAAQ,IAAI,OAAO;EACpE,OAAO,YAAY;CACrB;CACA,OAAO;AACT;AAEA,SAAgB,gCACd,QACA,QACA,UAAiC,CAAC,GACb;CACrB,OAAO,OAAO,OAAO;EACnB,UAAU,OAAO;EACjB,MAAM,QAAQ,OAA0C;GACtD,MAAM,UAAU,MAAM,OAAO,OAAO,QAAQ,KAAK;GACjD,IAAI,QAAQ,UAAU,aAAa;GACnC,MAAM,WAAW,QAAQ,QAAQ,QAAQ,IAAI,OAAO;EACtD;CACF,CAAC;AACH;;;ACtDA,SAAgB,0BACd,UACqB;CACrB,MAAM,4BAAY,IAAI,IAAkC;CACxD,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,UAAU,QAAQ,QAAQ,KAAK;EACrC,IAAI,QAAQ,WAAW,GACrB,MAAM,IAAI,MAAM,4CAA4C;EAE9D,IAAI,UAAU,IAAI,OAAO,GACvB,MAAM,IAAI,MAAM,yBAAyB,QAAQ,+BAA+B;EAElF,UAAU,IAAI,SAAS,OAAO;CAChC;CACA,MAAM,WAAW,OAAO,OAAO,CAAC,GAAG,UAAU,KAAK,CAAC,CAAC;CAEpD,OAAO,OAAO,OAAO;EACnB;EACA,MAAM,QAAQ,OAA0C;GACtD,MAAM,QAAQ,yBAAyB,MAAM,KAAK;GAClD,MAAM,UAAU,UAAU,IAAI,MAAM,QAAQ,OAAO;GACnD,IAAI,YAAY,KAAA,GACd,MAAM,IAAI,MAAM,8CAA8C,MAAM,QAAQ,QAAQ,EAAE;GAExF,MAAM,QAAQ,QAAQ,KAAK;EAC7B;CACF,CAAC;AACH"}
@@ -1,4 +1,5 @@
1
- import { a as NormalizedSignal, n as CommunicationEvent, t as CommunicationAdapter } from "../index-Cy4jfXh5.mjs";
1
+ import { st as CommunicationEvent } from "../engine-DFHU2Pe-.mjs";
2
+ import { r as NormalizedSignal, t as CommunicationAdapter } from "../index-Bw6hggoH.mjs";
2
3
  //#region src/connectors/slack.d.ts
3
4
  interface SlackCommunicationDelivery {
4
5
  eventId: string;
@@ -24,6 +25,16 @@ interface SlackReplyTarget {
24
25
  channelId: string;
25
26
  threadTs: string;
26
27
  }
28
+ type SlackPullRequestSelection = {
29
+ status: "none";
30
+ } | {
31
+ status: "ambiguous";
32
+ } | {
33
+ status: "selected";
34
+ pullRequestNumber: number;
35
+ };
36
+ /** Resolve an explicit pull-request reference after the repository has been selected. */
37
+ declare function selectSlackPullRequest(text: string, repositorySlug: string): SlackPullRequestSelection;
27
38
  /** Parse Eve's Slack continuation token into its channel and thread target. */
28
39
  declare function parseSlackReplyAddress(address: string): SlackReplyTarget;
29
40
  /** Render a provider-neutral communication event as Slack mrkdwn. */
@@ -77,5 +88,5 @@ declare function selectSlackRepositories(text: string, repositories: readonly st
77
88
  */
78
89
  declare function selectSlackRepository(text: string, repositories: readonly string[]): SlackRepositorySelection;
79
90
  //#endregion
80
- export { SlackCommunicationAdapter, SlackCommunicationAdapterOptions, SlackCommunicationDelivery, SlackMessageEvent, SlackMessageSignal, SlackReplyTarget, SlackRepositoriesSelection, SlackRepositorySelection, createSlackCommunicationAdapter, normalizeSlackMessage, parseSlackReplyAddress, renderSlackCommunicationEvent, selectSlackRepositories, selectSlackRepository };
91
+ export { SlackCommunicationAdapter, SlackCommunicationAdapterOptions, SlackCommunicationDelivery, SlackMessageEvent, SlackMessageSignal, SlackPullRequestSelection, SlackReplyTarget, SlackRepositoriesSelection, SlackRepositorySelection, createSlackCommunicationAdapter, normalizeSlackMessage, parseSlackReplyAddress, renderSlackCommunicationEvent, selectSlackPullRequest, selectSlackRepositories, selectSlackRepository };
81
92
  //# sourceMappingURL=slack.d.mts.map
@@ -1,4 +1,5 @@
1
- import { communicationEventSchema } from "./index.mjs";
1
+ import { t as communicationEventSchema } from "../communication-Kd9nU5RP.mjs";
2
+ import "./index.mjs";
2
3
  import { createHash } from "node:crypto";
3
4
  //#region src/connectors/slack.ts
4
5
  const slackOutcomeLabels = {
@@ -42,6 +43,21 @@ function slackLink(url, label) {
42
43
  return null;
43
44
  }
44
45
  }
46
+ /** Resolve an explicit pull-request reference after the repository has been selected. */
47
+ function selectSlackPullRequest(text, repositorySlug) {
48
+ const escapedSlug = repositorySlug.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
49
+ const references = [...text.matchAll(new RegExp(`https://github\\.com/${escapedSlug}/pull/(\\d+)`, "giu")), ...text.matchAll(/\b(?:pr|pull\s+request)\s*#?\s*(\d+)\b/giu)].flatMap((match) => {
50
+ const value = Number(match[1]);
51
+ return Number.isSafeInteger(value) && value > 0 ? [value] : [];
52
+ });
53
+ const distinct = [...new Set(references)];
54
+ if (distinct.length === 0) return { status: "none" };
55
+ if (distinct.length > 1) return { status: "ambiguous" };
56
+ return {
57
+ status: "selected",
58
+ pullRequestNumber: distinct[0]
59
+ };
60
+ }
45
61
  /** Parse Eve's Slack continuation token into its channel and thread target. */
46
62
  function parseSlackReplyAddress(address) {
47
63
  const separator = address.indexOf(":");
@@ -202,6 +218,6 @@ function selectSlackRepository(text, repositories) {
202
218
  return { status: "required" };
203
219
  }
204
220
  //#endregion
205
- export { createSlackCommunicationAdapter, normalizeSlackMessage, parseSlackReplyAddress, renderSlackCommunicationEvent, selectSlackRepositories, selectSlackRepository };
221
+ export { createSlackCommunicationAdapter, normalizeSlackMessage, parseSlackReplyAddress, renderSlackCommunicationEvent, selectSlackPullRequest, selectSlackRepositories, selectSlackRepository };
206
222
 
207
223
  //# sourceMappingURL=slack.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"slack.mjs","names":[],"sources":["../../src/connectors/slack.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport {\n communicationEventSchema,\n type CommunicationAdapter,\n type CommunicationEvent,\n type CommunicationOutcome,\n type NormalizedSignal,\n} from \"./index\";\n\nconst slackOutcomeLabels: Record<CommunicationOutcome, string> = {\n accepted: \"Request accepted\",\n started: \"Work started\",\n succeeded: \"Work completed\",\n failed: \"Work failed\",\n needs_human: \"Human action required\",\n question: \"Question requires an answer\",\n digest: \"Factory digest\",\n rolled_back: \"Change rolled back\",\n budget_warning: \"Factory budget warning\",\n budget_exhausted: \"Factory budget exhausted\",\n message: \"Factory update\",\n};\n\nconst slackDetailFields = [\"title\", \"message\", \"summary\", \"question\", \"requestedAction\", \"reason\", \"url\"] as const;\nconst slackListFields = [\"items\", \"options\"] as const;\nconst slackTextLimit = 12_000;\nconst slackOwnerOutcomes = new Set<CommunicationOutcome>([\n \"digest\",\n \"budget_warning\",\n \"budget_exhausted\",\n]);\n\nfunction escapeSlackText(value: string): string {\n return value.replaceAll(\"&\", \"&amp;\").replaceAll(\"<\", \"&lt;\").replaceAll(\">\", \"&gt;\");\n}\n\nfunction slackLink(url: string, label: string): string | null {\n try {\n const parsed = new URL(url);\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") return null;\n return `<${escapeSlackText(parsed.toString())}|${escapeSlackText(label)}>`;\n } catch {\n return null;\n }\n}\n\nexport interface SlackCommunicationDelivery {\n eventId: string;\n clientMessageId: string;\n text: string;\n target:\n | { type: \"owner\" }\n | { type: \"thread\"; channelId: string; threadTs: string };\n}\n\nexport interface SlackCommunicationAdapter extends CommunicationAdapter {\n readonly channel: \"slack\";\n render(event: CommunicationEvent): string;\n}\n\nexport interface SlackCommunicationAdapterOptions {\n send(delivery: SlackCommunicationDelivery): Promise<void>;\n render?: (event: CommunicationEvent) => string;\n}\n\nexport interface SlackReplyTarget {\n channelId: string;\n threadTs: string;\n}\n\n/** Parse Eve's Slack continuation token into its channel and thread target. */\nexport function parseSlackReplyAddress(address: string): SlackReplyTarget {\n const separator = address.indexOf(\":\");\n const channelId = address.slice(0, separator);\n const threadTs = address.slice(separator + 1);\n if (separator <= 0 || !/^[A-Z][A-Z0-9]+$/.test(channelId) || !/^\\d+\\.\\d+$/.test(threadTs)) {\n throw new Error(`Invalid Slack reply address: ${address}.`);\n }\n return { channelId, threadTs };\n}\n\n/** Render a provider-neutral communication event as Slack mrkdwn. */\nexport function renderSlackCommunicationEvent(input: CommunicationEvent): string {\n const event = communicationEventSchema.parse(input);\n const headline = `*${slackOutcomeLabels[event.outcome]}*`;\n const details = new Map(slackDetailFields.flatMap((field) => {\n const value = event.data[field];\n return typeof value === \"string\" && value.trim() !== \"\" ? [[field, value.trim()] as const] : [];\n }));\n const primaryField = ([\"title\", \"question\", \"requestedAction\"] as const)\n .find((field) => details.has(field));\n const primary = primaryField === undefined ? undefined : details.get(primaryField);\n const url = details.get(\"url\");\n const linkedPrimary = primary !== undefined && url !== undefined ? slackLink(url, primary) : null;\n if (primaryField !== undefined) details.delete(primaryField);\n if (linkedPrimary !== null) details.delete(\"url\");\n\n const detailLines = [...new Set([\n ...(linkedPrimary !== null ? [linkedPrimary] : primary === undefined ? [] : [escapeSlackText(primary)]),\n ...[...details.values()].map(escapeSlackText),\n ])];\n const lists = slackListFields.flatMap((field) => {\n const value = event.data[field];\n if (!Array.isArray(value)) return [];\n return value.flatMap((item) =>\n typeof item === \"string\" && item.trim() !== \"\" ? [`• ${escapeSlackText(item.trim())}`] : []\n );\n });\n const text = [headline, ...detailLines, ...(lists.length === 0 ? [] : [lists.join(\"\\n\")])]\n .join(\"\\n\\n\");\n return text.length <= slackTextLimit ? text : `${text.slice(0, slackTextLimit - 1)}…`;\n}\n\nfunction slackClientMessageId(eventId: string): string {\n const digest = createHash(\"sha256\").update(eventId).digest(\"hex\").slice(0, 32);\n return [\n digest.slice(0, 8),\n digest.slice(8, 12),\n digest.slice(12, 16),\n digest.slice(16, 20),\n digest.slice(20),\n ].join(\"-\");\n}\n\n/** Bind Slack rendering and delivery to the channel-neutral communication contract. */\nexport function createSlackCommunicationAdapter(\n options: SlackCommunicationAdapterOptions,\n): SlackCommunicationAdapter {\n const render = options.render ?? renderSlackCommunicationEvent;\n return Object.freeze({\n channel: \"slack\" as const,\n render,\n async deliver(input: CommunicationEvent): Promise<void> {\n const event = communicationEventSchema.parse(input);\n if (event.replyTo.channel !== \"slack\") {\n throw new Error(`Slack communication adapter cannot deliver to ${event.replyTo.channel}.`);\n }\n const target = slackOwnerOutcomes.has(event.outcome)\n ? { type: \"owner\" as const }\n : { type: \"thread\" as const, ...parseSlackReplyAddress(event.replyTo.address) };\n await options.send({\n eventId: event.eventId,\n clientMessageId: slackClientMessageId(event.eventId),\n text: render(event),\n target,\n });\n },\n });\n}\n\nexport interface SlackMessageEvent {\n teamId: string;\n channelId: string;\n messageTs: string;\n threadTs: string;\n actorId: string;\n text: string;\n replyAddress: string;\n repositorySlugs: readonly [string, ...string[]];\n payload: unknown;\n}\n\nexport interface SlackMessageSignal extends NormalizedSignal<\"slack\"> {\n repositorySlugs: readonly [string, ...string[]];\n message: {\n teamId: string;\n channelId: string;\n messageTs: string;\n threadTs: string;\n actorId: string;\n text: string;\n };\n}\n\n/** Normalize an Eve-verified Slack message after the caller resolves its repositories. */\nexport function normalizeSlackMessage(event: SlackMessageEvent): SlackMessageSignal {\n return {\n source: \"slack\",\n deliveryId: `${event.teamId}:${event.channelId}:${event.messageTs}`,\n repositorySlugs: [\n event.repositorySlugs[0].toLowerCase(),\n ...event.repositorySlugs.slice(1).map((slug) => slug.toLowerCase()),\n ],\n replyTo: { channel: \"slack\", address: event.replyAddress },\n message: {\n teamId: event.teamId,\n channelId: event.channelId,\n messageTs: event.messageTs,\n threadTs: event.threadTs,\n actorId: event.actorId,\n text: event.text,\n },\n payload: event.payload,\n };\n}\n\nexport type SlackRepositorySelection =\n | { status: \"selected\"; repositorySlug: string }\n | { status: \"required\" }\n | { status: \"ambiguous\"; repositorySlugs: readonly string[] };\n\nexport type SlackRepositoriesSelection =\n | { status: \"selected\"; repositorySlugs: readonly [string, ...string[]] }\n | { status: \"required\" };\n\nfunction escapedPattern(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction isDelimitedTextMentioned(text: string, value: string): boolean {\n return new RegExp(\n `(^|[^a-z0-9._/-])${escapedPattern(value)}($|[^a-z0-9._/-])`,\n \"i\",\n ).test(text);\n}\n\nfunction isRepositoryReferenceMentioned(text: string, slug: string): boolean {\n if (isDelimitedTextMentioned(text, slug)) return true;\n return new RegExp(\n `https?://github\\\\.com/${escapedPattern(slug)}(?:\\\\.git)?/?(?=$|[\\\\s?#|>)\\\\],.!])`,\n \"i\",\n ).test(text);\n}\n\nfunction repositoryName(slug: string): string {\n return slug.slice(slug.lastIndexOf(\"/\") + 1);\n}\n\ninterface SlackRepositoryMentions {\n repositories: readonly string[];\n matches: readonly string[];\n ambiguous: readonly string[];\n candidates: readonly string[];\n}\n\nfunction resolveSlackRepositoryMentions(\n text: string,\n repositories: readonly string[],\n): SlackRepositoryMentions {\n const normalized = [...new Set(repositories.map((slug) => slug.toLowerCase()))];\n const entries = normalized.map((slug) => ({ slug, name: repositoryName(slug) }));\n const counts = new Map<string, number>();\n for (const { name } of entries) {\n counts.set(name, (counts.get(name) ?? 0) + 1);\n }\n const mentionedNames = new Set(\n [...counts.keys()].filter((name) => isDelimitedTextMentioned(text, name)),\n );\n const matches = new Set(entries.flatMap(({ slug, name }) =>\n isRepositoryReferenceMentioned(text, slug) || (\n counts.get(name) === 1 && mentionedNames.has(name)\n )\n ? [slug]\n : []\n ));\n const ambiguous = new Set(entries.flatMap(({ slug, name }) =>\n counts.get(name) !== 1 && mentionedNames.has(name) ? [slug] : []\n ));\n\n return {\n repositories: normalized,\n matches: normalized.filter((slug) => matches.has(slug)),\n ambiguous: normalized.filter((slug) => ambiguous.has(slug)),\n candidates: normalized.filter((slug) => matches.has(slug) || ambiguous.has(slug)),\n };\n}\n\n/** Select every named configured repository, preserving configuration order. */\nexport function selectSlackRepositories(\n text: string,\n repositories: readonly string[],\n): SlackRepositoriesSelection {\n const mentions = resolveSlackRepositoryMentions(text, repositories);\n if (mentions.repositories.length === 1) {\n return { status: \"selected\", repositorySlugs: [mentions.repositories[0]!] };\n }\n if (mentions.ambiguous.length > 0) return { status: \"required\" };\n return mentions.matches.length === 0\n ? { status: \"required\" }\n : {\n status: \"selected\",\n repositorySlugs: [mentions.matches[0]!, ...mentions.matches.slice(1)],\n };\n}\n\n/**\n * Select the only configured repository, or match a full slug, unique short\n * name, or GitHub repository URL when a Slack surface fronts multiple repositories.\n */\nexport function selectSlackRepository(\n text: string,\n repositories: readonly string[],\n): SlackRepositorySelection {\n const mentions = resolveSlackRepositoryMentions(text, repositories);\n if (mentions.repositories.length === 1) {\n return { status: \"selected\", repositorySlug: mentions.repositories[0]! };\n }\n if (mentions.candidates.length === 1) {\n return { status: \"selected\", repositorySlug: mentions.candidates[0]! };\n }\n if (mentions.candidates.length > 1) {\n return { status: \"ambiguous\", repositorySlugs: mentions.candidates };\n }\n return { status: \"required\" };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"slack.mjs","names":[],"sources":["../../src/connectors/slack.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport {\n communicationEventSchema,\n type CommunicationAdapter,\n type CommunicationEvent,\n type CommunicationOutcome,\n type NormalizedSignal,\n} from \"./index\";\n\nconst slackOutcomeLabels: Record<CommunicationOutcome, string> = {\n accepted: \"Request accepted\",\n started: \"Work started\",\n succeeded: \"Work completed\",\n failed: \"Work failed\",\n needs_human: \"Human action required\",\n question: \"Question requires an answer\",\n digest: \"Factory digest\",\n rolled_back: \"Change rolled back\",\n budget_warning: \"Factory budget warning\",\n budget_exhausted: \"Factory budget exhausted\",\n message: \"Factory update\",\n};\n\nconst slackDetailFields = [\"title\", \"message\", \"summary\", \"question\", \"requestedAction\", \"reason\", \"url\"] as const;\nconst slackListFields = [\"items\", \"options\"] as const;\nconst slackTextLimit = 12_000;\nconst slackOwnerOutcomes = new Set<CommunicationOutcome>([\n \"digest\",\n \"budget_warning\",\n \"budget_exhausted\",\n]);\n\nfunction escapeSlackText(value: string): string {\n return value.replaceAll(\"&\", \"&amp;\").replaceAll(\"<\", \"&lt;\").replaceAll(\">\", \"&gt;\");\n}\n\nfunction slackLink(url: string, label: string): string | null {\n try {\n const parsed = new URL(url);\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") return null;\n return `<${escapeSlackText(parsed.toString())}|${escapeSlackText(label)}>`;\n } catch {\n return null;\n }\n}\n\nexport interface SlackCommunicationDelivery {\n eventId: string;\n clientMessageId: string;\n text: string;\n target:\n | { type: \"owner\" }\n | { type: \"thread\"; channelId: string; threadTs: string };\n}\n\nexport interface SlackCommunicationAdapter extends CommunicationAdapter {\n readonly channel: \"slack\";\n render(event: CommunicationEvent): string;\n}\n\nexport interface SlackCommunicationAdapterOptions {\n send(delivery: SlackCommunicationDelivery): Promise<void>;\n render?: (event: CommunicationEvent) => string;\n}\n\nexport interface SlackReplyTarget {\n channelId: string;\n threadTs: string;\n}\n\nexport type SlackPullRequestSelection =\n | { status: \"none\" }\n | { status: \"ambiguous\" }\n | { status: \"selected\"; pullRequestNumber: number };\n\n/** Resolve an explicit pull-request reference after the repository has been selected. */\nexport function selectSlackPullRequest(\n text: string,\n repositorySlug: string,\n): SlackPullRequestSelection {\n const escapedSlug = repositorySlug.replace(/[.*+?^${}()|[\\]\\\\]/gu, \"\\\\$&\");\n const references = [\n ...text.matchAll(new RegExp(`https://github\\\\.com/${escapedSlug}/pull/(\\\\d+)`, \"giu\")),\n ...text.matchAll(/\\b(?:pr|pull\\s+request)\\s*#?\\s*(\\d+)\\b/giu),\n ].flatMap((match) => {\n const value = Number(match[1]);\n return Number.isSafeInteger(value) && value > 0 ? [value] : [];\n });\n const distinct = [...new Set(references)];\n if (distinct.length === 0) return { status: \"none\" };\n if (distinct.length > 1) return { status: \"ambiguous\" };\n return { status: \"selected\", pullRequestNumber: distinct[0]! };\n}\n\n/** Parse Eve's Slack continuation token into its channel and thread target. */\nexport function parseSlackReplyAddress(address: string): SlackReplyTarget {\n const separator = address.indexOf(\":\");\n const channelId = address.slice(0, separator);\n const threadTs = address.slice(separator + 1);\n if (separator <= 0 || !/^[A-Z][A-Z0-9]+$/.test(channelId) || !/^\\d+\\.\\d+$/.test(threadTs)) {\n throw new Error(`Invalid Slack reply address: ${address}.`);\n }\n return { channelId, threadTs };\n}\n\n/** Render a provider-neutral communication event as Slack mrkdwn. */\nexport function renderSlackCommunicationEvent(input: CommunicationEvent): string {\n const event = communicationEventSchema.parse(input);\n const headline = `*${slackOutcomeLabels[event.outcome]}*`;\n const details = new Map(slackDetailFields.flatMap((field) => {\n const value = event.data[field];\n return typeof value === \"string\" && value.trim() !== \"\" ? [[field, value.trim()] as const] : [];\n }));\n const primaryField = ([\"title\", \"question\", \"requestedAction\"] as const)\n .find((field) => details.has(field));\n const primary = primaryField === undefined ? undefined : details.get(primaryField);\n const url = details.get(\"url\");\n const linkedPrimary = primary !== undefined && url !== undefined ? slackLink(url, primary) : null;\n if (primaryField !== undefined) details.delete(primaryField);\n if (linkedPrimary !== null) details.delete(\"url\");\n\n const detailLines = [...new Set([\n ...(linkedPrimary !== null ? [linkedPrimary] : primary === undefined ? [] : [escapeSlackText(primary)]),\n ...[...details.values()].map(escapeSlackText),\n ])];\n const lists = slackListFields.flatMap((field) => {\n const value = event.data[field];\n if (!Array.isArray(value)) return [];\n return value.flatMap((item) =>\n typeof item === \"string\" && item.trim() !== \"\" ? [`• ${escapeSlackText(item.trim())}`] : []\n );\n });\n const text = [headline, ...detailLines, ...(lists.length === 0 ? [] : [lists.join(\"\\n\")])]\n .join(\"\\n\\n\");\n return text.length <= slackTextLimit ? text : `${text.slice(0, slackTextLimit - 1)}…`;\n}\n\nfunction slackClientMessageId(eventId: string): string {\n const digest = createHash(\"sha256\").update(eventId).digest(\"hex\").slice(0, 32);\n return [\n digest.slice(0, 8),\n digest.slice(8, 12),\n digest.slice(12, 16),\n digest.slice(16, 20),\n digest.slice(20),\n ].join(\"-\");\n}\n\n/** Bind Slack rendering and delivery to the channel-neutral communication contract. */\nexport function createSlackCommunicationAdapter(\n options: SlackCommunicationAdapterOptions,\n): SlackCommunicationAdapter {\n const render = options.render ?? renderSlackCommunicationEvent;\n return Object.freeze({\n channel: \"slack\" as const,\n render,\n async deliver(input: CommunicationEvent): Promise<void> {\n const event = communicationEventSchema.parse(input);\n if (event.replyTo.channel !== \"slack\") {\n throw new Error(`Slack communication adapter cannot deliver to ${event.replyTo.channel}.`);\n }\n const target = slackOwnerOutcomes.has(event.outcome)\n ? { type: \"owner\" as const }\n : { type: \"thread\" as const, ...parseSlackReplyAddress(event.replyTo.address) };\n await options.send({\n eventId: event.eventId,\n clientMessageId: slackClientMessageId(event.eventId),\n text: render(event),\n target,\n });\n },\n });\n}\n\nexport interface SlackMessageEvent {\n teamId: string;\n channelId: string;\n messageTs: string;\n threadTs: string;\n actorId: string;\n text: string;\n replyAddress: string;\n repositorySlugs: readonly [string, ...string[]];\n payload: unknown;\n}\n\nexport interface SlackMessageSignal extends NormalizedSignal<\"slack\"> {\n repositorySlugs: readonly [string, ...string[]];\n message: {\n teamId: string;\n channelId: string;\n messageTs: string;\n threadTs: string;\n actorId: string;\n text: string;\n };\n}\n\n/** Normalize an Eve-verified Slack message after the caller resolves its repositories. */\nexport function normalizeSlackMessage(event: SlackMessageEvent): SlackMessageSignal {\n return {\n source: \"slack\",\n deliveryId: `${event.teamId}:${event.channelId}:${event.messageTs}`,\n repositorySlugs: [\n event.repositorySlugs[0].toLowerCase(),\n ...event.repositorySlugs.slice(1).map((slug) => slug.toLowerCase()),\n ],\n replyTo: { channel: \"slack\", address: event.replyAddress },\n message: {\n teamId: event.teamId,\n channelId: event.channelId,\n messageTs: event.messageTs,\n threadTs: event.threadTs,\n actorId: event.actorId,\n text: event.text,\n },\n payload: event.payload,\n };\n}\n\nexport type SlackRepositorySelection =\n | { status: \"selected\"; repositorySlug: string }\n | { status: \"required\" }\n | { status: \"ambiguous\"; repositorySlugs: readonly string[] };\n\nexport type SlackRepositoriesSelection =\n | { status: \"selected\"; repositorySlugs: readonly [string, ...string[]] }\n | { status: \"required\" };\n\nfunction escapedPattern(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction isDelimitedTextMentioned(text: string, value: string): boolean {\n return new RegExp(\n `(^|[^a-z0-9._/-])${escapedPattern(value)}($|[^a-z0-9._/-])`,\n \"i\",\n ).test(text);\n}\n\nfunction isRepositoryReferenceMentioned(text: string, slug: string): boolean {\n if (isDelimitedTextMentioned(text, slug)) return true;\n return new RegExp(\n `https?://github\\\\.com/${escapedPattern(slug)}(?:\\\\.git)?/?(?=$|[\\\\s?#|>)\\\\],.!])`,\n \"i\",\n ).test(text);\n}\n\nfunction repositoryName(slug: string): string {\n return slug.slice(slug.lastIndexOf(\"/\") + 1);\n}\n\ninterface SlackRepositoryMentions {\n repositories: readonly string[];\n matches: readonly string[];\n ambiguous: readonly string[];\n candidates: readonly string[];\n}\n\nfunction resolveSlackRepositoryMentions(\n text: string,\n repositories: readonly string[],\n): SlackRepositoryMentions {\n const normalized = [...new Set(repositories.map((slug) => slug.toLowerCase()))];\n const entries = normalized.map((slug) => ({ slug, name: repositoryName(slug) }));\n const counts = new Map<string, number>();\n for (const { name } of entries) {\n counts.set(name, (counts.get(name) ?? 0) + 1);\n }\n const mentionedNames = new Set(\n [...counts.keys()].filter((name) => isDelimitedTextMentioned(text, name)),\n );\n const matches = new Set(entries.flatMap(({ slug, name }) =>\n isRepositoryReferenceMentioned(text, slug) || (\n counts.get(name) === 1 && mentionedNames.has(name)\n )\n ? [slug]\n : []\n ));\n const ambiguous = new Set(entries.flatMap(({ slug, name }) =>\n counts.get(name) !== 1 && mentionedNames.has(name) ? [slug] : []\n ));\n\n return {\n repositories: normalized,\n matches: normalized.filter((slug) => matches.has(slug)),\n ambiguous: normalized.filter((slug) => ambiguous.has(slug)),\n candidates: normalized.filter((slug) => matches.has(slug) || ambiguous.has(slug)),\n };\n}\n\n/** Select every named configured repository, preserving configuration order. */\nexport function selectSlackRepositories(\n text: string,\n repositories: readonly string[],\n): SlackRepositoriesSelection {\n const mentions = resolveSlackRepositoryMentions(text, repositories);\n if (mentions.repositories.length === 1) {\n return { status: \"selected\", repositorySlugs: [mentions.repositories[0]!] };\n }\n if (mentions.ambiguous.length > 0) return { status: \"required\" };\n return mentions.matches.length === 0\n ? { status: \"required\" }\n : {\n status: \"selected\",\n repositorySlugs: [mentions.matches[0]!, ...mentions.matches.slice(1)],\n };\n}\n\n/**\n * Select the only configured repository, or match a full slug, unique short\n * name, or GitHub repository URL when a Slack surface fronts multiple repositories.\n */\nexport function selectSlackRepository(\n text: string,\n repositories: readonly string[],\n): SlackRepositorySelection {\n const mentions = resolveSlackRepositoryMentions(text, repositories);\n if (mentions.repositories.length === 1) {\n return { status: \"selected\", repositorySlug: mentions.repositories[0]! };\n }\n if (mentions.candidates.length === 1) {\n return { status: \"selected\", repositorySlug: mentions.candidates[0]! };\n }\n if (mentions.candidates.length > 1) {\n return { status: \"ambiguous\", repositorySlugs: mentions.candidates };\n }\n return { status: \"required\" };\n}\n"],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ import { n as RecordNotFoundError } from "./driver-Bbd25rNM.mjs";
2
+ //#region src/coordination.ts
3
+ /** Create one non-executable parent and one independently executable child per repository. */
4
+ async function createCoordination(stores, input) {
5
+ const repositoryIds = [...new Set(input.repositoryIds)];
6
+ if (repositoryIds.length < 2) throw new Error("coordination requires at least two distinct repositories");
7
+ const parent = await stores.tasks.create({
8
+ repositoryIds,
9
+ kind: "coordination",
10
+ origin: input.signalId === void 0 ? { operator: input.operator } : { signalId: input.signalId },
11
+ replyTo: input.replyTo,
12
+ dedupeKey: input.dedupeKey
13
+ });
14
+ return {
15
+ parent: parent.state === "queued" ? await stores.tasks.transition(parent.id, "running", { reason: "repository-scoped child tasks created" }) : parent,
16
+ children: await Promise.all(repositoryIds.map((repositoryId) => stores.tasks.create({
17
+ repositoryIds: [repositoryId],
18
+ kind: "code_change",
19
+ origin: { parentTaskId: parent.id },
20
+ replyTo: input.replyTo,
21
+ dedupeKey: input.dedupeKey === void 0 ? void 0 : `${input.dedupeKey}:${repositoryId}`
22
+ })))
23
+ };
24
+ }
25
+ /** Reconcile a coordination parent from its direct children without ever dispatching the parent. */
26
+ async function reconcileCoordination(stores, parentTaskId, options) {
27
+ const parent = await stores.tasks.get(parentTaskId);
28
+ if (parent === null) throw new RecordNotFoundError("tasks", parentTaskId);
29
+ if (parent.kind !== "coordination") throw new Error(`task ${parentTaskId} is ${parent.kind}, not coordination`);
30
+ const children = (await stores.tasks.list()).filter((task) => task.origin.parentTaskId === parentTaskId);
31
+ if (children.length !== parent.repositoryIds.length) return {
32
+ status: "pending",
33
+ parent,
34
+ children
35
+ };
36
+ if (parent.state === "succeeded" || parent.state === "failed") return {
37
+ status: parent.state,
38
+ transitioned: false,
39
+ parent,
40
+ children
41
+ };
42
+ if (children.some((child) => child.state === "failed" || child.state === "cancelled")) {
43
+ await options.present({
44
+ status: "failed",
45
+ parent,
46
+ children
47
+ });
48
+ return {
49
+ status: "failed",
50
+ transitioned: true,
51
+ parent: await stores.tasks.transition(parent.id, "failed", { reason: "one or more repository tasks failed" }),
52
+ children
53
+ };
54
+ }
55
+ if (!children.every((child) => child.state === "succeeded")) return {
56
+ status: "pending",
57
+ parent,
58
+ children
59
+ };
60
+ await options.present({
61
+ status: "succeeded",
62
+ parent,
63
+ children
64
+ });
65
+ await stores.tasks.transition(parent.id, "verifying", { reason: "all repository tasks succeeded" });
66
+ return {
67
+ status: "succeeded",
68
+ transitioned: true,
69
+ parent: await stores.tasks.transition(parent.id, "succeeded", { reason: "aggregate multi-repository handoff is ready" }),
70
+ children
71
+ };
72
+ }
73
+ //#endregion
74
+ export { reconcileCoordination as n, createCoordination as t };
75
+
76
+ //# sourceMappingURL=coordination-Cq08hb1l.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coordination-Cq08hb1l.mjs","names":[],"sources":["../src/coordination.ts"],"sourcesContent":["import type { ReplyTo } from \"./schema/common\";\nimport type { RepositoryId, SignalId, TaskId } from \"./schema/id\";\nimport type { Task } from \"./schema/task\";\nimport { RecordNotFoundError } from \"./store/driver\";\nimport type { FactoryStores } from \"./store/engine\";\n\ninterface CreateCoordinationInputBase {\n repositoryIds: readonly [RepositoryId, RepositoryId, ...RepositoryId[]];\n replyTo: ReplyTo;\n /** Same key recreates neither the parent nor its repository-scoped children. */\n dedupeKey?: string;\n}\n\nexport type CreateCoordinationInput = CreateCoordinationInputBase &\n ({ signalId: SignalId; operator?: never } | { operator: string; signalId?: never });\n\nexport interface CoordinationPlan {\n parent: Task;\n children: readonly Task[];\n}\n\nexport type CoordinationReconciliation =\n | { status: \"pending\"; parent: Task; children: readonly Task[] }\n | { status: \"succeeded\" | \"failed\"; transitioned: boolean; parent: Task; children: readonly Task[] };\n\nexport interface ReconcileCoordinationOptions {\n present(input: {\n status: \"succeeded\" | \"failed\";\n parent: Task;\n children: readonly Task[];\n }): Promise<void>;\n}\n\n/** Create one non-executable parent and one independently executable child per repository. */\nexport async function createCoordination(\n stores: FactoryStores,\n input: CreateCoordinationInput,\n): Promise<CoordinationPlan> {\n const repositoryIds = [...new Set(input.repositoryIds)];\n if (repositoryIds.length < 2) {\n throw new Error(\"coordination requires at least two distinct repositories\");\n }\n const parent = await stores.tasks.create({\n repositoryIds,\n kind: \"coordination\",\n origin: input.signalId === undefined ? { operator: input.operator } : { signalId: input.signalId },\n replyTo: input.replyTo,\n dedupeKey: input.dedupeKey,\n });\n const running = parent.state === \"queued\"\n ? await stores.tasks.transition(parent.id, \"running\", {\n reason: \"repository-scoped child tasks created\",\n })\n : parent;\n const children = await Promise.all(\n repositoryIds.map((repositoryId) =>\n stores.tasks.create({\n repositoryIds: [repositoryId],\n kind: \"code_change\",\n origin: { parentTaskId: parent.id },\n replyTo: input.replyTo,\n dedupeKey: input.dedupeKey === undefined ? undefined : `${input.dedupeKey}:${repositoryId}`,\n }),\n ),\n );\n return { parent: running, children };\n}\n\n/** Reconcile a coordination parent from its direct children without ever dispatching the parent. */\nexport async function reconcileCoordination(\n stores: FactoryStores,\n parentTaskId: TaskId,\n options: ReconcileCoordinationOptions,\n): Promise<CoordinationReconciliation> {\n const parent = await stores.tasks.get(parentTaskId);\n if (parent === null) throw new RecordNotFoundError(\"tasks\", parentTaskId);\n if (parent.kind !== \"coordination\") {\n throw new Error(`task ${parentTaskId} is ${parent.kind}, not coordination`);\n }\n const children = (await stores.tasks.list()).filter(\n (task) => task.origin.parentTaskId === parentTaskId,\n );\n if (children.length !== parent.repositoryIds.length) {\n return { status: \"pending\", parent, children };\n }\n if (parent.state === \"succeeded\" || parent.state === \"failed\") {\n return { status: parent.state, transitioned: false, parent, children };\n }\n if (children.some((child) => child.state === \"failed\" || child.state === \"cancelled\")) {\n await options.present({ status: \"failed\", parent, children });\n const failed = await stores.tasks.transition(parent.id, \"failed\", {\n reason: \"one or more repository tasks failed\",\n });\n return { status: \"failed\", transitioned: true, parent: failed, children };\n }\n if (!children.every((child) => child.state === \"succeeded\")) {\n return { status: \"pending\", parent, children };\n }\n await options.present({ status: \"succeeded\", parent, children });\n await stores.tasks.transition(parent.id, \"verifying\", {\n reason: \"all repository tasks succeeded\",\n });\n const succeeded = await stores.tasks.transition(parent.id, \"succeeded\", {\n reason: \"aggregate multi-repository handoff is ready\",\n });\n return { status: \"succeeded\", transitioned: true, parent: succeeded, children };\n}\n"],"mappings":";;;AAkCA,eAAsB,mBACpB,QACA,OAC2B;CAC3B,MAAM,gBAAgB,CAAC,GAAG,IAAI,IAAI,MAAM,aAAa,CAAC;CACtD,IAAI,cAAc,SAAS,GACzB,MAAM,IAAI,MAAM,0DAA0D;CAE5E,MAAM,SAAS,MAAM,OAAO,MAAM,OAAO;EACvC;EACA,MAAM;EACN,QAAQ,MAAM,aAAa,KAAA,IAAY,EAAE,UAAU,MAAM,SAAS,IAAI,EAAE,UAAU,MAAM,SAAS;EACjG,SAAS,MAAM;EACf,WAAW,MAAM;CACnB,CAAC;CAiBD,OAAO;EAAE,QAhBO,OAAO,UAAU,WAC7B,MAAM,OAAO,MAAM,WAAW,OAAO,IAAI,WAAW,EAClD,QAAQ,wCACV,CAAC,IACD;EAYsB,UAAA,MAXH,QAAQ,IAC7B,cAAc,KAAK,iBACjB,OAAO,MAAM,OAAO;GAClB,eAAe,CAAC,YAAY;GAC5B,MAAM;GACN,QAAQ,EAAE,cAAc,OAAO,GAAG;GAClC,SAAS,MAAM;GACf,WAAW,MAAM,cAAc,KAAA,IAAY,KAAA,IAAY,GAAG,MAAM,UAAU,GAAG;EAC/E,CAAC,CACH,CACF;CACmC;AACrC;;AAGA,eAAsB,sBACpB,QACA,cACA,SACqC;CACrC,MAAM,SAAS,MAAM,OAAO,MAAM,IAAI,YAAY;CAClD,IAAI,WAAW,MAAM,MAAM,IAAI,oBAAoB,SAAS,YAAY;CACxE,IAAI,OAAO,SAAS,gBAClB,MAAM,IAAI,MAAM,QAAQ,aAAa,MAAM,OAAO,KAAK,mBAAmB;CAE5E,MAAM,YAAY,MAAM,OAAO,MAAM,KAAK,EAAA,CAAG,QAC1C,SAAS,KAAK,OAAO,iBAAiB,YACzC;CACA,IAAI,SAAS,WAAW,OAAO,cAAc,QAC3C,OAAO;EAAE,QAAQ;EAAW;EAAQ;CAAS;CAE/C,IAAI,OAAO,UAAU,eAAe,OAAO,UAAU,UACnD,OAAO;EAAE,QAAQ,OAAO;EAAO,cAAc;EAAO;EAAQ;CAAS;CAEvE,IAAI,SAAS,MAAM,UAAU,MAAM,UAAU,YAAY,MAAM,UAAU,WAAW,GAAG;EACrF,MAAM,QAAQ,QAAQ;GAAE,QAAQ;GAAU;GAAQ;EAAS,CAAC;EAI5D,OAAO;GAAE,QAAQ;GAAU,cAAc;GAAM,QAAQ,MAHlC,OAAO,MAAM,WAAW,OAAO,IAAI,UAAU,EAChE,QAAQ,sCACV,CAAC;GAC8D;EAAS;CAC1E;CACA,IAAI,CAAC,SAAS,OAAO,UAAU,MAAM,UAAU,WAAW,GACxD,OAAO;EAAE,QAAQ;EAAW;EAAQ;CAAS;CAE/C,MAAM,QAAQ,QAAQ;EAAE,QAAQ;EAAa;EAAQ;CAAS,CAAC;CAC/D,MAAM,OAAO,MAAM,WAAW,OAAO,IAAI,aAAa,EACpD,QAAQ,iCACV,CAAC;CAID,OAAO;EAAE,QAAQ;EAAa,cAAc;EAAM,QAAQ,MAHlC,OAAO,MAAM,WAAW,OAAO,IAAI,aAAa,EACtE,QAAQ,8CACV,CAAC;EACoE;CAAS;AAChF"}
@@ -0,0 +1,76 @@
1
+ import { Tt as Task, Yt as TaskId, h as FactoryStores, on as BudgetLimits, wt as ExecutionReference } from "./engine-DFHU2Pe-.mjs";
2
+ import { z } from "zod";
3
+ //#region src/agent-routes.d.ts
4
+ declare const agentRouteSchema: z.ZodObject<{
5
+ id: z.ZodString;
6
+ agent: z.ZodString;
7
+ taskKind: z.ZodString;
8
+ description: z.ZodString;
9
+ permissions: z.ZodDefault<z.ZodArray<z.ZodString>>;
10
+ }, z.core.$strict>;
11
+ type AgentRoute = z.infer<typeof agentRouteSchema>;
12
+ type AgentRouteInput = z.input<typeof agentRouteSchema>;
13
+ interface AgentRouteRegistry {
14
+ readonly routes: readonly AgentRoute[];
15
+ get(id: string): AgentRoute | undefined;
16
+ forTaskKind(kind: string): AgentRoute | undefined;
17
+ }
18
+ /**
19
+ * Define the application-owned routes an agent or workflow may select. Route
20
+ * descriptions inform judgment; this validated registry remains the authority.
21
+ */
22
+ declare function defineAgentRoutes(inputs: readonly AgentRouteInput[]): AgentRouteRegistry;
23
+ //#endregion
24
+ //#region src/dispatch.d.ts
25
+ declare class DispatchRefusedError extends Error {
26
+ readonly taskId: TaskId;
27
+ readonly reason: string;
28
+ constructor(taskId: TaskId, reason: string);
29
+ }
30
+ /**
31
+ * An exhausted budget is definitive, not a retryable refusal: the task has
32
+ * already been failed and receipted when this throws, so it never re-enters
33
+ * the sweep. Operator retry recovers it once budget frees up.
34
+ */
35
+ declare class BudgetExhaustedError extends Error {
36
+ readonly taskId: TaskId;
37
+ readonly month: string;
38
+ readonly spendUsd: number;
39
+ constructor(taskId: TaskId, month: string, spendUsd: number);
40
+ }
41
+ interface DispatchBudget {
42
+ limits: Pick<BudgetLimits, "totalUsd" | "perTaskReservationUsd">;
43
+ /** Defaults to the current UTC month. */
44
+ month?: string;
45
+ now?: () => Date;
46
+ }
47
+ interface DispatchOptions {
48
+ /**
49
+ * Starts the durable execution for the task (an Eve agent run). Runs after
50
+ * the task has won the queued -> running transition. Returning the
51
+ * provider's execution reference records it on the task so the reconciler
52
+ * can find the run; returning nothing leaves the task reachable only by the
53
+ * stall window.
54
+ */
55
+ launch: (task: Task) => Promise<ExecutionReference | void>;
56
+ /** How many launches to try before the task fails for good; a failure re-queues for the next sweep. */
57
+ maxLaunchAttempts?: number;
58
+ /**
59
+ * Reserve-before-execution. When set, dispatch refuses definitively once
60
+ * the month's derived spend cannot fit another reservation, and otherwise
61
+ * stamps the reservation in the same write that wins queued -> running.
62
+ */
63
+ budget?: DispatchBudget;
64
+ }
65
+ declare const DEFAULT_MAX_LAUNCH_ATTEMPTS = 45;
66
+ /**
67
+ * The handshake between recorded work and execution: win the store-gated
68
+ * transition (at most one dispatcher can succeed), then start the run. A
69
+ * launch failure re-queues the task (bumping its attempt) until the attempt
70
+ * limit, then fails it; a crash between the transition and the launch leaves
71
+ * a running task with no execution, which the sweep reaps.
72
+ */
73
+ declare function dispatchTask(stores: FactoryStores, taskId: TaskId, options: DispatchOptions): Promise<Task>;
74
+ //#endregion
75
+ export { DispatchRefusedError as a, AgentRouteInput as c, defineAgentRoutes as d, DispatchOptions as i, AgentRouteRegistry as l, DEFAULT_MAX_LAUNCH_ATTEMPTS as n, dispatchTask as o, DispatchBudget as r, AgentRoute as s, BudgetExhaustedError as t, agentRouteSchema as u };
76
+ //# sourceMappingURL=dispatch-BaZS3PT8.d.mts.map
@@ -1,5 +1,5 @@
1
1
  //#region src/store/driver.d.ts
2
- type CollectionName = "repositories" | "signals" | "tasks" | "changes" | "receipts" | "budgets";
2
+ type CollectionName = "repositories" | "signals" | "tasks" | "changes" | "receipts" | "budgets" | "answers" | "sessions" | "outboxQueues" | "outboxDeliveries";
3
3
  interface StoredRecord {
4
4
  readonly value: unknown;
5
5
  readonly version: number;
@@ -31,4 +31,4 @@ interface StoreDriver {
31
31
  }
32
32
  //#endregion
33
33
  export { StoredEntry as a, StoreDriver as i, RecordExistsError as n, StoredRecord as o, RecordNotFoundError as r, VersionConflictError as s, CollectionName as t };
34
- //# sourceMappingURL=driver-BJutB0PE.d.mts.map
34
+ //# sourceMappingURL=driver-BC0Mt5KG.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"driver-Bbd25rNM.mjs","names":[],"sources":["../src/store/driver.ts"],"sourcesContent":["export type CollectionName =\n | \"repositories\"\n | \"signals\"\n | \"tasks\"\n | \"changes\"\n | \"receipts\"\n | \"budgets\";\n\nexport interface StoredRecord {\n readonly value: unknown;\n readonly version: number;\n}\n\nexport interface StoredEntry {\n readonly id: string;\n readonly record: StoredRecord;\n}\n\nexport class RecordExistsError extends Error {\n constructor(collection: CollectionName, id: string) {\n super(`record \"${id}\" already exists in \"${collection}\"`);\n this.name = \"RecordExistsError\";\n }\n}\n\nexport class RecordNotFoundError extends Error {\n constructor(collection: CollectionName, id: string) {\n super(`record \"${id}\" not found in \"${collection}\"`);\n this.name = \"RecordNotFoundError\";\n }\n}\n\nexport class VersionConflictError extends Error {\n constructor(collection: CollectionName, id: string, expectedVersion: number) {\n super(\n `record \"${id}\" in \"${collection}\" changed since version ${expectedVersion} was read`,\n );\n this.name = \"VersionConflictError\";\n }\n}\n\n/**\n * Raw persistence contract. Drivers store opaque values and enforce nothing\n * about their content; every invariant lives in the engine above. `update`\n * must be an atomic compare-and-swap on `expectedVersion`.\n */\nexport interface StoreDriver {\n readonly name: string;\n get(collection: CollectionName, id: string): Promise<StoredRecord | null>;\n insert(collection: CollectionName, id: string, value: unknown): Promise<void>;\n update(\n collection: CollectionName,\n id: string,\n value: unknown,\n expectedVersion: number,\n ): Promise<void>;\n list(collection: CollectionName): Promise<readonly StoredEntry[]>;\n}\n"],"mappings":";AAkBA,IAAa,oBAAb,cAAuC,MAAM;CAC3C,YAAY,YAA4B,IAAY;EAClD,MAAM,WAAW,GAAG,uBAAuB,WAAW,EAAE;EACxD,KAAK,OAAO;CACd;AACF;AAEA,IAAa,sBAAb,cAAyC,MAAM;CAC7C,YAAY,YAA4B,IAAY;EAClD,MAAM,WAAW,GAAG,kBAAkB,WAAW,EAAE;EACnD,KAAK,OAAO;CACd;AACF;AAEA,IAAa,uBAAb,cAA0C,MAAM;CAC9C,YAAY,YAA4B,IAAY,iBAAyB;EAC3E,MACE,WAAW,GAAG,QAAQ,WAAW,0BAA0B,gBAAgB,UAC7E;EACA,KAAK,OAAO;CACd;AACF"}
1
+ {"version":3,"file":"driver-Bbd25rNM.mjs","names":[],"sources":["../src/store/driver.ts"],"sourcesContent":["export type CollectionName =\n | \"repositories\"\n | \"signals\"\n | \"tasks\"\n | \"changes\"\n | \"receipts\"\n | \"budgets\"\n | \"answers\"\n | \"sessions\"\n | \"outboxQueues\"\n | \"outboxDeliveries\";\n\nexport interface StoredRecord {\n readonly value: unknown;\n readonly version: number;\n}\n\nexport interface StoredEntry {\n readonly id: string;\n readonly record: StoredRecord;\n}\n\nexport class RecordExistsError extends Error {\n constructor(collection: CollectionName, id: string) {\n super(`record \"${id}\" already exists in \"${collection}\"`);\n this.name = \"RecordExistsError\";\n }\n}\n\nexport class RecordNotFoundError extends Error {\n constructor(collection: CollectionName, id: string) {\n super(`record \"${id}\" not found in \"${collection}\"`);\n this.name = \"RecordNotFoundError\";\n }\n}\n\nexport class VersionConflictError extends Error {\n constructor(collection: CollectionName, id: string, expectedVersion: number) {\n super(\n `record \"${id}\" in \"${collection}\" changed since version ${expectedVersion} was read`,\n );\n this.name = \"VersionConflictError\";\n }\n}\n\n/**\n * Raw persistence contract. Drivers store opaque values and enforce nothing\n * about their content; every invariant lives in the engine above. `update`\n * must be an atomic compare-and-swap on `expectedVersion`.\n */\nexport interface StoreDriver {\n readonly name: string;\n get(collection: CollectionName, id: string): Promise<StoredRecord | null>;\n insert(collection: CollectionName, id: string, value: unknown): Promise<void>;\n update(\n collection: CollectionName,\n id: string,\n value: unknown,\n expectedVersion: number,\n ): Promise<void>;\n list(collection: CollectionName): Promise<readonly StoredEntry[]>;\n}\n"],"mappings":";AAsBA,IAAa,oBAAb,cAAuC,MAAM;CAC3C,YAAY,YAA4B,IAAY;EAClD,MAAM,WAAW,GAAG,uBAAuB,WAAW,EAAE;EACxD,KAAK,OAAO;CACd;AACF;AAEA,IAAa,sBAAb,cAAyC,MAAM;CAC7C,YAAY,YAA4B,IAAY;EAClD,MAAM,WAAW,GAAG,kBAAkB,WAAW,EAAE;EACnD,KAAK,OAAO;CACd;AACF;AAEA,IAAa,uBAAb,cAA0C,MAAM;CAC9C,YAAY,YAA4B,IAAY,iBAAyB;EAC3E,MACE,WAAW,GAAG,QAAQ,WAAW,0BAA0B,gBAAgB,UAC7E;EACA,KAAK,OAAO;CACd;AACF"}