@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,591 +1,293 @@
1
- // src/integrations/platform/github/event-worker.ts
1
+ import { listPullRequestSubscriptionsForWebhook, retirePullRequestSubscription } from "../../github/subscriptions.js";
2
+ import { dispatchGithubWebhook } from "../../github/webhook.js";
3
+ import { PlatformApiError } from "../api-client.js";
2
4
  import { randomUUID } from "crypto";
3
- import { isLeaseProvider, NoopLeaseProvider } from "@mastra/core/events";
5
+ import { NoopLeaseProvider, isLeaseProvider } from "@mastra/core/events";
4
6
  import { MastraWorker } from "@mastra/core/worker";
5
-
6
- // src/integrations/github/subscriptions.ts
7
- function changeRequestTargetKey(input) {
8
- return `change-request:${input.installationExternalId}:${input.repositoryExternalId}:${input.changeRequestId}`;
9
- }
10
- async function listPullRequestSubscriptionsForWebhook(input, options, storage) {
11
- const rows = await storage.subscriptions.listByTarget(changeRequestTargetKey(input));
12
- return options?.includeTerminal ? rows : rows.filter((row) => row.status === "open");
13
- }
14
- function retirePullRequestSubscription(id, status, storage) {
15
- return storage.subscriptions.updateStatus(id, status);
16
- }
17
-
18
- // src/integrations/github/webhook.ts
19
- import { createHmac, timingSafeEqual } from "crypto";
20
- function getObject(value) {
21
- return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
22
- }
23
- function getString(value) {
24
- return typeof value === "string" && value.length > 0 ? value : void 0;
25
- }
26
- function getNumber(value) {
27
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
28
- }
29
- function getBoolean(value) {
30
- return typeof value === "boolean" ? value : void 0;
31
- }
32
- function normalizeGithubWebhookMetadata(parsed) {
33
- const { event, deliveryId, payload } = parsed;
34
- const repository = getObject(payload.repository);
35
- const issue = getObject(payload.issue);
36
- const pullRequest = getObject(payload.pull_request);
37
- const sender = getObject(payload.sender);
38
- const installation = getObject(payload.installation);
39
- const issuePullRequest = getObject(issue?.pull_request);
40
- return {
41
- event,
42
- action: getString(payload.action),
43
- deliveryId,
44
- repository: getString(repository?.full_name),
45
- repositoryId: getNumber(repository?.id),
46
- issueNumber: getNumber(issue?.number),
47
- pullRequestNumber: getNumber(pullRequest?.number) ?? (event === "issue_comment" && issuePullRequest ? getNumber(issue?.number) : void 0),
48
- sender: getString(sender?.login),
49
- senderType: getString(sender?.type),
50
- installationId: getNumber(installation?.id)
51
- };
52
- }
53
- function notificationSummary(metadata, label) {
54
- const actor = metadata.sender ? `${metadata.sender} ` : "";
55
- return `${actor}${label} on ${metadata.repository}#${metadata.pullRequestNumber}`;
56
- }
57
- function notificationTargetUrl(event, payload) {
58
- if (event === "issue_comment" || event === "pull_request_review_comment") {
59
- return getString(getObject(payload.comment)?.html_url);
60
- }
61
- if (event === "pull_request_review") {
62
- return getString(getObject(payload.review)?.html_url);
63
- }
64
- return getString(getObject(payload.pull_request)?.html_url);
65
- }
66
- function classifyGithubWebhook(parsed) {
67
- const metadata = normalizeGithubWebhookMetadata(parsed);
68
- const { event, payload } = parsed;
69
- const action = metadata.action;
70
- if (!action || !metadata.repositoryId || !metadata.installationId || !metadata.pullRequestNumber || !metadata.repository) {
71
- return void 0;
72
- }
73
- let priority;
74
- let kind;
75
- let label;
76
- let terminal = false;
77
- if (event === "pull_request_review" && action === "submitted") {
78
- const state = getString(getObject(payload.review)?.state)?.toLowerCase().replaceAll("_", "-");
79
- priority = state === "approved" || state === "changes-requested" ? "urgent" : "high";
80
- kind = state === "approved" ? "review-approved" : state === "changes-requested" ? "review-changes-requested" : "review-submitted";
81
- label = state === "approved" ? "approved the pull request" : state === "changes-requested" ? "requested changes" : "submitted a review";
82
- } else if (event === "pull_request" && action === "closed") {
83
- const merged = getBoolean(getObject(payload.pull_request)?.merged) === true;
84
- priority = "urgent";
85
- kind = merged ? "pull-request-merged" : "pull-request-closed";
86
- label = merged ? "merged the pull request" : "closed the pull request";
87
- terminal = true;
88
- } else if (event === "issue_comment" && action === "created") {
89
- priority = "high";
90
- kind = "issue-comment-created";
91
- label = "commented";
92
- } else if (event === "pull_request_review_comment" && action === "created") {
93
- priority = "high";
94
- kind = "review-comment-created";
95
- label = "left a review comment";
96
- } else if (event === "pull_request" && action === "reopened") {
97
- priority = "high";
98
- kind = "pull-request-reopened";
99
- label = "reopened the pull request";
100
- } else if (event === "pull_request_review" && action === "dismissed") {
101
- priority = "high";
102
- kind = "review-dismissed";
103
- label = "dismissed a review";
104
- } else if (event === "pull_request" && [
105
- "synchronize",
106
- "ready_for_review",
107
- "converted_to_draft",
108
- "assigned",
109
- "unassigned",
110
- "review_requested",
111
- "review_request_removed"
112
- ].includes(action)) {
113
- priority = "medium";
114
- kind = `pull-request-${action.replaceAll("_", "-")}`;
115
- label = action.replaceAll("_", " ");
116
- } else if (event === "pull_request" && ["edited", "labeled", "unlabeled", "milestoned", "demilestoned"].includes(action)) {
117
- priority = "low";
118
- kind = `pull-request-${action.replaceAll("_", "-")}`;
119
- label = action.replaceAll("_", " ");
120
- } else {
121
- return void 0;
122
- }
123
- return {
124
- action,
125
- kind,
126
- priority,
127
- summary: notificationSummary(metadata, label),
128
- terminal,
129
- metadata: {
130
- ...metadata,
131
- pullRequestNumber: metadata.pullRequestNumber,
132
- repositoryId: metadata.repositoryId,
133
- installationId: metadata.installationId
134
- },
135
- payload
136
- };
137
- }
138
- async function resolveSubscriptionSession(controller, subscription) {
139
- const { sessionId, resourceId, threadId } = subscription;
140
- if (!sessionId || !resourceId || !threadId) {
141
- throw new Error(`GitHub subscription ${subscription.id} is missing its session binding.`);
142
- }
143
- const scope = subscription.sessionScope || void 0;
144
- let session = await controller.getSessionByResource(resourceId, scope);
145
- if (!session) {
146
- const tags = {
147
- factoryProjectId: resourceId,
148
- projectRepositoryId: subscription.data.projectRepositoryId,
149
- ...scope ? { worktreePath: scope } : {}
150
- };
151
- session = await controller.createSession({
152
- id: sessionId,
153
- ownerId: subscription.data.ownerId,
154
- resourceId,
155
- scope,
156
- tags
157
- });
158
- }
159
- if (session.thread.getId() !== threadId) {
160
- await session.thread.switch({ threadId, emitEvent: false });
161
- }
162
- if (session.thread.getId() !== threadId) {
163
- throw new Error(`Session ${sessionId} did not bind thread ${threadId}.`);
164
- }
165
- return session;
166
- }
167
- var AUTHORIZED_BOTS = /* @__PURE__ */ new Set(["coderabbitai[bot]", "devin-ai-integration[bot]"]);
168
- var AUTHORIZED_PERMISSIONS = /* @__PURE__ */ new Set(["admin", "maintain", "write"]);
169
- var PERMISSION_CHECK_TIMEOUT_MS = 5e3;
170
- var AUTHOR_GATED_KINDS = /* @__PURE__ */ new Set([
171
- "issue-comment-created",
172
- "review-comment-created",
173
- "review-submitted",
174
- "review-approved",
175
- "review-changes-requested",
176
- "review-dismissed"
7
+ //#region src/integrations/platform/github/event-worker.ts
8
+ const API_PREFIX = "/v1/server/github-app";
9
+ const DEFAULT_POLL_INTERVAL_MS = 2e4;
10
+ const EVENT_PAGE_SIZE = 500;
11
+ const MIN_LEASE_TTL_MS = 3e4;
12
+ const CURSOR_ORG_ID = "__platform_github_event_worker__";
13
+ const CURSOR_USER_ID = "worker";
14
+ const SUPPORTED_EVENTS = /* @__PURE__ */ new Set([
15
+ "issues",
16
+ "issue_comment",
17
+ "pull_request",
18
+ "pull_request_review",
19
+ "pull_request_review_comment"
177
20
  ]);
178
- async function isAuthorizedGithubSender(notification, github) {
179
- if (!AUTHOR_GATED_KINDS.has(notification.kind)) return true;
180
- const sender = notification.metadata.sender;
181
- const repository = notification.metadata.repository;
182
- if (!sender || !repository) return false;
183
- const normalizedSender = sender.toLowerCase();
184
- if (notification.metadata.senderType?.toLowerCase() === "bot" || normalizedSender.endsWith("[bot]")) {
185
- return AUTHORIZED_BOTS.has(normalizedSender);
186
- }
187
- if (!github) return false;
188
- const abortController = new AbortController();
189
- let timeout;
190
- try {
191
- const permission = await Promise.race([
192
- github.getRepositoryCollaboratorPermission(
193
- notification.metadata.installationId,
194
- repository,
195
- sender,
196
- abortController.signal
197
- ),
198
- new Promise((resolve) => {
199
- timeout = setTimeout(() => {
200
- abortController.abort();
201
- resolve(void 0);
202
- }, PERMISSION_CHECK_TIMEOUT_MS);
203
- })
204
- ]);
205
- return permission !== void 0 && AUTHORIZED_PERMISSIONS.has(permission);
206
- } catch {
207
- return false;
208
- } finally {
209
- if (timeout) clearTimeout(timeout);
210
- }
211
- }
212
- async function dispatchGithubWebhook(parsed, dependencies) {
213
- const notification = classifyGithubWebhook(parsed);
214
- if (!notification) return { delivered: 0, failed: 0, ignored: true };
215
- const isAuthorizedSender = dependencies.isAuthorizedSender ?? ((n) => isAuthorizedGithubSender(n, dependencies.github));
216
- if (!await isAuthorizedSender(notification)) {
217
- return { delivered: 0, failed: 0, ignored: true };
218
- }
219
- const target = {
220
- installationExternalId: notification.metadata.installationId.toString(),
221
- repositoryExternalId: notification.metadata.repositoryId.toString(),
222
- changeRequestId: notification.metadata.pullRequestNumber.toString()
223
- };
224
- const listSubscriptions = dependencies.listSubscriptions ?? ((subscriptionTarget, options) => {
225
- if (!dependencies.github) throw new Error("GitHub integration is required to load webhook subscriptions.");
226
- return listPullRequestSubscriptionsForWebhook(
227
- subscriptionTarget,
228
- options,
229
- dependencies.github.integrationStorage
230
- );
231
- });
232
- const retireSubscription = dependencies.retireSubscription ?? ((id, status) => {
233
- if (!dependencies.github) throw new Error("GitHub integration is required to retire webhook subscriptions.");
234
- return retirePullRequestSubscription(id, status, dependencies.github.integrationStorage);
235
- });
236
- const subscriptions = await listSubscriptions(target, { includeTerminal: notification.action === "reopened" });
237
- let delivered = 0;
238
- let failed = 0;
239
- for (const subscription of subscriptions) {
240
- try {
241
- const session = await resolveSubscriptionSession(dependencies.controller, subscription);
242
- const result = await session.sendNotificationSignal({
243
- source: "github",
244
- kind: notification.kind,
245
- summary: notification.summary,
246
- priority: notification.priority,
247
- payload: notification.payload,
248
- sourceId: parsed.deliveryId,
249
- dedupeKey: `${parsed.deliveryId}:${subscription.sessionId}:${subscription.threadId}`,
250
- coalesceKey: `github:${subscription.data.repositoryExternalId}:pull-request:${subscription.data.changeRequestId}`,
251
- metadata: {
252
- event: notification.metadata.event,
253
- action: notification.action,
254
- repository: notification.metadata.repository,
255
- issueNumber: notification.metadata.issueNumber,
256
- pullRequestNumber: notification.metadata.pullRequestNumber,
257
- targetUrl: notificationTargetUrl(parsed.event, parsed.payload),
258
- deliveryId: parsed.deliveryId
259
- }
260
- });
261
- await Promise.all([result.persisted, result.accepted].filter(Boolean));
262
- if (notification.terminal) {
263
- await retireSubscription(subscription.id, notification.kind === "pull-request-merged" ? "merged" : "closed");
264
- } else if (notification.action === "reopened") {
265
- await retireSubscription(subscription.id, "open");
266
- }
267
- delivered += 1;
268
- } catch (error) {
269
- failed += 1;
270
- dependencies.onTargetError?.(subscription, error);
271
- }
272
- }
273
- return { delivered, failed, ignored: false };
274
- }
275
-
276
- // src/integrations/platform/api-client.ts
277
- var PlatformApiError = class extends Error {
278
- status;
279
- retryAfterSeconds;
280
- constructor(message, status, retryAfterSeconds = null) {
281
- super(message);
282
- this.name = "PlatformApiError";
283
- this.status = status;
284
- this.retryAfterSeconds = retryAfterSeconds;
285
- }
286
- };
287
-
288
- // src/integrations/platform/github/event-worker.ts
289
- var API_PREFIX = "/v1/server/github-app";
290
- var DEFAULT_POLL_INTERVAL_MS = 2e4;
291
- var EVENT_PAGE_SIZE = 500;
292
- var MIN_LEASE_TTL_MS = 3e4;
293
- var CURSOR_ORG_ID = "__platform_github_event_worker__";
294
- var CURSOR_USER_ID = "worker";
295
- var SUPPORTED_EVENTS = /* @__PURE__ */ new Set([
296
- "issues",
297
- "issue_comment",
298
- "pull_request",
299
- "pull_request_review",
300
- "pull_request_review_comment"
21
+ const AUTHOR_GATED_KINDS = /* @__PURE__ */ new Set([
22
+ "issue-comment",
23
+ "pull-request-comment",
24
+ "pull-request-review",
25
+ "pull-request-review-comment"
301
26
  ]);
302
- var AUTHOR_GATED_KINDS2 = /* @__PURE__ */ new Set([
303
- "issue-comment",
304
- "pull-request-comment",
305
- "pull-request-review",
306
- "pull-request-review-comment"
27
+ const AUTHORIZED_BOTS = /* @__PURE__ */ new Set(["coderabbitai[bot]", "devin-ai-integration[bot]"]);
28
+ const AUTHORIZED_PERMISSIONS = /* @__PURE__ */ new Set([
29
+ "admin",
30
+ "maintain",
31
+ "write"
307
32
  ]);
308
- var AUTHORIZED_BOTS2 = /* @__PURE__ */ new Set(["coderabbitai[bot]", "devin-ai-integration[bot]"]);
309
- var AUTHORIZED_PERMISSIONS2 = /* @__PURE__ */ new Set(["admin", "maintain", "write"]);
310
- var PERMISSION_CHECK_TIMEOUT_MS2 = 5e3;
33
+ const PERMISSION_CHECK_TIMEOUT_MS = 5e3;
311
34
  var PlatformGithubEventWorker = class extends MastraWorker {
312
- name = "platform-github-events";
313
- #client;
314
- #controller;
315
- #github;
316
- #storage;
317
- #ingestFactoryEvent;
318
- #intervalMs;
319
- #now;
320
- #dispatch;
321
- #leaseOwner = randomUUID();
322
- #running = false;
323
- #timer;
324
- #leaseRenewalTimer;
325
- #inFlight;
326
- #leaseProvider = NoopLeaseProvider;
327
- #leaseTtlMs;
328
- #hasLease = false;
329
- #startedAt = 0;
330
- #settings = { version: 1, repositories: {} };
331
- constructor(config) {
332
- super();
333
- this.#client = config.client;
334
- this.#controller = config.controller;
335
- this.#github = config.github;
336
- this.#storage = config.storage;
337
- this.#ingestFactoryEvent = config.ingestFactoryEvent;
338
- this.#intervalMs = config.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
339
- if (!Number.isFinite(this.#intervalMs) || this.#intervalMs <= 0) {
340
- throw new Error("Platform GitHub event polling interval must be a positive number.");
341
- }
342
- this.#leaseTtlMs = Math.max(MIN_LEASE_TTL_MS, this.#intervalMs * 3);
343
- this.#now = config.now ?? Date.now;
344
- this.#dispatch = config.dispatch ?? dispatchGithubWebhook;
345
- }
346
- async init(deps) {
347
- await super.init(deps);
348
- this.#leaseProvider = getLeaseProvider(deps.pubsub);
349
- }
350
- async start() {
351
- if (this.#running) return;
352
- if (!this.deps) throw new Error("PlatformGithubEventWorker: call init() before start()");
353
- this.#startedAt = this.#now() - 1;
354
- this.#settings = normalizeSettings(await this.#storage.settings.get(CURSOR_ORG_ID, CURSOR_USER_ID));
355
- this.#running = true;
356
- this.deps.logger.info("Platform GitHub event polling started", {
357
- intervalMs: this.#intervalMs,
358
- leaseTtlMs: this.#leaseTtlMs
359
- });
360
- this.#schedule(0);
361
- }
362
- async stop() {
363
- if (!this.#running) return;
364
- this.#running = false;
365
- if (this.#timer) clearTimeout(this.#timer);
366
- this.#timer = void 0;
367
- this.#stopLeaseRenewal();
368
- await this.#inFlight;
369
- if (this.#hasLease) {
370
- await this.#leaseProvider.releaseLease(this.#leaseKey(), this.#leaseOwner).catch(() => void 0);
371
- this.#hasLease = false;
372
- }
373
- }
374
- get isRunning() {
375
- return this.#running;
376
- }
377
- #schedule(delayMs) {
378
- if (!this.#running) return;
379
- this.#timer = setTimeout(() => {
380
- this.#timer = void 0;
381
- const run = this.#tick();
382
- this.#inFlight = run;
383
- void run.finally(() => {
384
- if (this.#inFlight === run) this.#inFlight = void 0;
385
- });
386
- }, delayMs);
387
- this.#timer.unref?.();
388
- }
389
- async #tick() {
390
- let nextDelay = this.#intervalMs;
391
- try {
392
- if (!await this.#ensureLease()) return;
393
- nextDelay = await this.#poll();
394
- } catch (error) {
395
- nextDelay = retryDelay(error, this.#intervalMs);
396
- this.deps?.logger.error("Platform GitHub event polling cycle failed", {
397
- error: error instanceof Error ? error.message : String(error),
398
- retryInMs: nextDelay
399
- });
400
- } finally {
401
- this.#schedule(nextDelay);
402
- }
403
- }
404
- async #ensureLease() {
405
- if (this.#hasLease) return true;
406
- const result = await this.#leaseProvider.acquireLease(this.#leaseKey(), this.#leaseOwner, this.#leaseTtlMs);
407
- this.#hasLease = result.acquired;
408
- if (this.#hasLease) this.#startLeaseRenewal();
409
- return this.#hasLease;
410
- }
411
- #startLeaseRenewal() {
412
- if (this.#leaseRenewalTimer) return;
413
- this.#leaseRenewalTimer = setInterval(
414
- () => {
415
- void this.#leaseProvider.renewLease(this.#leaseKey(), this.#leaseOwner, this.#leaseTtlMs).then((renewed) => {
416
- if (!renewed) {
417
- this.#hasLease = false;
418
- this.#stopLeaseRenewal();
419
- }
420
- }).catch((error) => {
421
- this.#hasLease = false;
422
- this.#stopLeaseRenewal();
423
- this.deps?.logger.warn("Platform GitHub event polling lease renewal failed", {
424
- error: error instanceof Error ? error.message : String(error)
425
- });
426
- });
427
- },
428
- Math.floor(this.#leaseTtlMs / 3)
429
- );
430
- this.#leaseRenewalTimer.unref?.();
431
- }
432
- #stopLeaseRenewal() {
433
- if (this.#leaseRenewalTimer) clearInterval(this.#leaseRenewalTimer);
434
- this.#leaseRenewalTimer = void 0;
435
- }
436
- async #poll() {
437
- const repositories = await this.#discoverRepositories();
438
- let retryInMs = this.#intervalMs;
439
- for (const repository of repositories) {
440
- if (!this.#running || !this.#hasLease) break;
441
- try {
442
- await this.#pollRepository(repository.id);
443
- } catch (error) {
444
- const delay = retryDelay(error, this.#intervalMs);
445
- retryInMs = Math.max(retryInMs, delay);
446
- this.deps?.logger.error("Platform GitHub repository event polling failed", {
447
- repositoryId: repository.id,
448
- error: error instanceof Error ? error.message : String(error),
449
- retryInMs: delay
450
- });
451
- if (error instanceof PlatformApiError && error.status === 429) break;
452
- }
453
- }
454
- return retryInMs;
455
- }
456
- async #discoverRepositories() {
457
- const result = await this.#client.request("GET", `${API_PREFIX}/installations`);
458
- const repositories = /* @__PURE__ */ new Map();
459
- for (const installation of result.installations) {
460
- if (!installation.usable || installation.suspendedAt) continue;
461
- const page = await this.#client.request(
462
- "GET",
463
- `${API_PREFIX}/installations/${installation.installationId}/repositories`
464
- );
465
- for (const repository of page.repositories) repositories.set(repository.id, repository);
466
- }
467
- return [...repositories.values()];
468
- }
469
- async #pollRepository(repositoryId) {
470
- const key = String(repositoryId);
471
- if (!this.#settings.repositories[key]) {
472
- this.#settings.repositories[key] = { afterTimestamp: this.#startedAt };
473
- await this.#saveSettings();
474
- }
475
- while (this.#running && this.#hasLease) {
476
- const cursor = this.#settings.repositories[key];
477
- const query = new URLSearchParams({ limit: String(EVENT_PAGE_SIZE) });
478
- if ("afterEventId" in cursor) query.set("afterEventId", cursor.afterEventId);
479
- else query.set("afterTimestamp", String(cursor.afterTimestamp));
480
- const pollStartedAt = performance.now();
481
- const page = await this.#client.request(
482
- "GET",
483
- `${API_PREFIX}/repositories/${repositoryId}/events?${query}`
484
- );
485
- this.deps?.logger.info("Platform GitHub repository event poll completed", {
486
- repositoryId,
487
- eventCount: page.events.length,
488
- latencyMs: Math.round(performance.now() - pollStartedAt)
489
- });
490
- if (page.events.length === 0 || !page.nextCursor) return;
491
- for (const event of page.events) {
492
- if (!this.#running || !this.#hasLease) return;
493
- const parsed = parseEvent(event);
494
- if (!parsed) {
495
- this.deps?.logger.warn("Platform GitHub event log returned a malformed event", {
496
- repositoryId,
497
- eventId: event.id
498
- });
499
- continue;
500
- }
501
- if (isFactoryClosureEvent(parsed)) {
502
- await this.#ingestFactoryEvent?.(parsed);
503
- }
504
- const result = await this.#dispatch(parsed, {
505
- controller: this.#controller,
506
- listSubscriptions: (target, options) => listPullRequestSubscriptionsForWebhook(target, options, this.#github.integrationStorage),
507
- retireSubscription: (id, status) => retirePullRequestSubscription(id, status, this.#github.integrationStorage),
508
- isAuthorizedSender: (notification) => this.#isAuthorizedSender(notification),
509
- onTargetError: (subscription, error) => {
510
- this.deps?.logger.error("Platform GitHub event delivery failed for a subscription", {
511
- subscriptionId: subscription.id,
512
- resourceId: subscription.resourceId,
513
- threadId: subscription.threadId,
514
- error: error instanceof Error ? error.message : String(error)
515
- });
516
- }
517
- });
518
- if (result.failed > 0) {
519
- throw new Error(
520
- `Platform GitHub event ${event.deliveryId} failed for ${result.failed} subscribed target(s).`
521
- );
522
- }
523
- }
524
- if (page.nextCursor === ("afterEventId" in cursor ? cursor.afterEventId : void 0)) return;
525
- this.#settings.repositories[key] = { afterEventId: page.nextCursor };
526
- await this.#saveSettings();
527
- }
528
- }
529
- async #isAuthorizedSender(notification) {
530
- if (!AUTHOR_GATED_KINDS2.has(notification.kind)) return true;
531
- const sender = notification.metadata.sender;
532
- const repository = notification.metadata.repository;
533
- if (!sender || !repository) return false;
534
- if (AUTHORIZED_BOTS2.has(sender)) return true;
535
- const abortController = new AbortController();
536
- const timeout = setTimeout(() => abortController.abort(), PERMISSION_CHECK_TIMEOUT_MS2);
537
- try {
538
- const permission = await this.#github.getRepositoryCollaboratorPermission(
539
- notification.metadata.installationId,
540
- repository,
541
- sender,
542
- abortController.signal
543
- );
544
- return permission !== void 0 && AUTHORIZED_PERMISSIONS2.has(permission);
545
- } catch {
546
- return false;
547
- } finally {
548
- clearTimeout(timeout);
549
- }
550
- }
551
- async #saveSettings() {
552
- await this.#storage.settings.save(CURSOR_ORG_ID, CURSOR_USER_ID, this.#settings);
553
- }
554
- #leaseKey() {
555
- return `${this.name}:${this.#storage.integrationId}`;
556
- }
35
+ name = "platform-github-events";
36
+ #client;
37
+ #controller;
38
+ #github;
39
+ #storage;
40
+ #ingestFactoryEvent;
41
+ #intervalMs;
42
+ #now;
43
+ #dispatch;
44
+ #leaseOwner = randomUUID();
45
+ #running = false;
46
+ #timer;
47
+ #leaseRenewalTimer;
48
+ #inFlight;
49
+ #leaseProvider = NoopLeaseProvider;
50
+ #leaseTtlMs;
51
+ #hasLease = false;
52
+ #startedAt = 0;
53
+ #settings = {
54
+ version: 1,
55
+ repositories: {}
56
+ };
57
+ constructor(config) {
58
+ super();
59
+ this.#client = config.client;
60
+ this.#controller = config.controller;
61
+ this.#github = config.github;
62
+ this.#storage = config.storage;
63
+ this.#ingestFactoryEvent = config.ingestFactoryEvent;
64
+ this.#intervalMs = config.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
65
+ if (!Number.isFinite(this.#intervalMs) || this.#intervalMs <= 0) throw new Error("Platform GitHub event polling interval must be a positive number.");
66
+ this.#leaseTtlMs = Math.max(MIN_LEASE_TTL_MS, this.#intervalMs * 3);
67
+ this.#now = config.now ?? Date.now;
68
+ this.#dispatch = config.dispatch ?? dispatchGithubWebhook;
69
+ }
70
+ async init(deps) {
71
+ await super.init(deps);
72
+ this.#leaseProvider = getLeaseProvider(deps.pubsub);
73
+ }
74
+ async start() {
75
+ if (this.#running) return;
76
+ if (!this.deps) throw new Error("PlatformGithubEventWorker: call init() before start()");
77
+ this.#startedAt = this.#now() - 1;
78
+ this.#settings = normalizeSettings(await this.#storage.settings.get(CURSOR_ORG_ID, CURSOR_USER_ID));
79
+ this.#running = true;
80
+ this.deps.logger.info("Platform GitHub event polling started", {
81
+ intervalMs: this.#intervalMs,
82
+ leaseTtlMs: this.#leaseTtlMs
83
+ });
84
+ this.#schedule(0);
85
+ }
86
+ async stop() {
87
+ if (!this.#running) return;
88
+ this.#running = false;
89
+ if (this.#timer) clearTimeout(this.#timer);
90
+ this.#timer = void 0;
91
+ this.#stopLeaseRenewal();
92
+ await this.#inFlight;
93
+ if (this.#hasLease) {
94
+ await this.#leaseProvider.releaseLease(this.#leaseKey(), this.#leaseOwner).catch(() => void 0);
95
+ this.#hasLease = false;
96
+ }
97
+ }
98
+ get isRunning() {
99
+ return this.#running;
100
+ }
101
+ #schedule(delayMs) {
102
+ if (!this.#running) return;
103
+ this.#timer = setTimeout(() => {
104
+ this.#timer = void 0;
105
+ const run = this.#tick();
106
+ this.#inFlight = run;
107
+ run.finally(() => {
108
+ if (this.#inFlight === run) this.#inFlight = void 0;
109
+ });
110
+ }, delayMs);
111
+ this.#timer.unref?.();
112
+ }
113
+ async #tick() {
114
+ let nextDelay = this.#intervalMs;
115
+ try {
116
+ if (!await this.#ensureLease()) return;
117
+ nextDelay = await this.#poll();
118
+ } catch (error) {
119
+ nextDelay = retryDelay(error, this.#intervalMs);
120
+ this.deps?.logger.error("Platform GitHub event polling cycle failed", {
121
+ error: error instanceof Error ? error.message : String(error),
122
+ retryInMs: nextDelay
123
+ });
124
+ } finally {
125
+ this.#schedule(nextDelay);
126
+ }
127
+ }
128
+ async #ensureLease() {
129
+ if (this.#hasLease) return true;
130
+ const result = await this.#leaseProvider.acquireLease(this.#leaseKey(), this.#leaseOwner, this.#leaseTtlMs);
131
+ this.#hasLease = result.acquired;
132
+ if (this.#hasLease) this.#startLeaseRenewal();
133
+ return this.#hasLease;
134
+ }
135
+ #startLeaseRenewal() {
136
+ if (this.#leaseRenewalTimer) return;
137
+ this.#leaseRenewalTimer = setInterval(() => {
138
+ this.#leaseProvider.renewLease(this.#leaseKey(), this.#leaseOwner, this.#leaseTtlMs).then((renewed) => {
139
+ if (!renewed) {
140
+ this.#hasLease = false;
141
+ this.#stopLeaseRenewal();
142
+ }
143
+ }).catch((error) => {
144
+ this.#hasLease = false;
145
+ this.#stopLeaseRenewal();
146
+ this.deps?.logger.warn("Platform GitHub event polling lease renewal failed", { error: error instanceof Error ? error.message : String(error) });
147
+ });
148
+ }, Math.floor(this.#leaseTtlMs / 3));
149
+ this.#leaseRenewalTimer.unref?.();
150
+ }
151
+ #stopLeaseRenewal() {
152
+ if (this.#leaseRenewalTimer) clearInterval(this.#leaseRenewalTimer);
153
+ this.#leaseRenewalTimer = void 0;
154
+ }
155
+ async #poll() {
156
+ const repositories = await this.#discoverRepositories();
157
+ let retryInMs = this.#intervalMs;
158
+ for (const repository of repositories) {
159
+ if (!this.#running || !this.#hasLease) break;
160
+ try {
161
+ await this.#pollRepository(repository.id);
162
+ } catch (error) {
163
+ const delay = retryDelay(error, this.#intervalMs);
164
+ retryInMs = Math.max(retryInMs, delay);
165
+ this.deps?.logger.error("Platform GitHub repository event polling failed", {
166
+ repositoryId: repository.id,
167
+ error: error instanceof Error ? error.message : String(error),
168
+ retryInMs: delay
169
+ });
170
+ if (error instanceof PlatformApiError && error.status === 429) break;
171
+ }
172
+ }
173
+ return retryInMs;
174
+ }
175
+ async #discoverRepositories() {
176
+ const result = await this.#client.request("GET", `${API_PREFIX}/installations`);
177
+ const repositories = /* @__PURE__ */ new Map();
178
+ for (const installation of result.installations) {
179
+ if (!installation.usable || installation.suspendedAt) continue;
180
+ const page = await this.#client.request("GET", `${API_PREFIX}/installations/${installation.installationId}/repositories`);
181
+ for (const repository of page.repositories) repositories.set(repository.id, repository);
182
+ }
183
+ return [...repositories.values()];
184
+ }
185
+ async #pollRepository(repositoryId) {
186
+ const key = String(repositoryId);
187
+ if (!this.#settings.repositories[key]) {
188
+ this.#settings.repositories[key] = { afterTimestamp: this.#startedAt };
189
+ await this.#saveSettings();
190
+ }
191
+ while (this.#running && this.#hasLease) {
192
+ const cursor = this.#settings.repositories[key];
193
+ const query = new URLSearchParams({ limit: String(EVENT_PAGE_SIZE) });
194
+ if ("afterEventId" in cursor) query.set("afterEventId", cursor.afterEventId);
195
+ else query.set("afterTimestamp", String(cursor.afterTimestamp));
196
+ const pollStartedAt = performance.now();
197
+ const page = await this.#client.request("GET", `${API_PREFIX}/repositories/${repositoryId}/events?${query}`);
198
+ this.deps?.logger.info("Platform GitHub repository event poll completed", {
199
+ repositoryId,
200
+ eventCount: page.events.length,
201
+ latencyMs: Math.round(performance.now() - pollStartedAt)
202
+ });
203
+ if (page.events.length === 0 || !page.nextCursor) return;
204
+ for (const event of page.events) {
205
+ if (!this.#running || !this.#hasLease) return;
206
+ const parsed = parseEvent(event);
207
+ if (!parsed) {
208
+ this.deps?.logger.warn("Platform GitHub event log returned a malformed event", {
209
+ repositoryId,
210
+ eventId: event.id
211
+ });
212
+ continue;
213
+ }
214
+ if (isFactoryClosureEvent(parsed)) await this.#ingestFactoryEvent?.(parsed);
215
+ const result = await this.#dispatch(parsed, {
216
+ controller: this.#controller,
217
+ listSubscriptions: (target, options) => listPullRequestSubscriptionsForWebhook(target, options, this.#github.integrationStorage),
218
+ retireSubscription: (id, status) => retirePullRequestSubscription(id, status, this.#github.integrationStorage),
219
+ isAuthorizedSender: (notification) => this.#isAuthorizedSender(notification),
220
+ onTargetError: (subscription, error) => {
221
+ this.deps?.logger.error("Platform GitHub event delivery failed for a subscription", {
222
+ subscriptionId: subscription.id,
223
+ resourceId: subscription.resourceId,
224
+ threadId: subscription.threadId,
225
+ error: error instanceof Error ? error.message : String(error)
226
+ });
227
+ }
228
+ });
229
+ if (result.failed > 0) throw new Error(`Platform GitHub event ${event.deliveryId} failed for ${result.failed} subscribed target(s).`);
230
+ }
231
+ if (page.nextCursor === ("afterEventId" in cursor ? cursor.afterEventId : void 0)) return;
232
+ this.#settings.repositories[key] = { afterEventId: page.nextCursor };
233
+ await this.#saveSettings();
234
+ }
235
+ }
236
+ async #isAuthorizedSender(notification) {
237
+ if (!AUTHOR_GATED_KINDS.has(notification.kind)) return true;
238
+ const sender = notification.metadata.sender;
239
+ const repository = notification.metadata.repository;
240
+ if (!sender || !repository) return false;
241
+ if (AUTHORIZED_BOTS.has(sender)) return true;
242
+ const abortController = new AbortController();
243
+ const timeout = setTimeout(() => abortController.abort(), PERMISSION_CHECK_TIMEOUT_MS);
244
+ try {
245
+ const permission = await this.#github.getRepositoryCollaboratorPermission(notification.metadata.installationId, repository, sender, abortController.signal);
246
+ return permission !== void 0 && AUTHORIZED_PERMISSIONS.has(permission);
247
+ } catch {
248
+ return false;
249
+ } finally {
250
+ clearTimeout(timeout);
251
+ }
252
+ }
253
+ async #saveSettings() {
254
+ await this.#storage.settings.save(CURSOR_ORG_ID, CURSOR_USER_ID, this.#settings);
255
+ }
256
+ #leaseKey() {
257
+ return `${this.name}:${this.#storage.integrationId}`;
258
+ }
557
259
  };
558
260
  function getLeaseProvider(pubsub) {
559
- const getProvider = pubsub.getLeaseProvider;
560
- if (typeof getProvider === "function") return getProvider.call(pubsub) ?? NoopLeaseProvider;
561
- return isLeaseProvider(pubsub) ? pubsub : NoopLeaseProvider;
261
+ const getProvider = pubsub.getLeaseProvider;
262
+ if (typeof getProvider === "function") return getProvider.call(pubsub) ?? NoopLeaseProvider;
263
+ return isLeaseProvider(pubsub) ? pubsub : NoopLeaseProvider;
562
264
  }
563
265
  function normalizeSettings(value) {
564
- if (!value || value.version !== 1 || !value.repositories || typeof value.repositories !== "object") {
565
- return { version: 1, repositories: {} };
566
- }
567
- return { version: 1, repositories: { ...value.repositories } };
266
+ if (!value || value.version !== 1 || !value.repositories || typeof value.repositories !== "object") return {
267
+ version: 1,
268
+ repositories: {}
269
+ };
270
+ return {
271
+ version: 1,
272
+ repositories: { ...value.repositories }
273
+ };
568
274
  }
569
275
  function isFactoryClosureEvent(event) {
570
- return (event.event === "issues" || event.event === "pull_request") && event.payload.action === "closed";
276
+ return (event.event === "issues" || event.event === "pull_request") && event.payload.action === "closed";
571
277
  }
572
278
  function parseEvent(event) {
573
- if (!event.id || !event.deliveryId || !SUPPORTED_EVENTS.has(event.event) || !event.payload || typeof event.payload !== "object" || Array.isArray(event.payload)) {
574
- return null;
575
- }
576
- return {
577
- event: event.event,
578
- deliveryId: event.deliveryId,
579
- payload: event.payload
580
- };
279
+ if (!event.id || !event.deliveryId || !SUPPORTED_EVENTS.has(event.event) || !event.payload || typeof event.payload !== "object" || Array.isArray(event.payload)) return null;
280
+ return {
281
+ event: event.event,
282
+ deliveryId: event.deliveryId,
283
+ payload: event.payload
284
+ };
581
285
  }
582
286
  function retryDelay(error, fallbackMs) {
583
- if (error instanceof PlatformApiError && error.status === 429 && error.retryAfterSeconds !== null) {
584
- return Math.max(fallbackMs, error.retryAfterSeconds * 1e3);
585
- }
586
- return fallbackMs;
287
+ if (error instanceof PlatformApiError && error.status === 429 && error.retryAfterSeconds !== null) return Math.max(fallbackMs, error.retryAfterSeconds * 1e3);
288
+ return fallbackMs;
587
289
  }
588
- export {
589
- PlatformGithubEventWorker
590
- };
290
+ //#endregion
291
+ export { PlatformGithubEventWorker };
292
+
591
293
  //# sourceMappingURL=event-worker.js.map