@mastra/factory 0.2.2-alpha.0 → 0.2.2-alpha.2

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 (174) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/auth.d.ts +2 -2
  3. package/dist/auth.d.ts.map +1 -1
  4. package/dist/auth.js +401 -314
  5. package/dist/auth.js.map +1 -1
  6. package/dist/capabilities/connection.js +1 -1
  7. package/dist/capabilities/intake.js +1 -1
  8. package/dist/capabilities/version-control.js +1 -1
  9. package/dist/factory.d.ts.map +1 -1
  10. package/dist/factory.js +441 -39455
  11. package/dist/factory.js.map +1 -1
  12. package/dist/index.js +2 -39466
  13. package/dist/integrations/base.js +1 -1
  14. package/dist/integrations/github/config.js +36 -22
  15. package/dist/integrations/github/config.js.map +1 -1
  16. package/dist/integrations/github/factory-session.js +33 -37
  17. package/dist/integrations/github/factory-session.js.map +1 -1
  18. package/dist/integrations/github/integration.js +1037 -3624
  19. package/dist/integrations/github/integration.js.map +1 -1
  20. package/dist/integrations/github/issue-triage.js +71 -71
  21. package/dist/integrations/github/issue-triage.js.map +1 -1
  22. package/dist/integrations/github/pat.js +47 -36
  23. package/dist/integrations/github/pat.js.map +1 -1
  24. package/dist/integrations/github/provenance.js +72 -101
  25. package/dist/integrations/github/provenance.js.map +1 -1
  26. package/dist/integrations/github/routes.d.ts.map +1 -1
  27. package/dist/integrations/github/routes.js +1272 -1833
  28. package/dist/integrations/github/routes.js.map +1 -1
  29. package/dist/integrations/github/rules.js +203 -471
  30. package/dist/integrations/github/rules.js.map +1 -1
  31. package/dist/integrations/github/sandbox.d.ts +2 -0
  32. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  33. package/dist/integrations/github/sandbox.js +401 -326
  34. package/dist/integrations/github/sandbox.js.map +1 -1
  35. package/dist/integrations/github/session-subscriptions.js +165 -223
  36. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  37. package/dist/integrations/github/subscriptions.js +43 -51
  38. package/dist/integrations/github/subscriptions.js.map +1 -1
  39. package/dist/integrations/github/token-refresh.js +15 -18
  40. package/dist/integrations/github/token-refresh.js.map +1 -1
  41. package/dist/integrations/github/webhook.js +359 -343
  42. package/dist/integrations/github/webhook.js.map +1 -1
  43. package/dist/integrations/linear/agent-tools.js +77 -100
  44. package/dist/integrations/linear/agent-tools.js.map +1 -1
  45. package/dist/integrations/linear/integration.js +611 -1309
  46. package/dist/integrations/linear/integration.js.map +1 -1
  47. package/dist/integrations/linear/routes.js +227 -230
  48. package/dist/integrations/linear/routes.js.map +1 -1
  49. package/dist/integrations/linear/rules.js +126 -346
  50. package/dist/integrations/linear/rules.js.map +1 -1
  51. package/dist/integrations/linear/storage.js +1 -1
  52. package/dist/integrations/platform/api-client.js +131 -135
  53. package/dist/integrations/platform/api-client.js.map +1 -1
  54. package/dist/integrations/platform/github/event-worker.js +275 -573
  55. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  56. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  57. package/dist/integrations/platform/github/integration.js +845 -3942
  58. package/dist/integrations/platform/github/integration.js.map +1 -1
  59. package/dist/integrations/platform/linear/integration.js +457 -1309
  60. package/dist/integrations/platform/linear/integration.js.map +1 -1
  61. package/dist/integrations/workos/integration.js +80 -85
  62. package/dist/integrations/workos/integration.js.map +1 -1
  63. package/dist/routes/config.d.ts.map +1 -1
  64. package/dist/routes/config.js +933 -915
  65. package/dist/routes/config.js.map +1 -1
  66. package/dist/routes/custom-provider-source.js +110 -93
  67. package/dist/routes/custom-provider-source.js.map +1 -1
  68. package/dist/routes/fs.js +401 -349
  69. package/dist/routes/fs.js.map +1 -1
  70. package/dist/routes/intake.js +165 -165
  71. package/dist/routes/intake.js.map +1 -1
  72. package/dist/routes/oauth.js +447 -25166
  73. package/dist/routes/oauth.js.map +1 -1
  74. package/dist/routes/projects.js +437 -376
  75. package/dist/routes/projects.js.map +1 -1
  76. package/dist/routes/provider-credentials.js +76 -58
  77. package/dist/routes/provider-credentials.js.map +1 -1
  78. package/dist/routes/route.js +12 -8
  79. package/dist/routes/route.js.map +1 -1
  80. package/dist/routes/skills.js +160 -173
  81. package/dist/routes/skills.js.map +1 -1
  82. package/dist/routes/surface.js +250 -28618
  83. package/dist/routes/surface.js.map +1 -1
  84. package/dist/routes/tenant-credentials.js +141 -166
  85. package/dist/routes/tenant-credentials.js.map +1 -1
  86. package/dist/routes/work-items.js +654 -862
  87. package/dist/routes/work-items.js.map +1 -1
  88. package/dist/rules/binding-context.js +23 -35
  89. package/dist/rules/binding-context.js.map +1 -1
  90. package/dist/rules/defaults.js +200 -309
  91. package/dist/rules/defaults.js.map +1 -1
  92. package/dist/rules/dispatcher.d.ts.map +1 -1
  93. package/dist/rules/dispatcher.js +438 -677
  94. package/dist/rules/dispatcher.js.map +1 -1
  95. package/dist/rules/index.js +5 -599
  96. package/dist/rules/processor.js +344 -596
  97. package/dist/rules/processor.js.map +1 -1
  98. package/dist/rules/resolve.js +21 -18
  99. package/dist/rules/resolve.js.map +1 -1
  100. package/dist/rules/start-coordinator.js +168 -157
  101. package/dist/rules/start-coordinator.js.map +1 -1
  102. package/dist/rules/tools.js +50 -85
  103. package/dist/rules/tools.js.map +1 -1
  104. package/dist/rules/transition-service.js +183 -454
  105. package/dist/rules/transition-service.js.map +1 -1
  106. package/dist/rules/types.js +33 -29
  107. package/dist/rules/types.js.map +1 -1
  108. package/dist/rules/validation.js +266 -281
  109. package/dist/rules/validation.js.map +1 -1
  110. package/dist/sandbox/fleet.js +272 -250
  111. package/dist/sandbox/fleet.js.map +1 -1
  112. package/dist/sandbox/reattach.js +13 -6
  113. package/dist/sandbox/reattach.js.map +1 -1
  114. package/dist/server-error.js +12 -16
  115. package/dist/server-error.js.map +1 -1
  116. package/dist/skills/service.js +30 -35
  117. package/dist/skills/service.js.map +1 -1
  118. package/dist/spa-static.d.ts.map +1 -1
  119. package/dist/spa-static.js +91 -64
  120. package/dist/spa-static.js.map +1 -1
  121. package/dist/state-signing.js +73 -44
  122. package/dist/state-signing.js.map +1 -1
  123. package/dist/storage/domains/audit/agent-audit.js +60 -59
  124. package/dist/storage/domains/audit/agent-audit.js.map +1 -1
  125. package/dist/storage/domains/audit/base.js +169 -112
  126. package/dist/storage/domains/audit/base.js.map +1 -1
  127. package/dist/storage/domains/audit/domain.js +155 -153
  128. package/dist/storage/domains/audit/domain.js.map +1 -1
  129. package/dist/storage/domains/credentials/base.js +259 -243
  130. package/dist/storage/domains/credentials/base.js.map +1 -1
  131. package/dist/storage/domains/custom-providers/base.js +138 -143
  132. package/dist/storage/domains/custom-providers/base.js.map +1 -1
  133. package/dist/storage/domains/intake/base.js +75 -57
  134. package/dist/storage/domains/intake/base.js.map +1 -1
  135. package/dist/storage/domains/integrations/base.js +310 -245
  136. package/dist/storage/domains/integrations/base.js.map +1 -1
  137. package/dist/storage/domains/memory-settings/base.js +117 -102
  138. package/dist/storage/domains/memory-settings/base.js.map +1 -1
  139. package/dist/storage/domains/model-packs/base.js +89 -88
  140. package/dist/storage/domains/model-packs/base.js.map +1 -1
  141. package/dist/storage/domains/projects/base.js +97 -92
  142. package/dist/storage/domains/projects/base.js.map +1 -1
  143. package/dist/storage/domains/queue-health/base.js +111 -76
  144. package/dist/storage/domains/queue-health/base.js.map +1 -1
  145. package/dist/storage/domains/source-control/base.d.ts +9 -0
  146. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  147. package/dist/storage/domains/source-control/base.js +777 -702
  148. package/dist/storage/domains/source-control/base.js.map +1 -1
  149. package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
  150. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  151. package/dist/storage/domains/source-control/inmemory.js +322 -344
  152. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  153. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  154. package/dist/storage/domains/work-items/base.js +1266 -1132
  155. package/dist/storage/domains/work-items/base.js.map +1 -1
  156. package/dist/storage/domains/work-items/metrics.js +202 -184
  157. package/dist/storage/domains/work-items/metrics.js.map +1 -1
  158. package/dist/timing.d.ts +15 -0
  159. package/dist/timing.d.ts.map +1 -0
  160. package/dist/timing.js +34 -0
  161. package/dist/timing.js.map +1 -0
  162. package/dist/workspace.d.ts +2 -3
  163. package/dist/workspace.d.ts.map +1 -1
  164. package/dist/workspace.js +236 -438
  165. package/dist/workspace.js.map +1 -1
  166. package/factory-skills/factory-review/SKILL.md +9 -2
  167. package/package.json +9 -9
  168. package/dist/capabilities/connection.js.map +0 -1
  169. package/dist/capabilities/intake.js.map +0 -1
  170. package/dist/capabilities/version-control.js.map +0 -1
  171. package/dist/index.js.map +0 -1
  172. package/dist/integrations/base.js.map +0 -1
  173. package/dist/integrations/linear/storage.js.map +0 -1
  174. package/dist/rules/index.js.map +0 -1
