agent-inbox 0.1.8 → 0.2.0

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.
Files changed (75) hide show
  1. package/CLAUDE.md +44 -7
  2. package/README.md +67 -24
  3. package/dist/cli.d.ts +20 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +89 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/federation/connection-manager.d.ts +13 -2
  8. package/dist/federation/connection-manager.d.ts.map +1 -1
  9. package/dist/federation/connection-manager.js +109 -10
  10. package/dist/federation/connection-manager.js.map +1 -1
  11. package/dist/index.d.mts +2 -0
  12. package/dist/index.d.ts +25 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +58 -5
  15. package/dist/index.js.map +1 -1
  16. package/dist/index.mjs +1 -0
  17. package/dist/index.mjs.map +1 -0
  18. package/dist/ipc/ipc-server.d.ts +2 -0
  19. package/dist/ipc/ipc-server.d.ts.map +1 -1
  20. package/dist/ipc/ipc-server.js +48 -0
  21. package/dist/ipc/ipc-server.js.map +1 -1
  22. package/dist/map/map-client.d.ts +100 -0
  23. package/dist/map/map-client.d.ts.map +1 -1
  24. package/dist/map/map-client.js +61 -0
  25. package/dist/map/map-client.js.map +1 -1
  26. package/dist/mcp/mcp-proxy.d.ts +28 -0
  27. package/dist/mcp/mcp-proxy.d.ts.map +1 -0
  28. package/dist/mcp/mcp-proxy.js +280 -0
  29. package/dist/mcp/mcp-proxy.js.map +1 -0
  30. package/dist/mesh/delivery-bridge.d.ts +47 -0
  31. package/dist/mesh/delivery-bridge.d.ts.map +1 -0
  32. package/dist/mesh/delivery-bridge.js +73 -0
  33. package/dist/mesh/delivery-bridge.js.map +1 -0
  34. package/dist/mesh/mesh-connector.d.ts +29 -0
  35. package/dist/mesh/mesh-connector.d.ts.map +1 -0
  36. package/dist/mesh/mesh-connector.js +36 -0
  37. package/dist/mesh/mesh-connector.js.map +1 -0
  38. package/dist/mesh/mesh-transport.d.ts +70 -0
  39. package/dist/mesh/mesh-transport.d.ts.map +1 -0
  40. package/dist/mesh/mesh-transport.js +92 -0
  41. package/dist/mesh/mesh-transport.js.map +1 -0
  42. package/dist/mesh/type-mapper.d.ts +67 -0
  43. package/dist/mesh/type-mapper.d.ts.map +1 -0
  44. package/dist/mesh/type-mapper.js +165 -0
  45. package/dist/mesh/type-mapper.js.map +1 -0
  46. package/dist/types.d.ts +29 -2
  47. package/dist/types.d.ts.map +1 -1
  48. package/docs/CLAUDE-CODE-SWARM-PROPOSAL.md +137 -0
  49. package/package.json +10 -2
  50. package/src/cli.ts +94 -0
  51. package/src/federation/connection-manager.ts +125 -10
  52. package/src/index.ts +96 -5
  53. package/src/ipc/ipc-server.ts +58 -0
  54. package/src/map/map-client.ts +152 -0
  55. package/src/mcp/mcp-proxy.ts +326 -0
  56. package/src/mesh/delivery-bridge.ts +110 -0
  57. package/src/mesh/mesh-connector.ts +41 -0
  58. package/src/mesh/mesh-transport.ts +157 -0
  59. package/src/mesh/type-mapper.ts +239 -0
  60. package/src/types.ts +33 -1
  61. package/test/federation/integration.test.ts +37 -3
  62. package/test/federation/sdk-integration.test.ts +4 -8
  63. package/test/ipc-new-commands.test.ts +200 -0
  64. package/test/mcp-proxy.test.ts +191 -0
  65. package/test/mesh/delivery-bridge.test.ts +178 -0
  66. package/test/mesh/e2e-mesh.test.ts +527 -0
  67. package/test/mesh/e2e-real-meshpeer.test.ts +629 -0
  68. package/test/mesh/federation-mesh.test.ts +269 -0
  69. package/test/mesh/mesh-connector.test.ts +66 -0
  70. package/test/mesh/mesh-transport.test.ts +191 -0
  71. package/test/mesh/meshpeer-integration.test.ts +442 -0
  72. package/test/mesh/mock-mesh.ts +125 -0
  73. package/test/mesh/mock-meshpeer.ts +266 -0
  74. package/test/mesh/type-mapper.test.ts +226 -0
  75. package/docs/PLAN.md +0 -545
package/CLAUDE.md CHANGED
@@ -13,7 +13,7 @@ npm test # Run all tests (vitest)
13
13
  npm run test:watch # Watch mode
