@desplega.ai/agent-swarm 1.0.1 → 1.2.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.
@@ -0,0 +1,17 @@
1
+ [Unit]
2
+ Description=Agent Swarm MCP Server
3
+ After=network.target
4
+
5
+ [Service]
6
+ Type=simple
7
+ User=root
8
+ Group=root
9
+ WorkingDirectory=/opt/agent-swarm
10
+ ExecStart=/usr/local/bin/bun run start:http
11
+ ExecStartPost=/bin/sh -c 'sleep 2 && curl -sf http://localhost:3013/health || exit 1'
12
+ Restart=always
13
+ RestartSec=5
14
+ EnvironmentFile=/opt/agent-swarm/.env
15
+
16
+ [Install]
17
+ WantedBy=multi-user.target
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { $ } from "bun";
4
+
5
+ const APP_DIR = "/opt/agent-swarm";
6
+ const SERVICE_FILE = "/etc/systemd/system/agent-swarm.service";
7
+ const SCRIPT_DIR = import.meta.dir;
8
+ const PROJECT_DIR = `${SCRIPT_DIR}/..`;
9
+
10
+ // Detect bun path
11
+ const bunPath = (await $`which bun`.text()).trim();
12
+ console.log(`Using bun at: ${bunPath}`);
13
+
14
+ // Copy project files
15
+ await $`mkdir -p ${APP_DIR}`;
16
+ await $`cp -r ${PROJECT_DIR}/src ${APP_DIR}/`;
17
+ await $`cp ${PROJECT_DIR}/package.json ${PROJECT_DIR}/bun.lock ${PROJECT_DIR}/tsconfig.json ${APP_DIR}/`;
18
+
19
+ // Install dependencies
20
+ await $`cd ${APP_DIR} && bun install --frozen-lockfile --production`;
21
+
22
+ // Create .env if not exists
23
+ const envFile = Bun.file(`${APP_DIR}/.env`);
24
+ if (!(await envFile.exists())) {
25
+ await Bun.write(envFile, `PORT=3013
26
+ API_KEY=
27
+ `);
28
+ console.log("Created .env - set API_KEY for authentication");
29
+ }
30
+
31
+ // Set ownership
32
+ await $`chown -R root:root ${APP_DIR}`;
33
+
34
+ // Install systemd service with detected bun path
35
+ const serviceContent = `[Unit]
36
+ Description=Agent Swarm MCP Server
37
+ After=network.target
38
+
39
+ [Service]
40
+ Type=simple
41
+ User=root
42
+ Group=root
43
+ WorkingDirectory=${APP_DIR}
44
+ ExecStart=${bunPath} run start:http
45
+ ExecStartPost=/bin/sh -c 'sleep 2 && curl -sf http://localhost:3013/health || exit 1'
46
+ Restart=always
47
+ RestartSec=5
48
+ EnvironmentFile=${APP_DIR}/.env
49
+
50
+ [Install]
51
+ WantedBy=multi-user.target
52
+ `;
53
+
54
+ await Bun.write(SERVICE_FILE, serviceContent);
55
+
56
+ // Healthcheck service (runs curl, restarts main service on failure)
57
+ const healthcheckService = `[Unit]
58
+ Description=Agent Swarm Health Check
59
+
60
+ [Service]
61
+ Type=oneshot
62
+ ExecStart=/bin/sh -c 'curl -sf http://localhost:3013/health || systemctl restart agent-swarm'
63
+ `;
64
+
65
+ // Timer to run healthcheck every 30 seconds
66
+ const healthcheckTimer = `[Unit]
67
+ Description=Agent Swarm Health Check Timer
68
+
69
+ [Timer]
70
+ OnBootSec=30s
71
+ OnUnitActiveSec=30s
72
+
73
+ [Install]
74
+ WantedBy=timers.target
75
+ `;
76
+
77
+ await Bun.write("/etc/systemd/system/agent-swarm-healthcheck.service", healthcheckService);
78
+ await Bun.write("/etc/systemd/system/agent-swarm-healthcheck.timer", healthcheckTimer);
79
+
80
+ await $`systemctl daemon-reload`;
81
+ await $`systemctl enable agent-swarm agent-swarm-healthcheck.timer`;
82
+ await $`systemctl restart agent-swarm`;
83
+ await $`systemctl start agent-swarm-healthcheck.timer`;
84
+
85
+ console.log("Installed and running with health checks every 30s.");
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { $ } from "bun";
4
+ import * as readline from "node:readline";
5
+
6
+ const DB_PATH = "/opt/agent-swarm/agent-swarm-db.sqlite";
7
+ const SSH_HOST = process.argv[2] || "hetzner";
8
+
9
+ console.log(`Connected to ${SSH_HOST}:${DB_PATH}`);
10
+ console.log("Type SQL queries or .tables, .schema, etc. Ctrl+C to exit.\n");
11
+
12
+ const rl = readline.createInterface({
13
+ input: process.stdin,
14
+ output: process.stdout,
15
+ prompt: "sqlite> ",
16
+ });
17
+
18
+ rl.prompt();
19
+
20
+ rl.on("line", async (line) => {
21
+ const query = line.trim();
22
+ if (!query) {
23
+ rl.prompt();
24
+ return;
25
+ }
26
+
27
+ try {
28
+ // Escape single quotes in query and wrap in single quotes for remote shell
29
+ const escaped = query.replace(/'/g, "'\\''");
30
+ const result = await $`ssh ${SSH_HOST} sqlite3 -header -column ${DB_PATH} ${"'" + escaped + "'"}`.text();
31
+ if (result) console.log(result);
32
+ } catch (e: any) {
33
+ console.error(e.stderr?.toString() || e.message);
34
+ }
35
+
36
+ rl.prompt();
37
+ });
38
+
39
+ rl.on("close", () => {
40
+ console.log("\nBye!");
41
+ process.exit(0);
42
+ });
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { $ } from "bun";
4
+
5
+ await $`systemctl stop agent-swarm agent-swarm-healthcheck.timer`.nothrow();
6
+ await $`systemctl disable agent-swarm agent-swarm-healthcheck.timer`.nothrow();
7
+ await $`rm -f /etc/systemd/system/agent-swarm.service`;
8
+ await $`rm -f /etc/systemd/system/agent-swarm-healthcheck.service`;
9
+ await $`rm -f /etc/systemd/system/agent-swarm-healthcheck.timer`;
10
+ await $`systemctl daemon-reload`;
11
+
12
+ console.log("Service removed. Data remains at /opt/agent-swarm");
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { $ } from "bun";
4
+
5
+ const APP_DIR = "/opt/agent-swarm";
6
+ const SCRIPT_DIR = import.meta.dir;
7
+ const PROJECT_DIR = `${SCRIPT_DIR}/..`;
8
+
9
+ console.log("Updating agent-swarm...");
10
+
11
+ // Copy project files
12
+ await $`cp -r ${PROJECT_DIR}/src ${APP_DIR}/`;
13
+ await $`cp ${PROJECT_DIR}/package.json ${PROJECT_DIR}/bun.lock ${PROJECT_DIR}/tsconfig.json ${APP_DIR}/`;
14
+
15
+ // Install dependencies
16
+ await $`cd ${APP_DIR} && bun install --frozen-lockfile --production`;
17
+
18
+ // Restart service
19
+ await $`systemctl restart agent-swarm`;
20
+
21
+ console.log("Updated and restarted.");
@@ -0,0 +1,35 @@
1
+ # Docker Compose for Agent Swarm Worker
2
+ #
3
+ # Usage:
4
+ # docker-compose -f docker-compose.worker.yml up --build
5
+ #
6
+ # Required environment variables (in .env.docker or passed directly):
7
+ # - CLAUDE_CODE_OAUTH_TOKEN: OAuth token for Claude CLI
8
+ # - API_KEY: API key for MCP server authentication
9
+ #
10
+ # Optional environment variables:
11
+ # - AGENT_ID: UUID for agent identification (assigned on join if not set)
12
+ # - MCP_BASE_URL: MCP server URL (default: http://host.docker.internal:3013)
13
+ # - SESSION_ID: Folder name for logs (auto-generated if not provided)
14
+ # - WORKER_YOLO: If "true", continue on errors (default: false)
15
+
16
+ services:
17
+ worker:
18
+ build:
19
+ context: .
20
+ dockerfile: Dockerfile.worker
21
+ environment:
22
+ - CLAUDE_CODE_OAUTH_TOKEN=${CLAUDE_CODE_OAUTH_TOKEN}
23
+ - API_KEY=${API_KEY}
24
+ - AGENT_ID=${AGENT_ID:-}
25
+ # Use host.docker.internal to reach host's localhost on Mac/Windows
26
+ # On Linux, use --add-host=host.docker.internal:host-gateway or --network host
27
+ - MCP_BASE_URL=${MCP_BASE_URL:-http://host.docker.internal:3013}
28
+ - SESSION_ID=${SESSION_ID:-}
29
+ - WORKER_YOLO=${WORKER_YOLO:-false}
30
+ volumes:
31
+ - ./logs:/logs
32
+ # Optional: mount a workspace directory for persistent work
33
+ # - ./workspace:/workspace
34
+ restart: unless-stopped
35
+ # No port mappings needed - worker only makes outbound connections
@@ -0,0 +1,62 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # Validate required environment variables
5
+ if [ -z "$CLAUDE_CODE_OAUTH_TOKEN" ]; then
6
+ echo "Error: CLAUDE_CODE_OAUTH_TOKEN environment variable is required"
7
+ exit 1
8
+ fi
9
+
10
+ if [ -z "$API_KEY" ]; then
11
+ echo "Error: API_KEY environment variable is required"
12
+ exit 1
13
+ fi
14
+
15
+ MCP_URL="${MCP_BASE_URL:-http://host.docker.internal:3013}"
16
+
17
+ echo "=== Agent Swarm Worker ==="
18
+ echo "Agent ID: ${AGENT_ID:-<not set>}"
19
+ echo "MCP Base URL: $MCP_URL"
20
+ echo "YOLO Mode: ${WORKER_YOLO:-false}"
21
+ echo "Session ID: ${SESSION_ID:-<auto-generated>}"
22
+ echo "Working Directory: /workspace"
23
+ echo "=========================="
24
+
25
+ # Create .mcp.json in /workspace (project-level config)
26
+ echo "Creating MCP config in /workspace..."
27
+ if [ -n "$AGENT_ID" ]; then
28
+ # With AGENT_ID header
29
+ cat > /workspace/.mcp.json << EOF
30
+ {
31
+ "mcpServers": {
32
+ "agent-swarm": {
33
+ "type": "http",
34
+ "url": "${MCP_URL}/mcp",
35
+ "headers": {
36
+ "Authorization": "Bearer ${API_KEY}",
37
+ "X-Agent-ID": "${AGENT_ID}"
38
+ }
39
+ }
40
+ }
41
+ }
42
+ EOF
43
+ else
44
+ # Without AGENT_ID header
45
+ cat > /workspace/.mcp.json << EOF
46
+ {
47
+ "mcpServers": {
48
+ "agent-swarm": {
49
+ "type": "http",
50
+ "url": "${MCP_URL}/mcp",
51
+ "headers": {
52
+ "Authorization": "Bearer ${API_KEY}"
53
+ }
54
+ }
55
+ }
56
+ }
57
+ EOF
58
+ fi
59
+
60
+ # Run the worker using compiled binary
61
+ echo "Starting worker..."
62
+ exec /usr/local/bin/agent-swarm worker "$@"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@desplega.ai/agent-swarm",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "description": "Agent orchestration layer MCP for Claude Code, Codex, Gemini CLI, and more!",
5
5
  "module": "src/stdio.ts",
6
6
  "type": "module",
@@ -17,6 +17,8 @@
17
17
  "hook": "bun src/hooks/hook.ts",
18
18
  "claude": "bun src/cli.tsx claude",
19
19
  "claude:headless": "bun src/cli.tsx claude --headless",
20
+ "worker": "bun src/cli.tsx worker",
21
+ "worker:yolo": "bun src/cli.tsx worker --yolo",
20
22
  "dev": "bun --hot src/stdio.ts",
21
23
  "dev:http": "bun --hot src/http.ts",
22
24
  "start": "bun src/stdio.ts",
@@ -25,7 +27,11 @@
25
27
  "inspector:http": "bunx @modelcontextprotocol/inspector --transport http http://localhost:3013/mcp",
26
28
  "lint": "biome check src",
27
29
  "lint:fix": "biome check --write src",
28
- "format": "biome format --write src"
30
+ "format": "biome format --write src",
31
+ "build:binary": "bun build ./src/cli.tsx --compile --target=bun-linux-x64 --outfile ./dist/agent-swarm",
32
+ "build:binary:arm64": "bun build ./src/cli.tsx --compile --target=bun-linux-arm64 --outfile ./dist/agent-swarm",
33
+ "docker:build:worker": "docker build -f Dockerfile.worker -t agent-swarm-worker .",
34
+ "docker:run:worker": "docker run --rm -it --env-file .env.docker -v ./logs:/logs agent-swarm-worker"
29
35
  },
30
36
  "devDependencies": {
31
37
  "@biomejs/biome": "^2.3.9",
@@ -41,6 +47,7 @@
41
47
  "date-fns": "^4.1.0",
42
48
  "ink": "^6.5.1",
43
49
  "react": "^19.2.3",
50
+ "react-devtools-core": "^7.0.1",
44
51
  "zod": "^4.2.1"
45
52
  }
46
53
  }
