@sleep2agi/commhub-server 0.8.1-preview.2 → 0.8.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/db.ts +21 -3
- package/src/index.ts +100 -1
- package/src/tools.ts +46 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sleep2agi/commhub-server",
|
|
3
|
-
"version": "0.8.1
|
|
3
|
+
"version": "0.8.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/db.ts
CHANGED
|
@@ -19,6 +19,11 @@ db.exec(`
|
|
|
19
19
|
output TEXT,
|
|
20
20
|
progress INTEGER DEFAULT 0,
|
|
21
21
|
score REAL,
|
|
22
|
+
cpu_load_1min REAL,
|
|
23
|
+
cpu_cores INTEGER,
|
|
24
|
+
mem_total_gb REAL,
|
|
25
|
+
mem_used_gb REAL,
|
|
26
|
+
mem_avail_gb REAL,
|
|
22
27
|
network_id TEXT NOT NULL DEFAULT 'default',
|
|
23
28
|
registered_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
24
29
|
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
@@ -55,7 +60,7 @@ db.exec(`
|
|
|
55
60
|
|
|
56
61
|
// ── V2 schema migration (ALTER TABLE, safe to re-run) ──
|
|
57
62
|
|
|
58
|
-
// sessions: add node_id, session_id, config_path, channels, last_seen_at, model
|
|
63
|
+
// sessions: add node_id, session_id, config_path, channels, last_seen_at, model, host telemetry
|
|
59
64
|
for (const col of [
|
|
60
65
|
{ name: "node_id", def: "TEXT" },
|
|
61
66
|
{ name: "session_id", def: "TEXT" },
|
|
@@ -63,6 +68,11 @@ for (const col of [
|
|
|
63
68
|
{ name: "channels", def: "TEXT" },
|
|
64
69
|
{ name: "last_seen_at", def: "TEXT" },
|
|
65
70
|
{ name: "model", def: "TEXT" },
|
|
71
|
+
{ name: "cpu_load_1min", def: "REAL" },
|
|
72
|
+
{ name: "cpu_cores", def: "INTEGER" },
|
|
73
|
+
{ name: "mem_total_gb", def: "REAL" },
|
|
74
|
+
{ name: "mem_used_gb", def: "REAL" },
|
|
75
|
+
{ name: "mem_avail_gb", def: "REAL" },
|
|
66
76
|
]) {
|
|
67
77
|
try { db.exec(`ALTER TABLE sessions ADD COLUMN ${col.name} ${col.def}`); } catch {}
|
|
68
78
|
}
|
|
@@ -376,6 +386,12 @@ function migrateSessionsNetworkAliasUnique() {
|
|
|
376
386
|
config_path TEXT,
|
|
377
387
|
channels TEXT,
|
|
378
388
|
last_seen_at TEXT,
|
|
389
|
+
model TEXT,
|
|
390
|
+
cpu_load_1min REAL,
|
|
391
|
+
cpu_cores INTEGER,
|
|
392
|
+
mem_total_gb REAL,
|
|
393
|
+
mem_used_gb REAL,
|
|
394
|
+
mem_avail_gb REAL,
|
|
379
395
|
network_id TEXT NOT NULL DEFAULT 'default',
|
|
380
396
|
UNIQUE (network_id, alias)
|
|
381
397
|
)
|
|
@@ -384,12 +400,14 @@ function migrateSessionsNetworkAliasUnique() {
|
|
|
384
400
|
INSERT OR REPLACE INTO sessions_migrated (
|
|
385
401
|
resume_id, alias, tmux_name, server, ip, hostname, agent, project_dir, version,
|
|
386
402
|
status, task, output, progress, score, registered_at, updated_at, node_id,
|
|
387
|
-
session_id, config_path, channels, last_seen_at,
|
|
403
|
+
session_id, config_path, channels, last_seen_at, model, cpu_load_1min,
|
|
404
|
+
cpu_cores, mem_total_gb, mem_used_gb, mem_avail_gb, network_id
|
|
388
405
|
)
|
|
389
406
|
SELECT
|
|
390
407
|
resume_id, alias, tmux_name, server, ip, hostname, agent, project_dir, version,
|
|
391
408
|
status, task, output, progress, score, registered_at, updated_at, node_id,
|
|
392
|
-
session_id, config_path, channels, last_seen_at,
|
|
409
|
+
session_id, config_path, channels, last_seen_at, model, cpu_load_1min,
|
|
410
|
+
cpu_cores, mem_total_gb, mem_used_gb, mem_avail_gb,
|
|
393
411
|
COALESCE(NULLIF(network_id, ''), 'default')
|
|
394
412
|
FROM sessions
|
|
395
413
|
ORDER BY updated_at
|
package/src/index.ts
CHANGED
|
@@ -266,7 +266,7 @@ function corsHeaders(req: Request): Record<string, string> {
|
|
|
266
266
|
const allowed = CORS_ORIGINS.includes(origin) ? origin : "";
|
|
267
267
|
return {
|
|
268
268
|
"Access-Control-Allow-Origin": allowed,
|
|
269
|
-
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
269
|
+
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
270
270
|
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
271
271
|
"Access-Control-Max-Age": "86400",
|
|
272
272
|
};
|
|
@@ -841,6 +841,59 @@ Bun.serve({
|
|
|
841
841
|
return withCors(req, Response.json({ ok: true, sessions, summary }));
|
|
842
842
|
}
|
|
843
843
|
|
|
844
|
+
// ── REST: aggregate agents by physical server ──
|
|
845
|
+
if (url.pathname === "/api/servers") {
|
|
846
|
+
const cutoff = new Date(Date.now() - 10 * 60 * 1000).toISOString().replace("T", " ").slice(0, 19);
|
|
847
|
+
const staleParams: any[] = [cutoff];
|
|
848
|
+
let staleSql = "UPDATE sessions SET status = 'offline' WHERE updated_at < ?1 AND status != 'offline'";
|
|
849
|
+
staleSql = addNetworkScope(staleSql, staleParams, restScope);
|
|
850
|
+
db.run(staleSql, staleParams);
|
|
851
|
+
|
|
852
|
+
const params: any[] = [];
|
|
853
|
+
let sql = `
|
|
854
|
+
SELECT hostname, ip, cpu_load_1min, cpu_cores, mem_avail_gb, mem_used_gb,
|
|
855
|
+
COALESCE(last_seen_at, updated_at) AS last_seen
|
|
856
|
+
FROM sessions
|
|
857
|
+
WHERE 1=1
|
|
858
|
+
`;
|
|
859
|
+
sql = addNetworkScope(sql, params, restScope);
|
|
860
|
+
sql += " ORDER BY COALESCE(last_seen_at, updated_at) DESC";
|
|
861
|
+
|
|
862
|
+
const grouped = new Map<string, {
|
|
863
|
+
hostname: string;
|
|
864
|
+
ip: string;
|
|
865
|
+
agent_count: number;
|
|
866
|
+
cpu_load_1min: number | null;
|
|
867
|
+
cpu_cores: number | null;
|
|
868
|
+
mem_avail_gb: number | null;
|
|
869
|
+
mem_used_gb: number | null;
|
|
870
|
+
last_seen: string | null;
|
|
871
|
+
}>();
|
|
872
|
+
|
|
873
|
+
for (const row of db.all<any>(sql, ...params)) {
|
|
874
|
+
const hostname = row.hostname || "unknown";
|
|
875
|
+
const ip = row.ip || "unknown";
|
|
876
|
+
const key = `${hostname}\u0000${ip}`;
|
|
877
|
+
const existing = grouped.get(key);
|
|
878
|
+
if (existing) {
|
|
879
|
+
existing.agent_count += 1;
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
grouped.set(key, {
|
|
883
|
+
hostname,
|
|
884
|
+
ip,
|
|
885
|
+
agent_count: 1,
|
|
886
|
+
cpu_load_1min: row.cpu_load_1min ?? null,
|
|
887
|
+
cpu_cores: row.cpu_cores ?? null,
|
|
888
|
+
mem_avail_gb: row.mem_avail_gb ?? null,
|
|
889
|
+
mem_used_gb: row.mem_used_gb ?? null,
|
|
890
|
+
last_seen: row.last_seen ?? null,
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
return withCors(req, Response.json(Array.from(grouped.values())));
|
|
895
|
+
}
|
|
896
|
+
|
|
844
897
|
// ── REST: send task ──
|
|
845
898
|
if (url.pathname === "/api/task" && req.method === "POST") {
|
|
846
899
|
let raw: unknown;
|
|
@@ -1109,6 +1162,52 @@ Bun.serve({
|
|
|
1109
1162
|
return withCors(req, Response.json({ ok: true, events: rows, count: rows.length }));
|
|
1110
1163
|
}
|
|
1111
1164
|
|
|
1165
|
+
// ── REST: delete node (Dashboard/CLI remote cleanup) ──
|
|
1166
|
+
const nodeDeleteMatch = url.pathname.match(/^\/api\/nodes\/([^/]+)$/);
|
|
1167
|
+
if (nodeDeleteMatch && req.method === "DELETE") {
|
|
1168
|
+
const ref = decodeURIComponent(nodeDeleteMatch[1]);
|
|
1169
|
+
const params: any[] = [ref, ref, ref];
|
|
1170
|
+
let sql = "SELECT * FROM nodes WHERE (node_id = ?1 OR node_name = ?2 OR alias = ?3)";
|
|
1171
|
+
sql = addNetworkScope(sql, params, restScope);
|
|
1172
|
+
sql += " ORDER BY updated_at DESC LIMIT 1";
|
|
1173
|
+
const node = db.get<any>(sql, ...params);
|
|
1174
|
+
if (!node) return withCors(req, Response.json({ ok: false, error: "node not found" }, { status: 404 }));
|
|
1175
|
+
|
|
1176
|
+
const nodeNetId = node.network_id ?? singleNetworkId(restScope);
|
|
1177
|
+
if (!canRestWriteNetwork(restAuth, nodeNetId, isAdmin)) {
|
|
1178
|
+
return withCors(req, Response.json({ ok: false, error: "permission_denied" }, { status: 403 }));
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
db.transaction(() => {
|
|
1182
|
+
db.run("DELETE FROM nodes WHERE node_id = ?1", [node.node_id]);
|
|
1183
|
+
if (node.alias) {
|
|
1184
|
+
db.run(
|
|
1185
|
+
"DELETE FROM sessions WHERE alias = ?1 AND (network_id = ?2 OR (?2 IS NULL AND network_id IS NULL))",
|
|
1186
|
+
[node.alias, node.network_id ?? null]
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
if (node.alias) {
|
|
1192
|
+
pushEvent(node.alias, {
|
|
1193
|
+
type: "node_deleted",
|
|
1194
|
+
node_id: node.node_id,
|
|
1195
|
+
node_name: node.node_name,
|
|
1196
|
+
alias: node.alias,
|
|
1197
|
+
network_id: node.network_id ?? null,
|
|
1198
|
+
}, node.network_id ?? null);
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
return withCors(req, Response.json({
|
|
1202
|
+
ok: true,
|
|
1203
|
+
deleted: true,
|
|
1204
|
+
node_id: node.node_id,
|
|
1205
|
+
node_name: node.node_name,
|
|
1206
|
+
alias: node.alias,
|
|
1207
|
+
network_id: node.network_id ?? null,
|
|
1208
|
+
}));
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1112
1211
|
// ── REST: nodes table (V2 Sprint 2) ──
|
|
1113
1212
|
if (url.pathname === "/api/nodes") {
|
|
1114
1213
|
const nodeId = url.searchParams.get("node_id");
|
package/src/tools.ts
CHANGED
|
@@ -29,6 +29,16 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
29
29
|
return !!role && role !== "viewer"; // owner/admin/member can write
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
+
const writeDeniedReply = (effectiveNetworkId?: string | null, action = "write") => {
|
|
33
|
+
const netId = enforceNetworkId ?? effectiveNetworkId ?? null;
|
|
34
|
+
const message = !netId
|
|
35
|
+
? "network_id required (utok current_network is null; pass explicit network_id from /api/auth/me networks[0].network_id)"
|
|
36
|
+
: action === "send_task"
|
|
37
|
+
? "Viewer role cannot send tasks"
|
|
38
|
+
: "Viewer role cannot write to this network";
|
|
39
|
+
return { content: [{ type: "text" as const, text: JSON.stringify({ ok: false, error: "permission_denied", message }) }] };
|
|
40
|
+
};
|
|
41
|
+
|
|
32
42
|
const addScope = (sql: string, params: any[], networkId?: string | null, column = "network_id"): string => {
|
|
33
43
|
if (!networkId) return sql;
|
|
34
44
|
sql += ` AND ${column} = ?${params.length + 1}`;
|
|
@@ -109,25 +119,41 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
109
119
|
model: z.string().max(200).optional().describe("AI model name"),
|
|
110
120
|
node_name: z.string().max(200).optional().describe("Stable node display name (may differ from alias)"),
|
|
111
121
|
network_id: z.string().max(200).optional().describe("Network this agent belongs to"),
|
|
122
|
+
host: z.object({
|
|
123
|
+
hostname: z.string().max(200).optional(),
|
|
124
|
+
ip: z.string().max(200).optional(),
|
|
125
|
+
cpu_load_1min: z.number().nullable().optional(),
|
|
126
|
+
cpu_cores: z.number().nullable().optional(),
|
|
127
|
+
mem_total_gb: z.number().nullable().optional(),
|
|
128
|
+
mem_used_gb: z.number().nullable().optional(),
|
|
129
|
+
mem_avail_gb: z.number().nullable().optional(),
|
|
130
|
+
}).optional().describe("Host telemetry reported by agent-node"),
|
|
112
131
|
},
|
|
113
|
-
async ({ resume_id, alias, status, task, output, score, progress, server: srv, hostname: hn, agent: ag, project_dir: pd, version: ver, tmux_name: tmux, node_id, session_id, config_path, channels, model: mdl, node_name: nn, network_id: netId }) => {
|
|
132
|
+
async ({ resume_id, alias, status, task, output, score, progress, server: srv, hostname: hn, agent: ag, project_dir: pd, version: ver, tmux_name: tmux, node_id, session_id, config_path, channels, model: mdl, node_name: nn, network_id: netId, host }) => {
|
|
114
133
|
const effectiveNetId = getNetworkId(netId);
|
|
115
134
|
const sessionNetId = effectiveNetId ?? "default";
|
|
116
135
|
if (!callerTokenIsNetwork || !enforceNetworkId) {
|
|
117
136
|
return { content: [{ type: "text" as const, text: JSON.stringify({ ok: false, error: "network_token_required" }) }] };
|
|
118
137
|
}
|
|
119
138
|
if (!canWrite(effectiveNetId)) {
|
|
120
|
-
return
|
|
139
|
+
return writeDeniedReply(effectiveNetId);
|
|
121
140
|
}
|
|
122
141
|
console.log(`[${ts()}] ${alias} (${resume_id.slice(0, 8)}) → report_status: ${status}${task ? " | " + task.slice(0, 60) : ""}${effectiveNetId ? " [net]" : ""}`);
|
|
123
142
|
const trimmedOutput = output?.slice(0, 4000);
|
|
143
|
+
const hostHostname = host?.hostname || hn || null;
|
|
144
|
+
const hostIp = host?.ip || clientIP || null;
|
|
145
|
+
const cpuLoad1m = typeof host?.cpu_load_1min === "number" ? host.cpu_load_1min : null;
|
|
146
|
+
const cpuCores = typeof host?.cpu_cores === "number" ? host.cpu_cores : null;
|
|
147
|
+
const memTotalGb = typeof host?.mem_total_gb === "number" ? host.mem_total_gb : null;
|
|
148
|
+
const memUsedGb = typeof host?.mem_used_gb === "number" ? host.mem_used_gb : null;
|
|
149
|
+
const memAvailGb = typeof host?.mem_avail_gb === "number" ? host.mem_avail_gb : null;
|
|
124
150
|
|
|
125
151
|
db.transaction(() => {
|
|
126
152
|
// Only delete same-alias sessions within the same network
|
|
127
153
|
db.run("DELETE FROM sessions WHERE alias = ?1 AND resume_id != ?2 AND network_id = ?3", [alias, resume_id, sessionNetId]);
|
|
128
154
|
db.run(
|
|
129
|
-
`INSERT INTO sessions (resume_id, alias, tmux_name, server, ip, hostname, agent, project_dir, version, status, task, output, progress, score, node_id, session_id, config_path, channels, network_id, model, last_seen_at, updated_at)
|
|
130
|
-
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, datetime('now'), datetime('now'))
|
|
155
|
+
`INSERT INTO sessions (resume_id, alias, tmux_name, server, ip, hostname, agent, project_dir, version, status, task, output, progress, score, node_id, session_id, config_path, channels, network_id, model, cpu_load_1min, cpu_cores, mem_total_gb, mem_used_gb, mem_avail_gb, last_seen_at, updated_at)
|
|
156
|
+
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, datetime('now'), datetime('now'))
|
|
131
157
|
ON CONFLICT(resume_id) DO UPDATE SET
|
|
132
158
|
alias = COALESCE(?2, sessions.alias), tmux_name = COALESCE(?3, sessions.tmux_name),
|
|
133
159
|
server = COALESCE(?4, sessions.server), ip = COALESCE(?5, sessions.ip),
|
|
@@ -139,8 +165,13 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
139
165
|
session_id = COALESCE(?16, sessions.session_id), config_path = COALESCE(?17, sessions.config_path),
|
|
140
166
|
channels = COALESCE(?18, sessions.channels), network_id = COALESCE(?19, sessions.network_id),
|
|
141
167
|
model = COALESCE(?20, sessions.model),
|
|
168
|
+
cpu_load_1min = COALESCE(?21, sessions.cpu_load_1min),
|
|
169
|
+
cpu_cores = COALESCE(?22, sessions.cpu_cores),
|
|
170
|
+
mem_total_gb = COALESCE(?23, sessions.mem_total_gb),
|
|
171
|
+
mem_used_gb = COALESCE(?24, sessions.mem_used_gb),
|
|
172
|
+
mem_avail_gb = COALESCE(?25, sessions.mem_avail_gb),
|
|
142
173
|
last_seen_at = datetime('now'), updated_at = datetime('now')`,
|
|
143
|
-
[resume_id, alias, tmux ?? null, srv ?? null,
|
|
174
|
+
[resume_id, alias, tmux ?? null, srv ?? null, hostIp, hostHostname, ag ?? null, pd ?? null, ver ?? null, status, task ?? null, trimmedOutput ?? null, progress ?? null, score ?? null, node_id ?? null, session_id ?? null, config_path ?? null, channels ?? null, sessionNetId, mdl ?? null, cpuLoad1m, cpuCores, memTotalGb, memUsedGb, memAvailGb]
|
|
144
175
|
);
|
|
145
176
|
});
|
|
146
177
|
|
|
@@ -225,7 +256,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
225
256
|
async ({ alias, task, result, artifacts, score, duration_minutes, network_id: netId }) => {
|
|
226
257
|
const effectiveNetId = getNetworkId(netId);
|
|
227
258
|
if (!canWrite(effectiveNetId)) {
|
|
228
|
-
return
|
|
259
|
+
return writeDeniedReply(effectiveNetId);
|
|
229
260
|
}
|
|
230
261
|
console.log(`[${ts()}] ${alias} → report_completion: ${task.slice(0, 60)}${effectiveNetId ? " [net]" : ""}`);
|
|
231
262
|
const id = uuidv4();
|
|
@@ -337,7 +368,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
337
368
|
},
|
|
338
369
|
async ({ alias, message_id, response }) => {
|
|
339
370
|
const effectiveNetId = getNetworkId(null);
|
|
340
|
-
if (!canWrite(effectiveNetId)) return
|
|
371
|
+
if (!canWrite(effectiveNetId)) return writeDeniedReply(effectiveNetId);
|
|
341
372
|
console.log(`[${ts()}] ${alias} → ack_inbox: ${message_id.slice(0, 8)}`);
|
|
342
373
|
const ackParams: any[] = [message_id, alias];
|
|
343
374
|
let ackSql = "UPDATE inbox SET acked = 1 WHERE id = ?1 AND session_name = ?2";
|
|
@@ -478,7 +509,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
478
509
|
|
|
479
510
|
// Role check: viewer cannot send tasks
|
|
480
511
|
if (!canWrite(effectiveNetId)) {
|
|
481
|
-
return
|
|
512
|
+
return writeDeniedReply(effectiveNetId, "send_task");
|
|
482
513
|
}
|
|
483
514
|
|
|
484
515
|
// License check
|
|
@@ -557,7 +588,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
557
588
|
},
|
|
558
589
|
async ({ alias, message, from_session: _fromIn }) => { const from_session = defaultFrom(_fromIn);
|
|
559
590
|
const effectiveNetId = getNetworkId(null);
|
|
560
|
-
if (!canWrite(effectiveNetId)) return
|
|
591
|
+
if (!canWrite(effectiveNetId)) return writeDeniedReply(effectiveNetId);
|
|
561
592
|
console.log(`[${ts()}] ${from_session} → send_message → ${alias}: ${message.slice(0, 60)}`);
|
|
562
593
|
const id = uuidv4();
|
|
563
594
|
db.run(
|
|
@@ -598,7 +629,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
598
629
|
},
|
|
599
630
|
async ({ alias, text, in_reply_to, status: replyStatus, from_session: _fromIn }) => { const from_session = defaultFrom(_fromIn);
|
|
600
631
|
const effectiveNetId = getNetworkId(null);
|
|
601
|
-
if (!canWrite(effectiveNetId)) return
|
|
632
|
+
if (!canWrite(effectiveNetId)) return writeDeniedReply(effectiveNetId);
|
|
602
633
|
console.log(`[${ts()}] ${from_session} → send_reply (${replyStatus}) → ${alias}: ${text.slice(0, 60)}`);
|
|
603
634
|
const id = uuidv4();
|
|
604
635
|
const replyLogged = db.transaction(() => {
|
|
@@ -673,7 +704,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
673
704
|
},
|
|
674
705
|
async ({ task_id, from_session: _fromIn }) => { const from_session = defaultFrom(_fromIn);
|
|
675
706
|
const effectiveNetId = getNetworkId(null);
|
|
676
|
-
if (!canWrite(effectiveNetId)) return
|
|
707
|
+
if (!canWrite(effectiveNetId)) return writeDeniedReply(effectiveNetId);
|
|
677
708
|
console.log(`[${ts()}] ${from_session} → send_ack → task ${task_id.slice(0, 8)}`);
|
|
678
709
|
const updateParams: any[] = [task_id];
|
|
679
710
|
let updateSql = "UPDATE tasks SET status = 'acked' WHERE task_id = ?1 AND status IN ('created', 'delivered')";
|
|
@@ -699,7 +730,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
699
730
|
},
|
|
700
731
|
async ({ task_id, from_session: _fromIn }) => { const from_session = defaultFrom(_fromIn);
|
|
701
732
|
const effectiveNetId = getNetworkId(null);
|
|
702
|
-
if (!canWrite(effectiveNetId)) return
|
|
733
|
+
if (!canWrite(effectiveNetId)) return writeDeniedReply(effectiveNetId);
|
|
703
734
|
console.log(`[${ts()}] ${from_session} → retry_task → ${task_id.slice(0, 8)}`);
|
|
704
735
|
// Find the original task
|
|
705
736
|
const taskParams: any[] = [task_id];
|
|
@@ -810,7 +841,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
810
841
|
},
|
|
811
842
|
async ({ task_id, reason, from_session: _fromIn }) => { const from_session = defaultFrom(_fromIn);
|
|
812
843
|
const effectiveNetId = getNetworkId(null);
|
|
813
|
-
if (!canWrite(effectiveNetId)) return
|
|
844
|
+
if (!canWrite(effectiveNetId)) return writeDeniedReply(effectiveNetId);
|
|
814
845
|
console.log(`[${ts()}] ${from_session} → cancel_task → ${task_id.slice(0, 8)}`);
|
|
815
846
|
const updateParams: any[] = [reason || "cancelled by " + from_session, task_id];
|
|
816
847
|
let updateSql = `UPDATE tasks SET status = 'cancelled', result = ?1, completed_at = datetime('now')
|
|
@@ -842,7 +873,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
842
873
|
},
|
|
843
874
|
async ({ task_id, new_alias, from_session: _fromIn }) => { const from_session = defaultFrom(_fromIn);
|
|
844
875
|
const effectiveNetId = getNetworkId(null);
|
|
845
|
-
if (!canWrite(effectiveNetId)) return
|
|
876
|
+
if (!canWrite(effectiveNetId)) return writeDeniedReply(effectiveNetId);
|
|
846
877
|
console.log(`[${ts()}] ${from_session} → reassign_task → ${task_id.slice(0, 8)} → ${new_alias}`);
|
|
847
878
|
const taskParams: any[] = [task_id];
|
|
848
879
|
let taskSql = "SELECT * FROM tasks WHERE task_id = ?1";
|
|
@@ -886,7 +917,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
886
917
|
},
|
|
887
918
|
async ({ message, filter_server, filter_status, network_id: netId }) => {
|
|
888
919
|
const effectiveNetId = getNetworkId(netId);
|
|
889
|
-
if (!canWrite(effectiveNetId)) return
|
|
920
|
+
if (!canWrite(effectiveNetId)) return writeDeniedReply(effectiveNetId);
|
|
890
921
|
console.log(`[${ts()}] hub → broadcast: ${message.slice(0, 60)}${effectiveNetId ? " [net=" + effectiveNetId.slice(0, 12) + "]" : ""}`);
|
|
891
922
|
let sql = "SELECT alias, network_id FROM sessions WHERE alias IS NOT NULL";
|
|
892
923
|
const params: any[] = [];
|