@sleep2agi/commhub-server 0.8.4-preview.0 → 0.8.4

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  CommHub: MCP Streamable HTTP + SSE push + REST API for an AI agent network. Single-process Bun server, SQLite-backed, zero config when launched through `anet`.
4
4
 
5
- The supported path is to install the `anet` CLI (`@sleep2agi/agent-network` 2.1.7) and run `anet hub start`, which wires up the port, default admin account, recovery admin `utok_`, and local config for you.
5
+ The supported path is to install the `anet` CLI (`@sleep2agi/agent-network`, currently v2.2.9 at v0.10.10) and run `anet hub start`, which wires up the port, default admin account, recovery admin `utok_`, and local config for you.
6
6
 
7
7
  ## Quick start (verified)
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleep2agi/commhub-server",
3
- "version": "0.8.4-preview.0",
3
+ "version": "0.8.4",
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/tools.ts CHANGED
@@ -26,6 +26,22 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
26
26
  // couldn't tell who actually asked them. Network-bound node tokens are an
27
27
  // identity boundary: they must not spoof another node via from_session.
28
28
  const defaultFrom = (clientFrom?: string) => (callerTokenIsNetwork && callerAlias) ? callerAlias : (clientFrom || callerAlias || "hub");
29
+ const fromIdentityMismatchReply = (clientFrom?: string) => {
30
+ const requestedFrom = clientFrom?.trim();
31
+ if (!callerTokenIsNetwork || !callerAlias || !requestedFrom || requestedFrom === callerAlias) return null;
32
+ return {
33
+ content: [{
34
+ type: "text" as const,
35
+ text: JSON.stringify({
36
+ ok: false,
37
+ error: "from_session_identity_mismatch",
38
+ message: "network token from_session does not match token-bound node alias",
39
+ token_alias: callerAlias,
40
+ requested_from_session: requestedFrom,
41
+ }),
42
+ }],
43
+ };
44
+ };
29
45
  // If enforceNetworkId is set, override any client-supplied network_id
30
46
  const getNetworkId = (clientNetId?: string | null) => enforceNetworkId ?? clientNetId ?? null;
31
47
 
@@ -605,7 +621,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
605
621
  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."),
606
622
  meta: z.any().optional().describe("Optional structured task metadata, e.g. { attachments: [{ type, path, url, mime, name, size }] }."),
607
623
  },
