@mastra/factory 0.0.0-esbuild-bundle-worker-20260807173433

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 (329) hide show
  1. package/CHANGELOG.md +1008 -0
  2. package/LICENSE.md +30 -0
  3. package/README.md +27 -0
  4. package/dist/auth.d.ts +183 -0
  5. package/dist/auth.d.ts.map +1 -0
  6. package/dist/auth.js +507 -0
  7. package/dist/auth.js.map +1 -0
  8. package/dist/capabilities/connection.d.ts +9 -0
  9. package/dist/capabilities/connection.d.ts.map +1 -0
  10. package/dist/capabilities/connection.js +1 -0
  11. package/dist/capabilities/intake.d.ts +141 -0
  12. package/dist/capabilities/intake.d.ts.map +1 -0
  13. package/dist/capabilities/intake.js +1 -0
  14. package/dist/capabilities/version-control.d.ts +247 -0
  15. package/dist/capabilities/version-control.d.ts.map +1 -0
  16. package/dist/capabilities/version-control.js +1 -0
  17. package/dist/factory.d.ts +159 -0
  18. package/dist/factory.d.ts.map +1 -0
  19. package/dist/factory.js +491 -0
  20. package/dist/factory.js.map +1 -0
  21. package/dist/index.d.ts +14 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +7 -0
  24. package/dist/integrations/base.d.ts +254 -0
  25. package/dist/integrations/base.d.ts.map +1 -0
  26. package/dist/integrations/base.js +1 -0
  27. package/dist/integrations/github/config.d.ts +60 -0
  28. package/dist/integrations/github/config.d.ts.map +1 -0
  29. package/dist/integrations/github/config.js +42 -0
  30. package/dist/integrations/github/config.js.map +1 -0
  31. package/dist/integrations/github/integration.d.ts +220 -0
  32. package/dist/integrations/github/integration.d.ts.map +1 -0
  33. package/dist/integrations/github/integration.js +1162 -0
  34. package/dist/integrations/github/integration.js.map +1 -0
  35. package/dist/integrations/github/issue-triage.d.ts +27 -0
  36. package/dist/integrations/github/issue-triage.d.ts.map +1 -0
  37. package/dist/integrations/github/issue-triage.js +82 -0
  38. package/dist/integrations/github/issue-triage.js.map +1 -0
  39. package/dist/integrations/github/pat.d.ts +38 -0
  40. package/dist/integrations/github/pat.d.ts.map +1 -0
  41. package/dist/integrations/github/pat.js +59 -0
  42. package/dist/integrations/github/pat.js.map +1 -0
  43. package/dist/integrations/github/provenance.d.ts +27 -0
  44. package/dist/integrations/github/provenance.d.ts.map +1 -0
  45. package/dist/integrations/github/provenance.js +75 -0
  46. package/dist/integrations/github/provenance.js.map +1 -0
  47. package/dist/integrations/github/reconcile-worker.d.ts +41 -0
  48. package/dist/integrations/github/reconcile-worker.d.ts.map +1 -0
  49. package/dist/integrations/github/reconcile-worker.js +125 -0
  50. package/dist/integrations/github/reconcile-worker.js.map +1 -0
  51. package/dist/integrations/github/routes.d.ts +69 -0
  52. package/dist/integrations/github/routes.d.ts.map +1 -0
  53. package/dist/integrations/github/routes.js +1335 -0
  54. package/dist/integrations/github/routes.js.map +1 -0
  55. package/dist/integrations/github/rules.d.ts +105 -0
  56. package/dist/integrations/github/rules.d.ts.map +1 -0
  57. package/dist/integrations/github/rules.js +551 -0
  58. package/dist/integrations/github/rules.js.map +1 -0
  59. package/dist/integrations/github/sandbox-release.d.ts +53 -0
  60. package/dist/integrations/github/sandbox-release.d.ts.map +1 -0
  61. package/dist/integrations/github/sandbox-release.js +115 -0
  62. package/dist/integrations/github/sandbox-release.js.map +1 -0
  63. package/dist/integrations/github/sandbox.d.ts +281 -0
  64. package/dist/integrations/github/sandbox.d.ts.map +1 -0
  65. package/dist/integrations/github/sandbox.js +547 -0
  66. package/dist/integrations/github/sandbox.js.map +1 -0
  67. package/dist/integrations/github/session-subscriptions.d.ts +26 -0
  68. package/dist/integrations/github/session-subscriptions.d.ts.map +1 -0
  69. package/dist/integrations/github/session-subscriptions.js +192 -0
  70. package/dist/integrations/github/session-subscriptions.js.map +1 -0
  71. package/dist/integrations/github/subscriptions.d.ts +54 -0
  72. package/dist/integrations/github/subscriptions.d.ts.map +1 -0
  73. package/dist/integrations/github/subscriptions.js +62 -0
  74. package/dist/integrations/github/subscriptions.js.map +1 -0
  75. package/dist/integrations/github/token-refresh.d.ts +12 -0
  76. package/dist/integrations/github/token-refresh.d.ts.map +1 -0
  77. package/dist/integrations/github/token-refresh.js +24 -0
  78. package/dist/integrations/github/token-refresh.js.map +1 -0
  79. package/dist/integrations/github/webhook.d.ts +107 -0
  80. package/dist/integrations/github/webhook.d.ts.map +1 -0
  81. package/dist/integrations/github/webhook.js +371 -0
  82. package/dist/integrations/github/webhook.js.map +1 -0
  83. package/dist/integrations/linear/agent-tools.d.ts +34 -0
  84. package/dist/integrations/linear/agent-tools.d.ts.map +1 -0
  85. package/dist/integrations/linear/agent-tools.js +85 -0
  86. package/dist/integrations/linear/agent-tools.js.map +1 -0
  87. package/dist/integrations/linear/integration.d.ts +235 -0
  88. package/dist/integrations/linear/integration.d.ts.map +1 -0
  89. package/dist/integrations/linear/integration.js +676 -0
  90. package/dist/integrations/linear/integration.js.map +1 -0
  91. package/dist/integrations/linear/routes.d.ts +61 -0
  92. package/dist/integrations/linear/routes.d.ts.map +1 -0
  93. package/dist/integrations/linear/routes.js +240 -0
  94. package/dist/integrations/linear/routes.js.map +1 -0
  95. package/dist/integrations/linear/rules.d.ts +44 -0
  96. package/dist/integrations/linear/rules.d.ts.map +1 -0
  97. package/dist/integrations/linear/rules.js +133 -0
  98. package/dist/integrations/linear/rules.js.map +1 -0
  99. package/dist/integrations/linear/storage.d.ts +65 -0
  100. package/dist/integrations/linear/storage.d.ts.map +1 -0
  101. package/dist/integrations/linear/storage.js +1 -0
  102. package/dist/integrations/platform/api-client.d.ts +26 -0
  103. package/dist/integrations/platform/api-client.d.ts.map +1 -0
  104. package/dist/integrations/platform/api-client.js +156 -0
  105. package/dist/integrations/platform/api-client.js.map +1 -0
  106. package/dist/integrations/platform/github/event-worker.d.ts +44 -0
  107. package/dist/integrations/platform/github/event-worker.d.ts.map +1 -0
  108. package/dist/integrations/platform/github/event-worker.js +349 -0
  109. package/dist/integrations/platform/github/event-worker.js.map +1 -0
  110. package/dist/integrations/platform/github/integration.d.ts +64 -0
  111. package/dist/integrations/platform/github/integration.d.ts.map +1 -0
  112. package/dist/integrations/platform/github/integration.js +1006 -0
  113. package/dist/integrations/platform/github/integration.js.map +1 -0
  114. package/dist/integrations/platform/linear/integration.d.ts +46 -0
  115. package/dist/integrations/platform/linear/integration.d.ts.map +1 -0
  116. package/dist/integrations/platform/linear/integration.js +483 -0
  117. package/dist/integrations/platform/linear/integration.js.map +1 -0
  118. package/dist/integrations/slack/connect-route.d.ts +46 -0
  119. package/dist/integrations/slack/connect-route.d.ts.map +1 -0
  120. package/dist/integrations/slack/connect-route.js +186 -0
  121. package/dist/integrations/slack/connect-route.js.map +1 -0
  122. package/dist/integrations/slack/integration.d.ts +62 -0
  123. package/dist/integrations/slack/integration.d.ts.map +1 -0
  124. package/dist/integrations/slack/integration.js +68 -0
  125. package/dist/integrations/slack/integration.js.map +1 -0
  126. package/dist/integrations/slack/slack.d.ts +200 -0
  127. package/dist/integrations/slack/slack.d.ts.map +1 -0
  128. package/dist/integrations/slack/slack.js +411 -0
  129. package/dist/integrations/slack/slack.js.map +1 -0
  130. package/dist/integrations/workos/integration.d.ts +39 -0
  131. package/dist/integrations/workos/integration.d.ts.map +1 -0
  132. package/dist/integrations/workos/integration.js +91 -0
  133. package/dist/integrations/workos/integration.js.map +1 -0
  134. package/dist/routes/config.d.ts +235 -0
  135. package/dist/routes/config.d.ts.map +1 -0
  136. package/dist/routes/config.js +972 -0
  137. package/dist/routes/config.js.map +1 -0
  138. package/dist/routes/custom-provider-source.d.ts +52 -0
  139. package/dist/routes/custom-provider-source.d.ts.map +1 -0
  140. package/dist/routes/custom-provider-source.js +124 -0
  141. package/dist/routes/custom-provider-source.js.map +1 -0
  142. package/dist/routes/fs.d.ts +151 -0
  143. package/dist/routes/fs.d.ts.map +1 -0
  144. package/dist/routes/fs.js +708 -0
  145. package/dist/routes/fs.js.map +1 -0
  146. package/dist/routes/intake.d.ts +23 -0
  147. package/dist/routes/intake.d.ts.map +1 -0
  148. package/dist/routes/intake.js +179 -0
  149. package/dist/routes/intake.js.map +1 -0
  150. package/dist/routes/oauth.d.ts +43 -0
  151. package/dist/routes/oauth.d.ts.map +1 -0
  152. package/dist/routes/oauth.js +463 -0
  153. package/dist/routes/oauth.js.map +1 -0
  154. package/dist/routes/projects.d.ts +19 -0
  155. package/dist/routes/projects.d.ts.map +1 -0
  156. package/dist/routes/projects.js +461 -0
  157. package/dist/routes/projects.js.map +1 -0
  158. package/dist/routes/provider-credentials.d.ts +81 -0
  159. package/dist/routes/provider-credentials.d.ts.map +1 -0
  160. package/dist/routes/provider-credentials.js +86 -0
  161. package/dist/routes/provider-credentials.js.map +1 -0
  162. package/dist/routes/route.d.ts +48 -0
  163. package/dist/routes/route.d.ts.map +1 -0
  164. package/dist/routes/route.js +15 -0
  165. package/dist/routes/route.js.map +1 -0
  166. package/dist/routes/skills.d.ts +30 -0
  167. package/dist/routes/skills.d.ts.map +1 -0
  168. package/dist/routes/skills.js +168 -0
  169. package/dist/routes/skills.js.map +1 -0
  170. package/dist/routes/surface.d.ts +107 -0
  171. package/dist/routes/surface.d.ts.map +1 -0
  172. package/dist/routes/surface.js +297 -0
  173. package/dist/routes/surface.js.map +1 -0
  174. package/dist/routes/tenant-credentials.d.ts +74 -0
  175. package/dist/routes/tenant-credentials.d.ts.map +1 -0
  176. package/dist/routes/tenant-credentials.js +156 -0
  177. package/dist/routes/tenant-credentials.js.map +1 -0
  178. package/dist/routes/work-items.d.ts +40 -0
  179. package/dist/routes/work-items.d.ts.map +1 -0
  180. package/dist/routes/work-items.js +695 -0
  181. package/dist/routes/work-items.js.map +1 -0
  182. package/dist/rules/binding-context.d.ts +5 -0
  183. package/dist/rules/binding-context.d.ts.map +1 -0
  184. package/dist/rules/binding-context.js +28 -0
  185. package/dist/rules/binding-context.js.map +1 -0
  186. package/dist/rules/defaults.d.ts +9 -0
  187. package/dist/rules/defaults.d.ts.map +1 -0
  188. package/dist/rules/defaults.js +322 -0
  189. package/dist/rules/defaults.js.map +1 -0
  190. package/dist/rules/dispatcher.d.ts +43 -0
  191. package/dist/rules/dispatcher.d.ts.map +1 -0
  192. package/dist/rules/dispatcher.js +547 -0
  193. package/dist/rules/dispatcher.js.map +1 -0
  194. package/dist/rules/index.d.ts +7 -0
  195. package/dist/rules/index.d.ts.map +1 -0
  196. package/dist/rules/index.js +5 -0
  197. package/dist/rules/processor.d.ts +54 -0
  198. package/dist/rules/processor.d.ts.map +1 -0
  199. package/dist/rules/processor.js +378 -0
  200. package/dist/rules/processor.js.map +1 -0
  201. package/dist/rules/resolve.d.ts +17 -0
  202. package/dist/rules/resolve.d.ts.map +1 -0
  203. package/dist/rules/resolve.js +30 -0
  204. package/dist/rules/resolve.js.map +1 -0
  205. package/dist/rules/start-coordinator.d.ts +60 -0
  206. package/dist/rules/start-coordinator.d.ts.map +1 -0
  207. package/dist/rules/start-coordinator.js +175 -0
  208. package/dist/rules/start-coordinator.js.map +1 -0
  209. package/dist/rules/tools.d.ts +10 -0
  210. package/dist/rules/tools.d.ts.map +1 -0
  211. package/dist/rules/tools.js +59 -0
  212. package/dist/rules/tools.js.map +1 -0
  213. package/dist/rules/transition-service.d.ts +49 -0
  214. package/dist/rules/transition-service.d.ts.map +1 -0
  215. package/dist/rules/transition-service.js +232 -0
  216. package/dist/rules/transition-service.js.map +1 -0
  217. package/dist/rules/types.d.ts +261 -0
  218. package/dist/rules/types.d.ts.map +1 -0
  219. package/dist/rules/types.js +43 -0
  220. package/dist/rules/types.js.map +1 -0
  221. package/dist/rules/validation.d.ts +11 -0
  222. package/dist/rules/validation.d.ts.map +1 -0
  223. package/dist/rules/validation.js +308 -0
  224. package/dist/rules/validation.js.map +1 -0
  225. package/dist/sandbox/fleet.d.ts +222 -0
  226. package/dist/sandbox/fleet.d.ts.map +1 -0
  227. package/dist/sandbox/fleet.js +322 -0
  228. package/dist/sandbox/fleet.js.map +1 -0
  229. package/dist/sandbox/reattach.d.ts +3 -0
  230. package/dist/sandbox/reattach.d.ts.map +1 -0
  231. package/dist/sandbox/reattach.js +16 -0
  232. package/dist/sandbox/reattach.js.map +1 -0
  233. package/dist/server-error.d.ts +12 -0
  234. package/dist/server-error.d.ts.map +1 -0
  235. package/dist/server-error.js +16 -0
  236. package/dist/server-error.js.map +1 -0
  237. package/dist/session/factory-session.d.ts +116 -0
  238. package/dist/session/factory-session.d.ts.map +1 -0
  239. package/dist/session/factory-session.js +162 -0
  240. package/dist/session/factory-session.js.map +1 -0
  241. package/dist/skills/service.d.ts +48 -0
  242. package/dist/skills/service.d.ts.map +1 -0
  243. package/dist/skills/service.js +40 -0
  244. package/dist/skills/service.js.map +1 -0
  245. package/dist/spa-static.d.ts +33 -0
  246. package/dist/spa-static.d.ts.map +1 -0
  247. package/dist/spa-static.js +104 -0
  248. package/dist/spa-static.js.map +1 -0
  249. package/dist/state-signing.d.ts +59 -0
  250. package/dist/state-signing.d.ts.map +1 -0
  251. package/dist/state-signing.js +83 -0
  252. package/dist/state-signing.js.map +1 -0
  253. package/dist/storage/domains/audit/agent-audit.d.ts +27 -0
  254. package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -0
  255. package/dist/storage/domains/audit/agent-audit.js +66 -0
  256. package/dist/storage/domains/audit/agent-audit.js.map +1 -0
  257. package/dist/storage/domains/audit/base.d.ts +140 -0
  258. package/dist/storage/domains/audit/base.d.ts.map +1 -0
  259. package/dist/storage/domains/audit/base.js +184 -0
  260. package/dist/storage/domains/audit/base.js.map +1 -0
  261. package/dist/storage/domains/audit/domain.d.ts +76 -0
  262. package/dist/storage/domains/audit/domain.d.ts.map +1 -0
  263. package/dist/storage/domains/audit/domain.js +269 -0
  264. package/dist/storage/domains/audit/domain.js.map +1 -0
  265. package/dist/storage/domains/channel-identity/base.d.ts +96 -0
  266. package/dist/storage/domains/channel-identity/base.d.ts.map +1 -0
  267. package/dist/storage/domains/channel-identity/base.js +149 -0
  268. package/dist/storage/domains/channel-identity/base.js.map +1 -0
  269. package/dist/storage/domains/credentials/base.d.ts +134 -0
  270. package/dist/storage/domains/credentials/base.d.ts.map +1 -0
  271. package/dist/storage/domains/credentials/base.js +273 -0
  272. package/dist/storage/domains/credentials/base.js.map +1 -0
  273. package/dist/storage/domains/custom-providers/base.d.ts +57 -0
  274. package/dist/storage/domains/custom-providers/base.d.ts.map +1 -0
  275. package/dist/storage/domains/custom-providers/base.js +145 -0
  276. package/dist/storage/domains/custom-providers/base.js.map +1 -0
  277. package/dist/storage/domains/intake/base.d.ts +38 -0
  278. package/dist/storage/domains/intake/base.d.ts.map +1 -0
  279. package/dist/storage/domains/intake/base.js +80 -0
  280. package/dist/storage/domains/intake/base.js.map +1 -0
  281. package/dist/storage/domains/integrations/base.d.ts +126 -0
  282. package/dist/storage/domains/integrations/base.d.ts.map +1 -0
  283. package/dist/storage/domains/integrations/base.js +317 -0
  284. package/dist/storage/domains/integrations/base.js.map +1 -0
  285. package/dist/storage/domains/memory-settings/base.d.ts +62 -0
  286. package/dist/storage/domains/memory-settings/base.d.ts.map +1 -0
  287. package/dist/storage/domains/memory-settings/base.js +126 -0
  288. package/dist/storage/domains/memory-settings/base.js.map +1 -0
  289. package/dist/storage/domains/model-packs/base.d.ts +49 -0
  290. package/dist/storage/domains/model-packs/base.d.ts.map +1 -0
  291. package/dist/storage/domains/model-packs/base.js +96 -0
  292. package/dist/storage/domains/model-packs/base.js.map +1 -0
  293. package/dist/storage/domains/projects/base.d.ts +58 -0
  294. package/dist/storage/domains/projects/base.d.ts.map +1 -0
  295. package/dist/storage/domains/projects/base.js +111 -0
  296. package/dist/storage/domains/projects/base.js.map +1 -0
  297. package/dist/storage/domains/queue-health/base.d.ts +52 -0
  298. package/dist/storage/domains/queue-health/base.d.ts.map +1 -0
  299. package/dist/storage/domains/queue-health/base.js +122 -0
  300. package/dist/storage/domains/queue-health/base.js.map +1 -0
  301. package/dist/storage/domains/source-control/base.d.ts +360 -0
  302. package/dist/storage/domains/source-control/base.d.ts.map +1 -0
  303. package/dist/storage/domains/source-control/base.js +914 -0
  304. package/dist/storage/domains/source-control/base.js.map +1 -0
  305. package/dist/storage/domains/source-control/inmemory.d.ts +175 -0
  306. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -0
  307. package/dist/storage/domains/source-control/inmemory.js +380 -0
  308. package/dist/storage/domains/source-control/inmemory.js.map +1 -0
  309. package/dist/storage/domains/work-items/base.d.ts +412 -0
  310. package/dist/storage/domains/work-items/base.d.ts.map +1 -0
  311. package/dist/storage/domains/work-items/base.js +1320 -0
  312. package/dist/storage/domains/work-items/base.js.map +1 -0
  313. package/dist/storage/domains/work-items/metrics.d.ts +101 -0
  314. package/dist/storage/domains/work-items/metrics.d.ts.map +1 -0
  315. package/dist/storage/domains/work-items/metrics.js +221 -0
  316. package/dist/storage/domains/work-items/metrics.js.map +1 -0
  317. package/dist/timing.d.ts +15 -0
  318. package/dist/timing.d.ts.map +1 -0
  319. package/dist/timing.js +34 -0
  320. package/dist/timing.js.map +1 -0
  321. package/dist/workspace.d.ts +24 -0
  322. package/dist/workspace.d.ts.map +1 -0
  323. package/dist/workspace.js +287 -0
  324. package/dist/workspace.js.map +1 -0
  325. package/factory-skills/configure-factory-rules/SKILL.md +67 -0
  326. package/factory-skills/factory-plan/SKILL.md +61 -0
  327. package/factory-skills/factory-review/SKILL.md +157 -0
  328. package/factory-skills/factory-triage/SKILL.md +99 -0
  329. package/package.json +83 -0
