@desplega.ai/agent-swarm 1.0.1 → 1.0.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.
- package/Dockerfile +16 -0
- package/deploy/DEPLOY.md +57 -0
- package/deploy/agent-swarm.service +16 -0
- package/deploy/install.ts +35 -0
- package/deploy/uninstall.ts +10 -0
- package/package.json +1 -1
- package/src/be/db.ts +156 -2
- package/src/commands/setup.tsx +9 -7
- package/src/http.ts +12 -4
- package/src/types.ts +24 -0
package/Dockerfile
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
FROM oven/bun:1-alpine
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
COPY package.json bun.lock ./
|
|
6
|
+
RUN bun install --frozen-lockfile --production
|
|
7
|
+
|
|
8
|
+
COPY . .
|
|
9
|
+
|
|
10
|
+
ENV PORT=3013
|
|
11
|
+
EXPOSE 3013
|
|
12
|
+
|
|
13
|
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|
14
|
+
CMD wget -qO- http://localhost:3013/health || exit 1
|
|
15
|
+
|
|
16
|
+
CMD ["bun", "run", "start:http"]
|
package/deploy/DEPLOY.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Deployment
|
|
2
|
+
|
|
3
|
+
## Environment Variables
|
|
4
|
+
|
|
5
|
+
| Variable | Default | Description |
|
|
6
|
+
|----------|---------|-------------|
|
|
7
|
+
| `PORT` | `3013` | Server port |
|
|
8
|
+
| `API_KEY` | _(empty)_ | Bearer token for auth (optional) |
|
|
9
|
+
|
|
10
|
+
## Docker
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# Build
|
|
14
|
+
docker build -t agent-swarm .
|
|
15
|
+
|
|
16
|
+
# Run (persists database to ./agent-swarm-db.sqlite on host)
|
|
17
|
+
docker run -d --name agent-swarm -p 3013:3013 \
|
|
18
|
+
-e API_KEY=your-secret-key \
|
|
19
|
+
-v $(pwd)/agent-swarm-db.sqlite:/app/agent-swarm-db.sqlite \
|
|
20
|
+
agent-swarm
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## systemd
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Install service
|
|
27
|
+
sudo bun deploy/install.ts
|
|
28
|
+
|
|
29
|
+
# Control
|
|
30
|
+
sudo systemctl start agent-swarm
|
|
31
|
+
sudo systemctl stop agent-swarm
|
|
32
|
+
sudo systemctl status agent-swarm
|
|
33
|
+
journalctl -u agent-swarm -f
|
|
34
|
+
|
|
35
|
+
# Uninstall
|
|
36
|
+
sudo bun deploy/uninstall.ts
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Caddy (reverse proxy)
|
|
40
|
+
|
|
41
|
+
Add to your Caddyfile:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
agent-swarm.example.com {
|
|
45
|
+
reverse_proxy localhost:3013
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Or with API key header injection:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
agent-swarm.example.com {
|
|
53
|
+
reverse_proxy localhost:3013 {
|
|
54
|
+
header_up Authorization "Bearer {env.AGENT_SWARM_API_KEY}"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=Agent Swarm MCP Server
|
|
3
|
+
After=network.target
|
|
4
|
+
|
|
5
|
+
[Service]
|
|
6
|
+
Type=simple
|
|
7
|
+
User=www-data
|
|
8
|
+
Group=www-data
|
|
9
|
+
WorkingDirectory=/opt/agent-swarm
|
|
10
|
+
ExecStart=/usr/local/bin/bun run start:http
|
|
11
|
+
Restart=always
|
|
12
|
+
RestartSec=5
|
|
13
|
+
EnvironmentFile=/opt/agent-swarm/.env
|
|
14
|
+
|
|
15
|
+
[Install]
|
|
16
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
// Copy project files
|
|
11
|
+
await $`mkdir -p ${APP_DIR}`;
|
|
12
|
+
await $`cp -r ${PROJECT_DIR}/src ${APP_DIR}/`;
|
|
13
|
+
await $`cp ${PROJECT_DIR}/package.json ${PROJECT_DIR}/bun.lock ${APP_DIR}/`;
|
|
14
|
+
|
|
15
|
+
// Install dependencies
|
|
16
|
+
await $`cd ${APP_DIR} && bun install --frozen-lockfile --production`;
|
|
17
|
+
|
|
18
|
+
// Create .env if not exists
|
|
19
|
+
const envFile = Bun.file(`${APP_DIR}/.env`);
|
|
20
|
+
if (!(await envFile.exists())) {
|
|
21
|
+
await Bun.write(envFile, `PORT=3013
|
|
22
|
+
API_KEY=
|
|
23
|
+
`);
|
|
24
|
+
console.log("Created .env - set API_KEY for authentication");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Set ownership
|
|
28
|
+
await $`chown -R www-data:www-data ${APP_DIR}`;
|
|
29
|
+
|
|
30
|
+
// Install systemd service
|
|
31
|
+
await $`cp ${SCRIPT_DIR}/agent-swarm.service ${SERVICE_FILE}`;
|
|
32
|
+
await $`systemctl daemon-reload`;
|
|
33
|
+
await $`systemctl enable agent-swarm`;
|
|
34
|
+
|
|
35
|
+
console.log("Installed. Edit /opt/agent-swarm/.env then run: systemctl start agent-swarm");
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { $ } from "bun";
|
|
4
|
+
|
|
5
|
+
await $`systemctl stop agent-swarm`.nothrow();
|
|
6
|
+
await $`systemctl disable agent-swarm`.nothrow();
|
|
7
|
+
await $`rm -f /etc/systemd/system/agent-swarm.service`;
|
|
8
|
+
await $`systemctl daemon-reload`;
|
|
9
|
+
|
|
10
|
+
console.log("Service removed. Data remains at /opt/agent-swarm");
|
package/package.json
CHANGED
package/src/be/db.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
|
-
import type { Agent, AgentStatus, AgentTask, AgentTaskStatus, AgentWithTasks } from "../types";
|
|
2
|
+
import type { Agent, AgentLog, AgentLogEventType, AgentStatus, AgentTask, AgentTaskStatus, AgentWithTasks } from "../types";
|
|
3
3
|
|
|
4
4
|
let db: Database | null = null;
|
|
5
5
|
|
|
6
|
-
export function initDb(dbPath = "./
|
|
6
|
+
export function initDb(dbPath = "./agent-swarm-db.sqlite"): Database {
|
|
7
7
|
if (db) {
|
|
8
8
|
return db;
|
|
9
9
|
}
|
|
@@ -38,6 +38,22 @@ export function initDb(dbPath = "./cc-orch.sqlite"): Database {
|
|
|
38
38
|
|
|
39
39
|
CREATE INDEX IF NOT EXISTS idx_agent_tasks_agentId ON agent_tasks(agentId);
|
|
40
40
|
CREATE INDEX IF NOT EXISTS idx_agent_tasks_status ON agent_tasks(status);
|
|
41
|
+
|
|
42
|
+
CREATE TABLE IF NOT EXISTS agent_log (
|
|
43
|
+
id TEXT PRIMARY KEY,
|
|
44
|
+
eventType TEXT NOT NULL,
|
|
45
|
+
agentId TEXT,
|
|
46
|
+
taskId TEXT,
|
|
47
|
+
oldValue TEXT,
|
|
48
|
+
newValue TEXT,
|
|
49
|
+
metadata TEXT,
|
|
50
|
+
createdAt TEXT NOT NULL
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_agent_log_agentId ON agent_log(agentId);
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_agent_log_taskId ON agent_log(taskId);
|
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_agent_log_eventType ON agent_log(eventType);
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_agent_log_createdAt ON agent_log(createdAt);
|
|
41
57
|
`);
|
|
42
58
|
|
|
43
59
|
return db;
|
|
@@ -105,6 +121,9 @@ export function createAgent(
|
|
|
105
121
|
const id = agent.id ?? crypto.randomUUID();
|
|
106
122
|
const row = agentQueries.insert().get(id, agent.name, agent.isLead ? 1 : 0, agent.status);
|
|
107
123
|
if (!row) throw new Error("Failed to create agent");
|
|
124
|
+
try {
|
|
125
|
+
createLogEntry({ eventType: "agent_joined", agentId: id, newValue: agent.status });
|
|
126
|
+
} catch {}
|
|
108
127
|
return rowToAgent(row);
|
|
109
128
|
}
|
|
110
129
|
|
|
@@ -118,11 +137,23 @@ export function getAllAgents(): Agent[] {
|
|
|
118
137
|
}
|
|
119
138
|
|
|
120
139
|
export function updateAgentStatus(id: string, status: AgentStatus): Agent | null {
|
|
140
|
+
const oldAgent = getAgentById(id);
|
|
121
141
|
const row = agentQueries.updateStatus().get(status, id);
|
|
142
|
+
if (row && oldAgent) {
|
|
143
|
+
try {
|
|
144
|
+
createLogEntry({ eventType: "agent_status_change", agentId: id, oldValue: oldAgent.status, newValue: status });
|
|
145
|
+
} catch {}
|
|
146
|
+
}
|
|
122
147
|
return row ? rowToAgent(row) : null;
|
|
123
148
|
}
|
|
124
149
|
|
|
125
150
|
export function deleteAgent(id: string): boolean {
|
|
151
|
+
const agent = getAgentById(id);
|
|
152
|
+
if (agent) {
|
|
153
|
+
try {
|
|
154
|
+
createLogEntry({ eventType: "agent_left", agentId: id, oldValue: agent.status });
|
|
155
|
+
} catch {}
|
|
156
|
+
}
|
|
126
157
|
const result = getDb().run("DELETE FROM agents WHERE id = ?", [id]);
|
|
127
158
|
return result.changes > 0;
|
|
128
159
|
}
|
|
@@ -206,6 +237,9 @@ export function createTask(agentId: string, task: string): AgentTask {
|
|
|
206
237
|
const id = crypto.randomUUID();
|
|
207
238
|
const row = taskQueries.insert().get(id, agentId, task, "pending");
|
|
208
239
|
if (!row) throw new Error("Failed to create task");
|
|
240
|
+
try {
|
|
241
|
+
createLogEntry({ eventType: "task_created", agentId, taskId: id, newValue: "pending" });
|
|
242
|
+
} catch {}
|
|
209
243
|
return rowToAgentTask(row);
|
|
210
244
|
}
|
|
211
245
|
|
|
@@ -219,12 +253,18 @@ export function getPendingTaskForAgent(agentId: string): AgentTask | null {
|
|
|
219
253
|
}
|
|
220
254
|
|
|
221
255
|
export function startTask(taskId: string): AgentTask | null {
|
|
256
|
+
const oldTask = getTaskById(taskId);
|
|
222
257
|
const row = getDb()
|
|
223
258
|
.prepare<AgentTaskRow, [string]>(
|
|
224
259
|
`UPDATE agent_tasks SET status = 'in_progress', lastUpdatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
225
260
|
WHERE id = ? RETURNING *`,
|
|
226
261
|
)
|
|
227
262
|
.get(taskId);
|
|
263
|
+
if (row && oldTask) {
|
|
264
|
+
try {
|
|
265
|
+
createLogEntry({ eventType: "task_status_change", taskId, agentId: row.agentId, oldValue: oldTask.status, newValue: "in_progress" });
|
|
266
|
+
} catch {}
|
|
267
|
+
}
|
|
228
268
|
return row ? rowToAgentTask(row) : null;
|
|
229
269
|
}
|
|
230
270
|
|
|
@@ -257,6 +297,7 @@ export function getAllTasks(status?: AgentTaskStatus): AgentTask[] {
|
|
|
257
297
|
}
|
|
258
298
|
|
|
259
299
|
export function completeTask(id: string, output?: string): AgentTask | null {
|
|
300
|
+
const oldTask = getTaskById(id);
|
|
260
301
|
const finishedAt = new Date().toISOString();
|
|
261
302
|
let row = taskQueries.updateStatus().get("completed", finishedAt, id);
|
|
262
303
|
if (!row) return null;
|
|
@@ -265,12 +306,24 @@ export function completeTask(id: string, output?: string): AgentTask | null {
|
|
|
265
306
|
row = taskQueries.setOutput().get(output, id);
|
|
266
307
|
}
|
|
267
308
|
|
|
309
|
+
if (row && oldTask) {
|
|
310
|
+
try {
|
|
311
|
+
createLogEntry({ eventType: "task_status_change", taskId: id, agentId: row.agentId, oldValue: oldTask.status, newValue: "completed" });
|
|
312
|
+
} catch {}
|
|
313
|
+
}
|
|
314
|
+
|
|
268
315
|
return row ? rowToAgentTask(row) : null;
|
|
269
316
|
}
|
|
270
317
|
|
|
271
318
|
export function failTask(id: string, reason: string): AgentTask | null {
|
|
319
|
+
const oldTask = getTaskById(id);
|
|
272
320
|
const finishedAt = new Date().toISOString();
|
|
273
321
|
const row = taskQueries.setFailure().get(reason, finishedAt, id);
|
|
322
|
+
if (row && oldTask) {
|
|
323
|
+
try {
|
|
324
|
+
createLogEntry({ eventType: "task_status_change", taskId: id, agentId: row.agentId, oldValue: oldTask.status, newValue: "failed", metadata: { reason } });
|
|
325
|
+
} catch {}
|
|
326
|
+
}
|
|
274
327
|
return row ? rowToAgentTask(row) : null;
|
|
275
328
|
}
|
|
276
329
|
|
|
@@ -281,6 +334,11 @@ export function deleteTask(id: string): boolean {
|
|
|
281
334
|
|
|
282
335
|
export function updateTaskProgress(id: string, progress: string): AgentTask | null {
|
|
283
336
|
const row = taskQueries.setProgress().get(progress, id);
|
|
337
|
+
if (row) {
|
|
338
|
+
try {
|
|
339
|
+
createLogEntry({ eventType: "task_progress", taskId: id, agentId: row.agentId, newValue: progress });
|
|
340
|
+
} catch {}
|
|
341
|
+
}
|
|
284
342
|
return row ? rowToAgentTask(row) : null;
|
|
285
343
|
}
|
|
286
344
|
|
|
@@ -311,3 +369,99 @@ export function getAllAgentsWithTasks(): AgentWithTasks[] {
|
|
|
311
369
|
|
|
312
370
|
return txn();
|
|
313
371
|
}
|
|
372
|
+
|
|
373
|
+
// ============================================================================
|
|
374
|
+
// Agent Log Queries
|
|
375
|
+
// ============================================================================
|
|
376
|
+
|
|
377
|
+
type AgentLogRow = {
|
|
378
|
+
id: string;
|
|
379
|
+
eventType: AgentLogEventType;
|
|
380
|
+
agentId: string | null;
|
|
381
|
+
taskId: string | null;
|
|
382
|
+
oldValue: string | null;
|
|
383
|
+
newValue: string | null;
|
|
384
|
+
metadata: string | null;
|
|
385
|
+
createdAt: string;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
function rowToAgentLog(row: AgentLogRow): AgentLog {
|
|
389
|
+
return {
|
|
390
|
+
id: row.id,
|
|
391
|
+
eventType: row.eventType,
|
|
392
|
+
agentId: row.agentId ?? undefined,
|
|
393
|
+
taskId: row.taskId ?? undefined,
|
|
394
|
+
oldValue: row.oldValue ?? undefined,
|
|
395
|
+
newValue: row.newValue ?? undefined,
|
|
396
|
+
metadata: row.metadata ?? undefined,
|
|
397
|
+
createdAt: row.createdAt,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export const logQueries = {
|
|
402
|
+
insert: () =>
|
|
403
|
+
getDb().prepare<AgentLogRow, [string, string, string | null, string | null, string | null, string | null, string | null]>(
|
|
404
|
+
`INSERT INTO agent_log (id, eventType, agentId, taskId, oldValue, newValue, metadata, createdAt)
|
|
405
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) RETURNING *`,
|
|
406
|
+
),
|
|
407
|
+
|
|
408
|
+
getByAgentId: () =>
|
|
409
|
+
getDb().prepare<AgentLogRow, [string]>(
|
|
410
|
+
"SELECT * FROM agent_log WHERE agentId = ? ORDER BY createdAt DESC",
|
|
411
|
+
),
|
|
412
|
+
|
|
413
|
+
getByTaskId: () =>
|
|
414
|
+
getDb().prepare<AgentLogRow, [string]>(
|
|
415
|
+
"SELECT * FROM agent_log WHERE taskId = ? ORDER BY createdAt DESC",
|
|
416
|
+
),
|
|
417
|
+
|
|
418
|
+
getByEventType: () =>
|
|
419
|
+
getDb().prepare<AgentLogRow, [string]>(
|
|
420
|
+
"SELECT * FROM agent_log WHERE eventType = ? ORDER BY createdAt DESC",
|
|
421
|
+
),
|
|
422
|
+
|
|
423
|
+
getAll: () =>
|
|
424
|
+
getDb().prepare<AgentLogRow, []>(
|
|
425
|
+
"SELECT * FROM agent_log ORDER BY createdAt DESC",
|
|
426
|
+
),
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
export function createLogEntry(entry: {
|
|
430
|
+
eventType: AgentLogEventType;
|
|
431
|
+
agentId?: string;
|
|
432
|
+
taskId?: string;
|
|
433
|
+
oldValue?: string;
|
|
434
|
+
newValue?: string;
|
|
435
|
+
metadata?: Record<string, unknown>;
|
|
436
|
+
}): AgentLog {
|
|
437
|
+
const id = crypto.randomUUID();
|
|
438
|
+
const row = logQueries.insert().get(
|
|
439
|
+
id,
|
|
440
|
+
entry.eventType,
|
|
441
|
+
entry.agentId ?? null,
|
|
442
|
+
entry.taskId ?? null,
|
|
443
|
+
entry.oldValue ?? null,
|
|
444
|
+
entry.newValue ?? null,
|
|
445
|
+
entry.metadata ? JSON.stringify(entry.metadata) : null,
|
|
446
|
+
);
|
|
447
|
+
if (!row) throw new Error("Failed to create log entry");
|
|
448
|
+
return rowToAgentLog(row);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export function getLogsByAgentId(agentId: string): AgentLog[] {
|
|
452
|
+
return logQueries.getByAgentId().all(agentId).map(rowToAgentLog);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export function getLogsByTaskId(taskId: string): AgentLog[] {
|
|
456
|
+
return logQueries.getByTaskId().all(taskId).map(rowToAgentLog);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export function getAllLogs(limit?: number): AgentLog[] {
|
|
460
|
+
if (limit) {
|
|
461
|
+
return getDb()
|
|
462
|
+
.prepare<AgentLogRow, [number]>("SELECT * FROM agent_log ORDER BY createdAt DESC LIMIT ?")
|
|
463
|
+
.all(limit)
|
|
464
|
+
.map(rowToAgentLog);
|
|
465
|
+
}
|
|
466
|
+
return logQueries.getAll().all().map(rowToAgentLog);
|
|
467
|
+
}
|
package/src/commands/setup.tsx
CHANGED
|
@@ -198,20 +198,22 @@ export function Setup({ dryRun = false, restore = false }: SetupProps) {
|
|
|
198
198
|
addLog(".mcp.json exists");
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
// Check if it's a git repo
|
|
201
|
+
// Check if it's a git repo by finding the git root
|
|
202
202
|
let isGitRepo = false;
|
|
203
|
+
let gitRoot = "";
|
|
203
204
|
try {
|
|
204
|
-
const
|
|
205
|
-
|
|
205
|
+
const result = await Bun.$`git -C ${cwd} rev-parse --show-toplevel`.quiet();
|
|
206
|
+
gitRoot = result.text().trim();
|
|
207
|
+
isGitRepo = result.exitCode === 0 && gitRoot.length > 0;
|
|
206
208
|
} catch {
|
|
207
209
|
isGitRepo = false;
|
|
208
210
|
}
|
|
209
211
|
|
|
210
212
|
if (isGitRepo) {
|
|
211
|
-
addLog(
|
|
213
|
+
addLog(`Git repository detected (root: ${gitRoot})`);
|
|
212
214
|
|
|
213
|
-
// Check .gitignore
|
|
214
|
-
const gitignoreFile = Bun.file(`${
|
|
215
|
+
// Check .gitignore at git root
|
|
216
|
+
const gitignoreFile = Bun.file(`${gitRoot}/.gitignore`);
|
|
215
217
|
let gitignoreContent = "";
|
|
216
218
|
|
|
217
219
|
if (await gitignoreFile.exists()) {
|
|
@@ -228,7 +230,7 @@ export function Setup({ dryRun = false, restore = false }: SetupProps) {
|
|
|
228
230
|
|
|
229
231
|
if (entriesToAdd.length > 0) {
|
|
230
232
|
// Backup .gitignore before modifying
|
|
231
|
-
await createBackup(`${
|
|
233
|
+
await createBackup(`${gitRoot}/.gitignore`);
|
|
232
234
|
if (!dryRun) {
|
|
233
235
|
const newEntries = `# Added by ${SERVER_NAME} setup\n${entriesToAdd.join("\n")}\n\n`;
|
|
234
236
|
await Bun.write(gitignoreFile, newEntries + gitignoreContent);
|
package/src/http.ts
CHANGED
|
@@ -113,7 +113,7 @@ const httpServer = createHttpServer(async (req, res) => {
|
|
|
113
113
|
if (!agent) {
|
|
114
114
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
115
115
|
res.end(JSON.stringify({ error: "Agent not found" }));
|
|
116
|
-
return;
|
|
116
|
+
return false;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
let status: AgentStatus = "idle";
|
|
@@ -123,9 +123,13 @@ const httpServer = createHttpServer(async (req, res) => {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
updateAgentStatus(agent.id, status);
|
|
126
|
+
|
|
127
|
+
return true;
|
|
126
128
|
});
|
|
127
129
|
|
|
128
|
-
tx()
|
|
130
|
+
if (!tx()) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
129
133
|
|
|
130
134
|
res.writeHead(204);
|
|
131
135
|
res.end();
|
|
@@ -145,13 +149,17 @@ const httpServer = createHttpServer(async (req, res) => {
|
|
|
145
149
|
if (!agent) {
|
|
146
150
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
147
151
|
res.end(JSON.stringify({ error: "Agent not found" }));
|
|
148
|
-
return;
|
|
152
|
+
return false;
|
|
149
153
|
}
|
|
150
154
|
|
|
151
155
|
updateAgentStatus(agent.id, "offline");
|
|
156
|
+
|
|
157
|
+
return true;
|
|
152
158
|
});
|
|
153
159
|
|
|
154
|
-
tx()
|
|
160
|
+
if (!tx()) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
155
163
|
|
|
156
164
|
res.writeHead(204);
|
|
157
165
|
res.end();
|
package/src/types.ts
CHANGED
|
@@ -40,3 +40,27 @@ export type AgentTask = z.infer<typeof AgentTaskSchema>;
|
|
|
40
40
|
export type AgentStatus = z.infer<typeof AgentStatusSchema>;
|
|
41
41
|
export type Agent = z.infer<typeof AgentSchema>;
|
|
42
42
|
export type AgentWithTasks = z.infer<typeof AgentWithTasksSchema>;
|
|
43
|
+
|
|
44
|
+
// Agent Log Types
|
|
45
|
+
export const AgentLogEventTypeSchema = z.enum([
|
|
46
|
+
"agent_joined",
|
|
47
|
+
"agent_status_change",
|
|
48
|
+
"agent_left",
|
|
49
|
+
"task_created",
|
|
50
|
+
"task_status_change",
|
|
51
|
+
"task_progress",
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
export const AgentLogSchema = z.object({
|
|
55
|
+
id: z.uuid(),
|
|
56
|
+
eventType: AgentLogEventTypeSchema,
|
|
57
|
+
agentId: z.string().optional(),
|
|
58
|
+
taskId: z.string().optional(),
|
|
59
|
+
oldValue: z.string().optional(),
|
|
60
|
+
newValue: z.string().optional(),
|
|
61
|
+
metadata: z.string().optional(),
|
|
62
|
+
createdAt: z.iso.datetime(),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export type AgentLogEventType = z.infer<typeof AgentLogEventTypeSchema>;
|
|
66
|
+
export type AgentLog = z.infer<typeof AgentLogSchema>;
|