@mastra/factory 0.6.0 → 0.7.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/dist/factory.d.ts.map +1 -1
  3. package/dist/factory.js +8 -1
  4. package/dist/factory.js.map +1 -1
  5. package/dist/integrations/github/issue-triage.js +2 -2
  6. package/dist/integrations/github/issue-triage.js.map +1 -1
  7. package/dist/integrations/github/routes.d.ts.map +1 -1
  8. package/dist/integrations/github/routes.js +3 -3
  9. package/dist/integrations/github/routes.js.map +1 -1
  10. package/dist/integrations/github/rules.d.ts.map +1 -1
  11. package/dist/integrations/github/rules.js +2 -1
  12. package/dist/integrations/github/rules.js.map +1 -1
  13. package/dist/integrations/github/webhook.d.ts.map +1 -1
  14. package/dist/integrations/github/webhook.js +2 -2
  15. package/dist/integrations/github/webhook.js.map +1 -1
  16. package/dist/integrations/platform/github/event-worker.d.ts.map +1 -1
  17. package/dist/integrations/platform/github/event-worker.js +15 -4
  18. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  19. package/dist/routes/projects.d.ts.map +1 -1
  20. package/dist/routes/projects.js +4 -0
  21. package/dist/routes/projects.js.map +1 -1
  22. package/dist/routes/skills.d.ts.map +1 -1
  23. package/dist/routes/skills.js +30 -9
  24. package/dist/routes/skills.js.map +1 -1
  25. package/dist/routes/surface.d.ts.map +1 -1
  26. package/dist/routes/surface.js +3 -1
  27. package/dist/routes/surface.js.map +1 -1
  28. package/dist/routes/work-items.d.ts +3 -0
  29. package/dist/routes/work-items.d.ts.map +1 -1
  30. package/dist/routes/work-items.js +63 -4
  31. package/dist/routes/work-items.js.map +1 -1
  32. package/dist/rules/defaults.d.ts.map +1 -1
  33. package/dist/rules/defaults.js +18 -3
  34. package/dist/rules/defaults.js.map +1 -1
  35. package/dist/rules/dispatcher.d.ts +5 -0
  36. package/dist/rules/dispatcher.d.ts.map +1 -1
  37. package/dist/rules/dispatcher.js +15 -0
  38. package/dist/rules/dispatcher.js.map +1 -1
  39. package/dist/rules/start-coordinator.d.ts.map +1 -1
  40. package/dist/rules/start-coordinator.js +1 -1
  41. package/dist/rules/start-coordinator.js.map +1 -1
  42. package/dist/rules/types.d.ts +1 -0
  43. package/dist/rules/types.d.ts.map +1 -1
  44. package/dist/rules/types.js.map +1 -1
  45. package/dist/rules/validation.d.ts.map +1 -1
  46. package/dist/rules/validation.js +5 -2
  47. package/dist/rules/validation.js.map +1 -1
  48. package/dist/session/checkpoint-capture.d.ts.map +1 -1
  49. package/dist/session/checkpoint-capture.js +1 -1
  50. package/dist/session/checkpoint-capture.js.map +1 -1
  51. package/dist/session/live-sessions.d.ts +33 -0
  52. package/dist/session/live-sessions.d.ts.map +1 -0
  53. package/dist/session/live-sessions.js +28 -0
  54. package/dist/session/live-sessions.js.map +1 -0
  55. package/dist/skills/catalog.d.ts +9 -0
  56. package/dist/skills/catalog.d.ts.map +1 -0
  57. package/dist/skills/catalog.js +45 -0
  58. package/dist/skills/catalog.js.map +1 -0
  59. package/dist/storage/domains/projects/base.d.ts +3 -0
  60. package/dist/storage/domains/projects/base.d.ts.map +1 -1
  61. package/dist/storage/domains/projects/base.js +7 -0
  62. package/dist/storage/domains/projects/base.js.map +1 -1
  63. package/dist/storage/domains/work-items/base.d.ts +19 -14
  64. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  65. package/dist/storage/domains/work-items/base.js +75 -21
  66. package/dist/storage/domains/work-items/base.js.map +1 -1
  67. package/dist/storage/domains/work-items/metrics.d.ts +27 -22
  68. package/dist/storage/domains/work-items/metrics.d.ts.map +1 -1
  69. package/dist/storage/domains/work-items/metrics.js +79 -49
  70. package/dist/storage/domains/work-items/metrics.js.map +1 -1
  71. package/dist/workspace.d.ts +2 -0
  72. package/dist/workspace.d.ts.map +1 -1
  73. package/dist/workspace.js +2 -1
  74. package/dist/workspace.js.map +1 -1
  75. package/factory-skills/factory-rereview/SKILL.md +166 -0
  76. package/factory-skills/factory-triage/SKILL.md +3 -3
  77. package/package.json +6 -6
