@mastra/factory 0.16.0-alpha.10 → 0.16.0-alpha.12

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 (216) hide show
  1. package/README.md +37 -0
  2. package/dist/auth.js +1 -1
  3. package/dist/auth.js.map +1 -1
  4. package/dist/boards/review.d.ts.map +1 -1
  5. package/dist/boards/review.js +19 -8
  6. package/dist/boards/review.js.map +1 -1
  7. package/dist/boards/work.d.ts.map +1 -1
  8. package/dist/boards/work.js +31 -3
  9. package/dist/boards/work.js.map +1 -1
  10. package/dist/capabilities/intake.d.ts +4 -0
  11. package/dist/capabilities/intake.d.ts.map +1 -1
  12. package/dist/capabilities/version-control.d.ts +15 -0
  13. package/dist/capabilities/version-control.d.ts.map +1 -1
  14. package/dist/factory.d.ts +7 -7
  15. package/dist/factory.d.ts.map +1 -1
  16. package/dist/factory.js +100 -40
  17. package/dist/factory.js.map +1 -1
  18. package/dist/integrations/base.d.ts +3 -0
  19. package/dist/integrations/base.d.ts.map +1 -1
  20. package/dist/integrations/github/integration.d.ts.map +1 -1
  21. package/dist/integrations/github/integration.js +21 -0
  22. package/dist/integrations/github/integration.js.map +1 -1
  23. package/dist/integrations/github/routes.js +2 -1
  24. package/dist/integrations/github/routes.js.map +1 -1
  25. package/dist/integrations/github/sandbox.d.ts +41 -42
  26. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  27. package/dist/integrations/github/sandbox.js +416 -227
  28. package/dist/integrations/github/sandbox.js.map +1 -1
  29. package/dist/integrations/github/webhook.d.ts +2 -5
  30. package/dist/integrations/github/webhook.d.ts.map +1 -1
  31. package/dist/integrations/github/webhook.js +8 -48
  32. package/dist/integrations/github/webhook.js.map +1 -1
  33. package/dist/integrations/gitlab/agent-tools.d.ts +14 -0
  34. package/dist/integrations/gitlab/agent-tools.d.ts.map +1 -0
  35. package/dist/integrations/gitlab/agent-tools.js +42 -0
  36. package/dist/integrations/gitlab/agent-tools.js.map +1 -0
  37. package/dist/integrations/gitlab/api.d.ts +222 -0
  38. package/dist/integrations/gitlab/api.d.ts.map +1 -0
  39. package/dist/integrations/gitlab/api.js +274 -0
  40. package/dist/integrations/gitlab/api.js.map +1 -0
  41. package/dist/integrations/gitlab/default-rules.d.ts +125 -0
  42. package/dist/integrations/gitlab/default-rules.d.ts.map +1 -0
  43. package/dist/integrations/gitlab/default-rules.js +172 -0
  44. package/dist/integrations/gitlab/default-rules.js.map +1 -0
  45. package/dist/integrations/gitlab/integration.d.ts +138 -0
  46. package/dist/integrations/gitlab/integration.d.ts.map +1 -0
  47. package/dist/integrations/gitlab/integration.js +705 -0
  48. package/dist/integrations/gitlab/integration.js.map +1 -0
  49. package/dist/integrations/gitlab/issue-reconciler.d.ts +6 -0
  50. package/dist/integrations/gitlab/issue-reconciler.d.ts.map +1 -0
  51. package/dist/integrations/gitlab/issue-reconciler.js +123 -0
  52. package/dist/integrations/gitlab/issue-reconciler.js.map +1 -0
  53. package/dist/integrations/gitlab/merge-request-reconciler.d.ts +6 -0
  54. package/dist/integrations/gitlab/merge-request-reconciler.d.ts.map +1 -0
  55. package/dist/integrations/gitlab/merge-request-reconciler.js +267 -0
  56. package/dist/integrations/gitlab/merge-request-reconciler.js.map +1 -0
  57. package/dist/integrations/gitlab/reconciler.d.ts +5 -0
  58. package/dist/integrations/gitlab/reconciler.d.ts.map +1 -0
  59. package/dist/integrations/gitlab/reconciler.js +31 -0
  60. package/dist/integrations/gitlab/reconciler.js.map +1 -0
  61. package/dist/integrations/gitlab/reconciliation-config.d.ts +3 -0
  62. package/dist/integrations/gitlab/reconciliation-config.d.ts.map +1 -0
  63. package/dist/integrations/gitlab/reconciliation-config.js +17 -0
  64. package/dist/integrations/gitlab/reconciliation-config.js.map +1 -0
  65. package/dist/integrations/gitlab/routes.d.ts +20 -0
  66. package/dist/integrations/gitlab/routes.d.ts.map +1 -0
  67. package/dist/integrations/gitlab/routes.js +473 -0
  68. package/dist/integrations/gitlab/routes.js.map +1 -0
  69. package/dist/integrations/gitlab/rules.d.ts +35 -0
  70. package/dist/integrations/gitlab/rules.d.ts.map +1 -0
  71. package/dist/integrations/gitlab/rules.js +391 -0
  72. package/dist/integrations/gitlab/rules.js.map +1 -0
  73. package/dist/integrations/gitlab/session-subscriptions.d.ts +32 -0
  74. package/dist/integrations/gitlab/session-subscriptions.d.ts.map +1 -0
  75. package/dist/integrations/gitlab/session-subscriptions.js +192 -0
  76. package/dist/integrations/gitlab/session-subscriptions.js.map +1 -0
  77. package/dist/integrations/gitlab/subscriptions.d.ts +70 -0
  78. package/dist/integrations/gitlab/subscriptions.d.ts.map +1 -0
  79. package/dist/integrations/gitlab/subscriptions.js +76 -0
  80. package/dist/integrations/gitlab/subscriptions.js.map +1 -0
  81. package/dist/integrations/gitlab/version-control.d.ts +16 -0
  82. package/dist/integrations/gitlab/version-control.d.ts.map +1 -0
  83. package/dist/integrations/gitlab/version-control.js +622 -0
  84. package/dist/integrations/gitlab/version-control.js.map +1 -0
  85. package/dist/integrations/gitlab/webhook-dispatch.d.ts +75 -0
  86. package/dist/integrations/gitlab/webhook-dispatch.d.ts.map +1 -0
  87. package/dist/integrations/gitlab/webhook-dispatch.js +235 -0
  88. package/dist/integrations/gitlab/webhook-dispatch.js.map +1 -0
  89. package/dist/integrations/gitlab/webhook.d.ts +72 -0
  90. package/dist/integrations/gitlab/webhook.d.ts.map +1 -0
  91. package/dist/integrations/gitlab/webhook.js +201 -0
  92. package/dist/integrations/gitlab/webhook.js.map +1 -0
  93. package/dist/integrations/incidentio/integration.js +1 -1
  94. package/dist/integrations/issue-reconciler.d.ts +3 -3
  95. package/dist/integrations/issue-reconciler.d.ts.map +1 -1
  96. package/dist/integrations/issue-reconciler.js +22 -2
  97. package/dist/integrations/issue-reconciler.js.map +1 -1
  98. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  99. package/dist/integrations/platform/github/integration.js +21 -0
  100. package/dist/integrations/platform/github/integration.js.map +1 -1
  101. package/dist/integrations/platform/gitlab/event-worker.d.ts +64 -0
  102. package/dist/integrations/platform/gitlab/event-worker.d.ts.map +1 -0
  103. package/dist/integrations/platform/gitlab/event-worker.js +306 -0
  104. package/dist/integrations/platform/gitlab/event-worker.js.map +1 -0
  105. package/dist/integrations/platform/gitlab/integration.d.ts +57 -0
  106. package/dist/integrations/platform/gitlab/integration.d.ts.map +1 -0
  107. package/dist/integrations/platform/gitlab/integration.js +125 -0
  108. package/dist/integrations/platform/gitlab/integration.js.map +1 -0
  109. package/dist/integrations/platform/incidentio/integration.js +1 -1
  110. package/dist/integrations/slack/integration.d.ts.map +1 -1
  111. package/dist/integrations/slack/integration.js +3 -3
  112. package/dist/integrations/slack/integration.js.map +1 -1
  113. package/dist/integrations/slack/slack.d.ts +2 -0
  114. package/dist/integrations/slack/slack.d.ts.map +1 -1
  115. package/dist/integrations/slack/slack.js +21 -7
  116. package/dist/integrations/slack/slack.js.map +1 -1
  117. package/dist/integrations/subscription-session.d.ts +37 -0
  118. package/dist/integrations/subscription-session.d.ts.map +1 -0
  119. package/dist/integrations/subscription-session.js +83 -0
  120. package/dist/integrations/subscription-session.js.map +1 -0
  121. package/dist/packages/_internals/workspace/dist/index.js +1 -11
  122. package/dist/packages/_internals/workspace/dist/index.js.map +1 -1
  123. package/dist/routes/intake.d.ts.map +1 -1
  124. package/dist/routes/intake.js +4 -2
  125. package/dist/routes/intake.js.map +1 -1
  126. package/dist/routes/oauth.js +1 -1
  127. package/dist/routes/projects.d.ts +1 -0
  128. package/dist/routes/projects.d.ts.map +1 -1
  129. package/dist/routes/projects.js +1 -0
  130. package/dist/routes/projects.js.map +1 -1
  131. package/dist/routes/source-control-sessions.d.ts +20 -0
  132. package/dist/routes/source-control-sessions.d.ts.map +1 -0
  133. package/dist/routes/source-control-sessions.js +319 -0
  134. package/dist/routes/source-control-sessions.js.map +1 -0
  135. package/dist/routes/source-control-settings.d.ts +10 -0
  136. package/dist/routes/source-control-settings.d.ts.map +1 -0
  137. package/dist/routes/source-control-settings.js +87 -0
  138. package/dist/routes/source-control-settings.js.map +1 -0
  139. package/dist/routes/surface.d.ts +3 -3
  140. package/dist/routes/surface.d.ts.map +1 -1
  141. package/dist/routes/surface.js +55 -13
  142. package/dist/routes/surface.js.map +1 -1
  143. package/dist/routes/tenant-credentials.d.ts +8 -0
  144. package/dist/routes/tenant-credentials.d.ts.map +1 -1
  145. package/dist/routes/tenant-credentials.js +20 -3
  146. package/dist/routes/tenant-credentials.js.map +1 -1
  147. package/dist/routes/work-items.d.ts.map +1 -1
  148. package/dist/routes/work-items.js +1 -1
  149. package/dist/routes/work-items.js.map +1 -1
  150. package/dist/rules/dispatcher.d.ts.map +1 -1
  151. package/dist/rules/dispatcher.js +7 -1
  152. package/dist/rules/dispatcher.js.map +1 -1
  153. package/dist/rules/start-coordinator.d.ts +1 -1
  154. package/dist/rules/start-coordinator.d.ts.map +1 -1
  155. package/dist/rules/start-coordinator.js +3 -2
  156. package/dist/rules/start-coordinator.js.map +1 -1
  157. package/dist/rules/transition-service.d.ts.map +1 -1
  158. package/dist/rules/transition-service.js +6 -3
  159. package/dist/rules/transition-service.js.map +1 -1
  160. package/dist/rules/types.d.ts +67 -3
  161. package/dist/rules/types.d.ts.map +1 -1
  162. package/dist/rules/types.js +23 -2
  163. package/dist/rules/types.js.map +1 -1
  164. package/dist/rules/validation.d.ts.map +1 -1
  165. package/dist/rules/validation.js +2 -0
  166. package/dist/rules/validation.js.map +1 -1
  167. package/dist/sandbox/git-ref.d.ts +7 -0
  168. package/dist/sandbox/git-ref.d.ts.map +1 -0
  169. package/dist/sandbox/git-ref.js +15 -0
  170. package/dist/sandbox/git-ref.js.map +1 -0
  171. package/dist/sandbox/session-retirement.js +1 -1
  172. package/dist/sandbox/workdir.d.ts.map +1 -1
  173. package/dist/sandbox/workdir.js +6 -6
  174. package/dist/sandbox/workdir.js.map +1 -1
  175. package/dist/session/factory-session.d.ts +31 -1
  176. package/dist/session/factory-session.d.ts.map +1 -1
  177. package/dist/session/factory-session.js +60 -1
  178. package/dist/session/factory-session.js.map +1 -1
  179. package/dist/session/source-control-tools.d.ts +16 -0
  180. package/dist/session/source-control-tools.d.ts.map +1 -0
  181. package/dist/session/source-control-tools.js +464 -0
  182. package/dist/session/source-control-tools.js.map +1 -0
  183. package/dist/storage/domains/intake/base.d.ts +18 -0
  184. package/dist/storage/domains/intake/base.d.ts.map +1 -1
  185. package/dist/storage/domains/intake/base.js +77 -0
  186. package/dist/storage/domains/intake/base.js.map +1 -1
  187. package/dist/work-item-branch.d.ts +3 -1
  188. package/dist/work-item-branch.d.ts.map +1 -1
  189. package/dist/work-item-branch.js +22 -4
  190. package/dist/work-item-branch.js.map +1 -1
  191. package/dist/workspace.d.ts +15 -1
  192. package/dist/workspace.d.ts.map +1 -1
  193. package/dist/workspace.js +57 -23
  194. package/dist/workspace.js.map +1 -1
  195. package/factory-skills/factory-gitlab-rereview/SKILL.md +19 -0
  196. package/factory-skills/factory-gitlab-review/SKILL.md +30 -0
  197. package/factory-skills/factory-rereview/SKILL.md +13 -6
  198. package/factory-skills/factory-review/SKILL.md +13 -6
  199. package/factory-skills/factory-review/references/archaeology.md +225 -0
  200. package/factory-skills/factory-review/references/categories/README.md +34 -0
  201. package/factory-skills/factory-review/references/categories/behavior-change.md +36 -0
  202. package/factory-skills/factory-review/references/categories/bug-fix.md +37 -0
  203. package/factory-skills/factory-review/references/categories/docs.md +32 -0
  204. package/factory-skills/factory-review/references/categories/experimental-flagged.md +34 -0
  205. package/factory-skills/factory-review/references/categories/infra-tooling.md +37 -0
  206. package/factory-skills/factory-review/references/categories/internal-capability.md +35 -0
  207. package/factory-skills/factory-review/references/categories/large-cross-cutting.md +35 -0
  208. package/factory-skills/factory-review/references/categories/mechanical.md +36 -0
  209. package/factory-skills/factory-review/references/categories/performance.md +33 -0
  210. package/factory-skills/factory-review/references/categories/public-api.md +38 -0
  211. package/factory-skills/factory-review/references/categories/refactor.md +35 -0
  212. package/factory-skills/factory-review/references/categories/revert.md +31 -0
  213. package/factory-skills/factory-review/references/categories/schema-storage.md +39 -0
  214. package/factory-skills/factory-review/references/categories/security.md +35 -0
  215. package/factory-skills/factory-review/references/categories/tests-only.md +38 -0
  216. package/package.json +9 -9
