@desplega.ai/agent-swarm 1.9.0 → 1.10.2

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.
@@ -22,7 +22,9 @@
22
22
  "Bash(bun run build:*)",
23
23
  "Bash(pnpm run build:*)",
24
24
  "WebFetch(domain:geminicli.com)",
25
- "Bash(bun run docs:mcp:*)"
25
+ "Bash(bun run docs:mcp:*)",
26
+ "Bash(bun test:*)",
27
+ "Bash(curl:*)"
26
28
  ]
27
29
  },
28
30
  "enableAllProjectMcpServers": true,
package/Dockerfile CHANGED
@@ -1,16 +1,54 @@
1
- FROM oven/bun:1-alpine
1
+ # Agent Swarm MCP Server Dockerfile
2
+ # Multi-stage build: compiles to standalone binary for minimal image size
3
+
4
+ # Stage 1: Build the binary
5
+ FROM oven/bun:latest AS builder
6
+
7
+ WORKDIR /build
8
+
9
+ # Copy package files first for better layer caching
10
+ COPY package.json bun.lock* ./
11
+ RUN bun install --frozen-lockfile
12
+
13
+ # Copy source files
14
+ COPY src/ ./src/
15
+ COPY tsconfig.json ./
16
+
17
+ # Compile HTTP server to standalone binary
18
+ RUN bun build ./src/http.ts --compile --outfile ./agent-swarm-api
19
+
20
+ # Stage 2: Minimal runtime image
21
+ FROM debian:bookworm-slim
22
+
23
+ # Install minimal dependencies (for bun:sqlite and networking)
24
+ RUN apt-get update && apt-get install -y --no-install-recommends \
25
+ ca-certificates \
26
+ wget \
27
+ && rm -rf /var/lib/apt/lists/*
2
28
 
3
29
  WORKDIR /app
4
30
 
5
- COPY package.json bun.lock ./
6
- RUN bun install --frozen-lockfile --production
31
+ # Copy compiled binary from builder
32
+ COPY --from=builder /build/agent-swarm-api /usr/local/bin/agent-swarm-api
33
+ RUN chmod +x /usr/local/bin/agent-swarm-api
7
34
 
8
- COPY . .
35
+ # Copy package.json for version info
36
+ COPY package.json ./
37
+
38
+ # Database will be created in /app (mount /app for persistence)
39
+ VOLUME /app
9
40
 
10
41
  ENV PORT=3013
42
+
11
43
  EXPOSE 3013
12
44
 
45
+ # Health check
13
46
  HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
14
47
  CMD wget -qO- http://localhost:3013/health || exit 1
15
48
 
16
- CMD ["bun", "run", "start:http"]
49
+ # Print version on startup and run the server
50
+ CMD echo "=== Agent Swarm API v$(cat /app/package.json | grep '\"version\"' | cut -d'"' -f4) ===" && \
51
+ echo "Port: $PORT" && \
52
+ echo "Database: /app/agent-swarm-db.sqlite" && \
53
+ echo "==============================" && \
54
+ exec /usr/local/bin/agent-swarm-api
package/Dockerfile.worker CHANGED
@@ -118,7 +118,7 @@ WORKDIR /workspace
118
118
  VOLUME ["/logs"]
119
119
 
120
120
  RUN mkdir -p ./personal ./shared
121
- VOLUME ["/workspace/personal" "/workspace/shared"]
121
+ VOLUME ["/workspace/personal", "/workspace/shared"]
122
122
 
123
123
  # Expose service port for PM2 processes
124
124
  EXPOSE 3000
package/README.md CHANGED
@@ -26,6 +26,21 @@ Agent Swarm MCP enables multi-agent coordination for AI coding assistants. It pr
26
26
  - **Service Discovery** - Register and discover background services
27
27
  - **Docker Workers** - Run isolated Claude workers in containers
28
28
  - **Lead/Worker Pattern** - Coordinate work with a lead agent and multiple workers
29
+ - **Dashboard UI** - Real-time monitoring dashboard for agents, tasks, and channels
30
+
31
+ ---
32
+
33
+ ## Dashboard UI
34
+
35
+ A React-based monitoring dashboard is available in the `ui/` directory. See [UI.md](./UI.md) for details.
36
+
37
+ ```bash
38
+ cd ui
39
+ pnpm install
40
+ pnpm run dev
41
+ ```
42
+
43
+ The dashboard runs at `http://localhost:5173` by default.
29
44
 
30
45
  ---
31
46
 
@@ -166,6 +181,7 @@ Full deployment options are documented in [DEPLOYMENT.md](./DEPLOYMENT.md).
166
181
 
167
182
  | Document | Description |
168
183
  |----------|-------------|
184
+ | [UI.md](./UI.md) | Dashboard UI overview |
169
185
  | [DEPLOYMENT.md](./DEPLOYMENT.md) | Docker, Docker Compose, systemd deployment |
170
186
  | [CONTRIBUTING.md](./CONTRIBUTING.md) | Development setup, code quality, project structure |
171
187
  | [MCP.md](./MCP.md) | MCP tools reference (auto-generated) |
package/UI.md ADDED
@@ -0,0 +1,40 @@
1
+ # Dashboard UI
2
+
3
+ A React-based monitoring dashboard for Agent Swarm.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ cd ui
9
+ pnpm install
10
+ pnpm run dev
11
+ ```
12
+
13
+ The dashboard runs at `http://localhost:5173`. Make sure the API server is running (`bun run start:http` on port 3013).
14
+
15
+ ## Features
16
+
17
+ - Real-time agent and task monitoring
18
+ - Channel-based messaging with threads
19
+ - Service registry and health status
20
+ - Dark/light theme with honeycomb aesthetic
21
+ - Responsive design (desktop and mobile)
22
+ - URL-based state for shareable links
23
+
24
+ ## Tabs
25
+
26
+ | Tab | Description |
27
+ |-----|-------------|
28
+ | **Agents** | View agents, their status (idle/busy/offline), and assigned tasks |
29
+ | **Tasks** | Browse and filter tasks by status, agent, or search query |
30
+ | **Chat** | Channel messaging between agents with thread support |
31
+ | **Services** | Monitor registered background services and health checks |
32
+
33
+ ## Configuration
34
+
35
+ Click the settings icon in the header to configure:
36
+
37
+ - **API URL** - The Agent Swarm API endpoint (default: `http://localhost:3013`)
38
+ - **API Key** - Optional authentication key (matches your `API_KEY` env var)
39
+
40
+ Settings are stored in localStorage.
package/deploy/prod-db.ts CHANGED
@@ -3,8 +3,8 @@
3
3
  import { $ } from "bun";
4
4
  import * as readline from "node:readline";
5
5
 
6
- const DB_PATH = "/opt/agent-swarm/agent-swarm-db.sqlite";
7
- const SSH_HOST = process.argv[2] || "hetzner";
6
+ const DB_PATH = "/var/lib/docker/volumes/agent-swarm-nrz8v0_swarm_api/_data/agent-swarm-db.sqlite";
7
+ const SSH_HOST = process.argv[2] || "swarm";
8
8
 
9
9
  console.log(`Connected to ${SSH_HOST}:${DB_PATH}`);
10
10
  console.log("Type SQL queries or .tables, .schema, etc. Ctrl+C to exit.\n");
@@ -10,16 +10,14 @@
10
10
 
11
11
  services:
12
12
  api:
13
- build:
14
- context: .
15
- dockerfile: Dockerfile
13
+ image: "ghcr.io/desplega-ai/agent-swarm:latest"
14
+ pull_policy: always
16
15
 
17
16
  environment:
18
- - ENV=${ENV:-development}
19
17
  - API_KEY=${API_KEY}
20
18
 
21
- - MCP_BASE_URL=${MCP_BASE_URL:-http://localhost:3013}
22
- - APP_URL=${APP_URL:-http://localhost:5175}
19
+ - MCP_BASE_URL=${MCP_BASE_URL}
20
+ - APP_URL=${APP_URL}
23
21
 
24
22
  # Optional: Enable Slack integration
25
23
  - SLACK_DISABLE=${SLACK_DISABLE:-false}
@@ -30,97 +28,102 @@ services:
30
28
  - "3013:3013"
31
29
 
32
30
  volumes:
33
- # For the sqlite database
34
31
  - swarm_api:/app
35
32
 
36
33
  restart: unless-stopped
37
34
 
38
- worker-1:
39
- build:
40
- context: .
41
- dockerfile: Dockerfile.worker
35
+ lead:
36
+ image: "ghcr.io/desplega-ai/agent-swarm-worker:latest"
37
+ pull_policy: always
42
38
 
43
39
  environment:
44
40
  - CLAUDE_CODE_OAUTH_TOKEN=${CLAUDE_CODE_OAUTH_TOKEN}
45
41
  - API_KEY=${API_KEY}
46
- - AGENT_ID=${AGENT_ID}
47
- - AGENT_ROLE=worker
42
+ # Replace by a valid UUID
43
+ - AGENT_ID=d454d1a5-4df9-49bd-8a89-e58d6a657dc3
44
+
45
+ # Important: Lead agent role
46
+ - AGENT_ROLE=lead
48
47
 
49
- - MCP_BASE_URL=${MCP_BASE_URL:-http://api:3013}
48
+ # - MCP_BASE_URL=${MCP_BASE_URL:-http://api:3013}
49
+ # If in the same Docker network, use the service name
50
+ - MCP_BASE_URL=http://api:3013
50
51
 
51
- # Optional environment variables
52
- - SESSION_ID=${SESSION_ID:-}
53
- - YOLO=${YOLO:-false}
54
- # GitHub credentials for git push and PR operations
52
+ - YOLO=true
55
53
  - GITHUB_TOKEN=${GITHUB_TOKEN:-}
56
54
  - GITHUB_EMAIL=${GITHUB_EMAIL:-}
57
55
  - GITHUB_NAME=${GITHUB_NAME:-}
58
- # Service registry URL (for service discovery)
59
56
  - SWARM_URL=${SWARM_URL:-localhost}
60
57
 
61
58
  ports:
62
- # Expose service port for PM2 processes
63
- - "${SERVICE_PORT:-3001}:3000"
59
+ - "${SERVICE_PORT:-3020}:3000"
64
60
 
65
61
  volumes:
66
62
  - swarm_logs:/logs
67
- # Optional: mount a workspace directory for persistent work
68
63
  - swarm_shared:/workspace/shared
69
- - swarm_worker_1/personal:/workspace/personal
64
+ - swarm_lead:/workspace/personal
70
65
 
71
66
  restart: unless-stopped
72
67
 
73
- worker-2:
74
- build:
75
- context: .
76
- dockerfile: Dockerfile.worker
68
+ worker-1:
69
+ image: "ghcr.io/desplega-ai/agent-swarm-worker:latest"
70
+ pull_policy: always
77
71
 
78
72
  environment:
79
73
  - CLAUDE_CODE_OAUTH_TOKEN=${CLAUDE_CODE_OAUTH_TOKEN}
80
74
  - API_KEY=${API_KEY}
81
- - AGENT_ID=${AGENT_ID_2}
75
+ # Replace by a valid UUID
76
+ - AGENT_ID=38d36438-58a0-45b5-8602-a5d52b07c2f1
82
77
  - AGENT_ROLE=worker
83
78
 
84
- - MCP_BASE_URL=${MCP_BASE_URL:-http://api:3013}
79
+ # - MCP_BASE_URL=${MCP_BASE_URL:-http://api:3013}
80
+ # If in the same Docker network, use the service name
81
+ - MCP_BASE_URL=http://api:3013
85
82
 
86
- # Same as above...
83
+ - YOLO=true
84
+ - GITHUB_TOKEN=${GITHUB_TOKEN:-}
85
+ - GITHUB_EMAIL=${GITHUB_EMAIL:-}
86
+ - GITHUB_NAME=${GITHUB_NAME:-}
87
+ - SWARM_URL=${SWARM_URL:-localhost}
87
88
 
88
89
  ports:
89
- # Expose service port for PM2 processes
90
- - "${SERVICE_PORT:-3002}:3000"
90
+ - "${SERVICE_PORT:-3021}:3000"
91
91
 
92
92
  volumes:
93
93
  - swarm_logs:/logs
94
94
  - swarm_shared:/workspace/shared
95
- - swarm_worker_2/personal:/workspace/personal
95
+ - swarm_worker_1:/workspace/personal
96
96
 
97
97
  restart: unless-stopped
98
98
 
99
- lead:
100
- build:
101
- context: .
102
- dockerfile: Dockerfile.worker
99
+ worker-2:
100
+ image: "ghcr.io/desplega-ai/agent-swarm-worker:latest"
101
+ pull_policy: always
103
102
 
104
103
  environment:
105
104
  - CLAUDE_CODE_OAUTH_TOKEN=${CLAUDE_CODE_OAUTH_TOKEN}
106
105
  - API_KEY=${API_KEY}
107
- - AGENT_ID=${AGENT_ID_LEAD}
108
-
109
- # Important: Lead agent role
110
- - AGENT_ROLE=lead
106
+ # Replace by a valid UUID
107
+ - AGENT_ID=c1e2f3a4-5678-90ab-cdef-1234567890ab
108
+ - AGENT_ROLE=worker
111
109
 
112
- - MCP_BASE_URL=${MCP_BASE_URL:-http://api:3013}
110
+ # - MCP_BASE_URL=${MCP_BASE_URL:-http://api:3013}
111
+ # If in the same Docker network, use the service name
112
+ - MCP_BASE_URL=http://api:3013
113
113
 
114
- # Same as above...
114
+ - YOLO=true
115
+ - GITHUB_TOKEN=${GITHUB_TOKEN:-}
116
+ - GITHUB_EMAIL=${GITHUB_EMAIL:-}
117
+ - GITHUB_NAME=${GITHUB_NAME:-}
118
+ - SWARM_URL=${SWARM_URL:-localhost}
115
119
 
116
120
  ports:
117
- # Expose service port for PM2 processes
118
- - "${SERVICE_PORT:-3003}:3000"
121
+ - "${SERVICE_PORT:-3022}:3000"
119
122
 
120
123
  volumes:
121
124
  - swarm_logs:/logs
122
125
  - swarm_shared:/workspace/shared
123
- - swarm_personal_lead:/workspace/personal
126
+ - swarm_worker_2:/workspace/personal
124
127
 
125
128
  restart: unless-stopped
126
129
 
@@ -131,7 +134,3 @@ volumes:
131
134
  swarm_lead:
132
135
  swarm_worker_1:
133
136
  swarm_worker_2:
134
-
135
- networks:
136
- default:
137
- driver: bridge
@@ -16,6 +16,9 @@ fi
16
16
  ROLE="${AGENT_ROLE:-worker}"
17
17
  MCP_URL="${MCP_BASE_URL:-http://host.docker.internal:3013}"
18
18
 
19
+ # Get version from compiled binary (extract just the version number)
20
+ VERSION=$(/usr/local/bin/agent-swarm version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || echo "unknown")
21
+
19
22
  # Determine YOLO mode based on role
20
23
  if [ "$ROLE" = "lead" ]; then
21
24
  YOLO_MODE="${LEAD_YOLO:-false}"
@@ -23,7 +26,7 @@ else
23
26
  YOLO_MODE="${WORKER_YOLO:-false}"
24
27
  fi
25
28
 
26
- echo "=== Agent Swarm ${ROLE^} ==="
29
+ echo "=== Agent Swarm ${ROLE^} v${VERSION} ==="
27
30
  echo "Agent ID: ${AGENT_ID:-<not set>}"
28
31
  echo "MCP Base URL: $MCP_URL"
29
32
  echo "YOLO Mode: $YOLO_MODE"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@desplega.ai/agent-swarm",
3
- "version": "1.9.0",
3
+ "version": "1.10.2",
4
4
  "description": "Agent orchestration layer MCP for Claude Code, Codex, Gemini CLI, and more!",
5
5
  "module": "src/http.ts",
6
6
  "type": "module",
@@ -0,0 +1,45 @@
1
+ ---
2
+ description: Review a task that has been offered to you and decide whether to accept or reject it
3
+ argument-hint: [taskId]
4
+ ---
5
+
6
+ # Review Offered Task
7
+
8
+ You have been offered a task. Your job is to review it and decide whether to accept or reject it based on your capabilities and current workload.
9
+
10
+ ## Workflow
11
+
12
+ 1. **Get task details**: Call the `get-task-details` tool with the provided `taskId` to understand what the task involves.
13
+
14
+ 2. **Evaluate the task**: Consider:
15
+ - Does this task match your capabilities?
16
+ - Do you have the necessary context or access to complete it?
17
+ - Is the task description clear enough to proceed?
18
+
19
+ 3. **Make a decision**:
20
+ - **Accept**: If you can complete this task, call `task-action` with `action: "accept"` and `taskId: "<taskId>"`. Then immediately use `/work-on-task <taskId>` to start working on it.
21
+ - **Reject**: If you cannot complete this task, call `task-action` with `action: "reject"`, `taskId: "<taskId>"`, and provide a `reason` explaining why you're rejecting it (e.g., "Task requires Python expertise which I don't have", "Task description is too vague").
22
+
23
+ ## Example Accept Flow
24
+
25
+ ```
26
+ 1. get-task-details taskId="abc-123"
27
+ 2. [Review the task details]
28
+ 3. task-action action="accept" taskId="abc-123"
29
+ 4. /work-on-task abc-123
30
+ ```
31
+
32
+ ## Example Reject Flow
33
+
34
+ ```
35
+ 1. get-task-details taskId="abc-123"
36
+ 2. [Review the task details]
37
+ 3. task-action action="reject" taskId="abc-123" reason="Task requires access to production database which I don't have"
38
+ 4. Reply "DONE" to end the session
39
+ ```
40
+
41
+ ## Important Notes
42
+
43
+ - Always provide a clear reason when rejecting a task - this helps the lead agent reassign it appropriately
44
+ - If you accept, you must immediately start working on the task using `/work-on-task`
45
+ - If you reject, the task returns to the unassigned pool for reassignment
@@ -89,13 +89,15 @@ When you assign tasks to workers, they might need to let them know to use some o
89
89
  - `/create-plan` - Useful command for workers to create a detailed plan for how they will approach and complete the task. Will store in the shared filesystem automatically, no need to tell them to do it.
90
90
  - `/implement-plan` - Useful command for workers to implement the plan they created for the task. It can be used to continue working on the implementation too (not just start it). Will store in the shared filesystem automatically, no need to tell them to do it.
91
91
 
92
- ## Communication Etiquette
93
-
94
- - You should ALWAYS follow-up to the user messages using the `/swarm-chat` command. You should also use it to communicate with workers when needed.
95
- - If you already provided an update to the user and nothing happened in the swarm, you should NOT spam the user with repeated updates. Only provide updates when something relevant happens.
96
-
97
92
  ## Filesystem
98
93
 
99
94
  You will have your own persisted directory at `/workspace/personal`. Use it to store any files you need to keep between sessions.
100
95
 
101
96
  If you want to share files with workers, use the shared `/workspace/shared` directory, which all agents in the swarm can access. The same way, workers can share files with you there. Take this into account when assigning tasks that require file access, or that you want check later, or pass to other workers.
97
+
98
+ ## Communication Etiquette
99
+
100
+ - ONLY follow-up if there are relevant updates (check history to avoid spamming), or if stated by the user (human). If not, avoid unnecessary messages.
101
+ - When communicating, ALWAYS use the `/swarm-chat` command. You may also use it to communicate with workers when needed, but that should be rare.
102
+ - If you already provided an update to the user and nothing happened in the swarm, you should NOT SPAM the user with repeated updates (e.g. do not send messages like "Ready to lead"). Only provide meaningful updates when something relevant happens.
103
+
@@ -54,3 +54,8 @@ To do so, use the `agent-swarm` MCP server and call the `join-swarm` with a name
54
54
  You will have your own persisted directory at `/workspace/personal`. Use it to store any files you need to keep between sessions.
55
55
 
56
56
  If you want to share files with workers and the lead, use the shared `/workspace/shared` directory, which all agents in the swarm can access. Make sure to use it if the task requires sharing files.
57
+
58
+ ## Communication Etiquette
59
+
60
+ - ONLY follow-up if clearly stated by the user or the lead. Do NOT send random updates about your status unless explicitly requested.
61
+ - When communicating, ALWAYS use the `/swarm-chat` command.
package/src/be/db.ts CHANGED
@@ -13,6 +13,7 @@ import type {
13
13
  ChannelType,
14
14
  Service,
15
15
  ServiceStatus,
16
+ SessionLog,
16
17
  } from "../types";
17
18
 
18
19
  let db: Database | null = null;
@@ -149,6 +150,21 @@ export function initDb(dbPath = "./agent-swarm-db.sqlite"): Database {
149
150
 
150
151
  CREATE INDEX IF NOT EXISTS idx_services_agentId ON services(agentId);
151
152
  CREATE INDEX IF NOT EXISTS idx_services_status ON services(status);
153
+
154
+ -- Session logs table (raw CLI output from runner)
155
+ CREATE TABLE IF NOT EXISTS session_logs (
156
+ id TEXT PRIMARY KEY,
157
+ taskId TEXT,
158
+ sessionId TEXT NOT NULL,
159
+ iteration INTEGER NOT NULL,
160
+ cli TEXT NOT NULL DEFAULT 'claude',
161
+ content TEXT NOT NULL,
162
+ lineNumber INTEGER NOT NULL,
163
+ createdAt TEXT NOT NULL
164
+ );
165
+
166
+ CREATE INDEX IF NOT EXISTS idx_session_logs_taskId ON session_logs(taskId);
167
+ CREATE INDEX IF NOT EXISTS idx_session_logs_sessionId ON session_logs(sessionId);
152
168
  `);
153
169
 
154
170
  // Seed default general channel if it doesn't exist
@@ -1919,3 +1935,88 @@ export function deleteServicesByAgentId(agentId: string): number {
1919
1935
  const result = getDb().run("DELETE FROM services WHERE agentId = ?", [agentId]);
1920
1936
  return result.changes;
1921
1937
  }
1938
+
1939
+ // ============================================================================
1940
+ // Session Log Operations (raw CLI output)
1941
+ // ============================================================================
1942
+
1943
+ type SessionLogRow = {
1944
+ id: string;
1945
+ taskId: string | null;
1946
+ sessionId: string;
1947
+ iteration: number;
1948
+ cli: string;
1949
+ content: string;
1950
+ lineNumber: number;
1951
+ createdAt: string;
1952
+ };
1953
+
1954
+ function rowToSessionLog(row: SessionLogRow): SessionLog {
1955
+ return {
1956
+ id: row.id,
1957
+ taskId: row.taskId ?? undefined,
1958
+ sessionId: row.sessionId,
1959
+ iteration: row.iteration,
1960
+ cli: row.cli,
1961
+ content: row.content,
1962
+ lineNumber: row.lineNumber,
1963
+ createdAt: row.createdAt,
1964
+ };
1965
+ }
1966
+
1967
+ export const sessionLogQueries = {
1968
+ insert: () =>
1969
+ getDb().prepare<SessionLogRow, [string, string | null, string, number, string, string, number]>(
1970
+ `INSERT INTO session_logs (id, taskId, sessionId, iteration, cli, content, lineNumber, createdAt)
1971
+ VALUES (?, ?, ?, ?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) RETURNING *`,
1972
+ ),
1973
+
1974
+ insertBatch: () =>
1975
+ getDb().prepare<null, [string, string | null, string, number, string, string, number]>(
1976
+ `INSERT INTO session_logs (id, taskId, sessionId, iteration, cli, content, lineNumber, createdAt)
1977
+ VALUES (?, ?, ?, ?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`,
1978
+ ),
1979
+
1980
+ getByTaskId: () =>
1981
+ getDb().prepare<SessionLogRow, [string]>(
1982
+ "SELECT * FROM session_logs WHERE taskId = ? ORDER BY iteration ASC, lineNumber ASC",
1983
+ ),
1984
+
1985
+ getBySessionId: () =>
1986
+ getDb().prepare<SessionLogRow, [string, number]>(
1987
+ "SELECT * FROM session_logs WHERE sessionId = ? AND iteration = ? ORDER BY lineNumber ASC",
1988
+ ),
1989
+ };
1990
+
1991
+ export function createSessionLogs(logs: {
1992
+ taskId?: string;
1993
+ sessionId: string;
1994
+ iteration: number;
1995
+ cli: string;
1996
+ lines: string[];
1997
+ }): void {
1998
+ const stmt = sessionLogQueries.insertBatch();
1999
+ getDb().transaction(() => {
2000
+ for (let i = 0; i < logs.lines.length; i++) {
2001
+ const line = logs.lines[i];
2002
+ if (line === undefined) continue;
2003
+ stmt.run(
2004
+ crypto.randomUUID(),
2005
+ logs.taskId ?? null,
2006
+ logs.sessionId,
2007
+ logs.iteration,
2008
+ logs.cli,
2009
+ line,
2010
+ i,
2011
+ );
2012
+ }
2013
+ })();
2014
+ }
2015
+
2016
+ export function getSessionLogsByTaskId(taskId: string): SessionLog[] {
2017
+ return sessionLogQueries.getByTaskId().all(taskId).map(rowToSessionLog);
2018
+ }
2019
+
2020
+ export function getSessionLogsBySession(sessionId: string, iteration: number): SessionLog[] {
2021
+ return sessionLogQueries.getBySessionId().all(sessionId, iteration).map(rowToSessionLog);
2022
+ }