608
- async ({ alias, task, priority, context, from_session: _fromIn, ttl_seconds, network_id: netId, parent_task_id: parentIn, meta }) => { const from_session = defaultFrom(_fromIn);
624
+ async ({ alias, task, priority, context, from_session: _fromIn, ttl_seconds, network_id: netId, parent_task_id: parentIn, meta }) => { const fromMismatch = fromIdentityMismatchReply(_fromIn); if (fromMismatch) return fromMismatch; const from_session = defaultFrom(_fromIn);
609
625
  const effectiveNetId = getNetworkId(netId);
610
626
  const metaJson = normalizeMetaJson(meta);
611
627
  // Resolve parent_task_id: explicit > inferred (caller's most recent
@@ -704,7 +720,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
704
720
  message: z.string().min(1).max(10000).describe("Message content"),
705
721
  from_session: z.string().max(200).optional(),
706
722
  },
707
- async ({ alias, message, from_session: _fromIn }) => { const from_session = defaultFrom(_fromIn);
723
+ async ({ alias, message, from_session: _fromIn }) => { const fromMismatch = fromIdentityMismatchReply(_fromIn); if (fromMismatch) return fromMismatch; const from_session = defaultFrom(_fromIn);
708
724
  const effectiveNetId = getNetworkId(null);
709
725
  if (!canWrite(effectiveNetId)) return writeDeniedReply(effectiveNetId);
710
726
  console.log(`[${ts()}] ${from_session} → send_message → ${alias}: ${message.slice(0, 60)}`);
@@ -745,7 +761,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
745
761
  status: z.enum(["replied", "failed", "cancelled"]).optional().default("replied").describe("Task outcome"),
746
762
  from_session: z.string().max(200).optional(),
747
763
  },
748
- async ({ alias, text, in_reply_to, status: replyStatus, from_session: _fromIn }) => { const from_session = defaultFrom(_fromIn);
764
+ async ({ alias, text, in_reply_to, status: replyStatus, from_session: _fromIn }) => { const fromMismatch = fromIdentityMismatchReply(_fromIn); if (fromMismatch) return fromMismatch; const from_session = defaultFrom(_fromIn);
749
765
  const effectiveNetId = getNetworkId(null);
750
766
  if (!canWrite(effectiveNetId)) return writeDeniedReply(effectiveNetId);
751
767
  console.log(`[${ts()}] ${from_session} → send_reply (${replyStatus}) → ${alias}: ${text.slice(0, 60)}`);
@@ -820,7 +836,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
820
836
  task_id: z.string().min(1).max(200).describe("Task ID to acknowledge"),
821
837
  from_session: z.string().max(200).optional(),
822
838
  },
823
- async ({ task_id, from_session: _fromIn }) => { const from_session = defaultFrom(_fromIn);
839
+ async ({ task_id, from_session: _fromIn }) => { const fromMismatch = fromIdentityMismatchReply(_fromIn); if (fromMismatch) return fromMismatch; const from_session = defaultFrom(_fromIn);
824
840
  const effectiveNetId = getNetworkId(null);
825
841
  if (!canWrite(effectiveNetId)) return writeDeniedReply(effectiveNetId);
826
842
  console.log(`[${ts()}] ${from_session} → send_ack → task ${task_id.slice(0, 8)}`);
@@ -846,7 +862,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
846
862
  task_id: z.string().min(1).max(200).describe("Task ID to retry"),
847
863
  from_session: z.string().max(200).optional(),
848
864
  },
849
- async ({ task_id, from_session: _fromIn }) => { const from_session = defaultFrom(_fromIn);
865
+ async ({ task_id, from_session: _fromIn }) => { const fromMismatch = fromIdentityMismatchReply(_fromIn); if (fromMismatch) return fromMismatch; const from_session = defaultFrom(_fromIn);
850
866
  const effectiveNetId = getNetworkId(null);
851
867
  if (!canWrite(effectiveNetId)) return writeDeniedReply(effectiveNetId);
852
868
  console.log(`[${ts()}] ${from_session} → retry_task → ${task_id.slice(0, 8)}`);
@@ -957,7 +973,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
957
973
  reason: z.string().max(1000).optional().describe("Cancellation reason"),
958
974
  from_session: z.string().max(200).optional(),
959
975
  },
960
- async ({ task_id, reason, from_session: _fromIn }) => { const from_session = defaultFrom(_fromIn);
976
+ async ({ task_id, reason, from_session: _fromIn }) => { const fromMismatch = fromIdentityMismatchReply(_fromIn); if (fromMismatch) return fromMismatch; const from_session = defaultFrom(_fromIn);
961
977
  const effectiveNetId = getNetworkId(null);
962
978
  if (!canWrite(effectiveNetId)) return writeDeniedReply(effectiveNetId);
963
979
  console.log(`[${ts()}] ${from_session} → cancel_task → ${task_id.slice(0, 8)}`);
@@ -989,7 +1005,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
989
1005
  new_alias: z.string().min(1).max(200).describe("Target agent alias"),
990
1006
  from_session: z.string().max(200).optional(),
991
1007
  },
992
- async ({ task_id, new_alias, from_session: _fromIn }) => { const from_session = defaultFrom(_fromIn);
1008
+ async ({ task_id, new_alias, from_session: _fromIn }) => { const fromMismatch = fromIdentityMismatchReply(_fromIn); if (fromMismatch) return fromMismatch; const from_session = defaultFrom(_fromIn);
993
1009
  const effectiveNetId = getNetworkId(null);
994
1010
  if (!canWrite(effectiveNetId)) return writeDeniedReply(effectiveNetId);
995
1011
  console.log(`[${ts()}] ${from_session} → reassign_task → ${task_id.slice(0, 8)} → ${new_alias}`);