@desplega.ai/agent-swarm 1.73.2 → 1.73.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.
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.73.2",
5
+ "version": "1.73.3",
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": [
@@ -2914,6 +2914,112 @@
2914
2914
  }
2915
2915
  }
2916
2916
  },
2917
+ "/api/memory/list": {
2918
+ "post": {
2919
+ "summary": "List or semantically search memories across all agents (debug/admin)",
2920
+ "tags": [
2921
+ "Memory"
2922
+ ],
2923
+ "security": [
2924
+ {
2925
+ "bearerAuth": []
2926
+ }
2927
+ ],
2928
+ "requestBody": {
2929
+ "content": {
2930
+ "application/json": {
2931
+ "schema": {
2932
+ "type": "object",
2933
+ "properties": {
2934
+ "query": {
2935
+ "type": "string",
2936
+ "description": "Natural-language query. If present, runs semantic search; otherwise lists by recency."
2937
+ },
2938
+ "agentId": {
2939
+ "type": "string",
2940
+ "format": "uuid",
2941
+ "description": "Filter to a single agent. Omit for all."
2942
+ },
2943
+ "scope": {
2944
+ "type": "string",
2945
+ "enum": [
2946
+ "agent",
2947
+ "swarm",
2948
+ "all"
2949
+ ],
2950
+ "default": "all"
2951
+ },
2952
+ "source": {
2953
+ "type": "string",
2954
+ "enum": [
2955
+ "manual",
2956
+ "file_index",
2957
+ "session_summary",
2958
+ "task_completion"
2959
+ ]
2960
+ },
2961
+ "sourcePath": {
2962
+ "type": "string",
2963
+ "description": "Substring match against sourcePath (case-insensitive). Useful for file_index memories."
2964
+ },
2965
+ "limit": {
2966
+ "type": "integer",
2967
+ "minimum": 1,
2968
+ "maximum": 100,
2969
+ "default": 20
2970
+ },
2971
+ "offset": {
2972
+ "type": "integer",
2973
+ "minimum": 0,
2974
+ "default": 0
2975
+ }
2976
+ }
2977
+ }
2978
+ }
2979
+ }
2980
+ },
2981
+ "responses": {
2982
+ "200": {
2983
+ "description": "Memory list / search results"
2984
+ },
2985
+ "400": {
2986
+ "description": "Validation error"
2987
+ }
2988
+ }
2989
+ }
2990
+ },
2991
+ "/api/memory/{id}": {
2992
+ "delete": {
2993
+ "summary": "Delete a single memory by ID (debug/admin)",
2994
+ "tags": [
2995
+ "Memory"
2996
+ ],
2997
+ "security": [
2998
+ {
2999
+ "bearerAuth": []
3000
+ }
3001
+ ],
3002
+ "parameters": [
3003
+ {
3004
+ "schema": {
3005
+ "type": "string",
3006
+ "format": "uuid"
3007
+ },
3008
+ "required": true,
3009
+ "name": "id",
3010
+ "in": "path"
3011
+ }
3012
+ ],
3013
+ "responses": {
3014
+ "200": {
3015
+ "description": "Memory deleted"
3016
+ },
3017
+ "404": {
3018
+ "description": "Memory not found"
3019
+ }
3020
+ }
3021
+ }
3022
+ },
2917
3023
  "/api/prompt-templates/resolved": {
2918
3024
  "get": {
2919
3025
  "summary": "Resolve a prompt template for a given event type and scope chain",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@desplega.ai/agent-swarm",
3
- "version": "1.73.2",
3
+ "version": "1.73.3",
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>",
@@ -69,6 +69,52 @@ const reEmbedMemory = route({
69
69
  },
70
70
  });
71
71
 
72
+ const listMemory = route({
73
+ method: "post",
74
+ path: "/api/memory/list",
75
+ pattern: ["api", "memory", "list"],
76
+ summary: "List or semantically search memories across all agents (debug/admin)",
77
+ tags: ["Memory"],
78
+ auth: { apiKey: true },
79
+ body: z.object({
80
+ query: z
81
+ .string()
82
+ .optional()
83
+ .describe(
84
+ "Natural-language query. If present, runs semantic search; otherwise lists by recency.",
85
+ ),
86
+ agentId: z.string().uuid().optional().describe("Filter to a single agent. Omit for all."),
87
+ scope: z.enum(["agent", "swarm", "all"]).default("all"),
88
+ source: AgentMemorySourceSchema.optional(),
89
+ sourcePath: z
90
+ .string()
91
+ .optional()
92
+ .describe(
93
+ "Substring match against sourcePath (case-insensitive). Useful for file_index memories.",
94
+ ),
95
+ limit: z.number().int().min(1).max(100).default(20),
96
+ offset: z.number().int().min(0).default(0),
97
+ }),
98
+ responses: {
99
+ 200: { description: "Memory list / search results" },
100
+ 400: { description: "Validation error" },
101
+ },
102
+ });
103
+
104
+ const deleteMemoryById = route({
105
+ method: "delete",
106
+ path: "/api/memory/{id}",
107
+ pattern: ["api", "memory", null],
108
+ summary: "Delete a single memory by ID (debug/admin)",
109
+ tags: ["Memory"],
110
+ auth: { apiKey: true },
111
+ params: z.object({ id: z.string().uuid() }),
112
+ responses: {
113
+ 200: { description: "Memory deleted" },
114
+ 404: { description: "Memory not found" },
115
+ },
116
+ });
117
+
72
118
  // ─── Handler ─────────────────────────────────────────────────────────────────
73
119
 
74
120
  export async function handleMemory(
@@ -182,6 +228,132 @@ export async function handleMemory(
182
228
  return true;
183
229
  }
184
230
 
231
+ if (listMemory.match(req.method, pathSegments)) {
232
+ const parsed = await listMemory.parse(req, res, pathSegments, new URLSearchParams());
233
+ if (!parsed) return true;
234
+
235
+ const { query, agentId, scope, source, sourcePath, limit, offset } = parsed.body;
236
+ const store = getMemoryStore();
237
+ const pathNeedle = sourcePath?.trim().toLowerCase();
238
+ const matchesPath = (p: string | null) =>
239
+ !pathNeedle || (p?.toLowerCase().includes(pathNeedle) ?? false);
240
+
241
+ try {
242
+ if (query && query.trim().length > 0) {
243
+ const provider = getEmbeddingProvider();
244
+ const queryEmbedding = await provider.embed(query.trim());
245
+
246
+ if (!queryEmbedding) {
247
+ json(res, { results: [], total: 0, mode: "semantic" });
248
+ return true;
249
+ }
250
+
251
+ const candidateLimit = Math.min(limit, 100) * CANDIDATE_SET_MULTIPLIER;
252
+ let candidates = store.search(queryEmbedding, agentId ?? "", {
253
+ scope,
254
+ limit: candidateLimit,
255
+ isLead: true,
256
+ source,
257
+ });
258
+ if (agentId) {
259
+ candidates = candidates.filter((c) => c.agentId === agentId);
260
+ }
261
+ if (pathNeedle) {
262
+ candidates = candidates.filter((c) => matchesPath(c.sourcePath));
263
+ }
264
+ const ranked = rerank(candidates, { limit: Math.min(limit, 100) });
265
+
266
+ json(res, {
267
+ results: ranked.map((r) => ({
268
+ id: r.id,
269
+ name: r.name,
270
+ content: r.content,
271
+ agentId: r.agentId,
272
+ scope: r.scope,
273
+ source: r.source,
274
+ similarity: r.similarity,
275
+ createdAt: r.createdAt,
276
+ accessedAt: r.accessedAt,
277
+ accessCount: r.accessCount ?? 0,
278
+ expiresAt: r.expiresAt ?? null,
279
+ embeddingModel: r.embeddingModel ?? null,
280
+ sourceTaskId: r.sourceTaskId,
281
+ sourcePath: r.sourcePath,
282
+ chunkIndex: r.chunkIndex,
283
+ totalChunks: r.totalChunks,
284
+ tags: r.tags,
285
+ })),
286
+ total: ranked.length,
287
+ mode: "semantic",
288
+ });
289
+ return true;
290
+ }
291
+
292
+ // When filtering by sourcePath, over-fetch then post-filter so the visible
293
+ // page isn't gutted by the in-memory filter.
294
+ const fetchLimit = pathNeedle
295
+ ? Math.min(500, Math.max(limit * 10, 100))
296
+ : Math.min(limit, 100);
297
+ let rows = store.list(agentId ?? "", {
298
+ scope,
299
+ limit: fetchLimit,
300
+ offset,
301
+ isLead: true,
302
+ });
303
+ if (agentId) {
304
+ rows = rows.filter((r) => r.agentId === agentId);
305
+ }
306
+ if (source) {
307
+ rows = rows.filter((r) => r.source === source);
308
+ }
309
+ if (pathNeedle) {
310
+ rows = rows.filter((r) => matchesPath(r.sourcePath));
311
+ }
312
+ rows = rows.slice(0, Math.min(limit, 100));
313
+
314
+ json(res, {
315
+ results: rows.map((r) => ({
316
+ id: r.id,
317
+ name: r.name,
318
+ content: r.content,
319
+ agentId: r.agentId,
320
+ scope: r.scope,
321
+ source: r.source,
322
+ createdAt: r.createdAt,
323
+ accessedAt: r.accessedAt,
324
+ accessCount: r.accessCount ?? 0,
325
+ expiresAt: r.expiresAt ?? null,
326
+ embeddingModel: r.embeddingModel ?? null,
327
+ sourceTaskId: r.sourceTaskId,
328
+ sourcePath: r.sourcePath,
329
+ chunkIndex: r.chunkIndex,
330
+ totalChunks: r.totalChunks,
331
+ tags: r.tags,
332
+ })),
333
+ total: rows.length,
334
+ mode: "list",
335
+ });
336
+ } catch (err) {
337
+ console.error("[memory-list] Error:", (err as Error).message);
338
+ jsonError(res, "Memory list failed", 500);
339
+ }
340
+ return true;
341
+ }
342
+
343
+ if (deleteMemoryById.match(req.method, pathSegments)) {
344
+ const parsed = await deleteMemoryById.parse(req, res, pathSegments, new URLSearchParams());
345
+ if (!parsed) return true;
346
+
347
+ const store = getMemoryStore();
348
+ const deleted = store.delete(parsed.params.id);
349
+ if (!deleted) {
350
+ jsonError(res, "Memory not found", 404);
351
+ return true;
352
+ }
353
+ json(res, { deleted: true });
354
+ return true;
355
+ }
356
+
185
357
  if (reEmbedMemory.match(req.method, pathSegments)) {
186
358
  const parsed = await reEmbedMemory.parse(req, res, pathSegments, new URLSearchParams());
187
359
  if (!parsed) return true;