@desplega.ai/agent-swarm 1.52.1 → 1.53.0

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.
Files changed (43) hide show
  1. package/openapi.json +1517 -488
  2. package/package.json +5 -2
  3. package/src/be/db.ts +530 -0
  4. package/src/be/events.ts +322 -0
  5. package/src/be/migrations/021_events.sql +24 -0
  6. package/src/be/migrations/022_context_usage.sql +34 -0
  7. package/src/be/migrations/023_mcp_servers.sql +44 -0
  8. package/src/commands/runner.ts +348 -1
  9. package/src/http/context.ts +118 -0
  10. package/src/http/events.ts +188 -0
  11. package/src/http/index.ts +6 -0
  12. package/src/http/mcp-servers.ts +364 -0
  13. package/src/http/tasks.ts +33 -0
  14. package/src/linear/outbound.ts +8 -1
  15. package/src/linear/sync.ts +3 -0
  16. package/src/oauth/ensure-token.ts +50 -0
  17. package/src/prompts/base-prompt.ts +7 -0
  18. package/src/providers/claude-adapter.ts +156 -15
  19. package/src/providers/pi-mono-adapter.ts +68 -0
  20. package/src/providers/pi-mono-extension.ts +56 -2
  21. package/src/providers/pi-mono-mcp-client.ts +10 -1
  22. package/src/providers/types.ts +14 -1
  23. package/src/server.ts +19 -0
  24. package/src/tests/context-window.test.ts +66 -0
  25. package/src/tests/ensure-token.test.ts +170 -0
  26. package/src/tests/events-db.test.ts +314 -0
  27. package/src/tests/events-http.test.ts +267 -0
  28. package/src/tests/prompt-template-remaining.test.ts +5 -5
  29. package/src/tests/tool-annotations.test.ts +2 -2
  30. package/src/tests/vcs-tracking.test.ts +176 -0
  31. package/src/tests/workflow-executors.test.ts +8 -1
  32. package/src/tools/mcp-servers/index.ts +7 -0
  33. package/src/tools/mcp-servers/mcp-server-create.ts +138 -0
  34. package/src/tools/mcp-servers/mcp-server-delete.ts +72 -0
  35. package/src/tools/mcp-servers/mcp-server-get.ts +80 -0
  36. package/src/tools/mcp-servers/mcp-server-install.ts +110 -0
  37. package/src/tools/mcp-servers/mcp-server-list.ts +67 -0
  38. package/src/tools/mcp-servers/mcp-server-uninstall.ts +71 -0
  39. package/src/tools/mcp-servers/mcp-server-update.ts +120 -0
  40. package/src/tools/tool-config.ts +9 -0
  41. package/src/types.ts +153 -0
  42. package/src/utils/context-window.ts +41 -0
  43. package/src/workflows/executors/base.ts +9 -1
