@mastra/factory 0.12.0-alpha.9 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -10
- package/dist/factory.d.ts +8 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +4 -3
- package/dist/factory.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/integrations/github/acceptance-labels.d.ts +17 -0
- package/dist/integrations/github/acceptance-labels.d.ts.map +1 -0
- package/dist/integrations/github/acceptance-labels.js +55 -0
- package/dist/integrations/github/acceptance-labels.js.map +1 -0
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +47 -10
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/reconcile-worker.d.ts.map +1 -1
- package/dist/integrations/github/reconcile-worker.js +2 -2
- package/dist/integrations/github/reconcile-worker.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +10 -13
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +74 -65
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/github/settle-or-abort.d.ts +7 -0
- package/dist/integrations/github/settle-or-abort.d.ts.map +1 -0
- package/dist/integrations/github/settle-or-abort.js +25 -0
- package/dist/integrations/github/settle-or-abort.js.map +1 -0
- package/dist/integrations/platform/github/event-worker.d.ts.map +1 -1
- package/dist/integrations/platform/github/event-worker.js +3 -3
- package/dist/integrations/platform/github/event-worker.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +37 -5
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/packages/_internals/workspace/dist/index.js +35 -0
- package/dist/packages/_internals/workspace/dist/index.js.map +1 -0
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +6 -0
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/oauth.d.ts +3 -0
- package/dist/routes/oauth.d.ts.map +1 -1
- package/dist/routes/oauth.js +10 -2
- package/dist/routes/oauth.js.map +1 -1
- package/dist/routes/om-seed.d.ts +22 -0
- package/dist/routes/om-seed.d.ts.map +1 -0
- package/dist/routes/om-seed.js +40 -0
- package/dist/routes/om-seed.js.map +1 -0
- package/dist/routes/surface.d.ts +1 -1
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +4 -1
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +7 -0
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/defaults.d.ts.map +1 -1
- package/dist/rules/defaults.js +3 -0
- package/dist/rules/defaults.js.map +1 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +16 -1
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/tools.d.ts.map +1 -1
- package/dist/rules/tools.js +1 -23
- package/dist/rules/tools.js.map +1 -1
- package/dist/rules/transition-service.d.ts +12 -1
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +28 -3
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/sandbox/session-sandbox.d.ts +22 -8
- package/dist/sandbox/session-sandbox.d.ts.map +1 -1
- package/dist/sandbox/session-sandbox.js +36 -37
- package/dist/sandbox/session-sandbox.js.map +1 -1
- package/dist/session/factory-session.js.map +1 -1
- package/dist/session/memory-settings-hydration.d.ts +3 -3
- package/dist/session/memory-settings-hydration.js +3 -3
- package/dist/session/memory-settings-hydration.js.map +1 -1
- package/dist/session/org-seed.d.ts +5 -5
- package/dist/session/org-seed.js +2 -2
- package/dist/session/org-seed.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +8 -0
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +10 -1
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/workspace.d.ts +8 -0
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +21 -6
- package/dist/workspace.js.map +1 -1
- package/package.json +11 -10
- package/dist/session/checkpoint-capture.d.ts +0 -18
- package/dist/session/checkpoint-capture.d.ts.map +0 -1
- package/dist/session/checkpoint-capture.js +0 -27
- package/dist/session/checkpoint-capture.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconcile-worker.js","names":["#reconcile","#reconcileIssues","#sourceControl","#intervalMs","#issueIntervalMs","#leaseTtlMs","#leaseOwner","#now","#leaseProvider","#running","#schedule","#timer","#inFlight","#tick","#nextDelay","#nextPullRequestReconcileAt","#nextIssueReconcileAt","#targets"],"sources":["../../../src/integrations/github/reconcile-worker.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\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 {\n ConfiguredExternalRepositoryKey,\n ExternalRepositoryProjectTarget,\n SourceControlRepository,\n} from '../../storage/domains/source-control/base.js';\nimport type { GithubIssueReconciler } from './issue-reconciler.js';\nimport type { GithubPullRequestReconciler, ReconcileRepository } from './rules.js';\n\nexport const DEFAULT_GITHUB_RECONCILE_INTERVAL_MS = 5 * 60_000;\nconst MIN_LEASE_TTL_MS = 30_000;\nconst LEASE_KEY = 'github:pull-request-reconcile';\n\n/** Storage slice the sweep needs to turn configured repositories into targets. */\nexport interface GithubReconcileRepositorySource {\n projectRepositories: {\n listConfiguredExternalKeys(): Promise<ConfiguredExternalRepositoryKey[]>;\n listByExternalRepository(args: {\n installationExternalId: string;\n repositoryExternalId: string;\n }): Promise<ExternalRepositoryProjectTarget[]>;\n };\n repositories: {\n findByExternalId(args: { orgId: string; externalId: string }): Promise<SourceControlRepository | null>;\n };\n}\n\nexport interface GithubReconcileWorkerConfig {\n reconcile?: GithubPullRequestReconciler;\n reconcileIssues?: GithubIssueReconciler;\n sourceControl: GithubReconcileRepositorySource;\n intervalMs?: number;\n issueIntervalMs?: number;\n now?: () => number;\n}\n\n/**\n * Periodic GitHub state sweeps for the self-hosted integration. Pull requests\n * drive review cards and issues drive work cards, but both share discovery and\n * a lease so replicas never sweep the same configured repositories together.\n */\nexport class GithubReconcileWorker extends MastraWorker {\n readonly name = 'github-pull-request-reconcile';\n\n readonly #reconcile: GithubPullRequestReconciler | undefined;\n readonly #reconcileIssues: GithubIssueReconciler | undefined;\n readonly #sourceControl: GithubReconcileRepositorySource;\n readonly #intervalMs: number;\n readonly #issueIntervalMs: number;\n readonly #leaseTtlMs: number;\n readonly #leaseOwner = randomUUID();\n readonly #now: () => number;\n\n #running = false;\n #timer: ReturnType<typeof setTimeout> | undefined;\n #inFlight: Promise<void> | undefined;\n #leaseProvider: LeaseProvider = NoopLeaseProvider;\n #nextPullRequestReconcileAt = 0;\n #nextIssueReconcileAt = 0;\n\n constructor(config: GithubReconcileWorkerConfig) {\n super();\n if (!config.reconcile && !config.reconcileIssues) {\n throw new Error('GitHub reconcile worker requires a pull request or issue reconciler.');\n }\n this.#reconcile = config.reconcile;\n this.#reconcileIssues = config.reconcileIssues;\n this.#sourceControl = config.sourceControl;\n this.#intervalMs = config.intervalMs ?? DEFAULT_GITHUB_RECONCILE_INTERVAL_MS;\n this.#issueIntervalMs = config.issueIntervalMs ?? this.#intervalMs;\n if (!Number.isFinite(this.#intervalMs) || this.#intervalMs <= 0) {\n throw new Error('GitHub pull request reconcile interval must be a positive number.');\n }\n if (!Number.isFinite(this.#issueIntervalMs) || this.#issueIntervalMs <= 0) {\n throw new Error('GitHub issue reconcile interval must be a positive number.');\n }\n this.#leaseTtlMs = Math.max(MIN_LEASE_TTL_MS, Math.min(this.#intervalMs, this.#issueIntervalMs) * 3);\n this.#now = config.now ?? Date.now;\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('GithubReconcileWorker: call init() before start()');\n this.#running = true;\n this.deps.logger.info('GitHub reconcile worker started', {\n pullRequestIntervalMs: this.#reconcile ? this.#intervalMs : undefined,\n issueIntervalMs: this.#reconcileIssues ? this.#issueIntervalMs : undefined,\n });\n // Sweep on boot: a restart is exactly when webhooks were most likely missed.\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 await this.#inFlight;\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().finally(() => {\n this.#inFlight = undefined;\n this.#schedule(this.#nextDelay());\n });\n this.#inFlight = run;\n }, delayMs);\n this.#timer.unref?.();\n }\n\n #nextDelay(): number {\n const now = this.#now();\n const due = [\n this.#reconcile ? this.#nextPullRequestReconcileAt : undefined,\n this.#reconcileIssues ? this.#nextIssueReconcileAt : undefined,\n ].filter((at): at is number => at !== undefined);\n return Math.max(0, Math.min(...due) - now);\n }\n\n async #tick(): Promise<void> {\n const now = this.#now();\n const reconcilePullRequests = Boolean(this.#reconcile && now >= this.#nextPullRequestReconcileAt);\n const reconcileIssues = Boolean(this.#reconcileIssues && now >= this.#nextIssueReconcileAt);\n if (!reconcilePullRequests && !reconcileIssues) return;\n if (reconcilePullRequests) this.#nextPullRequestReconcileAt = now + this.#intervalMs;\n if (reconcileIssues) this.#nextIssueReconcileAt = now + this.#issueIntervalMs;\n\n // Replicas share one sweep: the ingress dedupes duplicate writes, but the\n // reads still burn the installation's rate limit.\n const lease = await this.#leaseProvider\n .acquireLease(LEASE_KEY, this.#leaseOwner, this.#leaseTtlMs)\n .catch(() => ({ acquired: false }));\n if (!lease.acquired) return;\n\n let hasLease = true;\n const renewalTimer = setInterval(\n () => {\n void this.#leaseProvider\n .renewLease(LEASE_KEY, this.#leaseOwner, this.#leaseTtlMs)\n .then(renewed => {\n if (!renewed) {\n hasLease = false;\n clearInterval(renewalTimer);\n this.deps?.logger.warn('GitHub reconcile lease was lost mid-sweep; aborting further writes');\n }\n })\n .catch(error => {\n hasLease = false;\n clearInterval(renewalTimer);\n this.deps?.logger.warn('GitHub reconcile lease renewal failed; aborting further writes', {\n error: error instanceof Error ? error.message : String(error),\n });\n });\n },\n Math.max(1_000, Math.floor(this.#leaseTtlMs / 3)),\n );\n renewalTimer.unref?.();\n\n try {\n const targets = await this.#targets();\n if (targets.length === 0) return;\n\n if (reconcilePullRequests && this.#reconcile) {\n const startedAt = Date.now();\n try {\n const { errors, ...counts } = await this.#reconcile(targets);\n const context = { ...counts, candidateRepositories: targets.length, durationMs: Date.now() - startedAt };\n if (counts.failed > 0) {\n this.deps?.logger.warn('GitHub pull request reconcile sweep completed with failures', { ...context, errors });\n } else if (counts.merged > 0 || counts.closed > 0) {\n this.deps?.logger.info('GitHub pull request reconcile replayed missed merges/closes', context);\n } else {\n this.deps?.logger.debug('GitHub pull request reconcile sweep completed', context);\n }\n } catch (error) {\n this.deps?.logger.warn('GitHub pull request reconcile sweep failed', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n if (reconcileIssues && this.#reconcileIssues && hasLease) {\n const startedAt = Date.now();\n try {\n const { errors, ...counts } = await this.#reconcileIssues(targets);\n const context = { ...counts, candidateRepositories: targets.length, durationMs: Date.now() - startedAt };\n if (counts.failed > 0) {\n this.deps?.logger.warn('GitHub issue reconcile sweep completed with failures', { ...context, errors });\n } else if (counts.closed > 0) {\n this.deps?.logger.info('GitHub issue reconcile replayed closed work items', context);\n } else if (counts.updated > 0) {\n this.deps?.logger.info('GitHub issue reconcile patched stale metadata', context);\n } else {\n this.deps?.logger.debug('GitHub issue reconcile sweep completed', context);\n }\n } catch (error) {\n this.deps?.logger.warn('GitHub issue reconcile sweep failed', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n } else if (reconcileIssues && this.#reconcileIssues && !hasLease) {\n this.deps?.logger.debug('GitHub issue reconcile skipped: lease lost during pull-request sweep');\n }\n\n } finally {\n clearInterval(renewalTimer);\n if (hasLease) {\n await this.#leaseProvider.releaseLease(LEASE_KEY, this.#leaseOwner).catch(() => undefined);\n }\n }\n }\n\n /**\n * Configured (installation, repository) pairs resolved to the numeric ids and\n * slug the sweep addresses GitHub with. A pair whose repository row is gone\n * is skipped rather than failing the sweep for the others.\n */\n async #targets(): Promise<ReconcileRepository[]> {\n const keys = await this.#sourceControl.projectRepositories.listConfiguredExternalKeys();\n const targets: ReconcileRepository[] = [];\n for (const key of keys) {\n const installationId = Number(key.installationExternalId);\n const repositoryId = Number(key.repositoryExternalId);\n if (!Number.isSafeInteger(installationId) || !Number.isSafeInteger(repositoryId)) continue;\n const projects = await this.#sourceControl.projectRepositories.listByExternalRepository(key);\n const orgId = projects[0]?.orgId;\n if (!orgId) continue;\n const repository = await this.#sourceControl.repositories.findByExternalId({\n orgId,\n externalId: key.repositoryExternalId,\n });\n if (!repository?.slug) continue;\n targets.push({ id: repositoryId, fullName: repository.slug, installationId });\n }\n return targets;\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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"reconcile-worker.js","names":["#reconcile","#reconcileIssues","#sourceControl","#intervalMs","#issueIntervalMs","#leaseTtlMs","#leaseOwner","#now","#leaseProvider","#running","#schedule","#timer","#inFlight","#tick","#nextDelay","#nextPullRequestReconcileAt","#nextIssueReconcileAt","#targets"],"sources":["../../../src/integrations/github/reconcile-worker.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\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 {\n ConfiguredExternalRepositoryKey,\n ExternalRepositoryProjectTarget,\n SourceControlRepository,\n} from '../../storage/domains/source-control/base.js';\nimport type { GithubIssueReconciler } from './issue-reconciler.js';\nimport type { GithubPullRequestReconciler, ReconcileRepository } from './rules.js';\n\n// Webhooks are the primary sync; the sweeps only catch drift, so hourly.\nexport const DEFAULT_GITHUB_RECONCILE_INTERVAL_MS = 60 * 60_000;\nconst MIN_LEASE_TTL_MS = 30_000;\nconst LEASE_KEY = 'github:pull-request-reconcile';\n\n/** Storage slice the sweep needs to turn configured repositories into targets. */\nexport interface GithubReconcileRepositorySource {\n projectRepositories: {\n listConfiguredExternalKeys(): Promise<ConfiguredExternalRepositoryKey[]>;\n listByExternalRepository(args: {\n installationExternalId: string;\n repositoryExternalId: string;\n }): Promise<ExternalRepositoryProjectTarget[]>;\n };\n repositories: {\n findByExternalId(args: { orgId: string; externalId: string }): Promise<SourceControlRepository | null>;\n };\n}\n\nexport interface GithubReconcileWorkerConfig {\n reconcile?: GithubPullRequestReconciler;\n reconcileIssues?: GithubIssueReconciler;\n sourceControl: GithubReconcileRepositorySource;\n intervalMs?: number;\n issueIntervalMs?: number;\n now?: () => number;\n}\n\n/**\n * Periodic GitHub state sweeps for the self-hosted integration. Pull requests\n * drive review cards and issues drive work cards, but both share discovery and\n * a lease so replicas never sweep the same configured repositories together.\n */\nexport class GithubReconcileWorker extends MastraWorker {\n readonly name = 'github-pull-request-reconcile';\n\n readonly #reconcile: GithubPullRequestReconciler | undefined;\n readonly #reconcileIssues: GithubIssueReconciler | undefined;\n readonly #sourceControl: GithubReconcileRepositorySource;\n readonly #intervalMs: number;\n readonly #issueIntervalMs: number;\n readonly #leaseTtlMs: number;\n readonly #leaseOwner = randomUUID();\n readonly #now: () => number;\n\n #running = false;\n #timer: ReturnType<typeof setTimeout> | undefined;\n #inFlight: Promise<void> | undefined;\n #leaseProvider: LeaseProvider = NoopLeaseProvider;\n #nextPullRequestReconcileAt = 0;\n #nextIssueReconcileAt = 0;\n\n constructor(config: GithubReconcileWorkerConfig) {\n super();\n if (!config.reconcile && !config.reconcileIssues) {\n throw new Error('GitHub reconcile worker requires a pull request or issue reconciler.');\n }\n this.#reconcile = config.reconcile;\n this.#reconcileIssues = config.reconcileIssues;\n this.#sourceControl = config.sourceControl;\n this.#intervalMs = config.intervalMs ?? DEFAULT_GITHUB_RECONCILE_INTERVAL_MS;\n this.#issueIntervalMs = config.issueIntervalMs ?? this.#intervalMs;\n if (!Number.isFinite(this.#intervalMs) || this.#intervalMs <= 0) {\n throw new Error('GitHub pull request reconcile interval must be a positive number.');\n }\n if (!Number.isFinite(this.#issueIntervalMs) || this.#issueIntervalMs <= 0) {\n throw new Error('GitHub issue reconcile interval must be a positive number.');\n }\n this.#leaseTtlMs = Math.max(MIN_LEASE_TTL_MS, Math.min(this.#intervalMs, this.#issueIntervalMs) * 3);\n this.#now = config.now ?? Date.now;\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('GithubReconcileWorker: call init() before start()');\n this.#running = true;\n this.deps.logger.info('GitHub reconcile worker started', {\n pullRequestIntervalMs: this.#reconcile ? this.#intervalMs : undefined,\n issueIntervalMs: this.#reconcileIssues ? this.#issueIntervalMs : undefined,\n });\n // Sweep on boot: a restart is exactly when webhooks were most likely missed.\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 await this.#inFlight;\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().finally(() => {\n this.#inFlight = undefined;\n this.#schedule(this.#nextDelay());\n });\n this.#inFlight = run;\n }, delayMs);\n this.#timer.unref?.();\n }\n\n #nextDelay(): number {\n const now = this.#now();\n const due = [\n this.#reconcile ? this.#nextPullRequestReconcileAt : undefined,\n this.#reconcileIssues ? this.#nextIssueReconcileAt : undefined,\n ].filter((at): at is number => at !== undefined);\n return Math.max(0, Math.min(...due) - now);\n }\n\n async #tick(): Promise<void> {\n const now = this.#now();\n const reconcilePullRequests = Boolean(this.#reconcile && now >= this.#nextPullRequestReconcileAt);\n const reconcileIssues = Boolean(this.#reconcileIssues && now >= this.#nextIssueReconcileAt);\n if (!reconcilePullRequests && !reconcileIssues) return;\n if (reconcilePullRequests) this.#nextPullRequestReconcileAt = now + this.#intervalMs;\n if (reconcileIssues) this.#nextIssueReconcileAt = now + this.#issueIntervalMs;\n\n // Replicas share one sweep: the ingress dedupes duplicate writes, but the\n // reads still burn the installation's rate limit.\n const lease = await this.#leaseProvider\n .acquireLease(LEASE_KEY, this.#leaseOwner, this.#leaseTtlMs)\n .catch(() => ({ acquired: false }));\n if (!lease.acquired) return;\n\n let hasLease = true;\n const renewalTimer = setInterval(\n () => {\n void this.#leaseProvider\n .renewLease(LEASE_KEY, this.#leaseOwner, this.#leaseTtlMs)\n .then(renewed => {\n if (!renewed) {\n hasLease = false;\n clearInterval(renewalTimer);\n this.deps?.logger.warn('GitHub reconcile lease was lost mid-sweep; aborting further writes');\n }\n })\n .catch(error => {\n hasLease = false;\n clearInterval(renewalTimer);\n this.deps?.logger.warn('GitHub reconcile lease renewal failed; aborting further writes', {\n error: error instanceof Error ? error.message : String(error),\n });\n });\n },\n Math.max(1_000, Math.floor(this.#leaseTtlMs / 3)),\n );\n renewalTimer.unref?.();\n\n try {\n const targets = await this.#targets();\n if (targets.length === 0) return;\n\n if (reconcilePullRequests && this.#reconcile) {\n const startedAt = Date.now();\n try {\n const { errors, ...counts } = await this.#reconcile(targets);\n const context = { ...counts, candidateRepositories: targets.length, durationMs: Date.now() - startedAt };\n if (counts.failed > 0) {\n this.deps?.logger.warn('GitHub pull request reconcile sweep completed with failures', { ...context, errors });\n } else if (counts.merged > 0 || counts.closed > 0) {\n this.deps?.logger.info('GitHub pull request reconcile replayed missed merges/closes', context);\n } else {\n this.deps?.logger.debug('GitHub pull request reconcile sweep completed', context);\n }\n } catch (error) {\n this.deps?.logger.warn('GitHub pull request reconcile sweep failed', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n if (reconcileIssues && this.#reconcileIssues && hasLease) {\n const startedAt = Date.now();\n try {\n const { errors, ...counts } = await this.#reconcileIssues(targets);\n const context = { ...counts, candidateRepositories: targets.length, durationMs: Date.now() - startedAt };\n if (counts.failed > 0) {\n this.deps?.logger.warn('GitHub issue reconcile sweep completed with failures', { ...context, errors });\n } else if (counts.closed > 0) {\n this.deps?.logger.info('GitHub issue reconcile replayed closed work items', context);\n } else if (counts.updated > 0) {\n this.deps?.logger.info('GitHub issue reconcile patched stale metadata', context);\n } else {\n this.deps?.logger.debug('GitHub issue reconcile sweep completed', context);\n }\n } catch (error) {\n this.deps?.logger.warn('GitHub issue reconcile sweep failed', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n } else if (reconcileIssues && this.#reconcileIssues && !hasLease) {\n this.deps?.logger.debug('GitHub issue reconcile skipped: lease lost during pull-request sweep');\n }\n\n } finally {\n clearInterval(renewalTimer);\n if (hasLease) {\n await this.#leaseProvider.releaseLease(LEASE_KEY, this.#leaseOwner).catch(() => undefined);\n }\n }\n }\n\n /**\n * Configured (installation, repository) pairs resolved to the numeric ids and\n * slug the sweep addresses GitHub with. A pair whose repository row is gone\n * is skipped rather than failing the sweep for the others.\n */\n async #targets(): Promise<ReconcileRepository[]> {\n const keys = await this.#sourceControl.projectRepositories.listConfiguredExternalKeys();\n const targets: ReconcileRepository[] = [];\n for (const key of keys) {\n const installationId = Number(key.installationExternalId);\n const repositoryId = Number(key.repositoryExternalId);\n if (!Number.isSafeInteger(installationId) || !Number.isSafeInteger(repositoryId)) continue;\n const projects = await this.#sourceControl.projectRepositories.listByExternalRepository(key);\n const orgId = projects[0]?.orgId;\n if (!orgId) continue;\n const repository = await this.#sourceControl.repositories.findByExternalId({\n orgId,\n externalId: key.repositoryExternalId,\n });\n if (!repository?.slug) continue;\n targets.push({ id: repositoryId, fullName: repository.slug, installationId });\n }\n return targets;\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"],"mappings":"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"}
|
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
* *inside* the session's sandbox, so the agent's file tools and command tools
|
|
6
6
|
* operate entirely against the remote checkout.
|
|
7
7
|
*
|
|
8
|
-
* - `materializeRepo(row, token)`
|
|
9
|
-
* (
|
|
10
|
-
* scrubbed from the git remote afterwards so it
|
|
8
|
+
* - `materializeRepo(row, token)` clones the repo inside the sandbox when no
|
|
9
|
+
* checkout exists yet (a base-image boot, a wiped disk), using a short-lived
|
|
10
|
+
* installation token that is scrubbed from the git remote afterwards so it
|
|
11
|
+
* never persists in the VM. A checkout that is already there, from a repo
|
|
12
|
+
* template image or an earlier start, is left exactly as it is.
|
|
11
13
|
*
|
|
12
|
-
* This module owns everything git/GitHub: clone
|
|
14
|
+
* This module owns everything git/GitHub: clone, commit/push, setup/teardown commands,
|
|
13
15
|
* and `gh pr create`. Workdir layout lives in `../sandbox/workdir`.
|
|
14
16
|
*/
|
|
15
17
|
import type { ExecutableSandbox, SandboxCommandResult } from '../../sandbox/materialization.js';
|
|
@@ -74,9 +76,10 @@ export interface MaterializeRepoOptions {
|
|
|
74
76
|
storage: MaterializationStore;
|
|
75
77
|
}
|
|
76
78
|
/**
|
|
77
|
-
* Materialize the repo inside the user's sandbox
|
|
78
|
-
*
|
|
79
|
-
* `materialized_at` on the per-user sandbox
|
|
79
|
+
* Materialize the repo inside the user's sandbox: clone when no checkout of
|
|
80
|
+
* this repo exists, otherwise nothing. Scrubs the install token from the
|
|
81
|
+
* remote after a clone and sets `materialized_at` on the per-user sandbox
|
|
82
|
+
* binding row.
|
|
80
83
|
*/
|
|
81
84
|
export declare function materializeRepo(options: MaterializeRepoOptions): Promise<void>;
|
|
82
85
|
/** Check out a session's branch inside its isolated repository clone. */
|
|
@@ -86,12 +89,6 @@ export declare function checkoutSessionBranch(sandbox: ExecutableSandbox, workdi
|
|
|
86
89
|
token: string;
|
|
87
90
|
repoFullName: string;
|
|
88
91
|
}): Promise<void>;
|
|
89
|
-
/**
|
|
90
|
-
* True when the workdir already holds a git checkout whose `origin` points at
|
|
91
|
-
* this exact repo. Matches both the clean and token-auth URL forms; any other
|
|
92
|
-
* remote (or no git dir at all) falls back to the clone path.
|
|
93
|
-
*/
|
|
94
|
-
export declare function hasExistingCheckout(sandbox: ExecutableSandbox, workdir: string, repoFullName: string): Promise<boolean>;
|
|
95
92
|
/**
|
|
96
93
|
* Validate a git ref (branch) name. Server-side defense-in-depth: only allow a
|
|
97
94
|
* conservative character set so a branch can never be built into a shell
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/sandbox.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/sandbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import { timedPhase } from "../../timing.js";
|
|
2
|
+
import { repoCloneCommand } from "../../packages/_internals/workspace/dist/index.js";
|
|
2
3
|
//#region src/integrations/github/sandbox.ts
|
|
3
4
|
/**
|
|
5
|
+
* Repo materialization for GitHub-backed repositories.
|
|
6
|
+
*
|
|
7
|
+
* A GitHub repo is never cloned onto the server host. The repo is cloned
|
|
8
|
+
* *inside* the session's sandbox, so the agent's file tools and command tools
|
|
9
|
+
* operate entirely against the remote checkout.
|
|
10
|
+
*
|
|
11
|
+
* - `materializeRepo(row, token)` clones the repo inside the sandbox when no
|
|
12
|
+
* checkout exists yet (a base-image boot, a wiped disk), using a short-lived
|
|
13
|
+
* installation token that is scrubbed from the git remote afterwards so it
|
|
14
|
+
* never persists in the VM. A checkout that is already there, from a repo
|
|
15
|
+
* template image or an earlier start, is left exactly as it is.
|
|
16
|
+
*
|
|
17
|
+
* This module owns everything git/GitHub: clone, commit/push, setup/teardown commands,
|
|
18
|
+
* and `gh pr create`. Workdir layout lives in `../sandbox/workdir`.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
4
21
|
* Single-quote a string for safe POSIX shell interpolation. Wraps the value in
|
|
5
22
|
* single quotes and escapes any embedded single quote using the canonical
|
|
6
23
|
* close-quote / escaped-quote / reopen-quote sequence (`'\''`). This is the
|
|
@@ -42,16 +59,22 @@ const SH_RETRY_DELAY_MS = 2e3;
|
|
|
42
59
|
*/
|
|
43
60
|
async function sh(sandbox, script, options = {}) {
|
|
44
61
|
const deadlineMs = Date.now() + (options.timeoutMs ?? 9e5);
|
|
45
|
-
for (let attempt = 0;; attempt++)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
for (let attempt = 0;; attempt++) {
|
|
63
|
+
const started = performance.now();
|
|
64
|
+
try {
|
|
65
|
+
const result = await shOnce(sandbox, script, {
|
|
66
|
+
...options,
|
|
67
|
+
timeoutMs: Math.max(deadlineMs - Date.now(), 1)
|
|
68
|
+
});
|
|
69
|
+
if (options.phase) process.stderr.write(`[factory:timing] ${options.phase} attempt=${attempt + 1} exit=${result.exitCode} ${Math.round(performance.now() - started)}ms\n`);
|
|
70
|
+
return result;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (options.phase) process.stderr.write(`[factory:timing] ${options.phase} attempt=${attempt + 1} threw after ${Math.round(performance.now() - started)}ms: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
73
|
+
if (attempt >= SH_RETRIES || !isTransientTransportError(error)) throw error;
|
|
74
|
+
const delayMs = SH_RETRY_DELAY_MS * (attempt + 1);
|
|
75
|
+
if (deadlineMs - Date.now() <= delayMs) throw error;
|
|
76
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
77
|
+
}
|
|
55
78
|
}
|
|
56
79
|
}
|
|
57
80
|
/** Single `sh -c` execution attempt, bounded by the hang guard. */
|
|
@@ -109,6 +132,7 @@ async function gitTransfer(sandbox, script, options = {}) {
|
|
|
109
132
|
timeoutMs: Math.max(deadlineMs - Date.now(), 1)
|
|
110
133
|
});
|
|
111
134
|
if (result.exitCode === 0 || attempt >= GIT_TRANSFER_RETRIES || !isTransientGitFailure(result)) return result;
|
|
135
|
+
process.stderr.write(`[factory:timing] git ${shOptions.phase ?? "transfer"} retrying after attempt ${attempt + 1}\n`);
|
|
112
136
|
const delayMs = GIT_TRANSFER_RETRY_DELAY_MS * (attempt + 1);
|
|
113
137
|
if (deadlineMs - Date.now() <= delayMs) return result;
|
|
114
138
|
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
@@ -135,9 +159,10 @@ function cleanUrl(repoFullName) {
|
|
|
135
159
|
return `https://github.com/${repoFullName}.git`;
|
|
136
160
|
}
|
|
137
161
|
/**
|
|
138
|
-
* Materialize the repo inside the user's sandbox
|
|
139
|
-
*
|
|
140
|
-
* `materialized_at` on the per-user sandbox
|
|
162
|
+
* Materialize the repo inside the user's sandbox: clone when no checkout of
|
|
163
|
+
* this repo exists, otherwise nothing. Scrubs the install token from the
|
|
164
|
+
* remote after a clone and sets `materialized_at` on the per-user sandbox
|
|
165
|
+
* binding row.
|
|
141
166
|
*/
|
|
142
167
|
async function materializeRepo(options) {
|
|
143
168
|
return timedPhase("workspace.materialize", () => materializeRepoImpl(options));
|
|
@@ -149,13 +174,18 @@ async function materializeRepoImpl(options) {
|
|
|
149
174
|
if (!/^[\w.-]+\/[\w.-]+$/.test(repo)) throw new MaterializeError(`Refusing to materialize: invalid repo full name '${repo}'.`, "clone-failed");
|
|
150
175
|
if (!/^[A-Za-z0-9_./-]+$/.test(repoInfo.defaultBranch)) throw new MaterializeError(`Refusing to materialize: invalid default branch '${repoInfo.defaultBranch}'.`, "clone-failed");
|
|
151
176
|
if ((await sh(sandbox, "git --version")).exitCode !== 0) throw new MaterializeError("git is not installed in the sandbox. The sandbox template must include git.", "git-missing");
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
177
|
+
const existing = await existingCheckoutRemote(sandbox, workdir, repo);
|
|
178
|
+
if (existing !== null) {
|
|
179
|
+
if (/\/\/[^/]*@/.test(existing)) await scrubRemote(sandbox, workdir, repo, true);
|
|
180
|
+
} else {
|
|
181
|
+
await sh(sandbox, `mkdir -p ${shellQuote(workdir)} && find ${shellQuote(workdir)} -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +`);
|
|
182
|
+
let tokenInRemote = false;
|
|
183
|
+
try {
|
|
184
|
+
const clone = await gitTransfer(sandbox, repoCloneCommand({
|
|
185
|
+
cloneUrl: tokenUrl(repo, token),
|
|
186
|
+
destination: workdir,
|
|
187
|
+
branch: repoInfo.defaultBranch
|
|
188
|
+
}), {
|
|
159
189
|
phase: "repository clone",
|
|
160
190
|
beforeRetry: async () => {
|
|
161
191
|
await sh(sandbox, `mkdir -p ${shellQuote(workdir)} && find ${shellQuote(workdir)} -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +`);
|
|
@@ -166,19 +196,11 @@ async function materializeRepoImpl(options) {
|
|
|
166
196
|
throw classifyGitFailure(clone, "clone-failed");
|
|
167
197
|
}
|
|
168
198
|
tokenInRemote = true;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (setUrl.exitCode !== 0) throw new MaterializeError(`Failed to set git remote: ${setUrl.stderr}`, "pull-failed");
|
|
172
|
-
tokenInRemote = true;
|
|
173
|
-
const pull = await gitTransfer(sandbox, `git -C ${shellQuote(workdir)} pull --ff-only`, { phase: "repository pull" });
|
|
174
|
-
if (pull.exitCode !== 0) {
|
|
175
|
-
if (!isBenignNonFastForward(pull)) throw classifyGitFailure(pull, "pull-failed");
|
|
176
|
-
}
|
|
199
|
+
} catch (primary) {
|
|
200
|
+
throw await scrubbedFailure(sandbox, workdir, repo, tokenInRemote, primary, "clone-failed");
|
|
177
201
|
}
|
|
178
|
-
|
|
179
|
-
throw await scrubbedFailure(sandbox, workdir, repo, tokenInRemote, primary, "pull-failed");
|
|
202
|
+
await scrubRemote(sandbox, workdir, repo, tokenInRemote);
|
|
180
203
|
}
|
|
181
|
-
await scrubRemote(sandbox, workdir, repo, tokenInRemote);
|
|
182
204
|
await storage.markMaterialized({ id: sandboxRow.id });
|
|
183
205
|
}
|
|
184
206
|
/** Check out a session's branch inside its isolated repository clone. */
|
|
@@ -199,7 +221,7 @@ async function checkoutSessionBranchImpl(sandbox, workdir, { branch, baseBranch,
|
|
|
199
221
|
}
|
|
200
222
|
const authUrl = tokenUrl(repoFullName, token);
|
|
201
223
|
try {
|
|
202
|
-
const setUrl = await sh(sandbox, `git -C ${shellQuote(workdir)} remote set-url origin ${shellQuote(authUrl)}
|
|
224
|
+
const setUrl = await sh(sandbox, `git -C ${shellQuote(workdir)} remote set-url origin ${shellQuote(authUrl)}`, { phase: "branch checkout remote" });
|
|
203
225
|
if (setUrl.exitCode !== 0) throw classifyGitFailure(setUrl, "pull-failed");
|
|
204
226
|
const fetch = await sh(sandbox, `git -C ${shellQuote(workdir)} fetch origin ${shellQuote(baseBranch)} && git -C ${shellQuote(workdir)} checkout -b ${shellQuote(branch)} FETCH_HEAD`, {
|
|
205
227
|
timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,
|
|
@@ -244,16 +266,27 @@ function isBlockedByLocalWork(result) {
|
|
|
244
266
|
return /Your local changes to the following files would be overwritten by checkout|untracked working tree files would be overwritten by checkout/i.test(output);
|
|
245
267
|
}
|
|
246
268
|
/**
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
* remote (or no git dir at all)
|
|
269
|
+
* The `origin` URL of a checkout of this exact repo in the workdir, or null
|
|
270
|
+
* when there is none. Matches both the clean and token-auth URL forms; any
|
|
271
|
+
* other remote (or no git dir at all) sends materialize down the clone path.
|
|
250
272
|
*/
|
|
251
|
-
async function
|
|
273
|
+
async function existingCheckoutRemote(sandbox, workdir, repoFullName) {
|
|
252
274
|
const result = await sh(sandbox, `git -C ${shellQuote(workdir)} remote get-url origin`);
|
|
253
|
-
if (result.exitCode !== 0) return
|
|
254
|
-
const url = result.stdout.trim()
|
|
255
|
-
|
|
256
|
-
|
|
275
|
+
if (result.exitCode !== 0) return null;
|
|
276
|
+
const url = result.stdout.trim();
|
|
277
|
+
return isRemoteForRepo(url, repoFullName) ? url : null;
|
|
278
|
+
}
|
|
279
|
+
/** True only for `https://github.com/<repo>[.git]`, with or without embedded credentials. */
|
|
280
|
+
function isRemoteForRepo(url, repoFullName) {
|
|
281
|
+
let parsed;
|
|
282
|
+
try {
|
|
283
|
+
parsed = new URL(url);
|
|
284
|
+
} catch {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
if (parsed.protocol !== "https:" || parsed.hostname.toLowerCase() !== "github.com") return false;
|
|
288
|
+
if (parsed.port !== "" || parsed.search !== "" || parsed.hash !== "") return false;
|
|
289
|
+
return parsed.pathname.replace(/\.git$/, "").toLowerCase() === `/${repoFullName.toLowerCase()}`;
|
|
257
290
|
}
|
|
258
291
|
/** Probed without `git -C` so a missing workdir returns false instead of throwing. */
|
|
259
292
|
async function hasGitDir(sandbox, workdir) {
|
|
@@ -302,30 +335,6 @@ async function scrubbedFailure(sandbox, workdir, repoFullName, tokenInRemote, pr
|
|
|
302
335
|
}
|
|
303
336
|
}
|
|
304
337
|
/**
|
|
305
|
-
* True when a failed `git pull --ff-only` on re-open just means the current
|
|
306
|
-
* branch can't be fast-forwarded — not that anything is broken. The shared
|
|
307
|
-
* workdir is routinely left on a session's working branch, which may have
|
|
308
|
-
* local commits diverging from upstream, no upstream at all (session branches
|
|
309
|
-
* are created from `FETCH_HEAD`), or a detached HEAD. A checkout can also
|
|
310
|
-
* hold uncommitted or untracked files (a build or script run left residue,
|
|
311
|
-
* or an older session worked directly in the shared checkout), which makes
|
|
312
|
-
* git refuse the merge outright. A checkout carrying `pull.rebase` in its git
|
|
313
|
-
* config refuses for the same reason but says so in rebase's words instead of
|
|
314
|
-
* merge's. After a PR merges with branch auto-delete, the configured upstream
|
|
315
|
-
* ref may also be gone (`no such ref was fetched` / `couldn't find remote ref`);
|
|
316
|
-
* there is nothing to pull and the checkout is still intact. In all of these
|
|
317
|
-
* cases materialization must keep it as-is rather than fail the workspace open
|
|
318
|
-
* — and must never discard the local state to force the pull through.
|
|
319
|
-
*/
|
|
320
|
-
function isDeletedUpstreamRef(result) {
|
|
321
|
-
const output = `${result.stderr || ""}\n${result.stdout || ""}`;
|
|
322
|
-
return /no such ref was fetched|couldn't find remote ref/i.test(output);
|
|
323
|
-
}
|
|
324
|
-
function isBenignNonFastForward(result) {
|
|
325
|
-
const output = `${result.stderr || ""}\n${result.stdout || ""}`;
|
|
326
|
-
return isDeletedUpstreamRef(result) || /Not possible to fast-forward|Diverging branches can't be fast-forwarded|no tracking information for the current branch|You are not currently on a branch|Your local changes to the following files would be overwritten by merge|untracked working tree files would be overwritten by merge|cannot pull with rebase|cannot rebase: You have unstaged changes|Your index contains uncommitted changes/i.test(output);
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
338
|
* Turn a failed git command into an actionable error, detecting the common
|
|
330
339
|
* "cannot reach github.com" egress failure.
|
|
331
340
|
*/
|
|
@@ -524,6 +533,6 @@ async function createPullRequest(sandbox, workdir, { token, base, head, title, b
|
|
|
524
533
|
return { url };
|
|
525
534
|
}
|
|
526
535
|
//#endregion
|
|
527
|
-
export { CHECKOUT_COMMAND_TIMEOUT_MS, DEFAULT_COMMAND_TIMEOUT_MS, MaterializeError, SetupCommandError, checkoutSessionBranch, commitAll, configureGitIdentity, createPullRequest,
|
|
536
|
+
export { CHECKOUT_COMMAND_TIMEOUT_MS, DEFAULT_COMMAND_TIMEOUT_MS, MaterializeError, SetupCommandError, checkoutSessionBranch, commitAll, configureGitIdentity, createPullRequest, isValidGitRef, materializeRepo, pushBranch, resolveGitIdentity, runSetupCommand, runTeardownCommand, sh, shellQuote, withInstallToken };
|
|
528
537
|
|
|
529
538
|
//# sourceMappingURL=sandbox.js.map
|