@mastra/factory 0.2.2-alpha.0 → 0.2.2-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 +36 -0
- package/dist/auth.d.ts +2 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +401 -314
- package/dist/auth.js.map +1 -1
- package/dist/capabilities/connection.js +1 -1
- package/dist/capabilities/intake.js +1 -1
- package/dist/capabilities/version-control.js +1 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +441 -39455
- package/dist/factory.js.map +1 -1
- package/dist/index.js +2 -39466
- package/dist/integrations/base.js +1 -1
- package/dist/integrations/github/config.js +36 -22
- package/dist/integrations/github/config.js.map +1 -1
- package/dist/integrations/github/factory-session.js +33 -37
- package/dist/integrations/github/factory-session.js.map +1 -1
- package/dist/integrations/github/integration.js +1037 -3624
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/issue-triage.js +71 -71
- package/dist/integrations/github/issue-triage.js.map +1 -1
- package/dist/integrations/github/pat.js +47 -36
- package/dist/integrations/github/pat.js.map +1 -1
- package/dist/integrations/github/provenance.js +72 -101
- package/dist/integrations/github/provenance.js.map +1 -1
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +1272 -1833
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/rules.js +203 -471
- package/dist/integrations/github/rules.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +2 -0
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +401 -326
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/github/session-subscriptions.js +165 -223
- package/dist/integrations/github/session-subscriptions.js.map +1 -1
- package/dist/integrations/github/subscriptions.js +43 -51
- package/dist/integrations/github/subscriptions.js.map +1 -1
- package/dist/integrations/github/token-refresh.js +15 -18
- package/dist/integrations/github/token-refresh.js.map +1 -1
- package/dist/integrations/github/webhook.js +359 -343
- package/dist/integrations/github/webhook.js.map +1 -1
- package/dist/integrations/linear/agent-tools.js +77 -100
- package/dist/integrations/linear/agent-tools.js.map +1 -1
- package/dist/integrations/linear/integration.js +611 -1309
- package/dist/integrations/linear/integration.js.map +1 -1
- package/dist/integrations/linear/routes.js +227 -230
- package/dist/integrations/linear/routes.js.map +1 -1
- package/dist/integrations/linear/rules.js +126 -346
- package/dist/integrations/linear/rules.js.map +1 -1
- package/dist/integrations/linear/storage.js +1 -1
- package/dist/integrations/platform/api-client.js +131 -135
- package/dist/integrations/platform/api-client.js.map +1 -1
- package/dist/integrations/platform/github/event-worker.js +275 -573
- 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 +845 -3942
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/platform/linear/integration.js +457 -1309
- package/dist/integrations/platform/linear/integration.js.map +1 -1
- package/dist/integrations/workos/integration.js +80 -85
- package/dist/integrations/workos/integration.js.map +1 -1
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +933 -915
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/custom-provider-source.js +110 -93
- package/dist/routes/custom-provider-source.js.map +1 -1
- package/dist/routes/fs.js +401 -349
- package/dist/routes/fs.js.map +1 -1
- package/dist/routes/intake.js +165 -165
- package/dist/routes/intake.js.map +1 -1
- package/dist/routes/oauth.js +447 -25166
- package/dist/routes/oauth.js.map +1 -1
- package/dist/routes/projects.js +437 -376
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/provider-credentials.js +76 -58
- package/dist/routes/provider-credentials.js.map +1 -1
- package/dist/routes/route.js +12 -8
- package/dist/routes/route.js.map +1 -1
- package/dist/routes/skills.js +160 -173
- package/dist/routes/skills.js.map +1 -1
- package/dist/routes/surface.js +250 -28618
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/tenant-credentials.js +141 -166
- package/dist/routes/tenant-credentials.js.map +1 -1
- package/dist/routes/work-items.js +654 -862
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/binding-context.js +23 -35
- package/dist/rules/binding-context.js.map +1 -1
- package/dist/rules/defaults.js +200 -309
- package/dist/rules/defaults.js.map +1 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +438 -677
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/index.js +5 -599
- package/dist/rules/processor.js +344 -596
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/resolve.js +21 -18
- package/dist/rules/resolve.js.map +1 -1
- package/dist/rules/start-coordinator.js +168 -157
- package/dist/rules/start-coordinator.js.map +1 -1
- package/dist/rules/tools.js +50 -85
- package/dist/rules/tools.js.map +1 -1
- package/dist/rules/transition-service.js +183 -454
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.js +33 -29
- package/dist/rules/types.js.map +1 -1
- package/dist/rules/validation.js +266 -281
- package/dist/rules/validation.js.map +1 -1
- package/dist/sandbox/fleet.js +272 -250
- package/dist/sandbox/fleet.js.map +1 -1
- package/dist/sandbox/reattach.js +13 -6
- package/dist/sandbox/reattach.js.map +1 -1
- package/dist/server-error.js +12 -16
- package/dist/server-error.js.map +1 -1
- package/dist/skills/service.js +30 -35
- package/dist/skills/service.js.map +1 -1
- package/dist/spa-static.d.ts.map +1 -1
- package/dist/spa-static.js +91 -64
- package/dist/spa-static.js.map +1 -1
- package/dist/state-signing.js +73 -44
- package/dist/state-signing.js.map +1 -1
- package/dist/storage/domains/audit/agent-audit.js +60 -59
- package/dist/storage/domains/audit/agent-audit.js.map +1 -1
- package/dist/storage/domains/audit/base.js +169 -112
- package/dist/storage/domains/audit/base.js.map +1 -1
- package/dist/storage/domains/audit/domain.js +155 -153
- package/dist/storage/domains/audit/domain.js.map +1 -1
- package/dist/storage/domains/credentials/base.js +259 -243
- package/dist/storage/domains/credentials/base.js.map +1 -1
- package/dist/storage/domains/custom-providers/base.js +138 -143
- package/dist/storage/domains/custom-providers/base.js.map +1 -1
- package/dist/storage/domains/intake/base.js +75 -57
- package/dist/storage/domains/intake/base.js.map +1 -1
- package/dist/storage/domains/integrations/base.js +310 -245
- package/dist/storage/domains/integrations/base.js.map +1 -1
- package/dist/storage/domains/memory-settings/base.js +117 -102
- package/dist/storage/domains/memory-settings/base.js.map +1 -1
- package/dist/storage/domains/model-packs/base.js +89 -88
- package/dist/storage/domains/model-packs/base.js.map +1 -1
- package/dist/storage/domains/projects/base.js +97 -92
- package/dist/storage/domains/projects/base.js.map +1 -1
- package/dist/storage/domains/queue-health/base.js +111 -76
- package/dist/storage/domains/queue-health/base.js.map +1 -1
- package/dist/storage/domains/source-control/base.d.ts +9 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -1
- package/dist/storage/domains/source-control/base.js +777 -702
- package/dist/storage/domains/source-control/base.js.map +1 -1
- package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/source-control/inmemory.js +322 -344
- package/dist/storage/domains/source-control/inmemory.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +1266 -1132
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/storage/domains/work-items/metrics.js +202 -184
- package/dist/storage/domains/work-items/metrics.js.map +1 -1
- package/dist/timing.d.ts +15 -0
- package/dist/timing.d.ts.map +1 -0
- package/dist/timing.js +34 -0
- package/dist/timing.js.map +1 -0
- package/dist/workspace.d.ts +2 -3
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +236 -438
- package/dist/workspace.js.map +1 -1
- package/factory-skills/factory-review/SKILL.md +9 -2
- package/package.json +9 -9
- package/dist/capabilities/connection.js.map +0 -1
- package/dist/capabilities/intake.js.map +0 -1
- package/dist/capabilities/version-control.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/integrations/base.js.map +0 -1
- package/dist/integrations/linear/storage.js.map +0 -1
- package/dist/rules/index.js.map +0 -1
|
@@ -1,381 +1,397 @@
|
|
|
1
|
-
|
|
1
|
+
import { listPullRequestSubscriptionsForWebhook, retirePullRequestSubscription } from "./subscriptions.js";
|
|
2
2
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
return options?.includeTerminal ? rows : rows.filter((row) => row.status === "open");
|
|
11
|
-
}
|
|
12
|
-
function retirePullRequestSubscription(id, status, storage) {
|
|
13
|
-
return storage.subscriptions.updateStatus(id, status);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// src/integrations/github/webhook.ts
|
|
17
|
-
var SUPPORTED_GITHUB_WEBHOOK_EVENTS = /* @__PURE__ */ new Set([
|
|
18
|
-
"issues",
|
|
19
|
-
"issue_comment",
|
|
20
|
-
"pull_request",
|
|
21
|
-
"pull_request_review",
|
|
22
|
-
"pull_request_review_comment"
|
|
3
|
+
//#region src/integrations/github/webhook.ts
|
|
4
|
+
const SUPPORTED_GITHUB_WEBHOOK_EVENTS = /* @__PURE__ */ new Set([
|
|
5
|
+
"issues",
|
|
6
|
+
"issue_comment",
|
|
7
|
+
"pull_request",
|
|
8
|
+
"pull_request_review",
|
|
9
|
+
"pull_request_review_comment"
|
|
23
10
|
]);
|
|
24
11
|
function normalizeHeader(value) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
12
|
+
if (!value) return null;
|
|
13
|
+
const trimmed = value.trim();
|
|
14
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
28
15
|
}
|
|
29
16
|
function verifySignature(rawBody, signature, secret) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
17
|
+
if (!signature.startsWith("sha256=")) return false;
|
|
18
|
+
const signatureHex = signature.slice(7);
|
|
19
|
+
if (!/^[a-fA-F0-9]{64}$/.test(signatureHex)) return false;
|
|
20
|
+
const expectedHex = createHmac("sha256", secret).update(rawBody).digest("hex");
|
|
21
|
+
const received = Buffer.from(signatureHex, "hex");
|
|
22
|
+
const expected = Buffer.from(expectedHex, "hex");
|
|
23
|
+
return received.length === expected.length && timingSafeEqual(received, expected);
|
|
37
24
|
}
|
|
38
25
|
async function parseGithubWebhook(c, secret) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
26
|
+
if (!secret) return {
|
|
27
|
+
status: 401,
|
|
28
|
+
body: {
|
|
29
|
+
error: "unauthorized",
|
|
30
|
+
message: "GitHub webhook secret is not configured"
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const event = normalizeHeader(c.req.header("x-github-event"));
|
|
34
|
+
const deliveryId = normalizeHeader(c.req.header("x-github-delivery"));
|
|
35
|
+
const signature = normalizeHeader(c.req.header("x-hub-signature-256"));
|
|
36
|
+
if (!event) return {
|
|
37
|
+
status: 400,
|
|
38
|
+
body: {
|
|
39
|
+
error: "bad_request",
|
|
40
|
+
message: "Missing x-github-event header"
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
if (!deliveryId) return {
|
|
44
|
+
status: 400,
|
|
45
|
+
body: {
|
|
46
|
+
error: "bad_request",
|
|
47
|
+
message: "Missing x-github-delivery header"
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
if (!signature) return {
|
|
51
|
+
status: 401,
|
|
52
|
+
body: {
|
|
53
|
+
error: "unauthorized",
|
|
54
|
+
message: "Missing x-hub-signature-256 header"
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const rawBody = await c.req.text();
|
|
58
|
+
if (!verifySignature(rawBody, signature, secret)) return {
|
|
59
|
+
status: 401,
|
|
60
|
+
body: {
|
|
61
|
+
error: "unauthorized",
|
|
62
|
+
message: "Invalid GitHub webhook signature"
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
let payload;
|
|
66
|
+
try {
|
|
67
|
+
payload = JSON.parse(rawBody);
|
|
68
|
+
} catch {
|
|
69
|
+
return {
|
|
70
|
+
status: 400,
|
|
71
|
+
body: {
|
|
72
|
+
error: "bad_request",
|
|
73
|
+
message: "Malformed JSON payload"
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return {
|
|
78
|
+
status: 400,
|
|
79
|
+
body: {
|
|
80
|
+
error: "bad_request",
|
|
81
|
+
message: "Payload must be a JSON object"
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
return {
|
|
85
|
+
event,
|
|
86
|
+
deliveryId,
|
|
87
|
+
payload
|
|
88
|
+
};
|
|
63
89
|
}
|
|
64
90
|
function getObject(value) {
|
|
65
|
-
|
|
91
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
66
92
|
}
|
|
67
93
|
function getString(value) {
|
|
68
|
-
|
|
94
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
69
95
|
}
|
|
70
96
|
function getNumber(value) {
|
|
71
|
-
|
|
97
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
72
98
|
}
|
|
73
99
|
function getBoolean(value) {
|
|
74
|
-
|
|
100
|
+
return typeof value === "boolean" ? value : void 0;
|
|
75
101
|
}
|
|
76
102
|
function getLabels(value) {
|
|
77
|
-
|
|
78
|
-
|
|
103
|
+
if (!Array.isArray(value)) return [];
|
|
104
|
+
return value.map((label) => typeof label === "string" ? label : getString(getObject(label)?.name)).filter((label) => Boolean(label));
|
|
79
105
|
}
|
|
80
106
|
function getIssueTriageRunInput(parsed) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
107
|
+
if (parsed.event !== "issues" || getString(parsed.payload.action) !== "opened") return null;
|
|
108
|
+
const repository = getString(getObject(parsed.payload.repository)?.full_name);
|
|
109
|
+
const issue = getObject(parsed.payload.issue);
|
|
110
|
+
const sender = getString(getObject(parsed.payload.sender)?.login);
|
|
111
|
+
const installationId = getNumber(getObject(parsed.payload.installation)?.id);
|
|
112
|
+
const issueNumber = getNumber(issue?.number);
|
|
113
|
+
const issueTitle = getString(issue?.title);
|
|
114
|
+
const issueUrl = getString(issue?.html_url);
|
|
115
|
+
if (!repository || !installationId || !issueNumber || !issueTitle || !issueUrl) return null;
|
|
116
|
+
return {
|
|
117
|
+
repository,
|
|
118
|
+
issueNumber,
|
|
119
|
+
issueTitle,
|
|
120
|
+
issueUrl,
|
|
121
|
+
labels: getLabels(issue?.labels),
|
|
122
|
+
sender,
|
|
123
|
+
installationId
|
|
124
|
+
};
|
|
99
125
|
}
|
|
100
126
|
function normalizeGithubWebhookMetadata(parsed) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
127
|
+
const { event, deliveryId, payload } = parsed;
|
|
128
|
+
const repository = getObject(payload.repository);
|
|
129
|
+
const issue = getObject(payload.issue);
|
|
130
|
+
const pullRequest = getObject(payload.pull_request);
|
|
131
|
+
const sender = getObject(payload.sender);
|
|
132
|
+
const installation = getObject(payload.installation);
|
|
133
|
+
const issuePullRequest = getObject(issue?.pull_request);
|
|
134
|
+
return {
|
|
135
|
+
event,
|
|
136
|
+
action: getString(payload.action),
|
|
137
|
+
deliveryId,
|
|
138
|
+
repository: getString(repository?.full_name),
|
|
139
|
+
repositoryId: getNumber(repository?.id),
|
|
140
|
+
issueNumber: getNumber(issue?.number),
|
|
141
|
+
pullRequestNumber: getNumber(pullRequest?.number) ?? (event === "issue_comment" && issuePullRequest ? getNumber(issue?.number) : void 0),
|
|
142
|
+
sender: getString(sender?.login),
|
|
143
|
+
senderType: getString(sender?.type),
|
|
144
|
+
installationId: getNumber(installation?.id)
|
|
145
|
+
};
|
|
120
146
|
}
|
|
121
147
|
function notificationSummary(metadata, label) {
|
|
122
|
-
|
|
123
|
-
return `${actor}${label} on ${metadata.repository}#${metadata.pullRequestNumber}`;
|
|
148
|
+
return `${metadata.sender ? `${metadata.sender} ` : ""}${label} on ${metadata.repository}#${metadata.pullRequestNumber}`;
|
|
124
149
|
}
|
|
125
150
|
function notificationTargetUrl(event, payload) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (event === "pull_request_review") {
|
|
130
|
-
return getString(getObject(payload.review)?.html_url);
|
|
131
|
-
}
|
|
132
|
-
return getString(getObject(payload.pull_request)?.html_url);
|
|
151
|
+
if (event === "issue_comment" || event === "pull_request_review_comment") return getString(getObject(payload.comment)?.html_url);
|
|
152
|
+
if (event === "pull_request_review") return getString(getObject(payload.review)?.html_url);
|
|
153
|
+
return getString(getObject(payload.pull_request)?.html_url);
|
|
133
154
|
}
|
|
134
155
|
function classifyGithubWebhook(parsed) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
156
|
+
const metadata = normalizeGithubWebhookMetadata(parsed);
|
|
157
|
+
const { event, payload } = parsed;
|
|
158
|
+
const action = metadata.action;
|
|
159
|
+
if (!action || !metadata.repositoryId || !metadata.installationId || !metadata.pullRequestNumber || !metadata.repository) return;
|
|
160
|
+
let priority;
|
|
161
|
+
let kind;
|
|
162
|
+
let label;
|
|
163
|
+
let terminal = false;
|
|
164
|
+
if (event === "pull_request_review" && action === "submitted") {
|
|
165
|
+
const state = getString(getObject(payload.review)?.state)?.toLowerCase().replaceAll("_", "-");
|
|
166
|
+
priority = state === "approved" || state === "changes-requested" ? "urgent" : "high";
|
|
167
|
+
kind = state === "approved" ? "review-approved" : state === "changes-requested" ? "review-changes-requested" : "review-submitted";
|
|
168
|
+
label = state === "approved" ? "approved the pull request" : state === "changes-requested" ? "requested changes" : "submitted a review";
|
|
169
|
+
} else if (event === "pull_request" && action === "closed") {
|
|
170
|
+
const merged = getBoolean(getObject(payload.pull_request)?.merged) === true;
|
|
171
|
+
priority = "urgent";
|
|
172
|
+
kind = merged ? "pull-request-merged" : "pull-request-closed";
|
|
173
|
+
label = merged ? "merged the pull request" : "closed the pull request";
|
|
174
|
+
terminal = true;
|
|
175
|
+
} else if (event === "issue_comment" && action === "created") {
|
|
176
|
+
priority = "high";
|
|
177
|
+
kind = "issue-comment-created";
|
|
178
|
+
label = "commented";
|
|
179
|
+
} else if (event === "pull_request_review_comment" && action === "created") {
|
|
180
|
+
priority = "high";
|
|
181
|
+
kind = "review-comment-created";
|
|
182
|
+
label = "left a review comment";
|
|
183
|
+
} else if (event === "pull_request" && action === "reopened") {
|
|
184
|
+
priority = "high";
|
|
185
|
+
kind = "pull-request-reopened";
|
|
186
|
+
label = "reopened the pull request";
|
|
187
|
+
} else if (event === "pull_request_review" && action === "dismissed") {
|
|
188
|
+
priority = "high";
|
|
189
|
+
kind = "review-dismissed";
|
|
190
|
+
label = "dismissed a review";
|
|
191
|
+
} else if (event === "pull_request" && [
|
|
192
|
+
"synchronize",
|
|
193
|
+
"ready_for_review",
|
|
194
|
+
"converted_to_draft",
|
|
195
|
+
"assigned",
|
|
196
|
+
"unassigned",
|
|
197
|
+
"review_requested",
|
|
198
|
+
"review_request_removed"
|
|
199
|
+
].includes(action)) {
|
|
200
|
+
priority = "medium";
|
|
201
|
+
kind = `pull-request-${action.replaceAll("_", "-")}`;
|
|
202
|
+
label = action.replaceAll("_", " ");
|
|
203
|
+
} else if (event === "pull_request" && [
|
|
204
|
+
"edited",
|
|
205
|
+
"labeled",
|
|
206
|
+
"unlabeled",
|
|
207
|
+
"milestoned",
|
|
208
|
+
"demilestoned"
|
|
209
|
+
].includes(action)) {
|
|
210
|
+
priority = "low";
|
|
211
|
+
kind = `pull-request-${action.replaceAll("_", "-")}`;
|
|
212
|
+
label = action.replaceAll("_", " ");
|
|
213
|
+
} else return;
|
|
214
|
+
return {
|
|
215
|
+
action,
|
|
216
|
+
kind,
|
|
217
|
+
priority,
|
|
218
|
+
summary: notificationSummary(metadata, label),
|
|
219
|
+
terminal,
|
|
220
|
+
metadata: {
|
|
221
|
+
...metadata,
|
|
222
|
+
pullRequestNumber: metadata.pullRequestNumber,
|
|
223
|
+
repositoryId: metadata.repositoryId,
|
|
224
|
+
installationId: metadata.installationId
|
|
225
|
+
},
|
|
226
|
+
payload
|
|
227
|
+
};
|
|
205
228
|
}
|
|
206
229
|
async function resolveSubscriptionSession(controller, subscription) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
throw new Error(`Session ${sessionId} did not bind thread ${threadId}.`);
|
|
232
|
-
}
|
|
233
|
-
return session;
|
|
230
|
+
const { sessionId, resourceId, threadId } = subscription;
|
|
231
|
+
if (!sessionId || !resourceId || !threadId) throw new Error(`GitHub subscription ${subscription.id} is missing its session binding.`);
|
|
232
|
+
const scope = subscription.sessionScope || void 0;
|
|
233
|
+
let session = await controller.getSessionByResource(resourceId, scope);
|
|
234
|
+
if (!session) {
|
|
235
|
+
const tags = {
|
|
236
|
+
factoryProjectId: resourceId,
|
|
237
|
+
projectRepositoryId: subscription.data.projectRepositoryId,
|
|
238
|
+
...scope ? { worktreePath: scope } : {}
|
|
239
|
+
};
|
|
240
|
+
session = await controller.createSession({
|
|
241
|
+
id: sessionId,
|
|
242
|
+
ownerId: subscription.data.ownerId,
|
|
243
|
+
resourceId,
|
|
244
|
+
scope,
|
|
245
|
+
tags
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
if (session.thread.getId() !== threadId) await session.thread.switch({
|
|
249
|
+
threadId,
|
|
250
|
+
emitEvent: false
|
|
251
|
+
});
|
|
252
|
+
if (session.thread.getId() !== threadId) throw new Error(`Session ${sessionId} did not bind thread ${threadId}.`);
|
|
253
|
+
return session;
|
|
234
254
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
255
|
+
const AUTHORIZED_BOTS = /* @__PURE__ */ new Set(["coderabbitai[bot]", "devin-ai-integration[bot]"]);
|
|
256
|
+
const AUTHORIZED_PERMISSIONS = /* @__PURE__ */ new Set([
|
|
257
|
+
"admin",
|
|
258
|
+
"maintain",
|
|
259
|
+
"write"
|
|
260
|
+
]);
|
|
261
|
+
const PERMISSION_CHECK_TIMEOUT_MS = 5e3;
|
|
262
|
+
const AUTHOR_GATED_KINDS = /* @__PURE__ */ new Set([
|
|
263
|
+
"issue-comment-created",
|
|
264
|
+
"review-comment-created",
|
|
265
|
+
"review-submitted",
|
|
266
|
+
"review-approved",
|
|
267
|
+
"review-changes-requested",
|
|
268
|
+
"review-dismissed"
|
|
245
269
|
]);
|
|
246
270
|
async function isAuthorizedGithubSender(notification, github) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
resolve(void 0);
|
|
270
|
-
}, PERMISSION_CHECK_TIMEOUT_MS);
|
|
271
|
-
})
|
|
272
|
-
]);
|
|
273
|
-
return permission !== void 0 && AUTHORIZED_PERMISSIONS.has(permission);
|
|
274
|
-
} catch {
|
|
275
|
-
return false;
|
|
276
|
-
} finally {
|
|
277
|
-
if (timeout) clearTimeout(timeout);
|
|
278
|
-
}
|
|
271
|
+
if (!AUTHOR_GATED_KINDS.has(notification.kind)) return true;
|
|
272
|
+
const sender = notification.metadata.sender;
|
|
273
|
+
const repository = notification.metadata.repository;
|
|
274
|
+
if (!sender || !repository) return false;
|
|
275
|
+
const normalizedSender = sender.toLowerCase();
|
|
276
|
+
if (notification.metadata.senderType?.toLowerCase() === "bot" || normalizedSender.endsWith("[bot]")) return AUTHORIZED_BOTS.has(normalizedSender);
|
|
277
|
+
if (!github) return false;
|
|
278
|
+
const abortController = new AbortController();
|
|
279
|
+
let timeout;
|
|
280
|
+
try {
|
|
281
|
+
const permission = await Promise.race([github.getRepositoryCollaboratorPermission(notification.metadata.installationId, repository, sender, abortController.signal), new Promise((resolve) => {
|
|
282
|
+
timeout = setTimeout(() => {
|
|
283
|
+
abortController.abort();
|
|
284
|
+
resolve(void 0);
|
|
285
|
+
}, PERMISSION_CHECK_TIMEOUT_MS);
|
|
286
|
+
})]);
|
|
287
|
+
return permission !== void 0 && AUTHORIZED_PERMISSIONS.has(permission);
|
|
288
|
+
} catch {
|
|
289
|
+
return false;
|
|
290
|
+
} finally {
|
|
291
|
+
if (timeout) clearTimeout(timeout);
|
|
292
|
+
}
|
|
279
293
|
}
|
|
280
294
|
async function dispatchGithubWebhook(parsed, dependencies) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
return { delivered, failed, ignored: false };
|
|
295
|
+
const notification = classifyGithubWebhook(parsed);
|
|
296
|
+
if (!notification) return {
|
|
297
|
+
delivered: 0,
|
|
298
|
+
failed: 0,
|
|
299
|
+
ignored: true
|
|
300
|
+
};
|
|
301
|
+
if (!await (dependencies.isAuthorizedSender ?? ((n) => isAuthorizedGithubSender(n, dependencies.github)))(notification)) return {
|
|
302
|
+
delivered: 0,
|
|
303
|
+
failed: 0,
|
|
304
|
+
ignored: true
|
|
305
|
+
};
|
|
306
|
+
const target = {
|
|
307
|
+
installationExternalId: notification.metadata.installationId.toString(),
|
|
308
|
+
repositoryExternalId: notification.metadata.repositoryId.toString(),
|
|
309
|
+
changeRequestId: notification.metadata.pullRequestNumber.toString()
|
|
310
|
+
};
|
|
311
|
+
const listSubscriptions = dependencies.listSubscriptions ?? ((subscriptionTarget, options) => {
|
|
312
|
+
if (!dependencies.github) throw new Error("GitHub integration is required to load webhook subscriptions.");
|
|
313
|
+
return listPullRequestSubscriptionsForWebhook(subscriptionTarget, options, dependencies.github.integrationStorage);
|
|
314
|
+
});
|
|
315
|
+
const retireSubscription = dependencies.retireSubscription ?? ((id, status) => {
|
|
316
|
+
if (!dependencies.github) throw new Error("GitHub integration is required to retire webhook subscriptions.");
|
|
317
|
+
return retirePullRequestSubscription(id, status, dependencies.github.integrationStorage);
|
|
318
|
+
});
|
|
319
|
+
const subscriptions = await listSubscriptions(target, { includeTerminal: notification.action === "reopened" });
|
|
320
|
+
let delivered = 0;
|
|
321
|
+
let failed = 0;
|
|
322
|
+
for (const subscription of subscriptions) try {
|
|
323
|
+
const result = await (await resolveSubscriptionSession(dependencies.controller, subscription)).sendNotificationSignal({
|
|
324
|
+
source: "github",
|
|
325
|
+
kind: notification.kind,
|
|
326
|
+
summary: notification.summary,
|
|
327
|
+
priority: notification.priority,
|
|
328
|
+
payload: notification.payload,
|
|
329
|
+
sourceId: parsed.deliveryId,
|
|
330
|
+
dedupeKey: `${parsed.deliveryId}:${subscription.sessionId}:${subscription.threadId}`,
|
|
331
|
+
coalesceKey: `github:${subscription.data.repositoryExternalId}:pull-request:${subscription.data.changeRequestId}`,
|
|
332
|
+
metadata: {
|
|
333
|
+
event: notification.metadata.event,
|
|
334
|
+
action: notification.action,
|
|
335
|
+
repository: notification.metadata.repository,
|
|
336
|
+
issueNumber: notification.metadata.issueNumber,
|
|
337
|
+
pullRequestNumber: notification.metadata.pullRequestNumber,
|
|
338
|
+
targetUrl: notificationTargetUrl(parsed.event, parsed.payload),
|
|
339
|
+
deliveryId: parsed.deliveryId
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
await Promise.all([result.persisted, result.accepted].filter(Boolean));
|
|
343
|
+
if (notification.terminal) await retireSubscription(subscription.id, notification.kind === "pull-request-merged" ? "merged" : "closed");
|
|
344
|
+
else if (notification.action === "reopened") await retireSubscription(subscription.id, "open");
|
|
345
|
+
delivered += 1;
|
|
346
|
+
} catch (error) {
|
|
347
|
+
failed += 1;
|
|
348
|
+
dependencies.onTargetError?.(subscription, error);
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
delivered,
|
|
352
|
+
failed,
|
|
353
|
+
ignored: false
|
|
354
|
+
};
|
|
342
355
|
}
|
|
343
356
|
async function handleGithubWebhook(c, options) {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
357
|
+
const parsed = await parseGithubWebhook(c, options.github.webhookSecret);
|
|
358
|
+
if ("status" in parsed) return parsed;
|
|
359
|
+
if (!SUPPORTED_GITHUB_WEBHOOK_EVENTS.has(parsed.event)) return {
|
|
360
|
+
status: 202,
|
|
361
|
+
body: {
|
|
362
|
+
ok: true,
|
|
363
|
+
ignored: true
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
const metadata = normalizeGithubWebhookMetadata(parsed);
|
|
367
|
+
console.info("[GitHub Webhook]", metadata);
|
|
368
|
+
if (options.ingestFactoryEvent) await options.ingestFactoryEvent(parsed);
|
|
369
|
+
else {
|
|
370
|
+
const issueTriageRun = getIssueTriageRunInput(parsed);
|
|
371
|
+
if (issueTriageRun && options.runIssueTriage) options.runIssueTriage(issueTriageRun).catch((error) => {
|
|
372
|
+
console.error("[GitHub Webhook] Failed to run issue triage", {
|
|
373
|
+
deliveryId: metadata.deliveryId,
|
|
374
|
+
repository: metadata.repository,
|
|
375
|
+
issueNumber: metadata.issueNumber,
|
|
376
|
+
error: error instanceof Error ? error.message : String(error)
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
if (!options.controller) return {
|
|
381
|
+
status: 202,
|
|
382
|
+
body: { ok: true }
|
|
383
|
+
};
|
|
384
|
+
const result = await dispatchGithubWebhook(parsed, options);
|
|
385
|
+
if (result.failed > 0) console.warn(`[GitHub Webhook] ${result.failed} subscribed target(s) failed for delivery ${parsed.deliveryId}.`);
|
|
386
|
+
return {
|
|
387
|
+
status: 202,
|
|
388
|
+
body: {
|
|
389
|
+
ok: true,
|
|
390
|
+
...result.ignored ? { ignored: true } : {}
|
|
391
|
+
}
|
|
392
|
+
};
|
|
374
393
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
handleGithubWebhook,
|
|
379
|
-
normalizeGithubWebhookMetadata
|
|
380
|
-
};
|
|
394
|
+
//#endregion
|
|
395
|
+
export { classifyGithubWebhook, dispatchGithubWebhook, handleGithubWebhook, normalizeGithubWebhookMetadata };
|
|
396
|
+
|
|
381
397
|
//# sourceMappingURL=webhook.js.map
|