@mastra/factory 0.6.1-alpha.0 → 0.7.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -0
- package/dist/factory.d.ts +2 -8
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +9 -5
- package/dist/factory.js.map +1 -1
- package/dist/integrations/github/integration.d.ts +2 -4
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +15 -17
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/issue-reconciler.js +4 -4
- package/dist/integrations/github/issue-reconciler.js.map +1 -1
- package/dist/integrations/github/reconcile-worker.d.ts +6 -10
- package/dist/integrations/github/reconcile-worker.d.ts.map +1 -1
- package/dist/integrations/github/reconcile-worker.js +62 -35
- package/dist/integrations/github/reconcile-worker.js.map +1 -1
- package/dist/integrations/github/reconciliation-config.d.ts +3 -0
- package/dist/integrations/github/reconciliation-config.d.ts.map +1 -0
- package/dist/integrations/github/reconciliation-config.js +19 -0
- package/dist/integrations/github/reconciliation-config.js.map +1 -0
- package/dist/integrations/github/routes.d.ts +1 -3
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +1 -95
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/rules.d.ts.map +1 -1
- package/dist/integrations/github/rules.js +2 -1
- package/dist/integrations/github/rules.js.map +1 -1
- package/dist/integrations/github/webhook.d.ts +0 -4
- package/dist/integrations/github/webhook.d.ts.map +1 -1
- package/dist/integrations/github/webhook.js +2 -2
- package/dist/integrations/github/webhook.js.map +1 -1
- package/dist/integrations/linear/integration.d.ts.map +1 -1
- package/dist/integrations/linear/integration.js +4 -3
- package/dist/integrations/linear/integration.js.map +1 -1
- package/dist/integrations/linear/reconciliation-config.d.ts +3 -0
- package/dist/integrations/linear/reconciliation-config.d.ts.map +1 -0
- package/dist/integrations/linear/reconciliation-config.js +27 -0
- package/dist/integrations/linear/reconciliation-config.js.map +1 -0
- package/dist/integrations/platform/github/event-worker.d.ts +4 -6
- package/dist/integrations/platform/github/event-worker.d.ts.map +1 -1
- package/dist/integrations/platform/github/event-worker.js +82 -61
- package/dist/integrations/platform/github/event-worker.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts +0 -2
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +30 -16
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/platform/linear/integration.d.ts.map +1 -1
- package/dist/integrations/platform/linear/integration.js +3 -2
- package/dist/integrations/platform/linear/integration.js.map +1 -1
- package/dist/integrations/reconciliation-config.d.ts +2 -0
- package/dist/integrations/reconciliation-config.d.ts.map +1 -0
- package/dist/integrations/reconciliation-config.js +9 -0
- package/dist/integrations/reconciliation-config.js.map +1 -0
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +4 -0
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/skills.d.ts.map +1 -1
- package/dist/routes/skills.js +30 -9
- package/dist/routes/skills.js.map +1 -1
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +4 -2
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/work-items.d.ts +3 -0
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +63 -4
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/defaults.d.ts.map +1 -1
- package/dist/rules/defaults.js +19 -4
- package/dist/rules/defaults.js.map +1 -1
- package/dist/rules/dispatcher.d.ts +5 -0
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +15 -0
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/start-coordinator.d.ts.map +1 -1
- package/dist/rules/start-coordinator.js +1 -1
- package/dist/rules/start-coordinator.js.map +1 -1
- package/dist/rules/types.d.ts +1 -0
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js.map +1 -1
- package/dist/rules/validation.d.ts.map +1 -1
- package/dist/rules/validation.js +5 -2
- package/dist/rules/validation.js.map +1 -1
- package/dist/session/checkpoint-capture.d.ts.map +1 -1
- package/dist/session/checkpoint-capture.js +1 -1
- package/dist/session/checkpoint-capture.js.map +1 -1
- package/dist/session/live-sessions.d.ts +33 -0
- package/dist/session/live-sessions.d.ts.map +1 -0
- package/dist/session/live-sessions.js +28 -0
- package/dist/session/live-sessions.js.map +1 -0
- package/dist/skills/catalog.d.ts +9 -0
- package/dist/skills/catalog.d.ts.map +1 -0
- package/dist/skills/catalog.js +45 -0
- package/dist/skills/catalog.js.map +1 -0
- package/dist/storage/domains/audit/base.d.ts +0 -1
- package/dist/storage/domains/audit/base.d.ts.map +1 -1
- package/dist/storage/domains/audit/base.js +0 -1
- package/dist/storage/domains/audit/base.js.map +1 -1
- package/dist/storage/domains/projects/base.d.ts +3 -0
- package/dist/storage/domains/projects/base.d.ts.map +1 -1
- package/dist/storage/domains/projects/base.js +7 -0
- package/dist/storage/domains/projects/base.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +19 -14
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +75 -21
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/storage/domains/work-items/metrics.d.ts +27 -22
- package/dist/storage/domains/work-items/metrics.d.ts.map +1 -1
- package/dist/storage/domains/work-items/metrics.js +79 -49
- package/dist/storage/domains/work-items/metrics.js.map +1 -1
- package/dist/workspace.d.ts +2 -0
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +2 -1
- package/dist/workspace.js.map +1 -1
- package/factory-skills/factory-rereview/SKILL.md +166 -0
- package/factory-skills/factory-triage/SKILL.md +64 -12
- package/package.json +4 -4
- package/dist/integrations/github/issue-triage.d.ts +0 -27
- package/dist/integrations/github/issue-triage.d.ts.map +0 -1
- package/dist/integrations/github/issue-triage.js +0 -82
- package/dist/integrations/github/issue-triage.js.map +0 -1
|
@@ -6,9 +6,9 @@ const DEFAULT_GITHUB_RECONCILE_INTERVAL_MS = 5 * 6e4;
|
|
|
6
6
|
const MIN_LEASE_TTL_MS = 3e4;
|
|
7
7
|
const LEASE_KEY = "github:pull-request-reconcile";
|
|
8
8
|
/**
|
|
9
|
-
* Periodic
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Periodic GitHub state sweeps for the self-hosted integration. Pull requests
|
|
10
|
+
* drive review cards and issues drive work cards, but both share discovery and
|
|
11
|
+
* a lease so replicas never sweep the same configured repositories together.
|
|
12
12
|
*/
|
|
13
13
|
var GithubReconcileWorker = class extends MastraWorker {
|
|
14
14
|
name = "github-pull-request-reconcile";
|
|
@@ -16,20 +16,28 @@ var GithubReconcileWorker = class extends MastraWorker {
|
|
|
16
16
|
#reconcileIssues;
|
|
17
17
|
#sourceControl;
|
|
18
18
|
#intervalMs;
|
|
19
|
+
#issueIntervalMs;
|
|
19
20
|
#leaseTtlMs;
|
|
20
21
|
#leaseOwner = randomUUID();
|
|
22
|
+
#now;
|
|
21
23
|
#running = false;
|
|
22
24
|
#timer;
|
|
23
25
|
#inFlight;
|
|
24
26
|
#leaseProvider = NoopLeaseProvider;
|
|
27
|
+
#nextPullRequestReconcileAt = 0;
|
|
28
|
+
#nextIssueReconcileAt = 0;
|
|
25
29
|
constructor(config) {
|
|
26
30
|
super();
|
|
31
|
+
if (!config.reconcile && !config.reconcileIssues) throw new Error("GitHub reconcile worker requires a pull request or issue reconciler.");
|
|
27
32
|
this.#reconcile = config.reconcile;
|
|
28
33
|
this.#reconcileIssues = config.reconcileIssues;
|
|
29
34
|
this.#sourceControl = config.sourceControl;
|
|
30
35
|
this.#intervalMs = config.intervalMs ?? 3e5;
|
|
36
|
+
this.#issueIntervalMs = config.issueIntervalMs ?? this.#intervalMs;
|
|
31
37
|
if (!Number.isFinite(this.#intervalMs) || this.#intervalMs <= 0) throw new Error("GitHub pull request reconcile interval must be a positive number.");
|
|
32
|
-
this.#
|
|
38
|
+
if (!Number.isFinite(this.#issueIntervalMs) || this.#issueIntervalMs <= 0) throw new Error("GitHub issue reconcile interval must be a positive number.");
|
|
39
|
+
this.#leaseTtlMs = Math.max(MIN_LEASE_TTL_MS, Math.min(this.#intervalMs, this.#issueIntervalMs) * 3);
|
|
40
|
+
this.#now = config.now ?? Date.now;
|
|
33
41
|
}
|
|
34
42
|
async init(deps) {
|
|
35
43
|
await super.init(deps);
|
|
@@ -39,7 +47,10 @@ var GithubReconcileWorker = class extends MastraWorker {
|
|
|
39
47
|
if (this.#running) return;
|
|
40
48
|
if (!this.deps) throw new Error("GithubReconcileWorker: call init() before start()");
|
|
41
49
|
this.#running = true;
|
|
42
|
-
this.deps.logger.info("GitHub
|
|
50
|
+
this.deps.logger.info("GitHub reconcile worker started", {
|
|
51
|
+
pullRequestIntervalMs: this.#reconcile ? this.#intervalMs : void 0,
|
|
52
|
+
issueIntervalMs: this.#reconcileIssues ? this.#issueIntervalMs : void 0
|
|
53
|
+
});
|
|
43
54
|
this.#schedule(0);
|
|
44
55
|
}
|
|
45
56
|
async stop() {
|
|
@@ -58,13 +69,24 @@ var GithubReconcileWorker = class extends MastraWorker {
|
|
|
58
69
|
this.#timer = void 0;
|
|
59
70
|
const run = this.#tick().finally(() => {
|
|
60
71
|
this.#inFlight = void 0;
|
|
61
|
-
this.#schedule(this.#
|
|
72
|
+
this.#schedule(this.#nextDelay());
|
|
62
73
|
});
|
|
63
74
|
this.#inFlight = run;
|
|
64
75
|
}, delayMs);
|
|
65
76
|
this.#timer.unref?.();
|
|
66
77
|
}
|
|
78
|
+
#nextDelay() {
|
|
79
|
+
const now = this.#now();
|
|
80
|
+
const due = [this.#reconcile ? this.#nextPullRequestReconcileAt : void 0, this.#reconcileIssues ? this.#nextIssueReconcileAt : void 0].filter((at) => at !== void 0);
|
|
81
|
+
return Math.max(0, Math.min(...due) - now);
|
|
82
|
+
}
|
|
67
83
|
async #tick() {
|
|
84
|
+
const now = this.#now();
|
|
85
|
+
const reconcilePullRequests = Boolean(this.#reconcile && now >= this.#nextPullRequestReconcileAt);
|
|
86
|
+
const reconcileIssues = Boolean(this.#reconcileIssues && now >= this.#nextIssueReconcileAt);
|
|
87
|
+
if (!reconcilePullRequests && !reconcileIssues) return;
|
|
88
|
+
if (reconcilePullRequests) this.#nextPullRequestReconcileAt = now + this.#intervalMs;
|
|
89
|
+
if (reconcileIssues) this.#nextIssueReconcileAt = now + this.#issueIntervalMs;
|
|
68
90
|
if (!(await this.#leaseProvider.acquireLease(LEASE_KEY, this.#leaseOwner, this.#leaseTtlMs).catch(() => ({ acquired: false }))).acquired) return;
|
|
69
91
|
let hasLease = true;
|
|
70
92
|
const renewalTimer = setInterval(() => {
|
|
@@ -84,40 +106,45 @@ var GithubReconcileWorker = class extends MastraWorker {
|
|
|
84
106
|
try {
|
|
85
107
|
const targets = await this.#targets();
|
|
86
108
|
if (targets.length === 0) return;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
109
|
+
if (reconcilePullRequests && this.#reconcile) {
|
|
110
|
+
const startedAt = Date.now();
|
|
111
|
+
try {
|
|
112
|
+
const { errors, ...counts } = await this.#reconcile(targets);
|
|
113
|
+
const context = {
|
|
114
|
+
...counts,
|
|
115
|
+
candidateRepositories: targets.length,
|
|
116
|
+
durationMs: Date.now() - startedAt
|
|
117
|
+
};
|
|
118
|
+
if (counts.failed > 0) this.deps?.logger.warn("GitHub pull request reconcile sweep completed with failures", {
|
|
119
|
+
...context,
|
|
120
|
+
errors
|
|
121
|
+
});
|
|
122
|
+
else if (counts.merged > 0 || counts.closed > 0) this.deps?.logger.info("GitHub pull request reconcile replayed missed merges/closes", context);
|
|
123
|
+
else this.deps?.logger.debug("GitHub pull request reconcile sweep completed", context);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
this.deps?.logger.warn("GitHub pull request reconcile sweep failed", { error: error instanceof Error ? error.message : String(error) });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (reconcileIssues && this.#reconcileIssues && hasLease) {
|
|
129
|
+
const startedAt = Date.now();
|
|
102
130
|
try {
|
|
103
|
-
const { errors
|
|
104
|
-
const
|
|
105
|
-
...
|
|
131
|
+
const { errors, ...counts } = await this.#reconcileIssues(targets);
|
|
132
|
+
const context = {
|
|
133
|
+
...counts,
|
|
106
134
|
candidateRepositories: targets.length,
|
|
107
|
-
durationMs: Date.now() -
|
|
135
|
+
durationMs: Date.now() - startedAt
|
|
108
136
|
};
|
|
109
|
-
if (
|
|
110
|
-
...
|
|
111
|
-
errors
|
|
137
|
+
if (counts.failed > 0) this.deps?.logger.warn("GitHub issue reconcile sweep completed with failures", {
|
|
138
|
+
...context,
|
|
139
|
+
errors
|
|
112
140
|
});
|
|
113
|
-
else if (
|
|
114
|
-
else this.deps?.logger.
|
|
115
|
-
|
|
116
|
-
|
|
141
|
+
else if (counts.closed > 0) this.deps?.logger.info("GitHub issue reconcile replayed closed work items", context);
|
|
142
|
+
else if (counts.updated > 0) this.deps?.logger.info("GitHub issue reconcile patched stale metadata", context);
|
|
143
|
+
else this.deps?.logger.debug("GitHub issue reconcile sweep completed", context);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
this.deps?.logger.warn("GitHub issue reconcile sweep failed", { error: error instanceof Error ? error.message : String(error) });
|
|
117
146
|
}
|
|
118
|
-
} else if (this.#reconcileIssues && !hasLease) this.deps?.logger.debug("GitHub issue reconcile skipped: lease lost during pull-request sweep");
|
|
119
|
-
} catch (error) {
|
|
120
|
-
this.deps?.logger.warn("GitHub pull request reconcile sweep failed", { error: error instanceof Error ? error.message : String(error) });
|
|
147
|
+
} else if (reconcileIssues && this.#reconcileIssues && !hasLease) this.deps?.logger.debug("GitHub issue reconcile skipped: lease lost during pull-request sweep");
|
|
121
148
|
} finally {
|
|
122
149
|
clearInterval(renewalTimer);
|
|
123
150
|
if (hasLease) await this.#leaseProvider.releaseLease(LEASE_KEY, this.#leaseOwner).catch(() => void 0);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconcile-worker.js","names":["#reconcile","#reconcileIssues","#sourceControl","#intervalMs","#leaseTtlMs","#leaseOwner","#leaseProvider","#running","#schedule","#timer","#inFlight","#tick","#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 /**\n * Optional issue-metadata sweep. When provided, the worker folds it into the\n * same tick as the PR reconciler, sharing the lease and the same\n * `ReconcileRepository` target discovery. Mirrors how webhook-driven ingress\n * flows both issues and PRs through one path.\n */\n reconcileIssues?: GithubIssueReconciler;\n sourceControl: GithubReconcileRepositorySource;\n intervalMs?: number;\n}\n\n/**\n * Periodic merge-state sweep for the self-hosted GitHub integration: webhooks\n * are the only other writer of merge state, and a deployment GitHub cannot\n * reach never receives one, so cards would stay open forever.\n */\nexport class GithubReconcileWorker extends MastraWorker {\n readonly name = 'github-pull-request-reconcile';\n\n readonly #reconcile: GithubPullRequestReconciler;\n readonly #reconcileIssues: GithubIssueReconciler | undefined;\n readonly #sourceControl: GithubReconcileRepositorySource;\n readonly #intervalMs: number;\n readonly #leaseTtlMs: number;\n readonly #leaseOwner = randomUUID();\n\n #running = false;\n #timer: ReturnType<typeof setTimeout> | undefined;\n #inFlight: Promise<void> | undefined;\n #leaseProvider: LeaseProvider = NoopLeaseProvider;\n\n constructor(config: GithubReconcileWorkerConfig) {\n super();\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 if (!Number.isFinite(this.#intervalMs) || this.#intervalMs <= 0) {\n throw new Error('GitHub pull request reconcile interval must be a positive number.');\n }\n this.#leaseTtlMs = Math.max(MIN_LEASE_TTL_MS, this.#intervalMs * 3);\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 pull request reconcile worker started', { intervalMs: this.#intervalMs });\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.#intervalMs);\n });\n this.#inFlight = run;\n }, delayMs);\n this.#timer.unref?.();\n }\n\n async #tick(): Promise<void> {\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 // Track whether this owner still holds the lease. A `renewLease` result\n // of `false` (or a renewal error) means another replica has taken the\n // lease. Once that happens the folded issue sweep must not run — it\n // would race the new owner's writes.\n let hasLease = true;\n // Folding the issue sweep into the same tick extends how long we hold the\n // lease. Renew periodically so a replica can't grab the expired lease and\n // start an overlapping sweep partway through this one.\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 const startedAt = Date.now();\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 // Fold the issue-metadata sweep into the same tick so a single lease\n // covers both writers of card state for these repositories. Skip it\n // if the lease was lost during the PR sweep — running would race a\n // replica that already re-acquired the lease.\n if (this.#reconcileIssues && hasLease) {\n const issueStartedAt = Date.now();\n try {\n const { errors: issueErrors, ...issueCounts } = await this.#reconcileIssues(targets);\n const issueContext = {\n ...issueCounts,\n candidateRepositories: targets.length,\n durationMs: Date.now() - issueStartedAt,\n };\n if (issueCounts.failed > 0) {\n this.deps?.logger.warn('GitHub issue reconcile sweep completed with failures', {\n ...issueContext,\n errors: issueErrors,\n });\n } else if (issueCounts.updated > 0) {\n this.deps?.logger.info('GitHub issue reconcile patched stale metadata', issueContext);\n } else {\n this.deps?.logger.debug('GitHub issue reconcile sweep completed', issueContext);\n }\n } catch (issueError) {\n this.deps?.logger.warn('GitHub issue reconcile sweep failed', {\n error: issueError instanceof Error ? issueError.message : String(issueError),\n });\n }\n } else if (this.#reconcileIssues && !hasLease) {\n this.deps?.logger.debug('GitHub issue reconcile skipped: lease lost during pull-request sweep');\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 } 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\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 } 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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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconciliation-config.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/reconciliation-config.ts"],"names":[],"mappings":"AAEA,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEpG;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAE/E"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { optionalPositiveInteger } from "../reconciliation-config.js";
|
|
2
|
+
//#region src/integrations/github/reconciliation-config.ts
|
|
3
|
+
function reconciliationEnabled(child, legacy) {
|
|
4
|
+
return parseOptionalBoolean(child) ?? parseOptionalBoolean(legacy) ?? true;
|
|
5
|
+
}
|
|
6
|
+
function reconcileInterval(value) {
|
|
7
|
+
return optionalPositiveInteger(value);
|
|
8
|
+
}
|
|
9
|
+
function parseOptionalBoolean(value) {
|
|
10
|
+
switch (value?.trim().toLowerCase()) {
|
|
11
|
+
case "true": return true;
|
|
12
|
+
case "false": return false;
|
|
13
|
+
default: return;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { reconcileInterval, reconciliationEnabled };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=reconciliation-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconciliation-config.js","names":[],"sources":["../../../src/integrations/github/reconciliation-config.ts"],"sourcesContent":["import { optionalPositiveInteger } from '../reconciliation-config.js';\n\nexport function reconciliationEnabled(child: string | undefined, legacy: string | undefined): boolean {\n return parseOptionalBoolean(child) ?? parseOptionalBoolean(legacy) ?? true;\n}\n\nexport function reconcileInterval(value: string | undefined): number | undefined {\n return optionalPositiveInteger(value);\n}\n\nfunction parseOptionalBoolean(value: string | undefined): boolean | undefined {\n switch (value?.trim().toLowerCase()) {\n case 'true':\n return true;\n case 'false':\n return false;\n default:\n return undefined;\n }\n}\n"],"mappings":";;AAEA,SAAgB,sBAAsB,OAA2B,QAAqC;CACpG,OAAO,qBAAqB,KAAK,KAAK,qBAAqB,MAAM,KAAK;AACxE;AAEA,SAAgB,kBAAkB,OAA+C;CAC/E,OAAO,wBAAwB,KAAK;AACtC;AAEA,SAAS,qBAAqB,OAAgD;CAC5E,QAAQ,OAAO,KAAK,CAAC,CAAC,YAAY,GAAlC;EACE,KAAK,QACH,OAAO;EACT,KAAK,SACH,OAAO;EACT,SACE;CACJ;AACF"}
|
|
@@ -19,7 +19,7 @@ import type { StateSigner } from '../../state-signing.js';
|
|
|
19
19
|
import type { AuditEmitter } from '../../storage/domains/audit/domain.js';
|
|
20
20
|
import type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js';
|
|
21
21
|
import type { GithubIntegration } from './integration.js';
|
|
22
|
-
import type {
|
|
22
|
+
import type { ParsedGithubWebhook } from './webhook.js';
|
|
23
23
|
export interface MountGithubRoutesOptions {
|
|
24
24
|
/** Host auth seam — resolves the signed-in user/tenant for each request. */
|
|
25
25
|
auth: RouteAuth;
|
|
@@ -52,8 +52,6 @@ export interface MountGithubRoutesOptions {
|
|
|
52
52
|
redirectUri?: string;
|
|
53
53
|
/** Controller used to route verified webhook notifications to exact subscribed sessions. */
|
|
54
54
|
controller?: MountedMastraCode['controller'];
|
|
55
|
-
/** Run seam used by GitHub webhooks and manual Intake triage. */
|
|
56
|
-
runIssueTriage?: (input: GithubIssueTriageRunInput) => Promise<GithubIssueTriageRunResult>;
|
|
57
55
|
/** Best-effort audit emission supplied by the factory-owned audit domain. */
|
|
58
56
|
emitAudit?: AuditEmitter['emit'];
|
|
59
57
|
/** Factory projects domain — resolves a project's default triage model. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,KAAK,EAAuD,YAAY,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,KAAK,EAAuD,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAChH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAC1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AASrF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AA4C1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAgBxD,MAAM,WAAW,wBAAwB;IACvC,4EAA4E;IAC5E,IAAI,EAAE,SAAS,CAAC;IAChB;;;;OAIG;IACH,KAAK,EAAE,YAAY,CAAC;IACpB,yEAAyE;IACzE,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB;;;;OAIG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC;IAC7C,6EAA6E;IAC7E,SAAS,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IACjC,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAClC,+FAA+F;IAC/F,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACvE;AA4ND;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,QAAQ,EAAE,CA8kB/E"}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { clearGithubPat, getGithubPat, getGithubPatStatus, setGithubPat } from "./pat.js";
|
|
2
2
|
import { listPullRequestSubscriptionsForThread, subscribeToPullRequest } from "./subscriptions.js";
|
|
3
3
|
import { SandboxBudgetError } from "../../sandbox/fleet.js";
|
|
4
|
-
import { MaterializeError, WorktreeError, commitAll,
|
|
4
|
+
import { MaterializeError, WorktreeError, commitAll, ensureProjectSandbox, isValidGitRef as isValidGitRef$1, materializeRepo, pushBranch, teardownProjectSandbox } from "./sandbox.js";
|
|
5
5
|
import { reclaimDeletedSessionSandbox } from "./sandbox-release.js";
|
|
6
|
-
import { resolveFactoryDefaultModelId } from "../../session/factory-session.js";
|
|
7
6
|
import { getGithubFeatureDiagnostics, isGithubFeatureEnabled } from "./config.js";
|
|
8
7
|
import { handleGithubWebhook } from "./webhook.js";
|
|
9
8
|
import { registerApiRoute } from "@mastra/core/server";
|
|
@@ -57,15 +56,6 @@ function pullRequestNumberFromUrl(value, expectedRepo) {
|
|
|
57
56
|
return;
|
|
58
57
|
}
|
|
59
58
|
}
|
|
60
|
-
function isCanonicalGithubIssueUrl(value, repoFullName, issueNumber) {
|
|
61
|
-
try {
|
|
62
|
-
const url = new URL(value);
|
|
63
|
-
const [owner, repo] = repoFullName.split("/");
|
|
64
|
-
return url.protocol === "https:" && url.hostname === "github.com" && url.pathname === `/${owner}/${repo}/issues/${issueNumber}` && url.search === "" && url.hash === "";
|
|
65
|
-
} catch {
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
59
|
/**
|
|
70
60
|
* Validate a git branch/ref name against a strict whitelist. The value is later
|
|
71
61
|
* interpolated into a shell `git clone --branch` command, so it must never
|
|
@@ -123,15 +113,6 @@ function parseIssueLabelFilter(raw) {
|
|
|
123
113
|
if (VALID_ISSUE_LABEL_FILTERS.has(raw)) return raw;
|
|
124
114
|
return null;
|
|
125
115
|
}
|
|
126
|
-
function parseIssueNumberParam(raw) {
|
|
127
|
-
if (!raw || !/^\d{1,10}$/.test(raw)) return null;
|
|
128
|
-
const issueNumber = Number(raw);
|
|
129
|
-
return Number.isSafeInteger(issueNumber) && issueNumber > 0 ? issueNumber : null;
|
|
130
|
-
}
|
|
131
|
-
function parseStringList(value) {
|
|
132
|
-
if (!Array.isArray(value)) return [];
|
|
133
|
-
return value.filter((item) => typeof item === "string" && item.length > 0);
|
|
134
|
-
}
|
|
135
116
|
async function resolveProjectRepository(args) {
|
|
136
117
|
const projectRepository = await args.github.sourceControlStorage.projectRepositories.get({
|
|
137
118
|
orgId: args.orgId,
|
|
@@ -285,18 +266,6 @@ function buildGithubRoutes(options) {
|
|
|
285
266
|
}) || !github || !stateSigner) return routes;
|
|
286
267
|
const signState = (orgId, userId) => stateSigner.sign(orgId, userId);
|
|
287
268
|
const verifyState = (state) => stateSigner.verify(state);
|
|
288
|
-
const { runIssueTriage } = options;
|
|
289
|
-
const runBoardIssueTriage = runIssueTriage ? async (input) => {
|
|
290
|
-
if (!input.resourceId || !input.projectPath) throw new Error("GitHub issue triage requires an explicit Factory project repository");
|
|
291
|
-
await github.addIssueLabels(input.installationId, input.repository, input.issueNumber, ["status: auto-triaged"]);
|
|
292
|
-
if (input.labels.includes("status: needs triage")) await github.removeIssueLabel(input.installationId, input.repository, input.issueNumber, "status: needs triage");
|
|
293
|
-
const labels = input.labels.filter((label) => label !== "status: needs triage");
|
|
294
|
-
return runIssueTriage({
|
|
295
|
-
...input,
|
|
296
|
-
defaultModelId: input.defaultModelId ?? await resolveFactoryDefaultModelId(options.projects, input.resourceId),
|
|
297
|
-
labels: labels.includes("status: auto-triaged") ? labels : [...labels, "status: auto-triaged"]
|
|
298
|
-
});
|
|
299
|
-
} : void 0;
|
|
300
269
|
routes.push(registerApiRoute("/web/github/subscriptions", {
|
|
301
270
|
method: "GET",
|
|
302
271
|
handler: async (c) => {
|
|
@@ -327,7 +296,6 @@ function buildGithubRoutes(options) {
|
|
|
327
296
|
handler: async (c) => {
|
|
328
297
|
const result = await handleGithubWebhook(loose(c), {
|
|
329
298
|
github,
|
|
330
|
-
runIssueTriage: runBoardIssueTriage,
|
|
331
299
|
ingestFactoryEvent: options.ingestFactoryEvent,
|
|
332
300
|
...options.controller ? {
|
|
333
301
|
controller: options.controller,
|
|
@@ -562,68 +530,6 @@ function buildGithubRoutes(options) {
|
|
|
562
530
|
}
|
|
563
531
|
}
|
|
564
532
|
}));
|
|
565
|
-
routes.push(registerApiRoute("/web/github/projects/:id/issues/:number/triage", {
|
|
566
|
-
method: "POST",
|
|
567
|
-
requiresAuth: false,
|
|
568
|
-
handler: async (c) => {
|
|
569
|
-
const owned = await loadOwnedProject({
|
|
570
|
-
github,
|
|
571
|
-
auth,
|
|
572
|
-
fleet,
|
|
573
|
-
c: loose(c)
|
|
574
|
-
});
|
|
575
|
-
if ("response" in owned) return owned.response;
|
|
576
|
-
const { project, sandboxRow } = owned;
|
|
577
|
-
const issueNumber = parseIssueNumberParam(c.req.param("number"));
|
|
578
|
-
if (issueNumber === null) return c.json({ error: "invalid_issue_number" }, 400);
|
|
579
|
-
let body;
|
|
580
|
-
try {
|
|
581
|
-
body = await c.req.json();
|
|
582
|
-
} catch {
|
|
583
|
-
return c.json({ error: "Invalid JSON body" }, 400);
|
|
584
|
-
}
|
|
585
|
-
if (typeof body.title !== "string" || body.title.trim().length === 0 || body.title.length > 5e3) return c.json({ error: "invalid_title" }, 400);
|
|
586
|
-
if (typeof body.url !== "string" || body.url.trim().length === 0 || body.url.length > 2048 || !isCanonicalGithubIssueUrl(body.url, project.repository.slug, issueNumber)) return c.json({ error: "invalid_url" }, 400);
|
|
587
|
-
if (!runBoardIssueTriage) return c.json({ error: "triage_unavailable" }, 503);
|
|
588
|
-
const branch = `factory/issue-${issueNumber}`;
|
|
589
|
-
const projectPath = computeWorktreePath(sandboxRow.sandboxWorkdir, branch);
|
|
590
|
-
const result = await runBoardIssueTriage({
|
|
591
|
-
repository: project.repository.slug,
|
|
592
|
-
issueNumber,
|
|
593
|
-
issueTitle: body.title,
|
|
594
|
-
issueUrl: body.url,
|
|
595
|
-
labels: parseStringList(body.labels),
|
|
596
|
-
installationId: Number(project.installation.externalId),
|
|
597
|
-
resourceId: project.factoryProjectId,
|
|
598
|
-
projectPath,
|
|
599
|
-
branch
|
|
600
|
-
});
|
|
601
|
-
await emitAudit?.({
|
|
602
|
-
context: loose(c),
|
|
603
|
-
input: {
|
|
604
|
-
action: "factory.triage.started",
|
|
605
|
-
factoryProjectId: project.factoryProjectId,
|
|
606
|
-
projectRepositoryId: project.id,
|
|
607
|
-
targets: [{
|
|
608
|
-
type: "issue",
|
|
609
|
-
id: String(issueNumber),
|
|
610
|
-
name: body.title
|
|
611
|
-
}],
|
|
612
|
-
metadata: {
|
|
613
|
-
issueNumber,
|
|
614
|
-
branch,
|
|
615
|
-
threadId: result.threadId
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
});
|
|
619
|
-
return c.json({
|
|
620
|
-
ok: true,
|
|
621
|
-
threadId: result.threadId,
|
|
622
|
-
projectPath: result.projectPath ?? projectPath,
|
|
623
|
-
branch: result.branch ?? branch
|
|
624
|
-
}, 202);
|
|
625
|
-
}
|
|
626
|
-
}));
|
|
627
533
|
routes.push(registerApiRoute("/web/github/projects/:id/prs", {
|
|
628
534
|
method: "GET",
|
|
629
535
|
requiresAuth: false,
|