@urateam/core 0.1.41 → 0.1.43
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/dist/__tests__/audit-events-config-reloaded.test.d.ts +2 -0
- package/dist/__tests__/audit-events-config-reloaded.test.d.ts.map +1 -0
- package/dist/__tests__/audit-events-config-reloaded.test.js +33 -0
- package/dist/__tests__/audit-events-config-reloaded.test.js.map +1 -0
- package/dist/__tests__/bitbucket-webhook.test.d.ts +13 -0
- package/dist/__tests__/bitbucket-webhook.test.d.ts.map +1 -0
- package/dist/__tests__/bitbucket-webhook.test.js +379 -0
- package/dist/__tests__/bitbucket-webhook.test.js.map +1 -0
- package/dist/__tests__/bitbucket.test.d.ts +15 -0
- package/dist/__tests__/bitbucket.test.d.ts.map +1 -0
- package/dist/__tests__/bitbucket.test.js +237 -0
- package/dist/__tests__/bitbucket.test.js.map +1 -0
- package/dist/__tests__/gitlab-webhook.test.d.ts +13 -0
- package/dist/__tests__/gitlab-webhook.test.d.ts.map +1 -0
- package/dist/__tests__/gitlab-webhook.test.js +388 -0
- package/dist/__tests__/gitlab-webhook.test.js.map +1 -0
- package/dist/__tests__/runner-multi-vcs.test.d.ts +19 -0
- package/dist/__tests__/runner-multi-vcs.test.d.ts.map +1 -0
- package/dist/__tests__/runner-multi-vcs.test.js +346 -0
- package/dist/__tests__/runner-multi-vcs.test.js.map +1 -0
- package/dist/__tests__/triage-v2-prediction.test.d.ts +2 -0
- package/dist/__tests__/triage-v2-prediction.test.d.ts.map +1 -0
- package/dist/__tests__/triage-v2-prediction.test.js +70 -0
- package/dist/__tests__/triage-v2-prediction.test.js.map +1 -0
- package/dist/__tests__/triage-v2-prompt.test.d.ts +2 -0
- package/dist/__tests__/triage-v2-prompt.test.d.ts.map +1 -0
- package/dist/__tests__/triage-v2-prompt.test.js +127 -0
- package/dist/__tests__/triage-v2-prompt.test.js.map +1 -0
- package/dist/__tests__/triage-v2-render.test.d.ts +2 -0
- package/dist/__tests__/triage-v2-render.test.d.ts.map +1 -0
- package/dist/__tests__/triage-v2-render.test.js +200 -0
- package/dist/__tests__/triage-v2-render.test.js.map +1 -0
- package/dist/__tests__/triage-v2-schema.test.d.ts +2 -0
- package/dist/__tests__/triage-v2-schema.test.d.ts.map +1 -0
- package/dist/__tests__/triage-v2-schema.test.js +115 -0
- package/dist/__tests__/triage-v2-schema.test.js.map +1 -0
- package/dist/audit/events.d.ts +11 -0
- package/dist/audit/events.d.ts.map +1 -1
- package/dist/audit/events.js +18 -0
- package/dist/audit/events.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/pipeline/feedback-pipeline.d.ts +2 -0
- package/dist/pipeline/feedback-pipeline.d.ts.map +1 -1
- package/dist/pipeline/feedback-pipeline.js +4 -1
- package/dist/pipeline/feedback-pipeline.js.map +1 -1
- package/dist/pipeline/runner.d.ts +11 -0
- package/dist/pipeline/runner.d.ts.map +1 -1
- package/dist/pipeline/runner.js +314 -114
- package/dist/pipeline/runner.js.map +1 -1
- package/dist/pm/actions/triage-prompt.d.ts +42 -0
- package/dist/pm/actions/triage-prompt.d.ts.map +1 -0
- package/dist/pm/actions/triage-prompt.js +192 -0
- package/dist/pm/actions/triage-prompt.js.map +1 -0
- package/dist/pm/actions/triage-render.d.ts +39 -0
- package/dist/pm/actions/triage-render.d.ts.map +1 -0
- package/dist/pm/actions/triage-render.js +158 -0
- package/dist/pm/actions/triage-render.js.map +1 -0
- package/dist/pm/actions/triage.d.ts +2 -1
- package/dist/pm/actions/triage.d.ts.map +1 -1
- package/dist/pm/actions/triage.js +44 -58
- package/dist/pm/actions/triage.js.map +1 -1
- package/dist/pm/triage-prediction-quality.d.ts +26 -0
- package/dist/pm/triage-prediction-quality.d.ts.map +1 -0
- package/dist/pm/triage-prediction-quality.js +41 -0
- package/dist/pm/triage-prediction-quality.js.map +1 -0
- package/dist/pm/types.d.ts +60 -0
- package/dist/pm/types.d.ts.map +1 -1
- package/dist/pm/types.js +119 -0
- package/dist/pm/types.js.map +1 -1
- package/dist/repo/bitbucket.d.ts +136 -0
- package/dist/repo/bitbucket.d.ts.map +1 -0
- package/dist/repo/bitbucket.js +237 -0
- package/dist/repo/bitbucket.js.map +1 -0
- package/dist/repo/gitlab.d.ts +11 -0
- package/dist/repo/gitlab.d.ts.map +1 -1
- package/dist/repo/gitlab.js +37 -0
- package/dist/repo/gitlab.js.map +1 -1
- package/dist/repo/index.d.ts +3 -1
- package/dist/repo/index.d.ts.map +1 -1
- package/dist/repo/index.js +2 -1
- package/dist/repo/index.js.map +1 -1
- package/dist/server.d.ts +14 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +32 -0
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +7 -2
- package/dist/types.js.map +1 -1
- package/dist/webhook/bitbucket-handler.d.ts +65 -0
- package/dist/webhook/bitbucket-handler.d.ts.map +1 -0
- package/dist/webhook/bitbucket-handler.js +153 -0
- package/dist/webhook/bitbucket-handler.js.map +1 -0
- package/dist/webhook/gitlab-handler.d.ts +66 -0
- package/dist/webhook/gitlab-handler.d.ts.map +1 -0
- package/dist/webhook/gitlab-handler.js +159 -0
- package/dist/webhook/gitlab-handler.js.map +1 -0
- package/dist/webhook/index.d.ts +3 -0
- package/dist/webhook/index.d.ts.map +1 -1
- package/dist/webhook/index.js +3 -0
- package/dist/webhook/index.js.map +1 -1
- package/dist/webhook/shared-handlers.d.ts +110 -0
- package/dist/webhook/shared-handlers.d.ts.map +1 -0
- package/dist/webhook/shared-handlers.js +251 -0
- package/dist/webhook/shared-handlers.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitLab webhook handler.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `webhook/github-handler.ts` for GitLab MR events:
|
|
5
|
+
* - Signature verification via `X-Gitlab-Token` header (shared secret, not HMAC).
|
|
6
|
+
* - MR comment (Note Hook) events → `review-feedback` pipeline run triggers.
|
|
7
|
+
* - MR merged events → mark pipeline run as merged in DB.
|
|
8
|
+
*
|
|
9
|
+
* ## Setup
|
|
10
|
+
* In your GitLab project → Settings → Webhooks:
|
|
11
|
+
* 1. Set the URL to `https://<your-host>/webhooks/gitlab`.
|
|
12
|
+
* 2. Set a secret token in the "Secret token" field — this is the value you
|
|
13
|
+
* set as `gitlabWebhookToken` in your server config.
|
|
14
|
+
* 3. Enable: "Comments" and "Merge request events".
|
|
15
|
+
*
|
|
16
|
+
* The handler validates the `X-Gitlab-Token` header against `gitlabWebhookToken`.
|
|
17
|
+
*
|
|
18
|
+
* ## Environment variables
|
|
19
|
+
* No handler-specific env vars. Authentication is configured via `ServerConfig.gitlab.token`
|
|
20
|
+
* and `ServerConfig.gitlabWebhookToken` in your server config object.
|
|
21
|
+
*/
|
|
22
|
+
import { Hono } from "hono";
|
|
23
|
+
import { createLogger } from "../logger.js";
|
|
24
|
+
import { WebhookDedupSet, buildRepoConfigMap, findPipelineRunByUrlOrBranch, handleMergedEvent, processCommentFeedback, } from "./shared-handlers.js";
|
|
25
|
+
const log = createLogger({ component: "GitLabWebhookHandler" });
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Signature / token verification
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
/**
|
|
30
|
+
* Verify a GitLab webhook token.
|
|
31
|
+
*
|
|
32
|
+
* GitLab sends the configured secret as a plain string in `X-Gitlab-Token`.
|
|
33
|
+
* This function performs a constant-time comparison to prevent timing attacks.
|
|
34
|
+
*
|
|
35
|
+
* Both inputs are padded to a fixed length (256 bytes) before comparison so
|
|
36
|
+
* the loop always runs for the same number of iterations regardless of the
|
|
37
|
+
* actual token length, preventing timing side-channels.
|
|
38
|
+
*/
|
|
39
|
+
export function verifyGitLabToken(receivedToken, expectedToken) {
|
|
40
|
+
if (!receivedToken || !expectedToken)
|
|
41
|
+
return false;
|
|
42
|
+
// Pad both to the same length before comparing to maintain constant time
|
|
43
|
+
const a = Buffer.from(receivedToken.padEnd(256, "\0"));
|
|
44
|
+
const b = Buffer.from(expectedToken.padEnd(256, "\0"));
|
|
45
|
+
if (a.length !== b.length)
|
|
46
|
+
return false;
|
|
47
|
+
let result = 0;
|
|
48
|
+
for (let i = 0; i < a.length; i++) {
|
|
49
|
+
result |= a[i] ^ b[i];
|
|
50
|
+
}
|
|
51
|
+
return result === 0;
|
|
52
|
+
}
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Handler factory
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
/**
|
|
57
|
+
* Create a Hono app that handles GitLab webhook events at `/webhooks/gitlab`.
|
|
58
|
+
*
|
|
59
|
+
* Mount in your server: `app.route("/", createGitLabWebhookHandler(config))`.
|
|
60
|
+
*
|
|
61
|
+
* Shared DB helpers and comment-filtering logic are provided by
|
|
62
|
+
* `./shared-handlers.ts`. An O(1) Map of repo URL → RepoConfig is built
|
|
63
|
+
* once at initialisation to avoid per-request linear scans.
|
|
64
|
+
*/
|
|
65
|
+
export function createGitLabWebhookHandler(config) {
|
|
66
|
+
const app = new Hono();
|
|
67
|
+
const dedup = new WebhookDedupSet();
|
|
68
|
+
// Build O(1) URL → RepoConfig map once at init (avoids per-request O(n) scan)
|
|
69
|
+
const repoConfigsByUrl = buildRepoConfigMap(config.repoConfigs);
|
|
70
|
+
// Periodic cleanup of expired dedup entries
|
|
71
|
+
const cleanupTimer = setInterval(() => dedup.cleanup(), 60_000);
|
|
72
|
+
cleanupTimer.unref();
|
|
73
|
+
app.post("/webhooks/gitlab", async (c) => {
|
|
74
|
+
const rawBody = await c.req.text();
|
|
75
|
+
// 1. Verify token
|
|
76
|
+
if (config.webhookToken) {
|
|
77
|
+
const receivedToken = c.req.header("X-Gitlab-Token") ?? "";
|
|
78
|
+
if (!verifyGitLabToken(receivedToken, config.webhookToken)) {
|
|
79
|
+
return c.json({ error: "Invalid X-Gitlab-Token" }, 401);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// 2. Parse payload
|
|
83
|
+
let payload;
|
|
84
|
+
try {
|
|
85
|
+
payload = JSON.parse(rawBody);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return c.json({ error: "Invalid JSON" }, 400);
|
|
89
|
+
}
|
|
90
|
+
const objectKind = payload.object_kind ?? "";
|
|
91
|
+
const attrs = payload.object_attributes ?? {};
|
|
92
|
+
// 3a. MR merged event
|
|
93
|
+
if (objectKind === "merge_request" &&
|
|
94
|
+
(attrs.action === "merge" || attrs.state === "merged")) {
|
|
95
|
+
await handleMergedEvent(attrs.url ?? "", attrs.source_branch ?? "", {
|
|
96
|
+
db: config.db,
|
|
97
|
+
notifier: config.notifier,
|
|
98
|
+
mergeReason: "merged via GitLab",
|
|
99
|
+
});
|
|
100
|
+
return c.json({ ok: true, action: "mr-merged" });
|
|
101
|
+
}
|
|
102
|
+
// 3b. Note (comment) on a merge request
|
|
103
|
+
// GitLab sends object_kind="note" with noteable_type="MergeRequest"
|
|
104
|
+
if (objectKind !== "note" || attrs.noteable_type !== "MergeRequest") {
|
|
105
|
+
return c.json({ ok: true, skipped: "unhandled event type" });
|
|
106
|
+
}
|
|
107
|
+
// 4. Extract comment metadata
|
|
108
|
+
const noteId = String(attrs.id ?? "");
|
|
109
|
+
const commentBody = attrs.note ?? "";
|
|
110
|
+
const commentAuthor = payload.user?.username ?? "";
|
|
111
|
+
const commentHtmlUrl = attrs.url ?? "";
|
|
112
|
+
if (!commentBody.trim()) {
|
|
113
|
+
return c.json({ ok: true, skipped: "empty comment body" });
|
|
114
|
+
}
|
|
115
|
+
// 5. Extract MR metadata
|
|
116
|
+
const mr = payload.merge_request ?? {};
|
|
117
|
+
const mrUrl = mr.url ?? "";
|
|
118
|
+
const mrBranch = mr.source_branch ?? "";
|
|
119
|
+
const mrIsDraft = mr.draft === true || mr.work_in_progress === true;
|
|
120
|
+
if (mrIsDraft) {
|
|
121
|
+
return c.json({ ok: true, skipped: "MR is a draft — feedback loop disabled" });
|
|
122
|
+
}
|
|
123
|
+
if (!mrUrl) {
|
|
124
|
+
return c.json({ ok: true, skipped: "no MR URL in payload" });
|
|
125
|
+
}
|
|
126
|
+
// 6. Find the original pipeline run for this MR
|
|
127
|
+
const originalRun = await findPipelineRunByUrlOrBranch(config.db, mrUrl, mrBranch);
|
|
128
|
+
if (!originalRun) {
|
|
129
|
+
return c.json({ ok: true, skipped: "not an agent-created MR" });
|
|
130
|
+
}
|
|
131
|
+
// Resolve branch from DB if not available in payload
|
|
132
|
+
const prBranch = mrBranch || originalRun.branch || "";
|
|
133
|
+
// 7. Resolve repoConfig — O(1) Map lookup (initialised once at handler creation)
|
|
134
|
+
const repoConfig = repoConfigsByUrl.get(originalRun.repoUrl ?? "");
|
|
135
|
+
if (!repoConfig) {
|
|
136
|
+
log.warn({ repoUrl: originalRun.repoUrl }, "no repoConfig found for MR's repo URL");
|
|
137
|
+
return c.json({ ok: true, skipped: "no repo config for this MR" });
|
|
138
|
+
}
|
|
139
|
+
// 8–15. Shared filter/dedup/rate-limit/fire logic
|
|
140
|
+
const result = await processCommentFeedback({
|
|
141
|
+
commentId: noteId,
|
|
142
|
+
commentBody,
|
|
143
|
+
commentAuthor,
|
|
144
|
+
commentHtmlUrl,
|
|
145
|
+
prUrl: mrUrl,
|
|
146
|
+
prBranch,
|
|
147
|
+
prNumber: mr.iid ?? 0,
|
|
148
|
+
originalRun,
|
|
149
|
+
repoConfig,
|
|
150
|
+
}, {
|
|
151
|
+
runner: config.runner,
|
|
152
|
+
pipelineConfigs: config.pipelineConfigs,
|
|
153
|
+
dedup,
|
|
154
|
+
});
|
|
155
|
+
return c.json(result);
|
|
156
|
+
});
|
|
157
|
+
return app;
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=gitlab-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitlab-handler.js","sourceRoot":"","sources":["../../src/webhook/gitlab-handler.ts"],"names":[],"mappings":"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"}
|
package/dist/webhook/index.d.ts
CHANGED
|
@@ -2,4 +2,7 @@ export { verifyLinearSignature } from "./signature.js";
|
|
|
2
2
|
export { parseStateChange, type ParsedStateChange } from "./parser.js";
|
|
3
3
|
export { createWebhookHandler, type WebhookHandlerConfig } from "./handler.js";
|
|
4
4
|
export { createGitHubWebhookHandler, verifyGitHubSignature, type GitHubWebhookHandlerConfig, type ReviewFeedbackComment, } from "./github-handler.js";
|
|
5
|
+
export { createGitLabWebhookHandler, verifyGitLabToken, type GitLabWebhookHandlerConfig, } from "./gitlab-handler.js";
|
|
6
|
+
export { createBitbucketWebhookHandler, verifyBitbucketSignature, type BitbucketWebhookHandlerConfig, } from "./bitbucket-handler.js";
|
|
7
|
+
export { WebhookDedupSet, buildRepoConfigMap, findPipelineRunByUrlOrBranch, updatePipelineRunMerged, handleMergedEvent, processCommentFeedback, type MergedEventHandlerConfig, type CommentFeedbackInput, type CommentFeedbackResult, } from "./shared-handlers.js";
|
|
5
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/webhook/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,KAAK,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,EACrB,KAAK,0BAA0B,EAC/B,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/webhook/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,KAAK,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,EACrB,KAAK,0BAA0B,EAC/B,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,0BAA0B,EAC1B,iBAAiB,EACjB,KAAK,0BAA0B,GAChC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,6BAA6B,EAC7B,wBAAwB,EACxB,KAAK,6BAA6B,GACnC,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,4BAA4B,EAC5B,uBAAuB,EACvB,iBAAiB,EACjB,sBAAsB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,sBAAsB,CAAC"}
|
package/dist/webhook/index.js
CHANGED
|
@@ -2,4 +2,7 @@ export { verifyLinearSignature } from "./signature.js";
|
|
|
2
2
|
export { parseStateChange } from "./parser.js";
|
|
3
3
|
export { createWebhookHandler } from "./handler.js";
|
|
4
4
|
export { createGitHubWebhookHandler, verifyGitHubSignature, } from "./github-handler.js";
|
|
5
|
+
export { createGitLabWebhookHandler, verifyGitLabToken, } from "./gitlab-handler.js";
|
|
6
|
+
export { createBitbucketWebhookHandler, verifyBitbucketSignature, } from "./bitbucket-handler.js";
|
|
7
|
+
export { WebhookDedupSet, buildRepoConfigMap, findPipelineRunByUrlOrBranch, updatePipelineRunMerged, handleMergedEvent, processCommentFeedback, } from "./shared-handlers.js";
|
|
5
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/webhook/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAA0B,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAA6B,MAAM,cAAc,CAAC;AAC/E,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,GAGtB,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/webhook/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAA0B,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAA6B,MAAM,cAAc,CAAC;AAC/E,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,GAGtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,0BAA0B,EAC1B,iBAAiB,GAElB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,6BAA6B,EAC7B,wBAAwB,GAEzB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,4BAA4B,EAC5B,uBAAuB,EACvB,iBAAiB,EACjB,sBAAsB,GAIvB,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for GitLab and Bitbucket webhook handlers.
|
|
3
|
+
*
|
|
4
|
+
* Extracted to avoid duplication between `gitlab-handler.ts` and
|
|
5
|
+
* `bitbucket-handler.ts`. Both providers follow the same general pattern for:
|
|
6
|
+
* - In-memory comment dedup (TTL-based expiry)
|
|
7
|
+
* - DB lookups for pipeline runs
|
|
8
|
+
* - Generic "PR/MR merged" event handling
|
|
9
|
+
* - Common comment-event filtering and feedback-run triggering
|
|
10
|
+
*/
|
|
11
|
+
import { pipelineRuns } from "../db/schema.js";
|
|
12
|
+
import type { AnyDb } from "../db/client.js";
|
|
13
|
+
import type { PipelineRunner } from "../pipeline/runner.js";
|
|
14
|
+
import type { Notifier, PipelineConfig, RepoConfig } from "../types.js";
|
|
15
|
+
/**
|
|
16
|
+
* In-memory dedup set for processed webhook event IDs with TTL-based expiry.
|
|
17
|
+
* Used by GitLab (note IDs) and Bitbucket (comment IDs) webhook handlers to
|
|
18
|
+
* prevent double-processing when webhooks are delivered more than once.
|
|
19
|
+
*/
|
|
20
|
+
export declare class WebhookDedupSet {
|
|
21
|
+
private entries;
|
|
22
|
+
has(id: string): boolean;
|
|
23
|
+
add(id: string, ttlMs?: number): void;
|
|
24
|
+
/** Purge entries whose TTL has elapsed. Call periodically (e.g. every minute). */
|
|
25
|
+
cleanup(): void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Build an O(1) Map<repoUrl, RepoConfig> for fast webhook-time lookups.
|
|
29
|
+
*
|
|
30
|
+
* Call once at handler initialisation (inside `createXxxWebhookHandler()`),
|
|
31
|
+
* store as a closure variable, and replace linear `Object.values(repoConfigs)`
|
|
32
|
+
* scans with `repoConfigsByUrl.get(url)`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function buildRepoConfigMap(repoConfigs: Record<string, RepoConfig>): Map<string, RepoConfig>;
|
|
35
|
+
/**
|
|
36
|
+
* Look up a pipeline run by PR/MR URL (primary) then by branch name (fallback).
|
|
37
|
+
* Shared by GitLab and Bitbucket webhook handlers.
|
|
38
|
+
*/
|
|
39
|
+
export declare function findPipelineRunByUrlOrBranch(db: AnyDb, url?: string, branch?: string): Promise<(typeof pipelineRuns.$inferSelect) | undefined>;
|
|
40
|
+
/**
|
|
41
|
+
* Mark a pipeline run as externally merged.
|
|
42
|
+
* Shared by GitLab and Bitbucket webhook handlers.
|
|
43
|
+
*/
|
|
44
|
+
export declare function updatePipelineRunMerged(db: AnyDb, runId: string, merged: boolean | null, reason: string): Promise<void>;
|
|
45
|
+
export interface MergedEventHandlerConfig {
|
|
46
|
+
db: AnyDb;
|
|
47
|
+
notifier?: Notifier;
|
|
48
|
+
/** Human-readable reason stored in autoMergeReason, e.g. "merged via GitLab". */
|
|
49
|
+
mergeReason: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Generic handler for PR/MR merged events.
|
|
53
|
+
*
|
|
54
|
+
* Marks the pipeline run as merged in the DB and calls `notifier.onPRMerged`.
|
|
55
|
+
* The caller is responsible for extracting `url` and `branch` from the
|
|
56
|
+
* provider-specific webhook payload before calling this function.
|
|
57
|
+
*
|
|
58
|
+
* @param url The PR/MR web URL (e.g. https://gitlab.com/org/repo/-/merge_requests/7)
|
|
59
|
+
* @param branch The source branch of the PR/MR
|
|
60
|
+
* @param config DB, notifier, and merge-reason string
|
|
61
|
+
*/
|
|
62
|
+
export declare function handleMergedEvent(url: string, branch: string, config: MergedEventHandlerConfig): Promise<void>;
|
|
63
|
+
export interface CommentFeedbackInput {
|
|
64
|
+
/** Dedup key for this comment (note ID on GitLab, comment ID on Bitbucket). */
|
|
65
|
+
commentId: string;
|
|
66
|
+
/** Raw comment body text (before sanitisation). */
|
|
67
|
+
commentBody: string;
|
|
68
|
+
/** Author login / username / nickname. */
|
|
69
|
+
commentAuthor: string;
|
|
70
|
+
/** Permalink URL to the comment. */
|
|
71
|
+
commentHtmlUrl: string;
|
|
72
|
+
/** Web URL of the PR or MR. */
|
|
73
|
+
prUrl: string;
|
|
74
|
+
/** Source branch of the PR or MR. */
|
|
75
|
+
prBranch: string;
|
|
76
|
+
/** Numeric PR/MR ID used for startFeedback (iid on GitLab, id on Bitbucket). */
|
|
77
|
+
prNumber: number;
|
|
78
|
+
/** Already-resolved pipeline run for this PR/MR (from findPipelineRunByUrlOrBranch). */
|
|
79
|
+
originalRun: typeof pipelineRuns.$inferSelect;
|
|
80
|
+
/** Already-resolved RepoConfig for this run's repo URL. */
|
|
81
|
+
repoConfig: RepoConfig;
|
|
82
|
+
}
|
|
83
|
+
export interface CommentFeedbackResult {
|
|
84
|
+
ok: boolean;
|
|
85
|
+
action?: string;
|
|
86
|
+
skipped?: string;
|
|
87
|
+
deduplicated?: boolean;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Apply all filters and trigger a review-feedback pipeline run for a PR/MR
|
|
91
|
+
* comment event. Returns a JSON-serializable result for the HTTP response.
|
|
92
|
+
*
|
|
93
|
+
* Shared by GitLab and Bitbucket webhook handlers. The caller is responsible
|
|
94
|
+
* for auth/signature verification, payload parsing, field extraction, and
|
|
95
|
+
* resolving `originalRun` + `repoConfig` before calling this function.
|
|
96
|
+
*
|
|
97
|
+
* Filters applied (in order):
|
|
98
|
+
* 1. Bot exclusion (`feedbackCfg.botLogins`)
|
|
99
|
+
* 2. Allowed-reviewer list (`feedbackCfg.allowedReviewers`)
|
|
100
|
+
* 3. Trigger-keyword check (`feedbackCfg.triggerKeyword`)
|
|
101
|
+
* 4. In-memory dedup (24h TTL via `WebhookDedupSet`)
|
|
102
|
+
* 5. Rate-limit (one active feedback run per PR/MR URL)
|
|
103
|
+
* 6. Pipeline config lookup
|
|
104
|
+
*/
|
|
105
|
+
export declare function processCommentFeedback(input: CommentFeedbackInput, handlerConfig: {
|
|
106
|
+
runner: PipelineRunner;
|
|
107
|
+
pipelineConfigs: Record<string, PipelineConfig>;
|
|
108
|
+
dedup: WebhookDedupSet;
|
|
109
|
+
}): Promise<CommentFeedbackResult>;
|
|
110
|
+
//# sourceMappingURL=shared-handlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared-handlers.d.ts","sourceRoot":"","sources":["../../src/webhook/shared-handlers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAe,UAAU,EAAE,MAAM,aAAa,CAAC;AAUrF;;;;GAIG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,OAAO,CAA6B;IAE5C,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAUxB,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,SAAa,GAAc,IAAI;IAIpD,kFAAkF;IAClF,OAAO,IAAI,IAAI;CAMhB;AAMD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GACtC,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAMzB;AAMD;;;GAGG;AACH,wBAAsB,4BAA4B,CAChD,EAAE,EAAE,KAAK,EACT,GAAG,CAAC,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,CAAC,OAAO,YAAY,CAAC,YAAY,CAAC,GAAG,SAAS,CAAC,CAkBzD;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,KAAK,EACT,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,OAAO,GAAG,IAAI,EACtB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAKf;AAMD,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,KAAK,CAAC;IACV,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,IAAI,CAAC,CAoCf;AAMD,MAAM,WAAW,oBAAoB;IACnC,+EAA+E;IAC/E,SAAS,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB,oCAAoC;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,QAAQ,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,WAAW,EAAE,OAAO,YAAY,CAAC,YAAY,CAAC;IAC9C,2DAA2D;IAC3D,UAAU,EAAE,UAAU,CAAC;CACxB;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,oBAAoB,EAC3B,aAAa,EAAE;IACb,MAAM,EAAE,cAAc,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAChD,KAAK,EAAE,eAAe,CAAC;CACxB,GACA,OAAO,CAAC,qBAAqB,CAAC,CA0HhC"}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for GitLab and Bitbucket webhook handlers.
|
|
3
|
+
*
|
|
4
|
+
* Extracted to avoid duplication between `gitlab-handler.ts` and
|
|
5
|
+
* `bitbucket-handler.ts`. Both providers follow the same general pattern for:
|
|
6
|
+
* - In-memory comment dedup (TTL-based expiry)
|
|
7
|
+
* - DB lookups for pipeline runs
|
|
8
|
+
* - Generic "PR/MR merged" event handling
|
|
9
|
+
* - Common comment-event filtering and feedback-run triggering
|
|
10
|
+
*/
|
|
11
|
+
import { eq } from "drizzle-orm";
|
|
12
|
+
import { pipelineRuns } from "../db/schema.js";
|
|
13
|
+
import { createLogger } from "../logger.js";
|
|
14
|
+
const log = createLogger({ component: "WebhookSharedHandlers" });
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// WebhookDedupSet — in-memory dedup for processed event IDs (24h TTL)
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
/**
|
|
19
|
+
* In-memory dedup set for processed webhook event IDs with TTL-based expiry.
|
|
20
|
+
* Used by GitLab (note IDs) and Bitbucket (comment IDs) webhook handlers to
|
|
21
|
+
* prevent double-processing when webhooks are delivered more than once.
|
|
22
|
+
*/
|
|
23
|
+
export class WebhookDedupSet {
|
|
24
|
+
entries = new Map(); // id -> expiry ms
|
|
25
|
+
has(id) {
|
|
26
|
+
const expiry = this.entries.get(id);
|
|
27
|
+
if (expiry === undefined)
|
|
28
|
+
return false;
|
|
29
|
+
if (Date.now() > expiry) {
|
|
30
|
+
this.entries.delete(id);
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
add(id, ttlMs = 86_400_000 /* 24 h */) {
|
|
36
|
+
this.entries.set(id, Date.now() + ttlMs);
|
|
37
|
+
}
|
|
38
|
+
/** Purge entries whose TTL has elapsed. Call periodically (e.g. every minute). */
|
|
39
|
+
cleanup() {
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
for (const [id, expiry] of this.entries) {
|
|
42
|
+
if (now > expiry)
|
|
43
|
+
this.entries.delete(id);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// buildRepoConfigMap — O(1) URL → RepoConfig lookup
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
/**
|
|
51
|
+
* Build an O(1) Map<repoUrl, RepoConfig> for fast webhook-time lookups.
|
|
52
|
+
*
|
|
53
|
+
* Call once at handler initialisation (inside `createXxxWebhookHandler()`),
|
|
54
|
+
* store as a closure variable, and replace linear `Object.values(repoConfigs)`
|
|
55
|
+
* scans with `repoConfigsByUrl.get(url)`.
|
|
56
|
+
*/
|
|
57
|
+
export function buildRepoConfigMap(repoConfigs) {
|
|
58
|
+
const map = new Map();
|
|
59
|
+
for (const rc of Object.values(repoConfigs)) {
|
|
60
|
+
if (rc.url)
|
|
61
|
+
map.set(rc.url, rc);
|
|
62
|
+
}
|
|
63
|
+
return map;
|
|
64
|
+
}
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// DB helpers
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
/**
|
|
69
|
+
* Look up a pipeline run by PR/MR URL (primary) then by branch name (fallback).
|
|
70
|
+
* Shared by GitLab and Bitbucket webhook handlers.
|
|
71
|
+
*/
|
|
72
|
+
export async function findPipelineRunByUrlOrBranch(db, url, branch) {
|
|
73
|
+
if (url) {
|
|
74
|
+
const rows = await db
|
|
75
|
+
.select()
|
|
76
|
+
.from(pipelineRuns)
|
|
77
|
+
.where(eq(pipelineRuns.prUrl, url))
|
|
78
|
+
.limit(1);
|
|
79
|
+
if (rows.length > 0)
|
|
80
|
+
return rows[0];
|
|
81
|
+
}
|
|
82
|
+
if (branch?.startsWith("agent/")) {
|
|
83
|
+
const rows = await db
|
|
84
|
+
.select()
|
|
85
|
+
.from(pipelineRuns)
|
|
86
|
+
.where(eq(pipelineRuns.branch, branch))
|
|
87
|
+
.limit(1);
|
|
88
|
+
if (rows.length > 0)
|
|
89
|
+
return rows[0];
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Mark a pipeline run as externally merged.
|
|
95
|
+
* Shared by GitLab and Bitbucket webhook handlers.
|
|
96
|
+
*/
|
|
97
|
+
export async function updatePipelineRunMerged(db, runId, merged, reason) {
|
|
98
|
+
await db
|
|
99
|
+
.update(pipelineRuns)
|
|
100
|
+
.set({ autoMerged: merged, autoMergeReason: reason })
|
|
101
|
+
.where(eq(pipelineRuns.id, runId));
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Generic handler for PR/MR merged events.
|
|
105
|
+
*
|
|
106
|
+
* Marks the pipeline run as merged in the DB and calls `notifier.onPRMerged`.
|
|
107
|
+
* The caller is responsible for extracting `url` and `branch` from the
|
|
108
|
+
* provider-specific webhook payload before calling this function.
|
|
109
|
+
*
|
|
110
|
+
* @param url The PR/MR web URL (e.g. https://gitlab.com/org/repo/-/merge_requests/7)
|
|
111
|
+
* @param branch The source branch of the PR/MR
|
|
112
|
+
* @param config DB, notifier, and merge-reason string
|
|
113
|
+
*/
|
|
114
|
+
export async function handleMergedEvent(url, branch, config) {
|
|
115
|
+
if (!url) {
|
|
116
|
+
log.warn("merged-event: no URL extracted from payload — skipping");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const originalRun = await findPipelineRunByUrlOrBranch(config.db, url, branch);
|
|
120
|
+
if (!originalRun) {
|
|
121
|
+
log.debug({ url }, "merged-event: no pipeline run found — skipping");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (originalRun.autoMerged) {
|
|
125
|
+
log.debug({ runId: originalRun.id }, "merged-event: run already marked merged — skipping");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
await updatePipelineRunMerged(config.db, originalRun.id, true, config.mergeReason);
|
|
129
|
+
log.info({ runId: originalRun.id, url }, "merged-event: updated pipeline run auto_merged=true");
|
|
130
|
+
if (config.notifier?.onPRMerged) {
|
|
131
|
+
await config.notifier
|
|
132
|
+
.onPRMerged(originalRun)
|
|
133
|
+
.catch((err) => log.error({ err, runId: originalRun.id }, "merged-event: notifier.onPRMerged() failed"));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Apply all filters and trigger a review-feedback pipeline run for a PR/MR
|
|
138
|
+
* comment event. Returns a JSON-serializable result for the HTTP response.
|
|
139
|
+
*
|
|
140
|
+
* Shared by GitLab and Bitbucket webhook handlers. The caller is responsible
|
|
141
|
+
* for auth/signature verification, payload parsing, field extraction, and
|
|
142
|
+
* resolving `originalRun` + `repoConfig` before calling this function.
|
|
143
|
+
*
|
|
144
|
+
* Filters applied (in order):
|
|
145
|
+
* 1. Bot exclusion (`feedbackCfg.botLogins`)
|
|
146
|
+
* 2. Allowed-reviewer list (`feedbackCfg.allowedReviewers`)
|
|
147
|
+
* 3. Trigger-keyword check (`feedbackCfg.triggerKeyword`)
|
|
148
|
+
* 4. In-memory dedup (24h TTL via `WebhookDedupSet`)
|
|
149
|
+
* 5. Rate-limit (one active feedback run per PR/MR URL)
|
|
150
|
+
* 6. Pipeline config lookup
|
|
151
|
+
*/
|
|
152
|
+
export async function processCommentFeedback(input, handlerConfig) {
|
|
153
|
+
const { commentId, commentBody, commentAuthor, commentHtmlUrl, prUrl, prBranch, prNumber, originalRun, repoConfig, } = input;
|
|
154
|
+
const { runner, pipelineConfigs, dedup } = handlerConfig;
|
|
155
|
+
// Feedback config — `githubFeedback` field applies to all providers
|
|
156
|
+
// (named for historical reasons; used by GitHub, GitLab, and Bitbucket)
|
|
157
|
+
const feedbackCfg = repoConfig.githubFeedback;
|
|
158
|
+
// 1. Bot exclusion
|
|
159
|
+
const botLogins = feedbackCfg?.botLogins ?? [];
|
|
160
|
+
if (botLogins.length > 0 && botLogins.includes(commentAuthor)) {
|
|
161
|
+
return { ok: true, skipped: "comment from bot login" };
|
|
162
|
+
}
|
|
163
|
+
// 2. Allowed-reviewer filter
|
|
164
|
+
const allowedReviewers = feedbackCfg?.allowedReviewers ?? [];
|
|
165
|
+
if (allowedReviewers.length > 0 && !allowedReviewers.includes(commentAuthor)) {
|
|
166
|
+
return { ok: true, skipped: "commenter not in allowedReviewers" };
|
|
167
|
+
}
|
|
168
|
+
// 3. Trigger-keyword check
|
|
169
|
+
const triggerKeyword = feedbackCfg?.triggerKeyword;
|
|
170
|
+
const autoTrigger = feedbackCfg?.autoTrigger !== false;
|
|
171
|
+
if (triggerKeyword) {
|
|
172
|
+
if (!commentBody.includes(triggerKeyword)) {
|
|
173
|
+
return { ok: true, skipped: "trigger keyword not found" };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
else if (!autoTrigger) {
|
|
177
|
+
return { ok: true, skipped: "autoTrigger is disabled and no triggerKeyword configured" };
|
|
178
|
+
}
|
|
179
|
+
// 4. Dedup
|
|
180
|
+
if (dedup.has(commentId)) {
|
|
181
|
+
return { ok: true, deduplicated: true };
|
|
182
|
+
}
|
|
183
|
+
// 5. Rate-limit: one active feedback run per PR/MR URL
|
|
184
|
+
if (runner.isActiveFeedback(prUrl)) {
|
|
185
|
+
log.info({ prUrl }, "feedback run already in progress for this PR/MR — skipping");
|
|
186
|
+
return { ok: true, skipped: "feedback run already in progress" };
|
|
187
|
+
}
|
|
188
|
+
// 6. Pipeline config lookup
|
|
189
|
+
const pipelineKey = originalRun.pipelineKey;
|
|
190
|
+
const pipelineConfig = pipelineConfigs[pipelineKey];
|
|
191
|
+
if (!pipelineConfig) {
|
|
192
|
+
log.warn({ pipelineKey }, "no pipeline config found for original run's pipelineKey");
|
|
193
|
+
return { ok: true, skipped: "pipeline config not found" };
|
|
194
|
+
}
|
|
195
|
+
// Commit dedup entry now that we're going to trigger a run
|
|
196
|
+
dedup.add(commentId);
|
|
197
|
+
// Sanitise comment body before passing to agent pipeline
|
|
198
|
+
const MAX_COMMENT_LENGTH = 4000;
|
|
199
|
+
const sanitizedBody = commentBody
|
|
200
|
+
.slice(0, MAX_COMMENT_LENGTH)
|
|
201
|
+
.replace(/<\/review-comment>/gi, "[/review-comment]");
|
|
202
|
+
const feedbackComment = {
|
|
203
|
+
commentId,
|
|
204
|
+
author: commentAuthor,
|
|
205
|
+
body: sanitizedBody,
|
|
206
|
+
htmlUrl: commentHtmlUrl,
|
|
207
|
+
};
|
|
208
|
+
// Build minimal issue object from DB row
|
|
209
|
+
const issue = {
|
|
210
|
+
id: originalRun.issueId,
|
|
211
|
+
identifier: originalRun.issueId,
|
|
212
|
+
title: originalRun.issueTitle,
|
|
213
|
+
description: "",
|
|
214
|
+
labels: [],
|
|
215
|
+
priority: 0,
|
|
216
|
+
teamId: "",
|
|
217
|
+
};
|
|
218
|
+
// Resolve branch: caller may have pre-resolved via `|| originalRun.branch`,
|
|
219
|
+
// but we apply the same fallback here for safety.
|
|
220
|
+
const resolvedBranch = prBranch || originalRun.branch || "";
|
|
221
|
+
const branchSlug = resolvedBranch.startsWith("agent/")
|
|
222
|
+
? resolvedBranch.slice("agent/".length)
|
|
223
|
+
: (originalRun.issueId ?? "feedback");
|
|
224
|
+
const sanitizedIssue = {
|
|
225
|
+
id: originalRun.issueId,
|
|
226
|
+
slug: branchSlug,
|
|
227
|
+
title: originalRun.issueTitle,
|
|
228
|
+
description: "",
|
|
229
|
+
acceptanceCriteria: [],
|
|
230
|
+
labels: [],
|
|
231
|
+
priority: 0,
|
|
232
|
+
};
|
|
233
|
+
// Fire-and-forget feedback run
|
|
234
|
+
runner
|
|
235
|
+
.startFeedback({
|
|
236
|
+
issue,
|
|
237
|
+
pipelineKey,
|
|
238
|
+
pipelineConfig,
|
|
239
|
+
repoConfig,
|
|
240
|
+
sanitizedIssue,
|
|
241
|
+
branch: resolvedBranch,
|
|
242
|
+
prUrl,
|
|
243
|
+
prNumber,
|
|
244
|
+
parentRunId: originalRun.id,
|
|
245
|
+
feedbackComments: [feedbackComment],
|
|
246
|
+
rerequestReview: false, // GitLab/Bitbucket don't have GitHub's re-request concept
|
|
247
|
+
})
|
|
248
|
+
.catch((err) => log.error({ err }, "runner.startFeedback() failed"));
|
|
249
|
+
return { ok: true, action: "feedback-triggered" };
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=shared-handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared-handlers.js","sourceRoot":"","sources":["../../src/webhook/shared-handlers.ts"],"names":[],"mappings":"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"}
|