@sleep2agi/commhub-server 0.8.3-preview.2 → 0.8.3
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 +2 -0
- package/src/index.ts +14 -6
- package/src/tools.ts +24 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sleep2agi/commhub-server",
|
|
3
|
-
"version": "0.8.3
|
|
3
|
+
"version": "0.8.3",
|
|
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
|
@@ -129,6 +129,7 @@ for (const col of [
|
|
|
129
129
|
{ name: "requires_response", def: "TEXT DEFAULT 'reply'" },
|
|
130
130
|
{ name: "expires_at", def: "TEXT" },
|
|
131
131
|
{ name: "scope", def: "TEXT DEFAULT 'single'" },
|
|
132
|
+
{ name: "meta_json", def: "TEXT" },
|
|
132
133
|
]) {
|
|
133
134
|
try { db.exec(`ALTER TABLE inbox ADD COLUMN ${col.name} ${col.def}`); } catch {}
|
|
134
135
|
}
|
|
@@ -489,6 +490,7 @@ try { db.exec("CREATE INDEX IF NOT EXISTS idx_completions_network ON completions
|
|
|
489
490
|
// admin to see the answer even if 指挥室's own session has died. The hub forwards
|
|
490
491
|
// the reply up the chain via parent_task_id.
|
|
491
492
|
try { db.exec("ALTER TABLE tasks ADD COLUMN parent_task_id TEXT"); } catch {}
|
|
493
|
+
try { db.exec("ALTER TABLE tasks ADD COLUMN meta_json TEXT"); } catch {}
|
|
492
494
|
try { db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_task_id)"); } catch {}
|
|
493
495
|
|
|
494
496
|
// Helpers
|
package/src/index.ts
CHANGED
|
@@ -51,6 +51,11 @@ console.info = (...args: any[]) => { pushLog("info", args); _origConsole.info(..
|
|
|
51
51
|
console.warn = (...args: any[]) => { pushLog("warn", args); _origConsole.warn(...args); };
|
|
52
52
|
console.error = (...args: any[]) => { pushLog("error", args); _origConsole.error(...args); };
|
|
53
53
|
|
|
54
|
+
function normalizeMetaJson(meta: unknown): string | null {
|
|
55
|
+
if (!meta || typeof meta !== "object") return null;
|
|
56
|
+
try { return JSON.stringify(meta); } catch { return null; }
|
|
57
|
+
}
|
|
58
|
+
|
|
54
59
|
// ── Rate limiter (in-memory, per IP) ──
|
|
55
60
|
const rateLimits = new Map<string, { count: number; resetAt: number }>();
|
|
56
61
|
function checkRateLimit(ip: string, maxPerMinute = 60): boolean {
|
|
@@ -343,6 +348,8 @@ const TaskSchema = z.object({
|
|
|
343
348
|
from: z.string().max(200).optional(),
|
|
344
349
|
network_id: z.string().max(200).optional(),
|
|
345
350
|
parent_task_id: z.string().max(200).optional(),
|
|
351
|
+
ttl_seconds: z.number().min(1).max(86400).optional(),
|
|
352
|
+
meta: z.any().optional(),
|
|
346
353
|
});
|
|
347
354
|
|
|
348
355
|
const BroadcastSchema = z.object({
|
|
@@ -1194,6 +1201,7 @@ Bun.serve({
|
|
|
1194
1201
|
const id = crypto.randomUUID();
|
|
1195
1202
|
const fromSession = body.from || "api";
|
|
1196
1203
|
const ttlSeconds = (body as any).ttl_seconds || 3600;
|
|
1204
|
+
const metaJson = normalizeMetaJson((body as any).meta);
|
|
1197
1205
|
// Mirror send_task MCP: write inbox + tasks rows in a single
|
|
1198
1206
|
// transaction so the dispatch is visible to dashboard's Tasks page
|
|
1199
1207
|
// and the parent_task_id lineage chain. Previously this endpoint
|
|
@@ -1201,14 +1209,14 @@ Bun.serve({
|
|
|
1201
1209
|
// dispatched via REST (anet demo, dashboard Dispatch button, etc.).
|
|
1202
1210
|
db.transaction(() => {
|
|
1203
1211
|
db.run(
|
|
1204
|
-
`INSERT INTO inbox (id, session_name, type, priority, content, from_session, requires_response, network_id)
|
|
1205
|
-
VALUES (?1, ?2, 'task', ?3, ?4, ?5, 'reply', ?6)`,
|
|
1206
|
-
[id, targetAlias, body.priority, body.task, fromSession, taskNetId]
|
|
1212
|
+
`INSERT INTO inbox (id, session_name, type, priority, content, from_session, requires_response, network_id, meta_json)
|
|
1213
|
+
VALUES (?1, ?2, 'task', ?3, ?4, ?5, 'reply', ?6, ?7)`,
|
|
1214
|
+
[id, targetAlias, body.priority, body.task, fromSession, taskNetId, metaJson]
|
|
1207
1215
|
);
|
|
1208
1216
|
db.run(
|
|
1209
|
-
`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)
|
|
1210
|
-
VALUES (?1, ?2, ?3, ?4, 'delivered', ?5, 'reply', datetime('now'), datetime('now'), datetime('now', ?6), ?7, ?8)`,
|
|
1211
|
-
[id, fromSession, targetAlias, body.priority, body.task, `+${ttlSeconds} seconds`, taskNetId, body.parent_task_id ?? null]
|
|
1217
|
+
`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, meta_json)
|
|
1218
|
+
VALUES (?1, ?2, ?3, ?4, 'delivered', ?5, 'reply', datetime('now'), datetime('now'), datetime('now', ?6), ?7, ?8, ?9)`,
|
|
1219
|
+
[id, fromSession, targetAlias, body.priority, body.task, `+${ttlSeconds} seconds`, taskNetId, body.parent_task_id ?? null, metaJson]
|
|
1212
1220
|
);
|
|
1213
1221
|
// Touch session row so the dashboard reflects "task in flight"
|
|
1214
1222
|
// immediately, without waiting for the agent's report_status to
|
package/src/tools.ts
CHANGED
|
@@ -9,6 +9,16 @@ function ts(): string {
|
|
|
9
9
|
return new Date().toTimeString().slice(0, 8);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
function parseMetaJson(value: unknown): unknown | null {
|
|
13
|
+
if (!value || typeof value !== "string") return null;
|
|
14
|
+
try { return JSON.parse(value); } catch { return null; }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizeMetaJson(meta: unknown): string | null {
|
|
18
|
+
if (!meta || typeof meta !== "object") return null;
|
|
19
|
+
try { return JSON.stringify(meta); } catch { return null; }
|
|
20
|
+
}
|
|
21
|
+
|
|
12
22
|
export function registerTools(server: McpServer, clientIP?: string, enforceNetworkId?: string | null, enforceUserId?: string | null, callerAlias?: string | null, callerTokenIsNetwork = false) {
|
|
13
23
|
// Default from_session for outbound tools — extracted from the calling
|
|
14
24
|
// token's binding (ntok_ → node alias, utok_ → username). Without this,
|
|
@@ -439,13 +449,16 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
439
449
|
const rows0 = db.get<{ cnt: number }>(countSql, ...countParams);
|
|
440
450
|
console.log(`[${ts()}] ${alias} → get_inbox: ${rows0?.cnt ?? 0} pending messages`);
|
|
441
451
|
const rowsParams: any[] = [alias];
|
|
442
|
-
let rowsSql = `SELECT id, type, priority, content, context, from_session, created_at, network_id
|
|
452
|
+
let rowsSql = `SELECT id, type, priority, content, context, from_session, created_at, network_id, meta_json
|
|
443
453
|
FROM inbox WHERE session_name = ?1 AND acked = 0`;
|
|
444
454
|
rowsSql = addReadScope(rowsSql, rowsParams, readScope);
|
|
445
455
|
rowsSql += ` ORDER BY CASE priority WHEN 'high' THEN 0 WHEN 'normal' THEN 1 ELSE 2 END, created_at
|
|
446
456
|
LIMIT ?${rowsParams.length + 1}`;
|
|
447
457
|
rowsParams.push(limit);
|
|
448
|
-
const rows = db.all(rowsSql, ...rowsParams)
|
|
458
|
+
const rows = db.all(rowsSql, ...rowsParams).map((row: any) => ({
|
|
459
|
+
...row,
|
|
460
|
+
meta: parseMetaJson(row.meta_json),
|
|
461
|
+
}));
|
|
449
462
|
|
|
450
463
|
return {
|
|
451
464
|
content: [{ type: "text" as const, text: JSON.stringify({ ok: true, messages: rows }) }],
|
|
@@ -585,9 +598,11 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
585
598
|
ttl_seconds: z.number().min(1).max(86400).optional().describe("Task TTL in seconds (default: 3600)"),
|
|
586
599
|
network_id: z.string().max(200).optional().describe("Network scope"),
|
|
587
600
|
parent_task_id: z.string().max(200).optional().describe("Parent task this dispatch is on behalf of. When the child task replies the hub will auto-chain the answer to the parent task's originator, so the user sees the final result even if the intermediate session ends."),
|
|
601
|
+
meta: z.any().optional().describe("Optional structured task metadata, e.g. { attachments: [{ type, path, url, mime, name, size }] }."),
|
|
588
602
|
},
|
|
589
|
-
async ({ alias, task, priority, context, from_session: _fromIn, ttl_seconds, network_id: netId, parent_task_id: parentIn }) => { const from_session = defaultFrom(_fromIn);
|
|
603
|
+
async ({ alias, task, priority, context, from_session: _fromIn, ttl_seconds, network_id: netId, parent_task_id: parentIn, meta }) => { const from_session = defaultFrom(_fromIn);
|
|
590
604
|
const effectiveNetId = getNetworkId(netId);
|
|
605
|
+
const metaJson = normalizeMetaJson(meta);
|
|
591
606
|
// Resolve parent_task_id: explicit > inferred (caller's most recent
|
|
592
607
|
// delivered/started inbox task that's still open). Inference is the
|
|
593
608
|
// safety net for when the LLM forgets to pass parent_task_id.
|
|
@@ -629,14 +644,14 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
629
644
|
// 报告冲突)。
|
|
630
645
|
db.transaction(() => {
|
|
631
646
|
db.run(
|
|
632
|
-
`INSERT INTO inbox (id, session_name, type, priority, content, context, from_session, requires_response, network_id)
|
|
633
|
-
VALUES (?1, ?2, 'task', ?3, ?4, ?5, ?6, 'reply', ?7)`,
|
|
634
|
-
[id, targetAlias, priority, task, context ?? null, from_session, effectiveNetId ?? null]
|
|
647
|
+
`INSERT INTO inbox (id, session_name, type, priority, content, context, from_session, requires_response, network_id, meta_json)
|
|
648
|
+
VALUES (?1, ?2, 'task', ?3, ?4, ?5, ?6, 'reply', ?7, ?8)`,
|
|
649
|
+
[id, targetAlias, priority, task, context ?? null, from_session, effectiveNetId ?? null, metaJson]
|
|
635
650
|
);
|
|
636
651
|
db.run(
|
|
637
|
-
`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)
|
|
638
|
-
VALUES (?1, ?2, ?3, ?4, 'delivered', ?5, 'reply', datetime('now'), datetime('now'), datetime('now', ?6), ?7, ?8)`,
|
|
639
|
-
[id, from_session, targetAlias, priority, task, `+${ttl_seconds || 3600} seconds`, effectiveNetId ?? null, parentTaskId]
|
|
652
|
+
`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, meta_json)
|
|
653
|
+
VALUES (?1, ?2, ?3, ?4, 'delivered', ?5, 'reply', datetime('now'), datetime('now'), datetime('now', ?6), ?7, ?8, ?9)`,
|
|
654
|
+
[id, from_session, targetAlias, priority, task, `+${ttl_seconds || 3600} seconds`, effectiveNetId ?? null, parentTaskId, metaJson]
|
|
640
655
|
);
|
|
641
656
|
const touchParams: any[] = [task.slice(0, 200), targetAlias];
|
|
642
657
|
let touchSql = "UPDATE sessions SET task = ?1, updated_at = datetime('now') WHERE alias = ?2";
|