14
14
  ```
15
15
 
16
- All tests use vitest. Run the full suite before committing — there are 256 tests across 18 files. Tests run fast (<2s total).
16
+ All tests use vitest. Run the full suite before committing — there are 356 tests across 27 files. Tests run fast (<6s total).
17
17
 
18
18
  ## Project layout
19
19
 
@@ -38,22 +38,31 @@ src/
38
38
  mcp/mcp-server.ts MCP tool server (send_message, check_inbox, read_thread, list_agents)
39
39
  jsonrpc/mail-server.ts MAP JSON-RPC mail methods
40
40
  push/notifier.ts Inbox file writes + inbox.message event emission
41
+ mesh/
42
+ mesh-transport.ts MapConnection impl over agentic-mesh MessageChannel
43
+ mesh-connector.ts Factory for creating MeshTransport connections (MapAgentConnectionClass)
44
+ delivery-bridge.ts DeliveryHandler bridge — intercepts MAP delivery → inbox storage
45
+ type-mapper.ts MAP Message ↔ Inbox Message bidirectional translation
41
46
 
42
47
  test/
43
48
  federation/ Federation tests (connection-manager, routing, queue, address, trust, integration, sdk-integration)
49
+ mesh/ Mesh transport tests (mesh-transport, mesh-connector, federation-mesh, delivery-bridge, type-mapper, meshpeer-integration, e2e)
44
50
  registry/ Warm registry tests
45
51
  *.test.ts Core tests (storage, router, traceability, IPC, mail-server, push)
52
+
53
+ docs/
54
+ CLAUDE-CODE-SWARM-PROPOSAL.md Proposal: replace claude-code-swarm's sidecar with embedded MeshPeer + agent-inbox
46
55
  ```
47
56
 
48
57
  ## Key architecture concepts
49
58
 
50
59
  ### Message flow
51
60
 
52
- 1. Message comes in via IPC socket, MCP tool, or MAP connection
61
+ 1. Message comes in via IPC socket, MCP tool, MAP connection, or MeshPeer delivery
53
62
  2. `MessageRouter.routeMessage()` resolves recipients, normalizes content, stores the message
54
63
  3. Local recipients: message stored in their inbox via storage
55
64
  4. Remote recipients (`agent@system`): delegated to `ConnectionManager.route()`
56
- 5. Federation sends via MAP SDK transport (or queues if peer is offline)
65
+ 5. Federation sends via FederationGateway (mesh), MAP SDK (WebSocket), or queues if peer is offline
57
66
  6. Traceability layer auto-creates conversations/threads from events
58
67
 
59
68
  ### Federation addressing
@@ -64,6 +73,20 @@ test/
64
73
 
65
74
  Parsed by `parseAddress()` in `src/federation/address.ts`.
66
75
 
76
+ ### Federation transports
77
+
78
+ Two federation transport options, configurable per-peer:
79
+
80
+ **WebSocket (MAP SDK):** Traditional MAP federation over WebSocket. Configure with `FederationPeerConfig.url`.
81
+
82
+ **Mesh (agentic-mesh):** Encrypted P2P federation over agentic-mesh. Two integration levels:
83
+
84
+ - **Phase 1 (raw channel):** `MeshTransport` wraps a `MessageChannel` behind `MapConnection`. Configure with `CreateOptions.mesh` + `FederationPeerConfig.meshPeerId`. Uses channel name `"proto:agent-inbox"`.
85
+
86
+ - **Phase 2 (full MeshPeer):** Agent-inbox registers as an agent on the MeshPeer's MapServer. Uses `FederationGateway` for cross-mesh routing with envelope wrapping, hop counting, and loop detection. `DeliveryBridge` intercepts MAP message delivery → inbox storage. Configure with `CreateOptions.meshPeer`.
87
+
88
+ Both transports can coexist — configure per-peer via `url` vs `meshPeerId`.
89
+
67
90
  ### Storage interface
68
91
 
69
92
  All storage operations go through `Storage` interface (`src/storage/interface.ts`). Two implementations: `InMemoryStorage` (tests + ephemeral) and `SQLiteStorage` (persistent). Tests always use `InMemoryStorage`.