@@ -0,0 +1,306 @@
1
+ import { PlatformApiError } from "../api-client.js";
2
+ import { parseGitLabWebhookBody, processGitLabWebhook } from "../../gitlab/webhook.js";
3
+ import { NoopLeaseProvider, isLeaseProvider } from "@mastra/core/events";
4
+ import { randomUUID } from "crypto";
5
+ import { MastraWorker } from "@mastra/core/worker";
6
+ //#region src/integrations/platform/gitlab/event-worker.ts
7
+ const DEFAULT_POLL_INTERVAL_MS = 2e4;
8
+ const EVENT_PAGE_SIZE = 500;
9
+ /**
10
+ * Pages drained per connection per cycle. A connection that never runs dry
11
+ * (a busy group webhook, or a backlog after downtime) hands over to the next
12
+ * connection after this many pages and resumes from its saved cursor next tick.
13
+ */
14
+ const MAX_PAGES_PER_CONNECTION_PER_CYCLE = 10;
15
+ const MIN_LEASE_TTL_MS = 3e4;
16
+ const CURSOR_ORG_ID = "__platform_gitlab_event_worker__";
17
+ const CURSOR_USER_ID = "worker";
18
+ const DELIVERY_ID_PREFIX = "platform";
19
+ /**
20
+ * Tails the Platform event log for every active GitLab connection and feeds
21
+ * each stored webhook body through the same ingress the direct
22
+ * `/web/gitlab/webhook` route uses. A Platform-managed connection has no
23
+ * project webhook pointing at Factory, so this is how issue, note, merge
24
+ * request and push events reach the rules engine and subscribed sessions.
25
+ *
26
+ * One replica polls at a time (lease). Each connection keeps its own cursor,
27
+ * persisted only after a whole page has been processed, so a crash mid-page
28
+ * replays the page rather than losing it; replay handling in the rules ingress
29
+ * and the delivery-id dedupe keys make that safe.
30
+ */
31
+ var PlatformGitLabEventWorker = class extends MastraWorker {
32
+ name = "platform-gitlab-events";
33
+ #client;
34
+ #controller;
35
+ #gitlab;
36
+ #storage;
37
+ #listConnections;
38
+ #ingestFactoryEvent;
39
+ #intervalMs;
40
+ #now;
41
+ #process;
42
+ #leaseOwner = randomUUID();
43
+ #running = false;
44
+ #timer;
45
+ #leaseRenewalTimer;
46
+ #inFlight;
47
+ #leaseProvider = NoopLeaseProvider;
48
+ #leaseTtlMs;
49
+ #hasLease = false;
50
+ #startedAt = 0;
51
+ #settings = {
52
+ version: 1,
53
+ connections: {}
54
+ };
55
+ constructor(config) {
56
+ super();
57
+ this.#client = config.client;
58
+ this.#controller = config.controller;
59
+ this.#gitlab = config.gitlab;
60
+ this.#storage = config.storage;
61
+ this.#listConnections = config.listConnections;
62
+ this.#ingestFactoryEvent = config.ingestFactoryEvent;
63
+ this.#intervalMs = config.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
64
+ if (!Number.isFinite(this.#intervalMs) || this.#intervalMs <= 0) throw new Error("Platform GitLab event polling interval must be a positive number.");
65
+ this.#leaseTtlMs = Math.max(MIN_LEASE_TTL_MS, this.#intervalMs * 3);
66
+ this.#now = config.now ?? Date.now;
67
+ this.#process = config.process ?? processGitLabWebhook;
68
+ }
69
+ async init(deps) {
70
+ await super.init(deps);
71
+ this.#leaseProvider = getLeaseProvider(deps.pubsub);
72
+ }
73
+ async start() {
74
+ if (this.#running) return;
75
+ if (!this.deps) throw new Error("PlatformGitLabEventWorker: call init() before start()");
76
+ this.#startedAt = this.#now() - 1;
77
+ this.#settings = normalizeSettings(await this.#storage.settings.get(CURSOR_ORG_ID, CURSOR_USER_ID));
78
+ this.#running = true;
79
+ this.deps.logger.info("Platform GitLab event polling started", {
80
+ intervalMs: this.#intervalMs,
81
+ leaseTtlMs: this.#leaseTtlMs
82
+ });
83
+ this.#schedule(0);
84
+ }
85
+ async stop() {
86
+ if (!this.#running) return;
87
+ this.#running = false;
88
+ if (this.#timer) clearTimeout(this.#timer);
89
+ this.#timer = void 0;
90
+ this.#stopLeaseRenewal();
91
+ await this.#inFlight;
92
+ if (this.#hasLease) {
93
+ await this.#leaseProvider.releaseLease(this.#leaseKey(), this.#leaseOwner).catch(() => void 0);
94
+ this.#hasLease = false;
95
+ }
96
+ }
97
+ get isRunning() {
98
+ return this.#running;
99
+ }
100
+ #schedule(delayMs) {
101
+ if (!this.#running) return;
102
+ this.#timer = setTimeout(() => {
103
+ this.#timer = void 0;
104
+ const run = this.#tick();
105
+ this.#inFlight = run;
106
+ run.finally(() => {
107
+ if (this.#inFlight === run) this.#inFlight = void 0;
108
+ });
109
+ }, delayMs);
110
+ this.#timer.unref?.();
111
+ }
112
+ async #tick() {
113
+ let nextDelay = this.#intervalMs;
114
+ try {
115
+ if (!await this.#ensureLease()) return;
116
+ nextDelay = await this.#poll();
117
+ } catch (error) {
118
+ nextDelay = retryDelay(error, this.#intervalMs);
119
+ this.deps?.logger.error("Platform GitLab event polling cycle failed", {
120
+ error: error instanceof Error ? error.message : String(error),
121
+ retryInMs: nextDelay
122
+ });
123
+ } finally {
124
+ this.#schedule(nextDelay);
125
+ }
126
+ }
127
+ async #ensureLease() {
128
+ if (this.#hasLease) return true;
129
+ const result = await this.#leaseProvider.acquireLease(this.#leaseKey(), this.#leaseOwner, this.#leaseTtlMs);
130
+ this.#hasLease = result.acquired;
131
+ if (this.#hasLease) this.#startLeaseRenewal();
132
+ return this.#hasLease;
133
+ }
134
+ #startLeaseRenewal() {
135
+ if (this.#leaseRenewalTimer) return;
136
+ this.#leaseRenewalTimer = setInterval(() => {
137
+ this.#leaseProvider.renewLease(this.#leaseKey(), this.#leaseOwner, this.#leaseTtlMs).then((renewed) => {
138
+ if (!renewed) {
139
+ this.#hasLease = false;
140
+ this.#stopLeaseRenewal();
141
+ }
142
+ }).catch((error) => {
143
+ this.#hasLease = false;
144
+ this.#stopLeaseRenewal();
145
+ this.deps?.logger.warn("Platform GitLab event polling lease renewal failed", { error: error instanceof Error ? error.message : String(error) });
146
+ });
147
+ }, Math.floor(this.#leaseTtlMs / 3));
148
+ this.#leaseRenewalTimer.unref?.();
149
+ }
150
+ #stopLeaseRenewal() {
151
+ if (this.#leaseRenewalTimer) clearInterval(this.#leaseRenewalTimer);
152
+ this.#leaseRenewalTimer = void 0;
153
+ }
154
+ async #poll() {
155
+ const connections = (await this.#listConnections()).filter((connection) => connection.status === "active");
156
+ let retryInMs = this.#intervalMs;
157
+ for (const connection of connections) {
158
+ if (!this.#running || !this.#hasLease) break;
159
+ try {
160
+ await this.#pollConnection(connection.id);
161
+ } catch (error) {
162
+ if (error instanceof PlatformApiError && (error.status === 401 || error.status === 404)) {
163
+ this.deps?.logger.warn("Platform GitLab connection needs reauthorization; skipped this cycle", {
164
+ connectionId: connection.id,
165
+ status: error.status
166
+ });
167
+ continue;
168
+ }
169
+ const delay = retryDelay(error, this.#intervalMs);
170
+ retryInMs = Math.max(retryInMs, delay);
171
+ this.deps?.logger.error("Platform GitLab connection event polling failed", {
172
+ connectionId: connection.id,
173
+ error: error instanceof Error ? error.message : String(error),
174
+ retryInMs: delay
175
+ });
176
+ if (error instanceof PlatformApiError && error.status === 429) break;
177
+ }
178
+ }
179
+ return retryInMs;
180
+ }
181
+ async #pollConnection(connectionId) {
182
+ if (!this.#settings.connections[connectionId]) {
183
+ this.#settings.connections[connectionId] = { afterEventId: `${this.#startedAt}-0` };
184
+ await this.#saveSettings();
185
+ }
186
+ for (let pageIndex = 0; this.#running && this.#hasLease; pageIndex += 1) {
187
+ if (pageIndex >= MAX_PAGES_PER_CONNECTION_PER_CYCLE) {
188
+ this.deps?.logger.debug("Platform GitLab connection page budget reached; resuming next cycle", {
189
+ connectionId,
190
+ pages: pageIndex
191
+ });
192
+ return;
193
+ }
194
+ const cursor = this.#settings.connections[connectionId];
195
+ const query = new URLSearchParams({
196
+ afterEventId: cursor.afterEventId,
197
+ limit: String(EVENT_PAGE_SIZE)
198
+ });
199
+ const pollStartedAt = performance.now();
200
+ const page = await this.#client.request("GET", `/v2/connections/${encodeURIComponent(connectionId)}/events?${query}`);
201
+ this.deps?.logger.debug("Platform GitLab connection event poll completed", {
202
+ connectionId,
203
+ eventCount: page.events.length,
204
+ latencyMs: Math.round(performance.now() - pollStartedAt)
205
+ });
206
+ if (page.events.length === 0 || !page.nextCursor) return;
207
+ for (const event of page.events) {
208
+ if (!this.#running || !this.#hasLease) return;
209
+ await this.#processEvent(connectionId, event);
210
+ }
211
+ if (page.nextCursor === cursor.afterEventId) return;
212
+ if (!this.#hasLease) return;
213
+ this.#settings.connections[connectionId] = { afterEventId: page.nextCursor };
214
+ await this.#saveSettings();
215
+ }
216
+ }
217
+ async #processEvent(connectionId, event) {
218
+ if (!event.id) {
219
+ this.deps?.logger.warn("Platform GitLab event log returned an event without an id", { connectionId });
220
+ return;
221
+ }
222
+ const deliveryId = `${DELIVERY_ID_PREFIX}:${connectionId}:${event.id}`;
223
+ const parsed = parseGitLabWebhookBody(event.payload, deliveryId);
224
+ if (!parsed) {
225
+ this.deps?.logger.debug("Platform GitLab event ignored: unsupported or malformed body", {
226
+ connectionId,
227
+ eventId: event.id,
228
+ eventType: event.eventType
229
+ });
230
+ return;
231
+ }
232
+ const result = await this.#process(parsed, {
233
+ ingestFactoryEvent: this.#ingestFactoryEvent,
234
+ controller: this.#controller,
235
+ gitlab: this.#gitlab,
236
+ sourceConnectionId: connectionId,
237
+ onConnectionMismatch: (subscription) => {
238
+ this.deps?.logger.debug("Platform GitLab event skipped: subscription belongs to another connection", {
239
+ deliveryId,
240
+ subscriptionId: subscription.id,
241
+ subscriptionConnectionId: subscription.data.installationExternalId
242
+ });
243
+ },
244
+ onTargetSkipped: (subscription) => {
245
+ this.deps?.logger.debug("Platform GitLab event skipped: thread is not held here", {
246
+ deliveryId,
247
+ subscriptionId: subscription.id,
248
+ threadId: subscription.threadId
249
+ });
250
+ },
251
+ onSenderRejected: (notification) => {
252
+ this.deps?.logger.debug("Platform GitLab event dropped: sender not authorized", {
253
+ deliveryId,
254
+ repository: notification.metadata.projectPath,
255
+ sender: notification.metadata.sender,
256
+ kind: notification.kind
257
+ });
258
+ },
259
+ onTargetError: (subscription, error) => {
260
+ this.deps?.logger.error("Platform GitLab event delivery failed for a subscription", {
261
+ deliveryId,
262
+ subscriptionId: subscription.id,
263
+ resourceId: subscription.resourceId,
264
+ threadId: subscription.threadId,
265
+ error: error instanceof Error ? error.message : String(error)
266
+ });
267
+ }
268
+ });
269
+ if (result.status !== 202) this.deps?.logger.warn("Platform GitLab event was not accepted by the webhook processor", {
270
+ connectionId,
271
+ deliveryId,
272
+ status: result.status
273
+ });
274
+ }
275
+ async #saveSettings() {
276
+ await this.#storage.settings.save(CURSOR_ORG_ID, CURSOR_USER_ID, this.#settings);
277
+ }
278
+ #leaseKey() {
279
+ return `${this.name}:${this.#storage.integrationId}`;
280
+ }
281
+ };
282
+ function getLeaseProvider(pubsub) {
283
+ const getProvider = pubsub.getLeaseProvider;
284
+ if (typeof getProvider === "function") return getProvider.call(pubsub) ?? NoopLeaseProvider;
285
+ return isLeaseProvider(pubsub) ? pubsub : NoopLeaseProvider;
286
+ }
287
+ function normalizeSettings(value) {
288
+ if (!value || value.version !== 1 || !value.connections || typeof value.connections !== "object") return {
289
+ version: 1,
290
+ connections: {}
291
+ };
292
+ const connections = {};
293
+ for (const [connectionId, cursor] of Object.entries(value.connections)) if (typeof cursor?.afterEventId === "string" && cursor.afterEventId.length > 0) connections[connectionId] = { afterEventId: cursor.afterEventId };
294
+ return {
295
+ version: 1,
296
+ connections
297
+ };
298
+ }
299
+ function retryDelay(error, fallbackMs) {
300
+ if (error instanceof PlatformApiError && error.status === 429 && error.retryAfterSeconds !== null) return Math.max(fallbackMs, error.retryAfterSeconds * 1e3);
301
+ return fallbackMs;
302
+ }
303
+ //#endregion
304
+ export { PlatformGitLabEventWorker };
305
+
306
+ //# sourceMappingURL=event-worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-worker.js","names":["#client","#controller","#gitlab","#storage","#listConnections","#ingestFactoryEvent","#intervalMs","#now","#process","#leaseOwner","#leaseTtlMs","#leaseProvider","#running","#startedAt","#settings","#schedule","#timer","#stopLeaseRenewal","#inFlight","#hasLease","#leaseKey","#tick","#ensureLease","#poll","#startLeaseRenewal","#leaseRenewalTimer","#pollConnection","#saveSettings","#processEvent"],"sources":["../../../../src/integrations/platform/gitlab/event-worker.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport type { MountedMastraCode } from '@mastra/code-sdk';\nimport { isLeaseProvider, NoopLeaseProvider } from '@mastra/core/events';\nimport type { LeaseProvider, PubSub } from '@mastra/core/events';\nimport { MastraWorker } from '@mastra/core/worker';\nimport type { WorkerDeps } from '@mastra/core/worker';\n\nimport type { IntegrationStorageHandle } from '../../../storage/domains/integrations/base.js';\nimport type { GitLabWebhookDispatchIntegration } from '../../gitlab/webhook-dispatch.js';\nimport { parseGitLabWebhookBody, processGitLabWebhook } from '../../gitlab/webhook.js';\nimport type { ParsedGitLabWebhook } from '../../gitlab/webhook.js';\nimport type { PlatformApiClient } from '../api-client.js';\nimport { PlatformApiError } from '../api-client.js';\n\nconst DEFAULT_POLL_INTERVAL_MS = 20_000;\nconst EVENT_PAGE_SIZE = 500;\n/**\n * Pages drained per connection per cycle. A connection that never runs dry\n * (a busy group webhook, or a backlog after downtime) hands over to the next\n * connection after this many pages and resumes from its saved cursor next tick.\n */\nconst MAX_PAGES_PER_CONNECTION_PER_CYCLE = 10;\nconst MIN_LEASE_TTL_MS = 30_000;\nconst CURSOR_ORG_ID = '__platform_gitlab_event_worker__';\nconst CURSOR_USER_ID = 'worker';\nconst DELIVERY_ID_PREFIX = 'platform';\n\ntype PlatformGitLabEventWorkerSettings = {\n version: 1;\n connections: Record<string, { afterEventId: string }>;\n};\n\nexport type PlatformGitLabEventStorage = IntegrationStorageHandle<\n Record<string, unknown>,\n PlatformGitLabEventWorkerSettings,\n Record<string, unknown>\n>;\n\n/** One row of the Platform integration event log. */\nexport interface IntegrationEventEntry {\n id: string;\n timestamp: number;\n integrationId: string;\n eventType: string;\n sourceEventId: string | null;\n payload: unknown;\n}\n\nexport interface PlatformGitLabEventConnection {\n id: string;\n status: 'active' | 'needs_reauth';\n}\n\nexport type PlatformGitLabEventDispatchIntegration = GitLabWebhookDispatchIntegration;\n\nexport interface PlatformGitLabEventWorkerConfig {\n client: PlatformApiClient;\n controller: MountedMastraCode['controller'];\n gitlab: PlatformGitLabEventDispatchIntegration;\n storage: PlatformGitLabEventStorage;\n /** Platform connections to tail; only `active` ones are polled. */\n listConnections: () => Promise<PlatformGitLabEventConnection[]>;\n ingestFactoryEvent?: (event: ParsedGitLabWebhook) => Promise<unknown>;\n intervalMs?: number;\n now?: () => number;\n process?: typeof processGitLabWebhook;\n}\n\n/**\n * Tails the Platform event log for every active GitLab connection and feeds\n * each stored webhook body through the same ingress the direct\n * `/web/gitlab/webhook` route uses. A Platform-managed connection has no\n * project webhook pointing at Factory, so this is how issue, note, merge\n * request and push events reach the rules engine and subscribed sessions.\n *\n * One replica polls at a time (lease). Each connection keeps its own cursor,\n * persisted only after a whole page has been processed, so a crash mid-page\n * replays the page rather than losing it; replay handling in the rules ingress\n * and the delivery-id dedupe keys make that safe.\n */\nexport class PlatformGitLabEventWorker extends MastraWorker {\n readonly name = 'platform-gitlab-events';\n\n readonly #client: PlatformApiClient;\n readonly #controller: MountedMastraCode['controller'];\n readonly #gitlab: PlatformGitLabEventDispatchIntegration;\n readonly #storage: PlatformGitLabEventStorage;\n readonly #listConnections: () => Promise<PlatformGitLabEventConnection[]>;\n readonly #ingestFactoryEvent: ((event: ParsedGitLabWebhook) => Promise<unknown>) | undefined;\n readonly #intervalMs: number;\n readonly #now: () => number;\n readonly #process: typeof processGitLabWebhook;\n readonly #leaseOwner = randomUUID();\n\n #running = false;\n #timer: ReturnType<typeof setTimeout> | undefined;\n #leaseRenewalTimer: ReturnType<typeof setInterval> | undefined;\n #inFlight: Promise<void> | undefined;\n #leaseProvider: LeaseProvider = NoopLeaseProvider;\n #leaseTtlMs: number;\n #hasLease = false;\n #startedAt = 0;\n #settings: PlatformGitLabEventWorkerSettings = { version: 1, connections: {} };\n\n constructor(config: PlatformGitLabEventWorkerConfig) {\n super();\n this.#client = config.client;\n this.#controller = config.controller;\n this.#gitlab = config.gitlab;\n this.#storage = config.storage;\n this.#listConnections = config.listConnections;\n this.#ingestFactoryEvent = config.ingestFactoryEvent;\n this.#intervalMs = config.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n if (!Number.isFinite(this.#intervalMs) || this.#intervalMs <= 0) {\n throw new Error('Platform GitLab event polling interval must be a positive number.');\n }\n this.#leaseTtlMs = Math.max(MIN_LEASE_TTL_MS, this.#intervalMs * 3);\n this.#now = config.now ?? Date.now;\n this.#process = config.process ?? processGitLabWebhook;\n }\n\n async init(deps: WorkerDeps): Promise<void> {\n await super.init(deps);\n this.#leaseProvider = getLeaseProvider(deps.pubsub);\n }\n\n async start(): Promise<void> {\n if (this.#running) return;\n if (!this.deps) throw new Error('PlatformGitLabEventWorker: call init() before start()');\n\n this.#startedAt = this.#now() - 1;\n this.#settings = normalizeSettings(await this.#storage.settings.get(CURSOR_ORG_ID, CURSOR_USER_ID));\n this.#running = true;\n this.deps.logger.info('Platform GitLab event polling started', {\n intervalMs: this.#intervalMs,\n leaseTtlMs: this.#leaseTtlMs,\n });\n this.#schedule(0);\n }\n\n async stop(): Promise<void> {\n if (!this.#running) return;\n this.#running = false;\n if (this.#timer) clearTimeout(this.#timer);\n this.#timer = undefined;\n this.#stopLeaseRenewal();\n await this.#inFlight;\n if (this.#hasLease) {\n await this.#leaseProvider.releaseLease(this.#leaseKey(), this.#leaseOwner).catch(() => undefined);\n this.#hasLease = false;\n }\n }\n\n get isRunning(): boolean {\n return this.#running;\n }\n\n #schedule(delayMs: number): void {\n if (!this.#running) return;\n this.#timer = setTimeout(() => {\n this.#timer = undefined;\n const run = this.#tick();\n this.#inFlight = run;\n void run.finally(() => {\n if (this.#inFlight === run) this.#inFlight = undefined;\n });\n }, delayMs);\n this.#timer.unref?.();\n }\n\n async #tick(): Promise<void> {\n let nextDelay = this.#intervalMs;\n try {\n if (!(await this.#ensureLease())) return;\n nextDelay = await this.#poll();\n } catch (error) {\n nextDelay = retryDelay(error, this.#intervalMs);\n this.deps?.logger.error('Platform GitLab event polling cycle failed', {\n error: error instanceof Error ? error.message : String(error),\n retryInMs: nextDelay,\n });\n } finally {\n this.#schedule(nextDelay);\n }\n }\n\n async #ensureLease(): Promise<boolean> {\n if (this.#hasLease) return true;\n const result = await this.#leaseProvider.acquireLease(this.#leaseKey(), this.#leaseOwner, this.#leaseTtlMs);\n this.#hasLease = result.acquired;\n if (this.#hasLease) this.#startLeaseRenewal();\n return this.#hasLease;\n }\n\n #startLeaseRenewal(): void {\n if (this.#leaseRenewalTimer) return;\n this.#leaseRenewalTimer = setInterval(\n () => {\n void this.#leaseProvider\n .renewLease(this.#leaseKey(), this.#leaseOwner, this.#leaseTtlMs)\n .then(renewed => {\n if (!renewed) {\n this.#hasLease = false;\n this.#stopLeaseRenewal();\n }\n })\n .catch(error => {\n this.#hasLease = false;\n this.#stopLeaseRenewal();\n this.deps?.logger.warn('Platform GitLab event polling lease renewal failed', {\n error: error instanceof Error ? error.message : String(error),\n });\n });\n },\n Math.floor(this.#leaseTtlMs / 3),\n );\n this.#leaseRenewalTimer.unref?.();\n }\n\n #stopLeaseRenewal(): void {\n if (this.#leaseRenewalTimer) clearInterval(this.#leaseRenewalTimer);\n this.#leaseRenewalTimer = undefined;\n }\n\n async #poll(): Promise<number> {\n const connections = (await this.#listConnections()).filter(connection => connection.status === 'active');\n let retryInMs = this.#intervalMs;\n\n for (const connection of connections) {\n if (!this.#running || !this.#hasLease) break;\n try {\n await this.#pollConnection(connection.id);\n } catch (error) {\n if (error instanceof PlatformApiError && (error.status === 401 || error.status === 404)) {\n // Platform no longer serves this connection (revoked, expired or\n // deleted). Its status will read needs_reauth on the next listing;\n // nothing to retry here, so the other connections keep flowing.\n this.deps?.logger.warn('Platform GitLab connection needs reauthorization; skipped this cycle', {\n connectionId: connection.id,\n status: error.status,\n });\n continue;\n }\n const delay = retryDelay(error, this.#intervalMs);\n retryInMs = Math.max(retryInMs, delay);\n this.deps?.logger.error('Platform GitLab connection event polling failed', {\n connectionId: connection.id,\n error: error instanceof Error ? error.message : String(error),\n retryInMs: delay,\n });\n if (error instanceof PlatformApiError && error.status === 429) break;\n }\n }\n return retryInMs;\n }\n\n async #pollConnection(connectionId: string): Promise<void> {\n if (!this.#settings.connections[connectionId]) {\n // Event ids are Redis stream ids (`<unix ms>-<sequence>`), so a\n // synthesized id at start time skips history the same way the GitHub\n // worker's start timestamp does. Events that arrived before Factory was\n // deployed are not work Factory was asked to do.\n this.#settings.connections[connectionId] = { afterEventId: `${this.#startedAt}-0` };\n await this.#saveSettings();\n }\n\n for (let pageIndex = 0; this.#running && this.#hasLease; pageIndex += 1) {\n if (pageIndex >= MAX_PAGES_PER_CONNECTION_PER_CYCLE) {\n this.deps?.logger.debug('Platform GitLab connection page budget reached; resuming next cycle', {\n connectionId,\n pages: pageIndex,\n });\n return;\n }\n const cursor: { afterEventId: string } = this.#settings.connections[connectionId]!;\n const query = new URLSearchParams({ afterEventId: cursor.afterEventId, limit: String(EVENT_PAGE_SIZE) });\n const pollStartedAt = performance.now();\n const page = await this.#client.request<{ events: IntegrationEventEntry[]; nextCursor: string | null }>(\n 'GET',\n `/v2/connections/${encodeURIComponent(connectionId)}/events?${query}`,\n );\n this.deps?.logger.debug('Platform GitLab connection event poll completed', {\n connectionId,\n eventCount: page.events.length,\n latencyMs: Math.round(performance.now() - pollStartedAt),\n });\n if (page.events.length === 0 || !page.nextCursor) return;\n\n for (const event of page.events) {\n if (!this.#running || !this.#hasLease) return;\n await this.#processEvent(connectionId, event);\n }\n\n if (page.nextCursor === cursor.afterEventId) return;\n // A replica that lost its lease while processing this page must not\n // write a cursor over one the new holder has already advanced. The\n // page is replayed there; delivery is deduplicated by delivery id.\n if (!this.#hasLease) return;\n this.#settings.connections[connectionId] = { afterEventId: page.nextCursor };\n await this.#saveSettings();\n }\n }\n\n async #processEvent(connectionId: string, event: IntegrationEventEntry): Promise<void> {\n if (!event.id) {\n this.deps?.logger.warn('Platform GitLab event log returned an event without an id', { connectionId });\n return;\n }\n const deliveryId = `${DELIVERY_ID_PREFIX}:${connectionId}:${event.id}`;\n const parsed = parseGitLabWebhookBody(event.payload, deliveryId);\n if (!parsed) {\n // Pipelines, deployments, wiki edits and the like: acknowledged and\n // dropped, exactly as the direct route treats an unsupported event header.\n this.deps?.logger.debug('Platform GitLab event ignored: unsupported or malformed body', {\n connectionId,\n eventId: event.id,\n eventType: event.eventType,\n });\n return;\n }\n\n const result = await this.#process(parsed, {\n ingestFactoryEvent: this.#ingestFactoryEvent,\n controller: this.#controller,\n // The integration supplies subscription storage and the project-member\n // trust check, so the dispatcher's default author gate applies unchanged.\n gitlab: this.#gitlab,\n sourceConnectionId: connectionId,\n onConnectionMismatch: subscription => {\n // Expected whenever two connections reach the same project: the\n // event is delivered by the connection that created the subscription.\n this.deps?.logger.debug('Platform GitLab event skipped: subscription belongs to another connection', {\n deliveryId,\n subscriptionId: subscription.id,\n subscriptionConnectionId: subscription.data.installationExternalId,\n });\n },\n onTargetSkipped: subscription => {\n // Routine when a subscription's thread belongs to another deployment,\n // so this stays at debug rather than warning on a loop.\n this.deps?.logger.debug('Platform GitLab event skipped: thread is not held here', {\n deliveryId,\n subscriptionId: subscription.id,\n threadId: subscription.threadId,\n });\n },\n onSenderRejected: notification => {\n this.deps?.logger.debug('Platform GitLab event dropped: sender not authorized', {\n deliveryId,\n repository: notification.metadata.projectPath,\n sender: notification.metadata.sender,\n kind: notification.kind,\n });\n },\n onTargetError: (subscription, error) => {\n this.deps?.logger.error('Platform GitLab event delivery failed for a subscription', {\n deliveryId,\n subscriptionId: subscription.id,\n resourceId: subscription.resourceId,\n threadId: subscription.threadId,\n error: error instanceof Error ? error.message : String(error),\n });\n },\n });\n if (result.status !== 202) {\n this.deps?.logger.warn('Platform GitLab event was not accepted by the webhook processor', {\n connectionId,\n deliveryId,\n status: result.status,\n });\n }\n }\n\n async #saveSettings(): Promise<void> {\n await this.#storage.settings.save(CURSOR_ORG_ID, CURSOR_USER_ID, this.#settings);\n }\n\n #leaseKey(): string {\n return `${this.name}:${this.#storage.integrationId}`;\n }\n}\n\nfunction getLeaseProvider(pubsub: PubSub): LeaseProvider {\n const getProvider = (pubsub as PubSub & { getLeaseProvider?: () => LeaseProvider | undefined }).getLeaseProvider;\n if (typeof getProvider === 'function') return getProvider.call(pubsub) ?? NoopLeaseProvider;\n return isLeaseProvider(pubsub) ? pubsub : NoopLeaseProvider;\n}\n\nfunction normalizeSettings(value: PlatformGitLabEventWorkerSettings | null): PlatformGitLabEventWorkerSettings {\n if (!value || value.version !== 1 || !value.connections || typeof value.connections !== 'object') {\n return { version: 1, connections: {} };\n }\n const connections: PlatformGitLabEventWorkerSettings['connections'] = {};\n for (const [connectionId, cursor] of Object.entries(value.connections)) {\n if (typeof cursor?.afterEventId === 'string' && cursor.afterEventId.length > 0) {\n connections[connectionId] = { afterEventId: cursor.afterEventId };\n }\n }\n return { version: 1, connections };\n}\n\nfunction retryDelay(error: unknown, fallbackMs: number): number {\n if (error instanceof PlatformApiError && error.status === 429 && error.retryAfterSeconds !== null) {\n return Math.max(fallbackMs, error.retryAfterSeconds * 1_000);\n }\n return fallbackMs;\n}\n"],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import type { MastraWorker } from '@mastra/core/worker';
2
+ import type { IntegrationConnection } from '../../../capabilities/connection.js';
3
+ import type { IntegrationContext } from '../../base.js';
4
+ import { GitLabApiClient } from '../../gitlab/api.js';
5
+ import type { GitLabRuleOverrides } from '../../gitlab/default-rules.js';
6
+ import { GitLabIntegrationBase } from '../../gitlab/integration.js';
7
+ import type { GitLabStatusConnection } from '../../gitlab/integration.js';
8
+ import type { PlatformApiClientConfig } from '../api-client.js';
9
+ interface PlatformIntegrationConnection {
10
+ id: string;
11
+ integrationId: string;
12
+ status: 'active' | 'needs_reauth';
13
+ accountLabel: string | null;
14
+ }
15
+ interface PlatformGitLabContext {
16
+ id: string;
17
+ label: string | null;
18
+ api: GitLabApiClient;
19
+ connection: IntegrationConnection;
20
+ host: string;
21
+ repositoryAccessToken: () => Promise<string>;
22
+ }
23
+ export interface PlatformGitLabIntegrationConfig {
24
+ rules?: GitLabRuleOverrides;
25
+ clientConfig?: PlatformApiClientConfig;
26
+ connectionId?: string;
27
+ /**
28
+ * Optional. Only a direct project webhook to `/web/gitlab/webhook` needs it;
29
+ * a Platform-connected deployment receives events by polling instead.
30
+ */
31
+ webhookSecret?: string;
32
+ /** Defaults to `MASTRA_PLATFORM_GITLAB_POLLING_ENABLED`, and to true when that is unset. */
33
+ pollingEnabled?: boolean;
34
+ /** Defaults to `MASTRA_PLATFORM_GITLAB_POLLING_INTERVAL_MS`, and to the worker's 20 s when unset. */
35
+ pollingIntervalMs?: number;
36
+ }
37
+ export declare class PlatformGitLabIntegration extends GitLabIntegrationBase {
38
+ #private;
39
+ constructor(config?: PlatformGitLabIntegrationConfig);
40
+ listConnections(): Promise<PlatformIntegrationConnection[]>;
41
+ statusConnections(): Promise<GitLabStatusConnection[]>;
42
+ hasActiveConnections(): Promise<boolean>;
43
+ authFailureMessage(): string;
44
+ protected get webhookSecret(): string | undefined;
45
+ protected activeContexts(): Promise<PlatformGitLabContext[]>;
46
+ protected contextById(connectionId: string): Promise<PlatformGitLabContext>;
47
+ /**
48
+ * The reconcile sweeps from the base class plus, when polling is on, the
49
+ * Platform event poller. Polling replaces the direct project webhook: the
50
+ * webhook route stays mounted for deployments that also configured a secret,
51
+ * but nothing in Platform mode requires one.
52
+ */
53
+ workers(ctx: IntegrationContext): MastraWorker[];
54
+ diagnostics(): Record<string, unknown>;
55
+ }
56
+ export {};
57
+ //# sourceMappingURL=integration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../../src/integrations/platform/gitlab/integration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,eAAe,EAAkB,MAAM,qBAAqB,CAAC;AACtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAoB,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACtF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAG1E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAIhE,UAAU,6BAA6B;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,QAAQ,GAAG,cAAc,CAAC;IAClC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,UAAU,qBAAqB;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,GAAG,EAAE,eAAe,CAAC;IACrB,UAAU,EAAE,qBAAqB,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,qBAAqB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;CAC9C;AAOD,MAAM,WAAW,+BAA+B;IAC9C,KAAK,CAAC,EAAE,mBAAmB,CAAC;IAC5B,YAAY,CAAC,EAAE,uBAAuB,CAAC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4FAA4F;IAC5F,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,qGAAqG;IACrG,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,qBAAa,yBAA0B,SAAQ,qBAAqB;;gBAQtD,MAAM,GAAE,+BAAoC;IAclD,eAAe,IAAI,OAAO,CAAC,6BAA6B,EAAE,CAAC;IA0BlD,iBAAiB,IAAI,OAAO,CAAC,sBAAsB,EAAE,CAAC;IAI/D,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC;IAI9C,kBAAkB,IAAI,MAAM;IAI5B,cAAuB,aAAa,IAAI,MAAM,GAAG,SAAS,CAEzD;cAEe,cAAc,IAAI,OAAO,CAAC,qBAAqB,EAAE,CAAC;cAIlD,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAMjF;;;;;OAKG;IACM,OAAO,CAAC,GAAG,EAAE,kBAAkB,GAAG,YAAY,EAAE;IAoBzD,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CA4CvC"}
@@ -0,0 +1,125 @@
1
+ import { PlatformApiClient, platformApiClientConfigFromEnv } from "../api-client.js";
2
+ import { GitLabApiClient, GitLabApiError } from "../../gitlab/api.js";
3
+ import { attachGitLabRules } from "../../gitlab/rules.js";
4
+ import { GitLabIntegrationBase, gitlabConnection } from "../../gitlab/integration.js";
5
+ import { PlatformGitLabEventWorker } from "./event-worker.js";
6
+ //#region src/integrations/platform/gitlab/integration.ts
7
+ const GITLAB_INTEGRATION_IDS = /* @__PURE__ */ new Set([
8
+ "gitlab",
9
+ "gitlab-group",
10
+ "gitlab-group-token",
11
+ "gitlab-pat"
12
+ ]);
13
+ var PlatformGitLabIntegration = class extends GitLabIntegrationBase {
14
+ #client;
15
+ #connectionId;
16
+ #endpointHost;
17
+ #webhookSecret;
18
+ #pollingEnabled;
19
+ #pollingIntervalMs;
20
+ constructor(config = {}) {
21
+ super(config.rules);
22
+ const connectionId = config.connectionId?.trim() || process.env.MASTRA_GITLAB_CONNECTION_ID?.trim() || void 0;
23
+ const clientConfig = config.clientConfig ?? platformApiClientConfigFromEnv();
24
+ this.#client = new PlatformApiClient(clientConfig);
25
+ this.#connectionId = connectionId;
26
+ this.#endpointHost = new URL(clientConfig.baseUrl).host;
27
+ this.#webhookSecret = config.webhookSecret?.trim() || process.env.MASTRA_GITLAB_WEBHOOK_SECRET?.trim() || void 0;
28
+ this.#pollingEnabled = config.pollingEnabled ?? process.env.MASTRA_PLATFORM_GITLAB_POLLING_ENABLED?.trim().toLowerCase() !== "false";
29
+ this.#pollingIntervalMs = config.pollingIntervalMs ?? optionalPositiveIntegerEnv("MASTRA_PLATFORM_GITLAB_POLLING_INTERVAL_MS");
30
+ }
31
+ async listConnections() {
32
+ const pages = await Promise.all([...GITLAB_INTEGRATION_IDS].map(async (integrationId) => {
33
+ return (await this.#client.request("GET", `/v2/connections?providerKey=${encodeURIComponent(integrationId)}`)).connections;
34
+ }));
35
+ const seen = /* @__PURE__ */ new Set();
36
+ return pages.flat().filter((connection) => {
37
+ if (this.#connectionId && connection.id !== this.#connectionId || !GITLAB_INTEGRATION_IDS.has(connection.integrationId) || seen.has(connection.id)) return false;
38
+ seen.add(connection.id);
39
+ return true;
40
+ });
41
+ }
42
+ async statusConnections() {
43
+ return this.listConnections();
44
+ }
45
+ async hasActiveConnections() {
46
+ return (await this.#activeConnections()).length > 0;
47
+ }
48
+ authFailureMessage() {
49
+ return "GitLab rejected the connected account. Reconnect it in Mastra Platform.";
50
+ }
51
+ get webhookSecret() {
52
+ return this.#webhookSecret;
53
+ }
54
+ async activeContexts() {
55
+ return (await this.#activeConnections()).map((connection) => this.#context(connection));
56
+ }
57
+ async contextById(connectionId) {
58
+ const connection = (await this.#activeConnections()).find((candidate) => candidate.id === connectionId);
59
+ if (!connection) throw new GitLabApiError("GitLab connection is unavailable or requires reauthentication.", 401);
60
+ return this.#context(connection);
61
+ }
62
+ /**
63
+ * The reconcile sweeps from the base class plus, when polling is on, the
64
+ * Platform event poller. Polling replaces the direct project webhook: the
65
+ * webhook route stays mounted for deployments that also configured a secret,
66
+ * but nothing in Platform mode requires one.
67
+ */
68
+ workers(ctx) {
69
+ const workers = super.workers(ctx);
70
+ if (!this.#pollingEnabled) return workers;
71
+ if (!ctx.controller) throw new Error("Platform GitLab event polling requires the mounted Mastra Code controller.");
72
+ return [...workers, new PlatformGitLabEventWorker({
73
+ client: this.#client,
74
+ controller: ctx.controller,
75
+ gitlab: this,
76
+ storage: ctx.storage.generic,
77
+ listConnections: () => this.listConnections(),
78
+ ingestFactoryEvent: attachGitLabRules(this, ctx),
79
+ intervalMs: this.#pollingIntervalMs
80
+ })];
81
+ }
82
+ diagnostics() {
83
+ return {
84
+ configured: true,
85
+ mode: "platform",
86
+ endpointHost: this.#endpointHost,
87
+ connectionFilterConfigured: Boolean(this.#connectionId),
88
+ webhookConfigured: Boolean(this.#webhookSecret),
89
+ pollingEnabled: this.#pollingEnabled,
90
+ ...this.#pollingIntervalMs === void 0 ? {} : { pollingIntervalMs: this.#pollingIntervalMs }
91
+ };
92
+ }
93
+ async #activeConnections() {
94
+ return (await this.listConnections()).filter((connection) => connection.status === "active");
95
+ }
96
+ #context(connection) {
97
+ return {
98
+ id: connection.id,
99
+ label: connection.accountLabel,
100
+ api: new GitLabApiClient({
101
+ client: this.#client,
102
+ connectionId: connection.id
103
+ }),
104
+ connection: gitlabConnection(connection.id),
105
+ host: "gitlab.com",
106
+ repositoryAccessToken: async () => {
107
+ const credential = await this.#client.request("GET", `/v2/connections/${encodeURIComponent(connection.id)}/credentials`);
108
+ const token = credential?.type === "oauth2" ? credential.accessToken : credential?.type === "api_key" ? credential.apiKey : void 0;
109
+ if (typeof token !== "string" || !token.trim()) throw new GitLabApiError("GitLab connection did not provide a repository credential.", 502);
110
+ return token;
111
+ }
112
+ };
113
+ }
114
+ };
115
+ function optionalPositiveIntegerEnv(name) {
116
+ const value = process.env[name]?.trim();
117
+ if (!value) return void 0;
118
+ const parsed = Number(value);
119
+ if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(parsed)) throw new Error(`${name} must be a positive integer.`);
120
+ return parsed;
121
+ }
122
+ //#endregion
123
+ export { PlatformGitLabIntegration };
124
+
125
+ //# sourceMappingURL=integration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration.js","names":["#client","#connectionId","#endpointHost","#webhookSecret","#pollingEnabled","#pollingIntervalMs","#activeConnections","#context"],"sources":["../../../../src/integrations/platform/gitlab/integration.ts"],"sourcesContent":["import type { MastraWorker } from '@mastra/core/worker';\n\nimport type { IntegrationConnection } from '../../../capabilities/connection.js';\nimport type { IntegrationContext } from '../../base.js';\nimport { GitLabApiClient, GitLabApiError } from '../../gitlab/api.js';\nimport type { GitLabRuleOverrides } from '../../gitlab/default-rules.js';\nimport { gitlabConnection, GitLabIntegrationBase } from '../../gitlab/integration.js';\nimport type { GitLabStatusConnection } from '../../gitlab/integration.js';\nimport { attachGitLabRules } from '../../gitlab/rules.js';\nimport { PlatformApiClient, platformApiClientConfigFromEnv } from '../api-client.js';\nimport type { PlatformApiClientConfig } from '../api-client.js';\nimport { PlatformGitLabEventWorker } from './event-worker.js';\nimport type { PlatformGitLabEventStorage } from './event-worker.js';\n\ninterface PlatformIntegrationConnection {\n id: string;\n integrationId: string;\n status: 'active' | 'needs_reauth';\n accountLabel: string | null;\n}\n\ninterface PlatformGitLabContext {\n id: string;\n label: string | null;\n api: GitLabApiClient;\n connection: IntegrationConnection;\n host: string;\n repositoryAccessToken: () => Promise<string>;\n}\n\ntype PlatformGitLabCredential =\n { type: 'oauth2'; accessToken: string; expiresAt: string | null } | { type: 'api_key'; apiKey: string };\n\nconst GITLAB_INTEGRATION_IDS = new Set(['gitlab', 'gitlab-group', 'gitlab-group-token', 'gitlab-pat']);\n\nexport interface PlatformGitLabIntegrationConfig {\n rules?: GitLabRuleOverrides;\n clientConfig?: PlatformApiClientConfig;\n connectionId?: string;\n /**\n * Optional. Only a direct project webhook to `/web/gitlab/webhook` needs it;\n * a Platform-connected deployment receives events by polling instead.\n */\n webhookSecret?: string;\n /** Defaults to `MASTRA_PLATFORM_GITLAB_POLLING_ENABLED`, and to true when that is unset. */\n pollingEnabled?: boolean;\n /** Defaults to `MASTRA_PLATFORM_GITLAB_POLLING_INTERVAL_MS`, and to the worker's 20 s when unset. */\n pollingIntervalMs?: number;\n}\n\nexport class PlatformGitLabIntegration extends GitLabIntegrationBase {\n readonly #client: PlatformApiClient;\n readonly #connectionId: string | undefined;\n readonly #endpointHost: string;\n readonly #webhookSecret: string | undefined;\n readonly #pollingEnabled: boolean;\n readonly #pollingIntervalMs: number | undefined;\n\n constructor(config: PlatformGitLabIntegrationConfig = {}) {\n super(config.rules);\n const connectionId = config.connectionId?.trim() || process.env.MASTRA_GITLAB_CONNECTION_ID?.trim() || undefined;\n const clientConfig = config.clientConfig ?? platformApiClientConfigFromEnv();\n this.#client = new PlatformApiClient(clientConfig);\n this.#connectionId = connectionId;\n this.#endpointHost = new URL(clientConfig.baseUrl).host;\n this.#webhookSecret = config.webhookSecret?.trim() || process.env.MASTRA_GITLAB_WEBHOOK_SECRET?.trim() || undefined;\n this.#pollingEnabled =\n config.pollingEnabled ?? process.env.MASTRA_PLATFORM_GITLAB_POLLING_ENABLED?.trim().toLowerCase() !== 'false';\n this.#pollingIntervalMs =\n config.pollingIntervalMs ?? optionalPositiveIntegerEnv('MASTRA_PLATFORM_GITLAB_POLLING_INTERVAL_MS');\n }\n\n async listConnections(): Promise<PlatformIntegrationConnection[]> {\n // Platform's providerKey filter matches a single integration ID, so query\n // each supported GitLab credential flow before applying the exact ID filter.\n const pages = await Promise.all(\n [...GITLAB_INTEGRATION_IDS].map(async integrationId => {\n const result = await this.#client.request<{ connections: PlatformIntegrationConnection[] }>(\n 'GET',\n `/v2/connections?providerKey=${encodeURIComponent(integrationId)}`,\n );\n return result.connections;\n }),\n );\n const seen = new Set<string>();\n return pages.flat().filter(connection => {\n if (\n (this.#connectionId && connection.id !== this.#connectionId) ||\n !GITLAB_INTEGRATION_IDS.has(connection.integrationId) ||\n seen.has(connection.id)\n ) {\n return false;\n }\n seen.add(connection.id);\n return true;\n });\n }\n\n override async statusConnections(): Promise<GitLabStatusConnection[]> {\n return this.listConnections();\n }\n\n async hasActiveConnections(): Promise<boolean> {\n return (await this.#activeConnections()).length > 0;\n }\n\n authFailureMessage(): string {\n return 'GitLab rejected the connected account. Reconnect it in Mastra Platform.';\n }\n\n protected override get webhookSecret(): string | undefined {\n return this.#webhookSecret;\n }\n\n protected async activeContexts(): Promise<PlatformGitLabContext[]> {\n return (await this.#activeConnections()).map(connection => this.#context(connection));\n }\n\n protected async contextById(connectionId: string): Promise<PlatformGitLabContext> {\n const connection = (await this.#activeConnections()).find(candidate => candidate.id === connectionId);\n if (!connection) throw new GitLabApiError('GitLab connection is unavailable or requires reauthentication.', 401);\n return this.#context(connection);\n }\n\n /**\n * The reconcile sweeps from the base class plus, when polling is on, the\n * Platform event poller. Polling replaces the direct project webhook: the\n * webhook route stays mounted for deployments that also configured a secret,\n * but nothing in Platform mode requires one.\n */\n override workers(ctx: IntegrationContext): MastraWorker[] {\n const workers = super.workers(ctx);\n if (!this.#pollingEnabled) return workers;\n if (!ctx.controller) {\n throw new Error('Platform GitLab event polling requires the mounted Mastra Code controller.');\n }\n return [\n ...workers,\n new PlatformGitLabEventWorker({\n client: this.#client,\n controller: ctx.controller,\n gitlab: this,\n storage: ctx.storage.generic as unknown as PlatformGitLabEventStorage,\n listConnections: () => this.listConnections(),\n ingestFactoryEvent: attachGitLabRules(this, ctx),\n intervalMs: this.#pollingIntervalMs,\n }),\n ];\n }\n\n diagnostics(): Record<string, unknown> {\n return {\n configured: true,\n mode: 'platform',\n endpointHost: this.#endpointHost,\n connectionFilterConfigured: Boolean(this.#connectionId),\n webhookConfigured: Boolean(this.#webhookSecret),\n pollingEnabled: this.#pollingEnabled,\n ...(this.#pollingIntervalMs === undefined ? {} : { pollingIntervalMs: this.#pollingIntervalMs }),\n };\n }\n\n async #activeConnections(): Promise<PlatformIntegrationConnection[]> {\n return (await this.listConnections()).filter(connection => connection.status === 'active');\n }\n\n #context(connection: PlatformIntegrationConnection): PlatformGitLabContext {\n return {\n id: connection.id,\n label: connection.accountLabel,\n api: new GitLabApiClient({ client: this.#client, connectionId: connection.id }),\n connection: gitlabConnection(connection.id),\n // Platform does not currently expose the connected GitLab instance host.\n host: 'gitlab.com',\n repositoryAccessToken: async () => {\n // Fetch for each git operation so an expiring OAuth token is refreshed by Platform.\n // Never persist or log the returned credential.\n const credential = await this.#client.request<PlatformGitLabCredential>(\n 'GET',\n `/v2/connections/${encodeURIComponent(connection.id)}/credentials`,\n );\n const token =\n credential?.type === 'oauth2'\n ? credential.accessToken\n : credential?.type === 'api_key'\n ? credential.apiKey\n : undefined;\n if (typeof token !== 'string' || !token.trim()) {\n throw new GitLabApiError('GitLab connection did not provide a repository credential.', 502);\n }\n return token;\n },\n };\n }\n}\n\nfunction optionalPositiveIntegerEnv(name: 'MASTRA_PLATFORM_GITLAB_POLLING_INTERVAL_MS'): number | undefined {\n const value = process.env[name]?.trim();\n if (!value) return undefined;\n const parsed = Number(value);\n if (!/^[1-9]\\d*$/.test(value) || !Number.isSafeInteger(parsed)) {\n throw new Error(`${name} must be a positive integer.`);\n }\n return parsed;\n}\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { PlatformApiClient, platformApiClientConfigFromEnv } from "../api-client.js";
2
+ import { IssueReconcileWorker } from "../../issue-reconcile-worker.js";
2
3
  import { IncidentioApiClient, IncidentioApiError } from "../../incidentio/api.js";
3
4
  import { buildIncidentioRoutes } from "../../incidentio/routes.js";
4
5
  import { buildIncidentioAgentTools } from "../../incidentio/agent-tools.js";
@@ -7,7 +8,6 @@ import { INCIDENTIO_FOLLOW_UPS_SOURCE_ID, INCIDENTIO_INCIDENTS_SOURCE_ID, create
7
8
  import { attachIncidentioRules } from "../../incidentio/rules.js";
8
9
  import { attachIncidentioIssueReconciler } from "../../incidentio/issue-reconciler.js";
9
10
  import { incidentioReconciliationEnabled, incidentioReconciliationInterval } from "../../incidentio/reconciliation-config.js";
10
- import { IssueReconcileWorker } from "../../issue-reconcile-worker.js";
11
11
  //#region src/integrations/platform/incidentio/integration.ts
12
12
  /**
13
13
  * Provider config key registered for incident.io in Mastra Platform's
@@ -1 +1 @@
1
- {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../src/integrations/slack/integration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAE/D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,6CAA6C,CAAC;AACzF,OAAO,KAAK,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAMhG;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;;;OAaG;IACH,cAAc,CAAC,EAAE,yBAAyB,CAAC;CAC5C;AA8BD,qBAAa,gBAAiB,YAAW,kBAAkB;;IACzD,QAAQ,CAAC,EAAE,WAAW;IACtB;;;;OAIG;IACH,QAAQ,CAAC,yBAAyB,QAAQ;gBAU9B,MAAM,EAAE,sBAAsB;IAS1C,QAAQ,CAAC,GAAG,EAAE,kBAAkB,GAAG,qBAAqB;IA0BxD,aAAa,CAAC,GAAG,EAAE,kBAAkB,GAAG,qBAAqB;IAI7D,MAAM,CAAC,GAAG,EAAE,kBAAkB,GAAG,QAAQ,EAAE;IAc3C,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CASvC"}
1
+ {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../src/integrations/slack/integration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAE/D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,6CAA6C,CAAC;AACzF,OAAO,KAAK,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAMhG;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;;;OAaG;IACH,cAAc,CAAC,EAAE,yBAAyB,CAAC;CAC5C;AA8BD,qBAAa,gBAAiB,YAAW,kBAAkB;;IACzD,QAAQ,CAAC,EAAE,WAAW;IACtB;;;;OAIG;IACH,QAAQ,CAAC,yBAAyB,QAAQ;gBAU9B,MAAM,EAAE,sBAAsB;IAS1C,QAAQ,CAAC,GAAG,EAAE,kBAAkB,GAAG,qBAAqB;IA2BxD,aAAa,CAAC,GAAG,EAAE,kBAAkB,GAAG,qBAAqB;IAI7D,MAAM,CAAC,GAAG,EAAE,kBAAkB,GAAG,QAAQ,EAAE;IAc3C,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CASvC"}
@@ -41,8 +41,8 @@ var SlackIntegration = class {
41
41
  this.#config = config;
42
42
  }
43
43
  channels(ctx) {
44
- const sourceControlOwner = ctx.storage.sourceControlOwner;
45
- this.#repoBackedSessions = Boolean(sourceControlOwner);
44
+ const sourceControls = ctx.storage.sourceControls ?? (ctx.storage.sourceControlOwner ? [ctx.storage.sourceControlOwner] : []);
45
+ this.#repoBackedSessions = sourceControls.length > 0;
46
46
  return createSlackChannelsConfig({
47
47
  slack: {
48
48
  clientId: this.#config.clientId,
@@ -52,7 +52,7 @@ var SlackIntegration = class {
52
52
  },
53
53
  accountLinks: ctx.storage.channelIdentity,
54
54
  projects: ctx.storage.projects,
55
- sourceControl: sourceControlOwner,
55
+ sourceControls,
56
56
  memorySettings: ctx.storage.memorySettings,
57
57
  workItems: ctx.runtime?.workItems,
58
58
  feed: ctx.feed,