@mastra/factory 0.2.2-alpha.1 → 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 (155) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/auth.js +401 -334
  3. package/dist/auth.js.map +1 -1
  4. package/dist/capabilities/connection.js +1 -1
  5. package/dist/capabilities/intake.js +1 -1
  6. package/dist/capabilities/version-control.js +1 -1
  7. package/dist/factory.js +441 -39622
  8. package/dist/factory.js.map +1 -1
  9. package/dist/index.js +2 -39633
  10. package/dist/integrations/base.js +1 -1
  11. package/dist/integrations/github/config.js +36 -22
  12. package/dist/integrations/github/config.js.map +1 -1
  13. package/dist/integrations/github/factory-session.js +33 -37
  14. package/dist/integrations/github/factory-session.js.map +1 -1
  15. package/dist/integrations/github/integration.js +1037 -3672
  16. package/dist/integrations/github/integration.js.map +1 -1
  17. package/dist/integrations/github/issue-triage.js +71 -71
  18. package/dist/integrations/github/issue-triage.js.map +1 -1
  19. package/dist/integrations/github/pat.js +47 -36
  20. package/dist/integrations/github/pat.js.map +1 -1
  21. package/dist/integrations/github/provenance.js +72 -101
  22. package/dist/integrations/github/provenance.js.map +1 -1
  23. package/dist/integrations/github/routes.js +1272 -1881
  24. package/dist/integrations/github/routes.js.map +1 -1
  25. package/dist/integrations/github/rules.js +203 -471
  26. package/dist/integrations/github/rules.js.map +1 -1
  27. package/dist/integrations/github/sandbox.js +400 -345
  28. package/dist/integrations/github/sandbox.js.map +1 -1
  29. package/dist/integrations/github/session-subscriptions.js +165 -223
  30. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  31. package/dist/integrations/github/subscriptions.js +43 -51
  32. package/dist/integrations/github/subscriptions.js.map +1 -1
  33. package/dist/integrations/github/token-refresh.js +15 -18
  34. package/dist/integrations/github/token-refresh.js.map +1 -1
  35. package/dist/integrations/github/webhook.js +359 -343
  36. package/dist/integrations/github/webhook.js.map +1 -1
  37. package/dist/integrations/linear/agent-tools.js +77 -100
  38. package/dist/integrations/linear/agent-tools.js.map +1 -1
  39. package/dist/integrations/linear/integration.js +611 -1309
  40. package/dist/integrations/linear/integration.js.map +1 -1
  41. package/dist/integrations/linear/routes.js +227 -230
  42. package/dist/integrations/linear/routes.js.map +1 -1
  43. package/dist/integrations/linear/rules.js +126 -346
  44. package/dist/integrations/linear/rules.js.map +1 -1
  45. package/dist/integrations/linear/storage.js +1 -1
  46. package/dist/integrations/platform/api-client.js +131 -135
  47. package/dist/integrations/platform/api-client.js.map +1 -1
  48. package/dist/integrations/platform/github/event-worker.js +275 -573
  49. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  50. package/dist/integrations/platform/github/integration.js +845 -4022
  51. package/dist/integrations/platform/github/integration.js.map +1 -1
  52. package/dist/integrations/platform/linear/integration.js +457 -1309
  53. package/dist/integrations/platform/linear/integration.js.map +1 -1
  54. package/dist/integrations/workos/integration.js +80 -85
  55. package/dist/integrations/workos/integration.js.map +1 -1
  56. package/dist/routes/config.d.ts.map +1 -1
  57. package/dist/routes/config.js +933 -912
  58. package/dist/routes/config.js.map +1 -1
  59. package/dist/routes/custom-provider-source.js +110 -93
  60. package/dist/routes/custom-provider-source.js.map +1 -1
  61. package/dist/routes/fs.js +401 -349
  62. package/dist/routes/fs.js.map +1 -1
  63. package/dist/routes/intake.js +165 -165
  64. package/dist/routes/intake.js.map +1 -1
  65. package/dist/routes/oauth.js +447 -25166
  66. package/dist/routes/oauth.js.map +1 -1
  67. package/dist/routes/projects.js +437 -376
  68. package/dist/routes/projects.js.map +1 -1
  69. package/dist/routes/provider-credentials.js +76 -58
  70. package/dist/routes/provider-credentials.js.map +1 -1
  71. package/dist/routes/route.js +12 -8
  72. package/dist/routes/route.js.map +1 -1
  73. package/dist/routes/skills.js +160 -173
  74. package/dist/routes/skills.js.map +1 -1
  75. package/dist/routes/surface.js +250 -28615
  76. package/dist/routes/surface.js.map +1 -1
  77. package/dist/routes/tenant-credentials.js +141 -166
  78. package/dist/routes/tenant-credentials.js.map +1 -1
  79. package/dist/routes/work-items.js +654 -862
  80. package/dist/routes/work-items.js.map +1 -1
  81. package/dist/rules/binding-context.js +23 -35
  82. package/dist/rules/binding-context.js.map +1 -1
  83. package/dist/rules/defaults.js +200 -309
  84. package/dist/rules/defaults.js.map +1 -1
  85. package/dist/rules/dispatcher.js +438 -683
  86. package/dist/rules/dispatcher.js.map +1 -1
  87. package/dist/rules/index.js +5 -599
  88. package/dist/rules/processor.js +344 -596
  89. package/dist/rules/processor.js.map +1 -1
  90. package/dist/rules/resolve.js +21 -18
  91. package/dist/rules/resolve.js.map +1 -1
  92. package/dist/rules/start-coordinator.js +168 -157
  93. package/dist/rules/start-coordinator.js.map +1 -1
  94. package/dist/rules/tools.js +50 -85
  95. package/dist/rules/tools.js.map +1 -1
  96. package/dist/rules/transition-service.js +183 -454
  97. package/dist/rules/transition-service.js.map +1 -1
  98. package/dist/rules/types.js +33 -29
  99. package/dist/rules/types.js.map +1 -1
  100. package/dist/rules/validation.js +266 -281
  101. package/dist/rules/validation.js.map +1 -1
  102. package/dist/sandbox/fleet.js +272 -250
  103. package/dist/sandbox/fleet.js.map +1 -1
  104. package/dist/sandbox/reattach.js +13 -6
  105. package/dist/sandbox/reattach.js.map +1 -1
  106. package/dist/server-error.js +12 -16
  107. package/dist/server-error.js.map +1 -1
  108. package/dist/skills/service.js +30 -35
  109. package/dist/skills/service.js.map +1 -1
  110. package/dist/spa-static.js +91 -65
  111. package/dist/spa-static.js.map +1 -1
  112. package/dist/state-signing.js +73 -44
  113. package/dist/state-signing.js.map +1 -1
  114. package/dist/storage/domains/audit/agent-audit.js +60 -59
  115. package/dist/storage/domains/audit/agent-audit.js.map +1 -1
  116. package/dist/storage/domains/audit/base.js +169 -112
  117. package/dist/storage/domains/audit/base.js.map +1 -1
  118. package/dist/storage/domains/audit/domain.js +155 -153
  119. package/dist/storage/domains/audit/domain.js.map +1 -1
  120. package/dist/storage/domains/credentials/base.js +259 -243
  121. package/dist/storage/domains/credentials/base.js.map +1 -1
  122. package/dist/storage/domains/custom-providers/base.js +138 -143
  123. package/dist/storage/domains/custom-providers/base.js.map +1 -1
  124. package/dist/storage/domains/intake/base.js +75 -57
  125. package/dist/storage/domains/intake/base.js.map +1 -1
  126. package/dist/storage/domains/integrations/base.js +310 -245
  127. package/dist/storage/domains/integrations/base.js.map +1 -1
  128. package/dist/storage/domains/memory-settings/base.js +117 -102
  129. package/dist/storage/domains/memory-settings/base.js.map +1 -1
  130. package/dist/storage/domains/model-packs/base.js +89 -88
  131. package/dist/storage/domains/model-packs/base.js.map +1 -1
  132. package/dist/storage/domains/projects/base.js +97 -92
  133. package/dist/storage/domains/projects/base.js.map +1 -1
  134. package/dist/storage/domains/queue-health/base.js +111 -76
  135. package/dist/storage/domains/queue-health/base.js.map +1 -1
  136. package/dist/storage/domains/source-control/base.js +777 -706
  137. package/dist/storage/domains/source-control/base.js.map +1 -1
  138. package/dist/storage/domains/source-control/inmemory.js +322 -348
  139. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  140. package/dist/storage/domains/work-items/base.js +1266 -1148
  141. package/dist/storage/domains/work-items/base.js.map +1 -1
  142. package/dist/storage/domains/work-items/metrics.js +202 -184
  143. package/dist/storage/domains/work-items/metrics.js.map +1 -1
  144. package/dist/timing.js +29 -22
  145. package/dist/timing.js.map +1 -1
  146. package/dist/workspace.js +236 -477
  147. package/dist/workspace.js.map +1 -1
  148. package/package.json +8 -8
  149. package/dist/capabilities/connection.js.map +0 -1
  150. package/dist/capabilities/intake.js.map +0 -1
  151. package/dist/capabilities/version-control.js.map +0 -1
  152. package/dist/index.js.map +0 -1
  153. package/dist/integrations/base.js.map +0 -1
  154. package/dist/integrations/linear/storage.js.map +0 -1
  155. 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