@desplega.ai/agent-swarm 1.81.0 → 1.81.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/openapi.json +10 -1
- package/package.json +1 -1
- package/src/be/db.ts +7 -0
- package/src/github/handlers.ts +7 -7
- package/src/http/sessions.ts +7 -0
- package/src/tests/github-handlers.test.ts +29 -0
- package/src/tests/sessions.test.ts +53 -0
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.81.
|
|
5
|
+
"version": "1.81.1",
|
|
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": [
|
|
@@ -7358,6 +7358,15 @@
|
|
|
7358
7358
|
"required": false,
|
|
7359
7359
|
"name": "q",
|
|
7360
7360
|
"in": "query"
|
|
7361
|
+
},
|
|
7362
|
+
{
|
|
7363
|
+
"schema": {
|
|
7364
|
+
"type": "string",
|
|
7365
|
+
"minLength": 1
|
|
7366
|
+
},
|
|
7367
|
+
"required": false,
|
|
7368
|
+
"name": "requestedByUserId",
|
|
7369
|
+
"in": "query"
|
|
7361
7370
|
}
|
|
7362
7371
|
],
|
|
7363
7372
|
"responses": {
|
package/package.json
CHANGED
package/src/be/db.ts
CHANGED
|
@@ -9110,11 +9110,14 @@ export function listRecentSessions(opts?: {
|
|
|
9110
9110
|
source?: string[];
|
|
9111
9111
|
/** Case-insensitive substring match against `r.task`. */
|
|
9112
9112
|
q?: string;
|
|
9113
|
+
/** When set, restrict to root tasks where `requestedByUserId` equals this value. NULL rows are excluded. */
|
|
9114
|
+
requestedByUserId?: string;
|
|
9113
9115
|
}): SessionListItem[] {
|
|
9114
9116
|
const limit = opts?.limit ?? 25;
|
|
9115
9117
|
const offset = opts?.offset ?? 0;
|
|
9116
9118
|
const sources = opts?.source?.filter((s) => s.length > 0) ?? [];
|
|
9117
9119
|
const q = opts?.q?.trim();
|
|
9120
|
+
const requestedByUserId = opts?.requestedByUserId?.trim() || undefined;
|
|
9118
9121
|
|
|
9119
9122
|
const conditions: string[] = ["r.parentTaskId IS NULL"];
|
|
9120
9123
|
const params: (string | number)[] = [];
|
|
@@ -9127,6 +9130,10 @@ export function listRecentSessions(opts?: {
|
|
|
9127
9130
|
conditions.push("lower(r.task) LIKE ?");
|
|
9128
9131
|
params.push(`%${q.toLowerCase()}%`);
|
|
9129
9132
|
}
|
|
9133
|
+
if (requestedByUserId) {
|
|
9134
|
+
conditions.push("r.requestedByUserId = ?");
|
|
9135
|
+
params.push(requestedByUserId);
|
|
9136
|
+
}
|
|
9130
9137
|
params.push(limit, offset);
|
|
9131
9138
|
|
|
9132
9139
|
const rootRows = getDb()
|
package/src/github/handlers.ts
CHANGED
|
@@ -798,11 +798,10 @@ export async function handleComment(
|
|
|
798
798
|
): Promise<{ created: boolean; taskId?: string }> {
|
|
799
799
|
const { action, comment, repository, sender, issue, pull_request, installation } = event;
|
|
800
800
|
|
|
801
|
-
// Resolve canonical user from GitHub sender
|
|
802
|
-
|
|
803
|
-
const _requestedByUserId = resolveGitHubSender(
|
|
801
|
+
// Resolve canonical user from GitHub sender
|
|
802
|
+
const requestedByUserId = resolveGitHubSender(
|
|
804
803
|
sender.login,
|
|
805
|
-
|
|
804
|
+
eventType,
|
|
806
805
|
comment.body.slice(0, 100),
|
|
807
806
|
);
|
|
808
807
|
|
|
@@ -873,6 +872,7 @@ export async function handleComment(
|
|
|
873
872
|
vcsNumber: targetNumber,
|
|
874
873
|
vcsCommentId: comment.id,
|
|
875
874
|
vcsAuthor: sender.login,
|
|
875
|
+
requestedByUserId,
|
|
876
876
|
vcsUrl: targetUrl,
|
|
877
877
|
vcsInstallationId: installation?.id,
|
|
878
878
|
vcsNodeId: comment.node_id,
|
|
@@ -911,9 +911,8 @@ export async function handlePullRequestReview(
|
|
|
911
911
|
): Promise<{ created: boolean; taskId?: string }> {
|
|
912
912
|
const { action, review, pull_request: pr, repository, sender, installation } = event;
|
|
913
913
|
|
|
914
|
-
// Resolve canonical user from GitHub sender
|
|
915
|
-
|
|
916
|
-
const _requestedByUserId = resolveGitHubSender(
|
|
914
|
+
// Resolve canonical user from GitHub sender
|
|
915
|
+
const requestedByUserId = resolveGitHubSender(
|
|
917
916
|
sender.login,
|
|
918
917
|
"pull_request_review",
|
|
919
918
|
`Review on PR #${pr.number}: ${review.state}`,
|
|
@@ -995,6 +994,7 @@ export async function handlePullRequestReview(
|
|
|
995
994
|
vcsEventType: "pull_request_review",
|
|
996
995
|
vcsNumber: pr.number,
|
|
997
996
|
vcsAuthor: sender.login,
|
|
997
|
+
requestedByUserId,
|
|
998
998
|
vcsUrl: review.html_url,
|
|
999
999
|
vcsInstallationId: installation?.id,
|
|
1000
1000
|
vcsNodeId: review.node_id,
|
package/src/http/sessions.ts
CHANGED
|
@@ -19,6 +19,12 @@ const listSessions = route({
|
|
|
19
19
|
source: z.string().optional(),
|
|
20
20
|
/** Case-insensitive substring match against the root task's text. */
|
|
21
21
|
q: z.string().optional(),
|
|
22
|
+
/**
|
|
23
|
+
* When present, restrict results to root tasks where
|
|
24
|
+
* `agent_tasks.requestedByUserId` equals this value. NULL rows are
|
|
25
|
+
* excluded. Omit to return every session (legacy / non-UI callers).
|
|
26
|
+
*/
|
|
27
|
+
requestedByUserId: z.string().min(1).optional(),
|
|
22
28
|
}),
|
|
23
29
|
responses: {
|
|
24
30
|
200: { description: "Recent sessions ordered by chain-wide last activity" },
|
|
@@ -64,6 +70,7 @@ export async function handleSessions(
|
|
|
64
70
|
offset: parsed.query.offset,
|
|
65
71
|
source: sources,
|
|
66
72
|
q: parsed.query.q,
|
|
73
|
+
requestedByUserId: parsed.query.requestedByUserId,
|
|
67
74
|
});
|
|
68
75
|
json(res, { sessions });
|
|
69
76
|
return true;
|
|
@@ -187,6 +187,35 @@ describe("known github sender", () => {
|
|
|
187
187
|
expect(getKv(UNMAPPED_NAMESPACE, "assigner:meta")).toBeNull();
|
|
188
188
|
expect(getKv(UNMAPPED_NAMESPACE, "assigner:count")).toBeNull();
|
|
189
189
|
});
|
|
190
|
+
|
|
191
|
+
test("comment event with bot mention from mapped user puts user id on the task", async () => {
|
|
192
|
+
const user = createUser({ name: "Mapped Commenter", email: "commenter@example.com" });
|
|
193
|
+
linkIdentity(user.id, "github", "commenter", SYSTEM_ACTOR);
|
|
194
|
+
|
|
195
|
+
const result = await handleComment(
|
|
196
|
+
makeCommentEvent("commenter", `Hey @${GITHUB_BOT_NAME} please take a look`),
|
|
197
|
+
"issue_comment",
|
|
198
|
+
);
|
|
199
|
+
expect(result.created).toBe(true);
|
|
200
|
+
expect(getMappedUserTaskCount(user.id)).toBe(1);
|
|
201
|
+
|
|
202
|
+
// Mapped sender → no unmapped kv writes.
|
|
203
|
+
expect(getKv(UNMAPPED_NAMESPACE, "commenter:meta")).toBeNull();
|
|
204
|
+
expect(getKv(UNMAPPED_NAMESPACE, "commenter:count")).toBeNull();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("review event from mapped user puts user id on the task", async () => {
|
|
208
|
+
const user = createUser({ name: "Mapped Reviewer", email: "reviewer@example.com" });
|
|
209
|
+
linkIdentity(user.id, "github", "reviewer", SYSTEM_ACTOR);
|
|
210
|
+
|
|
211
|
+
const result = await handlePullRequestReview(makeReviewEvent("reviewer"));
|
|
212
|
+
expect(result.created).toBe(true);
|
|
213
|
+
expect(getMappedUserTaskCount(user.id)).toBe(1);
|
|
214
|
+
|
|
215
|
+
// Mapped sender → no unmapped kv writes.
|
|
216
|
+
expect(getKv(UNMAPPED_NAMESPACE, "reviewer:meta")).toBeNull();
|
|
217
|
+
expect(getKv(UNMAPPED_NAMESPACE, "reviewer:count")).toBeNull();
|
|
218
|
+
});
|
|
190
219
|
});
|
|
191
220
|
|
|
192
221
|
// ── Unknown sender → unmapped kv tracker ──
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
closeDb,
|
|
5
5
|
createAgent,
|
|
6
6
|
createTaskExtended,
|
|
7
|
+
createUser,
|
|
7
8
|
getRootTaskChain,
|
|
8
9
|
initDb,
|
|
9
10
|
listRecentSessions,
|
|
@@ -138,4 +139,56 @@ describe("sessions — getRootTaskChain + listRecentSessions", () => {
|
|
|
138
139
|
expect(sessions[i - 1].lastActivityAt >= sessions[i].lastActivityAt).toBe(true);
|
|
139
140
|
}
|
|
140
141
|
});
|
|
142
|
+
|
|
143
|
+
test("listRecentSessions — requestedByUserId filter: positive / negative / NULL exclusion / compat", () => {
|
|
144
|
+
const userA = createUser({ name: "Test User A" });
|
|
145
|
+
const userB = createUser({ name: "Test User B" });
|
|
146
|
+
|
|
147
|
+
const agent = createAgent({
|
|
148
|
+
id: "sessions-test-agent-user-filter",
|
|
149
|
+
name: "Sessions Test Agent UserFilter",
|
|
150
|
+
isLead: false,
|
|
151
|
+
status: "idle",
|
|
152
|
+
});
|
|
153
|
+
createTaskExtended("user-a session 1", {
|
|
154
|
+
agentId: agent.id,
|
|
155
|
+
requestedByUserId: userA.id,
|
|
156
|
+
});
|
|
157
|
+
createTaskExtended("user-a session 2", {
|
|
158
|
+
agentId: agent.id,
|
|
159
|
+
requestedByUserId: userA.id,
|
|
160
|
+
});
|
|
161
|
+
createTaskExtended("user-b session 1", {
|
|
162
|
+
agentId: agent.id,
|
|
163
|
+
requestedByUserId: userB.id,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Positive: user A sees only their own sessions
|
|
167
|
+
const aOnly = listRecentSessions({ limit: 50, requestedByUserId: userA.id });
|
|
168
|
+
const aTasks = aOnly.map((s) => s.root.task);
|
|
169
|
+
expect(aTasks).toContain("user-a session 1");
|
|
170
|
+
expect(aTasks).toContain("user-a session 2");
|
|
171
|
+
expect(aTasks).not.toContain("user-b session 1");
|
|
172
|
+
for (const s of aOnly) {
|
|
173
|
+
expect(s.root.requestedByUserId).toBe(userA.id);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Negative: user A cannot see user B's sessions
|
|
177
|
+
const hasUserBInA = aOnly.some((s) => s.root.requestedByUserId === userB.id);
|
|
178
|
+
expect(hasUserBInA).toBe(false);
|
|
179
|
+
|
|
180
|
+
// NULL exclusion: NULL requestedByUserId rows excluded when filter is active
|
|
181
|
+
const hasNullInA = aOnly.some((s) => s.root.requestedByUserId == null);
|
|
182
|
+
expect(hasNullInA).toBe(false);
|
|
183
|
+
|
|
184
|
+
// Empty: unknown user ID returns empty list
|
|
185
|
+
const nobody = listRecentSessions({ limit: 50, requestedByUserId: "non-existent-user-id" });
|
|
186
|
+
expect(nobody).toHaveLength(0);
|
|
187
|
+
|
|
188
|
+
// Compat: no filter returns all sessions including NULL rows and both users
|
|
189
|
+
const all = listRecentSessions({ limit: 100 });
|
|
190
|
+
expect(all.some((s) => s.root.requestedByUserId == null)).toBe(true);
|
|
191
|
+
expect(all.some((s) => s.root.requestedByUserId === userA.id)).toBe(true);
|
|
192
|
+
expect(all.some((s) => s.root.requestedByUserId === userB.id)).toBe(true);
|
|
193
|
+
});
|
|
141
194
|
});
|