@desplega.ai/agent-swarm 1.56.3 → 1.56.4

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/openapi.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "Agent Swarm API",
5
- "version": "1.56.3",
5
+ "version": "1.56.4",
6
6
  "description": "Multi-agent orchestration API for Claude Code, Codex, and Gemini CLI. Enables task distribution, agent communication, and service discovery.\n\nMCP tools are documented separately in [MCP.md](./MCP.md)."
7
7
  },
8
8
  "servers": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@desplega.ai/agent-swarm",
3
- "version": "1.56.3",
3
+ "version": "1.56.4",
4
4
  "description": "Multi-agent orchestration for Claude Code, Codex, Gemini CLI, and other AI coding assistants",
5
5
  "license": "MIT",
6
6
  "author": "desplega.sh <contact@desplega.sh>",
@@ -800,13 +800,103 @@ export async function handleComment(
800
800
  export async function handlePullRequestReview(
801
801
  event: PullRequestReviewEvent,
802
802
  ): Promise<{ created: boolean; taskId?: string }> {
803
- const { action, pull_request: pr, repository } = event;
803
+ const { action, review, pull_request: pr, repository, sender, installation } = event;
804
804
 
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`,
805
+ // Only handle submitted reviews (the most important action)
806
+ // Edited reviews are less common and dismissed is handled by the state
807
+ if (action !== "submitted") {
808
+ return { created: false };
809
+ }
810
+
811
+ // Skip "commented" reviews that are empty - these are often just line comments
812
+ // without an overall review body
813
+ if (review.state === "commented" && !review.body) {
814
+ return { created: false };
815
+ }
816
+
817
+ // Deduplicate
818
+ const eventKey = `pr-review:${repository.full_name}:${pr.number}:${review.id}`;
819
+ if (isDuplicate(eventKey)) {
820
+ return { created: false };
821
+ }
822
+
823
+ // Find any existing task for this PR
824
+ const existingTask = findTaskByVcs(repository.full_name, pr.number);
825
+
826
+ // Only notify for PRs where bot is creator or already has a task
827
+ const isBotCreator = isBotAssignee(pr.user.login);
828
+ if (!isBotCreator && !existingTask) {
829
+ return { created: false };
830
+ }
831
+
832
+ // Find lead agent for new task
833
+ const lead = findLeadAgent();
834
+
835
+ // Get review state info
836
+ const { emoji, label } = getReviewStateInfo(review.state);
837
+
838
+ // Build task description
839
+ const reviewBodySection = review.body ? `\n\nReview Comment:\n${review.body}` : "";
840
+ const relatedTaskSection = existingTask
841
+ ? `Related task: ${existingTask.id}\nšŸ”€ Consider routing to the same agent working on the related task.\n`
842
+ : "";
843
+ const reviewSuggestions =
844
+ review.state === "approved"
845
+ ? "šŸ’” Suggested: Merge the PR or wait for additional reviews"
846
+ : review.state === "changes_requested"
847
+ ? "šŸ’” Suggested: Address the requested changes and update the PR"
848
+ : "šŸ’” Suggested: Review the feedback and respond if needed";
849
+
850
+ const result = resolveTemplate(
851
+ "github.pull_request.review_submitted",
852
+ {
853
+ review_emoji: emoji,
854
+ pr_number: pr.number,
855
+ review_label: label,
856
+ pr_title: pr.title,
857
+ sender_login: sender.login,
858
+ repo_full_name: repository.full_name,
859
+ review_url: review.html_url,
860
+ review_body_section: reviewBodySection,
861
+ related_task_section: relatedTaskSection,
862
+ review_suggestions: reviewSuggestions,
863
+ },
864
+ { agentId: lead?.id, repoId: repository.full_name },
808
865
  );
809
- return { created: false };
866
+
867
+ if (result.skipped) {
868
+ return { created: false };
869
+ }
870
+
871
+ // Create task (assigned to lead if available, otherwise unassigned)
872
+ const task = createTaskExtended(result.text, {
873
+ agentId: lead?.id ?? "",
874
+ source: "github",
875
+ vcsProvider: "github",
876
+ taskType: "github-review",
877
+ vcsRepo: repository.full_name,
878
+ vcsEventType: "pull_request_review",
879
+ vcsNumber: pr.number,
880
+ vcsAuthor: sender.login,
881
+ vcsUrl: review.html_url,
882
+ });
883
+
884
+ if (lead) {
885
+ console.log(
886
+ `[GitHub] Created task ${task.id} for PR #${pr.number} review (${review.state}) -> ${lead.name}`,
887
+ );
888
+ } else {
889
+ console.log(
890
+ `[GitHub] Created unassigned task ${task.id} for PR #${pr.number} review (${review.state}, no lead available)`,
891
+ );
892
+ }
893
+
894
+ // Add reaction to acknowledge the review
895
+ if (installation?.id) {
896
+ addIssueReaction(repository.full_name, pr.number, "eyes", installation.id);
897
+ }
898
+
899
+ return { created: true, taskId: task.id };
810
900
  }
