@desplega.ai/agent-swarm 1.55.0 → 1.56.3

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.
@@ -1,6 +1,12 @@
1
1
  import { createTaskExtended, failTask, findTaskByVcs, getAllAgents } from "../be/db";
2
2
  import { resolveTemplate } from "../prompts/resolver";
3
- import { detectMention, extractMentionContext, GITHUB_BOT_NAME, isBotAssignee } from "./mentions";
3
+ import {
4
+ detectMention,
5
+ extractMentionContext,
6
+ GITHUB_BOT_NAME,
7
+ isBotAssignee,
8
+ isSwarmLabel,
9
+ } from "./mentions";
4
10
  import { addIssueReaction, addReaction } from "./reactions";
5
11
  // Side-effect import: registers all GitHub event templates in the in-memory registry
6
12
  import "./templates";
@@ -315,42 +321,32 @@ export async function handlePullRequest(
315
321
  return { created: false };
316
322
  }
317
323
 
318
- // Handle closed action - PR was merged or closed without merge
319
- if (action === "closed") {
320
- // Find the related task
321
- const task = findTaskByVcs(repository.full_name, pr.number);
322
- if (!task) {
323
- // No task for this PR, nothing to notify
324
+ // Handle labeled action - swarm label added to PR
325
+ if (action === "labeled") {
326
+ const labelName = event.label?.name;
327
+ if (!labelName || !isSwarmLabel(labelName)) {
324
328
  return { created: false };
325
329
  }
326
330
 
327
331
  // Deduplicate
328
- const eventKey = `pr-closed:${repository.full_name}:${pr.number}`;
332
+ const eventKey = `pr-labeled:${repository.full_name}:${pr.number}:${labelName}`;
329
333
  if (isDuplicate(eventKey)) {
330
334
  return { created: false };
331
335
  }
332
336
 
333
337
  const lead = findLeadAgent();
334
- const wasMerged = pr.merged;
335
- const emoji = wasMerged ? "🎉" : "❌";
336
- const status = wasMerged ? "MERGED" : "CLOSED";
337
- const mergedBy = wasMerged && pr.merged_by ? ` by ${pr.merged_by.login}` : "";
338
- const followUpSuggestion = wasMerged
339
- ? "💡 PR successfully merged! Update any related issues or documentation."
340
- : "💡 PR was closed without merging. Review if follow-up is needed.";
341
-
342
338
  const result = resolveTemplate(
343
- "github.pull_request.closed",
339
+ "github.pull_request.labeled",
344
340
  {
345
- status_emoji: emoji,
346
341
  pr_number: pr.number,
347
- status,
348
- merged_by: mergedBy,
349
342
  pr_title: pr.title,
343
+ label_name: labelName,
344
+ sender_login: sender.login,
350
345
  repo_full_name: repository.full_name,
346
+ head_ref: pr.head.ref,
347
+ base_ref: pr.base.ref,
351
348
  pr_url: pr.html_url,
352
- related_task_id: task.id,
353
- follow_up_suggestion: followUpSuggestion,
349
+ context: pr.body || pr.title,
354
350
  },
355
351
  { agentId: lead?.id, repoId: repository.full_name },
356
352
  );
@@ -359,11 +355,11 @@ export async function handlePullRequest(
359
355
  return { created: false };
360
356
  }
361
357
 
362
- const notifyTask = createTaskExtended(result.text, {
358
+ const task = createTaskExtended(result.text, {
363
359
  agentId: lead?.id ?? "",
364
360
  source: "github",
365
361
  vcsProvider: "github",
366
- taskType: "github-pr-status",
362
+ taskType: "github-pr",
367
363
  vcsRepo: repository.full_name,
368
364
  vcsEventType: "pull_request",
369
365
  vcsNumber: pr.number,
@@ -371,64 +367,37 @@ export async function handlePullRequest(
371
367
  vcsUrl: pr.html_url,
372
368
  });
373
369
 
374
- console.log(
375
- `[GitHub] Created task ${notifyTask.id} for PR #${pr.number} (${status}) -> ${lead?.name ?? "unassigned"}`,
376
- );
377
-
378
- return { created: true, taskId: notifyTask.id };
379
- }
380
-
381
- // Handle synchronize action - new commits pushed to PR
382
- if (action === "synchronize") {
383
- // Find the related task
384
- const task = findTaskByVcs(repository.full_name, pr.number);
385
- if (!task) {
386
- // No task for this PR, nothing to notify
387
- return { created: false };
388
- }
389
-
390
- // Deduplicate using SHA to avoid duplicate notifications for same push
391
- const eventKey = `pr-sync:${repository.full_name}:${pr.number}:${pr.head.sha}`;
392
- if (isDuplicate(eventKey)) {
393
- return { created: false };
370
+ if (lead) {
371
+ console.log(
372
+ `[GitHub] Created task ${task.id} for PR #${pr.number} (labeled: ${labelName}) -> ${lead.name}`,
373
+ );
374
+ } else {
375
+ console.log(
376
+ `[GitHub] Created unassigned task ${task.id} for PR #${pr.number} (labeled: ${labelName}, no lead available)`,
377
+ );
394
378
  }
395
379
 
396
- const lead = findLeadAgent();
397
- const result = resolveTemplate(
398
- "github.pull_request.synchronize",
399
- {
400
- pr_number: pr.number,
401
- pr_title: pr.title,
402
- repo_full_name: repository.full_name,
403
- head_ref: pr.head.ref,
404
- head_sha_short: pr.head.sha.substring(0, 7),
405
- pr_url: pr.html_url,
406
- related_task_id: task.id,
407
- },
408
- { agentId: lead?.id, repoId: repository.full_name },
409
- );
410
-
411
- if (result.skipped) {
412
- return { created: false };
380
+ if (installation?.id) {
381
+ addIssueReaction(repository.full_name, pr.number, "eyes", installation.id);
413
382
  }
414
383
 
415
- const notifyTask = createTaskExtended(result.text, {
416
- agentId: lead?.id ?? "",
417
- source: "github",
418
- vcsProvider: "github",
419
- taskType: "github-pr-update",
420
- vcsRepo: repository.full_name,
421
- vcsEventType: "pull_request",
422
- vcsNumber: pr.number,
423
- vcsAuthor: sender.login,
424
- vcsUrl: pr.html_url,
425
- });
384
+ return { created: true, taskId: task.id };
385
+ }
426
386
 
387
+ // Suppressed: see thoughts/taras/plans/2026-03-30-github-event-safety-defaults.md
388
+ if (action === "closed") {
427
389
  console.log(
428
- `[GitHub] Created task ${notifyTask.id} for PR #${pr.number} (synchronize) -> ${lead?.name ?? "unassigned"}`,
390
+ `[GitHub:suppressed] pull_request.closed on ${repository.full_name}#${pr.number} lifecycle events disabled by default`,
429
391
  );
392
+ return { created: false };
393
+ }
430
394
 
431
- return { created: true, taskId: notifyTask.id };
395
+ // Suppressed: see thoughts/taras/plans/2026-03-30-github-event-safety-defaults.md
396
+ if (action === "synchronize") {
397
+ console.log(
398
+ `[GitHub:suppressed] pull_request.synchronize on ${repository.full_name}#${pr.number} — lifecycle events disabled by default`,
399
+ );
400
+ return { created: false };
432
401
  }
433
402
 
434
403
  // Only handle opened/edited actions for mention-based flow
@@ -595,6 +564,67 @@ export async function handleIssue(
595
564
  return { created: false };
596
565
  }
597
566
 
567
+ // Handle labeled action - swarm label added to issue
568
+ if (action === "labeled") {
569
+ const labelName = event.label?.name;
570
+ if (!labelName || !isSwarmLabel(labelName)) {
571
+ return { created: false };
572
+ }
573
+
574
+ // Deduplicate
575
+ const eventKey = `issue-labeled:${repository.full_name}:${issue.number}:${labelName}`;
576
+ if (isDuplicate(eventKey)) {
577
+ return { created: false };
578
+ }
579
+
580
+ const lead = findLeadAgent();
581
+ const result = resolveTemplate(
582
+ "github.issue.labeled",
583
+ {
584
+ issue_number: issue.number,
585
+ issue_title: issue.title,
586
+ label_name: labelName,
587
+ sender_login: sender.login,
588
+ repo_full_name: repository.full_name,
589
+ issue_url: issue.html_url,
590
+ context: issue.body || issue.title,
591
+ },
592
+ { agentId: lead?.id, repoId: repository.full_name },
593
+ );
594
+
595
+ if (result.skipped) {
596
+ return { created: false };
597
+ }
598
+
599
+ const task = createTaskExtended(result.text, {
600
+ agentId: lead?.id ?? "",
601
+ source: "github",
602
+ vcsProvider: "github",
603
+ taskType: "github-issue",
604
+ vcsRepo: repository.full_name,
605
+ vcsEventType: "issues",
606
+ vcsNumber: issue.number,
607
+ vcsAuthor: sender.login,
608
+ vcsUrl: issue.html_url,
609
+ });
610
+
611
+ if (lead) {
612
+ console.log(
613
+ `[GitHub] Created task ${task.id} for issue #${issue.number} (labeled: ${labelName}) -> ${lead.name}`,
614
+ );
615
+ } else {
616
+ console.log(
617
+ `[GitHub] Created unassigned task ${task.id} for issue #${issue.number} (labeled: ${labelName}, no lead available)`,
618
+ );
619
+ }
620
+
621
+ if (installation?.id) {
622
+ addIssueReaction(repository.full_name, issue.number, "eyes", installation.id);
623
+ }
624
+
625
+ return { created: true, taskId: task.id };
626
+ }
627
+
598
628
  // Only handle opened/edited actions for mention-based flow
599
629
  if (action !== "opened" && action !== "edited") {
600
630
  return { created: false };
@@ -770,103 +800,13 @@ export async function handleComment(
770
800
  export async function handlePullRequestReview(
771
801
  event: PullRequestReviewEvent,
772
802
  ): Promise<{ created: boolean; taskId?: string }> {
773
- const { action, review, pull_request: pr, repository, sender, installation } = event;
803
+ const { action, pull_request: pr, repository } = event;
774
804
 
775
- // Only handle submitted reviews (the most important action)
776
- // Edited reviews are less common and dismissed is handled by the state
777
- if (action !== "submitted") {
778
- return { created: false };
779
- }
780
-
781
- // Skip "commented" reviews that are empty - these are often just line comments
782
- // without an overall review body
783
- if (review.state === "commented" && !review.body) {
784
- return { created: false };
785
- }
786
-
787
- // Deduplicate
788
- const eventKey = `pr-review:${repository.full_name}:${pr.number}:${review.id}`;
789
- if (isDuplicate(eventKey)) {
790
- return { created: false };
791
- }
792
-
793
- // Find any existing task for this PR
794
- const existingTask = findTaskByVcs(repository.full_name, pr.number);
795
-
796
- // Only notify for PRs where bot is creator or already has a task
797
- const isBotCreator = isBotAssignee(pr.user.login);
798
- if (!isBotCreator && !existingTask) {
799
- return { created: false };
800
- }
801
-
802
- // Find lead agent for new task
803
- const lead = findLeadAgent();
804
-
805
- // Get review state info
806
- const { emoji, label } = getReviewStateInfo(review.state);
807
-
808
- // Build task description
809
- const reviewBodySection = review.body ? `\n\nReview Comment:\n${review.body}` : "";
810
- const relatedTaskSection = existingTask
811
- ? `Related task: ${existingTask.id}\n🔀 Consider routing to the same agent working on the related task.\n`
812
- : "";
813
- const reviewSuggestions =
814
- review.state === "approved"
815
- ? "💡 Suggested: Merge the PR or wait for additional reviews"
816
- : review.state === "changes_requested"
817
- ? "💡 Suggested: Address the requested changes and update the PR"
818
- : "💡 Suggested: Review the feedback and respond if needed";
819
-
820
- const result = resolveTemplate(
821
- "github.pull_request.review_submitted",
822
- {
823
- review_emoji: emoji,
824
- pr_number: pr.number,
825
- review_label: label,
826
- pr_title: pr.title,
827
- sender_login: sender.login,
828
- repo_full_name: repository.full_name,
829
- review_url: review.html_url,
830
- review_body_section: reviewBodySection,
831
- related_task_section: relatedTaskSection,
832
- review_suggestions: reviewSuggestions,
833
- },
834
- { agentId: lead?.id, repoId: repository.full_name },
805
+ // Suppressed: see thoughts/taras/plans/2026-03-30-github-event-safety-defaults.md
806
+ console.log(
807
+ `[GitHub:suppressed] pull_request_review.${action} on ${repository.full_name}#${pr.number} — review events disabled by default`,
835
808
  );
836
-
837
- if (result.skipped) {
838
- return { created: false };
839
- }
840
-
841
- // Create task (assigned to lead if available, otherwise unassigned)
842
- const task = createTaskExtended(result.text, {
843
- agentId: lead?.id ?? "",
844
- source: "github",
845
- vcsProvider: "github",
846
- taskType: "github-review",
847
- vcsRepo: repository.full_name,
848
- vcsEventType: "pull_request_review",
849
- vcsNumber: pr.number,
850
- vcsAuthor: sender.login,
851
- vcsUrl: review.html_url,
852
- });
853
-
854
- if (lead) {
855
- console.log(
856
- `[GitHub] Created task ${task.id} for PR #${pr.number} review (${review.state}) -> ${lead.name}`,
857
- );
858
- } else {
859
- console.log(
860
- `[GitHub] Created unassigned task ${task.id} for PR #${pr.number} review (${review.state}, no lead available)`,
861
- );
862
- }
863
-
864
- // Add reaction to acknowledge the review
865
- if (installation?.id) {
866
- addIssueReaction(repository.full_name, pr.number, "eyes", installation.id);
867
- }
868
-
869
- return { created: true, taskId: task.id };
809
+ return { created: false };
870
810
  }
871
811
 
872
812
  /**
@@ -879,89 +819,12 @@ export async function handleCheckRun(
879
819
  ): Promise<{ created: boolean; taskId?: string }> {
880
820
  const { action, check_run, repository } = event;
881
821
 
882
- // Only handle completed check runs
883
- if (action !== "completed") {
884
- return { created: false };
885
- }
886
-
887
- // Only notify on failure or action_required - success is less critical
888
- // Skip neutral/skipped/cancelled as they're usually not actionable
889
- const conclusion = check_run.conclusion;
890
- if (conclusion !== "failure" && conclusion !== "action_required") {
891
- return { created: false };
892
- }
893
-
894
- // Must be associated with at least one PR
895
- if (!check_run.pull_requests || check_run.pull_requests.length === 0) {
896
- return { created: false };
897
- }
898
-
899
- // Check if we have a task for any of these PRs
900
- let relatedTask = null;
901
- let prNumber = 0;
902
- for (const pr of check_run.pull_requests) {
903
- const task = findTaskByVcs(repository.full_name, pr.number);
904
- if (task) {
905
- relatedTask = task;
906
- prNumber = pr.number;
907
- break;
908
- }
909
- }
910
-
911
- if (!relatedTask) {
912
- // No task for any of the associated PRs
913
- return { created: false };
914
- }
915
-
916
- // Deduplicate
917
- const eventKey = `check-run:${repository.full_name}:${check_run.id}`;
918
- if (isDuplicate(eventKey)) {
919
- return { created: false };
920
- }
921
-
922
- const lead = findLeadAgent();
923
- const { emoji, label } = getCheckConclusionInfo(conclusion);
924
-
925
- const outputSummarySection = check_run.output.summary
926
- ? `\n\nSummary:\n${check_run.output.summary.substring(0, 500)}`
927
- : "";
928
-
929
- const result = resolveTemplate(
930
- "github.check_run.failed",
931
- {
932
- conclusion_emoji: emoji,
933
- pr_number: prNumber,
934
- check_name: check_run.name,
935
- conclusion_label: label,
936
- repo_full_name: repository.full_name,
937
- check_url: check_run.html_url,
938
- output_summary_section: outputSummarySection,
939
- related_task_id: relatedTask.id,
940
- },
941
- { agentId: lead?.id, repoId: repository.full_name },
942
- );
943
-
944
- if (result.skipped) {
945
- return { created: false };
946
- }
947
-
948
- const task = createTaskExtended(result.text, {
949
- agentId: lead?.id ?? "",
950
- source: "github",
951
- vcsProvider: "github",
952
- taskType: "github-ci",
953
- vcsRepo: repository.full_name,
954
- vcsEventType: "check_run",
955
- vcsNumber: prNumber,
956
- vcsAuthor: "",
957
- vcsUrl: check_run.html_url,
958
- });
959
-
822
+ // Suppressed: see thoughts/taras/plans/2026-03-30-github-event-safety-defaults.md
823
+ const conclusion = check_run.conclusion ?? "unknown";
960
824
  console.log(
961
- `[GitHub] Created task ${task.id} for check_run ${check_run.name} (${conclusion}) on PR #${prNumber} -> ${lead?.name ?? "unassigned"}`,
825
+ `[GitHub:suppressed] check_run.${action} (${conclusion}) on ${repository.full_name} CI events disabled by default`,
962
826
  );
963
-
964
- return { created: true, taskId: task.id };
827
+ return { created: false };
965
828
  }
966
829
 
967
830
  /**
@@ -974,84 +837,12 @@ export async function handleCheckSuite(
974
837
  ): Promise<{ created: boolean; taskId?: string }> {
975
838
  const { action, check_suite, repository } = event;
976
839
 
977
- // Only handle completed check suites
978
- if (action !== "completed") {
979
- return { created: false };
980
- }
981
-
982
- // Only notify on failure - success notifications would be too noisy
983
- const conclusion = check_suite.conclusion;
984
- if (conclusion !== "failure") {
985
- return { created: false };
986
- }
987
-
988
- // Must be associated with at least one PR
989
- if (!check_suite.pull_requests || check_suite.pull_requests.length === 0) {
990
- return { created: false };
991
- }
992
-
993
- // Check if we have a task for any of these PRs
994
- let relatedTask = null;
995
- let prNumber = 0;
996
- for (const pr of check_suite.pull_requests) {
997
- const task = findTaskByVcs(repository.full_name, pr.number);
998
- if (task) {
999
- relatedTask = task;
1000
- prNumber = pr.number;
1001
- break;
1002
- }
1003
- }
1004
-
1005
- if (!relatedTask) {
1006
- // No task for any of the associated PRs
1007
- return { created: false };
1008
- }
1009
-
1010
- // Deduplicate
1011
- const eventKey = `check-suite:${repository.full_name}:${check_suite.id}`;
1012
- if (isDuplicate(eventKey)) {
1013
- return { created: false };
1014
- }
1015
-
1016
- const lead = findLeadAgent();
1017
- const { emoji, label } = getCheckConclusionInfo(conclusion);
1018
- const branch = check_suite.head_branch ?? "unknown";
1019
-
1020
- const result = resolveTemplate(
1021
- "github.check_suite.failed",
1022
- {
1023
- conclusion_emoji: emoji,
1024
- pr_number: prNumber,
1025
- conclusion_label: label,
1026
- repo_full_name: repository.full_name,
1027
- branch,
1028
- head_sha_short: check_suite.head_sha.substring(0, 7),
1029
- related_task_id: relatedTask.id,
1030
- },
1031
- { agentId: lead?.id, repoId: repository.full_name },
1032
- );
1033
-
1034
- if (result.skipped) {
1035
- return { created: false };
1036
- }
1037
-
1038
- const task = createTaskExtended(result.text, {
1039
- agentId: lead?.id ?? "",
1040
- source: "github",
1041
- vcsProvider: "github",
1042
- taskType: "github-ci",
1043
- vcsRepo: repository.full_name,
1044
- vcsEventType: "check_suite",
1045
- vcsNumber: prNumber,
1046
- vcsAuthor: "",
1047
- vcsUrl: repository.html_url,
1048
- });
1049
-
840
+ // Suppressed: see thoughts/taras/plans/2026-03-30-github-event-safety-defaults.md
841
+ const conclusion = check_suite.conclusion ?? "unknown";
1050
842
  console.log(
1051
- `[GitHub] Created task ${task.id} for check_suite (${conclusion}) on PR #${prNumber} -> ${lead?.name ?? "unassigned"}`,
843
+ `[GitHub:suppressed] check_suite.${action} (${conclusion}) on ${repository.full_name} CI events disabled by default`,
1052
844
  );
1053
-
1054
- return { created: true, taskId: task.id };
845
+ return { created: false };
1055
846
  }
1056
847
 
1057
848
  /**
@@ -1065,87 +856,12 @@ export async function handleCheckSuite(
1065
856
  export async function handleWorkflowRun(
1066
857
  event: WorkflowRunEvent,
1067
858
  ): Promise<{ created: boolean; taskId?: string }> {
1068
- const { action, workflow_run, workflow, repository } = event;
1069
-
1070
- // Only handle completed workflow runs
1071
- if (action !== "completed") {
1072
- return { created: false };
1073
- }
1074
-
1075
- // Only notify on failure - success notifications would be too noisy
1076
- const conclusion = workflow_run.conclusion;
1077
- if (conclusion !== "failure") {
1078
- return { created: false };
1079
- }
1080
-
1081
- // Must be associated with at least one PR
1082
- if (!workflow_run.pull_requests || workflow_run.pull_requests.length === 0) {
1083
- return { created: false };
1084
- }
1085
-
1086
- // Check if we have a task for any of these PRs
1087
- let relatedTask = null;
1088
- let prNumber = 0;
1089
- for (const pr of workflow_run.pull_requests) {
1090
- const task = findTaskByVcs(repository.full_name, pr.number);
1091
- if (task) {
1092
- relatedTask = task;
1093
- prNumber = pr.number;
1094
- break;
1095
- }
1096
- }
1097
-
1098
- if (!relatedTask) {
1099
- // No task for any of the associated PRs
1100
- return { created: false };
1101
- }
1102
-
1103
- // Deduplicate
1104
- const eventKey = `workflow-run:${repository.full_name}:${workflow_run.id}`;
1105
- if (isDuplicate(eventKey)) {
1106
- return { created: false };
1107
- }
1108
-
1109
- const lead = findLeadAgent();
1110
- const { emoji, label } = getCheckConclusionInfo(conclusion);
1111
-
1112
- const result = resolveTemplate(
1113
- "github.workflow_run.failed",
1114
- {
1115
- conclusion_emoji: emoji,
1116
- pr_number: prNumber,
1117
- workflow_run_name: workflow_run.name,
1118
- conclusion_label: label,
1119
- repo_full_name: repository.full_name,
1120
- workflow_name: workflow.name,
1121
- run_number: workflow_run.run_number,
1122
- head_branch: workflow_run.head_branch,
1123
- trigger_event: workflow_run.event,
1124
- logs_url: workflow_run.html_url,
1125
- related_task_id: relatedTask.id,
1126
- },
1127
- { agentId: lead?.id, repoId: repository.full_name },
1128
- );
1129
-
1130
- if (result.skipped) {
1131
- return { created: false };
1132
- }
1133
-
1134
- const task = createTaskExtended(result.text, {
1135
- agentId: lead?.id ?? "",
1136
- source: "github",
1137
- vcsProvider: "github",
1138
- taskType: "github-ci",
1139
- vcsRepo: repository.full_name,
1140
- vcsEventType: "workflow_run",
1141
- vcsNumber: prNumber,
1142
- vcsAuthor: "",
1143
- vcsUrl: workflow_run.html_url,
1144
- });
859
+ const { action, workflow_run, repository } = event;
1145
860
 
861
+ // Suppressed: see thoughts/taras/plans/2026-03-30-github-event-safety-defaults.md
862
+ const conclusion = workflow_run.conclusion ?? "unknown";
1146
863
  console.log(
1147
- `[GitHub] Created task ${task.id} for workflow_run "${workflow_run.name}" (${conclusion}) on PR #${prNumber} -> ${lead?.name ?? "unassigned"}`,
864
+ `[GitHub:suppressed] workflow_run.${action} (${conclusion}) on ${repository.full_name} CI events disabled by default`,
1148
865
  );
1149
-
1150
- return { created: true, taskId: task.id };
866
+ return { created: false };
1151
867
  }
@@ -17,7 +17,14 @@ export {
17
17
  handlePullRequestReview,
18
18
  handleWorkflowRun,
19
19
  } from "./handlers";
20
- export { detectMention, extractMentionContext, GITHUB_BOT_NAME, isBotAssignee } from "./mentions";
20
+ export {
21
+ detectMention,
22
+ extractMentionContext,
23
+ GITHUB_BOT_NAME,
24
+ GITHUB_EVENT_LABELS,
25
+ isBotAssignee,
26
+ isSwarmLabel,
27
+ } from "./mentions";
21
28
  export type { ReactionType } from "./reactions";
22
29
  export { addIssueReaction, addReaction, postComment } from "./reactions";
23
30
  export type {
@@ -1,6 +1,16 @@
1
1
  // Bot name for @mentions (can be overridden via env)
2
2
  export const GITHUB_BOT_NAME = process.env.GITHUB_BOT_NAME || "agent-swarm-bot";
3
3
 
4
+ // Labels that trigger agent action on PR/issue label events (comma-separated env var)
5
+ const GITHUB_EVENT_LABELS_RAW = process.env.GITHUB_EVENT_LABELS || "swarm-review";
6
+ export const GITHUB_EVENT_LABELS: string[] = GITHUB_EVENT_LABELS_RAW.split(",")
7
+ .map((l) => l.trim().toLowerCase())
8
+ .filter(Boolean);
9
+
10
+ export function isSwarmLabel(label: string): boolean {
11
+ return GITHUB_EVENT_LABELS.includes(label.toLowerCase());
12
+ }
13
+
4
14
  // Additional aliases that also trigger the bot (comma-separated env var)
5
15
  // Example: GITHUB_BOT_ALIASES=heysidekick,sidekick,review-bot
6
16
  function computeBotNames(): string[] {