@desplega.ai/agent-swarm 1.0.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/src/be/db.ts ADDED
@@ -0,0 +1,313 @@
1
+ import { Database } from "bun:sqlite";
2
+ import type { Agent, AgentStatus, AgentTask, AgentTaskStatus, AgentWithTasks } from "../types";
3
+
4
+ let db: Database | null = null;
5
+
6
+ export function initDb(dbPath = "./cc-orch.sqlite"): Database {
7
+ if (db) {
8
+ return db;
9
+ }
10
+
11
+ db = new Database(dbPath, { create: true });
12
+ db.run("PRAGMA journal_mode = WAL;");
13
+ db.run("PRAGMA foreign_keys = ON;");
14
+
15
+ db.run(`
16
+ CREATE TABLE IF NOT EXISTS agents (
17
+ id TEXT PRIMARY KEY,
18
+ name TEXT NOT NULL,
19
+ isLead INTEGER NOT NULL DEFAULT 0,
20
+ status TEXT NOT NULL CHECK(status IN ('idle', 'busy', 'offline')),
21
+ createdAt TEXT NOT NULL,
22
+ lastUpdatedAt TEXT NOT NULL
23
+ );
24
+
25
+ CREATE TABLE IF NOT EXISTS agent_tasks (
26
+ id TEXT PRIMARY KEY,
27
+ agentId TEXT NOT NULL,
28
+ task TEXT NOT NULL,
29
+ status TEXT NOT NULL CHECK(status IN ('pending', 'in_progress', 'completed', 'failed')),
30
+ createdAt TEXT NOT NULL,
31
+ lastUpdatedAt TEXT NOT NULL,
32
+ finishedAt TEXT,
33
+ failureReason TEXT,
34
+ output TEXT,
35
+ progress TEXT,
36
+ FOREIGN KEY (agentId) REFERENCES agents(id) ON DELETE CASCADE
37
+ );
38
+
39
+ CREATE INDEX IF NOT EXISTS idx_agent_tasks_agentId ON agent_tasks(agentId);
40
+ CREATE INDEX IF NOT EXISTS idx_agent_tasks_status ON agent_tasks(status);
41
+ `);
42
+
43
+ return db;
44
+ }
45
+
46
+ export function getDb(): Database {
47
+ if (!db) {
48
+ return initDb();
49
+ }
50
+ return db;
51
+ }
52
+
53
+ export function closeDb(): void {
54
+ if (db) {
55
+ db.close();
56
+ db = null;
57
+ }
58
+ }
59
+
60
+ // ============================================================================
61
+ // Agent Queries
62
+ // ============================================================================
63
+
64
+ type AgentRow = {
65
+ id: string;
66
+ name: string;
67
+ isLead: number;
68
+ status: AgentStatus;
69
+ createdAt: string;
70
+ lastUpdatedAt: string;
71
+ };
72
+
73
+ function rowToAgent(row: AgentRow): Agent {
74
+ return {
75
+ id: row.id,
76
+ name: row.name,
77
+ isLead: row.isLead === 1,
78
+ status: row.status,
79
+ createdAt: row.createdAt,
80
+ lastUpdatedAt: row.lastUpdatedAt,
81
+ };
82
+ }
83
+
84
+ export const agentQueries = {
85
+ insert: () =>
86
+ getDb().prepare<AgentRow, [string, string, number, AgentStatus]>(
87
+ "INSERT INTO agents (id, name, isLead, status, createdAt, lastUpdatedAt) VALUES (?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) RETURNING *",
88
+ ),
89
+
90
+ getById: () => getDb().prepare<AgentRow, [string]>("SELECT * FROM agents WHERE id = ?"),
91
+
92
+ getAll: () => getDb().prepare<AgentRow, []>("SELECT * FROM agents ORDER BY name"),
93
+
94
+ updateStatus: () =>
95
+ getDb().prepare<AgentRow, [AgentStatus, string]>(
96
+ "UPDATE agents SET status = ?, lastUpdatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = ? RETURNING *",
97
+ ),
98
+
99
+ delete: () => getDb().prepare<null, [string]>("DELETE FROM agents WHERE id = ?"),
100
+ };
101
+
102
+ export function createAgent(
103
+ agent: Omit<Agent, "id" | "createdAt" | "lastUpdatedAt"> & { id?: string },
104
+ ): Agent {
105
+ const id = agent.id ?? crypto.randomUUID();
106
+ const row = agentQueries.insert().get(id, agent.name, agent.isLead ? 1 : 0, agent.status);
107
+ if (!row) throw new Error("Failed to create agent");
108
+ return rowToAgent(row);
109
+ }
110
+
111
+ export function getAgentById(id: string): Agent | null {
112
+ const row = agentQueries.getById().get(id);
113
+ return row ? rowToAgent(row) : null;
114
+ }
115
+
116
+ export function getAllAgents(): Agent[] {
117
+ return agentQueries.getAll().all().map(rowToAgent);
118
+ }
119
+
120
+ export function updateAgentStatus(id: string, status: AgentStatus): Agent | null {
121
+ const row = agentQueries.updateStatus().get(status, id);
122
+ return row ? rowToAgent(row) : null;
123
+ }
124
+
125
+ export function deleteAgent(id: string): boolean {
126
+ const result = getDb().run("DELETE FROM agents WHERE id = ?", [id]);
127
+ return result.changes > 0;
128
+ }
129
+
130
+ // ============================================================================
131
+ // AgentTask Queries
132
+ // ============================================================================
133
+
134
+ type AgentTaskRow = {
135
+ id: string;
136
+ agentId: string;
137
+ task: string;
138
+ status: AgentTaskStatus;
139
+ createdAt: string;
140
+ lastUpdatedAt: string;
141
+ finishedAt: string | null;
142
+ failureReason: string | null;
143
+ output: string | null;
144
+ progress: string | null;
145
+ };
146
+
147
+ function rowToAgentTask(row: AgentTaskRow): AgentTask {
148
+ return {
149
+ id: row.id,
150
+ agentId: row.agentId,
151
+ task: row.task,
152
+ status: row.status,
153
+ createdAt: row.createdAt,
154
+ lastUpdatedAt: row.lastUpdatedAt,
155
+ finishedAt: row.finishedAt ?? undefined,
156
+ failureReason: row.failureReason ?? undefined,
157
+ output: row.output ?? undefined,
158
+ progress: row.progress ?? undefined,
159
+ };
160
+ }
161
+
162
+ export const taskQueries = {
163
+ insert: () =>
164
+ getDb().prepare<AgentTaskRow, [string, string, string, AgentTaskStatus]>(
165
+ `INSERT INTO agent_tasks (id, agentId, task, status, createdAt, lastUpdatedAt)
166
+ VALUES (?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) RETURNING *`,
167
+ ),
168
+
169
+ getById: () => getDb().prepare<AgentTaskRow, [string]>("SELECT * FROM agent_tasks WHERE id = ?"),
170
+
171
+ getByAgentId: () =>
172
+ getDb().prepare<AgentTaskRow, [string]>(
173
+ "SELECT * FROM agent_tasks WHERE agentId = ? ORDER BY createdAt DESC",
174
+ ),
175
+
176
+ getByStatus: () =>
177
+ getDb().prepare<AgentTaskRow, [AgentTaskStatus]>(
178
+ "SELECT * FROM agent_tasks WHERE status = ? ORDER BY createdAt DESC",
179
+ ),
180
+
181
+ updateStatus: () =>
182
+ getDb().prepare<AgentTaskRow, [AgentTaskStatus, string | null, string]>(
183
+ `UPDATE agent_tasks SET status = ?, finishedAt = ?, lastUpdatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = ? RETURNING *`,
184
+ ),
185
+
186
+ setOutput: () =>
187
+ getDb().prepare<AgentTaskRow, [string, string]>(
188
+ "UPDATE agent_tasks SET output = ?, lastUpdatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = ? RETURNING *",
189
+ ),
190
+
191
+ setFailure: () =>
192
+ getDb().prepare<AgentTaskRow, [string, string, string]>(
193
+ `UPDATE agent_tasks SET status = 'failed', failureReason = ?, finishedAt = ?, lastUpdatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
194
+ WHERE id = ? RETURNING *`,
195
+ ),
196
+
197
+ setProgress: () =>
198
+ getDb().prepare<AgentTaskRow, [string, string]>(
199
+ "UPDATE agent_tasks SET progress = ?, lastUpdatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = ? RETURNING *",
200
+ ),
201
+
202
+ delete: () => getDb().prepare<null, [string]>("DELETE FROM agent_tasks WHERE id = ?"),
203
+ };
204
+
205
+ export function createTask(agentId: string, task: string): AgentTask {
206
+ const id = crypto.randomUUID();
207
+ const row = taskQueries.insert().get(id, agentId, task, "pending");
208
+ if (!row) throw new Error("Failed to create task");
209
+ return rowToAgentTask(row);
210
+ }
211
+
212
+ export function getPendingTaskForAgent(agentId: string): AgentTask | null {
213
+ const row = getDb()
214
+ .prepare<AgentTaskRow, [string]>(
215
+ "SELECT * FROM agent_tasks WHERE agentId = ? AND status = 'pending' ORDER BY createdAt ASC LIMIT 1",
216
+ )
217
+ .get(agentId);
218
+ return row ? rowToAgentTask(row) : null;
219
+ }
220
+
221
+ export function startTask(taskId: string): AgentTask | null {
222
+ const row = getDb()
223
+ .prepare<AgentTaskRow, [string]>(
224
+ `UPDATE agent_tasks SET status = 'in_progress', lastUpdatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
225
+ WHERE id = ? RETURNING *`,
226
+ )
227
+ .get(taskId);
228
+ return row ? rowToAgentTask(row) : null;
229
+ }
230
+
231
+ export function getTaskById(id: string): AgentTask | null {
232
+ const row = taskQueries.getById().get(id);
233
+ return row ? rowToAgentTask(row) : null;
234
+ }
235
+
236
+ export function getTasksByAgentId(agentId: string): AgentTask[] {
237
+ return taskQueries.getByAgentId().all(agentId).map(rowToAgentTask);
238
+ }
239
+
240
+ export function getTasksByStatus(status: AgentTaskStatus): AgentTask[] {
241
+ return taskQueries.getByStatus().all(status).map(rowToAgentTask);
242
+ }
243
+
244
+ export function getAllTasks(status?: AgentTaskStatus): AgentTask[] {
245
+ if (status) {
246
+ return getDb()
247
+ .prepare<AgentTaskRow, [AgentTaskStatus]>(
248
+ "SELECT * FROM agent_tasks WHERE status = ? ORDER BY lastUpdatedAt DESC",
249
+ )
250
+ .all(status)
251
+ .map(rowToAgentTask);
252
+ }
253
+ return getDb()
254
+ .prepare<AgentTaskRow, []>("SELECT * FROM agent_tasks ORDER BY lastUpdatedAt DESC")
255
+ .all()
256
+ .map(rowToAgentTask);
257
+ }
258
+
259
+ export function completeTask(id: string, output?: string): AgentTask | null {
260
+ const finishedAt = new Date().toISOString();
261
+ let row = taskQueries.updateStatus().get("completed", finishedAt, id);
262
+ if (!row) return null;
263
+
264
+ if (output) {
265
+ row = taskQueries.setOutput().get(output, id);
266
+ }
267
+
268
+ return row ? rowToAgentTask(row) : null;
269
+ }
270
+
271
+ export function failTask(id: string, reason: string): AgentTask | null {
272
+ const finishedAt = new Date().toISOString();
273
+ const row = taskQueries.setFailure().get(reason, finishedAt, id);
274
+ return row ? rowToAgentTask(row) : null;
275
+ }
276
+
277
+ export function deleteTask(id: string): boolean {
278
+ const result = getDb().run("DELETE FROM agent_tasks WHERE id = ?", [id]);
279
+ return result.changes > 0;
280
+ }
281
+
282
+ export function updateTaskProgress(id: string, progress: string): AgentTask | null {
283
+ const row = taskQueries.setProgress().get(progress, id);
284
+ return row ? rowToAgentTask(row) : null;
285
+ }
286
+
287
+ // ============================================================================
288
+ // Combined Queries (Agent with Tasks)
289
+ // ============================================================================
290
+
291
+ export function getAgentWithTasks(id: string): AgentWithTasks | null {
292
+ const txn = getDb().transaction(() => {
293
+ const agent = getAgentById(id);
294
+ if (!agent) return null;
295
+
296
+ const tasks = getTasksByAgentId(id);
297
+ return { ...agent, tasks };
298
+ });
299
+
300
+ return txn();
301
+ }
302
+
303
+ export function getAllAgentsWithTasks(): AgentWithTasks[] {
304
+ const txn = getDb().transaction(() => {
305
+ const agents = getAllAgents();
306
+ return agents.map((agent) => ({
307
+ ...agent,
308
+ tasks: getTasksByAgentId(agent.id),
309
+ }));
310
+ });
311
+
312
+ return txn();
313
+ }
package/src/claude.ts ADDED
@@ -0,0 +1,80 @@
1
+ import { formatDuration } from "date-fns";
2
+
3
+ type RunCladeOptions = {
4
+ msg: string;
5
+ headless?: boolean;
6
+ additionalArgs?: string[];
7
+ };
8
+
9
+ export const runClaude = async (opts: RunCladeOptions) => {
10
+ const CMD = ["claude"];
11
+
12
+ if (opts.headless) {
13
+ CMD.push("--verbose");
14
+ CMD.push("--output-format");
15
+ CMD.push("stream-json");
16
+ CMD.push("-p");
17
+ CMD.push(opts.msg);
18
+ }
19
+
20
+ if (opts.additionalArgs && opts.additionalArgs.length > 0) {
21
+ CMD.push(...opts.additionalArgs);
22
+ }
23
+
24
+ console.debug(`[debug] Running in ${opts.headless ? "headless" : "normal"} mode`);
25
+
26
+ const proc = Bun.spawn(CMD, {
27
+ env: process.env,
28
+ stdout: opts.headless ? "pipe" : "inherit",
29
+ stdin: opts.headless ? undefined : "inherit",
30
+ onExit(_subprocess, exitCode, _signalCode, error) {
31
+ console.debug(`[debug] Process exited with code ${exitCode}`);
32
+
33
+ if (error) {
34
+ console.error("[error]", error);
35
+ }
36
+ },
37
+ });
38
+
39
+ if (opts.headless) {
40
+ if (proc.stdout) {
41
+ for await (const chunk of proc.stdout) {
42
+ // Uint8Array<ArrayBuffer>
43
+ const lines = new TextDecoder().decode(chunk).split("\n");
44
+
45
+ for (const line of lines) {
46
+ if (line.trim() === "") continue;
47
+
48
+ try {
49
+ const json = JSON.parse(line.trim());
50
+ console.log(json);
51
+ } catch (_e) {
52
+ console.error("[error] Failed to parse line:", line.trim());
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+
59
+ await proc.exited;
60
+ const usage = proc.resourceUsage();
61
+
62
+ if (usage) {
63
+ const memMB = usage.maxRSS / (1024 * 1024);
64
+
65
+ const cpuUserSec = Number(usage.cpuTime.user) / 1_000_000;
66
+ const cpuSysSec = Number(usage.cpuTime.system) / 1_000_000;
67
+ const cpuTotSec = Number(usage.cpuTime.total) / 1_000_000;
68
+
69
+ console.debug(`[debug] Max RSS: ${memMB.toFixed(2)} MB`);
70
+ console.debug("[debug] CPU Time");
71
+ console.debug(`[debug] > User ${formatDuration({ seconds: cpuUserSec })}`);
72
+ console.debug(`[debug] > System ${formatDuration({ seconds: cpuSysSec })}`);
73
+ console.debug(`[debug] > Total ${formatDuration({ seconds: cpuTotSec })}`);
74
+ } else {
75
+ console.warn("[warn] Resource usage information is not available.");
76
+ }
77
+
78
+ const elapsedSec = Bun.nanoseconds() / 1_000_000_000;
79
+ console.debug(`[debug] Total elapsed time: ${formatDuration({ seconds: elapsedSec })}`);
80
+ };