@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleep2agi/commhub-server",
3
- "version": "0.8.3-preview.2",
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";