811
901
 
812
902
  /**
@@ -101,7 +101,7 @@ describe("suppressed cascade events", () => {
101
101
  expect(result.created).toBe(false);
102
102
  });
103
103
 
104
- test("pull_request_review.submitted returns created: false", async () => {
104
+ test("pull_request_review.submitted returns created: false when bot is not PR author and no existing task", async () => {
105
105
  const event: PullRequestReviewEvent = {
106
106
  action: "submitted",
107
107
  review: {
@@ -128,6 +128,88 @@ describe("suppressed cascade events", () => {
128
128
  expect(result.created).toBe(false);
129
129
  });
130
130
 
131
+ test("pull_request_review.submitted creates task when bot is PR author", async () => {
132
+ const event: PullRequestReviewEvent = {
133
+ action: "submitted",
134
+ review: {
135
+ id: 2,
136
+ body: "LGTM",
137
+ state: "approved",
138
+ html_url: "https://github.com/test/repo/pull/99#pullrequestreview-2",
139
+ user: { login: "reviewer" },
140
+ submitted_at: "2026-01-01T00:00:00Z",
141
+ },
142
+ pull_request: {
143
+ number: 99,
144
+ title: "Bot PR",
145
+ body: null,
146
+ html_url: "https://github.com/test/repo/pull/99",
147
+ user: { login: GITHUB_BOT_NAME },
148
+ head: { ref: "bot-feature" },
149
+ base: { ref: "main" },
150
+ },
151
+ repository: BASE_REPO,
152
+ sender: { login: "reviewer" },
153
+ };
154
+ const result = await handlePullRequestReview(event);
155
+ expect(result.created).toBe(true);
156
+ expect(result.taskId).toBeDefined();
157
+ });
158
+
159
+ test("pull_request_review.edited is ignored", async () => {
160
+ const event: PullRequestReviewEvent = {
161
+ action: "edited",
162
+ review: {
163
+ id: 3,
164
+ body: "Updated review",
165
+ state: "approved",
166
+ html_url: "https://github.com/test/repo/pull/99#pullrequestreview-3",
167
+ user: { login: "reviewer" },
168
+ submitted_at: "2026-01-01T00:00:00Z",
169
+ },
170
+ pull_request: {
171
+ number: 99,
172
+ title: "Bot PR",
173
+ body: null,
174
+ html_url: "https://github.com/test/repo/pull/99",
175
+ user: { login: GITHUB_BOT_NAME },
176
+ head: { ref: "bot-feature" },
177
+ base: { ref: "main" },
178
+ },
179
+ repository: BASE_REPO,
180
+ sender: { login: "reviewer" },
181
+ };
182
+ const result = await handlePullRequestReview(event);
183
+ expect(result.created).toBe(false);
184
+ });
185
+
186
+ test("pull_request_review.submitted with empty commented review is ignored", async () => {
187
+ const event: PullRequestReviewEvent = {
188
+ action: "submitted",
189
+ review: {
190
+ id: 4,
191
+ body: "",
192
+ state: "commented",
193
+ html_url: "https://github.com/test/repo/pull/99#pullrequestreview-4",
194
+ user: { login: "reviewer" },
195
+ submitted_at: "2026-01-01T00:00:00Z",
196
+ },
197
+ pull_request: {
198
+ number: 99,
199
+ title: "Bot PR",
200
+ body: null,
201
+ html_url: "https://github.com/test/repo/pull/99",
202
+ user: { login: GITHUB_BOT_NAME },
203
+ head: { ref: "bot-feature" },
204
+ base: { ref: "main" },
205
+ },
206
+ repository: BASE_REPO,
207
+ sender: { login: "reviewer" },
208
+ };
209
+ const result = await handlePullRequestReview(event);
210
+ expect(result.created).toBe(false);
211
+ });
212
+
131
213
  test("check_run.completed with failure returns created: false", async () => {
132
214
  const event: CheckRunEvent = {
133
215
  action: "completed",