@@ -72,6 +95,22 @@ All storage operations go through `Storage` interface (`src/storage/interface.ts
72
95
 
73
96
  The `@multi-agent-protocol/sdk` is a peer dependency loaded dynamically in `src/map/map-client.ts`. Agent Inbox works without it (IPC + MCP only, no MAP transport). The SDK class is injected into `ConnectionManager` when available.
74
97
 
98
+ ### Mesh transport (agentic-mesh) is optional
99
+
100
+ [agentic-mesh](https://github.com/alexngai/agentic-mesh) is an optional peer dependency for encrypted P2P federation. When a `MeshPeer` is provided via `CreateOptions.meshPeer`, agent-inbox gets:
101
+
102
+ - Direct P2P federation (no central MAP server required)
103
+ - Encrypted tunnels (Nebula/Tailscale handle PKI and NAT traversal)
104
+ - Federation with hop/loop detection via `FederationGateway`
105
+ - Automatic agent discovery via MapServer registry
106
+ - `_meta` field passthrough for inbox semantics (threading, importance, subjects)
107
+
108
+ Key mesh components:
109
+ - `MeshTransport` (`src/mesh/mesh-transport.ts`) — `MapConnection` over `MessageChannel`
110
+ - `MeshConnector` (`src/mesh/mesh-connector.ts`) — factory implementing `MapAgentConnectionClass`
111
+ - `DeliveryBridge` (`src/mesh/delivery-bridge.ts`) — intercepts MAP delivery for inbox storage
112
+ - `TypeMapper` (`src/mesh/type-mapper.ts`) — bidirectional MAP ↔ Inbox message translation
113
+
75
114
  ### Event-driven internals
76
115
 
77
116
  Components communicate via `EventEmitter`. Key events:
@@ -90,10 +129,8 @@ Components communicate via `EventEmitter`. Key events:
90
129
  - IDs generated with `ulid` or `crypto.randomUUID()`
91
130
  - Content normalization: string payloads auto-wrapped to `{ type: "text", text: "..." }`
92
131
  - Federation tests use `MockMapServer` (in-process broker) in `test/federation/sdk-integration.test.ts`
93
-
94
- ## Known design gaps
95
-
96
- - **Broadcast/hierarchical routing strategies** are unreachable through `ConnectionManager.route()` for system-qualified addresses (`bob@system-2`). `resolveRoute()` always returns `addr.system` directly, never falling through to `handleUnknownRoute()`. Agent-only addresses that would trigger it are filtered by `isRemoteAddress()`. Documented in `test/federation/integration.test.ts` and `test/federation/sdk-integration.test.ts`.
132
+ - Mesh tests use `MockMeshContext` (linked pair) in `test/mesh/mock-mesh.ts`
133
+ - Mesh channel name convention: protocol channels use `"proto:"` prefix (e.g., `"proto:agent-inbox"`)
97
134
 
98
135
  ## Common tasks
99
136
 
package/README.md CHANGED
@@ -2,32 +2,34 @@
2
2
 
3
3
  A MAP-native message router for multi-agent systems. Provides structured inbox/outbox semantics, message threading, delivery tracking, and cross-system federation on top of the [Multi-Agent Protocol](https://github.com/anthropics/multi-agent-protocol) transport layer.
4
4
 
5
- Built to work with [claude-code-swarm](https://github.com/alexngai/claude-code-swarm) and any MAP-compatible agent framework.
5
+ Built to work with [claude-code-swarm](https://github.com/alexngai/claude-code-swarm) and any MAP-compatible agent framework. Optionally integrates with [agentic-mesh](https://github.com/alexngai/agentic-mesh) for encrypted P2P federation.
6
6
 
7
7
  ## What it does
8
8
 
9
9
  - **Message routing** — Send messages between agents with `to`, `cc`, `bcc` semantics. Messages are stored in per-agent inboxes with delivery and read tracking.
10
10
  - **Threading** — Messages can be grouped by `thread_tag` and linked via `in_reply_to` for conversation threading.
11
11
  - **Traceability** — Auto-creates conversations, turns, and threads from messaging events following MAP's mail conventions.
12
- - **Federation** — Route messages across independent systems via MAP federation. Supports configurable routing strategies (table, broadcast, hierarchical).
12
+ - **Federation** — Route messages across independent systems. Supports WebSocket (MAP SDK) and encrypted P2P mesh (agentic-mesh) transports, configurable per-peer. Routing strategies: table, broadcast, hierarchical.
13
13
  - **Multiple interfaces** — IPC (UNIX socket), MCP tools (stdio), MAP JSON-RPC, and event-based push via `inbox.message` events.
14
14
 
15
15
  ## Architecture
16
16
 
17
17
  ```
18
- Claude Code Session
19
- |
20
- Plugin (map-hook.mjs)
21
- |
22
- +-- spawn/done/trajectory --> MAP Sidecar (lifecycle)
23
- |
24
- +-- send/emit (messaging) --> Agent Inbox (routing + storage)
25
- |
26
- +-- Local delivery (same system)
27
- +-- Federation (cross-system via MAP)
18
+ Agent Runtime (Claude Code, OpenAI, etc.)
19
+
20
+ Adapter (~200 lines)
21
+
22
+ └── agent-inbox (routing, threading, storage, MCP tools)
23
+
24
+ ├── Local delivery (same system)
25
+ ├── Federation via MAP SDK (WebSocket)
26
+ └── Federation via agentic-mesh (encrypted P2P)
27
+
28
+ MeshPeer (MAP server, FederationGateway,
29
+ agent discovery, hop/loop detection)
28
30
  ```
29
31
 
30
- Agent Inbox runs as an independent service alongside the MAP sidecar. The plugin dispatches messaging commands to Agent Inbox and lifecycle commands to the sidecar.
32
+ Agent Inbox can run standalone (IPC + MCP only), with a MAP server (WebSocket federation), or with an embedded agentic-mesh `MeshPeer` (encrypted P2P federation with full MAP protocol support).
31
33
 
32
34
  ## Quick start
33
35
 
@@ -44,8 +46,10 @@ npm start
44
46
  | `INBOX_SOCKET_PATH` | `~/.claude/agent-inbox/inbox.sock` | IPC socket path |
45
47
  | `INBOX_SCOPE` | `default` | Default message scope |
46
48
  | `INBOX_SYSTEM_ID` | auto-generated | System identity for federation |
47
- | `MAP_SERVER_URL` | | MAP server URL (optional) |
48
- | `MAP_AGENT_NAME` | `agent-inbox` | Agent name for MAP connection |
49
+ | `INBOX_MAP_ENABLED` | `false` | Enable MAP server connection |
50
+ | `INBOX_MAP_SERVER` | | MAP server URL |
51
+ | `INBOX_SQLITE_PATH` | — | SQLite database path (enables persistent storage) |
52
+ | `INBOX_HTTP_PORT` | `0` | HTTP port for JSON-RPC endpoint |
49
53
 
50
54
  ### As an MCP tool server
51
55
 
@@ -58,6 +62,11 @@ Agent Inbox exposes 4 MCP tools that agents can call directly:
58
62
  | `read_thread` | Read all messages in a thread by `threadTag` |
59
63
  | `list_agents` | List registered agents (local and optionally federated) |
60
64
 
65
+ ```bash
66
+ # Run in MCP mode (stdio transport for Claude Code integration)
67
+ npm start -- mcp
68
+ ```
69
+
61
70
  ### IPC protocol
62
71
 
63
72
  NDJSON over UNIX socket. Commands:
@@ -86,25 +95,51 @@ Agents on different systems can message each other using `agent@system` addressi
86
95
  alice@system-1 --> bob@system-2
87
96
  ```
88
97
 
89
- ### Configuration
98
+ ### Transports
99
+
100
+ Two federation transport options, configurable per-peer:
101
+
102
+ **WebSocket (MAP SDK)** — Traditional MAP federation. Requires a reachable MAP server endpoint.
90
103
 
91
- Federation peers are configured explicitly:
104
+ ```typescript
105
+ await createAgentInbox({
106
+ enableFederation: true,
107
+ config: {
108
+ federation: {
109
+ peers: [{ systemId: "partner", url: "ws://partner:3001" }]
110
+ }
111
+ }
112
+ });
113
+ ```
114
+
115
+ **Mesh (agentic-mesh)** — Encrypted P2P federation via Nebula/Tailscale tunnels. No central server required. Provides hop/loop detection, message buffering, and automatic agent discovery.
92
116
 
93
117
  ```typescript
94
- await inbox.federate({
95
- systemId: "partner-system",
96
- url: "ws://partner.example.com:3001",
97
- exposure: { agents: "all" },
118
+ import { MeshPeer } from "agentic-mesh";
119
+
120
+ const peer = MeshPeer.createEmbedded({ peerId: "my-system" });
121
+ await peer.start();
122
+
123
+ await createAgentInbox({
124
+ meshPeer: peer,
125
+ enableFederation: true,
126
+ config: {
127
+ federation: {
128
+ peers: [{ systemId: "partner", meshPeerId: "partner-peer" }]
129
+ }
130
+ }
98
131
  });
99
132
  ```
100
133
 
134
+ Both transports can coexist — some peers use WebSocket, others use mesh.
135
+
101
136
  ### Routing strategies
102
137
 
103
138
  | Strategy | Behavior |
104
139
  |----------|----------|
105
140
  | `table` (default) | In-memory routing table populated from peer exposure data. TTL-based expiry. |
106
141
  | `broadcast` | Forward to all connected peers. First responder wins. |
107
- | `hierarchical` | Local table first, then delegate to upstream hubs. |
142
+ | `hierarchical` | Local table first, then delegate to upstream hubs. Works with system-qualified addresses for indirect relay. |
108
143
 
109
144
  ### Trust
110
145
 
@@ -153,6 +188,11 @@ src/
153
188
  mail-server.ts MAP JSON-RPC mail methods
154
189
  push/
155
190
  notifier.ts Inbox file writes + inbox.message event emission
191
+ mesh/
192
+ mesh-transport.ts MapConnection impl over agentic-mesh MessageChannel
193
+ mesh-connector.ts Factory for MeshTransport connections
194
+ delivery-bridge.ts DeliveryHandler bridge (MAP delivery → inbox storage)
195
+ type-mapper.ts Bidirectional MAP ↔ Inbox message translation
156
196
  ```
157
197
 
158
198
  ## Testing
@@ -162,7 +202,7 @@ npm test # Run all tests
162
202
  npm run test:watch # Watch mode
163
203
  ```
164
204
 
165
- 256 tests across 18 test files covering:
205
+ 357 tests across 27 test files covering:
166
206
 
167
207
  - Storage (in-memory + SQLite)
168
208
  - Message routing and content normalization
@@ -174,6 +214,9 @@ npm run test:watch # Watch mode
174
214
  - MAP JSON-RPC mail methods
175
215
  - Warm agent registry lifecycle
176
216
  - Push model (inbox.message events + inbox file writes)
217
+ - Mesh transport (MeshTransport, MeshConnector, federation-mesh integration)
218
+ - Mesh Phase 2 (DeliveryBridge, TypeMapper, MeshPeer integration)
219
+ - E2E with real agentic-mesh MeshPeer instances (bidirectional messaging, reply chains, concurrent load)
177
220
 
178
221
  ## Development
179
222
 
@@ -188,7 +231,7 @@ Requires Node.js >= 18.
188
231
  ## Further reading
189
232
 
190
233
  - [docs/DESIGN.md](docs/DESIGN.md) — Detailed architecture and design decisions
191
- - [docs/PLAN.md](docs/PLAN.md) — Implementation plan and phasing
234
+ - [docs/CLAUDE-CODE-SWARM-PROPOSAL.md](docs/CLAUDE-CODE-SWARM-PROPOSAL.md) — Proposal: replace claude-code-swarm's sidecar with embedded MeshPeer + agent-inbox
192
235
 
193
236
  ## License
194
237
 
package/dist/cli.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI entry point for agent-inbox.
4
+ *
5
+ * Usage:
6
+ * agent-inbox # IPC server mode (default)
7
+ * agent-inbox mcp # MCP stdio server (standalone or proxy if INBOX_SOCKET_PATH set)
8
+ * agent-inbox mcp-proxy # MCP proxy to existing IPC socket
9
+ *
10
+ * Environment variables:
11
+ * INBOX_SOCKET_PATH — IPC socket path (also triggers proxy mode for `mcp`)
12
+ * INBOX_SQLITE_PATH — SQLite DB path (default: in-memory)
13
+ * INBOX_SCOPE — Message scope (default: "default")
14
+ * INBOX_AGENT_ID — Agent ID for proxy mode (default: "anonymous")
15
+ * INBOX_HTTP_PORT — HTTP port for JSON-RPC (0 = disabled)
16
+ * INBOX_MAP_ENABLED — Enable MAP transport ("true")
17
+ * INBOX_MAP_SERVER — MAP server URL
18
+ */
19
+ export {};
20
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;GAgBG"}
package/dist/cli.js ADDED
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI entry point for agent-inbox.
4
+ *
5
+ * Usage:
6
+ * agent-inbox # IPC server mode (default)
7
+ * agent-inbox mcp # MCP stdio server (standalone or proxy if INBOX_SOCKET_PATH set)
8
+ * agent-inbox mcp-proxy # MCP proxy to existing IPC socket
9
+ *
10
+ * Environment variables:
11
+ * INBOX_SOCKET_PATH — IPC socket path (also triggers proxy mode for `mcp`)
12
+ * INBOX_SQLITE_PATH — SQLite DB path (default: in-memory)
13
+ * INBOX_SCOPE — Message scope (default: "default")
14
+ * INBOX_AGENT_ID — Agent ID for proxy mode (default: "anonymous")
15
+ * INBOX_HTTP_PORT — HTTP port for JSON-RPC (0 = disabled)
16
+ * INBOX_MAP_ENABLED — Enable MAP transport ("true")
17
+ * INBOX_MAP_SERVER — MAP server URL
18
+ */
19
+ import { EventEmitter } from "node:events";
20
+ import * as path from "node:path";
21
+ import * as os from "node:os";
22
+ import { InMemoryStorage } from "./storage/memory.js";
23
+ import { SqliteStorage } from "./storage/sqlite.js";
24
+ import { MessageRouter } from "./router/message-router.js";
25
+ import { TraceabilityLayer } from "./traceability/traceability.js";
26
+ import { InboxMcpServer } from "./mcp/mcp-server.js";
27
+ function defaultSocketPath() {
28
+ return path.join(os.homedir(), ".claude", "agent-inbox", "inbox.sock");
29
+ }
30
+ const mode = process.argv[2] ?? "mcp";
31
+ if (mode === "mcp-proxy" || (mode === "mcp" && process.env.INBOX_SOCKET_PATH)) {
32
+ // MCP proxy mode — forward tool calls to an existing inbox IPC socket
33
+ const { InboxMcpProxy } = await import("./mcp/mcp-proxy.js");
34
+ const socketPath = process.env.INBOX_SOCKET_PATH ?? defaultSocketPath();
35
+ const agentId = process.env.INBOX_AGENT_ID ?? "anonymous";
36
+ const scope = process.env.INBOX_SCOPE ?? "default";
37
+ console.error(`[agent-inbox] MCP proxy mode → ${socketPath} (agent: ${agentId})`);
38
+ const proxy = new InboxMcpProxy(socketPath, agentId, scope);
39
+ proxy.start().catch((err) => {
40
+ console.error("MCP proxy failed:", err);
41
+ process.exit(1);
42
+ });
43
+ }
44
+ else if (mode === "mcp") {
45
+ // MCP standalone mode — stdio transport, own storage
46
+ const scope = process.env.INBOX_SCOPE ?? "default";
47
+ const sqlitePath = process.env.INBOX_SQLITE_PATH;
48
+ const storage = sqlitePath
49
+ ? new SqliteStorage({ path: sqlitePath })
50
+ : new InMemoryStorage();
51
+ const events = new EventEmitter();
52
+ const router = new MessageRouter(storage, events, scope);
53
+ const _traceability = new TraceabilityLayer(storage, events);
54
+ const mcpServer = new InboxMcpServer(router, storage, scope);
55
+ console.error("[agent-inbox] MCP standalone mode (stdio)");
56
+ mcpServer.start().catch((err) => {
57
+ console.error("MCP server failed:", err);
58
+ process.exit(1);
59
+ });
60
+ }
61
+ else if (mode === "ipc") {
62
+ const { createAgentInbox } = await import("./index.js");
63
+ const httpPort = process.env.INBOX_HTTP_PORT
64
+ ? parseInt(process.env.INBOX_HTTP_PORT, 10)
65
+ : 0;
66
+ const sqlitePath = process.env.INBOX_SQLITE_PATH;
67
+ createAgentInbox({ httpPort, sqlitePath })
68
+ .then((inbox) => {
69
+ console.error(`[agent-inbox] IPC server listening on ${process.env.INBOX_SOCKET_PATH ?? defaultSocketPath()}`);
70
+ process.on("SIGINT", async () => {
71
+ await inbox.stop();
72
+ process.exit(0);
73
+ });
74
+ process.on("SIGTERM", async () => {
75
+ await inbox.stop();
76
+ process.exit(0);
77
+ });
78
+ })
79
+ .catch((err) => {
80
+ console.error("Failed to start Agent Inbox:", err);
81
+ process.exit(1);
82
+ });
83
+ }
84
+ else {
85
+ console.error(`Unknown mode: ${mode}`);
86
+ console.error("Usage: agent-inbox [mcp|mcp-proxy|ipc]");
87
+ process.exit(1);
88
+ }
89
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { EventEmitter } from "node:events";
2
2
  import type { FederationConfig, FederationLink, FederationPeerConfig, Message, SystemId } from "../types.js";
3
- import type { MapAgentConnectionClass } from "../map/map-client.js";
3
+ import type { MapAgentConnectionClass, MeshPeerLike } from "../map/map-client.js";
4
+ import type { MeshConnector } from "../mesh/mesh-connector.js";
4
5
  import { RoutingEngine } from "./routing-engine.js";
5
6
  import { DeliveryQueue } from "./delivery-queue.js";
6
7
  import { TrustManager } from "./trust.js";
@@ -32,14 +33,20 @@ export declare class ConnectionManager {
32
33
  private config;
33
34
  private peers;
34
35
  private connections;
36
+ private gateways;
35
37
  private systemId;
36
38
  private sdkClass;
39
+ private meshConnector;
40
+ private meshPeer;
37
41
  private onIncoming;
38
42
  readonly routing: RoutingEngine;
39
43
  readonly queue: DeliveryQueue;
40
44
  readonly trust: TrustManager;
41
45
  constructor(events: EventEmitter, config?: FederationConfig, opts?: {
42
46
  sdkClass?: MapAgentConnectionClass;
47
+ meshConnector?: MeshConnector;
48
+ /** Full MeshPeer for Phase 2 FederationGateway delegation */
49
+ meshPeer?: MeshPeerLike;
43
50
  onIncomingMessage?: IncomingMessageHandler;
44
51
  });
45
52
  /**
@@ -64,7 +71,7 @@ export declare class ConnectionManager {
64
71
  */
65
72
  route(message: Message): Promise<DeliveryResult>;
66
73
  /**
67
- * Send a message to a peer via real connection or event emission.
74
+ * Send a message to a peer via FederationGateway, real connection, or event.
68
75
  * Returns true if send succeeded (or event was emitted).
69
76
  */
70
77
  private sendToPeer;
@@ -100,6 +107,10 @@ export declare class ConnectionManager {
100
107
  /**
101
108
  * Clean up all state. Call on shutdown.
102
109
  */
110
+ /**
111
+ * Check if a FederationGateway exists for a peer.
112
+ */
113
+ hasGateway(peerId: string): boolean;
103
114
  destroy(): Promise<void>;
104
115
  /**
105
116
  * Resolve the system ID using tiered precedence:
@@ -1 +1 @@
1
- {"version":3,"file":"connection-manager.d.ts","sourceRoot":"","sources":["../../src/federation/connection-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAK3C,OAAO,KAAK,EAEV,gBAAgB,EAChB,cAAc,EACd,oBAAoB,EACpB,OAAO,EACP,QAAQ,EACT,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAEV,uBAAuB,EAExB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG1C,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,QAAQ,EAAE;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,KAAK,IAAI,CAAC;AAEX;;;;;;GAMG;AACH,qBAAa,iBAAiB;IAW1B,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,MAAM;IAXhB,OAAO,CAAC,KAAK,CAAqC;IAClD,OAAO,CAAC,WAAW,CAAoC;IACvD,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,UAAU,CAAgC;IAClD,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;gBAGnB,MAAM,EAAE,YAAY,EACpB,MAAM,GAAE,gBAAqB,EACrC,IAAI,CAAC,EAAE;QACL,QAAQ,CAAC,EAAE,uBAAuB,CAAC;QACnC,iBAAiB,CAAC,EAAE,sBAAsB,CAAC;KAC5C;IA6BH;;OAEG;IACH,WAAW,IAAI,QAAQ;IAIvB;;;;OAIG;IACG,QAAQ,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,cAAc,CAAC;IAyDnE;;;OAGG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqB/C;;;;OAIG;IACG,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC;IA6CtD;;;OAGG;YACW,UAAU;IAkCxB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAazB;;OAEG;YACW,kBAAkB;IA0DhC;;OAEG;IACH,QAAQ,IAAI,cAAc,EAAE;IAI5B;;OAEG;IACH,iBAAiB,IAAI,cAAc,EAAE;IAMrC;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAInD;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIpC;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIrC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB9B;;;;;;OAMG;IACH,OAAO,CAAC,eAAe;IAgCvB;;;OAGG;IACH,qBAAqB,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI;CAKnD"}
1
+ {"version":3,"file":"connection-manager.d.ts","sourceRoot":"","sources":["../../src/federation/connection-manager.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import * as os from "node:os";
4
4
  import * as crypto from "node:crypto";
5
+ import { inboxMessageToMap } from "../mesh/type-mapper.js";
5
6
  import { RoutingEngine } from "./routing-engine.js";
6
7
  import { DeliveryQueue } from "./delivery-queue.js";
7
8
  import { TrustManager } from "./trust.js";
@@ -18,8 +19,11 @@ export class ConnectionManager {
18
19
  config;
19
20
  peers = new Map();
20
21
  connections = new Map();
22
+ gateways = new Map();
21
23
  systemId;
22
24
  sdkClass;
25
+ meshConnector;
26
+ meshPeer;
23
27
  onIncoming;
24
28
  routing;
25
29
  queue;
@@ -32,6 +36,8 @@ export class ConnectionManager {
32
36
  this.queue = new DeliveryQueue(events, config.deliveryQueue);
33
37
  this.trust = new TrustManager(config.trust);
34
38
  this.sdkClass = opts?.sdkClass ?? null;
39
+ this.meshConnector = opts?.meshConnector ?? null;
40
+ this.meshPeer = opts?.meshPeer ?? null;
35
41
  this.onIncoming = opts?.onIncomingMessage ?? null;
36
42
  // Wire up flush-on-reconnect
37
43
  this.events.on("federation.connected", (peerId) => {
@@ -64,13 +70,63 @@ export class ConnectionManager {
64
70
  * Returns the federation link, or throws if trust check fails.
65
71
  */
66
72
  async federate(peer) {
73
+ if (!peer.url && !peer.meshPeerId) {
74
+ throw new Error(`Federation peer "${peer.systemId}" must have either url or meshPeerId`);
75
+ }
67
76
  // Trust check
68
77
  if (!this.trust.canConnect(peer.systemId)) {
69
78
  throw new Error(`Federation denied: system "${peer.systemId}" not in allowed servers list`);
70
79
  }
71
- // Open real MAP connection if SDK is available
72
80
  let conn;
73
- if (this.sdkClass) {
81
+ let transport = "websocket";
82
+ if (peer.meshPeerId && this.meshPeer) {
83
+ // Phase 2: Full MeshPeer integration with FederationGateway (v0.2.0+)
84
+ transport = "mesh";
85
+ try {
86
+ // Try to get an existing gateway first (e.g. pre-connected in test setup),
87
+ // otherwise create one via federateWith()
88
+ let gateway = this.meshPeer.getFederationGateway(peer.systemId);
89
+ if (!gateway) {
90
+ gateway = await this.meshPeer.federateWith(peer.systemId, {
91
+ localSystemId: this.meshPeer.peerId,
92
+ remoteSystemId: peer.systemId,
93
+ buffer: { enabled: true, maxMessages: 1000 },
94
+ routing: { maxHops: 5, trackPath: true },
95
+ });
96
+ }
97
+ this.gateways.set(peer.systemId, gateway);
98
+ // Listen for incoming federated messages
99
+ gateway.on("message:received", (envelope) => {
100
+ const env = envelope;
101
+ if (env.payload) {
102
+ this.handlePeerMessage(peer.systemId, {
103
+ from: env.payload.from ?? peer.systemId,
104
+ payload: env.payload.payload,
105
+ meta: env.payload.meta,
106
+ });
107
+ }
108
+ });
109
+ }
110
+ catch (err) {
111
+ throw new Error(`Mesh federation connection failed for "${peer.systemId}": ${err instanceof Error ? err.message : err}`);
112
+ }
113
+ }
114
+ else if (peer.meshPeerId && this.meshConnector) {
115
+ // Phase 1 fallback: Mesh transport via raw channel (no MeshPeer)
116
+ transport = "mesh";
117
+ try {
118
+ conn = await this.meshConnector.connect(peer.meshPeerId);
119
+ conn.onMessage((msg) => {
120
+ this.handlePeerMessage(peer.systemId, msg);
121
+ });
122
+ this.connections.set(peer.systemId, conn);
123
+ }
124
+ catch (err) {
125
+ throw new Error(`Mesh federation connection failed for "${peer.systemId}": ${err instanceof Error ? err.message : err}`);
126
+ }
127
+ }
128
+ else if (peer.url && this.sdkClass) {
129
+ // WebSocket transport — connect via MAP SDK
74
130
  try {
75
131
  conn = await this.sdkClass.connect(peer.url, {
76
132
  name: this.systemId.id,
@@ -89,7 +145,6 @@ export class ConnectionManager {
89
145
  maxDelayMs: 30000,
90
146
  },
91
147
  });
92
- // Register incoming message handler
93
148
  conn.onMessage((msg) => {
94
149
  this.handlePeerMessage(peer.systemId, msg);
95
150
  });
@@ -104,7 +159,8 @@ export class ConnectionManager {
104
159
  sessionId: crypto.randomUUID(),
105
160
  status: "connected",
106
161
  exposure: peer.exposure ?? { agents: "all" },
107
- url: peer.url,
162
+ url: peer.url ?? "",
163
+ transport,
108
164
  connectedAt: new Date().toISOString(),
109
165
  };
110
166
  this.peers.set(peer.systemId, link);
@@ -161,10 +217,13 @@ export class ConnectionManager {
161
217
  }
162
218
  const link = this.peers.get(peerId);
163
219
  if (!link || link.status !== "connected") {
164
- // TODO: When resolved peerId has no peer link (e.g., system-qualified address
165
- // targeting a system we're not directly connected to), fall through to
166
- // handleUnknownRoute so broadcast/hierarchical strategies can attempt delivery
167
- // via connected peers or upstream hubs instead of immediately queuing.
220
+ // No direct peer link try broadcast/hierarchical strategies before queuing.
221
+ // This handles cases like system-qualified addresses (bob@system-2) targeting
222
+ // a system we're not directly connected to but could reach via relay or hub.
223
+ const strategy = this.routing.getStrategy();
224
+ if (strategy !== "table") {
225
+ return this.handleUnknownRoute(addr, message);
226
+ }
168
227
  this.queue.enqueue(peerId, message);
169
228
  return { delivered: false, peerId, queued: true };
170
229
  }
@@ -181,13 +240,46 @@ export class ConnectionManager {
181
240
  return { delivered: false, error: "No remote recipients found" };
182
241
  }
183
242
  /**
184
- * Send a message to a peer via real connection or event emission.
243
+ * Send a message to a peer via FederationGateway, real connection, or event.
185
244
  * Returns true if send succeeded (or event was emitted).
186
245
  */
187
246
  async sendToPeer(peerId, addr, message) {
247
+ // Phase 2: Try FederationGateway first (provides envelope, hop/loop detection)
248
+ const gateway = this.gateways.get(peerId);
249
+ if (gateway) {
250
+ try {
251
+ const mapMsg = inboxMessageToMap(message);
252
+ // Build a MAP-compatible message for the gateway.
253
+ // Use local agent address (not federated) since the gateway
254
+ // already handles cross-system routing. A federated `to` like
255
+ // { system: "system-b", agent: "bob" } would cause the remote
256
+ // MapServer to call routeToFederation() instead of deliverToAgent().
257
+ const targetAgentIds = addr.agent ? [addr.agent] : [];
258
+ const localTo = targetAgentIds.length === 1
259
+ ? { agent: targetAgentIds[0] }
260
+ : targetAgentIds.length > 1
261
+ ? { agents: targetAgentIds }
262
+ : mapMsg.to;
263
+ const gatewayMessage = {
264
+ id: message.id,
265
+ from: message.sender_id,
266
+ to: localTo,
267
+ timestamp: Date.now(),
268
+ payload: mapMsg.payload,
269
+ meta: mapMsg.meta,
270
+ };
271
+ const routed = await gateway.route(gatewayMessage, targetAgentIds);
272
+ if (routed)
273
+ return true;
274
+ // Fall through to direct connection if gateway route fails
275
+ }
276
+ catch {
277
+ // Fall through to direct connection
278
+ }
279
+ }
188
280
  const conn = this.connections.get(peerId);
189
281
  if (conn) {
190
- // Real transport — send via MAP SDK connection
282
+ // Real transport — send via MAP SDK or mesh channel connection
191
283
  try {
192
284
  await conn.send({ agentId: addr.agent, scope: addr.scope }, message.content, {
193
285
  messageId: message.id,
@@ -307,6 +399,12 @@ export class ConnectionManager {
307
399
  /**
308
400
  * Clean up all state. Call on shutdown.
309
401
  */
402
+ /**
403
+ * Check if a FederationGateway exists for a peer.
404
+ */
405
+ hasGateway(peerId) {
406
+ return this.gateways.has(peerId);
407
+ }
310
408
  async destroy() {
311
409
  // Close all real connections
312
410
  for (const [peerId, conn] of this.connections) {
@@ -318,6 +416,7 @@ export class ConnectionManager {
318
416
  }
319
417
  this.connections.delete(peerId);
320
418
  }
419
+ this.gateways.clear();
321
420
  this.routing.destroy();
322
421
  this.queue.destroy();
323
422
  this.peers.clear();