@@ -1 +1 @@
1
- {"version":3,"file":"event-worker.js","names":["#client","#controller","#github","#storage","#ingestFactoryEvent","#reconcileFactoryState","#reconcileIssuesFactoryState","#pollEventsEnabled","#reconcileIntervalMs","#intervalMs","#now","#dispatch","#leaseOwner","#leaseTtlMs","#leaseProvider","#running","#startedAt","#settings","#schedule","#timer","#stopLeaseRenewal","#inFlight","#hasLease","#leaseKey","#tick","#ensureLease","#poll","#startLeaseRenewal","#leaseRenewalTimer","#discoverRepositories","#pollRepository","#maybeReconcile","#lastReconcileAt","#saveSettings","#isAuthorizedSender"],"sources":["../../../../src/integrations/platform/github/event-worker.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport type { MountedMastraCode } from '@mastra/code-sdk';\nimport { isLeaseProvider, NoopLeaseProvider } from '@mastra/core/events';\nimport type { LeaseProvider, PubSub } from '@mastra/core/events';\nimport { MastraWorker } from '@mastra/core/worker';\nimport type { WorkerDeps } from '@mastra/core/worker';\n\nimport type { IntegrationStorageHandle } from '../../../storage/domains/integrations/base.js';\nimport type { GithubRepositoryPermission } from '../../github/integration.js';\nimport type { GithubIssueReconciler } from '../../github/issue-reconciler.js';\nimport type { GithubPullRequestReconciler, ReconcileRepository } from '../../github/rules.js';\nimport { listPullRequestSubscriptionsForWebhook, retirePullRequestSubscription } from '../../github/subscriptions.js';\nimport type { GithubSubscriptionStorage } from '../../github/subscriptions.js';\nimport { dispatchGithubWebhook } from '../../github/webhook.js';\nimport type {\n GithubWebhookDispatchIntegration,\n GithubWebhookNotification,\n ParsedGithubWebhook,\n} 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 = 20_000;\nconst DEFAULT_RECONCILE_INTERVAL_MS = 5 * 60_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; fullName?: string; installationId: number };\n\nexport type PlatformGithubEventDispatchIntegration = GithubWebhookDispatchIntegration;\n\nexport interface PlatformGithubEventWorkerConfig {\n client: PlatformApiClient;\n controller: MountedMastraCode['controller'];\n github: PlatformGithubEventDispatchIntegration;\n storage: PlatformGithubEventStorage;\n ingestFactoryEvent?: (event: ParsedGithubWebhook) => Promise<unknown>;\n reconcileFactoryState?: GithubPullRequestReconciler;\n /**\n * Optional issue-metadata reconciler. When set, folded into the same\n * reconcile tick as `reconcileFactoryState` so a single lease covers both\n * writers of card state for the currently discovered repositories.\n */\n reconcileIssuesFactoryState?: GithubIssueReconciler;\n /** When false the worker skips event tailing and only runs the reconcile sweep. */\n pollEventsEnabled?: boolean;\n intervalMs?: number;\n reconcileIntervalMs?: 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 #reconcileFactoryState: GithubPullRequestReconciler | undefined;\n readonly #reconcileIssuesFactoryState: GithubIssueReconciler | undefined;\n readonly #pollEventsEnabled: boolean;\n readonly #reconcileIntervalMs: number;\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 #lastReconcileAt = 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.#reconcileFactoryState = config.reconcileFactoryState;\n this.#reconcileIssuesFactoryState = config.reconcileIssuesFactoryState;\n this.#pollEventsEnabled = config.pollEventsEnabled ?? true;\n this.#reconcileIntervalMs = config.reconcileIntervalMs ?? DEFAULT_RECONCILE_INTERVAL_MS;\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 // Reconcile-only mode has no event tail to keep fresh, so tick on the\n // slower reconcile cadence instead of the polling interval.\n let retryInMs = this.#pollEventsEnabled ? this.#intervalMs : this.#reconcileIntervalMs;\n\n if (this.#pollEventsEnabled) {\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\n await this.#maybeReconcile(repositories);\n\n return retryInMs;\n }\n\n /**\n * State-based safety net: event tailing can miss merges (cursor gaps,\n * downtime, terminally failed decisions), so on a slower cadence we compare\n * still-open PR cards against actual GitHub state and replay missed merges\n * through the normal ingress, which dedupes against the event path.\n */\n async #maybeReconcile(repositories: Repository[]): Promise<void> {\n if (!this.#reconcileFactoryState || !this.#running || !this.#hasLease) return;\n const now = this.#now();\n if (now - this.#lastReconcileAt < this.#reconcileIntervalMs) return;\n // Advance the clock before sweeping so a persistently failing sweep stays\n // on cadence instead of retrying every poll tick.\n this.#lastReconcileAt = now;\n const targets: ReconcileRepository[] = repositories.flatMap(repository =>\n repository.fullName\n ? [{ id: repository.id, fullName: repository.fullName, installationId: repository.installationId }]\n : [],\n );\n if (targets.length === 0) {\n this.deps?.logger.debug('Platform GitHub pull request reconcile skipped: no named repositories');\n return;\n }\n this.deps?.logger.debug('Platform GitHub pull request reconcile sweep starting', {\n candidateRepositories: targets.length,\n });\n const startedAt = Date.now();\n try {\n // `counts.repositories` is the factory-configured subset actually swept;\n // `candidateRepositories` is everything the installations exposed.\n const { errors, ...counts } = await this.#reconcileFactoryState(targets);\n const context = { ...counts, candidateRepositories: targets.length, durationMs: Date.now() - startedAt };\n if (counts.failed > 0) {\n this.deps?.logger.warn('Platform GitHub pull request reconcile sweep completed with failures', {\n ...context,\n errors,\n });\n } else if (counts.merged > 0 || counts.closed > 0) {\n this.deps?.logger.info('Platform GitHub pull request reconcile replayed missed merges/closes', context);\n } else {\n this.deps?.logger.info('Platform GitHub pull request reconcile sweep completed', context);\n }\n } catch (error) {\n this.deps?.logger.error('Platform GitHub pull request reconcile failed', {\n repositories: targets.length,\n durationMs: Date.now() - startedAt,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n\n // Same tick as the PR reconciler: repository set already discovered, lease\n // already held, cadence already gated. Issues have no closed-webhook replay\n // so this only patches drifted metadata (state/author/assignees/labels).\n if (!this.#reconcileIssuesFactoryState) return;\n const issueStartedAt = Date.now();\n try {\n const { errors, ...counts } = await this.#reconcileIssuesFactoryState(targets);\n const context = { ...counts, candidateRepositories: targets.length, durationMs: Date.now() - issueStartedAt };\n if (counts.failed > 0) {\n this.deps?.logger.warn('Platform GitHub issue reconcile sweep completed with failures', {\n ...context,\n errors,\n });\n } else if (counts.updated > 0) {\n this.deps?.logger.info('Platform GitHub issue reconcile patched stale metadata', context);\n } else {\n this.deps?.logger.debug('Platform GitHub issue reconcile sweep completed', context);\n }\n } catch (error) {\n this.deps?.logger.error('Platform GitHub issue reconcile failed', {\n repositories: targets.length,\n durationMs: Date.now() - issueStartedAt,\n error: error instanceof Error ? error.message : String(error),\n });\n }\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: Array<{ id: number; fullName?: string }> }>(\n 'GET',\n `${API_PREFIX}/installations/${installation.installationId}/repositories`,\n );\n for (const repository of page.repositories) {\n repositories.set(repository.id, { ...repository, installationId: installation.installationId });\n }\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 pollStartedAt = performance.now();\n const page = await this.#client.request<{ events: EventLogEntry[]; nextCursor: string | null }>(\n 'GET',\n `${API_PREFIX}/repositories/${repositoryId}/events?${query}`,\n );\n this.deps?.logger.debug('Platform GitHub repository event poll completed', {\n repositoryId,\n eventCount: page.events.length,\n latencyMs: Math.round(performance.now() - pollStartedAt),\n });\n if (page.events.length === 0 || !page.nextCursor) return;\n\n for (const event of page.events) {\n if (!this.#running || !this.#hasLease) return;\n 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 github: this.#github,\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"],"mappings":";;;;;;;AAuBA,MAAM,aAAa;AACnB,MAAM,2BAA2B;AACjC,MAAM,gCAAgC,IAAI;AAC1C,MAAM,kBAAkB;AACxB,MAAM,mBAAmB;AACzB,MAAM,gBAAgB;AACtB,MAAM,iBAAiB;AACvB,MAAM,mCAAmB,IAAI,IAAI;CAC/B;CACA;CACA;CACA;CACA;AACF,CAAC;AACD,MAAM,qCAAqB,IAAI,IAAI;CACjC;CACA;CACA;CACA;AACF,CAAC;AACD,MAAM,kCAAkB,IAAI,IAAI,CAAC,qBAAqB,2BAA2B,CAAC;AAClF,MAAM,yCAAyB,IAAI,IAAgC;CAAC;CAAS;CAAY;AAAO,CAAC;AACjG,MAAM,8BAA8B;AA8CpC,IAAa,4BAAb,cAA+C,aAAa;CAC1D,OAAgB;CAEhB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,cAAuB,WAAW;CAElC,WAAW;CACX;CACA;CACA;CACA,iBAAgC;CAChC;CACA,YAAY;CACZ,aAAa;CACb,mBAAmB;CACnB,YAA+C;EAAE,SAAS;EAAG,cAAc,CAAC;CAAE;CAE9E,YAAY,QAAyC;EACnD,MAAM;EACN,KAAKA,UAAU,OAAO;EACtB,KAAKC,cAAc,OAAO;EAC1B,KAAKC,UAAU,OAAO;EACtB,KAAKC,WAAW,OAAO;EACvB,KAAKC,sBAAsB,OAAO;EAClC,KAAKC,yBAAyB,OAAO;EACrC,KAAKC,+BAA+B,OAAO;EAC3C,KAAKC,qBAAqB,OAAO,qBAAqB;EACtD,KAAKC,uBAAuB,OAAO,uBAAuB;EAC1D,KAAKC,cAAc,OAAO,cAAc;EACxC,IAAI,CAAC,OAAO,SAAS,KAAKA,WAAW,KAAK,KAAKA,eAAe,GAC5D,MAAM,IAAI,MAAM,mEAAmE;EAErF,KAAKI,cAAc,KAAK,IAAI,kBAAkB,KAAKJ,cAAc,CAAC;EAClE,KAAKC,OAAO,OAAO,OAAO,KAAK;EAC/B,KAAKC,YAAY,OAAO,YAAY;CACtC;CAEA,MAAM,KAAK,MAAiC;EAC1C,MAAM,MAAM,KAAK,IAAI;EACrB,KAAKG,iBAAiB,iBAAiB,KAAK,MAAM;CACpD;CAEA,MAAM,QAAuB;EAC3B,IAAI,KAAKC,UAAU;EACnB,IAAI,CAAC,KAAK,MAAM,MAAM,IAAI,MAAM,uDAAuD;EAEvF,KAAKC,aAAa,KAAKN,KAAK,IAAI;EAChC,KAAKO,YAAY,kBAAkB,MAAM,KAAKd,SAAS,SAAS,IAAI,eAAe,cAAc,CAAC;EAClG,KAAKY,WAAW;EAChB,KAAK,KAAK,OAAO,KAAK,yCAAyC;GAC7D,YAAY,KAAKN;GACjB,YAAY,KAAKI;EACnB,CAAC;EACD,KAAKK,UAAU,CAAC;CAClB;CAEA,MAAM,OAAsB;EAC1B,IAAI,CAAC,KAAKH,UAAU;EACpB,KAAKA,WAAW;EAChB,IAAI,KAAKI,QAAQ,aAAa,KAAKA,MAAM;EACzC,KAAKA,SAAS,KAAA;EACd,KAAKC,kBAAkB;EACvB,MAAM,KAAKC;EACX,IAAI,KAAKC,WAAW;GAClB,MAAM,KAAKR,eAAe,aAAa,KAAKS,UAAU,GAAG,KAAKX,WAAW,CAAC,CAAC,YAAY,KAAA,CAAS;GAChG,KAAKU,YAAY;EACnB;CACF;CAEA,IAAI,YAAqB;EACvB,OAAO,KAAKP;CACd;CAEA,UAAU,SAAuB;EAC/B,IAAI,CAAC,KAAKA,UAAU;EACpB,KAAKI,SAAS,iBAAiB;GAC7B,KAAKA,SAAS,KAAA;GACd,MAAM,MAAM,KAAKK,MAAM;GACvB,KAAKH,YAAY;GACjB,IAAS,cAAc;IACrB,IAAI,KAAKA,cAAc,KAAK,KAAKA,YAAY,KAAA;GAC/C,CAAC;EACH,GAAG,OAAO;EACV,KAAKF,OAAO,QAAQ;CACtB;CAEA,MAAMK,QAAuB;EAC3B,IAAI,YAAY,KAAKf;EACrB,IAAI;GACF,IAAI,CAAE,MAAM,KAAKgB,aAAa,GAAI;GAClC,YAAY,MAAM,KAAKC,MAAM;EAC/B,SAAS,OAAO;GACd,YAAY,WAAW,OAAO,KAAKjB,WAAW;GAC9C,KAAK,MAAM,OAAO,MAAM,8CAA8C;IACpE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;IAC5D,WAAW;GACb,CAAC;EACH,UAAU;GACR,KAAKS,UAAU,SAAS;EAC1B;CACF;CAEA,MAAMO,eAAiC;EACrC,IAAI,KAAKH,WAAW,OAAO;EAC3B,MAAM,SAAS,MAAM,KAAKR,eAAe,aAAa,KAAKS,UAAU,GAAG,KAAKX,aAAa,KAAKC,WAAW;EAC1G,KAAKS,YAAY,OAAO;EACxB,IAAI,KAAKA,WAAW,KAAKK,mBAAmB;EAC5C,OAAO,KAAKL;CACd;CAEA,qBAA2B;EACzB,IAAI,KAAKM,oBAAoB;EAC7B,KAAKA,qBAAqB,kBAClB;GACJ,KAAUd,eACP,WAAW,KAAKS,UAAU,GAAG,KAAKX,aAAa,KAAKC,WAAW,CAAC,CAChE,MAAK,YAAW;IACf,IAAI,CAAC,SAAS;KACZ,KAAKS,YAAY;KACjB,KAAKF,kBAAkB;IACzB;GACF,CAAC,CAAC,CACD,OAAM,UAAS;IACd,KAAKE,YAAY;IACjB,KAAKF,kBAAkB;IACvB,KAAK,MAAM,OAAO,KAAK,sDAAsD,EAC3E,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAC9D,CAAC;GACH,CAAC;EACL,GACA,KAAK,MAAM,KAAKP,cAAc,CAAC,CACjC;EACA,KAAKe,mBAAmB,QAAQ;CAClC;CAEA,oBAA0B;EACxB,IAAI,KAAKA,oBAAoB,cAAc,KAAKA,kBAAkB;EAClE,KAAKA,qBAAqB,KAAA;CAC5B;CAEA,MAAMF,QAAyB;EAC7B,MAAM,eAAe,MAAM,KAAKG,sBAAsB;EAGtD,IAAI,YAAY,KAAKtB,qBAAqB,KAAKE,cAAc,KAAKD;EAElE,IAAI,KAAKD,oBACP,KAAK,MAAM,cAAc,cAAc;GACrC,IAAI,CAAC,KAAKQ,YAAY,CAAC,KAAKO,WAAW;GACvC,IAAI;IACF,MAAM,KAAKQ,gBAAgB,WAAW,EAAE;GAC1C,SAAS,OAAO;IACd,MAAM,QAAQ,WAAW,OAAO,KAAKrB,WAAW;IAChD,YAAY,KAAK,IAAI,WAAW,KAAK;IACrC,KAAK,MAAM,OAAO,MAAM,mDAAmD;KACzE,cAAc,WAAW;KACzB,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KAC5D,WAAW;IACb,CAAC;IACD,IAAI,iBAAiB,oBAAoB,MAAM,WAAW,KAAK;GACjE;EACF;EAGF,MAAM,KAAKsB,gBAAgB,YAAY;EAEvC,OAAO;CACT;;;;;;;CAQA,MAAMA,gBAAgB,cAA2C;EAC/D,IAAI,CAAC,KAAK1B,0BAA0B,CAAC,KAAKU,YAAY,CAAC,KAAKO,WAAW;EACvE,MAAM,MAAM,KAAKZ,KAAK;EACtB,IAAI,MAAM,KAAKsB,mBAAmB,KAAKxB,sBAAsB;EAG7D,KAAKwB,mBAAmB;EACxB,MAAM,UAAiC,aAAa,SAAQ,eAC1D,WAAW,WACP,CAAC;GAAE,IAAI,WAAW;GAAI,UAAU,WAAW;GAAU,gBAAgB,WAAW;EAAe,CAAC,IAChG,CAAC,CACP;EACA,IAAI,QAAQ,WAAW,GAAG;GACxB,KAAK,MAAM,OAAO,MAAM,uEAAuE;GAC/F;EACF;EACA,KAAK,MAAM,OAAO,MAAM,yDAAyD,EAC/E,uBAAuB,QAAQ,OACjC,CAAC;EACD,MAAM,YAAY,KAAK,IAAI;EAC3B,IAAI;GAGF,MAAM,EAAE,QAAQ,GAAG,WAAW,MAAM,KAAK3B,uBAAuB,OAAO;GACvE,MAAM,UAAU;IAAE,GAAG;IAAQ,uBAAuB,QAAQ;IAAQ,YAAY,KAAK,IAAI,IAAI;GAAU;GACvG,IAAI,OAAO,SAAS,GAClB,KAAK,MAAM,OAAO,KAAK,wEAAwE;IAC7F,GAAG;IACH;GACF,CAAC;QACI,IAAI,OAAO,SAAS,KAAK,OAAO,SAAS,GAC9C,KAAK,MAAM,OAAO,KAAK,wEAAwE,OAAO;QAEtG,KAAK,MAAM,OAAO,KAAK,0DAA0D,OAAO;EAE5F,SAAS,OAAO;GACd,KAAK,MAAM,OAAO,MAAM,iDAAiD;IACvE,cAAc,QAAQ;IACtB,YAAY,KAAK,IAAI,IAAI;IACzB,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAC9D,CAAC;EACH;EAKA,IAAI,CAAC,KAAKC,8BAA8B;EACxC,MAAM,iBAAiB,KAAK,IAAI;EAChC,IAAI;GACF,MAAM,EAAE,QAAQ,GAAG,WAAW,MAAM,KAAKA,6BAA6B,OAAO;GAC7E,MAAM,UAAU;IAAE,GAAG;IAAQ,uBAAuB,QAAQ;IAAQ,YAAY,KAAK,IAAI,IAAI;GAAe;GAC5G,IAAI,OAAO,SAAS,GAClB,KAAK,MAAM,OAAO,KAAK,iEAAiE;IACtF,GAAG;IACH;GACF,CAAC;QACI,IAAI,OAAO,UAAU,GAC1B,KAAK,MAAM,OAAO,KAAK,0DAA0D,OAAO;QAExF,KAAK,MAAM,OAAO,MAAM,mDAAmD,OAAO;EAEtF,SAAS,OAAO;GACd,KAAK,MAAM,OAAO,MAAM,0CAA0C;IAChE,cAAc,QAAQ;IACtB,YAAY,KAAK,IAAI,IAAI;IACzB,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAC9D,CAAC;EACH;CACF;CAEA,MAAMuB,wBAA+C;EACnD,MAAM,SAAS,MAAM,KAAK7B,QAAQ,QAM/B,OAAO,GAAG,WAAW,eAAe;EACvC,MAAM,+BAAe,IAAI,IAAwB;EAEjD,KAAK,MAAM,gBAAgB,OAAO,eAAe;GAC/C,IAAI,CAAC,aAAa,UAAU,aAAa,aAAa;GACtD,MAAM,OAAO,MAAM,KAAKA,QAAQ,QAC9B,OACA,GAAG,WAAW,iBAAiB,aAAa,eAAe,cAC7D;GACA,KAAK,MAAM,cAAc,KAAK,cAC5B,aAAa,IAAI,WAAW,IAAI;IAAE,GAAG;IAAY,gBAAgB,aAAa;GAAe,CAAC;EAElG;EAEA,OAAO,CAAC,GAAG,aAAa,OAAO,CAAC;CAClC;CAEA,MAAM8B,gBAAgB,cAAqC;EACzD,MAAM,MAAM,OAAO,YAAY;EAC/B,IAAI,CAAC,KAAKb,UAAU,aAAa,MAAM;GACrC,KAAKA,UAAU,aAAa,OAAO,EAAE,gBAAgB,KAAKD,WAAW;GACrE,MAAM,KAAKiB,cAAc;EAC3B;EAEA,OAAO,KAAKlB,YAAY,KAAKO,WAAW;GACtC,MAAM,SAAsB,KAAKL,UAAU,aAAa;GACxD,MAAM,QAAQ,IAAI,gBAAgB,EAAE,OAAO,OAAO,eAAe,EAAE,CAAC;GACpE,IAAI,kBAAkB,QAAQ,MAAM,IAAI,gBAAgB,OAAO,YAAY;QACtE,MAAM,IAAI,kBAAkB,OAAO,OAAO,cAAc,CAAC;GAE9D,MAAM,gBAAgB,YAAY,IAAI;GACtC,MAAM,OAAO,MAAM,KAAKjB,QAAQ,QAC9B,OACA,GAAG,WAAW,gBAAgB,aAAa,UAAU,OACvD;GACA,KAAK,MAAM,OAAO,MAAM,mDAAmD;IACzE;IACA,YAAY,KAAK,OAAO;IACxB,WAAW,KAAK,MAAM,YAAY,IAAI,IAAI,aAAa;GACzD,CAAC;GACD,IAAI,KAAK,OAAO,WAAW,KAAK,CAAC,KAAK,YAAY;GAElD,KAAK,MAAM,SAAS,KAAK,QAAQ;IAC/B,IAAI,CAAC,KAAKe,YAAY,CAAC,KAAKO,WAAW;IACvC,MAAM,SAAS,WAAW,KAAK;IAC/B,IAAI,CAAC,QAAQ;KACX,KAAK,MAAM,OAAO,KAAK,wDAAwD;MAC7E;MACA,SAAS,MAAM;KACjB,CAAC;KACD;IACF;IACA,IAAI,sBAAsB,MAAM,GAC9B,MAAM,KAAKlB,sBAAsB,MAAM;IAEzC,MAAM,SAAS,MAAM,KAAKO,UAAU,QAAQ;KAC1C,YAAY,KAAKV;KACjB,oBAAoB,QAAQ,YAC1B,uCAAuC,QAAQ,SAAS,KAAKC,QAAQ,kBAAkB;KACzF,qBAAqB,IAAI,WACvB,8BAA8B,IAAI,QAAQ,KAAKA,QAAQ,kBAAkB;KAC3E,QAAQ,KAAKA;KACb,qBAAoB,iBAAgB,KAAKgC,oBAAoB,YAAY;KACzE,gBAAgB,cAAc,UAAU;MACtC,KAAK,MAAM,OAAO,MAAM,4DAA4D;OAClF,gBAAgB,aAAa;OAC7B,YAAY,aAAa;OACzB,UAAU,aAAa;OACvB,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;MAC9D,CAAC;KACH;IACF,CAAC;IACD,IAAI,OAAO,SAAS,GAClB,MAAM,IAAI,MACR,yBAAyB,MAAM,WAAW,cAAc,OAAO,OAAO,uBACxE;GAEJ;GAEA,IAAI,KAAK,gBAAgB,kBAAkB,SAAS,OAAO,eAAe,KAAA,IAAY;GACtF,KAAKjB,UAAU,aAAa,OAAO,EAAE,cAAc,KAAK,WAAW;GACnE,MAAM,KAAKgB,cAAc;EAC3B;CACF;CAEA,MAAMC,oBAAoB,cAA2D;EACnF,IAAI,CAAC,mBAAmB,IAAI,aAAa,IAAI,GAAG,OAAO;EACvD,MAAM,SAAS,aAAa,SAAS;EACrC,MAAM,aAAa,aAAa,SAAS;EACzC,IAAI,CAAC,UAAU,CAAC,YAAY,OAAO;EACnC,IAAI,gBAAgB,IAAI,MAAM,GAAG,OAAO;EAExC,MAAM,kBAAkB,IAAI,gBAAgB;EAC5C,MAAM,UAAU,iBAAiB,gBAAgB,MAAM,GAAG,2BAA2B;EACrF,IAAI;GACF,MAAM,aAAa,MAAM,KAAKhC,QAAQ,oCACpC,aAAa,SAAS,gBACtB,YACA,QACA,gBAAgB,MAClB;GACA,OAAO,eAAe,KAAA,KAAa,uBAAuB,IAAI,UAAU;EAC1E,QAAQ;GACN,OAAO;EACT,UAAU;GACR,aAAa,OAAO;EACtB;CACF;CAEA,MAAM+B,gBAA+B;EACnC,MAAM,KAAK9B,SAAS,SAAS,KAAK,eAAe,gBAAgB,KAAKc,SAAS;CACjF;CAEA,YAAoB;EAClB,OAAO,GAAG,KAAK,KAAK,GAAG,KAAKd,SAAS;CACvC;AACF;AAEA,SAAS,iBAAiB,QAA+B;CACvD,MAAM,cAAe,OAA2E;CAChG,IAAI,OAAO,gBAAgB,YAAY,OAAO,YAAY,KAAK,MAAM,KAAK;CAC1E,OAAO,gBAAgB,MAAM,IAAI,SAAS;AAC5C;AAEA,SAAS,kBAAkB,OAAoF;CAC7G,IAAI,CAAC,SAAS,MAAM,YAAY,KAAK,CAAC,MAAM,gBAAgB,OAAO,MAAM,iBAAiB,UACxF,OAAO;EAAE,SAAS;EAAG,cAAc,CAAC;CAAE;CAExC,OAAO;EAAE,SAAS;EAAG,cAAc,EAAE,GAAG,MAAM,aAAa;CAAE;AAC/D;AAEA,SAAS,sBAAsB,OAAqC;CAClE,QAAQ,MAAM,UAAU,YAAY,MAAM,UAAU,mBAAmB,MAAM,QAAQ,WAAW;AAClG;AAEA,SAAS,WAAW,OAAkD;CACpE,IACE,CAAC,MAAM,MACP,CAAC,MAAM,cACP,CAAC,iBAAiB,IAAI,MAAM,KAAK,KACjC,CAAC,MAAM,WACP,OAAO,MAAM,YAAY,YACzB,MAAM,QAAQ,MAAM,OAAO,GAE3B,OAAO;CAET,OAAO;EACL,OAAO,MAAM;EACb,YAAY,MAAM;EAClB,SAAS,MAAM;CACjB;AACF;AAEA,SAAS,WAAW,OAAgB,YAA4B;CAC9D,IAAI,iBAAiB,oBAAoB,MAAM,WAAW,OAAO,MAAM,sBAAsB,MAC3F,OAAO,KAAK,IAAI,YAAY,MAAM,oBAAoB,GAAK;CAE7D,OAAO;AACT"}
1
+ {"version":3,"file":"event-worker.js","names":["#client","#controller","#github","#storage","#ingestFactoryEvent","#reconcileFactoryState","#reconcileIssuesFactoryState","#pollEventsEnabled","#reconcileIntervalMs","#intervalMs","#now","#dispatch","#leaseOwner","#leaseTtlMs","#leaseProvider","#running","#startedAt","#settings","#schedule","#timer","#stopLeaseRenewal","#inFlight","#hasLease","#leaseKey","#tick","#ensureLease","#poll","#startLeaseRenewal","#leaseRenewalTimer","#discoverRepositories","#pollRepository","#maybeReconcile","#lastReconcileAt","#saveSettings","#isAuthorizedSender"],"sources":["../../../../src/integrations/platform/github/event-worker.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport type { MountedMastraCode } from '@mastra/code-sdk';\nimport { isLeaseProvider, NoopLeaseProvider } from '@mastra/core/events';\nimport type { LeaseProvider, PubSub } from '@mastra/core/events';\nimport { MastraWorker } from '@mastra/core/worker';\nimport type { WorkerDeps } from '@mastra/core/worker';\n\nimport type { IntegrationStorageHandle } from '../../../storage/domains/integrations/base.js';\nimport type { GithubRepositoryPermission } from '../../github/integration.js';\nimport type { GithubIssueReconciler } from '../../github/issue-reconciler.js';\nimport type { GithubPullRequestReconciler, ReconcileRepository } from '../../github/rules.js';\nimport { listPullRequestSubscriptionsForWebhook, retirePullRequestSubscription } from '../../github/subscriptions.js';\nimport { dispatchGithubWebhook } from '../../github/webhook.js';\nimport type {\n GithubWebhookDispatchIntegration,\n GithubWebhookNotification,\n ParsedGithubWebhook,\n} 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 = 20_000;\nconst DEFAULT_RECONCILE_INTERVAL_MS = 5 * 60_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; fullName?: string; installationId: number };\n\nexport type PlatformGithubEventDispatchIntegration = GithubWebhookDispatchIntegration;\n\nexport interface PlatformGithubEventWorkerConfig {\n client: PlatformApiClient;\n controller: MountedMastraCode['controller'];\n github: PlatformGithubEventDispatchIntegration;\n storage: PlatformGithubEventStorage;\n ingestFactoryEvent?: (event: ParsedGithubWebhook) => Promise<unknown>;\n reconcileFactoryState?: GithubPullRequestReconciler;\n /**\n * Optional issue-metadata reconciler. When set, folded into the same\n * reconcile tick as `reconcileFactoryState` so a single lease covers both\n * writers of card state for the currently discovered repositories.\n */\n reconcileIssuesFactoryState?: GithubIssueReconciler;\n /** When false the worker skips event tailing and only runs the reconcile sweep. */\n pollEventsEnabled?: boolean;\n intervalMs?: number;\n reconcileIntervalMs?: 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 #reconcileFactoryState: GithubPullRequestReconciler | undefined;\n readonly #reconcileIssuesFactoryState: GithubIssueReconciler | undefined;\n readonly #pollEventsEnabled: boolean;\n readonly #reconcileIntervalMs: number;\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 #lastReconcileAt = 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.#reconcileFactoryState = config.reconcileFactoryState;\n this.#reconcileIssuesFactoryState = config.reconcileIssuesFactoryState;\n this.#pollEventsEnabled = config.pollEventsEnabled ?? true;\n this.#reconcileIntervalMs = config.reconcileIntervalMs ?? DEFAULT_RECONCILE_INTERVAL_MS;\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 // Reconcile-only mode has no event tail to keep fresh, so tick on the\n // slower reconcile cadence instead of the polling interval.\n let retryInMs = this.#pollEventsEnabled ? this.#intervalMs : this.#reconcileIntervalMs;\n\n if (this.#pollEventsEnabled) {\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\n await this.#maybeReconcile(repositories);\n\n return retryInMs;\n }\n\n /**\n * State-based safety net: event tailing can miss merges (cursor gaps,\n * downtime, terminally failed decisions), so on a slower cadence we compare\n * still-open PR cards against actual GitHub state and replay missed merges\n * through the normal ingress, which dedupes against the event path.\n */\n async #maybeReconcile(repositories: Repository[]): Promise<void> {\n if (!this.#reconcileFactoryState || !this.#running || !this.#hasLease) return;\n const now = this.#now();\n if (now - this.#lastReconcileAt < this.#reconcileIntervalMs) return;\n // Advance the clock before sweeping so a persistently failing sweep stays\n // on cadence instead of retrying every poll tick.\n this.#lastReconcileAt = now;\n const targets: ReconcileRepository[] = repositories.flatMap(repository =>\n repository.fullName\n ? [{ id: repository.id, fullName: repository.fullName, installationId: repository.installationId }]\n : [],\n );\n if (targets.length === 0) {\n this.deps?.logger.debug('Platform GitHub pull request reconcile skipped: no named repositories');\n return;\n }\n this.deps?.logger.debug('Platform GitHub pull request reconcile sweep starting', {\n candidateRepositories: targets.length,\n });\n const startedAt = Date.now();\n try {\n // `counts.repositories` is the factory-configured subset actually swept;\n // `candidateRepositories` is everything the installations exposed.\n const { errors, ...counts } = await this.#reconcileFactoryState(targets);\n const context = { ...counts, candidateRepositories: targets.length, durationMs: Date.now() - startedAt };\n if (counts.failed > 0) {\n this.deps?.logger.warn('Platform GitHub pull request reconcile sweep completed with failures', {\n ...context,\n errors,\n });\n } else if (counts.merged > 0 || counts.closed > 0) {\n this.deps?.logger.info('Platform GitHub pull request reconcile replayed missed merges/closes', context);\n } else {\n this.deps?.logger.info('Platform GitHub pull request reconcile sweep completed', context);\n }\n } catch (error) {\n this.deps?.logger.error('Platform GitHub pull request reconcile failed', {\n repositories: targets.length,\n durationMs: Date.now() - startedAt,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n\n // Same tick as the PR reconciler: repository set already discovered, lease\n // already held, cadence already gated. Issues have no closed-webhook replay\n // so this only patches drifted metadata (state/author/assignees/labels).\n if (!this.#reconcileIssuesFactoryState) return;\n const issueStartedAt = Date.now();\n try {\n const { errors, ...counts } = await this.#reconcileIssuesFactoryState(targets);\n const context = { ...counts, candidateRepositories: targets.length, durationMs: Date.now() - issueStartedAt };\n if (counts.failed > 0) {\n this.deps?.logger.warn('Platform GitHub issue reconcile sweep completed with failures', {\n ...context,\n errors,\n });\n } else if (counts.updated > 0) {\n this.deps?.logger.info('Platform GitHub issue reconcile patched stale metadata', context);\n } else {\n this.deps?.logger.debug('Platform GitHub issue reconcile sweep completed', context);\n }\n } catch (error) {\n this.deps?.logger.error('Platform GitHub issue reconcile failed', {\n repositories: targets.length,\n durationMs: Date.now() - issueStartedAt,\n error: error instanceof Error ? error.message : String(error),\n });\n }\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: Array<{ id: number; fullName?: string }> }>(\n 'GET',\n `${API_PREFIX}/installations/${installation.installationId}/repositories`,\n );\n for (const repository of page.repositories) {\n repositories.set(repository.id, { ...repository, installationId: installation.installationId });\n }\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 pollStartedAt = performance.now();\n const page = await this.#client.request<{ events: EventLogEntry[]; nextCursor: string | null }>(\n 'GET',\n `${API_PREFIX}/repositories/${repositoryId}/events?${query}`,\n );\n this.deps?.logger.debug('Platform GitHub repository event poll completed', {\n repositoryId,\n eventCount: page.events.length,\n latencyMs: Math.round(performance.now() - pollStartedAt),\n });\n if (page.events.length === 0 || !page.nextCursor) return;\n\n for (const event of page.events) {\n if (!this.#running || !this.#hasLease) return;\n 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 (isFactoryIngestedEvent(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 github: this.#github,\n isAuthorizedSender: notification => this.#isAuthorizedSender(notification),\n onTargetError: (subscription, error) => {\n this.deps?.logger.error('Platform GitHub event delivery failed for a subscription', {\n deliveryId: event.deliveryId,\n subscriptionId: subscription.id,\n resourceId: subscription.resourceId,\n threadId: subscription.threadId,\n error: error instanceof Error ? error.message : String(error),\n });\n },\n });\n if (result.failed > 0) {\n this.deps?.logger.warn('Platform GitHub event completed with failed subscription deliveries', {\n repositoryId,\n deliveryId: event.deliveryId,\n delivered: result.delivered,\n failed: result.failed,\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\n// Events the polling worker forwards to the factory rules engine. Closures\n// let the reconciler finalize cards; `synchronize` and `review_requested` on a\n// pull request are the two triggers the review board's re-review path listens\n// for. Direct-webhook consumers ingest every parsed event; the platform path\n// gates because most other events (comments, reviews, edits) only interest the\n// subscription dispatcher, not the factory rules.\nfunction isFactoryIngestedEvent(event: ParsedGithubWebhook): boolean {\n if ((event.event === 'issues' || event.event === 'pull_request') && event.payload.action === 'closed') {\n return true;\n }\n if (event.event === 'pull_request') {\n const action = event.payload.action;\n if (action === 'synchronize' || action === 'review_requested') return true;\n }\n return false;\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"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/routes/projects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIpD,OAAO,KAAK,EAEV,sBAAsB,EAEvB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,KAAK,EAEV,oBAAoB,EAGrB,MAAM,2CAA2C,CAAC;AACnD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AA2InC,MAAM,WAAW,iBAAkB,SAAQ,iBAAiB;IAC1D,wDAAwD;IACxD,QAAQ,EAAE,sBAAsB,CAAC;IACjC,2EAA2E;IAC3E,aAAa,EAAE,oBAAoB,CAAC;IACpC,oEAAoE;IACpE,4BAA4B,CAAC,EAAE,MAAM,EAAE,CAAC;CACzC;AAED,qBAAa,aAAc,SAAQ,KAAK,CAAC,iBAAiB,CAAC;;gBAG7C,IAAI,EAAE,iBAAiB;IA8DnC,MAAM,IAAI,QAAQ,EAAE;CAoOrB"}
1
+ {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/routes/projects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIpD,OAAO,KAAK,EAEV,sBAAsB,EAEvB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,KAAK,EAEV,oBAAoB,EAGrB,MAAM,2CAA2C,CAAC;AACnD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AA+InC,MAAM,WAAW,iBAAkB,SAAQ,iBAAiB;IAC1D,wDAAwD;IACxD,QAAQ,EAAE,sBAAsB,CAAC;IACjC,2EAA2E;IAC3E,aAAa,EAAE,oBAAoB,CAAC;IACpC,oEAAoE;IACpE,4BAA4B,CAAC,EAAE,MAAM,EAAE,CAAC;CACzC;AAED,qBAAa,aAAc,SAAQ,KAAK,CAAC,iBAAiB,CAAC;;gBAG7C,IAAI,EAAE,iBAAiB;IA8DnC,MAAM,IAAI,QAAQ,EAAE;CAoOrB"}
@@ -61,6 +61,10 @@ function parseUpdateInput(value) {
61
61
  if (typeof input.slackWorkItemsEnabled !== "boolean") return null;
62
62
  patch.slackWorkItemsEnabled = input.slackWorkItemsEnabled;
63
63
  }
64
+ if (input.autoRunEnabled !== void 0) {
65
+ if (typeof input.autoRunEnabled !== "boolean") return null;
66
+ patch.autoRunEnabled = input.autoRunEnabled;
67
+ }
64
68
  return Object.keys(patch).length > 0 ? patch : null;
65
69
  }
66
70
  function parseConnectionInput(value) {
@@ -1 +1 @@
1
- {"version":3,"file":"projects.js","names":["#versionControlIntegrationIds","#projects","#sourceControl","#handles","#project","#findConnection","#findProjectRepository","#repositoryPayload","#resolveTenant"],"sources":["../../src/routes/projects.ts"],"sourcesContent":["import type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport type {\n CreateFactoryProjectInput,\n FactoryProjectsStorage,\n UpdateFactoryProjectInput,\n} from '../storage/domains/projects/base.js';\nimport type {\n ProjectRepository,\n SourceControlStorage,\n SourceControlStorageHandle,\n UpdateProjectRepositoryInput,\n} from '../storage/domains/source-control/base.js';\nimport type { RouteDependencies } from './route.js';\nimport { Route } from './route.js';\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst MAX_NAME_LENGTH = 200;\nconst MAX_DESCRIPTION_LENGTH = 2_000;\nconst MAX_SETUP_COMMAND_LENGTH = 2_000;\nconst MAX_BRANCH_LENGTH = 255;\nconst MAX_SANDBOX_PROVIDER_LENGTH = 100;\nconst MAX_SANDBOX_WORKDIR_LENGTH = 1_000;\nconst CONTROL_CHAR_RE = /[\\0-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/;\n\nfunction loose(context: unknown): Context {\n return context as Context;\n}\n\nasync function readJson(context: Context): Promise<unknown | undefined> {\n try {\n return await context.req.json();\n } catch {\n return undefined;\n }\n}\n\nfunction parseCreateInput(value: unknown): CreateFactoryProjectInput | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Record<string, unknown>;\n if (typeof input.name !== 'string') return null;\n const name = input.name.trim();\n if (!name || name.length > MAX_NAME_LENGTH) return null;\n if (input.description !== undefined && input.description !== null && typeof input.description !== 'string')\n return null;\n const description = typeof input.description === 'string' ? input.description.trim() || null : null;\n if (description && description.length > MAX_DESCRIPTION_LENGTH) return null;\n return { name, description };\n}\n\nfunction parseUpdateInput(value: unknown): UpdateFactoryProjectInput | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Record<string, unknown>;\n const patch: UpdateFactoryProjectInput = {};\n if (input.name !== undefined) {\n if (typeof input.name !== 'string') return null;\n const name = input.name.trim();\n if (!name || name.length > MAX_NAME_LENGTH) return null;\n patch.name = name;\n }\n if (input.description !== undefined) {\n if (input.description !== null && typeof input.description !== 'string') return null;\n const description = typeof input.description === 'string' ? input.description.trim() || null : null;\n if (description && description.length > MAX_DESCRIPTION_LENGTH) return null;\n patch.description = description;\n }\n if (input.defaultModelId !== undefined) {\n const defaultModelId = parseOptionalString(input.defaultModelId, { maxLength: MAX_NAME_LENGTH, nullable: true });\n if (defaultModelId === false) return null;\n patch.defaultModelId = defaultModelId ?? null;\n }\n if (input.slackWorkItemsEnabled !== undefined) {\n if (typeof input.slackWorkItemsEnabled !== 'boolean') return null;\n patch.slackWorkItemsEnabled = input.slackWorkItemsEnabled;\n }\n return Object.keys(patch).length > 0 ? patch : null;\n}\n\nfunction parseConnectionInput(value: unknown): { integrationId: string; installationId: string } | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Record<string, unknown>;\n if (typeof input.integrationId !== 'string' || !input.integrationId.trim()) return null;\n if (typeof input.installationId !== 'string' || !UUID_RE.test(input.installationId)) return null;\n return { integrationId: input.integrationId.trim(), installationId: input.installationId };\n}\n\nfunction parseOptionalString(\n value: unknown,\n { maxLength, nullable = false }: { maxLength: number; nullable?: boolean },\n): string | null | undefined | false {\n if (value === undefined) return undefined;\n if (value === null) return nullable ? null : false;\n if (typeof value !== 'string') return false;\n const normalized = value.trim();\n if (!normalized) return nullable ? null : false;\n if (normalized.length > maxLength || CONTROL_CHAR_RE.test(normalized)) return false;\n return normalized;\n}\n\nfunction parseRepositoryLinkInput(value: unknown): {\n repositoryId: string;\n branch: string | null;\n sandboxProvider: string;\n sandboxWorkdir: string;\n setupCommand: string | null;\n} | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Record<string, unknown>;\n if (typeof input.repositoryId !== 'string' || !UUID_RE.test(input.repositoryId)) return null;\n const branch = parseOptionalString(input.branch, { maxLength: MAX_BRANCH_LENGTH, nullable: true });\n const sandboxProvider = parseOptionalString(input.sandboxProvider, { maxLength: MAX_SANDBOX_PROVIDER_LENGTH });\n const sandboxWorkdir = parseOptionalString(input.sandboxWorkdir, { maxLength: MAX_SANDBOX_WORKDIR_LENGTH });\n const setupCommand = parseOptionalString(input.setupCommand, { maxLength: MAX_SETUP_COMMAND_LENGTH, nullable: true });\n if (\n branch === false ||\n typeof sandboxProvider !== 'string' ||\n typeof sandboxWorkdir !== 'string' ||\n setupCommand === false\n )\n return null;\n return {\n repositoryId: input.repositoryId,\n branch: branch ?? null,\n sandboxProvider,\n sandboxWorkdir,\n setupCommand: setupCommand ?? null,\n };\n}\n\nfunction parseRepositoryUpdateInput(value: unknown): UpdateProjectRepositoryInput | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Record<string, unknown>;\n const patch: UpdateProjectRepositoryInput = {};\n const branch = parseOptionalString(input.branch, { maxLength: MAX_BRANCH_LENGTH, nullable: true });\n const sandboxProvider = parseOptionalString(input.sandboxProvider, { maxLength: MAX_SANDBOX_PROVIDER_LENGTH });\n const sandboxWorkdir = parseOptionalString(input.sandboxWorkdir, { maxLength: MAX_SANDBOX_WORKDIR_LENGTH });\n const setupCommand = parseOptionalString(input.setupCommand, { maxLength: MAX_SETUP_COMMAND_LENGTH, nullable: true });\n if (\n branch === false ||\n sandboxProvider === false ||\n sandboxProvider === null ||\n sandboxWorkdir === false ||\n sandboxWorkdir === null ||\n setupCommand === false\n )\n return null;\n if (branch !== undefined) patch.branch = branch;\n if (sandboxProvider !== undefined) patch.sandboxProvider = sandboxProvider;\n if (sandboxWorkdir !== undefined) patch.sandboxWorkdir = sandboxWorkdir;\n if (setupCommand !== undefined) patch.setupCommand = setupCommand;\n return Object.keys(patch).length > 0 ? patch : null;\n}\n\nexport interface ProjectRoutesDeps extends RouteDependencies {\n /** Factory projects domain backing the CRUD surface. */\n projects: FactoryProjectsStorage;\n /** Source-control domain the connection/repository routes fan out over. */\n sourceControl: SourceControlStorage;\n /** Integration ids allowed as source-control connection targets. */\n versionControlIntegrationIds?: string[];\n}\n\nexport class ProjectRoutes extends Route<ProjectRoutesDeps> {\n readonly #versionControlIntegrationIds: Set<string>;\n\n constructor(deps: ProjectRoutesDeps) {\n super(deps);\n this.#versionControlIntegrationIds = new Set(deps.versionControlIntegrationIds ?? []);\n }\n\n async #projects(): Promise<FactoryProjectsStorage> {\n await this.deps.projects.ensureReady();\n return this.deps.projects;\n }\n\n async #sourceControl(): Promise<SourceControlStorage> {\n await this.deps.sourceControl.ensureReady();\n return this.deps.sourceControl;\n }\n\n async #handles(): Promise<SourceControlStorageHandle[]> {\n const storage = await this.#sourceControl();\n return [...this.#versionControlIntegrationIds].map(integrationId => storage.forIntegration(integrationId));\n }\n\n async #project(orgId: string, id: string) {\n return (await this.#projects()).get({ orgId, id });\n }\n\n async #findConnection({ orgId, projectId, id }: { orgId: string; projectId: string; id: string }) {\n for (const handle of await this.#handles()) {\n const connection = await handle.connections.get({ orgId, id });\n if (connection?.factoryProjectId === projectId) return { handle, connection };\n }\n return null;\n }\n\n async #findProjectRepository({ orgId, projectId, id }: { orgId: string; projectId: string; id: string }) {\n for (const handle of await this.#handles()) {\n const projectRepository = await handle.projectRepositories.get({ orgId, id });\n if (!projectRepository) continue;\n const connection = await handle.connections.get({ orgId, id: projectRepository.connectionId });\n if (connection?.factoryProjectId === projectId) return { handle, connection, projectRepository };\n }\n return null;\n }\n\n async #repositoryPayload(handle: SourceControlStorageHandle, orgId: string, projectRepository: ProjectRepository) {\n const repository = await handle.repositories.get({ orgId, id: projectRepository.repositoryId });\n return { ...projectRepository, repository };\n }\n\n async #resolveTenant(context: Context): Promise<{ orgId: string; userId: string } | { response: Response }> {\n await this.deps.auth.ensureUser(context);\n const tenant = this.deps.auth.tenant(context);\n if (!tenant) return { response: context.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: context.json(\n { error: 'organization_required', message: 'Factory projects require an organization.' },\n 403,\n ),\n };\n }\n return { orgId: tenant.orgId, userId: tenant.userId };\n }\n\n routes(): ApiRoute[] {\n return [\n registerApiRoute('/web/factory/projects', {\n method: 'GET',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n return context.json({ projects: await (await this.#projects()).list({ orgId: tenant.orgId }) });\n },\n }),\n registerApiRoute('/web/factory/projects', {\n method: 'POST',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const input = parseCreateInput(await readJson(context));\n if (!input) return context.json({ error: 'invalid_project' }, 400);\n const project = await (await this.#projects()).create({ orgId: tenant.orgId, userId: tenant.userId, input });\n return context.json({ project }, 201);\n },\n }),\n registerApiRoute('/web/factory/projects/:id', {\n method: 'GET',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const id = context.req.param('id');\n if (!id || !UUID_RE.test(id)) return context.json({ error: 'Project not found' }, 404);\n const project = await this.#project(tenant.orgId, id);\n return project ? context.json({ project }) : context.json({ error: 'Project not found' }, 404);\n },\n }),\n registerApiRoute('/web/factory/projects/:id', {\n method: 'PATCH',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const id = context.req.param('id');\n if (!id || !UUID_RE.test(id)) return context.json({ error: 'Project not found' }, 404);\n const input = parseUpdateInput(await readJson(context));\n if (!input) return context.json({ error: 'invalid_project' }, 400);\n const project = await (await this.#projects()).update({ orgId: tenant.orgId, id, input });\n return project ? context.json({ project }) : context.json({ error: 'Project not found' }, 404);\n },\n }),\n registerApiRoute('/web/factory/projects/:id', {\n method: 'DELETE',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const id = context.req.param('id');\n if (!id || !UUID_RE.test(id)) return context.json({ error: 'Project not found' }, 404);\n if (!(await this.#project(tenant.orgId, id))) return context.json({ error: 'Project not found' }, 404);\n for (const handle of await this.#handles()) {\n for (const connection of await handle.connections.list({ orgId: tenant.orgId, factoryProjectId: id })) {\n await handle.connections.delete({ orgId: tenant.orgId, id: connection.id });\n }\n }\n await (await this.#projects()).delete({ orgId: tenant.orgId, id });\n return context.body(null, 204);\n },\n }),\n registerApiRoute('/web/factory/projects/:id/source-control-connections', {\n method: 'GET',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n if (!projectId || !UUID_RE.test(projectId) || !(await this.#project(tenant.orgId, projectId)))\n return context.json({ error: 'Project not found' }, 404);\n const connections = [];\n for (const handle of await this.#handles()) {\n for (const connection of await handle.connections.list({\n orgId: tenant.orgId,\n factoryProjectId: projectId,\n })) {\n const installation = await handle.installations.get({\n orgId: tenant.orgId,\n id: connection.installationId,\n });\n // Skip orphaned connections whose installation was pruned (e.g.\n // the user uninstalled the GitHub App). Otherwise\n // `projectRepositories.list` throws `requireConnection` and the\n // whole endpoint 500s, which hangs the web UI on the page\n // loader for every project. New code cascade-deletes these on\n // installation removal, but this defensive skip lets already-\n // orphaned rows in existing databases self-heal on read.\n if (!installation) continue;\n const links = await handle.projectRepositories.list({ orgId: tenant.orgId, connectionId: connection.id });\n connections.push({\n ...connection,\n installation,\n repositories: await Promise.all(links.map(link => this.#repositoryPayload(handle, tenant.orgId, link))),\n });\n }\n }\n return context.json({ connections });\n },\n }),\n registerApiRoute('/web/factory/projects/:id/source-control-connections', {\n method: 'POST',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n if (!projectId || !UUID_RE.test(projectId) || !(await this.#project(tenant.orgId, projectId)))\n return context.json({ error: 'Project not found' }, 404);\n const input = parseConnectionInput(await readJson(context));\n if (!input) return context.json({ error: 'invalid_source_control_connection' }, 400);\n if (!this.#versionControlIntegrationIds.has(input.integrationId))\n return context.json({ error: 'Source-control integration not found' }, 404);\n const handle = (await this.#sourceControl()).forIntegration(input.integrationId);\n if (!(await handle.installations.get({ orgId: tenant.orgId, id: input.installationId })))\n return context.json({ error: 'Source-control installation not found' }, 404);\n const connection = await handle.connections.create({\n orgId: tenant.orgId,\n factoryProjectId: projectId,\n installationId: input.installationId,\n createdByUserId: tenant.userId,\n });\n return context.json({ connection }, 201);\n },\n }),\n registerApiRoute('/web/factory/projects/:id/source-control-connections/:connectionId', {\n method: 'DELETE',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n const connectionId = context.req.param('connectionId');\n if (!projectId || !UUID_RE.test(projectId) || !connectionId || !UUID_RE.test(connectionId))\n return context.json({ error: 'Source-control connection not found' }, 404);\n const found = await this.#findConnection({ orgId: tenant.orgId, projectId, id: connectionId });\n if (!found) return context.json({ error: 'Source-control connection not found' }, 404);\n await found.handle.connections.delete({ orgId: tenant.orgId, id: connectionId });\n return context.body(null, 204);\n },\n }),\n registerApiRoute('/web/factory/projects/:id/source-control-connections/:connectionId/repositories', {\n method: 'POST',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n const connectionId = context.req.param('connectionId');\n if (!projectId || !UUID_RE.test(projectId) || !connectionId || !UUID_RE.test(connectionId))\n return context.json({ error: 'Source-control connection not found' }, 404);\n const found = await this.#findConnection({ orgId: tenant.orgId, projectId, id: connectionId });\n if (!found) return context.json({ error: 'Source-control connection not found' }, 404);\n const input = parseRepositoryLinkInput(await readJson(context));\n if (!input) return context.json({ error: 'invalid_project_repository' }, 400);\n const repository = await found.handle.repositories.get({ orgId: tenant.orgId, id: input.repositoryId });\n if (!repository || repository.installationId !== found.connection.installationId)\n return context.json({ error: 'Source-control repository not found' }, 404);\n const projectRepository = await found.handle.projectRepositories.link({\n orgId: tenant.orgId,\n connectionId,\n createdByUserId: tenant.userId,\n ...input,\n });\n return context.json(\n { projectRepository: await this.#repositoryPayload(found.handle, tenant.orgId, projectRepository) },\n 201,\n );\n },\n }),\n registerApiRoute('/web/factory/projects/:id/repositories/:projectRepositoryId', {\n method: 'PATCH',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n const projectRepositoryId = context.req.param('projectRepositoryId');\n if (!projectId || !UUID_RE.test(projectId) || !projectRepositoryId || !UUID_RE.test(projectRepositoryId))\n return context.json({ error: 'Project repository not found' }, 404);\n const found = await this.#findProjectRepository({ orgId: tenant.orgId, projectId, id: projectRepositoryId });\n if (!found) return context.json({ error: 'Project repository not found' }, 404);\n const input = parseRepositoryUpdateInput(await readJson(context));\n if (!input) return context.json({ error: 'invalid_project_repository' }, 400);\n const projectRepository = await found.handle.projectRepositories.update({\n orgId: tenant.orgId,\n id: projectRepositoryId,\n input,\n });\n return context.json({\n projectRepository: await this.#repositoryPayload(found.handle, tenant.orgId, projectRepository!),\n });\n },\n }),\n registerApiRoute('/web/factory/projects/:id/repositories/:projectRepositoryId', {\n method: 'DELETE',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n const projectRepositoryId = context.req.param('projectRepositoryId');\n if (!projectId || !UUID_RE.test(projectId) || !projectRepositoryId || !UUID_RE.test(projectRepositoryId))\n return context.json({ error: 'Project repository not found' }, 404);\n const found = await this.#findProjectRepository({ orgId: tenant.orgId, projectId, id: projectRepositoryId });\n if (!found) return context.json({ error: 'Project repository not found' }, 404);\n await found.handle.projectRepositories.unlink({ orgId: tenant.orgId, id: projectRepositoryId });\n return context.body(null, 204);\n },\n }),\n ];\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"projects.js","names":["#versionControlIntegrationIds","#projects","#sourceControl","#handles","#project","#findConnection","#findProjectRepository","#repositoryPayload","#resolveTenant"],"sources":["../../src/routes/projects.ts"],"sourcesContent":["import type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport type {\n CreateFactoryProjectInput,\n FactoryProjectsStorage,\n UpdateFactoryProjectInput,\n} from '../storage/domains/projects/base.js';\nimport type {\n ProjectRepository,\n SourceControlStorage,\n SourceControlStorageHandle,\n UpdateProjectRepositoryInput,\n} from '../storage/domains/source-control/base.js';\nimport type { RouteDependencies } from './route.js';\nimport { Route } from './route.js';\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst MAX_NAME_LENGTH = 200;\nconst MAX_DESCRIPTION_LENGTH = 2_000;\nconst MAX_SETUP_COMMAND_LENGTH = 2_000;\nconst MAX_BRANCH_LENGTH = 255;\nconst MAX_SANDBOX_PROVIDER_LENGTH = 100;\nconst MAX_SANDBOX_WORKDIR_LENGTH = 1_000;\nconst CONTROL_CHAR_RE = /[\\0-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/;\n\nfunction loose(context: unknown): Context {\n return context as Context;\n}\n\nasync function readJson(context: Context): Promise<unknown | undefined> {\n try {\n return await context.req.json();\n } catch {\n return undefined;\n }\n}\n\nfunction parseCreateInput(value: unknown): CreateFactoryProjectInput | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Record<string, unknown>;\n if (typeof input.name !== 'string') return null;\n const name = input.name.trim();\n if (!name || name.length > MAX_NAME_LENGTH) return null;\n if (input.description !== undefined && input.description !== null && typeof input.description !== 'string')\n return null;\n const description = typeof input.description === 'string' ? input.description.trim() || null : null;\n if (description && description.length > MAX_DESCRIPTION_LENGTH) return null;\n return { name, description };\n}\n\nfunction parseUpdateInput(value: unknown): UpdateFactoryProjectInput | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Record<string, unknown>;\n const patch: UpdateFactoryProjectInput = {};\n if (input.name !== undefined) {\n if (typeof input.name !== 'string') return null;\n const name = input.name.trim();\n if (!name || name.length > MAX_NAME_LENGTH) return null;\n patch.name = name;\n }\n if (input.description !== undefined) {\n if (input.description !== null && typeof input.description !== 'string') return null;\n const description = typeof input.description === 'string' ? input.description.trim() || null : null;\n if (description && description.length > MAX_DESCRIPTION_LENGTH) return null;\n patch.description = description;\n }\n if (input.defaultModelId !== undefined) {\n const defaultModelId = parseOptionalString(input.defaultModelId, { maxLength: MAX_NAME_LENGTH, nullable: true });\n if (defaultModelId === false) return null;\n patch.defaultModelId = defaultModelId ?? null;\n }\n if (input.slackWorkItemsEnabled !== undefined) {\n if (typeof input.slackWorkItemsEnabled !== 'boolean') return null;\n patch.slackWorkItemsEnabled = input.slackWorkItemsEnabled;\n }\n if (input.autoRunEnabled !== undefined) {\n if (typeof input.autoRunEnabled !== 'boolean') return null;\n patch.autoRunEnabled = input.autoRunEnabled;\n }\n return Object.keys(patch).length > 0 ? patch : null;\n}\n\nfunction parseConnectionInput(value: unknown): { integrationId: string; installationId: string } | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Record<string, unknown>;\n if (typeof input.integrationId !== 'string' || !input.integrationId.trim()) return null;\n if (typeof input.installationId !== 'string' || !UUID_RE.test(input.installationId)) return null;\n return { integrationId: input.integrationId.trim(), installationId: input.installationId };\n}\n\nfunction parseOptionalString(\n value: unknown,\n { maxLength, nullable = false }: { maxLength: number; nullable?: boolean },\n): string | null | undefined | false {\n if (value === undefined) return undefined;\n if (value === null) return nullable ? null : false;\n if (typeof value !== 'string') return false;\n const normalized = value.trim();\n if (!normalized) return nullable ? null : false;\n if (normalized.length > maxLength || CONTROL_CHAR_RE.test(normalized)) return false;\n return normalized;\n}\n\nfunction parseRepositoryLinkInput(value: unknown): {\n repositoryId: string;\n branch: string | null;\n sandboxProvider: string;\n sandboxWorkdir: string;\n setupCommand: string | null;\n} | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Record<string, unknown>;\n if (typeof input.repositoryId !== 'string' || !UUID_RE.test(input.repositoryId)) return null;\n const branch = parseOptionalString(input.branch, { maxLength: MAX_BRANCH_LENGTH, nullable: true });\n const sandboxProvider = parseOptionalString(input.sandboxProvider, { maxLength: MAX_SANDBOX_PROVIDER_LENGTH });\n const sandboxWorkdir = parseOptionalString(input.sandboxWorkdir, { maxLength: MAX_SANDBOX_WORKDIR_LENGTH });\n const setupCommand = parseOptionalString(input.setupCommand, { maxLength: MAX_SETUP_COMMAND_LENGTH, nullable: true });\n if (\n branch === false ||\n typeof sandboxProvider !== 'string' ||\n typeof sandboxWorkdir !== 'string' ||\n setupCommand === false\n )\n return null;\n return {\n repositoryId: input.repositoryId,\n branch: branch ?? null,\n sandboxProvider,\n sandboxWorkdir,\n setupCommand: setupCommand ?? null,\n };\n}\n\nfunction parseRepositoryUpdateInput(value: unknown): UpdateProjectRepositoryInput | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Record<string, unknown>;\n const patch: UpdateProjectRepositoryInput = {};\n const branch = parseOptionalString(input.branch, { maxLength: MAX_BRANCH_LENGTH, nullable: true });\n const sandboxProvider = parseOptionalString(input.sandboxProvider, { maxLength: MAX_SANDBOX_PROVIDER_LENGTH });\n const sandboxWorkdir = parseOptionalString(input.sandboxWorkdir, { maxLength: MAX_SANDBOX_WORKDIR_LENGTH });\n const setupCommand = parseOptionalString(input.setupCommand, { maxLength: MAX_SETUP_COMMAND_LENGTH, nullable: true });\n if (\n branch === false ||\n sandboxProvider === false ||\n sandboxProvider === null ||\n sandboxWorkdir === false ||\n sandboxWorkdir === null ||\n setupCommand === false\n )\n return null;\n if (branch !== undefined) patch.branch = branch;\n if (sandboxProvider !== undefined) patch.sandboxProvider = sandboxProvider;\n if (sandboxWorkdir !== undefined) patch.sandboxWorkdir = sandboxWorkdir;\n if (setupCommand !== undefined) patch.setupCommand = setupCommand;\n return Object.keys(patch).length > 0 ? patch : null;\n}\n\nexport interface ProjectRoutesDeps extends RouteDependencies {\n /** Factory projects domain backing the CRUD surface. */\n projects: FactoryProjectsStorage;\n /** Source-control domain the connection/repository routes fan out over. */\n sourceControl: SourceControlStorage;\n /** Integration ids allowed as source-control connection targets. */\n versionControlIntegrationIds?: string[];\n}\n\nexport class ProjectRoutes extends Route<ProjectRoutesDeps> {\n readonly #versionControlIntegrationIds: Set<string>;\n\n constructor(deps: ProjectRoutesDeps) {\n super(deps);\n this.#versionControlIntegrationIds = new Set(deps.versionControlIntegrationIds ?? []);\n }\n\n async #projects(): Promise<FactoryProjectsStorage> {\n await this.deps.projects.ensureReady();\n return this.deps.projects;\n }\n\n async #sourceControl(): Promise<SourceControlStorage> {\n await this.deps.sourceControl.ensureReady();\n return this.deps.sourceControl;\n }\n\n async #handles(): Promise<SourceControlStorageHandle[]> {\n const storage = await this.#sourceControl();\n return [...this.#versionControlIntegrationIds].map(integrationId => storage.forIntegration(integrationId));\n }\n\n async #project(orgId: string, id: string) {\n return (await this.#projects()).get({ orgId, id });\n }\n\n async #findConnection({ orgId, projectId, id }: { orgId: string; projectId: string; id: string }) {\n for (const handle of await this.#handles()) {\n const connection = await handle.connections.get({ orgId, id });\n if (connection?.factoryProjectId === projectId) return { handle, connection };\n }\n return null;\n }\n\n async #findProjectRepository({ orgId, projectId, id }: { orgId: string; projectId: string; id: string }) {\n for (const handle of await this.#handles()) {\n const projectRepository = await handle.projectRepositories.get({ orgId, id });\n if (!projectRepository) continue;\n const connection = await handle.connections.get({ orgId, id: projectRepository.connectionId });\n if (connection?.factoryProjectId === projectId) return { handle, connection, projectRepository };\n }\n return null;\n }\n\n async #repositoryPayload(handle: SourceControlStorageHandle, orgId: string, projectRepository: ProjectRepository) {\n const repository = await handle.repositories.get({ orgId, id: projectRepository.repositoryId });\n return { ...projectRepository, repository };\n }\n\n async #resolveTenant(context: Context): Promise<{ orgId: string; userId: string } | { response: Response }> {\n await this.deps.auth.ensureUser(context);\n const tenant = this.deps.auth.tenant(context);\n if (!tenant) return { response: context.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: context.json(\n { error: 'organization_required', message: 'Factory projects require an organization.' },\n 403,\n ),\n };\n }\n return { orgId: tenant.orgId, userId: tenant.userId };\n }\n\n routes(): ApiRoute[] {\n return [\n registerApiRoute('/web/factory/projects', {\n method: 'GET',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n return context.json({ projects: await (await this.#projects()).list({ orgId: tenant.orgId }) });\n },\n }),\n registerApiRoute('/web/factory/projects', {\n method: 'POST',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const input = parseCreateInput(await readJson(context));\n if (!input) return context.json({ error: 'invalid_project' }, 400);\n const project = await (await this.#projects()).create({ orgId: tenant.orgId, userId: tenant.userId, input });\n return context.json({ project }, 201);\n },\n }),\n registerApiRoute('/web/factory/projects/:id', {\n method: 'GET',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const id = context.req.param('id');\n if (!id || !UUID_RE.test(id)) return context.json({ error: 'Project not found' }, 404);\n const project = await this.#project(tenant.orgId, id);\n return project ? context.json({ project }) : context.json({ error: 'Project not found' }, 404);\n },\n }),\n registerApiRoute('/web/factory/projects/:id', {\n method: 'PATCH',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const id = context.req.param('id');\n if (!id || !UUID_RE.test(id)) return context.json({ error: 'Project not found' }, 404);\n const input = parseUpdateInput(await readJson(context));\n if (!input) return context.json({ error: 'invalid_project' }, 400);\n const project = await (await this.#projects()).update({ orgId: tenant.orgId, id, input });\n return project ? context.json({ project }) : context.json({ error: 'Project not found' }, 404);\n },\n }),\n registerApiRoute('/web/factory/projects/:id', {\n method: 'DELETE',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const id = context.req.param('id');\n if (!id || !UUID_RE.test(id)) return context.json({ error: 'Project not found' }, 404);\n if (!(await this.#project(tenant.orgId, id))) return context.json({ error: 'Project not found' }, 404);\n for (const handle of await this.#handles()) {\n for (const connection of await handle.connections.list({ orgId: tenant.orgId, factoryProjectId: id })) {\n await handle.connections.delete({ orgId: tenant.orgId, id: connection.id });\n }\n }\n await (await this.#projects()).delete({ orgId: tenant.orgId, id });\n return context.body(null, 204);\n },\n }),\n registerApiRoute('/web/factory/projects/:id/source-control-connections', {\n method: 'GET',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n if (!projectId || !UUID_RE.test(projectId) || !(await this.#project(tenant.orgId, projectId)))\n return context.json({ error: 'Project not found' }, 404);\n const connections = [];\n for (const handle of await this.#handles()) {\n for (const connection of await handle.connections.list({\n orgId: tenant.orgId,\n factoryProjectId: projectId,\n })) {\n const installation = await handle.installations.get({\n orgId: tenant.orgId,\n id: connection.installationId,\n });\n // Skip orphaned connections whose installation was pruned (e.g.\n // the user uninstalled the GitHub App). Otherwise\n // `projectRepositories.list` throws `requireConnection` and the\n // whole endpoint 500s, which hangs the web UI on the page\n // loader for every project. New code cascade-deletes these on\n // installation removal, but this defensive skip lets already-\n // orphaned rows in existing databases self-heal on read.\n if (!installation) continue;\n const links = await handle.projectRepositories.list({ orgId: tenant.orgId, connectionId: connection.id });\n connections.push({\n ...connection,\n installation,\n repositories: await Promise.all(links.map(link => this.#repositoryPayload(handle, tenant.orgId, link))),\n });\n }\n }\n return context.json({ connections });\n },\n }),\n registerApiRoute('/web/factory/projects/:id/source-control-connections', {\n method: 'POST',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n if (!projectId || !UUID_RE.test(projectId) || !(await this.#project(tenant.orgId, projectId)))\n return context.json({ error: 'Project not found' }, 404);\n const input = parseConnectionInput(await readJson(context));\n if (!input) return context.json({ error: 'invalid_source_control_connection' }, 400);\n if (!this.#versionControlIntegrationIds.has(input.integrationId))\n return context.json({ error: 'Source-control integration not found' }, 404);\n const handle = (await this.#sourceControl()).forIntegration(input.integrationId);\n if (!(await handle.installations.get({ orgId: tenant.orgId, id: input.installationId })))\n return context.json({ error: 'Source-control installation not found' }, 404);\n const connection = await handle.connections.create({\n orgId: tenant.orgId,\n factoryProjectId: projectId,\n installationId: input.installationId,\n createdByUserId: tenant.userId,\n });\n return context.json({ connection }, 201);\n },\n }),\n registerApiRoute('/web/factory/projects/:id/source-control-connections/:connectionId', {\n method: 'DELETE',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n const connectionId = context.req.param('connectionId');\n if (!projectId || !UUID_RE.test(projectId) || !connectionId || !UUID_RE.test(connectionId))\n return context.json({ error: 'Source-control connection not found' }, 404);\n const found = await this.#findConnection({ orgId: tenant.orgId, projectId, id: connectionId });\n if (!found) return context.json({ error: 'Source-control connection not found' }, 404);\n await found.handle.connections.delete({ orgId: tenant.orgId, id: connectionId });\n return context.body(null, 204);\n },\n }),\n registerApiRoute('/web/factory/projects/:id/source-control-connections/:connectionId/repositories', {\n method: 'POST',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n const connectionId = context.req.param('connectionId');\n if (!projectId || !UUID_RE.test(projectId) || !connectionId || !UUID_RE.test(connectionId))\n return context.json({ error: 'Source-control connection not found' }, 404);\n const found = await this.#findConnection({ orgId: tenant.orgId, projectId, id: connectionId });\n if (!found) return context.json({ error: 'Source-control connection not found' }, 404);\n const input = parseRepositoryLinkInput(await readJson(context));\n if (!input) return context.json({ error: 'invalid_project_repository' }, 400);\n const repository = await found.handle.repositories.get({ orgId: tenant.orgId, id: input.repositoryId });\n if (!repository || repository.installationId !== found.connection.installationId)\n return context.json({ error: 'Source-control repository not found' }, 404);\n const projectRepository = await found.handle.projectRepositories.link({\n orgId: tenant.orgId,\n connectionId,\n createdByUserId: tenant.userId,\n ...input,\n });\n return context.json(\n { projectRepository: await this.#repositoryPayload(found.handle, tenant.orgId, projectRepository) },\n 201,\n );\n },\n }),\n registerApiRoute('/web/factory/projects/:id/repositories/:projectRepositoryId', {\n method: 'PATCH',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n const projectRepositoryId = context.req.param('projectRepositoryId');\n if (!projectId || !UUID_RE.test(projectId) || !projectRepositoryId || !UUID_RE.test(projectRepositoryId))\n return context.json({ error: 'Project repository not found' }, 404);\n const found = await this.#findProjectRepository({ orgId: tenant.orgId, projectId, id: projectRepositoryId });\n if (!found) return context.json({ error: 'Project repository not found' }, 404);\n const input = parseRepositoryUpdateInput(await readJson(context));\n if (!input) return context.json({ error: 'invalid_project_repository' }, 400);\n const projectRepository = await found.handle.projectRepositories.update({\n orgId: tenant.orgId,\n id: projectRepositoryId,\n input,\n });\n return context.json({\n projectRepository: await this.#repositoryPayload(found.handle, tenant.orgId, projectRepository!),\n });\n },\n }),\n registerApiRoute('/web/factory/projects/:id/repositories/:projectRepositoryId', {\n method: 'DELETE',\n requiresAuth: false,\n handler: async routeContext => {\n const context = loose(routeContext);\n const tenant = await this.#resolveTenant(context);\n if ('response' in tenant) return tenant.response;\n const projectId = context.req.param('id');\n const projectRepositoryId = context.req.param('projectRepositoryId');\n if (!projectId || !UUID_RE.test(projectId) || !projectRepositoryId || !UUID_RE.test(projectRepositoryId))\n return context.json({ error: 'Project repository not found' }, 404);\n const found = await this.#findProjectRepository({ orgId: tenant.orgId, projectId, id: projectRepositoryId });\n if (!found) return context.json({ error: 'Project repository not found' }, 404);\n await found.handle.projectRepositories.unlink({ orgId: tenant.orgId, id: projectRepositoryId });\n return context.body(null, 204);\n },\n }),\n ];\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/routes/skills.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAGpC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AAC5F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAgBnC,UAAU,0BAA0B;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IACzB,IAAI,CAAC,EAAE,iBAAiB,GAAG,cAAc,GAAG,mBAAmB,CAAC;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,eAAe,CAAC,EAAE,sBAAsB,CAAC,CAAC;IAC3E,oBAAoB,CAAC,EAAE,0BAA0B,CAAC;IAClD,wBAAwB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,uBAAuB,CAAC,EAAE,CACxB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAC1E,OAAO,CAAC,0BAA0B,CAAC,CAAC;CAC1C;AAiCD,qBAAa,WAAY,SAAQ,KAAK,CAAC,eAAe,CAAC;;IA6DrD,MAAM,IAAI,QAAQ,EAAE;CA6DrB"}
1
+ {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/routes/skills.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAIpC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AAC5F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAgBnC,UAAU,0BAA0B;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IACzB,IAAI,CAAC,EAAE,iBAAiB,GAAG,cAAc,GAAG,mBAAmB,CAAC;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,eAAe,CAAC,EAAE,sBAAsB,CAAC,CAAC;IAC3E,oBAAoB,CAAC,EAAE,0BAA0B,CAAC;IAClD,wBAAwB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,uBAAuB,CAAC,EAAE,CACxB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAC1E,OAAO,CAAC,0BAA0B,CAAC,CAAC;CAC1C;AAiCD,qBAAa,WAAY,SAAQ,KAAK,CAAC,eAAe,CAAC;;IA6DrD,MAAM,IAAI,QAAQ,EAAE;CA8ErB"}
@@ -1,4 +1,5 @@
1
1
  import { Route } from "./route.js";
2
+ import { listFactorySkills } from "../skills/catalog.js";
2
3
  import { SkillInvocationError, resolveSkillInvocation } from "../skills/service.js";
3
4
  import { registerApiRoute } from "@mastra/core/server";
4
5
  //#region src/routes/skills.ts
@@ -151,15 +152,35 @@ var SkillRoutes = class extends Route {
151
152
  throw error;
152
153
  }
153
154
  };
154
- return [registerApiRoute("/web/agent-controller/:controllerId/skills/prepare", {
155
- method: "POST",
156
- requiresAuth: false,
157
- handler: (context) => handleSkillRequest(context, false)
158
- }), registerApiRoute("/web/agent-controller/:controllerId/skills/invoke", {
159
- method: "POST",
160
- requiresAuth: false,
161
- handler: (context) => handleSkillRequest(context, true)
162
- })];
155
+ const handleFactorySkillsList = async (context) => {
156
+ const c = loose(context);
157
+ const { auth } = this.deps;
158
+ if (auth.enabled()) {
159
+ await auth.ensureUser(c);
160
+ if (!auth.tenant(c)) return c.json({
161
+ error: "unauthorized",
162
+ message: "Authentication required."
163
+ }, 401);
164
+ }
165
+ return c.json({ skills: await listFactorySkills() });
166
+ };
167
+ return [
168
+ registerApiRoute("/web/factory/skills", {
169
+ method: "GET",
170
+ requiresAuth: false,
171
+ handler: (context) => handleFactorySkillsList(context)
172
+ }),
173
+ registerApiRoute("/web/agent-controller/:controllerId/skills/prepare", {
174
+ method: "POST",
175
+ requiresAuth: false,
176
+ handler: (context) => handleSkillRequest(context, false)
177
+ }),
178
+ registerApiRoute("/web/agent-controller/:controllerId/skills/invoke", {
179
+ method: "POST",
180
+ requiresAuth: false,
181
+ handler: (context) => handleSkillRequest(context, true)
182
+ })
183
+ ];
163
184
  }
164
185
  };
165
186
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"skills.js","names":["#authorizeSessionAddress"],"sources":["../../src/routes/skills.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport { resolveSkillInvocation, SkillInvocationError } from '../skills/service.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport type { RouteDependencies } from './route.js';\nimport { Route } from './route.js';\n\nconst MAX_RESOURCE_ID_LENGTH = 512;\nconst MAX_SCOPE_LENGTH = 2048;\nconst MAX_ARGUMENTS_LENGTH = 16_384;\nconst SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\ninterface SkillInvocationBody {\n resourceId: string;\n projectRepositoryId?: string;\n scope?: string;\n name: string;\n arguments?: string;\n}\n\ninterface SessionAuthorizationResult {\n allowed: boolean;\n status?: 400 | 401 | 403;\n code?: 'invalid_request' | 'unauthorized' | 'session_forbidden';\n message?: string;\n}\n\nexport interface SkillRoutesDeps extends RouteDependencies {\n controllerId: string;\n controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>;\n sourceControlStorage?: SourceControlStorageHandle;\n ensureSourceControlReady?: () => Promise<void>;\n authorizeSessionAddress?: (\n context: Context,\n address: { resourceId: string; projectRepositoryId?: string; scope?: string },\n ) => Promise<SessionAuthorizationResult>;\n}\n\nfunction loose(context: unknown): Context {\n return context as Context;\n}\n\nfunction parseBody(value: unknown): SkillInvocationBody | undefined {\n if (!value || typeof value !== 'object') return undefined;\n const input = value as Record<string, unknown>;\n if (typeof input.resourceId !== 'string' || input.resourceId.length === 0) return undefined;\n if (input.resourceId.length > MAX_RESOURCE_ID_LENGTH) return undefined;\n if (\n input.projectRepositoryId !== undefined &&\n (typeof input.projectRepositoryId !== 'string' || !UUID_RE.test(input.projectRepositoryId))\n ) {\n return undefined;\n }\n if (input.scope !== undefined && (typeof input.scope !== 'string' || input.scope.length > MAX_SCOPE_LENGTH)) {\n return undefined;\n }\n if (typeof input.name !== 'string' || input.name.length > 64 || !SKILL_NAME_RE.test(input.name)) return undefined;\n if (input.arguments !== undefined) {\n if (typeof input.arguments !== 'string' || input.arguments.length > MAX_ARGUMENTS_LENGTH) return undefined;\n }\n return {\n resourceId: input.resourceId,\n ...(input.projectRepositoryId ? { projectRepositoryId: input.projectRepositoryId } : {}),\n ...(input.scope ? { scope: input.scope } : {}),\n name: input.name,\n ...(input.arguments !== undefined ? { arguments: input.arguments } : {}),\n };\n}\n\nexport class SkillRoutes extends Route<SkillRoutesDeps> {\n async #authorizeSessionAddress(\n context: Context,\n address: { resourceId: string; projectRepositoryId?: string; scope?: string },\n ): Promise<SessionAuthorizationResult> {\n const { auth, sourceControlStorage: storage, ensureSourceControlReady } = this.deps;\n if (!auth.enabled()) return { allowed: true };\n\n await auth.ensureUser(context);\n const tenant = auth.tenant(context);\n if (!tenant) {\n return { allowed: false, status: 401, code: 'unauthorized', message: 'Authentication required.' };\n }\n\n // Personal sessions are keyed by the authenticated WorkOS user id. Their\n // scope is a client-managed local/user-session worktree and needs no app DB.\n if (address.resourceId === tenant.userId) return { allowed: true };\n\n // Factory sessions are keyed by factoryProjectId and explicitly identify the\n // linked repository whose user-owned worktree supplies the session scope.\n if (\n !UUID_RE.test(address.resourceId) ||\n !address.projectRepositoryId ||\n !UUID_RE.test(address.projectRepositoryId)\n ) {\n return { allowed: false, status: 400, code: 'invalid_request', message: 'Invalid skill invocation request.' };\n }\n if (!tenant.orgId || !address.scope) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n if (!storage) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n if (ensureSourceControlReady) {\n try {\n await ensureSourceControlReady();\n } catch {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n }\n const projectRepository = await storage.projectRepositories.get({\n orgId: tenant.orgId,\n id: address.projectRepositoryId,\n });\n if (!projectRepository) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n const connection = await storage.connections.get({ orgId: tenant.orgId, id: projectRepository.connectionId });\n if (!connection || connection.factoryProjectId !== address.resourceId) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n const worktree = await storage.worktrees.findByPath({\n projectRepositoryId: address.projectRepositoryId,\n userId: tenant.userId,\n worktreePath: address.scope,\n });\n return worktree\n ? { allowed: true }\n : { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n\n routes(): ApiRoute[] {\n const { controllerId, controller, authorizeSessionAddress: customAuthorize } = this.deps;\n const authorize =\n customAuthorize ??\n ((context: Context, address: { resourceId: string; projectRepositoryId?: string; scope?: string }) =>\n this.#authorizeSessionAddress(context, address));\n const handleSkillRequest = async (context: unknown, dispatch: boolean) => {\n const c = loose(context);\n if (c.req.param('controllerId') !== controllerId) {\n return c.json({ error: 'controller_not_found', message: 'Agent controller not found.' }, 404);\n }\n\n let rawBody: unknown;\n try {\n rawBody = await c.req.json();\n } catch {\n return c.json({ error: 'invalid_request', message: 'Invalid JSON body.' }, 400);\n }\n const body = parseBody(rawBody);\n if (!body) {\n return c.json({ error: 'invalid_request', message: 'Invalid skill invocation request.' }, 400);\n }\n\n const authorization = await authorize(c, {\n resourceId: body.resourceId,\n projectRepositoryId: body.projectRepositoryId,\n scope: body.scope,\n });\n if (!authorization.allowed) {\n return c.json({ error: authorization.code, message: authorization.message }, authorization.status ?? 403);\n }\n\n try {\n const resolved = await resolveSkillInvocation(controller, body);\n if (dispatch) {\n void resolved.session.sendMessage({ content: resolved.message }).catch((error: unknown) => {\n console.error('Workspace skill dispatch failed after acceptance', error);\n });\n }\n return c.json({ ok: true, skill: resolved.skillName, message: resolved.message });\n } catch (error) {\n if (error instanceof SkillInvocationError) {\n return c.json({ error: error.code, message: error.message }, 404);\n }\n throw error;\n }\n };\n\n return [\n registerApiRoute('/web/agent-controller/:controllerId/skills/prepare', {\n method: 'POST',\n requiresAuth: false,\n handler: context => handleSkillRequest(context, false),\n }),\n registerApiRoute('/web/agent-controller/:controllerId/skills/invoke', {\n method: 'POST',\n requiresAuth: false,\n handler: context => handleSkillRequest(context, true),\n }),\n ];\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"skills.js","names":["#authorizeSessionAddress"],"sources":["../../src/routes/skills.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport { listFactorySkills } from '../skills/catalog.js';\nimport { resolveSkillInvocation, SkillInvocationError } from '../skills/service.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport type { RouteDependencies } from './route.js';\nimport { Route } from './route.js';\n\nconst MAX_RESOURCE_ID_LENGTH = 512;\nconst MAX_SCOPE_LENGTH = 2048;\nconst MAX_ARGUMENTS_LENGTH = 16_384;\nconst SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\ninterface SkillInvocationBody {\n resourceId: string;\n projectRepositoryId?: string;\n scope?: string;\n name: string;\n arguments?: string;\n}\n\ninterface SessionAuthorizationResult {\n allowed: boolean;\n status?: 400 | 401 | 403;\n code?: 'invalid_request' | 'unauthorized' | 'session_forbidden';\n message?: string;\n}\n\nexport interface SkillRoutesDeps extends RouteDependencies {\n controllerId: string;\n controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>;\n sourceControlStorage?: SourceControlStorageHandle;\n ensureSourceControlReady?: () => Promise<void>;\n authorizeSessionAddress?: (\n context: Context,\n address: { resourceId: string; projectRepositoryId?: string; scope?: string },\n ) => Promise<SessionAuthorizationResult>;\n}\n\nfunction loose(context: unknown): Context {\n return context as Context;\n}\n\nfunction parseBody(value: unknown): SkillInvocationBody | undefined {\n if (!value || typeof value !== 'object') return undefined;\n const input = value as Record<string, unknown>;\n if (typeof input.resourceId !== 'string' || input.resourceId.length === 0) return undefined;\n if (input.resourceId.length > MAX_RESOURCE_ID_LENGTH) return undefined;\n if (\n input.projectRepositoryId !== undefined &&\n (typeof input.projectRepositoryId !== 'string' || !UUID_RE.test(input.projectRepositoryId))\n ) {\n return undefined;\n }\n if (input.scope !== undefined && (typeof input.scope !== 'string' || input.scope.length > MAX_SCOPE_LENGTH)) {\n return undefined;\n }\n if (typeof input.name !== 'string' || input.name.length > 64 || !SKILL_NAME_RE.test(input.name)) return undefined;\n if (input.arguments !== undefined) {\n if (typeof input.arguments !== 'string' || input.arguments.length > MAX_ARGUMENTS_LENGTH) return undefined;\n }\n return {\n resourceId: input.resourceId,\n ...(input.projectRepositoryId ? { projectRepositoryId: input.projectRepositoryId } : {}),\n ...(input.scope ? { scope: input.scope } : {}),\n name: input.name,\n ...(input.arguments !== undefined ? { arguments: input.arguments } : {}),\n };\n}\n\nexport class SkillRoutes extends Route<SkillRoutesDeps> {\n async #authorizeSessionAddress(\n context: Context,\n address: { resourceId: string; projectRepositoryId?: string; scope?: string },\n ): Promise<SessionAuthorizationResult> {\n const { auth, sourceControlStorage: storage, ensureSourceControlReady } = this.deps;\n if (!auth.enabled()) return { allowed: true };\n\n await auth.ensureUser(context);\n const tenant = auth.tenant(context);\n if (!tenant) {\n return { allowed: false, status: 401, code: 'unauthorized', message: 'Authentication required.' };\n }\n\n // Personal sessions are keyed by the authenticated WorkOS user id. Their\n // scope is a client-managed local/user-session worktree and needs no app DB.\n if (address.resourceId === tenant.userId) return { allowed: true };\n\n // Factory sessions are keyed by factoryProjectId and explicitly identify the\n // linked repository whose user-owned worktree supplies the session scope.\n if (\n !UUID_RE.test(address.resourceId) ||\n !address.projectRepositoryId ||\n !UUID_RE.test(address.projectRepositoryId)\n ) {\n return { allowed: false, status: 400, code: 'invalid_request', message: 'Invalid skill invocation request.' };\n }\n if (!tenant.orgId || !address.scope) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n if (!storage) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n if (ensureSourceControlReady) {\n try {\n await ensureSourceControlReady();\n } catch {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n }\n const projectRepository = await storage.projectRepositories.get({\n orgId: tenant.orgId,\n id: address.projectRepositoryId,\n });\n if (!projectRepository) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n const connection = await storage.connections.get({ orgId: tenant.orgId, id: projectRepository.connectionId });\n if (!connection || connection.factoryProjectId !== address.resourceId) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n const worktree = await storage.worktrees.findByPath({\n projectRepositoryId: address.projectRepositoryId,\n userId: tenant.userId,\n worktreePath: address.scope,\n });\n return worktree\n ? { allowed: true }\n : { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n\n routes(): ApiRoute[] {\n const { controllerId, controller, authorizeSessionAddress: customAuthorize } = this.deps;\n const authorize =\n customAuthorize ??\n ((context: Context, address: { resourceId: string; projectRepositoryId?: string; scope?: string }) =>\n this.#authorizeSessionAddress(context, address));\n const handleSkillRequest = async (context: unknown, dispatch: boolean) => {\n const c = loose(context);\n if (c.req.param('controllerId') !== controllerId) {\n return c.json({ error: 'controller_not_found', message: 'Agent controller not found.' }, 404);\n }\n\n let rawBody: unknown;\n try {\n rawBody = await c.req.json();\n } catch {\n return c.json({ error: 'invalid_request', message: 'Invalid JSON body.' }, 400);\n }\n const body = parseBody(rawBody);\n if (!body) {\n return c.json({ error: 'invalid_request', message: 'Invalid skill invocation request.' }, 400);\n }\n\n const authorization = await authorize(c, {\n resourceId: body.resourceId,\n projectRepositoryId: body.projectRepositoryId,\n scope: body.scope,\n });\n if (!authorization.allowed) {\n return c.json({ error: authorization.code, message: authorization.message }, authorization.status ?? 403);\n }\n\n try {\n const resolved = await resolveSkillInvocation(controller, body);\n if (dispatch) {\n void resolved.session.sendMessage({ content: resolved.message }).catch((error: unknown) => {\n console.error('Workspace skill dispatch failed after acceptance', error);\n });\n }\n return c.json({ ok: true, skill: resolved.skillName, message: resolved.message });\n } catch (error) {\n if (error instanceof SkillInvocationError) {\n return c.json({ error: error.code, message: error.message }, 404);\n }\n throw error;\n }\n };\n\n const handleFactorySkillsList = async (context: unknown) => {\n const c = loose(context);\n const { auth } = this.deps;\n if (auth.enabled()) {\n await auth.ensureUser(c);\n if (!auth.tenant(c)) {\n return c.json({ error: 'unauthorized', message: 'Authentication required.' }, 401);\n }\n }\n return c.json({ skills: await listFactorySkills() });\n };\n\n return [\n registerApiRoute('/web/factory/skills', {\n method: 'GET',\n requiresAuth: false,\n handler: context => handleFactorySkillsList(context),\n }),\n registerApiRoute('/web/agent-controller/:controllerId/skills/prepare', {\n method: 'POST',\n requiresAuth: false,\n handler: context => handleSkillRequest(context, false),\n }),\n registerApiRoute('/web/agent-controller/:controllerId/skills/invoke', {\n method: 'POST',\n requiresAuth: false,\n handler: context => handleSkillRequest(context, true),\n }),\n ];\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../../src/routes/surface.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../../src/routes/surface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAEtF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6CAA6C,CAAC;AAC1F,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AACtF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6CAA6C,CAAC;AAC1F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAClF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AACxF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAClF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAM9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAK5C,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,kBAAkB,CAAC;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,eAAe,CAAC,eAAe,CAAC,CAAC;IAC7C,qEAAqE;IACrE,IAAI,EAAE,SAAS,CAAC;IAChB,WAAW,EAAE,WAAW,CAAC;IACzB,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,2EAA2E;IAC3E,KAAK,EAAE,YAAY,CAAC;IACpB,4EAA4E;IAC5E,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,oBAAoB,EAAE,oBAAoB,CAAC;IAC3C,mFAAmF;IACnF,OAAO,EAAE;QACP,MAAM,EAAE,aAAa,CAAC;QACtB,gBAAgB,EAAE,uBAAuB,CAAC;QAC1C,cAAc,EAAE,qBAAqB,CAAC;QACtC,eAAe,EAAE,sBAAsB,CAAC;QACxC,UAAU,EAAE,iBAAiB,CAAC;QAC9B,UAAU,EAAE,iBAAiB,CAAC;QAC9B,QAAQ,EAAE,sBAAsB,CAAC;QACjC,WAAW,EAAE,kBAAkB,CAAC;QAChC,SAAS,EAAE,gBAAgB,CAAC;QAC5B,eAAe,EAAE,sBAAsB,CAAC;KACzC,CAAC;IACF,YAAY,CAAC,EAAE,uBAAuB,EAAE,CAAC;IACzC,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,8EAA8E;IAC9E,KAAK,EAAE,YAAY,CAAC;IACpB,wBAAwB,CAAC,EAAE,wBAAwB,CAAC;IACpD,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE;QAC3B,iBAAiB,EAAE,wBAAwB,CAAC;QAC5C,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,8BAA8B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KAC3E,KAAK,IAAI,CAAC;CACZ;AAiDD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,8BAA8B,CAAC,MAAM,CAAC,GAAG,MAAM,CAsBtF;AAED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,iBAAiB,EACzB,WAAW,EAAE,uBAAuB,EACpC,QAAQ,EAAE,sBAAsB,EAChC,KAAK,EAAE,8BAA8B,GACpC,OAAO,CAAC,IAAI,CAAC,CAoCf;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,IAAI,CACR,oBAAoB,EACpB,YAAY,GAAG,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,gBAAgB,GAAG,oBAAoB,GAAG,sBAAsB,CACpH,GAAG;IACF,WAAW,EAAE,WAAW,CAAC;IACzB,SAAS,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IACjC,KAAK,EAAE,YAAY,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,IAAI,CACX,oBAAoB,CAAC,SAAS,CAAC,EAC/B,UAAU,GAAG,QAAQ,GAAG,WAAW,GAAG,iBAAiB,GAAG,gBAAgB,CAC3E,CAAC;IACF;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B,EACD,aAAa,EAAE,MAAM,GACpB,kBAAkB,CAsBpB;AA4ED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,oBAAoB,GAAG,QAAQ,EAAE,CAoH/E"}
@@ -3,6 +3,7 @@ import { getGithubFeatureDiagnostics } from "../integrations/github/config.js";
3
3
  import { invalidateCustomProvidersSnapshots } from "./custom-provider-source.js";
4
4
  import { FactoryStartCoordinator } from "../rules/start-coordinator.js";
5
5
  import { FactoryTransitionService } from "../rules/transition-service.js";
6
+ import { LiveSessions } from "../session/live-sessions.js";
6
7
  import { ConfigRoutes } from "./config.js";
7
8
  import { buildFsRoutes } from "./fs.js";
8
9
  import { IntakeRoutes } from "./intake.js";
@@ -288,7 +289,8 @@ function assembleFactoryApiRoutes(deps) {
288
289
  workItems: deps.domains.workItems,
289
290
  queueHealth: deps.domains.queueHealth,
290
291
  transitionService,
291
- startCoordinator
292
+ startCoordinator,
293
+ liveSessions: new LiveSessions(deps.controller)
292
294
  }).routes() : []
293
295
  ];
294
296
  }