@@ -1,381 +1,397 @@
1
- // src/integrations/github/webhook.ts
1
+ import { listPullRequestSubscriptionsForWebhook, retirePullRequestSubscription } from "./subscriptions.js";
2
2
  import { createHmac, timingSafeEqual } from "crypto";
3
-
4
- // src/integrations/github/subscriptions.ts
5
- function changeRequestTargetKey(input) {
6
- return `change-request:${input.installationExternalId}:${input.repositoryExternalId}:${input.changeRequestId}`;
7
- }
8
- async function listPullRequestSubscriptionsForWebhook(input, options, storage) {
9
- const rows = await storage.subscriptions.listByTarget(changeRequestTargetKey(input));
10
- return options?.includeTerminal ? rows : rows.filter((row) => row.status === "open");
11
- }
12
- function retirePullRequestSubscription(id, status, storage) {
13
- return storage.subscriptions.updateStatus(id, status);
14
- }
15
-
16
- // src/integrations/github/webhook.ts
17
- var SUPPORTED_GITHUB_WEBHOOK_EVENTS = /* @__PURE__ */ new Set([
18
- "issues",
19
- "issue_comment",
20
- "pull_request",
21
- "pull_request_review",
22
- "pull_request_review_comment"
3
+ //#region src/integrations/github/webhook.ts
4
+ const SUPPORTED_GITHUB_WEBHOOK_EVENTS = /* @__PURE__ */ new Set([
5
+ "issues",
6
+ "issue_comment",
7
+ "pull_request",
8
+ "pull_request_review",
9
+ "pull_request_review_comment"
23
10
  ]);
24
11
  function normalizeHeader(value) {
25
- if (!value) return null;
26
- const trimmed = value.trim();
27
- return trimmed.length > 0 ? trimmed : null;
12
+ if (!value) return null;
13
+ const trimmed = value.trim();
14
+ return trimmed.length > 0 ? trimmed : null;
28
15
  }
29
16
  function verifySignature(rawBody, signature, secret) {
30
- if (!signature.startsWith("sha256=")) return false;
31
- const signatureHex = signature.slice("sha256=".length);
32
- if (!/^[a-fA-F0-9]{64}$/.test(signatureHex)) return false;
33
- const expectedHex = createHmac("sha256", secret).update(rawBody).digest("hex");
34
- const received = Buffer.from(signatureHex, "hex");
35
- const expected = Buffer.from(expectedHex, "hex");
36
- return received.length === expected.length && timingSafeEqual(received, expected);
17
+ if (!signature.startsWith("sha256=")) return false;
18
+ const signatureHex = signature.slice(7);
19
+ if (!/^[a-fA-F0-9]{64}$/.test(signatureHex)) return false;
20
+ const expectedHex = createHmac("sha256", secret).update(rawBody).digest("hex");
21
+ const received = Buffer.from(signatureHex, "hex");
22
+ const expected = Buffer.from(expectedHex, "hex");
23
+ return received.length === expected.length && timingSafeEqual(received, expected);
37
24
  }
38
25
  async function parseGithubWebhook(c, secret) {
39
- if (!secret) {
40
- return { status: 401, body: { error: "unauthorized", message: "GitHub webhook secret is not configured" } };
41
- }
42
- const event = normalizeHeader(c.req.header("x-github-event"));
43
- const deliveryId = normalizeHeader(c.req.header("x-github-delivery"));
44
- const signature = normalizeHeader(c.req.header("x-hub-signature-256"));
45
- if (!event) return { status: 400, body: { error: "bad_request", message: "Missing x-github-event header" } };
46
- if (!deliveryId) return { status: 400, body: { error: "bad_request", message: "Missing x-github-delivery header" } };
47
- if (!signature)
48
- return { status: 401, body: { error: "unauthorized", message: "Missing x-hub-signature-256 header" } };
49
- const rawBody = await c.req.text();
50
- if (!verifySignature(rawBody, signature, secret)) {
51
- return { status: 401, body: { error: "unauthorized", message: "Invalid GitHub webhook signature" } };
52
- }
53
- let payload;
54
- try {
55
- payload = JSON.parse(rawBody);
56
- } catch {
57
- return { status: 400, body: { error: "bad_request", message: "Malformed JSON payload" } };
58
- }
59
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
60
- return { status: 400, body: { error: "bad_request", message: "Payload must be a JSON object" } };
61
- }
62
- return { event, deliveryId, payload };
26
+ if (!secret) return {
27
+ status: 401,
28
+ body: {
29
+ error: "unauthorized",
30
+ message: "GitHub webhook secret is not configured"
31
+ }
32
+ };
33
+ const event = normalizeHeader(c.req.header("x-github-event"));
34
+ const deliveryId = normalizeHeader(c.req.header("x-github-delivery"));
35
+ const signature = normalizeHeader(c.req.header("x-hub-signature-256"));
36
+ if (!event) return {
37
+ status: 400,
38
+ body: {
39
+ error: "bad_request",
40
+ message: "Missing x-github-event header"
41
+ }
42
+ };
43
+ if (!deliveryId) return {
44
+ status: 400,
45
+ body: {
46
+ error: "bad_request",
47
+ message: "Missing x-github-delivery header"
48
+ }
49
+ };
50
+ if (!signature) return {
51
+ status: 401,
52
+ body: {
53
+ error: "unauthorized",
54
+ message: "Missing x-hub-signature-256 header"
55
+ }
56
+ };
57
+ const rawBody = await c.req.text();
58
+ if (!verifySignature(rawBody, signature, secret)) return {
59
+ status: 401,
60
+ body: {
61
+ error: "unauthorized",
62
+ message: "Invalid GitHub webhook signature"
63
+ }
64
+ };
65
+ let payload;
66
+ try {
67
+ payload = JSON.parse(rawBody);
68
+ } catch {
69
+ return {
70
+ status: 400,
71
+ body: {
72
+ error: "bad_request",
73
+ message: "Malformed JSON payload"
74
+ }
75
+ };
76
+ }
77
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return {
78
+ status: 400,
79
+ body: {
80
+ error: "bad_request",
81
+ message: "Payload must be a JSON object"
82
+ }
83
+ };
84
+ return {
85
+ event,
86
+ deliveryId,
87
+ payload
88
+ };
63
89
  }
64
90
  function getObject(value) {
65
- return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
91
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
66
92
  }
67
93
  function getString(value) {
68
- return typeof value === "string" && value.length > 0 ? value : void 0;
94
+ return typeof value === "string" && value.length > 0 ? value : void 0;
69
95
  }
70
96
  function getNumber(value) {
71
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
97
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
72
98
  }
73
99
  function getBoolean(value) {
74
- return typeof value === "boolean" ? value : void 0;
100
+ return typeof value === "boolean" ? value : void 0;
75
101
  }
76
102
  function getLabels(value) {
77
- if (!Array.isArray(value)) return [];
78
- return value.map((label) => typeof label === "string" ? label : getString(getObject(label)?.name)).filter((label) => Boolean(label));
103
+ if (!Array.isArray(value)) return [];
104
+ return value.map((label) => typeof label === "string" ? label : getString(getObject(label)?.name)).filter((label) => Boolean(label));
79
105
  }
80
106
  function getIssueTriageRunInput(parsed) {
81
- if (parsed.event !== "issues" || getString(parsed.payload.action) !== "opened") return null;
82
- const repository = getString(getObject(parsed.payload.repository)?.full_name);
83
- const issue = getObject(parsed.payload.issue);
84
- const sender = getString(getObject(parsed.payload.sender)?.login);
85
- const installationId = getNumber(getObject(parsed.payload.installation)?.id);
86
- const issueNumber = getNumber(issue?.number);
87
- const issueTitle = getString(issue?.title);
88
- const issueUrl = getString(issue?.html_url);
89
- if (!repository || !installationId || !issueNumber || !issueTitle || !issueUrl) return null;
90
- return {
91
- repository,
92
- issueNumber,
93
- issueTitle,
94
- issueUrl,
95
- labels: getLabels(issue?.labels),
96
- sender,
97
- installationId
98
- };
107
+ if (parsed.event !== "issues" || getString(parsed.payload.action) !== "opened") return null;
108
+ const repository = getString(getObject(parsed.payload.repository)?.full_name);
109
+ const issue = getObject(parsed.payload.issue);
110
+ const sender = getString(getObject(parsed.payload.sender)?.login);
111
+ const installationId = getNumber(getObject(parsed.payload.installation)?.id);
112
+ const issueNumber = getNumber(issue?.number);
113
+ const issueTitle = getString(issue?.title);
114
+ const issueUrl = getString(issue?.html_url);
115
+ if (!repository || !installationId || !issueNumber || !issueTitle || !issueUrl) return null;
116
+ return {
117
+ repository,
118
+ issueNumber,
119
+ issueTitle,
120
+ issueUrl,
121
+ labels: getLabels(issue?.labels),
122
+ sender,
123
+ installationId
124
+ };
99
125
  }
100
126
  function normalizeGithubWebhookMetadata(parsed) {
101
- const { event, deliveryId, payload } = parsed;
102
- const repository = getObject(payload.repository);
103
- const issue = getObject(payload.issue);
104
- const pullRequest = getObject(payload.pull_request);
105
- const sender = getObject(payload.sender);
106
- const installation = getObject(payload.installation);
107
- const issuePullRequest = getObject(issue?.pull_request);
108
- return {
109
- event,
110
- action: getString(payload.action),
111
- deliveryId,
112
- repository: getString(repository?.full_name),
113
- repositoryId: getNumber(repository?.id),
114
- issueNumber: getNumber(issue?.number),
115
- pullRequestNumber: getNumber(pullRequest?.number) ?? (event === "issue_comment" && issuePullRequest ? getNumber(issue?.number) : void 0),
116
- sender: getString(sender?.login),
117
- senderType: getString(sender?.type),
118
- installationId: getNumber(installation?.id)
119
- };
127
+ const { event, deliveryId, payload } = parsed;
128
+ const repository = getObject(payload.repository);
129
+ const issue = getObject(payload.issue);
130
+ const pullRequest = getObject(payload.pull_request);
131
+ const sender = getObject(payload.sender);
132
+ const installation = getObject(payload.installation);
133
+ const issuePullRequest = getObject(issue?.pull_request);
134
+ return {
135
+ event,
136
+ action: getString(payload.action),
137
+ deliveryId,
138
+ repository: getString(repository?.full_name),
139
+ repositoryId: getNumber(repository?.id),
140
+ issueNumber: getNumber(issue?.number),
141
+ pullRequestNumber: getNumber(pullRequest?.number) ?? (event === "issue_comment" && issuePullRequest ? getNumber(issue?.number) : void 0),
142
+ sender: getString(sender?.login),
143
+ senderType: getString(sender?.type),
144
+ installationId: getNumber(installation?.id)
145
+ };
120
146
  }
121
147
  function notificationSummary(metadata, label) {
122
- const actor = metadata.sender ? `${metadata.sender} ` : "";
123
- return `${actor}${label} on ${metadata.repository}#${metadata.pullRequestNumber}`;
148
+ return `${metadata.sender ? `${metadata.sender} ` : ""}${label} on ${metadata.repository}#${metadata.pullRequestNumber}`;
124
149
  }
125
150
  function notificationTargetUrl(event, payload) {
126
- if (event === "issue_comment" || event === "pull_request_review_comment") {
127
- return getString(getObject(payload.comment)?.html_url);
128
- }
129
- if (event === "pull_request_review") {
130
- return getString(getObject(payload.review)?.html_url);
131
- }
132
- return getString(getObject(payload.pull_request)?.html_url);
151
+ if (event === "issue_comment" || event === "pull_request_review_comment") return getString(getObject(payload.comment)?.html_url);
152
+ if (event === "pull_request_review") return getString(getObject(payload.review)?.html_url);
153
+ return getString(getObject(payload.pull_request)?.html_url);
133
154
  }
134
155
  function classifyGithubWebhook(parsed) {
135
- const metadata = normalizeGithubWebhookMetadata(parsed);
136
- const { event, payload } = parsed;
137
- const action = metadata.action;
138
- if (!action || !metadata.repositoryId || !metadata.installationId || !metadata.pullRequestNumber || !metadata.repository) {
139
- return void 0;
140
- }
141
- let priority;
142
- let kind;
143
- let label;
144
- let terminal = false;
145
- if (event === "pull_request_review" && action === "submitted") {
146
- const state = getString(getObject(payload.review)?.state)?.toLowerCase().replaceAll("_", "-");
147
- priority = state === "approved" || state === "changes-requested" ? "urgent" : "high";
148
- kind = state === "approved" ? "review-approved" : state === "changes-requested" ? "review-changes-requested" : "review-submitted";
149
- label = state === "approved" ? "approved the pull request" : state === "changes-requested" ? "requested changes" : "submitted a review";
150
- } else if (event === "pull_request" && action === "closed") {
151
- const merged = getBoolean(getObject(payload.pull_request)?.merged) === true;
152
- priority = "urgent";
153
- kind = merged ? "pull-request-merged" : "pull-request-closed";
154
- label = merged ? "merged the pull request" : "closed the pull request";
155
- terminal = true;
156
- } else if (event === "issue_comment" && action === "created") {
157
- priority = "high";
158
- kind = "issue-comment-created";
159
- label = "commented";
160
- } else if (event === "pull_request_review_comment" && action === "created") {
161
- priority = "high";
162
- kind = "review-comment-created";
163
- label = "left a review comment";
164
- } else if (event === "pull_request" && action === "reopened") {
165
- priority = "high";
166
- kind = "pull-request-reopened";
167
- label = "reopened the pull request";
168
- } else if (event === "pull_request_review" && action === "dismissed") {
169
- priority = "high";
170
- kind = "review-dismissed";
171
- label = "dismissed a review";
172
- } else if (event === "pull_request" && [
173
- "synchronize",
174
- "ready_for_review",
175
- "converted_to_draft",
176
- "assigned",
177
- "unassigned",
178
- "review_requested",
179
- "review_request_removed"
180
- ].includes(action)) {
181
- priority = "medium";
182
- kind = `pull-request-${action.replaceAll("_", "-")}`;
183
- label = action.replaceAll("_", " ");
184
- } else if (event === "pull_request" && ["edited", "labeled", "unlabeled", "milestoned", "demilestoned"].includes(action)) {
185
- priority = "low";
186
- kind = `pull-request-${action.replaceAll("_", "-")}`;
187
- label = action.replaceAll("_", " ");
188
- } else {
189
- return void 0;
190
- }
191
- return {
192
- action,
193
- kind,
194
- priority,
195
- summary: notificationSummary(metadata, label),
196
- terminal,
197
- metadata: {
198
- ...metadata,
199
- pullRequestNumber: metadata.pullRequestNumber,
200
- repositoryId: metadata.repositoryId,
201
- installationId: metadata.installationId
202
- },
203
- payload
204
- };
156
+ const metadata = normalizeGithubWebhookMetadata(parsed);
157
+ const { event, payload } = parsed;
158
+ const action = metadata.action;
159
+ if (!action || !metadata.repositoryId || !metadata.installationId || !metadata.pullRequestNumber || !metadata.repository) return;
160
+ let priority;
161
+ let kind;
162
+ let label;
163
+ let terminal = false;
164
+ if (event === "pull_request_review" && action === "submitted") {
165
+ const state = getString(getObject(payload.review)?.state)?.toLowerCase().replaceAll("_", "-");
166
+ priority = state === "approved" || state === "changes-requested" ? "urgent" : "high";
167
+ kind = state === "approved" ? "review-approved" : state === "changes-requested" ? "review-changes-requested" : "review-submitted";
168
+ label = state === "approved" ? "approved the pull request" : state === "changes-requested" ? "requested changes" : "submitted a review";
169
+ } else if (event === "pull_request" && action === "closed") {
170
+ const merged = getBoolean(getObject(payload.pull_request)?.merged) === true;
171
+ priority = "urgent";
172
+ kind = merged ? "pull-request-merged" : "pull-request-closed";
173
+ label = merged ? "merged the pull request" : "closed the pull request";
174
+ terminal = true;
175
+ } else if (event === "issue_comment" && action === "created") {
176
+ priority = "high";
177
+ kind = "issue-comment-created";
178
+ label = "commented";
179
+ } else if (event === "pull_request_review_comment" && action === "created") {
180
+ priority = "high";
181
+ kind = "review-comment-created";
182
+ label = "left a review comment";
183
+ } else if (event === "pull_request" && action === "reopened") {
184
+ priority = "high";
185
+ kind = "pull-request-reopened";
186
+ label = "reopened the pull request";
187
+ } else if (event === "pull_request_review" && action === "dismissed") {
188
+ priority = "high";
189
+ kind = "review-dismissed";
190
+ label = "dismissed a review";
191
+ } else if (event === "pull_request" && [
192
+ "synchronize",
193
+ "ready_for_review",
194
+ "converted_to_draft",
195
+ "assigned",
196
+ "unassigned",
197
+ "review_requested",
198
+ "review_request_removed"
199
+ ].includes(action)) {
200
+ priority = "medium";
201
+ kind = `pull-request-${action.replaceAll("_", "-")}`;
202
+ label = action.replaceAll("_", " ");
203
+ } else if (event === "pull_request" && [
204
+ "edited",
205
+ "labeled",
206
+ "unlabeled",
207
+ "milestoned",
208
+ "demilestoned"
209
+ ].includes(action)) {
210
+ priority = "low";
211
+ kind = `pull-request-${action.replaceAll("_", "-")}`;
212
+ label = action.replaceAll("_", " ");
213
+ } else return;
214
+ return {
215
+ action,
216
+ kind,
217
+ priority,
218
+ summary: notificationSummary(metadata, label),
219
+ terminal,
220
+ metadata: {
221
+ ...metadata,
222
+ pullRequestNumber: metadata.pullRequestNumber,
223
+ repositoryId: metadata.repositoryId,
224
+ installationId: metadata.installationId
225
+ },
226
+ payload
227
+ };
205
228
  }
206
229
  async function resolveSubscriptionSession(controller, subscription) {
207
- const { sessionId, resourceId, threadId } = subscription;
208
- if (!sessionId || !resourceId || !threadId) {
209
- throw new Error(`GitHub subscription ${subscription.id} is missing its session binding.`);
210
- }
211
- const scope = subscription.sessionScope || void 0;
212
- let session = await controller.getSessionByResource(resourceId, scope);
213
- if (!session) {
214
- const tags = {
215
- factoryProjectId: resourceId,
216
- projectRepositoryId: subscription.data.projectRepositoryId,
217
- ...scope ? { worktreePath: scope } : {}
218
- };
219
- session = await controller.createSession({
220
- id: sessionId,
221
- ownerId: subscription.data.ownerId,
222
- resourceId,
223
- scope,
224
- tags
225
- });
226
- }
227
- if (session.thread.getId() !== threadId) {
228
- await session.thread.switch({ threadId, emitEvent: false });
229
- }
230
- if (session.thread.getId() !== threadId) {
231
- throw new Error(`Session ${sessionId} did not bind thread ${threadId}.`);
232
- }
233
- return session;
230
+ const { sessionId, resourceId, threadId } = subscription;
231
+ if (!sessionId || !resourceId || !threadId) throw new Error(`GitHub subscription ${subscription.id} is missing its session binding.`);
232
+ const scope = subscription.sessionScope || void 0;
233
+ let session = await controller.getSessionByResource(resourceId, scope);
234
+ if (!session) {
235
+ const tags = {
236
+ factoryProjectId: resourceId,
237
+ projectRepositoryId: subscription.data.projectRepositoryId,
238
+ ...scope ? { worktreePath: scope } : {}
239
+ };
240
+ session = await controller.createSession({
241
+ id: sessionId,
242
+ ownerId: subscription.data.ownerId,
243
+ resourceId,
244
+ scope,
245
+ tags
246
+ });
247
+ }
248
+ if (session.thread.getId() !== threadId) await session.thread.switch({
249
+ threadId,
250
+ emitEvent: false
251
+ });
252
+ if (session.thread.getId() !== threadId) throw new Error(`Session ${sessionId} did not bind thread ${threadId}.`);
253
+ return session;
234
254
  }
235
- var AUTHORIZED_BOTS = /* @__PURE__ */ new Set(["coderabbitai[bot]", "devin-ai-integration[bot]"]);
236
- var AUTHORIZED_PERMISSIONS = /* @__PURE__ */ new Set(["admin", "maintain", "write"]);
237
- var PERMISSION_CHECK_TIMEOUT_MS = 5e3;
238
- var AUTHOR_GATED_KINDS = /* @__PURE__ */ new Set([
239
- "issue-comment-created",
240
- "review-comment-created",
241
- "review-submitted",
242
- "review-approved",
243
- "review-changes-requested",
244
- "review-dismissed"
255
+ const AUTHORIZED_BOTS = /* @__PURE__ */ new Set(["coderabbitai[bot]", "devin-ai-integration[bot]"]);
256
+ const AUTHORIZED_PERMISSIONS = /* @__PURE__ */ new Set([
257
+ "admin",
258
+ "maintain",
259
+ "write"
260
+ ]);
261
+ const PERMISSION_CHECK_TIMEOUT_MS = 5e3;
262
+ const AUTHOR_GATED_KINDS = /* @__PURE__ */ new Set([
263
+ "issue-comment-created",
264
+ "review-comment-created",
265
+ "review-submitted",
266
+ "review-approved",
267
+ "review-changes-requested",
268
+ "review-dismissed"
245
269
  ]);
246
270
  async function isAuthorizedGithubSender(notification, github) {
247
- if (!AUTHOR_GATED_KINDS.has(notification.kind)) return true;
248
- const sender = notification.metadata.sender;
249
- const repository = notification.metadata.repository;
250
- if (!sender || !repository) return false;
251
- const normalizedSender = sender.toLowerCase();
252
- if (notification.metadata.senderType?.toLowerCase() === "bot" || normalizedSender.endsWith("[bot]")) {
253
- return AUTHORIZED_BOTS.has(normalizedSender);
254
- }
255
- if (!github) return false;
256
- const abortController = new AbortController();
257
- let timeout;
258
- try {
259
- const permission = await Promise.race([
260
- github.getRepositoryCollaboratorPermission(
261
- notification.metadata.installationId,
262
- repository,
263
- sender,
264
- abortController.signal
265
- ),
266
- new Promise((resolve) => {
267
- timeout = setTimeout(() => {
268
- abortController.abort();
269
- resolve(void 0);
270
- }, PERMISSION_CHECK_TIMEOUT_MS);
271
- })
272
- ]);
273
- return permission !== void 0 && AUTHORIZED_PERMISSIONS.has(permission);
274
- } catch {
275
- return false;
276
- } finally {
277
- if (timeout) clearTimeout(timeout);
278
- }
271
+ if (!AUTHOR_GATED_KINDS.has(notification.kind)) return true;
272
+ const sender = notification.metadata.sender;
273
+ const repository = notification.metadata.repository;
274
+ if (!sender || !repository) return false;
275
+ const normalizedSender = sender.toLowerCase();
276
+ if (notification.metadata.senderType?.toLowerCase() === "bot" || normalizedSender.endsWith("[bot]")) return AUTHORIZED_BOTS.has(normalizedSender);
277
+ if (!github) return false;
278
+ const abortController = new AbortController();
279
+ let timeout;
280
+ try {
281
+ const permission = await Promise.race([github.getRepositoryCollaboratorPermission(notification.metadata.installationId, repository, sender, abortController.signal), new Promise((resolve) => {
282
+ timeout = setTimeout(() => {
283
+ abortController.abort();
284
+ resolve(void 0);
285
+ }, PERMISSION_CHECK_TIMEOUT_MS);
286
+ })]);
287
+ return permission !== void 0 && AUTHORIZED_PERMISSIONS.has(permission);
288
+ } catch {
289
+ return false;
290
+ } finally {
291
+ if (timeout) clearTimeout(timeout);
292
+ }
279
293
  }
280
294
  async function dispatchGithubWebhook(parsed, dependencies) {
281
- const notification = classifyGithubWebhook(parsed);
282
- if (!notification) return { delivered: 0, failed: 0, ignored: true };
283
- const isAuthorizedSender = dependencies.isAuthorizedSender ?? ((n) => isAuthorizedGithubSender(n, dependencies.github));
284
- if (!await isAuthorizedSender(notification)) {
285
- return { delivered: 0, failed: 0, ignored: true };
286
- }
287
- const target = {
288
- installationExternalId: notification.metadata.installationId.toString(),
289
- repositoryExternalId: notification.metadata.repositoryId.toString(),
290
- changeRequestId: notification.metadata.pullRequestNumber.toString()
291
- };
292
- const listSubscriptions = dependencies.listSubscriptions ?? ((subscriptionTarget, options) => {
293
- if (!dependencies.github) throw new Error("GitHub integration is required to load webhook subscriptions.");
294
- return listPullRequestSubscriptionsForWebhook(
295
- subscriptionTarget,
296
- options,
297
- dependencies.github.integrationStorage
298
- );
299
- });
300
- const retireSubscription = dependencies.retireSubscription ?? ((id, status) => {
301
- if (!dependencies.github) throw new Error("GitHub integration is required to retire webhook subscriptions.");
302
- return retirePullRequestSubscription(id, status, dependencies.github.integrationStorage);
303
- });
304
- const subscriptions = await listSubscriptions(target, { includeTerminal: notification.action === "reopened" });
305
- let delivered = 0;
306
- let failed = 0;
307
- for (const subscription of subscriptions) {
308
- try {
309
- const session = await resolveSubscriptionSession(dependencies.controller, subscription);
310
- const result = await session.sendNotificationSignal({
311
- source: "github",
312
- kind: notification.kind,
313
- summary: notification.summary,
314
- priority: notification.priority,
315
- payload: notification.payload,
316
- sourceId: parsed.deliveryId,
317
- dedupeKey: `${parsed.deliveryId}:${subscription.sessionId}:${subscription.threadId}`,
318
- coalesceKey: `github:${subscription.data.repositoryExternalId}:pull-request:${subscription.data.changeRequestId}`,
319
- metadata: {
320
- event: notification.metadata.event,
321
- action: notification.action,
322
- repository: notification.metadata.repository,
323
- issueNumber: notification.metadata.issueNumber,
324
- pullRequestNumber: notification.metadata.pullRequestNumber,
325
- targetUrl: notificationTargetUrl(parsed.event, parsed.payload),
326
- deliveryId: parsed.deliveryId
327
- }
328
- });
329
- await Promise.all([result.persisted, result.accepted].filter(Boolean));
330
- if (notification.terminal) {
331
- await retireSubscription(subscription.id, notification.kind === "pull-request-merged" ? "merged" : "closed");
332
- } else if (notification.action === "reopened") {
333
- await retireSubscription(subscription.id, "open");
334
- }
335
- delivered += 1;
336
- } catch (error) {
337
- failed += 1;
338
- dependencies.onTargetError?.(subscription, error);
339
- }
340
- }
341
- return { delivered, failed, ignored: false };
295
+ const notification = classifyGithubWebhook(parsed);
296
+ if (!notification) return {
297
+ delivered: 0,
298
+ failed: 0,
299
+ ignored: true
300
+ };
301
+ if (!await (dependencies.isAuthorizedSender ?? ((n) => isAuthorizedGithubSender(n, dependencies.github)))(notification)) return {
302
+ delivered: 0,
303
+ failed: 0,
304
+ ignored: true
305
+ };
306
+ const target = {
307
+ installationExternalId: notification.metadata.installationId.toString(),
308
+ repositoryExternalId: notification.metadata.repositoryId.toString(),
309
+ changeRequestId: notification.metadata.pullRequestNumber.toString()
310
+ };
311
+ const listSubscriptions = dependencies.listSubscriptions ?? ((subscriptionTarget, options) => {
312
+ if (!dependencies.github) throw new Error("GitHub integration is required to load webhook subscriptions.");
313
+ return listPullRequestSubscriptionsForWebhook(subscriptionTarget, options, dependencies.github.integrationStorage);
314
+ });
315
+ const retireSubscription = dependencies.retireSubscription ?? ((id, status) => {
316
+ if (!dependencies.github) throw new Error("GitHub integration is required to retire webhook subscriptions.");
317
+ return retirePullRequestSubscription(id, status, dependencies.github.integrationStorage);
318
+ });
319
+ const subscriptions = await listSubscriptions(target, { includeTerminal: notification.action === "reopened" });
320
+ let delivered = 0;
321
+ let failed = 0;
322
+ for (const subscription of subscriptions) try {
323
+ const result = await (await resolveSubscriptionSession(dependencies.controller, subscription)).sendNotificationSignal({
324
+ source: "github",
325
+ kind: notification.kind,
326
+ summary: notification.summary,
327
+ priority: notification.priority,
328
+ payload: notification.payload,
329
+ sourceId: parsed.deliveryId,
330
+ dedupeKey: `${parsed.deliveryId}:${subscription.sessionId}:${subscription.threadId}`,
331
+ coalesceKey: `github:${subscription.data.repositoryExternalId}:pull-request:${subscription.data.changeRequestId}`,
332
+ metadata: {
333
+ event: notification.metadata.event,
334
+ action: notification.action,
335
+ repository: notification.metadata.repository,
336
+ issueNumber: notification.metadata.issueNumber,
337
+ pullRequestNumber: notification.metadata.pullRequestNumber,
338
+ targetUrl: notificationTargetUrl(parsed.event, parsed.payload),
339
+ deliveryId: parsed.deliveryId
340
+ }
341
+ });
342
+ await Promise.all([result.persisted, result.accepted].filter(Boolean));
343
+ if (notification.terminal) await retireSubscription(subscription.id, notification.kind === "pull-request-merged" ? "merged" : "closed");
344
+ else if (notification.action === "reopened") await retireSubscription(subscription.id, "open");
345
+ delivered += 1;
346
+ } catch (error) {
347
+ failed += 1;
348
+ dependencies.onTargetError?.(subscription, error);
349
+ }
350
+ return {
351
+ delivered,
352
+ failed,
353
+ ignored: false
354
+ };
342
355
  }
343
356
  async function handleGithubWebhook(c, options) {
344
- const parsed = await parseGithubWebhook(c, options.github.webhookSecret);
345
- if ("status" in parsed) return parsed;
346
- if (!SUPPORTED_GITHUB_WEBHOOK_EVENTS.has(parsed.event)) {
347
- return { status: 202, body: { ok: true, ignored: true } };
348
- }
349
- const metadata = normalizeGithubWebhookMetadata(parsed);
350
- console.info("[GitHub Webhook]", metadata);
351
- if (options.ingestFactoryEvent) {
352
- await options.ingestFactoryEvent(parsed);
353
- } else {
354
- const issueTriageRun = getIssueTriageRunInput(parsed);
355
- if (issueTriageRun && options.runIssueTriage) {
356
- void options.runIssueTriage(issueTriageRun).catch((error) => {
357
- console.error("[GitHub Webhook] Failed to run issue triage", {
358
- deliveryId: metadata.deliveryId,
359
- repository: metadata.repository,
360
- issueNumber: metadata.issueNumber,
361
- error: error instanceof Error ? error.message : String(error)
362
- });
363
- });
364
- }
365
- }
366
- if (!options.controller) {
367
- return { status: 202, body: { ok: true } };
368
- }
369
- const result = await dispatchGithubWebhook(parsed, options);
370
- if (result.failed > 0) {
371
- console.warn(`[GitHub Webhook] ${result.failed} subscribed target(s) failed for delivery ${parsed.deliveryId}.`);
372
- }
373
- return { status: 202, body: { ok: true, ...result.ignored ? { ignored: true } : {} } };
357
+ const parsed = await parseGithubWebhook(c, options.github.webhookSecret);
358
+ if ("status" in parsed) return parsed;
359
+ if (!SUPPORTED_GITHUB_WEBHOOK_EVENTS.has(parsed.event)) return {
360
+ status: 202,
361
+ body: {
362
+ ok: true,
363
+ ignored: true
364
+ }
365
+ };
366
+ const metadata = normalizeGithubWebhookMetadata(parsed);
367
+ console.info("[GitHub Webhook]", metadata);
368
+ if (options.ingestFactoryEvent) await options.ingestFactoryEvent(parsed);
369
+ else {
370
+ const issueTriageRun = getIssueTriageRunInput(parsed);
371
+ if (issueTriageRun && options.runIssueTriage) options.runIssueTriage(issueTriageRun).catch((error) => {
372
+ console.error("[GitHub Webhook] Failed to run issue triage", {
373
+ deliveryId: metadata.deliveryId,
374
+ repository: metadata.repository,
375
+ issueNumber: metadata.issueNumber,
376
+ error: error instanceof Error ? error.message : String(error)
377
+ });
378
+ });
379
+ }
380
+ if (!options.controller) return {
381
+ status: 202,
382
+ body: { ok: true }
383
+ };
384
+ const result = await dispatchGithubWebhook(parsed, options);
385
+ if (result.failed > 0) console.warn(`[GitHub Webhook] ${result.failed} subscribed target(s) failed for delivery ${parsed.deliveryId}.`);
386
+ return {
387
+ status: 202,
388
+ body: {
389
+ ok: true,
390
+ ...result.ignored ? { ignored: true } : {}
391
+ }
392
+ };
374
393
  }
375
- export {
376
- classifyGithubWebhook,
377
- dispatchGithubWebhook,
378
- handleGithubWebhook,
379
- normalizeGithubWebhookMetadata
380
- };
394
+ //#endregion
395
+ export { classifyGithubWebhook, dispatchGithubWebhook, handleGithubWebhook, normalizeGithubWebhookMetadata };
396
+
381
397
  //# sourceMappingURL=webhook.js.map