@@ -0,0 +1,1006 @@
1
+ import { createGithubSubscriptionTools, parseCreatedPullRequest, subscribeCurrentSessionToPullRequest } from "../../github/session-subscriptions.js";
2
+ import { runGithubIssueTriage } from "../../github/issue-triage.js";
3
+ import { buildGithubRoutes } from "../../github/routes.js";
4
+ import { attachGithubReconciler, attachGithubRules } from "../../github/rules.js";
5
+ import { PlatformApiClient, PlatformApiError, logPlatformInfo, logPlatformWarn, platformApiClientConfigFromEnv } from "../api-client.js";
6
+ import { PlatformGithubEventWorker } from "./event-worker.js";
7
+ import { registerApiRoute } from "@mastra/core/server";
8
+ //#region src/integrations/platform/github/integration.ts
9
+ const PAGE_SIZE = 30;
10
+ const API_PREFIX = "/v1/server";
11
+ /**
12
+ * How long an installation's repository listing may be reused. The repos
13
+ * route and token minting both call it — often several times within one UI
14
+ * interaction — and each call is a full Platform round trip.
15
+ */
16
+ const INSTALLATION_REPOS_CACHE_TTL_MS = 3e4;
17
+ /**
18
+ * How long a minted repository-scoped token may be reused. GitHub
19
+ * installation tokens live ~60 minutes; 5 minutes keeps a wide validity
20
+ * margin while collapsing the per-session-materialization mint round trip.
21
+ */
22
+ const REPOSITORY_ACCESS_CACHE_TTL_MS = 5 * 6e4;
23
+ /**
24
+ * Upper bound for both TTL caches. Entries expire lazily on re-access, so
25
+ * without a hard cap keys that stop being queried would accumulate for the
26
+ * integration's (long) lifetime. Insertion-order eviction — matches the
27
+ * bounded verification cache in `@mastra/auth-studio`.
28
+ */
29
+ const MAX_CACHE_ENTRIES = 1e3;
30
+ function setBounded(cache, key, value) {
31
+ cache.delete(key);
32
+ cache.set(key, value);
33
+ if (cache.size > MAX_CACHE_ENTRIES) {
34
+ const oldest = cache.keys().next().value;
35
+ if (oldest !== void 0) cache.delete(oldest);
36
+ }
37
+ }
38
+ const REPOSITORY_TOKEN_PERMISSIONS = {
39
+ contents: "write",
40
+ issues: "write",
41
+ pull_requests: "write"
42
+ };
43
+ function loose(c) {
44
+ return c;
45
+ }
46
+ function routeBaseUrl(ctx, requestUrl) {
47
+ return (ctx.baseUrl || new URL(requestUrl).origin).replace(/\/+$/, "");
48
+ }
49
+ var PlatformGithubIntegration = class {
50
+ id = "github";
51
+ #client;
52
+ #endpointHost;
53
+ #slug;
54
+ #pollingEnabled;
55
+ #pollingIntervalMs;
56
+ #reconcileEnabled;
57
+ #storage;
58
+ #integrationStorage;
59
+ /** installationId → cached repository listing (TTL-bounded). */
60
+ #installationReposCache = /* @__PURE__ */ new Map();
61
+ /** `orgId:repositoryId` → cached repository access (TTL-bounded). */
62
+ #repositoryAccessCache = /* @__PURE__ */ new Map();
63
+ #runIssueTriage;
64
+ intake = {
65
+ resolveIntakeDispatch: (input) => this.#resolveIntakeDispatch(input),
66
+ listSources: async ({ orgId, userId }) => {
67
+ const usable = (await this.#client.request("GET", `${API_PREFIX}/github-app/installations`)).installations.filter((installation) => installation.usable && !installation.suspendedAt);
68
+ return (await Promise.all(usable.map(async (installation) => {
69
+ const storedInstallation = await this.versionControl.registerInstallation({
70
+ orgId,
71
+ userId,
72
+ installation: {
73
+ externalId: String(installation.installationId),
74
+ accountName: installation.accountLogin,
75
+ accountType: installation.accountType
76
+ }
77
+ });
78
+ const result = await this.#client.request("GET", `${API_PREFIX}/github-app/installations/${installation.installationId}/repositories`);
79
+ await this.versionControl.registerRepositories({
80
+ orgId,
81
+ installationId: storedInstallation.id,
82
+ repositories: result.repositories.map((repository) => ({
83
+ externalId: String(repository.id),
84
+ slug: repository.fullName,
85
+ defaultBranch: repository.defaultBranch,
86
+ metadata: {
87
+ private: repository.private,
88
+ url: repository.htmlUrl
89
+ }
90
+ }))
91
+ });
92
+ return result.repositories.map((repository) => ({
93
+ repository,
94
+ installation
95
+ }));
96
+ }))).flat().map(({ repository, installation }) => ({
97
+ id: repository.fullName,
98
+ name: repository.fullName,
99
+ type: "repository",
100
+ metadata: {
101
+ installationId: installation.installationId,
102
+ accountLogin: installation.accountLogin,
103
+ accountType: installation.accountType,
104
+ repositoryId: repository.id,
105
+ defaultBranch: repository.defaultBranch,
106
+ private: repository.private,
107
+ url: repository.htmlUrl
108
+ }
109
+ }));
110
+ },
111
+ listItems: async ({ sourceIds, cursor }) => {
112
+ const page = parsePositiveCursor(cursor);
113
+ const pages = await Promise.all(sourceIds.map(async (sourceId) => {
114
+ const connection = {
115
+ type: "app-installation",
116
+ installationId: 1
117
+ };
118
+ const [issues, pullRequests] = await Promise.all([this.#listIssues(connection, sourceId, page), this.#listPullRequests({
119
+ connection,
120
+ sourceId,
121
+ includeDrafts: false,
122
+ cursor: String(page)
123
+ })]);
124
+ return {
125
+ items: [...issues.issues.map((issue) => ({
126
+ source: {
127
+ type: "issue",
128
+ externalId: `${sourceId}:${issue.id}`,
129
+ url: issue.url
130
+ },
131
+ sourceId,
132
+ title: issue.title,
133
+ status: issue.state ?? void 0,
134
+ labels: issue.labels,
135
+ assignee: issue.assignee,
136
+ createdAt: issue.createdAt,
137
+ updatedAt: issue.updatedAt,
138
+ metadata: {
139
+ repository: sourceId,
140
+ number: Number(issue.id),
141
+ author: issue.author
142
+ }
143
+ })), ...pullRequests.pullRequests.map((pullRequest) => ({
144
+ source: {
145
+ type: "pull-request",
146
+ externalId: `${sourceId}:${pullRequest.id}`,
147
+ url: pullRequest.url
148
+ },
149
+ sourceId,
150
+ title: pullRequest.title,
151
+ status: pullRequest.state,
152
+ createdAt: pullRequest.createdAt,
153
+ updatedAt: pullRequest.updatedAt,
154
+ metadata: {
155
+ repository: sourceId,
156
+ number: Number(pullRequest.id),
157
+ author: pullRequest.author,
158
+ baseBranch: pullRequest.baseBranch,
159
+ headBranch: pullRequest.headBranch
160
+ }
161
+ }))],
162
+ hasNextPage: issues.nextCursor !== null || pullRequests.nextCursor !== null
163
+ };
164
+ }));
165
+ return {
166
+ items: pages.flatMap((result) => result.items),
167
+ nextCursor: pages.some((result) => result.hasNextPage) ? String(page + 1) : null
168
+ };
169
+ },
170
+ listIssues: async (input) => {
171
+ requireGithubConnection(input.connection);
172
+ const sourceId = requireSingleSource(input.sourceIds, "GitHub Intake requires exactly one repository source.");
173
+ return this.#listIssues(input.connection, sourceId, parsePositiveCursor(input.cursor), input.labels);
174
+ },
175
+ getIssue: (input) => this.#getIssue(input.connection, input.sourceId, input.issueId),
176
+ createComment: (input) => this.#createIssueComment(input),
177
+ updateIssue: (input) => this.#updateIntakeIssue(input)
178
+ };
179
+ versionControl = {
180
+ initialize: ({ storage }) => {
181
+ this.#storage = storage;
182
+ },
183
+ registerInstallation: ({ orgId, userId, installation }) => this.storage.installations.upsert({
184
+ orgId,
185
+ connectedByUserId: userId,
186
+ externalId: installation.externalId,
187
+ accountName: installation.accountName,
188
+ accountType: installation.accountType,
189
+ providerMetadata: installation.metadata
190
+ }),
191
+ registerRepositories: ({ orgId, installationId, repositories }) => Promise.all(repositories.map((repository) => this.storage.repositories.upsert({
192
+ orgId,
193
+ input: {
194
+ installationId,
195
+ externalId: repository.externalId,
196
+ slug: repository.slug,
197
+ defaultBranch: repository.defaultBranch,
198
+ providerMetadata: repository.metadata
199
+ }
200
+ }))),
201
+ getRepositoryAccess: async ({ orgId, repositoryId }) => {
202
+ const cacheKey = `${orgId}:${repositoryId}`;
203
+ const cached = this.#repositoryAccessCache.get(cacheKey);
204
+ if (cached && cached.expiresAt > Date.now()) return cached.access;
205
+ this.#repositoryAccessCache.delete(cacheKey);
206
+ const repository = await this.storage.repositories.get({
207
+ orgId,
208
+ id: repositoryId
209
+ });
210
+ if (!repository) throw new Error("Version-control repository not found.");
211
+ const cloneUrl = `https://github.com/${repository.slug}.git`;
212
+ const installation = await this.storage.installations.get({
213
+ orgId,
214
+ id: repository.installationId
215
+ });
216
+ if (!installation) throw new Error("Version-control installation not found.");
217
+ const installationId = parsePositiveInteger(installation.externalId);
218
+ if (installationId === null) throw new Error("GitHub installation id is invalid.");
219
+ const repositoryName = splitRepository(repository.slug).repo;
220
+ try {
221
+ const access = {
222
+ cloneUrl,
223
+ authorization: {
224
+ scheme: "bearer",
225
+ token: (await this.#client.request("POST", `${API_PREFIX}/github-app/installations/${installationId}/token`, {
226
+ repositories: [repositoryName],
227
+ permissions: REPOSITORY_TOKEN_PERMISSIONS
228
+ })).token
229
+ }
230
+ };
231
+ setBounded(this.#repositoryAccessCache, cacheKey, {
232
+ access,
233
+ expiresAt: Date.now() + REPOSITORY_ACCESS_CACHE_TTL_MS
234
+ });
235
+ return access;
236
+ } catch (err) {
237
+ if (!isDeadInstallation(err) || !installation.accountName) throw err;
238
+ const newInstallation = (await this.storage.installations.list({ orgId })).find((inst) => inst.accountName === installation.accountName && inst.id !== installation.id);
239
+ if (!newInstallation) throw err;
240
+ const newInstallationId = parsePositiveInteger(newInstallation.externalId);
241
+ if (newInstallationId === null) throw err;
242
+ await this.storage.repositories.migrateInstallation({
243
+ orgId,
244
+ id: repositoryId,
245
+ newInstallationId: newInstallation.id
246
+ });
247
+ const access = {
248
+ cloneUrl,
249
+ authorization: {
250
+ scheme: "bearer",
251
+ token: (await this.#client.request("POST", `${API_PREFIX}/github-app/installations/${newInstallationId}/token`, {
252
+ repositories: [repositoryName],
253
+ permissions: REPOSITORY_TOKEN_PERMISSIONS
254
+ })).token
255
+ }
256
+ };
257
+ setBounded(this.#repositoryAccessCache, cacheKey, {
258
+ access,
259
+ expiresAt: Date.now() + REPOSITORY_ACCESS_CACHE_TTL_MS
260
+ });
261
+ return access;
262
+ }
263
+ },
264
+ listPullRequests: (input) => this.#listPullRequests(input),
265
+ getPullRequest: (input) => this.#getPullRequest(input),
266
+ createPullRequest: (input) => this.#createPullRequest(input),
267
+ updatePullRequest: (input) => this.#updatePullRequest(input),
268
+ closePullRequest: (input) => this.#updatePullRequest({
269
+ ...input,
270
+ state: "closed"
271
+ }),
272
+ mergePullRequest: (input) => this.#mergePullRequest(input),
273
+ listComments: (input) => this.#listComments(input),
274
+ createComment: (input) => this.#createComment(input),
275
+ updateComment: (input) => this.#updateComment(input),
276
+ deleteComment: (input) => this.#deleteComment(input),
277
+ listReviews: (input) => this.#listReviews(input),
278
+ getReview: (input) => this.#getReview(input),
279
+ createReview: (input) => this.#createReview(input),
280
+ updateReview: (input) => this.#updateReview(input),
281
+ submitReview: (input) => this.#submitReview(input),
282
+ dismissReview: (input) => this.#dismissReview(input),
283
+ deletePendingReview: (input) => this.#deletePendingReview(input),
284
+ listReviewComments: (input) => this.#listReviewComments(input),
285
+ createReviewComment: (input) => this.#createReviewComment(input),
286
+ updateReviewComment: (input) => this.#updateReviewComment(input),
287
+ deleteReviewComment: (input) => this.#deleteReviewComment(input),
288
+ listRequestedReviewers: (input) => this.#requestedReviewers("GET", input),
289
+ requestReviewers: (input) => this.#requestedReviewers("POST", input),
290
+ removeRequestedReviewers: (input) => this.#requestedReviewers("DELETE", input)
291
+ };
292
+ constructor(options = {}) {
293
+ const config = platformApiClientConfigFromEnv();
294
+ this.#client = new PlatformApiClient(config);
295
+ this.#endpointHost = new URL(config.baseUrl).host;
296
+ this.#slug = options.slug;
297
+ this.#pollingEnabled = process.env.MASTRA_PLATFORM_GITHUB_POLLING_ENABLED?.trim().toLowerCase() !== "false";
298
+ this.#pollingIntervalMs = optionalPositiveIntegerEnv("MASTRA_PLATFORM_GITHUB_POLLING_INTERVAL_MS");
299
+ this.#reconcileEnabled = process.env.MASTRA_PLATFORM_GITHUB_RECONCILE_ENABLED?.trim().toLowerCase() !== "false";
300
+ this.#runIssueTriage = options.runIssueTriage;
301
+ }
302
+ /** GitHub App slug when the deployment explicitly provides it. */
303
+ get slug() {
304
+ return this.#slug;
305
+ }
306
+ get storage() {
307
+ if (!this.#storage) throw new Error("PlatformGithubIntegration source-control storage has not been initialized.");
308
+ return this.#storage;
309
+ }
310
+ get sourceControlStorage() {
311
+ return this.storage;
312
+ }
313
+ get integrationStorage() {
314
+ if (!this.#integrationStorage) throw new Error("PlatformGithubIntegration generic storage has not been initialized.");
315
+ return this.#integrationStorage;
316
+ }
317
+ /** Resolve a stored GitHub locator without scanning installations or repositories. */
318
+ async #resolveIntakeDispatch({ orgId, externalSource }) {
319
+ const target = parseGithubExternalTarget(externalSource.externalId);
320
+ if (!target) return null;
321
+ const repository = target.repository.includes("/") ? target.repository : (await this.storage.repositories.findByExternalId({
322
+ orgId,
323
+ externalId: target.repository
324
+ }))?.slug;
325
+ if (!repository) return null;
326
+ return {
327
+ connection: {
328
+ type: "app-installation",
329
+ installationId: 1
330
+ },
331
+ sourceId: repository,
332
+ issueId: target.issueId
333
+ };
334
+ }
335
+ initialize({ storage }) {
336
+ this.#integrationStorage = storage;
337
+ logPlatformInfo("Platform GitHub integration initialized", {
338
+ endpointHost: this.#endpointHost,
339
+ pollingEnabled: this.#pollingEnabled,
340
+ pollingIntervalMs: this.#pollingIntervalMs,
341
+ reconcileEnabled: this.#reconcileEnabled
342
+ });
343
+ }
344
+ routes(ctx) {
345
+ const ingestFactoryEvent = attachGithubRules(this, ctx);
346
+ return [
347
+ this.#statusRoute(ctx),
348
+ this.#connectRoute(ctx),
349
+ this.#connectUserRoute(ctx),
350
+ ...buildGithubRoutes({
351
+ auth: ctx.auth,
352
+ fleet: ctx.fleet,
353
+ storage: ctx.factoryStorage,
354
+ github: this,
355
+ stateSigner: ctx.stateSigner,
356
+ baseUrl: ctx.baseUrl,
357
+ controller: ctx.controller,
358
+ projects: ctx.storage.projects,
359
+ emitAudit: ctx.hooks?.emitAudit,
360
+ ingestFactoryEvent,
361
+ runIssueTriage: this.#runIssueTriage ?? (ctx.controller ? (input) => runGithubIssueTriage({
362
+ controller: ctx.controller,
363
+ input
364
+ }) : void 0)
365
+ }).filter((route) => route.path !== "/web/github/status" && route.path !== "/web/github/webhook" && !route.path.startsWith("/auth/github/"))
366
+ ];
367
+ }
368
+ #statusRoute(ctx) {
369
+ return registerApiRoute("/web/github/status", {
370
+ method: "GET",
371
+ requiresAuth: false,
372
+ handler: async (c) => {
373
+ await ctx.auth.ensureUser(loose(c));
374
+ const tenant = ctx.auth.tenant(loose(c));
375
+ if (!tenant) return c.json({
376
+ error: "unauthorized",
377
+ reason: "auth_required"
378
+ }, 401);
379
+ if (!tenant.orgId) return c.json({
380
+ enabled: true,
381
+ sandboxEnabled: ctx.fleet.enabled,
382
+ organizationRequired: true,
383
+ connected: false,
384
+ installations: [],
385
+ userConnected: false,
386
+ userGithubUsername: null,
387
+ reason: "organization_required",
388
+ diagnostics: this.diagnostics()
389
+ });
390
+ const [installations, userConnection] = await Promise.all([this.#syncInstallations(tenant.orgId, tenant.userId), this.#fetchUserConnection(tenant.userId)]);
391
+ return c.json({
392
+ enabled: true,
393
+ sandboxEnabled: ctx.fleet.enabled,
394
+ connected: installations.length > 0,
395
+ installations: installations.map((installation) => ({
396
+ installationId: Number(installation.externalId),
397
+ accountLogin: installation.accountName,
398
+ accountType: installation.accountType
399
+ })),
400
+ userConnected: userConnection.connected,
401
+ userGithubUsername: userConnection.githubUsername,
402
+ reason: installations.length > 0 ? "ready" : "not_connected",
403
+ diagnostics: this.diagnostics()
404
+ });
405
+ }
406
+ });
407
+ }
408
+ #connectRoute(ctx) {
409
+ return registerApiRoute("/auth/github/connect", {
410
+ method: "GET",
411
+ requiresAuth: false,
412
+ handler: async (c) => {
413
+ await ctx.auth.ensureUser(loose(c));
414
+ const tenant = ctx.auth.tenant(loose(c));
415
+ if (!tenant?.orgId) return c.json({ error: "unauthorized" }, 401);
416
+ const redirectTo = c.req.query("redirectTo") || c.req.query("return_to") || "/";
417
+ const originator = routeBaseUrl(ctx, c.req.url);
418
+ logPlatformInfo("Starting Platform GitHub connect flow", {
419
+ orgId: tenant.orgId,
420
+ redirectTo,
421
+ originator
422
+ });
423
+ const query = new URLSearchParams({
424
+ action: "install",
425
+ redirectTo,
426
+ originator
427
+ });
428
+ const { url } = await this.#client.request("GET", `${API_PREFIX}/github-app/install-url?${query}`);
429
+ return c.redirect(url);
430
+ }
431
+ });
432
+ }
433
+ #connectUserRoute(ctx) {
434
+ return registerApiRoute("/auth/github/connect-user", {
435
+ method: "GET",
436
+ requiresAuth: false,
437
+ handler: async (c) => {
438
+ await ctx.auth.ensureUser(loose(c));
439
+ const tenant = ctx.auth.tenant(loose(c));
440
+ if (!tenant?.orgId) return c.json({ error: "unauthorized" }, 401);
441
+ const redirectTo = c.req.query("redirectTo") || c.req.query("return_to") || "/";
442
+ const originator = routeBaseUrl(ctx, c.req.url);
443
+ logPlatformInfo("Starting Platform GitHub user authorization flow", {
444
+ orgId: tenant.orgId,
445
+ redirectTo,
446
+ originator
447
+ });
448
+ const query = new URLSearchParams({
449
+ userId: tenant.userId,
450
+ redirectTo,
451
+ originator
452
+ });
453
+ const { url } = await this.#client.request("GET", `${API_PREFIX}/github-app/authenticate?${query}`);
454
+ return c.redirect(url);
455
+ }
456
+ });
457
+ }
458
+ /**
459
+ * Personal GitHub connection status for the acting user. Returns
460
+ * not-connected when the platform predates the user-connection endpoint.
461
+ */
462
+ async #fetchUserConnection(userId) {
463
+ try {
464
+ const connection = await this.#client.request("GET", `${API_PREFIX}/github-app/user-connection?${new URLSearchParams({ userId })}`);
465
+ if (!connection.connected && connection.reason) logPlatformWarn("Platform GitHub user connection verification failed", {
466
+ userId,
467
+ reason: connection.reason
468
+ });
469
+ return connection;
470
+ } catch {
471
+ return {
472
+ connected: false,
473
+ githubUsername: null
474
+ };
475
+ }
476
+ }
477
+ async #syncInstallations(orgId, userId) {
478
+ const usableInstallations = (await this.#client.request("GET", `${API_PREFIX}/github-app/installations`)).installations.filter((installation) => installation.usable && !installation.suspendedAt);
479
+ return Promise.all(usableInstallations.map((installation) => this.versionControl.registerInstallation({
480
+ orgId,
481
+ userId,
482
+ installation: {
483
+ externalId: String(installation.installationId),
484
+ accountName: installation.accountLogin,
485
+ accountType: installation.accountType
486
+ }
487
+ })));
488
+ }
489
+ workers(ctx) {
490
+ if (!this.#pollingEnabled && !this.#reconcileEnabled) return [];
491
+ if (!ctx.controller) throw new Error("Platform GitHub event polling requires the mounted Mastra Code controller.");
492
+ return [new PlatformGithubEventWorker({
493
+ client: this.#client,
494
+ controller: ctx.controller,
495
+ github: this,
496
+ storage: ctx.storage.generic,
497
+ ingestFactoryEvent: attachGithubRules(this, ctx),
498
+ reconcileFactoryState: this.#reconcileEnabled ? attachGithubReconciler(this, ctx, (input) => this.fetchPullRequestState(input), (input) => this.fetchIssueState(input)) : void 0,
499
+ pollEventsEnabled: this.#pollingEnabled,
500
+ intervalMs: this.#pollingIntervalMs
501
+ })];
502
+ }
503
+ /**
504
+ * Reads live PR state through the Platform GitHub proxy for the merge
505
+ * reconciler. Returns undefined when the PR cannot be resolved (missing,
506
+ * proxy error) so a sweep never fabricates a merge.
507
+ */
508
+ async fetchPullRequestState(input) {
509
+ let repository;
510
+ try {
511
+ repository = splitRepository(input.repository);
512
+ } catch {
513
+ return;
514
+ }
515
+ try {
516
+ const result = await this.#client.request("GET", `${API_PREFIX}/github/repos/${encodeURIComponent(repository.owner)}/${encodeURIComponent(repository.repo)}/pulls/${input.number}`);
517
+ return {
518
+ title: result.title ?? `PR ${input.number}`,
519
+ url: result.html_url ?? `https://github.com/${input.repository}/pull/${input.number}`,
520
+ state: result.state === "closed" ? "closed" : "open",
521
+ draft: result.draft === true,
522
+ merged: result.merged === true,
523
+ assignees: (result.assignees ?? []).flatMap((assignee) => assignee.login ? [assignee.login] : []),
524
+ requestedReviewers: (result.requested_reviewers ?? []).flatMap((reviewer) => reviewer.login ? [reviewer.login] : []),
525
+ headBranch: result.head?.ref ?? "",
526
+ baseBranch: result.base?.ref ?? "",
527
+ ...result.user?.login ? { author: result.user.login } : {},
528
+ ...result.created_at ? { createdAt: result.created_at } : {},
529
+ ...result.merged_by?.login ? { mergedBy: result.merged_by.login } : {}
530
+ };
531
+ } catch {
532
+ return;
533
+ }
534
+ }
535
+ /**
536
+ * Reads live issue state through the Platform GitHub proxy for the
537
+ * reconciler. Returns undefined when the issue cannot be resolved (missing,
538
+ * is actually a PR, proxy error) so a sweep never fabricates a close.
539
+ */
540
+ async fetchIssueState(input) {
541
+ let repository;
542
+ try {
543
+ repository = splitRepository(input.repository);
544
+ } catch {
545
+ return;
546
+ }
547
+ try {
548
+ const result = await this.#client.request("GET", `${API_PREFIX}/github/repos/${encodeURIComponent(repository.owner)}/${encodeURIComponent(repository.repo)}/issues/${input.number}`);
549
+ if (result.pull_request) return void 0;
550
+ return {
551
+ title: result.title ?? `Issue ${input.number}`,
552
+ url: result.html_url ?? `https://github.com/${input.repository}/issues/${input.number}`,
553
+ state: result.state === "closed" ? "closed" : "open",
554
+ ...result.state_reason ? { stateReason: result.state_reason } : {},
555
+ assignees: (result.assignees ?? []).flatMap((assignee) => assignee.login ? [assignee.login] : []),
556
+ ...result.user?.login ? { author: result.user.login } : {},
557
+ ...result.created_at ? { createdAt: result.created_at } : {},
558
+ ...result.updated_at ? { updatedAt: result.updated_at } : {}
559
+ };
560
+ } catch {
561
+ return;
562
+ }
563
+ }
564
+ sessionTools({ requestContext }) {
565
+ return createGithubSubscriptionTools(requestContext, this);
566
+ }
567
+ async postToolObserver({ toolContext, requestContext }) {
568
+ const pullRequestUrl = parseCreatedPullRequest(toolContext);
569
+ if (!pullRequestUrl || !requestContext) return;
570
+ await subscribeCurrentSessionToPullRequest(requestContext, pullRequestUrl, "auto-gh-pr-create", this);
571
+ }
572
+ diagnostics() {
573
+ return {
574
+ mode: "platform",
575
+ endpointHost: this.#endpointHost,
576
+ polling: {
577
+ enabled: this.#pollingEnabled,
578
+ ...this.#pollingIntervalMs === void 0 ? {} : { intervalMs: this.#pollingIntervalMs }
579
+ },
580
+ reconcile: { enabled: this.#reconcileEnabled }
581
+ };
582
+ }
583
+ async getRepositoryCollaboratorPermission(_installationId, repoFullName, username, signal) {
584
+ let repository;
585
+ try {
586
+ repository = splitRepository(repoFullName);
587
+ } catch {
588
+ return;
589
+ }
590
+ try {
591
+ return (await this.#client.request("GET", `${API_PREFIX}/github/repos/${encodeURIComponent(repository.owner)}/${encodeURIComponent(repository.repo)}/collaborators/${encodeURIComponent(username)}/permission`, void 0, { signal })).permission;
592
+ } catch {
593
+ return;
594
+ }
595
+ }
596
+ async listInstallationRepos(installationId) {
597
+ const cached = this.#installationReposCache.get(installationId);
598
+ if (cached && cached.expiresAt > Date.now()) return cached.repos;
599
+ this.#installationReposCache.delete(installationId);
600
+ const repos = (await this.#client.request("GET", `${API_PREFIX}/github-app/installations/${installationId}/repositories`)).repositories.map((repository) => ({
601
+ ...repository,
602
+ installationId
603
+ }));
604
+ setBounded(this.#installationReposCache, installationId, {
605
+ repos,
606
+ expiresAt: Date.now() + INSTALLATION_REPOS_CACHE_TTL_MS
607
+ });
608
+ return repos;
609
+ }
610
+ async mintInstallationToken(installationId) {
611
+ const repositories = await this.listInstallationRepos(installationId);
612
+ if (repositories.length === 0 || repositories.length > 10) throw new Error("Platform GitHub token minting requires between one and ten installation repositories.");
613
+ return (await this.#client.request("POST", `${API_PREFIX}/github-app/installations/${installationId}/token`, {
614
+ repositories: repositories.map((repository) => repository.name),
615
+ permissions: REPOSITORY_TOKEN_PERMISSIONS
616
+ })).token;
617
+ }
618
+ async addIssueLabels(_installationId, sourceId, issueNumber, labels) {
619
+ return (await this.#client.request("POST", repositoryPath(sourceId, `issues/${issueNumber}/labels`), { labels })).labels;
620
+ }
621
+ getInstallationOctokit(_installationId) {
622
+ return { pulls: { get: async ({ owner, repo, pull_number }) => {
623
+ return { data: { base: { repo: { id: (await this.#client.request("GET", repositoryPath(`${owner}/${repo}`, `pulls/${pull_number}`))).base.repo.id } } } };
624
+ } } };
625
+ }
626
+ async #listIssues(connection, sourceId, page, labels) {
627
+ requireGithubConnection(connection);
628
+ const path = repositoryPath(sourceId, "issues");
629
+ const query = new URLSearchParams({
630
+ state: "open",
631
+ page: String(page),
632
+ per_page: String(PAGE_SIZE)
633
+ });
634
+ const normalizedLabels = normalizeLabels(labels);
635
+ if (normalizedLabels.length > 0) query.set("label", normalizedLabels.join(","));
636
+ const result = await this.#client.request("GET", `${path}?${query}`);
637
+ return {
638
+ issues: result.issues.map((issue) => parseIntakeIssue(sourceId, issue)),
639
+ nextCursor: result.issues.length > 0 ? String(page + 1) : null
640
+ };
641
+ }
642
+ async #getIssue(connection, sourceId, issueId) {
643
+ requireGithubConnection(connection);
644
+ const repository = requireSource(sourceId, "GitHub Intake requires a repository source.");
645
+ const issueNumber = requirePositiveId(issueId, "issue");
646
+ try {
647
+ const [issue, comments] = await Promise.all([this.#client.request("GET", repositoryPath(repository, `issues/${issueNumber}`)), this.#client.request("GET", `${repositoryPath(repository, `issues/${issueNumber}/comments`)}?per_page=100`)]);
648
+ return parseIntakeIssueDetail(repository, issue, comments.comments);
649
+ } catch (error) {
650
+ if (isNotFound(error)) return null;
651
+ throw error;
652
+ }
653
+ }
654
+ async #updateIntakeIssue(input) {
655
+ requireGithubConnection(input.connection);
656
+ const repository = requireSource(input.sourceId, "GitHub Intake requires a repository source.");
657
+ const issueNumber = requirePositiveId(input.issueId, "issue");
658
+ if (input.state.kind === "byName") {
659
+ logPlatformWarn(`Platform GitHub: updateIssue byName is not supported (name=${input.state.name}); ignoring.`);
660
+ return null;
661
+ }
662
+ const targetState = input.state.stateType === "unstarted" || input.state.stateType === "started" ? "open" : "closed";
663
+ const stateReason = targetState === "closed" ? input.state.stateType === "canceled" ? "not_planned" : "completed" : null;
664
+ try {
665
+ await this.#client.request("GET", repositoryPath(repository, `pulls/${issueNumber}`));
666
+ logPlatformWarn(`Platform GitHub: updateIssue rejected — target ${repository}#${issueNumber} is a pull request.`);
667
+ return null;
668
+ } catch (error) {
669
+ if (!isNotFound(error)) throw error;
670
+ }
671
+ try {
672
+ return parseIntakeIssue(repository, await this.#client.request("PATCH", repositoryPath(repository, `issues/${issueNumber}`), {
673
+ state: targetState,
674
+ ...stateReason ? { state_reason: stateReason } : {}
675
+ }, { actingUserId: input.actingUserId }));
676
+ } catch (error) {
677
+ if (isNotFound(error)) return null;
678
+ throw error;
679
+ }
680
+ }
681
+ async #createIssueComment(input) {
682
+ requireGithubConnection(input.connection);
683
+ const repository = requireSource(input.sourceId, "GitHub Intake requires a repository source.");
684
+ const issueNumber = requirePositiveId(input.issueId, "issue");
685
+ try {
686
+ const comment = await this.#client.request("POST", repositoryPath(repository, `issues/${issueNumber}/comments`), { body: input.body }, { actingUserId: input.actingUserId });
687
+ return {
688
+ id: String(comment.id),
689
+ url: comment.htmlUrl
690
+ };
691
+ } catch (error) {
692
+ if (isNotFound(error)) return null;
693
+ throw error;
694
+ }
695
+ }
696
+ async #listPullRequests(input) {
697
+ requireGithubConnection(input.connection);
698
+ const page = parsePositiveCursor(input.cursor);
699
+ const query = new URLSearchParams({
700
+ state: input.state ?? "open",
701
+ page: String(page),
702
+ per_page: String(PAGE_SIZE)
703
+ });
704
+ const result = await this.#client.request("GET", `${repositoryPath(input.sourceId, "pulls")}?${query}`);
705
+ return {
706
+ pullRequests: result.pullRequests.filter((pullRequest) => input.includeDrafts !== false || !pullRequest.draft).map(parsePullRequest),
707
+ nextCursor: result.pullRequests.length === PAGE_SIZE ? String(page + 1) : null
708
+ };
709
+ }
710
+ async #getPullRequest(input) {
711
+ try {
712
+ return parsePullRequest(await this.#client.request("GET", pullRequestPath(input, input.pullRequestId)));
713
+ } catch (error) {
714
+ if (isNotFound(error)) return null;
715
+ throw error;
716
+ }
717
+ }
718
+ async #createPullRequest(input) {
719
+ requireGithubConnection(input.connection);
720
+ return parsePullRequest(await this.#client.request("POST", repositoryPath(input.sourceId, "pulls"), {
721
+ head: input.headBranch,
722
+ base: input.baseBranch,
723
+ title: input.title,
724
+ body: input.body,
725
+ draft: input.draft
726
+ }, { actingUserId: input.actingUserId }));
727
+ }
728
+ async #updatePullRequest(input) {
729
+ return parsePullRequest(await this.#client.request("PATCH", pullRequestPath(input, input.pullRequestId), {
730
+ title: input.title,
731
+ body: input.body === null ? "" : input.body,
732
+ base: input.baseBranch,
733
+ state: input.state
734
+ }, { actingUserId: input.actingUserId }));
735
+ }
736
+ #mergePullRequest(input) {
737
+ return this.#client.request("PUT", `${pullRequestPath(input, input.pullRequestId)}/merge`, {
738
+ commitTitle: input.commitTitle,
739
+ commitMessage: input.commitMessage,
740
+ method: input.method
741
+ }, { actingUserId: input.actingUserId });
742
+ }
743
+ async #listComments(input) {
744
+ const page = parsePositiveCursor(input.cursor);
745
+ const result = await this.#client.request("GET", `${repositoryPath(input.sourceId, `issues/${requirePositiveId(input.pullRequestId, "pull request")}/comments`)}?page=${page}&per_page=${PAGE_SIZE}`);
746
+ return {
747
+ comments: result.comments.map(parseComment),
748
+ nextCursor: result.comments.length === PAGE_SIZE ? String(page + 1) : null
749
+ };
750
+ }
751
+ async #createComment(input) {
752
+ return parseComment(await this.#client.request("POST", repositoryPath(input.sourceId, `issues/${requirePositiveId(input.pullRequestId, "pull request")}/comments`), { body: input.body }, { actingUserId: input.actingUserId }));
753
+ }
754
+ async #updateComment(input) {
755
+ requireGithubConnection(input.connection);
756
+ return parseComment(await this.#client.request("PATCH", repositoryPath(input.sourceId, `issues/comments/${requirePositiveId(input.commentId, "comment")}`), { body: input.body }, { actingUserId: input.actingUserId }));
757
+ }
758
+ async #deleteComment(input) {
759
+ requireGithubConnection(input.connection);
760
+ await this.#client.request("DELETE", repositoryPath(input.sourceId, `issues/comments/${requirePositiveId(input.commentId, "comment")}`), void 0, { actingUserId: input.actingUserId });
761
+ }
762
+ async #listReviews(input) {
763
+ const page = parsePositiveCursor(input.cursor);
764
+ const result = await this.#client.request("GET", `${pullRequestPath(input, input.pullRequestId)}/reviews?page=${page}&per_page=${PAGE_SIZE}`);
765
+ return {
766
+ reviews: result.reviews.map(parseReview),
767
+ nextCursor: result.reviews.length === PAGE_SIZE ? String(page + 1) : null
768
+ };
769
+ }
770
+ async #getReview(input) {
771
+ try {
772
+ return parseReview(await this.#client.request("GET", `${pullRequestPath(input, input.pullRequestId)}/reviews/${requirePositiveId(input.reviewId, "review")}`));
773
+ } catch (error) {
774
+ if (isNotFound(error)) return null;
775
+ throw error;
776
+ }
777
+ }
778
+ async #createReview(input) {
779
+ return parseReview(await this.#client.request("POST", `${pullRequestPath(input, input.pullRequestId)}/reviews`, {
780
+ body: input.body,
781
+ commitId: input.commitId,
782
+ event: input.event ? reviewEvent(input.event) : void 0
783
+ }, { actingUserId: input.actingUserId }));
784
+ }
785
+ async #updateReview(input) {
786
+ return parseReview(await this.#client.request("PUT", `${pullRequestPath(input, input.pullRequestId)}/reviews/${requirePositiveId(input.reviewId, "review")}`, { body: input.body }, { actingUserId: input.actingUserId }));
787
+ }
788
+ async #submitReview(input) {
789
+ return parseReview(await this.#client.request("POST", `${pullRequestPath(input, input.pullRequestId)}/reviews/${requirePositiveId(input.reviewId, "review")}/events`, {
790
+ body: input.body,
791
+ event: reviewEvent(input.event)
792
+ }, { actingUserId: input.actingUserId }));
793
+ }
794
+ async #dismissReview(input) {
795
+ return parseReview(await this.#client.request("PUT", `${pullRequestPath(input, input.pullRequestId)}/reviews/${requirePositiveId(input.reviewId, "review")}/dismissals`, { message: input.message }, { actingUserId: input.actingUserId }));
796
+ }
797
+ async #deletePendingReview(input) {
798
+ await this.#client.request("DELETE", `${pullRequestPath(input, input.pullRequestId)}/reviews/${requirePositiveId(input.reviewId, "review")}`, void 0, { actingUserId: input.actingUserId });
799
+ }
800
+ async #listReviewComments(input) {
801
+ const page = parsePositiveCursor(input.cursor);
802
+ const result = await this.#client.request("GET", `${pullRequestPath(input, input.pullRequestId)}/comments?page=${page}&per_page=${PAGE_SIZE}`);
803
+ return {
804
+ comments: result.comments.map(parseReviewComment),
805
+ nextCursor: result.comments.length === PAGE_SIZE ? String(page + 1) : null
806
+ };
807
+ }
808
+ async #createReviewComment(input) {
809
+ let body;
810
+ if (input.replyToId !== void 0) body = {
811
+ body: input.body,
812
+ replyToId: requirePositiveId(input.replyToId, "review comment")
813
+ };
814
+ else {
815
+ if (!input.commitId || !input.path || input.line === void 0 || !input.side) throw new Error("A review comment requires commitId, path, line, and side unless it is a reply.");
816
+ body = {
817
+ body: input.body,
818
+ commitId: input.commitId,
819
+ path: input.path,
820
+ line: input.line,
821
+ side: input.side.toUpperCase(),
822
+ startLine: input.startLine,
823
+ startSide: input.startSide?.toUpperCase()
824
+ };
825
+ }
826
+ return parseReviewComment(await this.#client.request("POST", `${pullRequestPath(input, input.pullRequestId)}/comments`, body, { actingUserId: input.actingUserId }));
827
+ }
828
+ async #updateReviewComment(input) {
829
+ requireGithubConnection(input.connection);
830
+ return parseReviewComment(await this.#client.request("PATCH", repositoryPath(input.sourceId, `pulls/comments/${requirePositiveId(input.commentId, "review comment")}`), { body: input.body }, { actingUserId: input.actingUserId }));
831
+ }
832
+ async #deleteReviewComment(input) {
833
+ requireGithubConnection(input.connection);
834
+ await this.#client.request("DELETE", repositoryPath(input.sourceId, `pulls/comments/${requirePositiveId(input.commentId, "review comment")}`), void 0, { actingUserId: input.actingUserId });
835
+ }
836
+ #requestedReviewers(method, input) {
837
+ requireGithubConnection(input.connection);
838
+ return this.#client.request(method, `${pullRequestPath(input, input.pullRequestId)}/requested-reviewers`, method === "GET" ? void 0 : {
839
+ users: input.users,
840
+ teams: input.teams
841
+ }, method === "GET" ? void 0 : { actingUserId: input.actingUserId });
842
+ }
843
+ };
844
+ function repositoryPath(sourceId, suffix) {
845
+ const { owner, repo } = splitRepository(sourceId);
846
+ return `${API_PREFIX}/github/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${suffix}`;
847
+ }
848
+ function pullRequestPath(input, pullRequestId) {
849
+ requireGithubConnection(input.connection);
850
+ return repositoryPath(input.sourceId, `pulls/${requirePositiveId(pullRequestId, "pull request")}`);
851
+ }
852
+ function splitRepository(sourceId) {
853
+ const slash = sourceId.indexOf("/");
854
+ if (slash <= 0 || slash === sourceId.length - 1) throw new Error("GitHub capabilities require an owner/repository source.");
855
+ return {
856
+ owner: sourceId.slice(0, slash),
857
+ repo: sourceId.slice(slash + 1)
858
+ };
859
+ }
860
+ function parseIntakeIssue(sourceId, issue) {
861
+ return {
862
+ id: String(issue.number),
863
+ identifier: `#${issue.number}`,
864
+ title: issue.title,
865
+ url: issue.htmlUrl,
866
+ author: issue.user?.login ?? null,
867
+ state: issue.state,
868
+ stateType: issue.state,
869
+ priority: null,
870
+ assignee: issue.assignees[0] ?? null,
871
+ assignees: issue.assignees,
872
+ source: sourceId,
873
+ labels: issue.labels,
874
+ commentCount: issue.commentCount,
875
+ createdAt: issue.createdAt,
876
+ updatedAt: issue.updatedAt
877
+ };
878
+ }
879
+ function parseIntakeIssueDetail(sourceId, issue, comments) {
880
+ return {
881
+ ...parseIntakeIssue(sourceId, issue),
882
+ description: issue.body?.trim() ? issue.body : null,
883
+ comments: comments.map((comment) => ({
884
+ author: comment.user?.login ?? null,
885
+ body: comment.body,
886
+ createdAt: comment.createdAt
887
+ }))
888
+ };
889
+ }
890
+ function parsePullRequest(pullRequest) {
891
+ return {
892
+ id: String(pullRequest.number),
893
+ title: pullRequest.title,
894
+ url: pullRequest.htmlUrl,
895
+ author: pullRequest.user?.login ?? null,
896
+ assignees: pullRequest.assignees ?? [],
897
+ requestedReviewers: pullRequest.requestedReviewers ?? [],
898
+ body: pullRequest.body?.trim() ? pullRequest.body : null,
899
+ state: pullRequest.state,
900
+ draft: pullRequest.draft,
901
+ merged: pullRequest.merged,
902
+ mergeable: pullRequest.mergeable,
903
+ baseBranch: pullRequest.base.ref,
904
+ headBranch: pullRequest.head.ref,
905
+ headSha: pullRequest.head.sha,
906
+ createdAt: pullRequest.createdAt,
907
+ updatedAt: pullRequest.updatedAt
908
+ };
909
+ }
910
+ function parseComment(comment) {
911
+ return {
912
+ id: String(comment.id),
913
+ url: comment.htmlUrl,
914
+ author: comment.user?.login ?? null,
915
+ body: comment.body,
916
+ createdAt: comment.createdAt,
917
+ updatedAt: comment.updatedAt
918
+ };
919
+ }
920
+ function parseReview(review) {
921
+ return {
922
+ id: String(review.id),
923
+ url: review.htmlUrl,
924
+ author: review.user?.login ?? null,
925
+ body: review.body?.trim() ? review.body : null,
926
+ state: {
927
+ PENDING: "pending",
928
+ COMMENTED: "commented",
929
+ APPROVED: "approved",
930
+ CHANGES_REQUESTED: "changes-requested",
931
+ DISMISSED: "dismissed"
932
+ }[review.state],
933
+ commitId: review.commitId,
934
+ submittedAt: review.submittedAt
935
+ };
936
+ }
937
+ function parseReviewComment(comment) {
938
+ return {
939
+ ...parseComment(comment),
940
+ path: comment.path,
941
+ line: comment.line,
942
+ side: comment.side?.toLowerCase(),
943
+ commitId: comment.commitId,
944
+ replyToId: comment.replyToId === null ? null : String(comment.replyToId)
945
+ };
946
+ }
947
+ function requireGithubConnection(connection) {
948
+ if (connection.type !== "app-installation" && connection.type !== "oauth") throw new Error("GitHub capabilities require a GitHub connection.");
949
+ }
950
+ function requireSingleSource(sourceIds, message) {
951
+ if (sourceIds.length !== 1) throw new Error(message);
952
+ return sourceIds[0];
953
+ }
954
+ function requireSource(sourceId, message) {
955
+ if (!sourceId) throw new Error(message);
956
+ return sourceId;
957
+ }
958
+ function normalizeLabels(labels) {
959
+ return [...new Set((labels ?? []).map((label) => label.trim()).filter(Boolean))];
960
+ }
961
+ function parsePositiveCursor(cursor) {
962
+ if (cursor === void 0) return 1;
963
+ const parsed = parsePositiveInteger(cursor);
964
+ if (parsed === null) throw new Error("GitHub cursor must be a positive page number.");
965
+ return parsed;
966
+ }
967
+ function parsePositiveInteger(value) {
968
+ if (!/^\d+$/.test(value)) return null;
969
+ const parsed = Number(value);
970
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
971
+ }
972
+ function parseGithubExternalTarget(externalId) {
973
+ const match = externalId.match(/^(.+\/.+):(\d+)$/) ?? externalId.match(/^github:(\d+):(?:issue|pull-request):(\d+)$/) ?? externalId.match(/^(\d+):(\d+)$/);
974
+ if (!match?.[1] || !match[2] || parsePositiveInteger(match[2]) === null) return null;
975
+ return {
976
+ repository: match[1],
977
+ issueId: match[2]
978
+ };
979
+ }
980
+ function optionalPositiveIntegerEnv(name) {
981
+ const value = process.env[name]?.trim();
982
+ if (!value) return void 0;
983
+ const parsed = parsePositiveInteger(value);
984
+ if (parsed === null) throw new Error(`${name} must be a positive integer.`);
985
+ return parsed;
986
+ }
987
+ function requirePositiveId(value, resource) {
988
+ const parsed = parsePositiveInteger(value);
989
+ if (parsed === null) throw new Error(`GitHub ${resource} id must be a positive integer.`);
990
+ return parsed;
991
+ }
992
+ function reviewEvent(event) {
993
+ if (event === "approve") return "APPROVE";
994
+ if (event === "request-changes") return "REQUEST_CHANGES";
995
+ return "COMMENT";
996
+ }
997
+ function isNotFound(error) {
998
+ return error instanceof PlatformApiError && error.status === 404;
999
+ }
1000
+ function isDeadInstallation(error) {
1001
+ return error instanceof PlatformApiError && (error.status === 404 || error.status === 409);
1002
+ }
1003
+ //#endregion
1004
+ export { PlatformGithubIntegration };
1005
+
1006
+ //# sourceMappingURL=integration.js.map