@@ -0,0 +1,322 @@
1
+ import type { EventCategory, EventName, EventSource, EventStatus, SwarmEvent } from "../types";
2
+ import { getDb } from "./db";
3
+
4
+ // -- Events --
5
+
6
+ type EventRow = {
7
+ id: string;
8
+ category: string;
9
+ event: string;
10
+ status: string;
11
+ source: string;
12
+ agentId: string | null;
13
+ taskId: string | null;
14
+ sessionId: string | null;
15
+ parentEventId: string | null;
16
+ numericValue: number | null;
17
+ durationMs: number | null;
18
+ data: string | null;
19
+ createdAt: string;
20
+ };
21
+
22
+ function rowToSwarmEvent(row: EventRow): SwarmEvent {
23
+ return {
24
+ id: row.id,
25
+ category: row.category as EventCategory,
26
+ event: row.event as EventName,
27
+ status: row.status as EventStatus,
28
+ source: row.source as EventSource,
29
+ agentId: row.agentId ?? undefined,
30
+ taskId: row.taskId ?? undefined,
31
+ sessionId: row.sessionId ?? undefined,
32
+ parentEventId: row.parentEventId ?? undefined,
33
+ numericValue: row.numericValue ?? undefined,
34
+ durationMs: row.durationMs ?? undefined,
35
+ data: row.data ? JSON.parse(row.data) : undefined,
36
+ createdAt: row.createdAt,
37
+ };
38
+ }
39
+
40
+ const eventQueries = {
41
+ insert: () =>
42
+ getDb().prepare<
43
+ null,
44
+ [
45
+ string,
46
+ string,
47
+ string,
48
+ string,
49
+ string,
50
+ string | null,
51
+ string | null,
52
+ string | null,
53
+ string | null,
54
+ number | null,
55
+ number | null,
56
+ string | null,
57
+ ]
58
+ >(
59
+ `INSERT INTO events (id, category, event, status, source, agentId, taskId,
60
+ sessionId, parentEventId, numericValue, durationMs, data, createdAt)
61
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`,
62
+ ),
63
+
64
+ getByCategory: () =>
65
+ getDb().prepare<EventRow, [string, number]>(
66
+ "SELECT * FROM events WHERE category = ? ORDER BY createdAt DESC LIMIT ?",
67
+ ),
68
+
69
+ getByEvent: () =>
70
+ getDb().prepare<EventRow, [string, number]>(
71
+ "SELECT * FROM events WHERE event = ? ORDER BY createdAt DESC LIMIT ?",
72
+ ),
73
+
74
+ getByAgentId: () =>
75
+ getDb().prepare<EventRow, [string, number]>(
76
+ "SELECT * FROM events WHERE agentId = ? ORDER BY createdAt DESC LIMIT ?",
77
+ ),
78
+
79
+ getByTaskId: () =>
80
+ getDb().prepare<EventRow, [string, number]>(
81
+ "SELECT * FROM events WHERE taskId = ? ORDER BY createdAt DESC LIMIT ?",
82
+ ),
83
+
84
+ getBySessionId: () =>
85
+ getDb().prepare<EventRow, [string, number]>(
86
+ "SELECT * FROM events WHERE sessionId = ? ORDER BY createdAt DESC LIMIT ?",
87
+ ),
88
+
89
+ getAll: () =>
90
+ getDb().prepare<EventRow, [number]>("SELECT * FROM events ORDER BY createdAt DESC LIMIT ?"),
91
+
92
+ countByEvent: () =>
93
+ getDb().prepare<{ event: string; count: number }, []>(
94
+ "SELECT event, COUNT(*) as count FROM events GROUP BY event ORDER BY count DESC",
95
+ ),
96
+
97
+ countByEventForAgent: () =>
98
+ getDb().prepare<{ event: string; count: number }, [string]>(
99
+ "SELECT event, COUNT(*) as count FROM events WHERE agentId = ? GROUP BY event ORDER BY count DESC",
100
+ ),
101
+ };
102
+
103
+ // ─── Create ─────────────────────────────────────────────────────────────────
104
+
105
+ export interface CreateEventInput {
106
+ category: EventCategory;
107
+ event: EventName;
108
+ status?: EventStatus;
109
+ source: EventSource;
110
+ agentId?: string;
111
+ taskId?: string;
112
+ sessionId?: string;
113
+ parentEventId?: string;
114
+ numericValue?: number;
115
+ durationMs?: number;
116
+ data?: Record<string, unknown>;
117
+ }
118
+
119
+ export function createEvent(input: CreateEventInput): SwarmEvent {
120
+ const id = crypto.randomUUID();
121
+ eventQueries
122
+ .insert()
123
+ .run(
124
+ id,
125
+ input.category,
126
+ input.event,
127
+ input.status ?? "ok",
128
+ input.source,
129
+ input.agentId ?? null,
130
+ input.taskId ?? null,
131
+ input.sessionId ?? null,
132
+ input.parentEventId ?? null,
133
+ input.numericValue ?? null,
134
+ input.durationMs ?? null,
135
+ input.data ? JSON.stringify(input.data) : null,
136
+ );
137
+ return {
138
+ id,
139
+ category: input.category,
140
+ event: input.event,
141
+ status: input.status ?? "ok",
142
+ source: input.source,
143
+ agentId: input.agentId,
144
+ taskId: input.taskId,
145
+ sessionId: input.sessionId,
146
+ parentEventId: input.parentEventId,
147
+ numericValue: input.numericValue,
148
+ durationMs: input.durationMs,
149
+ data: input.data,
150
+ createdAt: new Date().toISOString(),
151
+ };
152
+ }
153
+
154
+ export function createEventsBatch(inputs: CreateEventInput[]): number {
155
+ const insert = eventQueries.insert();
156
+ const tx = getDb().transaction(() => {
157
+ for (const input of inputs) {
158
+ const id = crypto.randomUUID();
159
+ insert.run(
160
+ id,
161
+ input.category,
162
+ input.event,
163
+ input.status ?? "ok",
164
+ input.source,
165
+ input.agentId ?? null,
166
+ input.taskId ?? null,
167
+ input.sessionId ?? null,
168
+ input.parentEventId ?? null,
169
+ input.numericValue ?? null,
170
+ input.durationMs ?? null,
171
+ input.data ? JSON.stringify(input.data) : null,
172
+ );
173
+ }
174
+ });
175
+ tx();
176
+ return inputs.length;
177
+ }
178
+
179
+ // ─── Query ──────────────────────────────────────────────────────────────────
180
+
181
+ export function getEventsByCategory(category: EventCategory, limit = 100): SwarmEvent[] {
182
+ return eventQueries.getByCategory().all(category, limit).map(rowToSwarmEvent);
183
+ }
184
+
185
+ export function getEventsByEvent(event: EventName, limit = 100): SwarmEvent[] {
186
+ return eventQueries.getByEvent().all(event, limit).map(rowToSwarmEvent);
187
+ }
188
+
189
+ export function getEventsByAgentId(agentId: string, limit = 100): SwarmEvent[] {
190
+ return eventQueries.getByAgentId().all(agentId, limit).map(rowToSwarmEvent);
191
+ }
192
+
193
+ export function getEventsByTaskId(taskId: string, limit = 100): SwarmEvent[] {
194
+ return eventQueries.getByTaskId().all(taskId, limit).map(rowToSwarmEvent);
195
+ }
196
+
197
+ export function getEventsBySessionId(sessionId: string, limit = 100): SwarmEvent[] {
198
+ return eventQueries.getBySessionId().all(sessionId, limit).map(rowToSwarmEvent);
199
+ }
200
+
201
+ export function getAllEvents(limit = 100): SwarmEvent[] {
202
+ return eventQueries.getAll().all(limit).map(rowToSwarmEvent);
203
+ }
204
+
205
+ export function getEventCounts(): Array<{ event: string; count: number }> {
206
+ return eventQueries.countByEvent().all();
207
+ }
208
+
209
+ export function getEventCountsForAgent(agentId: string): Array<{ event: string; count: number }> {
210
+ return eventQueries.countByEventForAgent().all(agentId);
211
+ }
212
+
213
+ export function getEventCountsFiltered(filters: {
214
+ category?: EventCategory;
215
+ source?: EventSource;
216
+ agentId?: string;
217
+ taskId?: string;
218
+ sessionId?: string;
219
+ since?: string;
220
+ until?: string;
221
+ }): Array<{ event: string; count: number }> {
222
+ const conditions: string[] = [];
223
+ const params: (string | number)[] = [];
224
+
225
+ if (filters.category) {
226
+ conditions.push("category = ?");
227
+ params.push(filters.category);
228
+ }
229
+ if (filters.source) {
230
+ conditions.push("source = ?");
231
+ params.push(filters.source);
232
+ }
233
+ if (filters.agentId) {
234
+ conditions.push("agentId = ?");
235
+ params.push(filters.agentId);
236
+ }
237
+ if (filters.taskId) {
238
+ conditions.push("taskId = ?");
239
+ params.push(filters.taskId);
240
+ }
241
+ if (filters.sessionId) {
242
+ conditions.push("sessionId = ?");
243
+ params.push(filters.sessionId);
244
+ }
245
+ if (filters.since) {
246
+ conditions.push("createdAt >= ?");
247
+ params.push(filters.since);
248
+ }
249
+ if (filters.until) {
250
+ conditions.push("createdAt <= ?");
251
+ params.push(filters.until);
252
+ }
253
+
254
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
255
+ const sql = `SELECT event, COUNT(*) as count FROM events ${where} GROUP BY event ORDER BY count DESC`;
256
+ return getDb()
257
+ .prepare<{ event: string; count: number }, (string | number)[]>(sql)
258
+ .all(...params);
259
+ }
260
+
261
+ export function getEventsFiltered(filters: {
262
+ category?: EventCategory;
263
+ event?: EventName;
264
+ status?: EventStatus;
265
+ source?: EventSource;
266
+ agentId?: string;
267
+ taskId?: string;
268
+ sessionId?: string;
269
+ since?: string;
270
+ until?: string;
271
+ limit?: number;
272
+ }): SwarmEvent[] {
273
+ const conditions: string[] = [];
274
+ const params: (string | number)[] = [];
275
+
276
+ if (filters.category) {
277
+ conditions.push("category = ?");
278
+ params.push(filters.category);
279
+ }
280
+ if (filters.event) {
281
+ conditions.push("event = ?");
282
+ params.push(filters.event);
283
+ }
284
+ if (filters.status) {
285
+ conditions.push("status = ?");
286
+ params.push(filters.status);
287
+ }
288
+ if (filters.source) {
289
+ conditions.push("source = ?");
290
+ params.push(filters.source);
291
+ }
292
+ if (filters.agentId) {
293
+ conditions.push("agentId = ?");
294
+ params.push(filters.agentId);
295
+ }
296
+ if (filters.taskId) {
297
+ conditions.push("taskId = ?");
298
+ params.push(filters.taskId);
299
+ }
300
+ if (filters.sessionId) {
301
+ conditions.push("sessionId = ?");
302
+ params.push(filters.sessionId);
303
+ }
304
+ if (filters.since) {
305
+ conditions.push("createdAt >= ?");
306
+ params.push(filters.since);
307
+ }
308
+ if (filters.until) {
309
+ conditions.push("createdAt <= ?");
310
+ params.push(filters.until);
311
+ }
312
+
313
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
314
+ const limit = filters.limit ?? 100;
315
+ params.push(limit);
316
+
317
+ const sql = `SELECT * FROM events ${where} ORDER BY createdAt DESC LIMIT ?`;
318
+ return getDb()
319
+ .prepare<EventRow, (string | number)[]>(sql)
320
+ .all(...params)
321
+ .map(rowToSwarmEvent);
322
+ }
@@ -0,0 +1,24 @@
1
+ CREATE TABLE IF NOT EXISTS events (
2
+ id TEXT PRIMARY KEY,
3
+ category TEXT NOT NULL,
4
+ event TEXT NOT NULL,
5
+ status TEXT NOT NULL DEFAULT 'ok',
6
+ source TEXT NOT NULL,
7
+ agentId TEXT,
8
+ taskId TEXT,
9
+ sessionId TEXT,
10
+ parentEventId TEXT,
11
+ numericValue REAL,
12
+ durationMs INTEGER,
13
+ data TEXT,
14
+ createdAt TEXT NOT NULL
15
+ );
16
+
17
+ CREATE INDEX IF NOT EXISTS idx_events_category ON events(category);
18
+ CREATE INDEX IF NOT EXISTS idx_events_event ON events(event);
19
+ CREATE INDEX IF NOT EXISTS idx_events_status ON events(status);
20
+ CREATE INDEX IF NOT EXISTS idx_events_source ON events(source);
21
+ CREATE INDEX IF NOT EXISTS idx_events_agentId ON events(agentId);
22
+ CREATE INDEX IF NOT EXISTS idx_events_taskId ON events(taskId);
23
+ CREATE INDEX IF NOT EXISTS idx_events_sessionId ON events(sessionId);
24
+ CREATE INDEX IF NOT EXISTS idx_events_createdAt ON events(createdAt);
@@ -0,0 +1,34 @@
1
+ -- Progressive context usage snapshots
2
+ CREATE TABLE IF NOT EXISTS task_context_snapshots (
3
+ id TEXT PRIMARY KEY,
4
+ taskId TEXT NOT NULL REFERENCES agent_tasks(id) ON DELETE CASCADE,
5
+ agentId TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
6
+ sessionId TEXT NOT NULL,
7
+
8
+ -- Context window state
9
+ contextUsedTokens INTEGER,
10
+ contextTotalTokens INTEGER,
11
+ contextPercent REAL,
12
+
13
+ -- Event metadata
14
+ eventType TEXT NOT NULL CHECK (eventType IN ('progress', 'compaction', 'completion')),
15
+
16
+ -- Compaction-specific (NULL for non-compaction)
17
+ compactTrigger TEXT CHECK (compactTrigger IN ('auto', 'manual') OR compactTrigger IS NULL),
18
+ preCompactTokens INTEGER,
19
+
20
+ -- Cumulative counters at this point
21
+ cumulativeInputTokens INTEGER DEFAULT 0,
22
+ cumulativeOutputTokens INTEGER DEFAULT 0,
23
+
24
+ createdAt TEXT NOT NULL
25
+ );
26
+
27
+ CREATE INDEX IF NOT EXISTS idx_context_snapshots_task ON task_context_snapshots(taskId);
28
+ CREATE INDEX IF NOT EXISTS idx_context_snapshots_session ON task_context_snapshots(sessionId);
29
+
30
+ -- Aggregate columns on agent_tasks
31
+ ALTER TABLE agent_tasks ADD COLUMN compactionCount INTEGER DEFAULT 0;
32
+ ALTER TABLE agent_tasks ADD COLUMN peakContextPercent REAL;
33
+ ALTER TABLE agent_tasks ADD COLUMN totalContextTokensUsed INTEGER;
34
+ ALTER TABLE agent_tasks ADD COLUMN contextWindowSize INTEGER;
@@ -0,0 +1,44 @@
1
+ -- MCP server definitions
2
+ CREATE TABLE IF NOT EXISTS mcp_servers (
3
+ id TEXT PRIMARY KEY,
4
+ name TEXT NOT NULL,
5
+ description TEXT,
6
+ scope TEXT NOT NULL CHECK(scope IN ('global', 'swarm', 'agent')),
7
+ ownerAgentId TEXT REFERENCES agents(id),
8
+ transport TEXT NOT NULL CHECK(transport IN ('stdio', 'http', 'sse')),
9
+ -- Stdio fields
10
+ command TEXT,
11
+ args TEXT, -- JSON array string
12
+ -- HTTP/SSE fields
13
+ url TEXT,
14
+ headers TEXT, -- JSON object string (non-secret headers only)
15
+ -- Secret references (keys in swarm_config, NOT actual values)
16
+ envConfigKeys TEXT, -- JSON object: {"ENV_VAR": "config-key-name"}
17
+ headerConfigKeys TEXT, -- JSON object: {"Header-Name": "config-key-name"}
18
+ -- Metadata
19
+ isEnabled INTEGER NOT NULL DEFAULT 1,
20
+ version INTEGER NOT NULL DEFAULT 1,
21
+ createdAt TEXT NOT NULL,
22
+ lastUpdatedAt TEXT NOT NULL
23
+ );
24
+
25
+ -- Unique constraint: name must be unique within scope+owner combination
26
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_mcp_servers_name_scope
27
+ ON mcp_servers(name, scope, COALESCE(ownerAgentId, ''));
28
+
29
+ CREATE INDEX IF NOT EXISTS idx_mcp_servers_scope ON mcp_servers(scope);
30
+ CREATE INDEX IF NOT EXISTS idx_mcp_servers_owner ON mcp_servers(ownerAgentId);
31
+ CREATE INDEX IF NOT EXISTS idx_mcp_servers_transport ON mcp_servers(transport);
32
+
33
+ -- Per-agent MCP server installation
34
+ CREATE TABLE IF NOT EXISTS agent_mcp_servers (
35
+ id TEXT PRIMARY KEY,
36
+ agentId TEXT NOT NULL REFERENCES agents(id),
37
+ mcpServerId TEXT NOT NULL REFERENCES mcp_servers(id) ON DELETE CASCADE,
38
+ isActive INTEGER NOT NULL DEFAULT 1,
39
+ installedAt TEXT NOT NULL,
40
+ UNIQUE(agentId, mcpServerId)
41
+ );
42
+
43
+ CREATE INDEX IF NOT EXISTS idx_agent_mcp_servers_agent ON agent_mcp_servers(agentId);
44
+ CREATE INDEX IF NOT EXISTS idx_agent_mcp_servers_server ON agent_mcp_servers(mcpServerId);