@sleep2agi/commhub-server 0.8.2 → 0.8.3-preview.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/package.json +1 -1
- package/src/index.ts +46 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sleep2agi/commhub-server",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3-preview.1",
|
|
4
4
|
"description": "CommHub Server — AI Agent communication hub with MCP protocol, multi-network isolation, user auth, and 17 MCP tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
package/src/index.ts
CHANGED
|
@@ -342,6 +342,7 @@ const TaskSchema = z.object({
|
|
|
342
342
|
priority: z.enum(["high", "normal", "low"]).default("normal"),
|
|
343
343
|
from: z.string().max(200).optional(),
|
|
344
344
|
network_id: z.string().max(200).optional(),
|
|
345
|
+
parent_task_id: z.string().max(200).optional(),
|
|
345
346
|
});
|
|
346
347
|
|
|
347
348
|
const BroadcastSchema = z.object({
|
|
@@ -987,13 +988,38 @@ Bun.serve({
|
|
|
987
988
|
last_seen: string | null;
|
|
988
989
|
}>();
|
|
989
990
|
|
|
991
|
+
const preferDisplayIp = (current: string, next: string) => {
|
|
992
|
+
const isWeak = (ip: string) => !ip || ip === "unknown" || ip === "127.0.0.1" || ip === "::1";
|
|
993
|
+
if (isWeak(current) && !isWeak(next)) return next;
|
|
994
|
+
return current;
|
|
995
|
+
};
|
|
996
|
+
const hasHostTelemetry = (row: any) =>
|
|
997
|
+
row.cpu_load_1min != null || row.cpu_cores != null || row.mem_avail_gb != null || row.mem_used_gb != null;
|
|
998
|
+
|
|
990
999
|
for (const row of db.all<any>(sql, ...params)) {
|
|
991
1000
|
const hostname = row.hostname || "unknown";
|
|
992
1001
|
const ip = row.ip || "unknown";
|
|
993
|
-
|
|
1002
|
+
// Group primarily by hostname. A single host can report both a
|
|
1003
|
+
// routable/container IP and loopback (127.0.0.1); splitting those
|
|
1004
|
+
// into separate cards makes the dashboard show one useful load row
|
|
1005
|
+
// plus one "n/a" duplicate. Unknown hostnames still fall back to IP.
|
|
1006
|
+
const key = hostname !== "unknown" ? `host:${hostname}` : `ip:${ip}`;
|
|
994
1007
|
const existing = grouped.get(key);
|
|
995
1008
|
if (existing) {
|
|
996
1009
|
existing.agent_count += 1;
|
|
1010
|
+
existing.ip = preferDisplayIp(existing.ip, ip);
|
|
1011
|
+
if (parseSqliteTime(row.last_seen) > parseSqliteTime(existing.last_seen)) existing.last_seen = row.last_seen ?? existing.last_seen;
|
|
1012
|
+
if (hasHostTelemetry(row) && (
|
|
1013
|
+
existing.cpu_load_1min == null ||
|
|
1014
|
+
existing.cpu_cores == null ||
|
|
1015
|
+
existing.mem_avail_gb == null ||
|
|
1016
|
+
existing.mem_used_gb == null
|
|
1017
|
+
)) {
|
|
1018
|
+
existing.cpu_load_1min = row.cpu_load_1min ?? existing.cpu_load_1min;
|
|
1019
|
+
existing.cpu_cores = row.cpu_cores ?? existing.cpu_cores;
|
|
1020
|
+
existing.mem_avail_gb = row.mem_avail_gb ?? existing.mem_avail_gb;
|
|
1021
|
+
existing.mem_used_gb = row.mem_used_gb ?? existing.mem_used_gb;
|
|
1022
|
+
}
|
|
997
1023
|
continue;
|
|
998
1024
|
}
|
|
999
1025
|
grouped.set(key, {
|
|
@@ -1177,9 +1203,9 @@ Bun.serve({
|
|
|
1177
1203
|
[id, body.alias, body.priority, body.task, fromSession, taskNetId]
|
|
1178
1204
|
);
|
|
1179
1205
|
db.run(
|
|
1180
|
-
`INSERT INTO tasks (task_id, from_name, to_name, priority, status, content, requires_response, created_at, delivered_at, expires_at, network_id)
|
|
1181
|
-
VALUES (?1, ?2, ?3, ?4, 'delivered', ?5, 'reply', datetime('now'), datetime('now'), datetime('now', ?6), ?7)`,
|
|
1182
|
-
[id, fromSession, body.alias, body.priority, body.task, `+${ttlSeconds} seconds`, taskNetId]
|
|
1206
|
+
`INSERT INTO tasks (task_id, from_name, to_name, priority, status, content, requires_response, created_at, delivered_at, expires_at, network_id, parent_task_id)
|
|
1207
|
+
VALUES (?1, ?2, ?3, ?4, 'delivered', ?5, 'reply', datetime('now'), datetime('now'), datetime('now', ?6), ?7, ?8)`,
|
|
1208
|
+
[id, fromSession, body.alias, body.priority, body.task, `+${ttlSeconds} seconds`, taskNetId, body.parent_task_id ?? null]
|
|
1183
1209
|
);
|
|
1184
1210
|
// Touch session row so the dashboard reflects "task in flight"
|
|
1185
1211
|
// immediately, without waiting for the agent's report_status to
|
|
@@ -1200,7 +1226,7 @@ Bun.serve({
|
|
|
1200
1226
|
if (taskNetId) { sessionSql += " AND network_id = ?2"; sessionParams.push(taskNetId); }
|
|
1201
1227
|
const targetSession = db.get<any>(sessionSql, ...sessionParams);
|
|
1202
1228
|
if (targetSession) pushEvent(body.alias, { type: "new_task", inbox_count: pending?.cnt ?? 1, priority: body.priority, from: fromSession }, taskNetId);
|
|
1203
|
-
return withCors(req, Response.json({ ok: true, message_id: id }));
|
|
1229
|
+
return withCors(req, Response.json({ ok: true, task_id: id, message_id: id }));
|
|
1204
1230
|
}
|
|
1205
1231
|
|
|
1206
1232
|
// ── REST: broadcast ──
|
|
@@ -1460,6 +1486,21 @@ Bun.serve({
|
|
|
1460
1486
|
return withCors(req, Response.json({ ok: true, nodes: rows, count: rows.length }));
|
|
1461
1487
|
}
|
|
1462
1488
|
|
|
1489
|
+
// ── REST: single task lookup (V2) ──
|
|
1490
|
+
const taskPathMatch = url.pathname.match(/^\/api\/tasks?\/([^/]+)$/);
|
|
1491
|
+
if (taskPathMatch && req.method === "GET") {
|
|
1492
|
+
const taskId = decodeURIComponent(taskPathMatch[1] ?? "");
|
|
1493
|
+
const params: any[] = [taskId];
|
|
1494
|
+
let sql = "SELECT * FROM tasks WHERE task_id = ?1";
|
|
1495
|
+
sql = addNetworkScope(sql, params, restScope);
|
|
1496
|
+
sql += " LIMIT 1";
|
|
1497
|
+
const task = db.get(sql, ...params);
|
|
1498
|
+
if (!task) {
|
|
1499
|
+
return withCors(req, Response.json({ ok: false, error: "task_not_found", task_id: taskId }, { status: 404 }));
|
|
1500
|
+
}
|
|
1501
|
+
return withCors(req, Response.json({ ok: true, task }));
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1463
1504
|
// ── REST: tasks table (V2) ──
|
|
1464
1505
|
if (url.pathname === "/api/tasks") {
|
|
1465
1506
|
const taskId = url.searchParams.get("task_id");
|