package/src/be/db.ts CHANGED
@@ -1,9 +1,17 @@
1
1
  import { Database } from "bun:sqlite";
2
- import type { Agent, AgentStatus, AgentTask, AgentTaskStatus, AgentWithTasks } from "../types";
2
+ import type {
3
+ Agent,
4
+ AgentLog,
5
+ AgentLogEventType,
6
+ AgentStatus,
7
+ AgentTask,
8
+ AgentTaskStatus,
9
+ AgentWithTasks,
10
+ } from "../types";
3
11
 
4
12
  let db: Database | null = null;
5
13
 
6
- export function initDb(dbPath = "./cc-orch.sqlite"): Database {
14
+ export function initDb(dbPath = "./agent-swarm-db.sqlite"): Database {
7
15
  if (db) {
8
16
  return db;
9
17
  }
@@ -38,6 +46,22 @@ export function initDb(dbPath = "./cc-orch.sqlite"): Database {
38
46
 
39
47
  CREATE INDEX IF NOT EXISTS idx_agent_tasks_agentId ON agent_tasks(agentId);
40
48
  CREATE INDEX IF NOT EXISTS idx_agent_tasks_status ON agent_tasks(status);
49
+
50
+ CREATE TABLE IF NOT EXISTS agent_log (
51
+ id TEXT PRIMARY KEY,
52
+ eventType TEXT NOT NULL,
53
+ agentId TEXT,
54
+ taskId TEXT,
55
+ oldValue TEXT,
56
+ newValue TEXT,
57
+ metadata TEXT,
58
+ createdAt TEXT NOT NULL
59
+ );
60
+
61
+ CREATE INDEX IF NOT EXISTS idx_agent_log_agentId ON agent_log(agentId);
62
+ CREATE INDEX IF NOT EXISTS idx_agent_log_taskId ON agent_log(taskId);
63
+ CREATE INDEX IF NOT EXISTS idx_agent_log_eventType ON agent_log(eventType);
64
+ CREATE INDEX IF NOT EXISTS idx_agent_log_createdAt ON agent_log(createdAt);
41
65
  `);
42
66
 
43
67
  return db;
@@ -105,6 +129,9 @@ export function createAgent(
105
129
  const id = agent.id ?? crypto.randomUUID();
106
130
  const row = agentQueries.insert().get(id, agent.name, agent.isLead ? 1 : 0, agent.status);
107
131
  if (!row) throw new Error("Failed to create agent");
132
+ try {
133
+ createLogEntry({ eventType: "agent_joined", agentId: id, newValue: agent.status });
134
+ } catch {}
108
135
  return rowToAgent(row);
109
136
  }
110
137
 
@@ -118,11 +145,28 @@ export function getAllAgents(): Agent[] {
118
145
  }
119
146
 
120
147
  export function updateAgentStatus(id: string, status: AgentStatus): Agent | null {
148
+ const oldAgent = getAgentById(id);
121
149
  const row = agentQueries.updateStatus().get(status, id);
150
+ if (row && oldAgent) {
151
+ try {
152
+ createLogEntry({
153
+ eventType: "agent_status_change",
154
+ agentId: id,
155
+ oldValue: oldAgent.status,
156
+ newValue: status,
157
+ });
158
+ } catch {}
159
+ }
122
160
  return row ? rowToAgent(row) : null;
123
161
  }
124
162
 
125
163
  export function deleteAgent(id: string): boolean {
164
+ const agent = getAgentById(id);
165
+ if (agent) {
166
+ try {
167
+ createLogEntry({ eventType: "agent_left", agentId: id, oldValue: agent.status });
168
+ } catch {}
169
+ }
126
170
  const result = getDb().run("DELETE FROM agents WHERE id = ?", [id]);
127
171
  return result.changes > 0;
128
172
  }
@@ -206,6 +250,9 @@ export function createTask(agentId: string, task: string): AgentTask {
206
250
  const id = crypto.randomUUID();
207
251
  const row = taskQueries.insert().get(id, agentId, task, "pending");
208
252
  if (!row) throw new Error("Failed to create task");
253
+ try {
254
+ createLogEntry({ eventType: "task_created", agentId, taskId: id, newValue: "pending" });
255
+ } catch {}
209
256
  return rowToAgentTask(row);
210
257
  }
211
258
 
@@ -219,12 +266,24 @@ export function getPendingTaskForAgent(agentId: string): AgentTask | null {
219
266
  }
220
267
 
221
268
  export function startTask(taskId: string): AgentTask | null {
269
+ const oldTask = getTaskById(taskId);
222
270
  const row = getDb()
223
271
  .prepare<AgentTaskRow, [string]>(
224
272
  `UPDATE agent_tasks SET status = 'in_progress', lastUpdatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
225
273
  WHERE id = ? RETURNING *`,
226
274
  )
227
275
  .get(taskId);
276
+ if (row && oldTask) {
277
+ try {
278
+ createLogEntry({
279
+ eventType: "task_status_change",
280
+ taskId,
281
+ agentId: row.agentId,
282
+ oldValue: oldTask.status,
283
+ newValue: "in_progress",
284
+ });
285
+ } catch {}
286
+ }
228
287
  return row ? rowToAgentTask(row) : null;
229
288
  }
230
289
 
@@ -257,6 +316,7 @@ export function getAllTasks(status?: AgentTaskStatus): AgentTask[] {
257
316
  }
258
317
 
259
318
  export function completeTask(id: string, output?: string): AgentTask | null {
319
+ const oldTask = getTaskById(id);
260
320
  const finishedAt = new Date().toISOString();
261
321
  let row = taskQueries.updateStatus().get("completed", finishedAt, id);
262
322
  if (!row) return null;
@@ -265,12 +325,37 @@ export function completeTask(id: string, output?: string): AgentTask | null {
265
325
  row = taskQueries.setOutput().get(output, id);
266
326
  }
267
327
 
328
+ if (row && oldTask) {
329
+ try {
330
+ createLogEntry({
331
+ eventType: "task_status_change",
332
+ taskId: id,
333
+ agentId: row.agentId,
334
+ oldValue: oldTask.status,
335
+ newValue: "completed",
336
+ });
337
+ } catch {}
338
+ }
339
+
268
340
  return row ? rowToAgentTask(row) : null;
269
341
  }
270
342
 
271
343
  export function failTask(id: string, reason: string): AgentTask | null {
344
+ const oldTask = getTaskById(id);
272
345
  const finishedAt = new Date().toISOString();
273
346
  const row = taskQueries.setFailure().get(reason, finishedAt, id);
347
+ if (row && oldTask) {
348
+ try {
349
+ createLogEntry({
350
+ eventType: "task_status_change",
351
+ taskId: id,
352
+ agentId: row.agentId,
353
+ oldValue: oldTask.status,
354
+ newValue: "failed",
355
+ metadata: { reason },
356
+ });
357
+ } catch {}
358
+ }
274
359
  return row ? rowToAgentTask(row) : null;
275
360
  }
276
361
 
@@ -281,6 +366,16 @@ export function deleteTask(id: string): boolean {
281
366
 
282
367
  export function updateTaskProgress(id: string, progress: string): AgentTask | null {
283
368
  const row = taskQueries.setProgress().get(progress, id);
369
+ if (row) {
370
+ try {
371
+ createLogEntry({
372
+ eventType: "task_progress",
373
+ taskId: id,
374
+ agentId: row.agentId,
375
+ newValue: progress,
376
+ });
377
+ } catch {}
378
+ }
284
379
  return row ? rowToAgentTask(row) : null;
285
380
  }
286
381
 
@@ -311,3 +406,110 @@ export function getAllAgentsWithTasks(): AgentWithTasks[] {
311
406
 
312
407
  return txn();
313
408
  }
409
+
410
+ // ============================================================================
411
+ // Agent Log Queries
412
+ // ============================================================================
413
+
414
+ type AgentLogRow = {
415
+ id: string;
416
+ eventType: AgentLogEventType;
417
+ agentId: string | null;
418
+ taskId: string | null;
419
+ oldValue: string | null;
420
+ newValue: string | null;
421
+ metadata: string | null;
422
+ createdAt: string;
423
+ };
424
+
425
+ function rowToAgentLog(row: AgentLogRow): AgentLog {
426
+ return {
427
+ id: row.id,
428
+ eventType: row.eventType,
429
+ agentId: row.agentId ?? undefined,
430
+ taskId: row.taskId ?? undefined,
431
+ oldValue: row.oldValue ?? undefined,
432
+ newValue: row.newValue ?? undefined,
433
+ metadata: row.metadata ?? undefined,
434
+ createdAt: row.createdAt,
435
+ };
436
+ }
437
+
438
+ export const logQueries = {
439
+ insert: () =>
440
+ getDb().prepare<
441
+ AgentLogRow,
442
+ [string, string, string | null, string | null, string | null, string | null, string | null]
443
+ >(
444
+ `INSERT INTO agent_log (id, eventType, agentId, taskId, oldValue, newValue, metadata, createdAt)
445
+ VALUES (?, ?, ?, ?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) RETURNING *`,
446
+ ),
447
+
448
+ getByAgentId: () =>
449
+ getDb().prepare<AgentLogRow, [string]>(
450
+ "SELECT * FROM agent_log WHERE agentId = ? ORDER BY createdAt DESC",
451
+ ),
452
+
453
+ getByTaskId: () =>
454
+ getDb().prepare<AgentLogRow, [string]>(
455
+ "SELECT * FROM agent_log WHERE taskId = ? ORDER BY createdAt DESC",
456
+ ),
457
+
458
+ getByEventType: () =>
459
+ getDb().prepare<AgentLogRow, [string]>(
460
+ "SELECT * FROM agent_log WHERE eventType = ? ORDER BY createdAt DESC",
461
+ ),
462
+
463
+ getAll: () => getDb().prepare<AgentLogRow, []>("SELECT * FROM agent_log ORDER BY createdAt DESC"),
464
+ };
465
+
466
+ export function createLogEntry(entry: {
467
+ eventType: AgentLogEventType;
468
+ agentId?: string;
469
+ taskId?: string;
470
+ oldValue?: string;
471
+ newValue?: string;
472
+ metadata?: Record<string, unknown>;
473
+ }): AgentLog {
474
+ const id = crypto.randomUUID();
475
+ const row = logQueries
476
+ .insert()
477
+ .get(
478
+ id,
479
+ entry.eventType,
480
+ entry.agentId ?? null,
481
+ entry.taskId ?? null,
482
+ entry.oldValue ?? null,
483
+ entry.newValue ?? null,
484
+ entry.metadata ? JSON.stringify(entry.metadata) : null,
485
+ );
486
+ if (!row) throw new Error("Failed to create log entry");
487
+ return rowToAgentLog(row);
488
+ }
489
+
490
+ export function getLogsByAgentId(agentId: string): AgentLog[] {
491
+ return logQueries.getByAgentId().all(agentId).map(rowToAgentLog);
492
+ }
493
+
494
+ export function getLogsByTaskId(taskId: string): AgentLog[] {
495
+ return logQueries.getByTaskId().all(taskId).map(rowToAgentLog);
496
+ }
497
+
498
+ export function getLogsByTaskIdChronological(taskId: string): AgentLog[] {
499
+ return getDb()
500
+ .prepare<AgentLogRow, [string]>(
501
+ "SELECT * FROM agent_log WHERE taskId = ? ORDER BY createdAt ASC",
502
+ )
503
+ .all(taskId)
504
+ .map(rowToAgentLog);
505
+ }
506
+
507
+ export function getAllLogs(limit?: number): AgentLog[] {
508
+ if (limit) {
509
+ return getDb()
510
+ .prepare<AgentLogRow, [number]>("SELECT * FROM agent_log ORDER BY createdAt DESC LIMIT ?")
511
+ .all(limit)
512
+ .map(rowToAgentLog);
513
+ }
514
+ return logQueries.getAll().all().map(rowToAgentLog);
515
+ }