@mastra/factory 0.1.0-alpha.2 → 0.1.0-alpha.4

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 (85) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/factory.d.ts.map +1 -1
  3. package/dist/factory.js +812 -209
  4. package/dist/factory.js.map +1 -1
  5. package/dist/index.js +812 -209
  6. package/dist/index.js.map +1 -1
  7. package/dist/integrations/github/integration.d.ts +2 -2
  8. package/dist/integrations/github/integration.js +45 -11
  9. package/dist/integrations/github/integration.js.map +1 -1
  10. package/dist/integrations/github/provenance.js.map +1 -1
  11. package/dist/integrations/github/routes.d.ts.map +1 -1
  12. package/dist/integrations/github/routes.js +12 -9
  13. package/dist/integrations/github/routes.js.map +1 -1
  14. package/dist/integrations/github/sandbox.d.ts +1 -0
  15. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  16. package/dist/integrations/github/sandbox.js +2 -2
  17. package/dist/integrations/github/sandbox.js.map +1 -1
  18. package/dist/integrations/github/session-subscriptions.d.ts +3 -0
  19. package/dist/integrations/github/session-subscriptions.d.ts.map +1 -1
  20. package/dist/integrations/github/session-subscriptions.js +30 -0
  21. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  22. package/dist/integrations/github/token-refresh.d.ts +6 -0
  23. package/dist/integrations/github/token-refresh.d.ts.map +1 -0
  24. package/dist/integrations/github/token-refresh.js +17 -0
  25. package/dist/integrations/github/token-refresh.js.map +1 -0
  26. package/dist/integrations/platform/github/event-worker.d.ts +2 -0
  27. package/dist/integrations/platform/github/event-worker.d.ts.map +1 -1
  28. package/dist/integrations/platform/github/event-worker.js +8 -0
  29. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  30. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  31. package/dist/integrations/platform/github/integration.js +52 -9
  32. package/dist/integrations/platform/github/integration.js.map +1 -1
  33. package/dist/routes/config.d.ts +16 -11
  34. package/dist/routes/config.d.ts.map +1 -1
  35. package/dist/routes/config.js +222 -109
  36. package/dist/routes/config.js.map +1 -1
  37. package/dist/routes/custom-provider-source.d.ts +52 -0
  38. package/dist/routes/custom-provider-source.d.ts.map +1 -0
  39. package/dist/routes/custom-provider-source.js +107 -0
  40. package/dist/routes/custom-provider-source.js.map +1 -0
  41. package/dist/routes/oauth.js +33 -1
  42. package/dist/routes/oauth.js.map +1 -1
  43. package/dist/routes/surface.d.ts +4 -0
  44. package/dist/routes/surface.d.ts.map +1 -1
  45. package/dist/routes/surface.js +282 -111
  46. package/dist/routes/surface.js.map +1 -1
  47. package/dist/routes/work-items.js.map +1 -1
  48. package/dist/rules/defaults.d.ts.map +1 -1
  49. package/dist/rules/defaults.js +8 -2
  50. package/dist/rules/defaults.js.map +1 -1
  51. package/dist/rules/dispatcher.js.map +1 -1
  52. package/dist/rules/github-service.d.ts +2 -0
  53. package/dist/rules/github-service.d.ts.map +1 -1
  54. package/dist/rules/github-service.js +15 -1
  55. package/dist/rules/github-service.js.map +1 -1
  56. package/dist/rules/index.js +8 -2
  57. package/dist/rules/index.js.map +1 -1
  58. package/dist/rules/linear-service.js.map +1 -1
  59. package/dist/rules/processor.js.map +1 -1
  60. package/dist/rules/tools.js.map +1 -1
  61. package/dist/rules/transition-service.js.map +1 -1
  62. package/dist/rules/types.d.ts +5 -0
  63. package/dist/rules/types.d.ts.map +1 -1
  64. package/dist/rules/types.js.map +1 -1
  65. package/dist/rules/validation.js.map +1 -1
  66. package/dist/sandbox/fleet.d.ts +3 -0
  67. package/dist/sandbox/fleet.d.ts.map +1 -1
  68. package/dist/sandbox/fleet.js +10 -3
  69. package/dist/sandbox/fleet.js.map +1 -1
  70. package/dist/spa-static.d.ts +4 -5
  71. package/dist/spa-static.d.ts.map +1 -1
  72. package/dist/spa-static.js +5 -2
  73. package/dist/spa-static.js.map +1 -1
  74. package/dist/storage/domains/custom-providers/base.d.ts +57 -0
  75. package/dist/storage/domains/custom-providers/base.d.ts.map +1 -0
  76. package/dist/storage/domains/custom-providers/base.js +150 -0
  77. package/dist/storage/domains/custom-providers/base.js.map +1 -0
  78. package/dist/storage/domains/memory-settings/base.d.ts +62 -0
  79. package/dist/storage/domains/memory-settings/base.d.ts.map +1 -0
  80. package/dist/storage/domains/memory-settings/base.js +111 -0
  81. package/dist/storage/domains/memory-settings/base.js.map +1 -0
  82. package/dist/workspace.d.ts.map +1 -1
  83. package/dist/workspace.js +17 -0
  84. package/dist/workspace.js.map +1 -1
  85. package/package.json +7 -6
@@ -5,6 +5,7 @@ import type { IntegrationStorageHandle } from '../../../storage/domains/integrat
5
5
  import type { GithubRepositoryPermission } from '../../github/integration.js';
6
6
  import type { GithubSubscriptionStorage } from '../../github/subscriptions.js';
7
7
  import { dispatchGithubWebhook } from '../../github/webhook.js';
8
+ import type { ParsedGithubWebhook } from '../../github/webhook.js';
8
9
  import type { PlatformApiClient } from '../api-client.js';
