@sym-bot/mesh-channel 0.3.20 → 0.3.21

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sym-mesh-channel",
3
- "version": "0.3.20",
3
+ "version": "0.3.21",
4
4
  "description": "Real-time Claude-to-Claude mesh. Agent-to-agent cognitive signals over Bonjour LAN or WebSocket relay.",
5
5
  "author": {
6
6
  "name": "Hongwei Xu",
package/.mcp.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "command": "npx",
5
5
  "args": [
6
6
  "-y",
7
- "@sym-bot/mesh-channel@0.3.20"
7
+ "@sym-bot/mesh-channel@0.3.21"
8
8
  ],
9
9
  "env": {
10
10
  "SYM_RELAY_URL": "${user_config.relay_url}",
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.21
4
+
5
+ ### Changed
6
+
7
+ - **`sym_inbox` is now a thin adapter over the SDK primitive `node.inbox()`** (`@sym-bot/sym` ^0.7.11). The pull-based receive buffer + drain cursor moved down into the node, where it belongs alongside `node.remember()` (send) — so the SDK is sufficient for send **and** pull on its own, and the wrapper owns no buffering logic. Behaviour is unchanged (FIFO drain, `peek`, `limit`); `sym_fetch` now also resolves SDK inbox ids (`inNNNN`). The wrapper still applies the peer allowlist + prompt-injection filter on the pull path before anything enters context.
8
+
3
9
  ## 0.3.20
4
10
 
5
11
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sym-bot/mesh-channel",
3
- "version": "0.3.20",
3
+ "version": "0.3.21",
4
4
  "description": "MCP server — real-time agent-to-agent cognition for Claude Code remote teams via the SYM mesh.",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -22,7 +22,7 @@
22
22
  ],
23
23
  "dependencies": {
24
24
  "@modelcontextprotocol/sdk": "^1.12.1",
25
- "@sym-bot/sym": "^0.7.10",
25
+ "@sym-bot/sym": "^0.7.11",
26
26
  "bonjour-service": "^1.3.0"
27
27
  },
28
28
  "engines": {
package/server.js CHANGED
@@ -651,6 +651,12 @@ mcp.setRequestHandler(CallToolRequestSchema, async (request) => {
651
651
  }
652
652
 
653
653
  case 'sym_fetch': {
654
+ // inNNNN → SDK delivery inbox (pull path); mNNN → channel-push store.
655
+ if (typeof args.msg_id === 'string' && args.msg_id.startsWith('in')) {
656
+ const m = node.inboxGet(args.msg_id);
657
+ if (!m) return { content: [{ type: 'text', text: `Message ${args.msg_id} not found (expired or invalid ID).` }] };
658
+ return { content: [{ type: 'text', text: `[${m.from}] ${new Date(m.receivedAt).toISOString()}\n\n${m.content}` }] };
659
+ }
654
660
  const entry = MESSAGE_STORE.get(args.msg_id);
655
661
  if (!entry) {
656
662
  return { content: [{ type: 'text', text: `Message ${args.msg_id} not found (expired or invalid ID).` }] };
@@ -664,35 +670,34 @@ mcp.setRequestHandler(CallToolRequestSchema, async (request) => {
664
670
  }
665
671
 
666
672
  case 'sym_inbox': {
667
- const limit = Math.max(1, Math.min(args.limit || 50, MAX_STORED));
668
- const fresh = [];
669
- for (const [id, entry] of MESSAGE_STORE) {
670
- const seq = parseInt(id.slice(1), 10);
671
- if (seq > inboxCursor) fresh.push({ id, seq, entry });
672
- }
673
- fresh.sort((a, b) => a.seq - b.seq);
674
- // FIFO: drain the OLDEST `limit` first so the cursor advances contiguously
675
- // and a backlog larger than `limit` is never skipped — the rest come on the
676
- // next call. (Newest-first would jump the cursor past un-returned items.)
677
- const slice = fresh.slice(0, limit);
678
- if (!args.peek && slice.length) {
679
- inboxCursor = Math.max(inboxCursor, ...slice.map((i) => i.seq));
680
- }
681
- if (!slice.length) {
673
+ // Thin adapter over the SDK primitive: the node owns the delivery buffer
674
+ // + drain cursor (node.inbox()). This wrapper only formats for display.
675
+ const { messages, remaining } = node.inbox({ peek: !!args.peek, limit: args.limit });
676
+ if (!messages.length) {
682
677
  return { content: [{ type: 'text', text: 'Inbox empty — no new mesh messages since your last check.' }] };
683
678
  }
684
- const more = fresh.length - slice.length;
685
679
  const now = Date.now();
686
- const lines = slice.map(({ id, entry }) => {
687
- const age = Math.round((now - entry.timestamp) / 1000);
688
- const head = entry.header || `[${entry.from}] ${String(entry.content || '').replace(/\s+/g, ' ').slice(0, 80)}`;
689
- return `${head} [${id}] (${age}s ago)`;
690
- });
691
- const moreNote = more > 0 ? ` (+${more} more — call sym_inbox again)` : '';
680
+ const lines = messages.map((m) => {
681
+ if (m.from === NODE_NAME) return null; // never surface our own deliveries
682
+ // The security layer still gates the pull path: peer allowlist +
683
+ // prompt-injection filter run on every message before it enters context.
684
+ if (!isPeerAllowed(m.from)) return null;
685
+ const sec = checkSecurity(m.from, m.fields || {}, m.fields?.payload);
686
+ if (!sec.safe) { securityAudit(sec.reason, m.from, sec.excerpt); return null; }
687
+ const age = Math.round((now - m.receivedAt) / 1000);
688
+ const focus = m.fields?.focus?.text || m.content || '';
689
+ const dirTag = m.directed ? ' →you' : '';
690
+ const memTag = m.directed && m.remixed === false ? ' ·not-stored' : '';
691
+ return `[${m.from}${dirTag}] ${String(focus).replace(/\s+/g, ' ').slice(0, 90)}${memTag} [${m.id}] (${age}s ago)`;
692
+ }).filter(Boolean);
693
+ if (!lines.length) {
694
+ return { content: [{ type: 'text', text: 'Inbox empty — no new mesh messages since your last check.' }] };
695
+ }
696
+ const moreNote = remaining > 0 ? ` (+${remaining} more — call sym_inbox again)` : '';
692
697
  return {
693
698
  content: [{
694
699
  type: 'text',
695
- text: `${slice.length} new mesh message(s)${args.peek ? ' (peek — not drained)' : ''}${moreNote}:\n${lines.join('\n')}\n\nUse sym_fetch <id> for full content; reply via sym_send to=<peer>.`,
700
+ text: `${lines.length} new mesh message(s)${args.peek ? ' (peek — not drained)' : ''}${moreNote}:\n${lines.join('\n')}\n\nUse sym_fetch <id> for full content; reply via sym_send to=<peer>.`,
696
701
  }],
697
702
  };
698
703
  }
@@ -909,9 +914,8 @@ mcp.setRequestHandler(CallToolRequestSchema, async (request) => {
909
914
  // Per COO spec cmb_compact_channel_v0.1.md: push compact headers,
910
915
  // store full content for on-demand sym_fetch retrieval. ~10% token
911
916
  // savings on mesh traffic without context loss.
912
- const MESSAGE_STORE = new Map();
917
+ const MESSAGE_STORE = new Map(); // channel-push surface (mNNN) for sym_fetch when channels are enabled
913
918
  let msgSeq = 0;
914
- let inboxCursor = 0; // highest msgSeq drained by sym_inbox (pull-based receive)
915
919
  const MAX_STORED = 200;
916
920
 
917
921
  function storeMessage(from, content, header) {