@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.
- package/CHANGELOG.md +46 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +812 -209
- package/dist/factory.js.map +1 -1
- package/dist/index.js +812 -209
- package/dist/index.js.map +1 -1
- package/dist/integrations/github/integration.d.ts +2 -2
- package/dist/integrations/github/integration.js +45 -11
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/provenance.js.map +1 -1
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +12 -9
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +1 -0
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +2 -2
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/github/session-subscriptions.d.ts +3 -0
- package/dist/integrations/github/session-subscriptions.d.ts.map +1 -1
- package/dist/integrations/github/session-subscriptions.js +30 -0
- package/dist/integrations/github/session-subscriptions.js.map +1 -1
- package/dist/integrations/github/token-refresh.d.ts +6 -0
- package/dist/integrations/github/token-refresh.d.ts.map +1 -0
- package/dist/integrations/github/token-refresh.js +17 -0
- package/dist/integrations/github/token-refresh.js.map +1 -0
- package/dist/integrations/platform/github/event-worker.d.ts +2 -0
- package/dist/integrations/platform/github/event-worker.d.ts.map +1 -1
- package/dist/integrations/platform/github/event-worker.js +8 -0
- package/dist/integrations/platform/github/event-worker.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +52 -9
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/routes/config.d.ts +16 -11
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +222 -109
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/custom-provider-source.d.ts +52 -0
- package/dist/routes/custom-provider-source.d.ts.map +1 -0
- package/dist/routes/custom-provider-source.js +107 -0
- package/dist/routes/custom-provider-source.js.map +1 -0
- package/dist/routes/oauth.js +33 -1
- package/dist/routes/oauth.js.map +1 -1
- package/dist/routes/surface.d.ts +4 -0
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +282 -111
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/defaults.d.ts.map +1 -1
- package/dist/rules/defaults.js +8 -2
- package/dist/rules/defaults.js.map +1 -1
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/github-service.d.ts +2 -0
- package/dist/rules/github-service.d.ts.map +1 -1
- package/dist/rules/github-service.js +15 -1
- package/dist/rules/github-service.js.map +1 -1
- package/dist/rules/index.js +8 -2
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/linear-service.js.map +1 -1
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/tools.js.map +1 -1
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.d.ts +5 -0
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js.map +1 -1
- package/dist/rules/validation.js.map +1 -1
- package/dist/sandbox/fleet.d.ts +3 -0
- package/dist/sandbox/fleet.d.ts.map +1 -1
- package/dist/sandbox/fleet.js +10 -3
- package/dist/sandbox/fleet.js.map +1 -1
- package/dist/spa-static.d.ts +4 -5
- package/dist/spa-static.d.ts.map +1 -1
- package/dist/spa-static.js +5 -2
- package/dist/spa-static.js.map +1 -1
- package/dist/storage/domains/custom-providers/base.d.ts +57 -0
- package/dist/storage/domains/custom-providers/base.d.ts.map +1 -0
- package/dist/storage/domains/custom-providers/base.js +150 -0
- package/dist/storage/domains/custom-providers/base.js.map +1 -0
- package/dist/storage/domains/memory-settings/base.d.ts +62 -0
- package/dist/storage/domains/memory-settings/base.d.ts.map +1 -0
- package/dist/storage/domains/memory-settings/base.js +111 -0
- package/dist/storage/domains/memory-settings/base.js.map +1 -0
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +17 -0
- package/dist/workspace.js.map +1 -1
- 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;
|
|
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;
|
|
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
|
];
|