@desplega.ai/agent-swarm 2.5.0 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{acp-adapter-11gp67nr.js → acp-adapter-7g6bpwbk.js} +1 -1
- package/dist/{actions-8p2kze8j.js → actions-w8xd014k.js} +16 -16
- package/dist/{app-0qp0a2gk.js → app-nndcvftq.js} +3 -3
- package/dist/{assistant-jedd3ktm.js → assistant-hjmqyscx.js} +17 -17
- package/dist/{boot-reembed-n4wq5dd2.js → boot-reembed-r8rhf9h8.js} +1 -1
- package/dist/{boot-reembed-4xwq5tg0.js → boot-reembed-vwcj2y5d.js} +1 -1
- package/dist/{boot-scrub-sweep-69h90xcq.js → boot-scrub-sweep-fr9q36ne.js} +2 -2
- package/dist/{boot-seeds-3vn0m9sg.js → boot-seeds-9jqwnegq.js} +4 -4
- package/dist/{claude-adapter-221mvad5.js → claude-adapter-2f8b85df.js} +2 -2
- package/dist/{claude-sdk-session-8thjsk5e.js → claude-sdk-session-05kat8rv.js} +2 -2
- package/dist/{cli-x6vf4sad.js → cli-0jy85zz4.js} +5 -5
- package/dist/{cli-5rvg1ddk.js → cli-1dp1zj04.js} +2 -2
- package/dist/{cli-179xcyky.js → cli-22rmcjkg.js} +1 -1
- package/dist/{cli-7g40g8hq.js → cli-2jg61wab.js} +8 -8
- package/dist/{cli-r25qpaxt.js → cli-2wba1ac9.js} +12 -12
- package/dist/{cli-y4x8ww6s.js → cli-3ee5z0q0.js} +1 -1
- package/dist/{cli-0y6f0ckq.js → cli-9mm5ade4.js} +1 -1
- package/dist/{cli-x21dqn18.js → cli-ajqhxtnz.js} +1 -1
- package/dist/{cli-segs7hx9.js → cli-cah5detr.js} +5 -5
- package/dist/{cli-4w24f7z3.js → cli-cwyafhw1.js} +1 -1
- package/dist/{cli-fy1jyyym.js → cli-e9btqw4y.js} +10 -10
- package/dist/{cli-bgdtv62r.js → cli-eym3jg7w.js} +6 -6
- package/dist/{cli-xt768r5m.js → cli-gwcf52gz.js} +1 -1
- package/dist/{cli-nnvjb17s.js → cli-hadv0725.js} +1 -1
- package/dist/{cli-5kzk3472.js → cli-kcsd1ez8.js} +60 -32
- package/dist/{cli-we0ra057.js → cli-mq7kg1b1.js} +2 -2
- package/dist/{cli-y9jvxfhk.js → cli-mz2ygeyq.js} +2 -2
- package/dist/{cli-3zwye9sn.js → cli-nj12tn2c.js} +2 -2
- package/dist/{cli-92mqenkc.js → cli-pyy4c9ch.js} +7 -7
- package/dist/{cli-fcc5qe3f.js → cli-q9pzwxvk.js} +3 -3
- package/dist/{cli-sy8f9705.js → cli-qfjm4gvt.js} +2 -2
- package/dist/{cli-mbfj11fq.js → cli-qfm6my86.js} +1 -1
- package/dist/{cli-1pr672hz.js → cli-r9fy0ddc.js} +1 -1
- package/dist/{cli-s4sntwt4.js → cli-rngde2jv.js} +1 -1
- package/dist/{cli-7bwtc6yx.js → cli-sn1vbtg2.js} +3 -3
- package/dist/{cli-36j4jnk3.js → cli-v2g93k4v.js} +3 -3
- package/dist/{cli-ftmq4nvk.js → cli-xc65x52q.js} +1 -1
- package/dist/{cli-9ce2xw98.js → cli-y3zyaee5.js} +4 -4
- package/dist/{cli-0z3vjrpm.js → cli-z2dv25mf.js} +1 -1
- package/dist/{cli-xawhpgc9.js → cli-znvq0k3t.js} +1 -1
- package/dist/cli.js +11 -11
- package/dist/{codex-adapter-tcg1q3jk.js → codex-adapter-p4ptwar5.js} +1 -1
- package/dist/{codex-session-runner-xfx4wd9x.js → codex-session-runner-964c7dnk.js} +1 -1
- package/dist/{commands-ew83nj70.js → commands-mbvhbrcy.js} +2 -2
- package/dist/{controllers-connect-tw1dan9r.js → controllers-connect-6dkm71tg.js} +16 -16
- package/dist/{db-7gbc53fd.js → db-88wcymxt.js} +1 -1
- package/dist/{db-t86xrnes.js → db-af9y8nkf.js} +3 -3
- package/dist/{dispatcher-64bdyhps.js → dispatcher-606ydx5v.js} +15 -15
- package/dist/{external-channel-e9nw24zv.js → external-channel-y8q5z8z2.js} +2 -2
- package/dist/{grok-adapter-v82srhrx.js → grok-adapter-39aha52t.js} +2 -2
- package/dist/{handlers-c9ajzhbn.js → handlers-rk9dqech.js} +18 -18
- package/dist/{heartbeat-2vqs1319.js → heartbeat-jfe9zfjr.js} +17 -17
- package/dist/{hook-jzpheegg.js → hook-b1mez1m8.js} +1 -1
- package/dist/{hook-01nk13wx.js → hook-ffevzed1.js} +1 -1
- package/dist/{http-fc1k6ec8.js → http-d5b44f1r.js} +36 -36
- package/dist/{index-q3t40ryv.js → index-4q3vj1yr.js} +15 -15
- package/dist/{index-sepja052.js → index-5en23s5z.js} +1 -1
- package/dist/{index-07ef7nkm.js → index-d2qeem97.js} +15 -15
- package/dist/{index-7yts8n9s.js → index-mj46rk9k.js} +17 -17
- package/dist/{index-zdycv3ja.js → index-nq8hhv28.js} +7 -7
- package/dist/{keepalive-5nh2rhmr.js → keepalive-wsby1btq.js} +3 -3
- package/dist/{lead-aayq64jn.js → lead-paa141j6.js} +22 -22
- package/dist/{maintenance-hjhzdt3k.js → maintenance-vnshcyv9.js} +1 -1
- package/dist/{oauth-refresh-sweep-y3n1knmd.js → oauth-refresh-sweep-d6sqn5np.js} +2 -2
- package/dist/{onboard-1zq3m7db.js → onboard-nr4xhpzy.js} +2 -2
- package/dist/{otel-impl-h9yyza47.js → otel-impl-ytn9wyv9.js} +1 -1
- package/dist/{pricing-refresh-nevhat8h.js → pricing-refresh-pva04zee.js} +3 -3
- package/dist/{rbac-roles-4y6ngvzq.js → rbac-roles-7rc1q3g4.js} +1 -1
- package/dist/{rbac-roles-exbg8xb6.js → rbac-roles-ff1ffmec.js} +2 -2
- package/dist/{render-v2-cfsh13z3.js → render-v2-pyfsg9zk.js} +3 -3
- package/dist/{resume-5s2t0dbs.js → resume-phj8xhqr.js} +5 -5
- package/dist/{session-access-dnbyp3cn.js → session-access-jzx2z9h5.js} +4 -4
- package/dist/{setup-1wj1y77j.js → setup-rfg0f0sg.js} +1 -1
- package/dist/{work-objects-7mg6216g.js → work-objects-44z2yjg8.js} +18 -18
- package/dist/{worker-nwb789mg.js → worker-zsfqzxhm.js} +22 -22
- package/openapi.json +1 -1
- package/package.json +1 -1
- package/src/gitlab/auth.ts +8 -2
- package/src/gitlab/handlers.ts +60 -12
- package/src/gitlab/index.ts +1 -1
- package/src/tests/gitlab-auth.test.ts +13 -5
- package/src/tests/gitlab-handlers.test.ts +270 -22
package/src/gitlab/handlers.ts
CHANGED
|
@@ -8,13 +8,21 @@
|
|
|
8
8
|
* - Detects bot mentions
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
failTask,
|
|
13
|
+
findActiveTasksByVcs,
|
|
14
|
+
findTaskByVcs,
|
|
15
|
+
getAllAgents,
|
|
16
|
+
incrKv,
|
|
17
|
+
upsertKv,
|
|
18
|
+
} from "../be/db";
|
|
12
19
|
import { renderIdentity, resolveIdentity } from "../be/identity";
|
|
13
20
|
import { findOrCreateUserByEmail, findUserByExternalId, linkIdentity } from "../be/users";
|
|
14
21
|
import { resolveTemplate } from "../prompts/resolver";
|
|
15
22
|
import { gitlabContextKey } from "../tasks/context-key";
|
|
16
23
|
import { createTaskWithSiblingAwareness } from "../tasks/sibling-awareness";
|
|
17
|
-
import {
|
|
24
|
+
import type { AgentTask } from "../types";
|
|
25
|
+
import { getGitLabBotName } from "./auth";
|
|
18
26
|
import { addGitLabNoteReaction, addGitLabReaction } from "./reactions";
|
|
19
27
|
import type { GitLabUser } from "./types";
|
|
20
28
|
// Side-effect import: registers all GitLab event templates in the in-memory registry
|
|
@@ -40,11 +48,37 @@ function isDuplicate(key: string): boolean {
|
|
|
40
48
|
|
|
41
49
|
function detectMention(text: string | null | undefined): boolean {
|
|
42
50
|
if (!text) return false;
|
|
43
|
-
return new RegExp(`@${
|
|
51
|
+
return new RegExp(`@${getGitLabBotName()}\\b`, "i").test(text);
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
function extractMentionContext(text: string): string {
|
|
47
|
-
return text.replace(new RegExp(`@${
|
|
55
|
+
return text.replace(new RegExp(`@${getGitLabBotName()}\\b`, "gi"), "").trim();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Legacy rows written before contextKey existed: scoped only on positive evidence.
|
|
59
|
+
const LEGACY_SCOPE = {
|
|
60
|
+
mr: {
|
|
61
|
+
taskTypes: new Set(["gitlab-mr", "gitlab-ci"]),
|
|
62
|
+
eventTypes: new Set(["merge_request", "note_on_mr", "pipeline"]),
|
|
63
|
+
},
|
|
64
|
+
issue: {
|
|
65
|
+
taskTypes: new Set(["gitlab-issue"]),
|
|
66
|
+
eventTypes: new Set(["issue", "note_on_issue"]),
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* GitLab issue and MR iids are separate per-project sequences, so a vcsNumber
|
|
72
|
+
* match alone hits both issue #N and MR !N. Match on the contextKey instead; a
|
|
73
|
+
* row without one counts only when its taskType or vcsEventType names the kind.
|
|
74
|
+
*/
|
|
75
|
+
function isScopedTask(task: AgentTask, kind: "mr" | "issue", contextKey: string): boolean {
|
|
76
|
+
if (task.contextKey) return task.contextKey === contextKey;
|
|
77
|
+
const legacy = LEGACY_SCOPE[kind];
|
|
78
|
+
return (
|
|
79
|
+
(task.taskType !== undefined && legacy.taskTypes.has(task.taskType)) ||
|
|
80
|
+
(task.vcsEventType !== undefined && legacy.eventTypes.has(task.vcsEventType))
|
|
81
|
+
);
|
|
48
82
|
}
|
|
49
83
|
|
|
50
84
|
async function findLeadAgent() {
|
|
@@ -206,12 +240,18 @@ export async function handleMergeRequest(
|
|
|
206
240
|
|
|
207
241
|
case "close":
|
|
208
242
|
case "merge": {
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
243
|
+
// Every active task, not only the newest: a note task is a child of the
|
|
244
|
+
// MR task, and failTask does not cascade up to the parent.
|
|
245
|
+
const reason = action === "merge" ? "MR was merged" : "MR was closed";
|
|
246
|
+
const mrContextKey = gitlabContextKey({
|
|
247
|
+
projectId: String(project.id),
|
|
248
|
+
kind: "mr",
|
|
249
|
+
iid: mr.iid,
|
|
250
|
+
});
|
|
251
|
+
const tasks = await findActiveTasksByVcs(repo, mr.iid, "gitlab");
|
|
252
|
+
for (const task of tasks.filter((t) => isScopedTask(t, "mr", mrContextKey))) {
|
|
253
|
+
console.log(`[GitLab] Cancelling task ${task.id} — ${reason}`);
|
|
254
|
+
await failTask(task.id, reason);
|
|
215
255
|
}
|
|
216
256
|
return { created: false };
|
|
217
257
|
}
|
|
@@ -262,7 +302,7 @@ export async function handleIssue(
|
|
|
262
302
|
if (!hasMention) {
|
|
263
303
|
// Check if bot is in assignees
|
|
264
304
|
const isBotAssigned = event.assignees?.some(
|
|
265
|
-
(a) => a.username.toLowerCase() ===
|
|
305
|
+
(a) => a.username.toLowerCase() === getGitLabBotName().toLowerCase(),
|
|
266
306
|
);
|
|
267
307
|
if (!isBotAssigned) {
|
|
268
308
|
console.log(`[GitLab] Issue opened without bot mention/assignment, skipping`);
|
|
@@ -318,7 +358,15 @@ export async function handleIssue(
|
|
|
318
358
|
}
|
|
319
359
|
|
|
320
360
|
case "close": {
|
|
321
|
-
const
|
|
361
|
+
const issueContextKey = gitlabContextKey({
|
|
362
|
+
projectId: String(project.id),
|
|
363
|
+
kind: "issue",
|
|
364
|
+
iid: issue.iid,
|
|
365
|
+
});
|
|
366
|
+
// Newest issue-scoped task only; MR !N shares the iid but not the issue.
|
|
367
|
+
const existingTask = (await findActiveTasksByVcs(repo, issue.iid, "gitlab")).find((t) =>
|
|
368
|
+
isScopedTask(t, "issue", issueContextKey),
|
|
369
|
+
);
|
|
322
370
|
if (existingTask) {
|
|
323
371
|
console.log(`[GitLab] Cancelling task ${existingTask.id} — issue closed`);
|
|
324
372
|
await failTask(existingTask.id, "Issue was closed");
|
package/src/gitlab/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
getGitLabBotName,
|
|
4
4
|
getGitLabToken,
|
|
5
5
|
getGitLabUrl,
|
|
6
6
|
initGitLab,
|
|
@@ -100,10 +100,18 @@ describe("GitLab auth", () => {
|
|
|
100
100
|
});
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
-
describe("
|
|
104
|
-
test("defaults to agent-swarm-bot", () => {
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
describe("getGitLabBotName", () => {
|
|
104
|
+
test("defaults to agent-swarm-bot and follows GITLAB_BOT_NAME", () => {
|
|
105
|
+
const saved = process.env.GITLAB_BOT_NAME;
|
|
106
|
+
try {
|
|
107
|
+
delete process.env.GITLAB_BOT_NAME;
|
|
108
|
+
expect(getGitLabBotName()).toBe("agent-swarm-bot");
|
|
109
|
+
process.env.GITLAB_BOT_NAME = "renamed-bot";
|
|
110
|
+
expect(getGitLabBotName()).toBe("renamed-bot");
|
|
111
|
+
} finally {
|
|
112
|
+
if (saved === undefined) delete process.env.GITLAB_BOT_NAME;
|
|
113
|
+
else process.env.GITLAB_BOT_NAME = saved;
|
|
114
|
+
}
|
|
107
115
|
});
|
|
108
116
|
});
|
|
109
117
|
});
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
initDb,
|
|
14
14
|
} from "../be/db";
|
|
15
15
|
import { findUserByExternalId, linkIdentity } from "../be/users";
|
|
16
|
-
import {
|
|
16
|
+
import { getGitLabBotName } from "../gitlab/auth";
|
|
17
17
|
import { handleIssue, handleMergeRequest, handleNote, handlePipeline } from "../gitlab/handlers";
|
|
18
18
|
import type { IssueEvent, MergeRequestEvent, NoteEvent, PipelineEvent } from "../gitlab/types";
|
|
19
19
|
|
|
@@ -37,7 +37,7 @@ function makeMREvent(overrides: Partial<MergeRequestEvent> = {}): MergeRequestEv
|
|
|
37
37
|
id: 100,
|
|
38
38
|
iid: 1,
|
|
39
39
|
title: "Test MR",
|
|
40
|
-
description: `@${
|
|
40
|
+
description: `@${getGitLabBotName()} please review`,
|
|
41
41
|
state: "opened",
|
|
42
42
|
action: "open",
|
|
43
43
|
source_branch: "feature",
|
|
@@ -66,7 +66,7 @@ function makeIssueEvent(overrides: Partial<IssueEvent> = {}): IssueEvent {
|
|
|
66
66
|
id: 200,
|
|
67
67
|
iid: 10,
|
|
68
68
|
title: "Test Issue",
|
|
69
|
-
description: `@${
|
|
69
|
+
description: `@${getGitLabBotName()} fix this`,
|
|
70
70
|
state: "opened",
|
|
71
71
|
action: "open",
|
|
72
72
|
url: "https://gitlab.com/group/project/-/issues/10",
|
|
@@ -90,7 +90,7 @@ function makeNoteEvent(overrides: Partial<NoteEvent> = {}): NoteEvent {
|
|
|
90
90
|
},
|
|
91
91
|
object_attributes: {
|
|
92
92
|
id: 300,
|
|
93
|
-
note: `@${
|
|
93
|
+
note: `@${getGitLabBotName()} can you help?`,
|
|
94
94
|
noteable_type: "MergeRequest",
|
|
95
95
|
noteable_id: 100,
|
|
96
96
|
url: "https://gitlab.com/group/project/-/merge_requests/1#note_300",
|
|
@@ -181,7 +181,7 @@ describe("handleMergeRequest", () => {
|
|
|
181
181
|
id: 101,
|
|
182
182
|
iid: 50,
|
|
183
183
|
title: "MR with mention",
|
|
184
|
-
description: `@${
|
|
184
|
+
description: `@${getGitLabBotName()} review this please`,
|
|
185
185
|
state: "opened",
|
|
186
186
|
action: "open",
|
|
187
187
|
source_branch: "feat-a",
|
|
@@ -302,13 +302,207 @@ describe("handleMergeRequest", () => {
|
|
|
302
302
|
expect(existing).toBeNull();
|
|
303
303
|
});
|
|
304
304
|
|
|
305
|
+
test("a merge cancels every active task for the MR, not only the newest", async () => {
|
|
306
|
+
const mrTask = await createTaskExtended("[GitLab MR #62] Test", {
|
|
307
|
+
source: "gitlab",
|
|
308
|
+
vcsProvider: "gitlab",
|
|
309
|
+
vcsRepo: "group/project",
|
|
310
|
+
vcsEventType: "merge_request",
|
|
311
|
+
vcsNumber: 62,
|
|
312
|
+
agentId: "lead-gl-001",
|
|
313
|
+
});
|
|
314
|
+
const noteTask = await createTaskExtended("[GitLab MR #62] Comment", {
|
|
315
|
+
source: "gitlab",
|
|
316
|
+
vcsProvider: "gitlab",
|
|
317
|
+
vcsRepo: "group/project",
|
|
318
|
+
vcsEventType: "note_on_mr",
|
|
319
|
+
vcsNumber: 62,
|
|
320
|
+
agentId: "lead-gl-001",
|
|
321
|
+
parentTaskId: mrTask.id,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const event = makeMREvent({
|
|
325
|
+
object_attributes: {
|
|
326
|
+
id: 105,
|
|
327
|
+
iid: 62,
|
|
328
|
+
title: "MR to merge",
|
|
329
|
+
description: null,
|
|
330
|
+
state: "merged",
|
|
331
|
+
action: "merge",
|
|
332
|
+
source_branch: "feat-e",
|
|
333
|
+
target_branch: "main",
|
|
334
|
+
url: "https://gitlab.com/group/project/-/merge_requests/62",
|
|
335
|
+
last_commit: null,
|
|
336
|
+
author_id: 1,
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
await handleMergeRequest(event);
|
|
340
|
+
|
|
341
|
+
for (const id of [mrTask.id, noteTask.id]) {
|
|
342
|
+
const task = await getTaskById(id);
|
|
343
|
+
expect(task?.status).toBe("failed");
|
|
344
|
+
expect(task?.failureReason).toBe("MR was merged");
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test("closing MR !5 leaves issue #5 and its notes active", async () => {
|
|
349
|
+
// GitLab issue and MR iids are separate sequences: issue #5 and MR !5 coexist.
|
|
350
|
+
const issue = await handleIssue(
|
|
351
|
+
makeIssueEvent({
|
|
352
|
+
object_attributes: {
|
|
353
|
+
id: 205,
|
|
354
|
+
iid: 5,
|
|
355
|
+
title: "Issue five",
|
|
356
|
+
description: `@${getGitLabBotName()} fix this`,
|
|
357
|
+
state: "opened",
|
|
358
|
+
action: "open",
|
|
359
|
+
url: "https://gitlab.com/group/project/-/issues/5",
|
|
360
|
+
author_id: 1,
|
|
361
|
+
},
|
|
362
|
+
}),
|
|
363
|
+
);
|
|
364
|
+
const issueNote = await handleNote(
|
|
365
|
+
makeNoteEvent({
|
|
366
|
+
object_attributes: {
|
|
367
|
+
id: 395,
|
|
368
|
+
note: `@${getGitLabBotName()} any update?`,
|
|
369
|
+
noteable_type: "Issue",
|
|
370
|
+
noteable_id: 205,
|
|
371
|
+
url: "https://gitlab.com/group/project/-/issues/5#note_395",
|
|
372
|
+
author_id: 1,
|
|
373
|
+
type: null,
|
|
374
|
+
},
|
|
375
|
+
merge_request: undefined,
|
|
376
|
+
issue: {
|
|
377
|
+
id: 205,
|
|
378
|
+
iid: 5,
|
|
379
|
+
title: "Issue five",
|
|
380
|
+
description: null,
|
|
381
|
+
state: "opened",
|
|
382
|
+
action: "open",
|
|
383
|
+
url: "https://gitlab.com/group/project/-/issues/5",
|
|
384
|
+
author_id: 1,
|
|
385
|
+
},
|
|
386
|
+
}),
|
|
387
|
+
);
|
|
388
|
+
const mr = await handleMergeRequest(
|
|
389
|
+
makeMREvent({
|
|
390
|
+
object_attributes: {
|
|
391
|
+
id: 155,
|
|
392
|
+
iid: 5,
|
|
393
|
+
title: "MR five",
|
|
394
|
+
description: `@${getGitLabBotName()} please review`,
|
|
395
|
+
state: "opened",
|
|
396
|
+
action: "open",
|
|
397
|
+
source_branch: "feat-five",
|
|
398
|
+
target_branch: "main",
|
|
399
|
+
url: "https://gitlab.com/group/project/-/merge_requests/5",
|
|
400
|
+
last_commit: null,
|
|
401
|
+
author_id: 1,
|
|
402
|
+
},
|
|
403
|
+
}),
|
|
404
|
+
);
|
|
405
|
+
const mrNote = await handleNote(
|
|
406
|
+
makeNoteEvent({
|
|
407
|
+
object_attributes: {
|
|
408
|
+
id: 396,
|
|
409
|
+
note: `@${getGitLabBotName()} looks good?`,
|
|
410
|
+
noteable_type: "MergeRequest",
|
|
411
|
+
noteable_id: 155,
|
|
412
|
+
url: "https://gitlab.com/group/project/-/merge_requests/5#note_396",
|
|
413
|
+
author_id: 1,
|
|
414
|
+
type: null,
|
|
415
|
+
},
|
|
416
|
+
merge_request: {
|
|
417
|
+
id: 155,
|
|
418
|
+
iid: 5,
|
|
419
|
+
title: "MR five",
|
|
420
|
+
description: null,
|
|
421
|
+
state: "opened",
|
|
422
|
+
action: "open",
|
|
423
|
+
source_branch: "feat-five",
|
|
424
|
+
target_branch: "main",
|
|
425
|
+
url: "https://gitlab.com/group/project/-/merge_requests/5",
|
|
426
|
+
last_commit: null,
|
|
427
|
+
author_id: 1,
|
|
428
|
+
},
|
|
429
|
+
}),
|
|
430
|
+
);
|
|
431
|
+
// A pre-contextKey issue row must not be swept up either.
|
|
432
|
+
const legacyIssue = await createTaskExtended("[GitLab Issue #5] Legacy", {
|
|
433
|
+
source: "gitlab",
|
|
434
|
+
vcsProvider: "gitlab",
|
|
435
|
+
vcsRepo: "group/project",
|
|
436
|
+
vcsEventType: "issue",
|
|
437
|
+
vcsNumber: 5,
|
|
438
|
+
agentId: "lead-gl-001",
|
|
439
|
+
});
|
|
440
|
+
for (const r of [issue, issueNote, mr, mrNote]) expect(r.created).toBe(true);
|
|
441
|
+
|
|
442
|
+
await handleMergeRequest(
|
|
443
|
+
makeMREvent({
|
|
444
|
+
object_attributes: {
|
|
445
|
+
id: 155,
|
|
446
|
+
iid: 5,
|
|
447
|
+
title: "MR five",
|
|
448
|
+
description: null,
|
|
449
|
+
state: "closed",
|
|
450
|
+
action: "close",
|
|
451
|
+
source_branch: "feat-five",
|
|
452
|
+
target_branch: "main",
|
|
453
|
+
url: "https://gitlab.com/group/project/-/merge_requests/5",
|
|
454
|
+
last_commit: null,
|
|
455
|
+
author_id: 1,
|
|
456
|
+
},
|
|
457
|
+
}),
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
for (const id of [mr.taskId!, mrNote.taskId!]) {
|
|
461
|
+
const task = await getTaskById(id);
|
|
462
|
+
expect(task?.status).toBe("failed");
|
|
463
|
+
expect(task?.failureReason).toBe("MR was closed");
|
|
464
|
+
}
|
|
465
|
+
for (const id of [issue.taskId!, issueNote.taskId!, legacyIssue.id]) {
|
|
466
|
+
const task = await getTaskById(id);
|
|
467
|
+
expect(task?.status).not.toBe("failed");
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
test("reads GITLAB_BOT_NAME at match time, so a config reload applies", async () => {
|
|
472
|
+
const mrEvent = (iid: number, description: string) =>
|
|
473
|
+
makeMREvent({
|
|
474
|
+
object_attributes: {
|
|
475
|
+
id: 1000 + iid,
|
|
476
|
+
iid,
|
|
477
|
+
title: "MR for renamed bot",
|
|
478
|
+
description,
|
|
479
|
+
state: "opened",
|
|
480
|
+
action: "open",
|
|
481
|
+
source_branch: "feat-renamed",
|
|
482
|
+
target_branch: "main",
|
|
483
|
+
url: `https://gitlab.com/group/project/-/merge_requests/${iid}`,
|
|
484
|
+
last_commit: null,
|
|
485
|
+
author_id: 1,
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
process.env.GITLAB_BOT_NAME = "renamed-bot";
|
|
489
|
+
try {
|
|
490
|
+
expect((await handleMergeRequest(mrEvent(63, "@renamed-bot review"))).created).toBe(true);
|
|
491
|
+
expect((await handleMergeRequest(mrEvent(64, "@agent-swarm-bot review"))).created).toBe(
|
|
492
|
+
false,
|
|
493
|
+
);
|
|
494
|
+
} finally {
|
|
495
|
+
delete process.env.GITLAB_BOT_NAME;
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
|
|
305
499
|
test("ignores update action when no active task", async () => {
|
|
306
500
|
const event = makeMREvent({
|
|
307
501
|
object_attributes: {
|
|
308
502
|
id: 105,
|
|
309
503
|
iid: 62,
|
|
310
504
|
title: "Updated MR",
|
|
311
|
-
description: `@${
|
|
505
|
+
description: `@${getGitLabBotName()} review`,
|
|
312
506
|
state: "opened",
|
|
313
507
|
action: "update",
|
|
314
508
|
source_branch: "feat-e",
|
|
@@ -329,7 +523,7 @@ describe("handleMergeRequest", () => {
|
|
|
329
523
|
id: 106,
|
|
330
524
|
iid: 70,
|
|
331
525
|
title: "Dedup MR",
|
|
332
|
-
description: `@${
|
|
526
|
+
description: `@${getGitLabBotName()} review`,
|
|
333
527
|
state: "opened",
|
|
334
528
|
action: "open",
|
|
335
529
|
source_branch: "feat-dedup",
|
|
@@ -360,7 +554,7 @@ describe("handleIssue", () => {
|
|
|
360
554
|
id: 201,
|
|
361
555
|
iid: 20,
|
|
362
556
|
title: "Issue with mention",
|
|
363
|
-
description: `@${
|
|
557
|
+
description: `@${getGitLabBotName()} implement this feature`,
|
|
364
558
|
state: "opened",
|
|
365
559
|
action: "open",
|
|
366
560
|
url: "https://gitlab.com/group/project/-/issues/20",
|
|
@@ -391,7 +585,7 @@ describe("handleIssue", () => {
|
|
|
391
585
|
url: "https://gitlab.com/group/project/-/issues/21",
|
|
392
586
|
author_id: 1,
|
|
393
587
|
},
|
|
394
|
-
assignees: [{ id: 99, name: "Bot", username:
|
|
588
|
+
assignees: [{ id: 99, name: "Bot", username: getGitLabBotName(), avatar_url: "" }],
|
|
395
589
|
});
|
|
396
590
|
|
|
397
591
|
const result = await handleIssue(event);
|
|
@@ -447,6 +641,60 @@ describe("handleIssue", () => {
|
|
|
447
641
|
const task = await findTaskByVcs("group/project", 30, "gitlab");
|
|
448
642
|
expect(task).toBeNull();
|
|
449
643
|
});
|
|
644
|
+
|
|
645
|
+
test("closing issue #6 fails the issue task, not the newer MR !6 task", async () => {
|
|
646
|
+
const issue = await handleIssue(
|
|
647
|
+
makeIssueEvent({
|
|
648
|
+
object_attributes: {
|
|
649
|
+
id: 206,
|
|
650
|
+
iid: 6,
|
|
651
|
+
title: "Issue six",
|
|
652
|
+
description: `@${getGitLabBotName()} fix this`,
|
|
653
|
+
state: "opened",
|
|
654
|
+
action: "open",
|
|
655
|
+
url: "https://gitlab.com/group/project/-/issues/6",
|
|
656
|
+
author_id: 1,
|
|
657
|
+
},
|
|
658
|
+
}),
|
|
659
|
+
);
|
|
660
|
+
// Created second, so it is the newest row for vcsNumber 6.
|
|
661
|
+
const mr = await handleMergeRequest(
|
|
662
|
+
makeMREvent({
|
|
663
|
+
object_attributes: {
|
|
664
|
+
id: 156,
|
|
665
|
+
iid: 6,
|
|
666
|
+
title: "MR six",
|
|
667
|
+
description: `@${getGitLabBotName()} please review`,
|
|
668
|
+
state: "opened",
|
|
669
|
+
action: "open",
|
|
670
|
+
source_branch: "feat-six",
|
|
671
|
+
target_branch: "main",
|
|
672
|
+
url: "https://gitlab.com/group/project/-/merge_requests/6",
|
|
673
|
+
last_commit: null,
|
|
674
|
+
author_id: 1,
|
|
675
|
+
},
|
|
676
|
+
}),
|
|
677
|
+
);
|
|
678
|
+
expect(issue.created && mr.created).toBe(true);
|
|
679
|
+
|
|
680
|
+
await handleIssue(
|
|
681
|
+
makeIssueEvent({
|
|
682
|
+
object_attributes: {
|
|
683
|
+
id: 206,
|
|
684
|
+
iid: 6,
|
|
685
|
+
title: "Issue six",
|
|
686
|
+
description: null,
|
|
687
|
+
state: "closed",
|
|
688
|
+
action: "close",
|
|
689
|
+
url: "https://gitlab.com/group/project/-/issues/6",
|
|
690
|
+
author_id: 1,
|
|
691
|
+
},
|
|
692
|
+
}),
|
|
693
|
+
);
|
|
694
|
+
|
|
695
|
+
expect((await getTaskById(issue.taskId!))?.failureReason).toBe("Issue was closed");
|
|
696
|
+
expect((await getTaskById(mr.taskId!))?.status).not.toBe("failed");
|
|
697
|
+
});
|
|
450
698
|
});
|
|
451
699
|
|
|
452
700
|
// ═══════════════════════════════════════════════════════
|
|
@@ -458,7 +706,7 @@ describe("handleNote", () => {
|
|
|
458
706
|
const event = makeNoteEvent({
|
|
459
707
|
object_attributes: {
|
|
460
708
|
id: 301,
|
|
461
|
-
note: `@${
|
|
709
|
+
note: `@${getGitLabBotName()} please check this code`,
|
|
462
710
|
noteable_type: "MergeRequest",
|
|
463
711
|
noteable_id: 100,
|
|
464
712
|
url: "https://gitlab.com/group/project/-/merge_requests/1#note_301",
|
|
@@ -493,7 +741,7 @@ describe("handleNote", () => {
|
|
|
493
741
|
const event = makeNoteEvent({
|
|
494
742
|
object_attributes: {
|
|
495
743
|
id: 302,
|
|
496
|
-
note: `@${
|
|
744
|
+
note: `@${getGitLabBotName()} what do you think?`,
|
|
497
745
|
noteable_type: "Issue",
|
|
498
746
|
noteable_id: 200,
|
|
499
747
|
url: "https://gitlab.com/group/project/-/issues/40#note_302",
|
|
@@ -542,7 +790,7 @@ describe("handleNote", () => {
|
|
|
542
790
|
const event = makeNoteEvent({
|
|
543
791
|
object_attributes: {
|
|
544
792
|
id: 304,
|
|
545
|
-
note: `@${
|
|
793
|
+
note: `@${getGitLabBotName()} review this commit`,
|
|
546
794
|
noteable_type: "Commit",
|
|
547
795
|
noteable_id: 999,
|
|
548
796
|
url: "https://gitlab.com/group/project/-/commit/abc#note_304",
|
|
@@ -572,7 +820,7 @@ describe("handleNote", () => {
|
|
|
572
820
|
const event = makeNoteEvent({
|
|
573
821
|
object_attributes: {
|
|
574
822
|
id: 305,
|
|
575
|
-
note: `@${
|
|
823
|
+
note: `@${getGitLabBotName()} also fix the linting`,
|
|
576
824
|
noteable_type: "MergeRequest",
|
|
577
825
|
noteable_id: 100,
|
|
578
826
|
url: "https://gitlab.com/group/project/-/merge_requests/85#note_305",
|
|
@@ -742,7 +990,7 @@ describe("identity resolution — MR handler", () => {
|
|
|
742
990
|
id: 901,
|
|
743
991
|
iid: 901,
|
|
744
992
|
title: "Known MR",
|
|
745
|
-
description: `@${
|
|
993
|
+
description: `@${getGitLabBotName()} review`,
|
|
746
994
|
state: "opened",
|
|
747
995
|
action: "open",
|
|
748
996
|
source_branch: "feat-known",
|
|
@@ -779,7 +1027,7 @@ describe("identity resolution — MR handler", () => {
|
|
|
779
1027
|
id: 902,
|
|
780
1028
|
iid: 902,
|
|
781
1029
|
title: "Inline-email MR",
|
|
782
|
-
description: `@${
|
|
1030
|
+
description: `@${getGitLabBotName()} please`,
|
|
783
1031
|
state: "opened",
|
|
784
1032
|
action: "open",
|
|
785
1033
|
source_branch: "feat-inline",
|
|
@@ -816,7 +1064,7 @@ describe("identity resolution — MR handler", () => {
|
|
|
816
1064
|
id: 903,
|
|
817
1065
|
iid: 903,
|
|
818
1066
|
title: "Ghost MR",
|
|
819
|
-
description: `@${
|
|
1067
|
+
description: `@${getGitLabBotName()} ping`,
|
|
820
1068
|
state: "opened",
|
|
821
1069
|
action: "open",
|
|
822
1070
|
source_branch: "feat-ghost",
|
|
@@ -850,7 +1098,7 @@ describe("identity resolution — MR handler", () => {
|
|
|
850
1098
|
id: 910 + i,
|
|
851
1099
|
iid: 910 + i,
|
|
852
1100
|
title: `Ghost MR ${i}`,
|
|
853
|
-
description: `@${
|
|
1101
|
+
description: `@${getGitLabBotName()} ping`,
|
|
854
1102
|
state: "opened",
|
|
855
1103
|
action: "open",
|
|
856
1104
|
source_branch: `feat-g-${i}`,
|
|
@@ -879,7 +1127,7 @@ describe("identity resolution — MR handler", () => {
|
|
|
879
1127
|
id: 920,
|
|
880
1128
|
iid: 920,
|
|
881
1129
|
title: "Empty email MR",
|
|
882
|
-
description: `@${
|
|
1130
|
+
description: `@${getGitLabBotName()} review`,
|
|
883
1131
|
state: "opened",
|
|
884
1132
|
action: "open",
|
|
885
1133
|
source_branch: "feat-empty",
|
|
@@ -914,7 +1162,7 @@ describe("identity resolution — Issue handler", () => {
|
|
|
914
1162
|
id: 950,
|
|
915
1163
|
iid: 950,
|
|
916
1164
|
title: "Ghost issue",
|
|
917
|
-
description: `@${
|
|
1165
|
+
description: `@${getGitLabBotName()} fix`,
|
|
918
1166
|
state: "opened",
|
|
919
1167
|
action: "open",
|
|
920
1168
|
url: "https://gitlab.com/group/project/-/issues/950",
|
|
@@ -942,7 +1190,7 @@ describe("identity resolution — Note handler", () => {
|
|
|
942
1190
|
});
|
|
943
1191
|
|
|
944
1192
|
test("unknown user WITHOUT email → unmapped entry tagged 'note'", async () => {
|
|
945
|
-
const noteBody = `@${
|
|
1193
|
+
const noteBody = `@${getGitLabBotName()} can you look at this comment that is somewhat longer than usual to verify the slice(0,100) truncation downstream`;
|
|
946
1194
|
const event = makeNoteEvent({
|
|
947
1195
|
user: { id: 31, name: "Note Ghost", username: "noteghost", avatar_url: "" },
|
|
948
1196
|
object_attributes: {
|
|
@@ -976,7 +1224,7 @@ describe("identity resolution — Note handler", () => {
|
|
|
976
1224
|
expect(meta).not.toBeNull();
|
|
977
1225
|
expect(meta?.sampleEventType).toBe("note");
|
|
978
1226
|
expect((meta?.sampleContext as string).length).toBeLessThanOrEqual(100);
|
|
979
|
-
expect((meta?.sampleContext as string).startsWith(`@${
|
|
1227
|
+
expect((meta?.sampleContext as string).startsWith(`@${getGitLabBotName()}`)).toBe(true);
|
|
980
1228
|
expect(await getUnmappedCount("noteghost")).toBe(1);
|
|
981
1229
|
|
|
982
1230
|
// The dead-coded `_requestedByUserId` is now wired: undefined for an
|
|
@@ -996,7 +1244,7 @@ describe("identity resolution — Note handler", () => {
|
|
|
996
1244
|
user: { id: 32, name: "Known Commenter", username: "knowncommenter", avatar_url: "" },
|
|
997
1245
|
object_attributes: {
|
|
998
1246
|
id: 961,
|
|
999
|
-
note: `@${
|
|
1247
|
+
note: `@${getGitLabBotName()} please take a look`,
|
|
1000
1248
|
noteable_type: "MergeRequest",
|
|
1001
1249
|
noteable_id: 101,
|
|
1002
1250
|
url: "https://gitlab.com/group/project/-/merge_requests/2#note_961",
|