@mastra/factory 0.2.0-alpha.0 → 0.2.0
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 +20 -0
- package/dist/factory.js +3 -3
- package/dist/factory.js.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/integrations/github/integration.js +1 -1
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/routes.js +1 -1
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/platform/api-client.js +1 -1
- package/dist/integrations/platform/api-client.js.map +1 -1
- package/dist/integrations/platform/github/event-worker.js.map +1 -1
- package/dist/integrations/platform/github/integration.js +2 -2
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/platform/linear/integration.js +1 -1
- package/dist/integrations/platform/linear/integration.js.map +1 -1
- package/dist/server-error.js +1 -1
- package/dist/server-error.js.map +1 -1
- package/package.json +5 -5
|
@@ -138,7 +138,7 @@ function logPlatformError(message, fields) {
|
|
|
138
138
|
}
|
|
139
139
|
function writePlatformLog(level, message, fields) {
|
|
140
140
|
const metadata = fields ? ` ${JSON.stringify(stripUndefined(fields))}` : "";
|
|
141
|
-
process.stderr.write(`[
|
|
141
|
+
process.stderr.write(`[Mastra Factory] ${level.toUpperCase()} ${message}${metadata}
|
|
142
142
|
`);
|
|
143
143
|
}
|
|
144
144
|
function stripUndefined(fields) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/integrations/platform/api-client.ts"],"sourcesContent":["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>(\n method: string,\n path: string,\n body?: unknown,\n options?: { signal?: AbortSignal; actingUserId?: string },\n ): 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; actingUserId?: string },\n redirect?: RequestInit['redirect'],\n ): Promise<Response> {\n const headers: Record<string, string> = {\n accept: 'application/json',\n authorization: `Bearer ${this.#accessToken}`,\n };\n // Acting end-user for platform GitHub writes: the platform resolves this\n // user's GitHub OAuth connection (org-scoped) so issues/PRs are authored\n // by the human instead of the App bot. Ignored by older platforms.\n if (options?.actingUserId) {\n headers['x-acting-user-id'] = options.actingUserId;\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 logPlatformWarn(message: string, fields?: Record<string, unknown>): void {\n writePlatformLog('warn', 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' | 'warn' | '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":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/integrations/platform/api-client.ts"],"sourcesContent":["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>(\n method: string,\n path: string,\n body?: unknown,\n options?: { signal?: AbortSignal; actingUserId?: string },\n ): 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; actingUserId?: string },\n redirect?: RequestInit['redirect'],\n ): Promise<Response> {\n const headers: Record<string, string> = {\n accept: 'application/json',\n authorization: `Bearer ${this.#accessToken}`,\n };\n // Acting end-user for platform GitHub writes: the platform resolves this\n // user's GitHub OAuth connection (org-scoped) so issues/PRs are authored\n // by the human instead of the App bot. Ignored by older platforms.\n if (options?.actingUserId) {\n headers['x-acting-user-id'] = options.actingUserId;\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 logPlatformWarn(message: string, fields?: Record<string, unknown>): void {\n writePlatformLog('warn', 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' | 'warn' | 'error', message: string, fields?: Record<string, unknown>): void {\n const metadata = fields ? ` ${JSON.stringify(stripUndefined(fields))}` : '';\n process.stderr.write(`[Mastra Factory] ${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":[]}
|
|
@@ -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 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>(\n method: string,\n path: string,\n body?: unknown,\n options?: { signal?: AbortSignal; actingUserId?: string },\n ): 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; actingUserId?: string },\n redirect?: RequestInit['redirect'],\n ): Promise<Response> {\n const headers: Record<string, string> = {\n accept: 'application/json',\n authorization: `Bearer ${this.#accessToken}`,\n };\n // Acting end-user for platform GitHub writes: the platform resolves this\n // user's GitHub OAuth connection (org-scoped) so issues/PRs are authored\n // by the human instead of the App bot. Ignored by older platforms.\n if (options?.actingUserId) {\n headers['x-acting-user-id'] = options.actingUserId;\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 logPlatformWarn(message: string, fields?: Record<string, unknown>): void {\n writePlatformLog('warn', 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' | 'warn' | '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>(\n method: string,\n path: string,\n body?: unknown,\n options?: { signal?: AbortSignal; actingUserId?: string },\n ): 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; actingUserId?: string },\n redirect?: RequestInit['redirect'],\n ): Promise<Response> {\n const headers: Record<string, string> = {\n accept: 'application/json',\n authorization: `Bearer ${this.#accessToken}`,\n };\n // Acting end-user for platform GitHub writes: the platform resolves this\n // user's GitHub OAuth connection (org-scoped) so issues/PRs are authored\n // by the human instead of the App bot. Ignored by older platforms.\n if (options?.actingUserId) {\n headers['x-acting-user-id'] = options.actingUserId;\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 logPlatformWarn(message: string, fields?: Record<string, unknown>): void {\n writePlatformLog('warn', 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' | 'warn' | 'error', message: string, fields?: Record<string, unknown>): void {\n const metadata = fields ? ` ${JSON.stringify(stripUndefined(fields))}` : '';\n process.stderr.write(`[Mastra Factory] ${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"]}
|
|
@@ -1128,7 +1128,7 @@ function buildGithubRoutes(options) {
|
|
|
1128
1128
|
list = await github.listInstallationRepos(Number(inst.externalId));
|
|
1129
1129
|
} catch (err) {
|
|
1130
1130
|
if (err.status !== 404) throw err;
|
|
1131
|
-
console.error(`[
|
|
1131
|
+
console.error(`[Mastra Factory] pruning stale GitHub installation ${inst.externalId} (404 from GitHub)`);
|
|
1132
1132
|
await github.sourceControlStorage.installations.delete({ orgId: resolved.tenant.orgId, id: inst.id });
|
|
1133
1133
|
continue;
|
|
1134
1134
|
}
|
|
@@ -2258,7 +2258,7 @@ function logPlatformError(message, fields) {
|
|
|
2258
2258
|
}
|
|
2259
2259
|
function writePlatformLog(level, message, fields) {
|
|
2260
2260
|
const metadata = fields ? ` ${JSON.stringify(stripUndefined(fields))}` : "";
|
|
2261
|
-
process.stderr.write(`[
|
|
2261
|
+
process.stderr.write(`[Mastra Factory] ${level.toUpperCase()} ${message}${metadata}
|
|
2262
2262
|
`);
|
|
2263
2263
|
}
|
|
2264
2264
|
function stripUndefined(fields) {
|