9
10
  type EventCursor = {
10
11
  afterEventId: string;
@@ -25,6 +26,7 @@ export interface PlatformGithubEventWorkerConfig {
25
26
  controller: MountedMastraCode['controller'];
26
27
  github: PlatformGithubEventDispatchIntegration;
27
28
  storage: PlatformGithubEventStorage;
29
+ ingestFactoryEvent?: (event: ParsedGithubWebhook) => Promise<unknown>;
28
30
  intervalMs?: number;
29
31
  now?: () => number;
30
32
  dispatch?: typeof dispatchGithubWebhook;
@@ -1 +1 @@
1
- {"version":3,"file":"event-worker.d.ts","sourceRoot":"","sources":["../../../../src/integrations/platform/github/event-worker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAG1D,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+CAA+C,CAAC;AAC9F,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AAE9E,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AA0B1D,KAAK,WAAW,GAAG;IAAE,YAAY,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC;AACzE,KAAK,iCAAiC,GAAG;IACvC,OAAO,EAAE,CAAC,CAAC;IACX,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CAC3C,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG,wBAAwB,CAC/D,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvB,iCAAiC,EACjC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CACxB,CAAC;AAWF,MAAM,WAAW,sCAAsC;IACrD,QAAQ,CAAC,kBAAkB,EAAE,yBAAyB,CAAC;IACvD,mCAAmC,CACjC,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,0BAA0B,GAAG,SAAS,CAAC,CAAC;CACpD;AAED,MAAM,WAAW,+BAA+B;IAC9C,MAAM,EAAE,iBAAiB,CAAC;IAC1B,UAAU,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC;IAC5C,MAAM,EAAE,sCAAsC,CAAC;IAC/C,OAAO,EAAE,0BAA0B,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,qBAAqB,CAAC;CACzC;AAED,qBAAa,yBAA0B,SAAQ,YAAY;;IACzD,QAAQ,CAAC,IAAI,4BAA4B;gBAqB7B,MAAM,EAAE,+BAA+B;IAe7C,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IActB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAa3B,IAAI,SAAS,IAAI,OAAO,CAEvB;CA2MF"}
1
+ {"version":3,"file":"event-worker.d.ts","sourceRoot":"","sources":["../../../../src/integrations/platform/github/event-worker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAG1D,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+CAA+C,CAAC;AAC9F,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AAE9E,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAA6B,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AA0B1D,KAAK,WAAW,GAAG;IAAE,YAAY,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC;AACzE,KAAK,iCAAiC,GAAG;IACvC,OAAO,EAAE,CAAC,CAAC;IACX,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CAC3C,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG,wBAAwB,CAC/D,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvB,iCAAiC,EACjC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CACxB,CAAC;AAWF,MAAM,WAAW,sCAAsC;IACrD,QAAQ,CAAC,kBAAkB,EAAE,yBAAyB,CAAC;IACvD,mCAAmC,CACjC,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,0BAA0B,GAAG,SAAS,CAAC,CAAC;CACpD;AAED,MAAM,WAAW,+BAA+B;IAC9C,MAAM,EAAE,iBAAiB,CAAC;IAC1B,UAAU,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC;IAC5C,MAAM,EAAE,sCAAsC,CAAC;IAC/C,OAAO,EAAE,0BAA0B,CAAC;IACpC,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,qBAAqB,CAAC;CACzC;AAED,qBAAa,yBAA0B,SAAQ,YAAY;;IACzD,QAAQ,CAAC,IAAI,4BAA4B;gBAsB7B,MAAM,EAAE,+BAA+B;IAgB7C,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IActB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAa3B,IAAI,SAAS,IAAI,OAAO,CAEvB;CA8MF"}
@@ -314,6 +314,7 @@ var PlatformGithubEventWorker = class extends MastraWorker {
314
314
  #controller;
315
315
  #github;
316
316
  #storage;
317
+ #ingestFactoryEvent;
317
318
  #intervalMs;
318
319
  #now;
319
320
  #dispatch;
@@ -333,6 +334,7 @@ var PlatformGithubEventWorker = class extends MastraWorker {
333
334
  this.#controller = config.controller;
334
335
  this.#github = config.github;
335
336
  this.#storage = config.storage;
337
+ this.#ingestFactoryEvent = config.ingestFactoryEvent;
336
338
  this.#intervalMs = config.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
337
339
  if (!Number.isFinite(this.#intervalMs) || this.#intervalMs <= 0) {
338
340
  throw new Error("Platform GitHub event polling interval must be a positive number.");
@@ -490,6 +492,9 @@ var PlatformGithubEventWorker = class extends MastraWorker {
490
492
  });
491
493
  continue;
492
494
  }
495
+ if (isFactoryClosureEvent(parsed)) {
496
+ await this.#ingestFactoryEvent?.(parsed);
497
+ }
493
498
  const result = await this.#dispatch(parsed, {
494
499
  controller: this.#controller,
495
500
  listSubscriptions: (target, options) => listPullRequestSubscriptionsForWebhook(target, options, this.#github.integrationStorage),
@@ -555,6 +560,9 @@ function normalizeSettings(value) {
555
560
  }
556
561
  return { version: 1, repositories: { ...value.repositories } };
557
562
  }
563
+ function isFactoryClosureEvent(event) {
564
+ return (event.event === "issues" || event.event === "pull_request") && event.payload.action === "closed";
565
+ }
558
566
  function parseEvent(event) {
559
567
  if (!event.id || !event.deliveryId || !SUPPORTED_EVENTS.has(event.event) || !event.payload || typeof event.payload !== "object" || Array.isArray(event.payload)) {
560
568
  return null;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/integrations/platform/github/event-worker.ts","../../../../src/integrations/github/subscriptions.ts","../../../../src/integrations/github/webhook.ts","../../../../src/integrations/platform/api-client.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 { GithubRepositoryPermission } from '../../github/integration.js';\nimport { listPullRequestSubscriptionsForWebhook, retirePullRequestSubscription } from '../../github/subscriptions.js';\nimport type { GithubSubscriptionStorage } from '../../github/subscriptions.js';\nimport { dispatchGithubWebhook } from '../../github/webhook.js';\nimport type { GithubWebhookNotification, ParsedGithubWebhook } from '../../github/webhook.js';\nimport type { PlatformApiClient } from '../api-client.js';\nimport { PlatformApiError } from '../api-client.js';\n\nconst API_PREFIX = '/v1/server/github-app';\nconst DEFAULT_POLL_INTERVAL_MS = 5_000;\nconst EVENT_PAGE_SIZE = 500;\nconst MIN_LEASE_TTL_MS = 30_000;\nconst CURSOR_ORG_ID = '__platform_github_event_worker__';\nconst CURSOR_USER_ID = 'worker';\nconst SUPPORTED_EVENTS = new Set([\n 'issues',\n 'issue_comment',\n 'pull_request',\n 'pull_request_review',\n 'pull_request_review_comment',\n]);\nconst AUTHOR_GATED_KINDS = new Set([\n 'issue-comment',\n 'pull-request-comment',\n 'pull-request-review',\n 'pull-request-review-comment',\n]);\nconst AUTHORIZED_BOTS = new Set(['coderabbitai[bot]', 'devin-ai-integration[bot]']);\nconst AUTHORIZED_PERMISSIONS = new Set<GithubRepositoryPermission>(['admin', 'maintain', 'write']);\nconst PERMISSION_CHECK_TIMEOUT_MS = 5_000;\n\ntype EventCursor = { afterEventId: string } | { afterTimestamp: number };\ntype PlatformGithubEventWorkerSettings = {\n version: 1;\n repositories: Record<string, EventCursor>;\n};\n\nexport type PlatformGithubEventStorage = IntegrationStorageHandle<\n Record<string, unknown>,\n PlatformGithubEventWorkerSettings,\n Record<string, unknown>\n>;\n\ntype EventLogEntry = {\n id: string;\n deliveryId: string;\n event: string;\n payload: unknown;\n};\n\ntype Repository = { id: number };\n\nexport interface PlatformGithubEventDispatchIntegration {\n readonly integrationStorage: GithubSubscriptionStorage;\n getRepositoryCollaboratorPermission(\n installationId: number,\n repoFullName: string,\n username: string,\n signal?: AbortSignal,\n ): Promise<GithubRepositoryPermission | undefined>;\n}\n\nexport interface PlatformGithubEventWorkerConfig {\n client: PlatformApiClient;\n controller: MountedMastraCode['controller'];\n github: PlatformGithubEventDispatchIntegration;\n storage: PlatformGithubEventStorage;\n intervalMs?: number;\n now?: () => number;\n dispatch?: typeof dispatchGithubWebhook;\n}\n\nexport class PlatformGithubEventWorker extends MastraWorker {\n readonly name = 'platform-github-events';\n\n readonly #client: PlatformApiClient;\n readonly #controller: MountedMastraCode['controller'];\n readonly #github: PlatformGithubEventDispatchIntegration;\n readonly #storage: PlatformGithubEventStorage;\n readonly #intervalMs: number;\n readonly #now: () => number;\n readonly #dispatch: typeof dispatchGithubWebhook;\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: PlatformGithubEventWorkerSettings = { version: 1, repositories: {} };\n\n constructor(config: PlatformGithubEventWorkerConfig) {\n super();\n this.#client = config.client;\n this.#controller = config.controller;\n this.#github = config.github;\n this.#storage = config.storage;\n this.#intervalMs = config.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n if (!Number.isFinite(this.#intervalMs) || this.#intervalMs <= 0) {\n throw new Error('Platform GitHub 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.#dispatch = config.dispatch ?? dispatchGithubWebhook;\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('PlatformGithubEventWorker: 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 GitHub 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 GitHub 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 GitHub 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 repositories = await this.#discoverRepositories();\n let retryInMs = this.#intervalMs;\n\n for (const repository of repositories) {\n if (!this.#running || !this.#hasLease) break;\n try {\n await this.#pollRepository(repository.id);\n } catch (error) {\n const delay = retryDelay(error, this.#intervalMs);\n retryInMs = Math.max(retryInMs, delay);\n this.deps?.logger.error('Platform GitHub repository event polling failed', {\n repositoryId: repository.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\n return retryInMs;\n }\n\n async #discoverRepositories(): Promise<Repository[]> {\n const result = await this.#client.request<{\n installations: Array<{\n installationId: number;\n usable: boolean;\n suspendedAt: string | null;\n }>;\n }>('GET', `${API_PREFIX}/installations`);\n const repositories = new Map<number, Repository>();\n\n for (const installation of result.installations) {\n if (!installation.usable || installation.suspendedAt) continue;\n const page = await this.#client.request<{ repositories: Repository[] }>(\n 'GET',\n `${API_PREFIX}/installations/${installation.installationId}/repositories`,\n );\n for (const repository of page.repositories) repositories.set(repository.id, repository);\n }\n\n return [...repositories.values()];\n }\n\n async #pollRepository(repositoryId: number): Promise<void> {\n const key = String(repositoryId);\n if (!this.#settings.repositories[key]) {\n this.#settings.repositories[key] = { afterTimestamp: this.#startedAt };\n await this.#saveSettings();\n }\n\n while (this.#running && this.#hasLease) {\n const cursor: EventCursor = this.#settings.repositories[key]!;\n const query = new URLSearchParams({ limit: String(EVENT_PAGE_SIZE) });\n if ('afterEventId' in cursor) query.set('afterEventId', cursor.afterEventId);\n else query.set('afterTimestamp', String(cursor.afterTimestamp));\n\n const page = await this.#client.request<{ events: EventLogEntry[]; nextCursor: string | null }>(\n 'GET',\n `${API_PREFIX}/repositories/${repositoryId}/events?${query}`,\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 const parsed = parseEvent(event);\n if (!parsed) {\n this.deps?.logger.warn('Platform GitHub event log returned a malformed event', {\n repositoryId,\n eventId: event.id,\n });\n continue;\n }\n const result = await this.#dispatch(parsed, {\n controller: this.#controller,\n listSubscriptions: (target, options) =>\n listPullRequestSubscriptionsForWebhook(target, options, this.#github.integrationStorage),\n retireSubscription: (id, status) =>\n retirePullRequestSubscription(id, status, this.#github.integrationStorage),\n isAuthorizedSender: notification => this.#isAuthorizedSender(notification),\n onTargetError: (subscription, error) => {\n this.deps?.logger.error('Platform GitHub event delivery failed for a subscription', {\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.failed > 0) {\n throw new Error(\n `Platform GitHub event ${event.deliveryId} failed for ${result.failed} subscribed target(s).`,\n );\n }\n }\n\n if (page.nextCursor === ('afterEventId' in cursor ? cursor.afterEventId : undefined)) return;\n this.#settings.repositories[key] = { afterEventId: page.nextCursor };\n await this.#saveSettings();\n }\n }\n\n async #isAuthorizedSender(notification: GithubWebhookNotification): Promise<boolean> {\n if (!AUTHOR_GATED_KINDS.has(notification.kind)) return true;\n const sender = notification.metadata.sender;\n const repository = notification.metadata.repository;\n if (!sender || !repository) return false;\n if (AUTHORIZED_BOTS.has(sender)) return true;\n\n const abortController = new AbortController();\n const timeout = setTimeout(() => abortController.abort(), PERMISSION_CHECK_TIMEOUT_MS);\n try {\n const permission = await this.#github.getRepositoryCollaboratorPermission(\n notification.metadata.installationId,\n repository,\n sender,\n abortController.signal,\n );\n return permission !== undefined && AUTHORIZED_PERMISSIONS.has(permission);\n } catch {\n return false;\n } finally {\n clearTimeout(timeout);\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: PlatformGithubEventWorkerSettings | null): PlatformGithubEventWorkerSettings {\n if (!value || value.version !== 1 || !value.repositories || typeof value.repositories !== 'object') {\n return { version: 1, repositories: {} };\n }\n return { version: 1, repositories: { ...value.repositories } };\n}\n\nfunction parseEvent(event: EventLogEntry): ParsedGithubWebhook | null {\n if (\n !event.id ||\n !event.deliveryId ||\n !SUPPORTED_EVENTS.has(event.event) ||\n !event.payload ||\n typeof event.payload !== 'object' ||\n Array.isArray(event.payload)\n ) {\n return null;\n }\n return {\n event: event.event,\n deliveryId: event.deliveryId,\n payload: event.payload as Record<string, unknown>,\n };\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","import type { IntegrationStorageHandle, IntegrationSubscription } from '../../storage/domains/integrations/base.js';\n\nexport type GithubSignalSubscriptionSource = 'auto-gh-pr-create' | 'factory-pr-create' | 'explicit-tool';\nexport type GithubSignalSubscriptionStatus = 'open' | 'closed' | 'merged';\n\nexport interface GithubSignalSubscriptionData {\n installationExternalId: string;\n projectRepositoryId: string;\n repositoryExternalId: string;\n repositorySlug: string;\n changeRequestId: string;\n ownerId: string;\n source: GithubSignalSubscriptionSource;\n subscribedByUserId: string | null;\n}\n\nexport type GithubSignalSubscriptionRow = IntegrationSubscription<GithubSignalSubscriptionData>;\nexport type GithubSubscriptionStorage = IntegrationStorageHandle<\n Record<string, unknown>,\n Record<string, unknown>,\n GithubSignalSubscriptionData\n>;\n\nexport interface SubscribeToPullRequestInput {\n orgId: string;\n installationExternalId: string;\n projectRepositoryId: string;\n repositoryExternalId: string;\n repositorySlug: string;\n changeRequestId: string;\n sessionId: string;\n ownerId: string;\n resourceId: string;\n threadId: string;\n sessionScope?: string;\n source: GithubSignalSubscriptionSource;\n subscribedByUserId?: string;\n}\n\nexport interface ThreadSubscriptionTarget {\n orgId: string;\n resourceId: string;\n threadId: string;\n sessionScope?: string;\n}\n\nexport interface PullRequestSubscriptionTarget {\n orgId: string;\n installationExternalId: string;\n repositoryExternalId: string;\n changeRequestId: string;\n}\n\nexport type GithubWebhookPullRequestTarget = Omit<PullRequestSubscriptionTarget, 'orgId'>;\n\nexport function changeRequestTargetKey(input: GithubWebhookPullRequestTarget): string {\n return `change-request:${input.installationExternalId}:${input.repositoryExternalId}:${input.changeRequestId}`;\n}\n\nfunction sameSession(row: GithubSignalSubscriptionRow, input: SubscribeToPullRequestInput): boolean {\n return (\n row.orgId === input.orgId &&\n row.sessionId === input.sessionId &&\n row.resourceId === input.resourceId &&\n row.threadId === input.threadId &&\n (row.sessionScope ?? '') === (input.sessionScope ?? '')\n );\n}\n\nexport async function subscribeToPullRequest(\n input: SubscribeToPullRequestInput,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow> {\n const targetKey = changeRequestTargetKey(input);\n const existing = (await storage.subscriptions.listByTarget(targetKey)).find(row => sameSession(row, input));\n if (existing) {\n if (existing.status !== 'open') await storage.subscriptions.updateStatus(existing.id, 'open');\n return { ...existing, status: 'open' };\n }\n\n return storage.subscriptions.create({\n orgId: input.orgId,\n targetKey,\n sessionId: input.sessionId,\n resourceId: input.resourceId,\n threadId: input.threadId,\n sessionScope: input.sessionScope ?? '',\n status: 'open',\n data: {\n installationExternalId: input.installationExternalId,\n projectRepositoryId: input.projectRepositoryId,\n repositoryExternalId: input.repositoryExternalId,\n repositorySlug: input.repositorySlug,\n changeRequestId: input.changeRequestId,\n ownerId: input.ownerId,\n source: input.source,\n subscribedByUserId: input.subscribedByUserId ?? null,\n },\n });\n}\n\nexport async function unsubscribeFromPullRequest(\n input: SubscribeToPullRequestInput,\n storage: GithubSubscriptionStorage,\n): Promise<void> {\n const rows = await storage.subscriptions.listByTarget(changeRequestTargetKey(input));\n await Promise.all(rows.filter(row => sameSession(row, input)).map(row => storage.subscriptions.delete(row.id)));\n}\n\nexport async function listPullRequestSubscriptionsForThread(\n input: ThreadSubscriptionTarget,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow[]> {\n const rows = await storage.subscriptions.listByThread(input.resourceId, input.threadId);\n return rows.filter(\n row =>\n row.orgId === input.orgId &&\n row.resourceId === input.resourceId &&\n row.threadId === input.threadId &&\n (row.sessionScope ?? '') === (input.sessionScope ?? ''),\n );\n}\n\nexport async function listPullRequestSubscriptions(\n input: PullRequestSubscriptionTarget,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow[]> {\n const rows = await storage.subscriptions.listByTarget(changeRequestTargetKey(input));\n return rows.filter(row => row.orgId === input.orgId && row.status === 'open');\n}\n\nexport async function listPullRequestSubscriptionsForWebhook(\n input: GithubWebhookPullRequestTarget,\n options: { includeTerminal?: boolean } | undefined,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow[]> {\n const rows = await storage.subscriptions.listByTarget(changeRequestTargetKey(input));\n return options?.includeTerminal ? rows : rows.filter(row => row.status === 'open');\n}\n\nexport function retirePullRequestSubscription(\n id: string,\n status: GithubSignalSubscriptionStatus,\n storage: GithubSubscriptionStorage,\n): Promise<void> {\n return storage.subscriptions.updateStatus(id, status);\n}\n\nexport async function retirePullRequestSubscriptions(\n input: PullRequestSubscriptionTarget,\n storage: GithubSubscriptionStorage,\n): Promise<void> {\n const rows = await listPullRequestSubscriptions(input, storage);\n await Promise.all(rows.map(row => storage.subscriptions.updateStatus(row.id, 'closed')));\n}\n","import { createHmac, timingSafeEqual } from 'node:crypto';\nimport type { MountedMastraCode } from '@mastra/code-sdk';\nimport type { NotificationPriority } from '@mastra/core/notifications';\nimport type { Context } from 'hono';\nimport type { GithubIntegration } from './integration.js';\nimport type { GithubIssueTriageInput, GithubIssueTriageResult } from './issue-triage.js';\nimport { listPullRequestSubscriptionsForWebhook, retirePullRequestSubscription } from './subscriptions.js';\nimport type { GithubSignalSubscriptionRow, GithubWebhookPullRequestTarget } from './subscriptions.js';\n\nexport type GithubIssueTriageRunInput = GithubIssueTriageInput;\nexport type GithubIssueTriageRunResult = GithubIssueTriageResult;\n\nexport interface GithubWebhookHandlerOptions {\n /** Integration providing webhook-secret verification + collaborator permission checks. */\n github: GithubIntegration;\n runIssueTriage?: (input: GithubIssueTriageRunInput) => Promise<GithubIssueTriageRunResult>;\n ingestFactoryEvent?: (event: ParsedGithubWebhook) => Promise<unknown>;\n}\n\nconst SUPPORTED_GITHUB_WEBHOOK_EVENTS = new Set([\n 'issues',\n 'issue_comment',\n 'pull_request',\n 'pull_request_review',\n 'pull_request_review_comment',\n]);\n\nexport interface GithubWebhookMetadata {\n event: string;\n action?: string;\n deliveryId: string;\n repository?: string;\n repositoryId?: number;\n issueNumber?: number;\n pullRequestNumber?: number;\n sender?: string;\n senderType?: string;\n installationId?: number;\n}\n\nexport interface ParsedGithubWebhook {\n event: string;\n deliveryId: string;\n payload: Record<string, unknown>;\n}\n\nexport type GithubWebhookResult =\n | { status: 202; body: { ok: true; ignored?: true } }\n | { status: 400; body: { error: 'bad_request'; message: string } }\n | { status: 401; body: { error: 'unauthorized'; message: string } };\n\nexport interface GithubWebhookNotification {\n action: string;\n kind: string;\n priority: NotificationPriority;\n summary: string;\n terminal: boolean;\n metadata: GithubWebhookMetadata & { pullRequestNumber: number; repositoryId: number; installationId: number };\n payload: Record<string, unknown>;\n}\n\nexport interface GithubWebhookDispatchDependencies {\n controller: MountedMastraCode['controller'];\n /**\n * Integration used by the default sender-authorization check (collaborator\n * permission lookup). Author-gated notifications fail closed when neither\n * this nor an `isAuthorizedSender` override is supplied.\n */\n github?: GithubIntegration;\n listSubscriptions?: (\n target: GithubWebhookPullRequestTarget,\n options?: { includeTerminal?: boolean },\n ) => Promise<GithubSignalSubscriptionRow[]>;\n retireSubscription?: (id: string, status: 'open' | 'closed' | 'merged') => Promise<void>;\n isAuthorizedSender?: (notification: GithubWebhookNotification) => Promise<boolean>;\n onTargetError?: (subscription: GithubSignalSubscriptionRow, error: unknown) => void;\n}\n\nfunction normalizeHeader(value: string | undefined | null): string | null {\n if (!value) return null;\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : null;\n}\n\nfunction verifySignature(rawBody: string, signature: string, secret: string): boolean {\n if (!signature.startsWith('sha256=')) return false;\n const signatureHex = signature.slice('sha256='.length);\n if (!/^[a-fA-F0-9]{64}$/.test(signatureHex)) return false;\n\n const expectedHex = createHmac('sha256', secret).update(rawBody).digest('hex');\n const received = Buffer.from(signatureHex, 'hex');\n const expected = Buffer.from(expectedHex, 'hex');\n return received.length === expected.length && timingSafeEqual(received, expected);\n}\n\nasync function parseGithubWebhook(\n c: Context,\n secret: string | undefined,\n): Promise<ParsedGithubWebhook | GithubWebhookResult> {\n if (!secret) {\n return { status: 401, body: { error: 'unauthorized', message: 'GitHub webhook secret is not configured' } };\n }\n\n const event = normalizeHeader(c.req.header('x-github-event'));\n const deliveryId = normalizeHeader(c.req.header('x-github-delivery'));\n const signature = normalizeHeader(c.req.header('x-hub-signature-256'));\n\n if (!event) return { status: 400, body: { error: 'bad_request', message: 'Missing x-github-event header' } };\n if (!deliveryId) return { status: 400, body: { error: 'bad_request', message: 'Missing x-github-delivery header' } };\n if (!signature)\n return { status: 401, body: { error: 'unauthorized', message: 'Missing x-hub-signature-256 header' } };\n\n const rawBody = await c.req.text();\n if (!verifySignature(rawBody, signature, secret)) {\n return { status: 401, body: { error: 'unauthorized', message: 'Invalid GitHub webhook signature' } };\n }\n\n let payload: unknown;\n try {\n payload = JSON.parse(rawBody);\n } catch {\n return { status: 400, body: { error: 'bad_request', message: 'Malformed JSON payload' } };\n }\n\n if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {\n return { status: 400, body: { error: 'bad_request', message: 'Payload must be a JSON object' } };\n }\n\n return { event, deliveryId, payload: payload as Record<string, unknown> };\n}\n\nfunction getObject(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;\n}\n\nfunction getString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction getNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction getBoolean(value: unknown): boolean | undefined {\n return typeof value === 'boolean' ? value : undefined;\n}\n\nfunction getLabels(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value\n .map(label => (typeof label === 'string' ? label : getString(getObject(label)?.name)))\n .filter((label): label is string => Boolean(label));\n}\n\nfunction getIssueTriageRunInput(parsed: ParsedGithubWebhook): GithubIssueTriageRunInput | null {\n if (parsed.event !== 'issues' || getString(parsed.payload.action) !== 'opened') return null;\n const repository = getString(getObject(parsed.payload.repository)?.full_name);\n const issue = getObject(parsed.payload.issue);\n const sender = getString(getObject(parsed.payload.sender)?.login);\n const installationId = getNumber(getObject(parsed.payload.installation)?.id);\n const issueNumber = getNumber(issue?.number);\n const issueTitle = getString(issue?.title);\n const issueUrl = getString(issue?.html_url);\n if (!repository || !installationId || !issueNumber || !issueTitle || !issueUrl) return null;\n return {\n repository,\n issueNumber,\n issueTitle,\n issueUrl,\n labels: getLabels(issue?.labels),\n sender,\n installationId,\n };\n}\n\nexport function normalizeGithubWebhookMetadata(parsed: ParsedGithubWebhook): GithubWebhookMetadata {\n const { event, deliveryId, payload } = parsed;\n const repository = getObject(payload.repository);\n const issue = getObject(payload.issue);\n const pullRequest = getObject(payload.pull_request);\n const sender = getObject(payload.sender);\n const installation = getObject(payload.installation);\n const issuePullRequest = getObject(issue?.pull_request);\n\n return {\n event,\n action: getString(payload.action),\n deliveryId,\n repository: getString(repository?.full_name),\n repositoryId: getNumber(repository?.id),\n issueNumber: getNumber(issue?.number),\n pullRequestNumber:\n getNumber(pullRequest?.number) ??\n (event === 'issue_comment' && issuePullRequest ? getNumber(issue?.number) : undefined),\n sender: getString(sender?.login),\n senderType: getString(sender?.type),\n installationId: getNumber(installation?.id),\n };\n}\n\nfunction notificationSummary(metadata: GithubWebhookMetadata, label: string): string {\n const actor = metadata.sender ? `${metadata.sender} ` : '';\n return `${actor}${label} on ${metadata.repository}#${metadata.pullRequestNumber}`;\n}\n\nfunction notificationTargetUrl(event: string, payload: Record<string, unknown>): string | undefined {\n if (event === 'issue_comment' || event === 'pull_request_review_comment') {\n return getString(getObject(payload.comment)?.html_url);\n }\n if (event === 'pull_request_review') {\n return getString(getObject(payload.review)?.html_url);\n }\n return getString(getObject(payload.pull_request)?.html_url);\n}\n\nexport function classifyGithubWebhook(parsed: ParsedGithubWebhook): GithubWebhookNotification | undefined {\n const metadata = normalizeGithubWebhookMetadata(parsed);\n const { event, payload } = parsed;\n const action = metadata.action;\n if (\n !action ||\n !metadata.repositoryId ||\n !metadata.installationId ||\n !metadata.pullRequestNumber ||\n !metadata.repository\n ) {\n return undefined;\n }\n\n let priority: NotificationPriority;\n let kind: string;\n let label: string;\n let terminal = false;\n\n if (event === 'pull_request_review' && action === 'submitted') {\n const state = getString(getObject(payload.review)?.state)?.toLowerCase().replaceAll('_', '-');\n priority = state === 'approved' || state === 'changes-requested' ? 'urgent' : 'high';\n kind =\n state === 'approved'\n ? 'review-approved'\n : state === 'changes-requested'\n ? 'review-changes-requested'\n : 'review-submitted';\n label =\n state === 'approved'\n ? 'approved the pull request'\n : state === 'changes-requested'\n ? 'requested changes'\n : 'submitted a review';\n } else if (event === 'pull_request' && action === 'closed') {\n const merged = getBoolean(getObject(payload.pull_request)?.merged) === true;\n priority = 'urgent';\n kind = merged ? 'pull-request-merged' : 'pull-request-closed';\n label = merged ? 'merged the pull request' : 'closed the pull request';\n terminal = true;\n } else if (event === 'issue_comment' && action === 'created') {\n priority = 'high';\n kind = 'issue-comment-created';\n label = 'commented';\n } else if (event === 'pull_request_review_comment' && action === 'created') {\n priority = 'high';\n kind = 'review-comment-created';\n label = 'left a review comment';\n } else if (event === 'pull_request' && action === 'reopened') {\n priority = 'high';\n kind = 'pull-request-reopened';\n label = 'reopened the pull request';\n } else if (event === 'pull_request_review' && action === 'dismissed') {\n priority = 'high';\n kind = 'review-dismissed';\n label = 'dismissed a review';\n } else if (\n event === 'pull_request' &&\n [\n 'synchronize',\n 'ready_for_review',\n 'converted_to_draft',\n 'assigned',\n 'unassigned',\n 'review_requested',\n 'review_request_removed',\n ].includes(action)\n ) {\n priority = 'medium';\n kind = `pull-request-${action.replaceAll('_', '-')}`;\n label = action.replaceAll('_', ' ');\n } else if (\n event === 'pull_request' &&\n ['edited', 'labeled', 'unlabeled', 'milestoned', 'demilestoned'].includes(action)\n ) {\n priority = 'low';\n kind = `pull-request-${action.replaceAll('_', '-')}`;\n label = action.replaceAll('_', ' ');\n } else {\n return undefined;\n }\n\n return {\n action,\n kind,\n priority,\n summary: notificationSummary(metadata, label),\n terminal,\n metadata: {\n ...metadata,\n pullRequestNumber: metadata.pullRequestNumber,\n repositoryId: metadata.repositoryId,\n installationId: metadata.installationId,\n },\n payload,\n };\n}\n\nasync function resolveSubscriptionSession(\n controller: MountedMastraCode['controller'],\n subscription: GithubSignalSubscriptionRow,\n) {\n const { sessionId, resourceId, threadId } = subscription;\n if (!sessionId || !resourceId || !threadId) {\n throw new Error(`GitHub subscription ${subscription.id} is missing its session binding.`);\n }\n const scope = subscription.sessionScope || undefined;\n let session = await controller.getSessionByResource(resourceId, scope);\n if (!session) {\n const tags = {\n factoryProjectId: resourceId,\n projectRepositoryId: subscription.data.projectRepositoryId,\n ...(scope ? { worktreePath: scope } : {}),\n };\n session = await controller.createSession({\n id: sessionId,\n ownerId: subscription.data.ownerId,\n resourceId,\n scope,\n tags,\n });\n }\n if (session.thread.getId() !== threadId) {\n await session.thread.switch({ threadId, emitEvent: false });\n }\n if (session.thread.getId() !== threadId) {\n throw new Error(`Session ${sessionId} did not bind thread ${threadId}.`);\n }\n return session;\n}\n\nconst AUTHORIZED_BOTS = new Set(['coderabbitai[bot]', 'devin-ai-integration[bot]']);\nconst AUTHORIZED_PERMISSIONS = new Set(['admin', 'maintain', 'write']);\nconst PERMISSION_CHECK_TIMEOUT_MS = 5_000;\nconst AUTHOR_GATED_KINDS = new Set([\n 'issue-comment-created',\n 'review-comment-created',\n 'review-submitted',\n 'review-approved',\n 'review-changes-requested',\n 'review-dismissed',\n]);\n\nasync function isAuthorizedGithubSender(\n notification: GithubWebhookNotification,\n github: GithubIntegration | undefined,\n): Promise<boolean> {\n if (!AUTHOR_GATED_KINDS.has(notification.kind)) return true;\n const sender = notification.metadata.sender;\n const repository = notification.metadata.repository;\n if (!sender || !repository) return false;\n const normalizedSender = sender.toLowerCase();\n if (notification.metadata.senderType?.toLowerCase() === 'bot' || normalizedSender.endsWith('[bot]')) {\n return AUTHORIZED_BOTS.has(normalizedSender);\n }\n if (!github) return false;\n const abortController = new AbortController();\n let timeout: ReturnType<typeof setTimeout> | undefined;\n try {\n const permission = await Promise.race([\n github.getRepositoryCollaboratorPermission(\n notification.metadata.installationId,\n repository,\n sender,\n abortController.signal,\n ),\n new Promise<undefined>(resolve => {\n timeout = setTimeout(() => {\n abortController.abort();\n resolve(undefined);\n }, PERMISSION_CHECK_TIMEOUT_MS);\n }),\n ]);\n return permission !== undefined && AUTHORIZED_PERMISSIONS.has(permission);\n } catch {\n return false;\n } finally {\n if (timeout) clearTimeout(timeout);\n }\n}\n\nexport async function dispatchGithubWebhook(\n parsed: ParsedGithubWebhook,\n dependencies: GithubWebhookDispatchDependencies,\n): Promise<{ delivered: number; failed: number; ignored: boolean }> {\n const notification = classifyGithubWebhook(parsed);\n if (!notification) return { delivered: 0, failed: 0, ignored: true };\n const isAuthorizedSender =\n dependencies.isAuthorizedSender ??\n ((n: GithubWebhookNotification) => isAuthorizedGithubSender(n, dependencies.github));\n if (!(await isAuthorizedSender(notification))) {\n return { delivered: 0, failed: 0, ignored: true };\n }\n\n const target = {\n installationExternalId: notification.metadata.installationId.toString(),\n repositoryExternalId: notification.metadata.repositoryId.toString(),\n changeRequestId: notification.metadata.pullRequestNumber.toString(),\n };\n const listSubscriptions =\n dependencies.listSubscriptions ??\n ((subscriptionTarget: GithubWebhookPullRequestTarget, options?: { includeTerminal?: boolean }) => {\n if (!dependencies.github) throw new Error('GitHub integration is required to load webhook subscriptions.');\n return listPullRequestSubscriptionsForWebhook(\n subscriptionTarget,\n options,\n dependencies.github.integrationStorage,\n );\n });\n const retireSubscription =\n dependencies.retireSubscription ??\n ((id: string, status: 'open' | 'closed' | 'merged') => {\n if (!dependencies.github) throw new Error('GitHub integration is required to retire webhook subscriptions.');\n return retirePullRequestSubscription(id, status, dependencies.github.integrationStorage);\n });\n const subscriptions = await listSubscriptions(target, { includeTerminal: notification.action === 'reopened' });\n let delivered = 0;\n let failed = 0;\n\n for (const subscription of subscriptions) {\n try {\n const session = await resolveSubscriptionSession(dependencies.controller, subscription);\n const result = await session.sendNotificationSignal({\n source: 'github',\n kind: notification.kind,\n summary: notification.summary,\n priority: notification.priority,\n payload: notification.payload,\n sourceId: parsed.deliveryId,\n dedupeKey: `${parsed.deliveryId}:${subscription.sessionId}:${subscription.threadId}`,\n coalesceKey: `github:${subscription.data.repositoryExternalId}:pull-request:${subscription.data.changeRequestId}`,\n metadata: {\n event: notification.metadata.event,\n action: notification.action,\n repository: notification.metadata.repository,\n issueNumber: notification.metadata.issueNumber,\n pullRequestNumber: notification.metadata.pullRequestNumber,\n targetUrl: notificationTargetUrl(parsed.event, parsed.payload),\n deliveryId: parsed.deliveryId,\n },\n });\n await Promise.all([result.persisted, result.accepted].filter(Boolean));\n if (notification.terminal) {\n await retireSubscription(subscription.id, notification.kind === 'pull-request-merged' ? 'merged' : 'closed');\n } else if (notification.action === 'reopened') {\n await retireSubscription(subscription.id, 'open');\n }\n delivered += 1;\n } catch (error) {\n failed += 1;\n dependencies.onTargetError?.(subscription, error);\n }\n }\n\n return { delivered, failed, ignored: false };\n}\n\nexport async function handleGithubWebhook(\n c: Context,\n options: GithubWebhookHandlerOptions & Partial<Omit<GithubWebhookDispatchDependencies, 'github'>>,\n): Promise<GithubWebhookResult> {\n const parsed = await parseGithubWebhook(c, options.github.webhookSecret);\n if ('status' in parsed) return parsed;\n\n if (!SUPPORTED_GITHUB_WEBHOOK_EVENTS.has(parsed.event)) {\n return { status: 202, body: { ok: true, ignored: true } };\n }\n\n const metadata = normalizeGithubWebhookMetadata(parsed);\n console.info('[GitHub Webhook]', metadata);\n\n if (options.ingestFactoryEvent) {\n await options.ingestFactoryEvent(parsed);\n } else {\n const issueTriageRun = getIssueTriageRunInput(parsed);\n if (issueTriageRun && options.runIssueTriage) {\n void options.runIssueTriage(issueTriageRun).catch((error: unknown) => {\n console.error('[GitHub Webhook] Failed to run issue triage', {\n deliveryId: metadata.deliveryId,\n repository: metadata.repository,\n issueNumber: metadata.issueNumber,\n error: error instanceof Error ? error.message : String(error),\n });\n });\n }\n }\n\n if (!options.controller) {\n return { status: 202, body: { ok: true } };\n }\n\n const result = await dispatchGithubWebhook(parsed, options as GithubWebhookDispatchDependencies);\n if (result.failed > 0) {\n console.warn(`[GitHub Webhook] ${result.failed} subscribed target(s) failed for delivery ${parsed.deliveryId}.`);\n }\n return { status: 202, body: { ok: true, ...(result.ignored ? { ignored: true as const } : {}) } };\n}\n","export interface PlatformApiClientConfig {\n baseUrl: string;\n accessToken: string;\n fetchImpl?: typeof fetch;\n}\n\nexport function platformApiClientConfigFromEnv(): PlatformApiClientConfig {\n const sharedApiUrl = process.env.MASTRA_SHARED_API_URL?.trim() || 'https://platform.mastra.ai/v1';\n const accessToken = process.env.MASTRA_PLATFORM_SECRET_KEY?.trim();\n if (!accessToken) {\n throw new Error('Platform integration: missing required environment variable MASTRA_PLATFORM_SECRET_KEY.');\n }\n return { baseUrl: normalizeSharedApiUrl(sharedApiUrl), accessToken };\n}\n\nfunction normalizeSharedApiUrl(sharedApiUrl: string): string {\n return sharedApiUrl.replace(/\\/+$/, '').replace(/\\/v1$/, '');\n}\n\nexport class PlatformApiError extends Error {\n readonly status: number;\n readonly retryAfterSeconds: number | null;\n\n constructor(message: string, status: number, retryAfterSeconds: number | null = null) {\n super(message);\n this.name = 'PlatformApiError';\n this.status = status;\n this.retryAfterSeconds = retryAfterSeconds;\n }\n}\n\nexport class PlatformApiClient {\n readonly #baseUrl: string;\n readonly #accessToken: string;\n readonly #fetch: typeof fetch;\n\n constructor(config: PlatformApiClientConfig) {\n const missing = ['baseUrl', 'accessToken'].filter(field => !config[field as keyof PlatformApiClientConfig]);\n if (missing.length > 0) {\n throw new Error(`Platform integration: missing required config field(s): ${missing.join(', ')}.`);\n }\n this.#baseUrl = config.baseUrl.replace(/\\/+$/, '');\n this.#accessToken = config.accessToken;\n this.#fetch = config.fetchImpl ?? globalThis.fetch;\n }\n\n async request<T>(method: string, path: string, body?: unknown, options?: { signal?: AbortSignal }): Promise<T> {\n const response = await this.#send(method, path, body, options);\n if (!response.ok) {\n const message = redact(await extractError(response), this.#accessToken);\n const retryAfterSeconds = parseRetryAfter(response.headers.get('retry-after'));\n logPlatformError('Platform API request failed', {\n method,\n path,\n status: response.status,\n retryAfterSeconds,\n message,\n });\n throw new PlatformApiError(message, response.status, retryAfterSeconds);\n }\n if (response.status === 204) return undefined as T;\n return (await response.json()) as T;\n }\n\n async requestRedirect(method: string, path: string, options?: { signal?: AbortSignal }): Promise<string> {\n const response = await this.#send(method, path, undefined, options, 'manual');\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get('location');\n if (location) return location;\n }\n if (!response.ok) {\n const message = redact(await extractError(response), this.#accessToken);\n const retryAfterSeconds = parseRetryAfter(response.headers.get('retry-after'));\n logPlatformError('Platform API redirect request failed', {\n method,\n path,\n status: response.status,\n retryAfterSeconds,\n message,\n });\n throw new PlatformApiError(message, response.status, retryAfterSeconds);\n }\n logPlatformError('Platform API request did not return a redirect', { method, path, status: response.status });\n throw new PlatformApiError('Platform API request did not return a redirect.', response.status);\n }\n\n async #send(\n method: string,\n path: string,\n body?: unknown,\n options?: { signal?: AbortSignal },\n redirect?: RequestInit['redirect'],\n ): Promise<Response> {\n const headers: Record<string, string> = {\n accept: 'application/json',\n authorization: `Bearer ${this.#accessToken}`,\n };\n const timeoutSignal = AbortSignal.timeout(15_000);\n const init: RequestInit = {\n method,\n headers,\n redirect,\n signal: options?.signal ? AbortSignal.any([options.signal, timeoutSignal]) : timeoutSignal,\n };\n if (body !== undefined) {\n headers['content-type'] = 'application/json';\n init.body = JSON.stringify(body);\n }\n\n try {\n return await this.#fetch(`${this.#baseUrl}${path}`, init);\n } catch (error) {\n if (error instanceof Error && error.message.includes(this.#accessToken)) {\n const redacted = new Error(redact(error.message, this.#accessToken));\n redacted.name = error.name;\n logPlatformError('Platform API transport error', {\n method,\n path,\n name: redacted.name,\n message: redacted.message,\n });\n throw redacted;\n }\n logPlatformError('Platform API transport error', {\n method,\n path,\n name: error instanceof Error ? error.name : undefined,\n message: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n }\n}\n\nasync function extractError(response: Response): Promise<string> {\n try {\n const data = (await response.clone().json()) as Record<string, unknown>;\n for (const field of ['detail', 'error', 'title']) {\n if (typeof data[field] === 'string' && data[field]) return data[field];\n }\n } catch {\n // Fall through to the status-based message.\n }\n return `Platform API request failed (${response.status})`;\n}\n\nfunction redact(message: string, accessToken: string): string {\n return message.split(accessToken).join('[REDACTED]');\n}\n\nexport function logPlatformInfo(message: string, fields?: Record<string, unknown>): void {\n writePlatformLog('info', message, fields);\n}\n\nexport function logPlatformError(message: string, fields?: Record<string, unknown>): void {\n writePlatformLog('error', message, fields);\n}\n\nfunction writePlatformLog(level: 'info' | 'error', message: string, fields?: Record<string, unknown>): void {\n const metadata = fields ? ` ${JSON.stringify(stripUndefined(fields))}` : '';\n process.stderr.write(`[MastraCode Web] ${level.toUpperCase()} ${message}${metadata}\\n`);\n}\n\nfunction stripUndefined(fields: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));\n}\n\nfunction parseRetryAfter(value: string | null): number | null {\n if (!value) return null;\n const seconds = Number.parseInt(value, 10);\n return Number.isSafeInteger(seconds) && seconds >= 0 ? seconds : null;\n}\n"],"mappings":"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","names":["AUTHOR_GATED_KINDS","AUTHORIZED_BOTS","AUTHORIZED_PERMISSIONS","PERMISSION_CHECK_TIMEOUT_MS"]}
1
+ {"version":3,"sources":["../../../../src/integrations/platform/github/event-worker.ts","../../../../src/integrations/github/subscriptions.ts","../../../../src/integrations/github/webhook.ts","../../../../src/integrations/platform/api-client.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 { GithubRepositoryPermission } from '../../github/integration.js';\nimport { listPullRequestSubscriptionsForWebhook, retirePullRequestSubscription } from '../../github/subscriptions.js';\nimport type { GithubSubscriptionStorage } from '../../github/subscriptions.js';\nimport { dispatchGithubWebhook } from '../../github/webhook.js';\nimport type { GithubWebhookNotification, ParsedGithubWebhook } from '../../github/webhook.js';\nimport type { PlatformApiClient } from '../api-client.js';\nimport { PlatformApiError } from '../api-client.js';\n\nconst API_PREFIX = '/v1/server/github-app';\nconst DEFAULT_POLL_INTERVAL_MS = 5_000;\nconst EVENT_PAGE_SIZE = 500;\nconst MIN_LEASE_TTL_MS = 30_000;\nconst CURSOR_ORG_ID = '__platform_github_event_worker__';\nconst CURSOR_USER_ID = 'worker';\nconst SUPPORTED_EVENTS = new Set([\n 'issues',\n 'issue_comment',\n 'pull_request',\n 'pull_request_review',\n 'pull_request_review_comment',\n]);\nconst AUTHOR_GATED_KINDS = new Set([\n 'issue-comment',\n 'pull-request-comment',\n 'pull-request-review',\n 'pull-request-review-comment',\n]);\nconst AUTHORIZED_BOTS = new Set(['coderabbitai[bot]', 'devin-ai-integration[bot]']);\nconst AUTHORIZED_PERMISSIONS = new Set<GithubRepositoryPermission>(['admin', 'maintain', 'write']);\nconst PERMISSION_CHECK_TIMEOUT_MS = 5_000;\n\ntype EventCursor = { afterEventId: string } | { afterTimestamp: number };\ntype PlatformGithubEventWorkerSettings = {\n version: 1;\n repositories: Record<string, EventCursor>;\n};\n\nexport type PlatformGithubEventStorage = IntegrationStorageHandle<\n Record<string, unknown>,\n PlatformGithubEventWorkerSettings,\n Record<string, unknown>\n>;\n\ntype EventLogEntry = {\n id: string;\n deliveryId: string;\n event: string;\n payload: unknown;\n};\n\ntype Repository = { id: number };\n\nexport interface PlatformGithubEventDispatchIntegration {\n readonly integrationStorage: GithubSubscriptionStorage;\n getRepositoryCollaboratorPermission(\n installationId: number,\n repoFullName: string,\n username: string,\n signal?: AbortSignal,\n ): Promise<GithubRepositoryPermission | undefined>;\n}\n\nexport interface PlatformGithubEventWorkerConfig {\n client: PlatformApiClient;\n controller: MountedMastraCode['controller'];\n github: PlatformGithubEventDispatchIntegration;\n storage: PlatformGithubEventStorage;\n ingestFactoryEvent?: (event: ParsedGithubWebhook) => Promise<unknown>;\n intervalMs?: number;\n now?: () => number;\n dispatch?: typeof dispatchGithubWebhook;\n}\n\nexport class PlatformGithubEventWorker extends MastraWorker {\n readonly name = 'platform-github-events';\n\n readonly #client: PlatformApiClient;\n readonly #controller: MountedMastraCode['controller'];\n readonly #github: PlatformGithubEventDispatchIntegration;\n readonly #storage: PlatformGithubEventStorage;\n readonly #ingestFactoryEvent: ((event: ParsedGithubWebhook) => Promise<unknown>) | undefined;\n readonly #intervalMs: number;\n readonly #now: () => number;\n readonly #dispatch: typeof dispatchGithubWebhook;\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: PlatformGithubEventWorkerSettings = { version: 1, repositories: {} };\n\n constructor(config: PlatformGithubEventWorkerConfig) {\n super();\n this.#client = config.client;\n this.#controller = config.controller;\n this.#github = config.github;\n this.#storage = config.storage;\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 GitHub 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.#dispatch = config.dispatch ?? dispatchGithubWebhook;\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('PlatformGithubEventWorker: 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 GitHub 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 GitHub 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 GitHub 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 repositories = await this.#discoverRepositories();\n let retryInMs = this.#intervalMs;\n\n for (const repository of repositories) {\n if (!this.#running || !this.#hasLease) break;\n try {\n await this.#pollRepository(repository.id);\n } catch (error) {\n const delay = retryDelay(error, this.#intervalMs);\n retryInMs = Math.max(retryInMs, delay);\n this.deps?.logger.error('Platform GitHub repository event polling failed', {\n repositoryId: repository.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\n return retryInMs;\n }\n\n async #discoverRepositories(): Promise<Repository[]> {\n const result = await this.#client.request<{\n installations: Array<{\n installationId: number;\n usable: boolean;\n suspendedAt: string | null;\n }>;\n }>('GET', `${API_PREFIX}/installations`);\n const repositories = new Map<number, Repository>();\n\n for (const installation of result.installations) {\n if (!installation.usable || installation.suspendedAt) continue;\n const page = await this.#client.request<{ repositories: Repository[] }>(\n 'GET',\n `${API_PREFIX}/installations/${installation.installationId}/repositories`,\n );\n for (const repository of page.repositories) repositories.set(repository.id, repository);\n }\n\n return [...repositories.values()];\n }\n\n async #pollRepository(repositoryId: number): Promise<void> {\n const key = String(repositoryId);\n if (!this.#settings.repositories[key]) {\n this.#settings.repositories[key] = { afterTimestamp: this.#startedAt };\n await this.#saveSettings();\n }\n\n while (this.#running && this.#hasLease) {\n const cursor: EventCursor = this.#settings.repositories[key]!;\n const query = new URLSearchParams({ limit: String(EVENT_PAGE_SIZE) });\n if ('afterEventId' in cursor) query.set('afterEventId', cursor.afterEventId);\n else query.set('afterTimestamp', String(cursor.afterTimestamp));\n\n const page = await this.#client.request<{ events: EventLogEntry[]; nextCursor: string | null }>(\n 'GET',\n `${API_PREFIX}/repositories/${repositoryId}/events?${query}`,\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 const parsed = parseEvent(event);\n if (!parsed) {\n this.deps?.logger.warn('Platform GitHub event log returned a malformed event', {\n repositoryId,\n eventId: event.id,\n });\n continue;\n }\n if (isFactoryClosureEvent(parsed)) {\n await this.#ingestFactoryEvent?.(parsed);\n }\n const result = await this.#dispatch(parsed, {\n controller: this.#controller,\n listSubscriptions: (target, options) =>\n listPullRequestSubscriptionsForWebhook(target, options, this.#github.integrationStorage),\n retireSubscription: (id, status) =>\n retirePullRequestSubscription(id, status, this.#github.integrationStorage),\n isAuthorizedSender: notification => this.#isAuthorizedSender(notification),\n onTargetError: (subscription, error) => {\n this.deps?.logger.error('Platform GitHub event delivery failed for a subscription', {\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.failed > 0) {\n throw new Error(\n `Platform GitHub event ${event.deliveryId} failed for ${result.failed} subscribed target(s).`,\n );\n }\n }\n\n if (page.nextCursor === ('afterEventId' in cursor ? cursor.afterEventId : undefined)) return;\n this.#settings.repositories[key] = { afterEventId: page.nextCursor };\n await this.#saveSettings();\n }\n }\n\n async #isAuthorizedSender(notification: GithubWebhookNotification): Promise<boolean> {\n if (!AUTHOR_GATED_KINDS.has(notification.kind)) return true;\n const sender = notification.metadata.sender;\n const repository = notification.metadata.repository;\n if (!sender || !repository) return false;\n if (AUTHORIZED_BOTS.has(sender)) return true;\n\n const abortController = new AbortController();\n const timeout = setTimeout(() => abortController.abort(), PERMISSION_CHECK_TIMEOUT_MS);\n try {\n const permission = await this.#github.getRepositoryCollaboratorPermission(\n notification.metadata.installationId,\n repository,\n sender,\n abortController.signal,\n );\n return permission !== undefined && AUTHORIZED_PERMISSIONS.has(permission);\n } catch {\n return false;\n } finally {\n clearTimeout(timeout);\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: PlatformGithubEventWorkerSettings | null): PlatformGithubEventWorkerSettings {\n if (!value || value.version !== 1 || !value.repositories || typeof value.repositories !== 'object') {\n return { version: 1, repositories: {} };\n }\n return { version: 1, repositories: { ...value.repositories } };\n}\n\nfunction isFactoryClosureEvent(event: ParsedGithubWebhook): boolean {\n return (event.event === 'issues' || event.event === 'pull_request') && event.payload.action === 'closed';\n}\n\nfunction parseEvent(event: EventLogEntry): ParsedGithubWebhook | null {\n if (\n !event.id ||\n !event.deliveryId ||\n !SUPPORTED_EVENTS.has(event.event) ||\n !event.payload ||\n typeof event.payload !== 'object' ||\n Array.isArray(event.payload)\n ) {\n return null;\n }\n return {\n event: event.event,\n deliveryId: event.deliveryId,\n payload: event.payload as Record<string, unknown>,\n };\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","import type { IntegrationStorageHandle, IntegrationSubscription } from '../../storage/domains/integrations/base.js';\n\nexport type GithubSignalSubscriptionSource = 'auto-gh-pr-create' | 'factory-pr-create' | 'explicit-tool';\nexport type GithubSignalSubscriptionStatus = 'open' | 'closed' | 'merged';\n\nexport interface GithubSignalSubscriptionData {\n installationExternalId: string;\n projectRepositoryId: string;\n repositoryExternalId: string;\n repositorySlug: string;\n changeRequestId: string;\n ownerId: string;\n source: GithubSignalSubscriptionSource;\n subscribedByUserId: string | null;\n}\n\nexport type GithubSignalSubscriptionRow = IntegrationSubscription<GithubSignalSubscriptionData>;\nexport type GithubSubscriptionStorage = IntegrationStorageHandle<\n Record<string, unknown>,\n Record<string, unknown>,\n GithubSignalSubscriptionData\n>;\n\nexport interface SubscribeToPullRequestInput {\n orgId: string;\n installationExternalId: string;\n projectRepositoryId: string;\n repositoryExternalId: string;\n repositorySlug: string;\n changeRequestId: string;\n sessionId: string;\n ownerId: string;\n resourceId: string;\n threadId: string;\n sessionScope?: string;\n source: GithubSignalSubscriptionSource;\n subscribedByUserId?: string;\n}\n\nexport interface ThreadSubscriptionTarget {\n orgId: string;\n resourceId: string;\n threadId: string;\n sessionScope?: string;\n}\n\nexport interface PullRequestSubscriptionTarget {\n orgId: string;\n installationExternalId: string;\n repositoryExternalId: string;\n changeRequestId: string;\n}\n\nexport type GithubWebhookPullRequestTarget = Omit<PullRequestSubscriptionTarget, 'orgId'>;\n\nexport function changeRequestTargetKey(input: GithubWebhookPullRequestTarget): string {\n return `change-request:${input.installationExternalId}:${input.repositoryExternalId}:${input.changeRequestId}`;\n}\n\nfunction sameSession(row: GithubSignalSubscriptionRow, input: SubscribeToPullRequestInput): boolean {\n return (\n row.orgId === input.orgId &&\n row.sessionId === input.sessionId &&\n row.resourceId === input.resourceId &&\n row.threadId === input.threadId &&\n (row.sessionScope ?? '') === (input.sessionScope ?? '')\n );\n}\n\nexport async function subscribeToPullRequest(\n input: SubscribeToPullRequestInput,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow> {\n const targetKey = changeRequestTargetKey(input);\n const existing = (await storage.subscriptions.listByTarget(targetKey)).find(row => sameSession(row, input));\n if (existing) {\n if (existing.status !== 'open') await storage.subscriptions.updateStatus(existing.id, 'open');\n return { ...existing, status: 'open' };\n }\n\n return storage.subscriptions.create({\n orgId: input.orgId,\n targetKey,\n sessionId: input.sessionId,\n resourceId: input.resourceId,\n threadId: input.threadId,\n sessionScope: input.sessionScope ?? '',\n status: 'open',\n data: {\n installationExternalId: input.installationExternalId,\n projectRepositoryId: input.projectRepositoryId,\n repositoryExternalId: input.repositoryExternalId,\n repositorySlug: input.repositorySlug,\n changeRequestId: input.changeRequestId,\n ownerId: input.ownerId,\n source: input.source,\n subscribedByUserId: input.subscribedByUserId ?? null,\n },\n });\n}\n\nexport async function unsubscribeFromPullRequest(\n input: SubscribeToPullRequestInput,\n storage: GithubSubscriptionStorage,\n): Promise<void> {\n const rows = await storage.subscriptions.listByTarget(changeRequestTargetKey(input));\n await Promise.all(rows.filter(row => sameSession(row, input)).map(row => storage.subscriptions.delete(row.id)));\n}\n\nexport async function listPullRequestSubscriptionsForThread(\n input: ThreadSubscriptionTarget,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow[]> {\n const rows = await storage.subscriptions.listByThread(input.resourceId, input.threadId);\n return rows.filter(\n row =>\n row.orgId === input.orgId &&\n row.resourceId === input.resourceId &&\n row.threadId === input.threadId &&\n (row.sessionScope ?? '') === (input.sessionScope ?? ''),\n );\n}\n\nexport async function listPullRequestSubscriptions(\n input: PullRequestSubscriptionTarget,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow[]> {\n const rows = await storage.subscriptions.listByTarget(changeRequestTargetKey(input));\n return rows.filter(row => row.orgId === input.orgId && row.status === 'open');\n}\n\nexport async function listPullRequestSubscriptionsForWebhook(\n input: GithubWebhookPullRequestTarget,\n options: { includeTerminal?: boolean } | undefined,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow[]> {\n const rows = await storage.subscriptions.listByTarget(changeRequestTargetKey(input));\n return options?.includeTerminal ? rows : rows.filter(row => row.status === 'open');\n}\n\nexport function retirePullRequestSubscription(\n id: string,\n status: GithubSignalSubscriptionStatus,\n storage: GithubSubscriptionStorage,\n): Promise<void> {\n return storage.subscriptions.updateStatus(id, status);\n}\n\nexport async function retirePullRequestSubscriptions(\n input: PullRequestSubscriptionTarget,\n storage: GithubSubscriptionStorage,\n): Promise<void> {\n const rows = await listPullRequestSubscriptions(input, storage);\n await Promise.all(rows.map(row => storage.subscriptions.updateStatus(row.id, 'closed')));\n}\n","import { createHmac, timingSafeEqual } from 'node:crypto';\nimport type { MountedMastraCode } from '@mastra/code-sdk';\nimport type { NotificationPriority } from '@mastra/core/notifications';\nimport type { Context } from 'hono';\nimport type { GithubIntegration } from './integration.js';\nimport type { GithubIssueTriageInput, GithubIssueTriageResult } from './issue-triage.js';\nimport { listPullRequestSubscriptionsForWebhook, retirePullRequestSubscription } from './subscriptions.js';\nimport type { GithubSignalSubscriptionRow, GithubWebhookPullRequestTarget } from './subscriptions.js';\n\nexport type GithubIssueTriageRunInput = GithubIssueTriageInput;\nexport type GithubIssueTriageRunResult = GithubIssueTriageResult;\n\nexport interface GithubWebhookHandlerOptions {\n /** Integration providing webhook-secret verification + collaborator permission checks. */\n github: GithubIntegration;\n runIssueTriage?: (input: GithubIssueTriageRunInput) => Promise<GithubIssueTriageRunResult>;\n ingestFactoryEvent?: (event: ParsedGithubWebhook) => Promise<unknown>;\n}\n\nconst SUPPORTED_GITHUB_WEBHOOK_EVENTS = new Set([\n 'issues',\n 'issue_comment',\n 'pull_request',\n 'pull_request_review',\n 'pull_request_review_comment',\n]);\n\nexport interface GithubWebhookMetadata {\n event: string;\n action?: string;\n deliveryId: string;\n repository?: string;\n repositoryId?: number;\n issueNumber?: number;\n pullRequestNumber?: number;\n sender?: string;\n senderType?: string;\n installationId?: number;\n}\n\nexport interface ParsedGithubWebhook {\n event: string;\n deliveryId: string;\n payload: Record<string, unknown>;\n}\n\nexport type GithubWebhookResult =\n | { status: 202; body: { ok: true; ignored?: true } }\n | { status: 400; body: { error: 'bad_request'; message: string } }\n | { status: 401; body: { error: 'unauthorized'; message: string } };\n\nexport interface GithubWebhookNotification {\n action: string;\n kind: string;\n priority: NotificationPriority;\n summary: string;\n terminal: boolean;\n metadata: GithubWebhookMetadata & { pullRequestNumber: number; repositoryId: number; installationId: number };\n payload: Record<string, unknown>;\n}\n\nexport interface GithubWebhookDispatchDependencies {\n controller: MountedMastraCode['controller'];\n /**\n * Integration used by the default sender-authorization check (collaborator\n * permission lookup). Author-gated notifications fail closed when neither\n * this nor an `isAuthorizedSender` override is supplied.\n */\n github?: GithubIntegration;\n listSubscriptions?: (\n target: GithubWebhookPullRequestTarget,\n options?: { includeTerminal?: boolean },\n ) => Promise<GithubSignalSubscriptionRow[]>;\n retireSubscription?: (id: string, status: 'open' | 'closed' | 'merged') => Promise<void>;\n isAuthorizedSender?: (notification: GithubWebhookNotification) => Promise<boolean>;\n onTargetError?: (subscription: GithubSignalSubscriptionRow, error: unknown) => void;\n}\n\nfunction normalizeHeader(value: string | undefined | null): string | null {\n if (!value) return null;\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : null;\n}\n\nfunction verifySignature(rawBody: string, signature: string, secret: string): boolean {\n if (!signature.startsWith('sha256=')) return false;\n const signatureHex = signature.slice('sha256='.length);\n if (!/^[a-fA-F0-9]{64}$/.test(signatureHex)) return false;\n\n const expectedHex = createHmac('sha256', secret).update(rawBody).digest('hex');\n const received = Buffer.from(signatureHex, 'hex');\n const expected = Buffer.from(expectedHex, 'hex');\n return received.length === expected.length && timingSafeEqual(received, expected);\n}\n\nasync function parseGithubWebhook(\n c: Context,\n secret: string | undefined,\n): Promise<ParsedGithubWebhook | GithubWebhookResult> {\n if (!secret) {\n return { status: 401, body: { error: 'unauthorized', message: 'GitHub webhook secret is not configured' } };\n }\n\n const event = normalizeHeader(c.req.header('x-github-event'));\n const deliveryId = normalizeHeader(c.req.header('x-github-delivery'));\n const signature = normalizeHeader(c.req.header('x-hub-signature-256'));\n\n if (!event) return { status: 400, body: { error: 'bad_request', message: 'Missing x-github-event header' } };\n if (!deliveryId) return { status: 400, body: { error: 'bad_request', message: 'Missing x-github-delivery header' } };\n if (!signature)\n return { status: 401, body: { error: 'unauthorized', message: 'Missing x-hub-signature-256 header' } };\n\n const rawBody = await c.req.text();\n if (!verifySignature(rawBody, signature, secret)) {\n return { status: 401, body: { error: 'unauthorized', message: 'Invalid GitHub webhook signature' } };\n }\n\n let payload: unknown;\n try {\n payload = JSON.parse(rawBody);\n } catch {\n return { status: 400, body: { error: 'bad_request', message: 'Malformed JSON payload' } };\n }\n\n if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {\n return { status: 400, body: { error: 'bad_request', message: 'Payload must be a JSON object' } };\n }\n\n return { event, deliveryId, payload: payload as Record<string, unknown> };\n}\n\nfunction getObject(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;\n}\n\nfunction getString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction getNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction getBoolean(value: unknown): boolean | undefined {\n return typeof value === 'boolean' ? value : undefined;\n}\n\nfunction getLabels(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value\n .map(label => (typeof label === 'string' ? label : getString(getObject(label)?.name)))\n .filter((label): label is string => Boolean(label));\n}\n\nfunction getIssueTriageRunInput(parsed: ParsedGithubWebhook): GithubIssueTriageRunInput | null {\n if (parsed.event !== 'issues' || getString(parsed.payload.action) !== 'opened') return null;\n const repository = getString(getObject(parsed.payload.repository)?.full_name);\n const issue = getObject(parsed.payload.issue);\n const sender = getString(getObject(parsed.payload.sender)?.login);\n const installationId = getNumber(getObject(parsed.payload.installation)?.id);\n const issueNumber = getNumber(issue?.number);\n const issueTitle = getString(issue?.title);\n const issueUrl = getString(issue?.html_url);\n if (!repository || !installationId || !issueNumber || !issueTitle || !issueUrl) return null;\n return {\n repository,\n issueNumber,\n issueTitle,\n issueUrl,\n labels: getLabels(issue?.labels),\n sender,\n installationId,\n };\n}\n\nexport function normalizeGithubWebhookMetadata(parsed: ParsedGithubWebhook): GithubWebhookMetadata {\n const { event, deliveryId, payload } = parsed;\n const repository = getObject(payload.repository);\n const issue = getObject(payload.issue);\n const pullRequest = getObject(payload.pull_request);\n const sender = getObject(payload.sender);\n const installation = getObject(payload.installation);\n const issuePullRequest = getObject(issue?.pull_request);\n\n return {\n event,\n action: getString(payload.action),\n deliveryId,\n repository: getString(repository?.full_name),\n repositoryId: getNumber(repository?.id),\n issueNumber: getNumber(issue?.number),\n pullRequestNumber:\n getNumber(pullRequest?.number) ??\n (event === 'issue_comment' && issuePullRequest ? getNumber(issue?.number) : undefined),\n sender: getString(sender?.login),\n senderType: getString(sender?.type),\n installationId: getNumber(installation?.id),\n };\n}\n\nfunction notificationSummary(metadata: GithubWebhookMetadata, label: string): string {\n const actor = metadata.sender ? `${metadata.sender} ` : '';\n return `${actor}${label} on ${metadata.repository}#${metadata.pullRequestNumber}`;\n}\n\nfunction notificationTargetUrl(event: string, payload: Record<string, unknown>): string | undefined {\n if (event === 'issue_comment' || event === 'pull_request_review_comment') {\n return getString(getObject(payload.comment)?.html_url);\n }\n if (event === 'pull_request_review') {\n return getString(getObject(payload.review)?.html_url);\n }\n return getString(getObject(payload.pull_request)?.html_url);\n}\n\nexport function classifyGithubWebhook(parsed: ParsedGithubWebhook): GithubWebhookNotification | undefined {\n const metadata = normalizeGithubWebhookMetadata(parsed);\n const { event, payload } = parsed;\n const action = metadata.action;\n if (\n !action ||\n !metadata.repositoryId ||\n !metadata.installationId ||\n !metadata.pullRequestNumber ||\n !metadata.repository\n ) {\n return undefined;\n }\n\n let priority: NotificationPriority;\n let kind: string;\n let label: string;\n let terminal = false;\n\n if (event === 'pull_request_review' && action === 'submitted') {\n const state = getString(getObject(payload.review)?.state)?.toLowerCase().replaceAll('_', '-');\n priority = state === 'approved' || state === 'changes-requested' ? 'urgent' : 'high';\n kind =\n state === 'approved'\n ? 'review-approved'\n : state === 'changes-requested'\n ? 'review-changes-requested'\n : 'review-submitted';\n label =\n state === 'approved'\n ? 'approved the pull request'\n : state === 'changes-requested'\n ? 'requested changes'\n : 'submitted a review';\n } else if (event === 'pull_request' && action === 'closed') {\n const merged = getBoolean(getObject(payload.pull_request)?.merged) === true;\n priority = 'urgent';\n kind = merged ? 'pull-request-merged' : 'pull-request-closed';\n label = merged ? 'merged the pull request' : 'closed the pull request';\n terminal = true;\n } else if (event === 'issue_comment' && action === 'created') {\n priority = 'high';\n kind = 'issue-comment-created';\n label = 'commented';\n } else if (event === 'pull_request_review_comment' && action === 'created') {\n priority = 'high';\n kind = 'review-comment-created';\n label = 'left a review comment';\n } else if (event === 'pull_request' && action === 'reopened') {\n priority = 'high';\n kind = 'pull-request-reopened';\n label = 'reopened the pull request';\n } else if (event === 'pull_request_review' && action === 'dismissed') {\n priority = 'high';\n kind = 'review-dismissed';\n label = 'dismissed a review';\n } else if (\n event === 'pull_request' &&\n [\n 'synchronize',\n 'ready_for_review',\n 'converted_to_draft',\n 'assigned',\n 'unassigned',\n 'review_requested',\n 'review_request_removed',\n ].includes(action)\n ) {\n priority = 'medium';\n kind = `pull-request-${action.replaceAll('_', '-')}`;\n label = action.replaceAll('_', ' ');\n } else if (\n event === 'pull_request' &&\n ['edited', 'labeled', 'unlabeled', 'milestoned', 'demilestoned'].includes(action)\n ) {\n priority = 'low';\n kind = `pull-request-${action.replaceAll('_', '-')}`;\n label = action.replaceAll('_', ' ');\n } else {\n return undefined;\n }\n\n return {\n action,\n kind,\n priority,\n summary: notificationSummary(metadata, label),\n terminal,\n metadata: {\n ...metadata,\n pullRequestNumber: metadata.pullRequestNumber,\n repositoryId: metadata.repositoryId,\n installationId: metadata.installationId,\n },\n payload,\n };\n}\n\nasync function resolveSubscriptionSession(\n controller: MountedMastraCode['controller'],\n subscription: GithubSignalSubscriptionRow,\n) {\n const { sessionId, resourceId, threadId } = subscription;\n if (!sessionId || !resourceId || !threadId) {\n throw new Error(`GitHub subscription ${subscription.id} is missing its session binding.`);\n }\n const scope = subscription.sessionScope || undefined;\n let session = await controller.getSessionByResource(resourceId, scope);\n if (!session) {\n const tags = {\n factoryProjectId: resourceId,\n projectRepositoryId: subscription.data.projectRepositoryId,\n ...(scope ? { worktreePath: scope } : {}),\n };\n session = await controller.createSession({\n id: sessionId,\n ownerId: subscription.data.ownerId,\n resourceId,\n scope,\n tags,\n });\n }\n if (session.thread.getId() !== threadId) {\n await session.thread.switch({ threadId, emitEvent: false });\n }\n if (session.thread.getId() !== threadId) {\n throw new Error(`Session ${sessionId} did not bind thread ${threadId}.`);\n }\n return session;\n}\n\nconst AUTHORIZED_BOTS = new Set(['coderabbitai[bot]', 'devin-ai-integration[bot]']);\nconst AUTHORIZED_PERMISSIONS = new Set(['admin', 'maintain', 'write']);\nconst PERMISSION_CHECK_TIMEOUT_MS = 5_000;\nconst AUTHOR_GATED_KINDS = new Set([\n 'issue-comment-created',\n 'review-comment-created',\n 'review-submitted',\n 'review-approved',\n 'review-changes-requested',\n 'review-dismissed',\n]);\n\nasync function isAuthorizedGithubSender(\n notification: GithubWebhookNotification,\n github: GithubIntegration | undefined,\n): Promise<boolean> {\n if (!AUTHOR_GATED_KINDS.has(notification.kind)) return true;\n const sender = notification.metadata.sender;\n const repository = notification.metadata.repository;\n if (!sender || !repository) return false;\n const normalizedSender = sender.toLowerCase();\n if (notification.metadata.senderType?.toLowerCase() === 'bot' || normalizedSender.endsWith('[bot]')) {\n return AUTHORIZED_BOTS.has(normalizedSender);\n }\n if (!github) return false;\n const abortController = new AbortController();\n let timeout: ReturnType<typeof setTimeout> | undefined;\n try {\n const permission = await Promise.race([\n github.getRepositoryCollaboratorPermission(\n notification.metadata.installationId,\n repository,\n sender,\n abortController.signal,\n ),\n new Promise<undefined>(resolve => {\n timeout = setTimeout(() => {\n abortController.abort();\n resolve(undefined);\n }, PERMISSION_CHECK_TIMEOUT_MS);\n }),\n ]);\n return permission !== undefined && AUTHORIZED_PERMISSIONS.has(permission);\n } catch {\n return false;\n } finally {\n if (timeout) clearTimeout(timeout);\n }\n}\n\nexport async function dispatchGithubWebhook(\n parsed: ParsedGithubWebhook,\n dependencies: GithubWebhookDispatchDependencies,\n): Promise<{ delivered: number; failed: number; ignored: boolean }> {\n const notification = classifyGithubWebhook(parsed);\n if (!notification) return { delivered: 0, failed: 0, ignored: true };\n const isAuthorizedSender =\n dependencies.isAuthorizedSender ??\n ((n: GithubWebhookNotification) => isAuthorizedGithubSender(n, dependencies.github));\n if (!(await isAuthorizedSender(notification))) {\n return { delivered: 0, failed: 0, ignored: true };\n }\n\n const target = {\n installationExternalId: notification.metadata.installationId.toString(),\n repositoryExternalId: notification.metadata.repositoryId.toString(),\n changeRequestId: notification.metadata.pullRequestNumber.toString(),\n };\n const listSubscriptions =\n dependencies.listSubscriptions ??\n ((subscriptionTarget: GithubWebhookPullRequestTarget, options?: { includeTerminal?: boolean }) => {\n if (!dependencies.github) throw new Error('GitHub integration is required to load webhook subscriptions.');\n return listPullRequestSubscriptionsForWebhook(\n subscriptionTarget,\n options,\n dependencies.github.integrationStorage,\n );\n });\n const retireSubscription =\n dependencies.retireSubscription ??\n ((id: string, status: 'open' | 'closed' | 'merged') => {\n if (!dependencies.github) throw new Error('GitHub integration is required to retire webhook subscriptions.');\n return retirePullRequestSubscription(id, status, dependencies.github.integrationStorage);\n });\n const subscriptions = await listSubscriptions(target, { includeTerminal: notification.action === 'reopened' });\n let delivered = 0;\n let failed = 0;\n\n for (const subscription of subscriptions) {\n try {\n const session = await resolveSubscriptionSession(dependencies.controller, subscription);\n const result = await session.sendNotificationSignal({\n source: 'github',\n kind: notification.kind,\n summary: notification.summary,\n priority: notification.priority,\n payload: notification.payload,\n sourceId: parsed.deliveryId,\n dedupeKey: `${parsed.deliveryId}:${subscription.sessionId}:${subscription.threadId}`,\n coalesceKey: `github:${subscription.data.repositoryExternalId}:pull-request:${subscription.data.changeRequestId}`,\n metadata: {\n event: notification.metadata.event,\n action: notification.action,\n repository: notification.metadata.repository,\n issueNumber: notification.metadata.issueNumber,\n pullRequestNumber: notification.metadata.pullRequestNumber,\n targetUrl: notificationTargetUrl(parsed.event, parsed.payload),\n deliveryId: parsed.deliveryId,\n },\n });\n await Promise.all([result.persisted, result.accepted].filter(Boolean));\n if (notification.terminal) {\n await retireSubscription(subscription.id, notification.kind === 'pull-request-merged' ? 'merged' : 'closed');\n } else if (notification.action === 'reopened') {\n await retireSubscription(subscription.id, 'open');\n }\n delivered += 1;\n } catch (error) {\n failed += 1;\n dependencies.onTargetError?.(subscription, error);\n }\n }\n\n return { delivered, failed, ignored: false };\n}\n\nexport async function handleGithubWebhook(\n c: Context,\n options: GithubWebhookHandlerOptions & Partial<Omit<GithubWebhookDispatchDependencies, 'github'>>,\n): Promise<GithubWebhookResult> {\n const parsed = await parseGithubWebhook(c, options.github.webhookSecret);\n if ('status' in parsed) return parsed;\n\n if (!SUPPORTED_GITHUB_WEBHOOK_EVENTS.has(parsed.event)) {\n return { status: 202, body: { ok: true, ignored: true } };\n }\n\n const metadata = normalizeGithubWebhookMetadata(parsed);\n console.info('[GitHub Webhook]', metadata);\n\n if (options.ingestFactoryEvent) {\n await options.ingestFactoryEvent(parsed);\n } else {\n const issueTriageRun = getIssueTriageRunInput(parsed);\n if (issueTriageRun && options.runIssueTriage) {\n void options.runIssueTriage(issueTriageRun).catch((error: unknown) => {\n console.error('[GitHub Webhook] Failed to run issue triage', {\n deliveryId: metadata.deliveryId,\n repository: metadata.repository,\n issueNumber: metadata.issueNumber,\n error: error instanceof Error ? error.message : String(error),\n });\n });\n }\n }\n\n if (!options.controller) {\n return { status: 202, body: { ok: true } };\n }\n\n const result = await dispatchGithubWebhook(parsed, options as GithubWebhookDispatchDependencies);\n if (result.failed > 0) {\n console.warn(`[GitHub Webhook] ${result.failed} subscribed target(s) failed for delivery ${parsed.deliveryId}.`);\n }\n return { status: 202, body: { ok: true, ...(result.ignored ? { ignored: true as const } : {}) } };\n}\n","export interface PlatformApiClientConfig {\n baseUrl: string;\n accessToken: string;\n fetchImpl?: typeof fetch;\n}\n\nexport function platformApiClientConfigFromEnv(): PlatformApiClientConfig {\n const sharedApiUrl = process.env.MASTRA_SHARED_API_URL?.trim() || 'https://platform.mastra.ai/v1';\n const accessToken = process.env.MASTRA_PLATFORM_SECRET_KEY?.trim();\n if (!accessToken) {\n throw new Error('Platform integration: missing required environment variable MASTRA_PLATFORM_SECRET_KEY.');\n }\n return { baseUrl: normalizeSharedApiUrl(sharedApiUrl), accessToken };\n}\n\nfunction normalizeSharedApiUrl(sharedApiUrl: string): string {\n return sharedApiUrl.replace(/\\/+$/, '').replace(/\\/v1$/, '');\n}\n\nexport class PlatformApiError extends Error {\n readonly status: number;\n readonly retryAfterSeconds: number | null;\n\n constructor(message: string, status: number, retryAfterSeconds: number | null = null) {\n super(message);\n this.name = 'PlatformApiError';\n this.status = status;\n this.retryAfterSeconds = retryAfterSeconds;\n }\n}\n\nexport class PlatformApiClient {\n readonly #baseUrl: string;\n readonly #accessToken: string;\n readonly #fetch: typeof fetch;\n\n constructor(config: PlatformApiClientConfig) {\n const missing = ['baseUrl', 'accessToken'].filter(field => !config[field as keyof PlatformApiClientConfig]);\n if (missing.length > 0) {\n throw new Error(`Platform integration: missing required config field(s): ${missing.join(', ')}.`);\n }\n this.#baseUrl = config.baseUrl.replace(/\\/+$/, '');\n this.#accessToken = config.accessToken;\n this.#fetch = config.fetchImpl ?? globalThis.fetch;\n }\n\n async request<T>(method: string, path: string, body?: unknown, options?: { signal?: AbortSignal }): Promise<T> {\n const response = await this.#send(method, path, body, options);\n if (!response.ok) {\n const message = redact(await extractError(response), this.#accessToken);\n const retryAfterSeconds = parseRetryAfter(response.headers.get('retry-after'));\n logPlatformError('Platform API request failed', {\n method,\n path,\n status: response.status,\n retryAfterSeconds,\n message,\n });\n throw new PlatformApiError(message, response.status, retryAfterSeconds);\n }\n if (response.status === 204) return undefined as T;\n return (await response.json()) as T;\n }\n\n async requestRedirect(method: string, path: string, options?: { signal?: AbortSignal }): Promise<string> {\n const response = await this.#send(method, path, undefined, options, 'manual');\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get('location');\n if (location) return location;\n }\n if (!response.ok) {\n const message = redact(await extractError(response), this.#accessToken);\n const retryAfterSeconds = parseRetryAfter(response.headers.get('retry-after'));\n logPlatformError('Platform API redirect request failed', {\n method,\n path,\n status: response.status,\n retryAfterSeconds,\n message,\n });\n throw new PlatformApiError(message, response.status, retryAfterSeconds);\n }\n logPlatformError('Platform API request did not return a redirect', { method, path, status: response.status });\n throw new PlatformApiError('Platform API request did not return a redirect.', response.status);\n }\n\n async #send(\n method: string,\n path: string,\n body?: unknown,\n options?: { signal?: AbortSignal },\n redirect?: RequestInit['redirect'],\n ): Promise<Response> {\n const headers: Record<string, string> = {\n accept: 'application/json',\n authorization: `Bearer ${this.#accessToken}`,\n };\n const timeoutSignal = AbortSignal.timeout(15_000);\n const init: RequestInit = {\n method,\n headers,\n redirect,\n signal: options?.signal ? AbortSignal.any([options.signal, timeoutSignal]) : timeoutSignal,\n };\n if (body !== undefined) {\n headers['content-type'] = 'application/json';\n init.body = JSON.stringify(body);\n }\n\n try {\n return await this.#fetch(`${this.#baseUrl}${path}`, init);\n } catch (error) {\n if (error instanceof Error && error.message.includes(this.#accessToken)) {\n const redacted = new Error(redact(error.message, this.#accessToken));\n redacted.name = error.name;\n logPlatformError('Platform API transport error', {\n method,\n path,\n name: redacted.name,\n message: redacted.message,\n });\n throw redacted;\n }\n logPlatformError('Platform API transport error', {\n method,\n path,\n name: error instanceof Error ? error.name : undefined,\n message: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n }\n}\n\nasync function extractError(response: Response): Promise<string> {\n try {\n const data = (await response.clone().json()) as Record<string, unknown>;\n for (const field of ['detail', 'error', 'title']) {\n if (typeof data[field] === 'string' && data[field]) return data[field];\n }\n } catch {\n // Fall through to the status-based message.\n }\n return `Platform API request failed (${response.status})`;\n}\n\nfunction redact(message: string, accessToken: string): string {\n return message.split(accessToken).join('[REDACTED]');\n}\n\nexport function logPlatformInfo(message: string, fields?: Record<string, unknown>): void {\n writePlatformLog('info', message, fields);\n}\n\nexport function logPlatformError(message: string, fields?: Record<string, unknown>): void {\n writePlatformLog('error', message, fields);\n}\n\nfunction writePlatformLog(level: 'info' | 'error', message: string, fields?: Record<string, unknown>): void {\n const metadata = fields ? ` ${JSON.stringify(stripUndefined(fields))}` : '';\n process.stderr.write(`[MastraCode Web] ${level.toUpperCase()} ${message}${metadata}\\n`);\n}\n\nfunction stripUndefined(fields: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));\n}\n\nfunction parseRetryAfter(value: string | null): number | null {\n if (!value) return null;\n const seconds = Number.parseInt(value, 10);\n return Number.isSafeInteger(seconds) && seconds >= 0 ? seconds : null;\n}\n"],"mappings":"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","names":["AUTHOR_GATED_KINDS","AUTHORIZED_BOTS","AUTHORIZED_PERMISSIONS","PERMISSION_CHECK_TIMEOUT_MS"]}
@@ -1 +1 @@
1
- {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../../src/integrations/platform/github/integration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAKpD,OAAO,KAAK,EAAE,MAAM,EAAkC,MAAM,iCAAiC,CAAC;AAC9F,OAAO,KAAK,EAuBV,cAAc,EACf,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+CAA+C,CAAC;AAC9F,OAAO,KAAK,EAEV,0BAA0B,EAC3B,MAAM,iDAAiD,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC9F,OAAO,KAAK,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAO9G,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAE/E,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAiF9D,qBAAa,yBAA0B,YAAW,kBAAkB;;IAClE,QAAQ,CAAC,EAAE,YAAY;IAQvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAoHrB;IAEF,QAAQ,CAAC,cAAc,EAAE,cAAc,CAsErC;;IAUF,IAAI,OAAO,IAAI,0BAA0B,CAGxC;IAED,IAAI,oBAAoB,IAAI,0BAA0B,CAErD;IAED,IAAI,kBAAkB,IAAI,yBAAyB,CAKlD;IAED,UAAU,CAAC,EAAE,OAAO,EAAE,EAAE;QAAE,OAAO,EAAE,wBAAwB,CAAA;KAAE,GAAG,IAAI;IASpE,MAAM,CAAC,GAAG,EAAE,kBAAkB,GAAG,QAAQ,EAAE;IAkH3C,OAAO,CAAC,GAAG,EAAE,kBAAkB,GAAG,yBAAyB,EAAE;IAgB7D,YAAY,CAAC,EAAE,cAAc,EAAE,EAAE;QAAE,cAAc,EAAE,cAAc,CAAA;KAAE,GAAG,gBAAgB;IAIhF,gBAAgB,CAAC,EACrB,WAAW,EACX,cAAc,GACf,EAAE,UAAU,CAAC,WAAW,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAWrF,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAWhC,mCAAmC,CACvC,eAAe,EAAE,MAAM,EACvB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,0BAA0B,GAAG,SAAS,CAAC;IAoB5C,qBAAqB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAcrE,qBAAqB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAa9D,cAAc,CAClB,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EAAE,GACf,OAAO,CAAC,MAAM,EAAE,CAAC;IASpB,sBAAsB,CAAC,eAAe,EAAE,MAAM,GAAG,UAAU,CAAC,iBAAiB,CAAC,wBAAwB,CAAC,CAAC;CA+SzG"}
1
+ {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../../src/integrations/platform/github/integration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAKpD,OAAO,KAAK,EAAE,MAAM,EAAkC,MAAM,iCAAiC,CAAC;AAC9F,OAAO,KAAK,EAuBV,cAAc,EACf,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+CAA+C,CAAC;AAC9F,OAAO,KAAK,EAEV,0BAA0B,EAC3B,MAAM,iDAAiD,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC9F,OAAO,KAAK,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAO9G,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAE/E,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAiF9D,qBAAa,yBAA0B,YAAW,kBAAkB;;IAClE,QAAQ,CAAC,EAAE,YAAY;IAQvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAoHrB;IAEF,QAAQ,CAAC,cAAc,EAAE,cAAc,CAsErC;;IAUF,IAAI,OAAO,IAAI,0BAA0B,CAGxC;IAED,IAAI,oBAAoB,IAAI,0BAA0B,CAErD;IAED,IAAI,kBAAkB,IAAI,yBAAyB,CAKlD;IAED,UAAU,CAAC,EAAE,OAAO,EAAE,EAAE;QAAE,OAAO,EAAE,wBAAwB,CAAA;KAAE,GAAG,IAAI;IASpE,MAAM,CAAC,GAAG,EAAE,kBAAkB,GAAG,QAAQ,EAAE;IAkH3C,OAAO,CAAC,GAAG,EAAE,kBAAkB,GAAG,yBAAyB,EAAE;IAiB7D,YAAY,CAAC,EAAE,cAAc,EAAE,EAAE;QAAE,cAAc,EAAE,cAAc,CAAA;KAAE,GAAG,gBAAgB;IAIhF,gBAAgB,CAAC,EACrB,WAAW,EACX,cAAc,GACf,EAAE,UAAU,CAAC,WAAW,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAWrF,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAWhC,mCAAmC,CACvC,eAAe,EAAE,MAAM,EACvB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,0BAA0B,GAAG,SAAS,CAAC;IAoB5C,qBAAqB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAcrE,qBAAqB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAa9D,cAAc,CAClB,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EAAE,GACf,OAAO,CAAC,MAAM,EAAE,CAAC;IASpB,sBAAsB,CAAC,eAAe,EAAE,MAAM,GAAG,UAAU,CAAC,iBAAiB,CAAC,wBAAwB,CAAC,CAAC;CA+SzG"}
@@ -117,8 +117,8 @@ function bindingStore(row, storage) {
117
117
  };
118
118
  }
119
119
  async function ensureProjectSandbox(options) {
120
- const { fleet, row, storage, onProgress } = options;
121
- return fleet.ensureSandbox(bindingStore(row, storage), onProgress);
120
+ const { fleet, row, storage, token, onProgress } = options;
121
+ return fleet.ensureSandbox(bindingStore(row, storage), { GH_TOKEN: token }, onProgress);
122
122
  }
123
123
  async function teardownProjectSandbox(options) {
124
124
  const { fleet, row, storage, sandbox } = options;
@@ -845,6 +845,7 @@ function polledIssueEvent(project, issue) {
845
845
  number: issue.number,
846
846
  title: issue.title,
847
847
  html_url: issue.url,
848
+ created_at: issue.createdAt,
848
849
  labels: issue.labels.map((name) => ({ name }))
849
850
  }
850
851
  }
@@ -864,6 +865,7 @@ function polledPullRequestEvent(project, pullRequest) {
864
865
  number: pullRequest.number,
865
866
  title: pullRequest.title,
866
867
  html_url: pullRequest.url,
868
+ created_at: pullRequest.createdAt,
867
869
  state: "open",
868
870
  merged: false,
869
871
  head: { ref: pullRequest.headBranch },
@@ -1393,13 +1395,6 @@ async function loadOrCreateSandboxRow(github, project, userId) {
1393
1395
  async function prepareProject(options) {
1394
1396
  const { github, fleet, project, userId, onProgress } = options;
1395
1397
  const sandboxRow = await loadOrCreateSandboxRow(github, project, userId);
1396
- const sandbox = await ensureProjectSandbox({
1397
- fleet,
1398
- row: sandboxRow,
1399
- storage: github.sourceControlStorage.sandboxes,
1400
- onProgress
1401
- });
1402
- const fresh = await github.sourceControlStorage.sandboxes.getById({ id: sandboxRow.id });
1403
1398
  const access = await github.versionControl.getRepositoryAccess({
1404
1399
  orgId: project.installation.orgId,
1405
1400
  repositoryId: project.repository.id
@@ -1407,6 +1402,14 @@ async function prepareProject(options) {
1407
1402
  if (!access.authorization) {
1408
1403
  throw new MaterializeError("Repository access did not include a bearer token.", "clone-failed");
1409
1404
  }
1405
+ const sandbox = await ensureProjectSandbox({
1406
+ fleet,
1407
+ row: sandboxRow,
1408
+ storage: github.sourceControlStorage.sandboxes,
1409
+ token: access.authorization.token,
1410
+ onProgress
1411
+ });
1412
+ const fresh = await github.sourceControlStorage.sandboxes.getById({ id: sandboxRow.id });
1410
1413
  const finalRow = fresh ?? sandboxRow;
1411
1414
  await materializeRepo({
1412
1415
  row: finalRow,
@@ -1837,6 +1840,18 @@ async function resolveSessionWorkspace(github, projectId, userId, sessionId) {
1837
1840
  // src/integrations/github/session-subscriptions.ts
1838
1841
  import { createTool } from "@mastra/core/tools";
1839
1842
  import { z } from "zod";
1843
+
1844
+ // src/integrations/github/token-refresh.ts
1845
+ var GITHUB_TOKEN_INJECTOR_CONTEXT_KEY = "factoryGithubTokenInjector";
1846
+ function injectGithubToken(requestContext, token) {
1847
+ const injector = requestContext.get(GITHUB_TOKEN_INJECTOR_CONTEXT_KEY);
1848
+ if (!injector) {
1849
+ throw new Error("GitHub token refresh requires an active Factory sandbox workspace.");
1850
+ }
1851
+ injector(token);
1852
+ }
1853
+
1854
+ // src/integrations/github/session-subscriptions.ts
1840
1855
  function sessionUserId(user) {
1841
1856
  return user?.workosId ?? user?.id;
1842
1857
  }
@@ -1923,11 +1938,30 @@ async function unsubscribeCurrentSessionFromPullRequest(requestContext, pullRequ
1923
1938
  await unsubscribeFromPullRequest(await subscriptionInput(target, number), github.integrationStorage);
1924
1939
  return number;
1925
1940
  }
1941
+ async function refreshGithubToken(requestContext, github) {
1942
+ const target = await resolveSessionTarget(requestContext, github);
1943
+ const access = await github.versionControl.getRepositoryAccess({
1944
+ orgId: target.orgId,
1945
+ repositoryId: target.repository.id
1946
+ });
1947
+ const token = access.authorization?.token;
1948
+ if (!token) throw new Error("Repository access did not include a bearer token for the Factory session.");
1949
+ injectGithubToken(requestContext, token);
1950
+ }
1926
1951
  function createGithubSubscriptionTools(requestContext, github) {
1927
1952
  const context = requestContext.get("controller");
1928
1953
  const user = requestContext.get("user");
1929
1954
  if (!context?.getState().projectRepositoryId || !sessionOrgId(user) || !sessionUserId(user)) return {};
1930
1955
  return {
1956
+ github_refresh_token: createTool({
1957
+ id: "github_refresh_token",
1958
+ description: "Refresh GitHub CLI authentication in the active Factory sandbox. Use this after a gh command fails because authentication is expired, invalid, or missing. It installs a fresh GH_TOKEN for subsequent sandbox commands. After this tool succeeds, retry the failed gh command. Takes no arguments and never returns the token.",
1959
+ inputSchema: z.object({}),
1960
+ execute: async () => {
1961
+ await refreshGithubToken(requestContext, github);
1962
+ return { refreshed: true };
1963
+ }
1964
+ }),
1931
1965
  github_subscribe_pr: createTool({
1932
1966
  id: "github_subscribe_pr",
1933
1967
  description: "Subscribe this thread to GitHub pull request activity. You usually do not need this tool: successful gh pr create commands subscribe automatically. Use it for an existing PR or to recover when automatic subscription did not occur. Closed or merged PRs are unsubscribed automatically. Accepts a PR number or canonical URL for the active project.",
@@ -2154,6 +2188,7 @@ var PlatformGithubEventWorker = class extends MastraWorker {
2154
2188
  #controller;
2155
2189
  #github;
2156
2190
  #storage;
2191
+ #ingestFactoryEvent;
2157
2192
  #intervalMs;
2158
2193
  #now;
2159
2194
  #dispatch;
@@ -2173,6 +2208,7 @@ var PlatformGithubEventWorker = class extends MastraWorker {
2173
2208
  this.#controller = config.controller;
2174
2209
  this.#github = config.github;
2175
2210
  this.#storage = config.storage;
2211
+ this.#ingestFactoryEvent = config.ingestFactoryEvent;
2176
2212
  this.#intervalMs = config.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
2177
2213
  if (!Number.isFinite(this.#intervalMs) || this.#intervalMs <= 0) {
2178
2214
  throw new Error("Platform GitHub event polling interval must be a positive number.");
@@ -2330,6 +2366,9 @@ var PlatformGithubEventWorker = class extends MastraWorker {
2330
2366
  });
2331
2367
  continue;
2332
2368
  }
2369
+ if (isFactoryClosureEvent(parsed)) {
2370
+ await this.#ingestFactoryEvent?.(parsed);
2371
+ }
2333
2372
  const result = await this.#dispatch(parsed, {
2334
2373
  controller: this.#controller,
2335
2374
  listSubscriptions: (target, options) => listPullRequestSubscriptionsForWebhook(target, options, this.#github.integrationStorage),
@@ -2395,6 +2434,9 @@ function normalizeSettings(value) {
2395
2434
  }
2396
2435
  return { version: 1, repositories: { ...value.repositories } };
2397
2436
  }
2437
+ function isFactoryClosureEvent(event) {
2438
+ return (event.event === "issues" || event.event === "pull_request") && event.payload.action === "closed";
2439
+ }
2398
2440
  function parseEvent(event) {
2399
2441
  if (!event.id || !event.deliveryId || !SUPPORTED_EVENTS.has(event.event) || !event.payload || typeof event.payload !== "object" || Array.isArray(event.payload)) {
2400
2442
  return null;
@@ -2743,6 +2785,7 @@ var PlatformGithubIntegration = class {
2743
2785
  controller: ctx.controller,
2744
2786
  github: this,
2745
2787
  storage: ctx.storage.generic,
2788
+ ingestFactoryEvent: ctx.hooks?.ingestGithubEvent,
2746
2789
  intervalMs: this.#pollingIntervalMs
2747
2790
  })
2748
2791
  ];