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.
- package/CLAUDE.md +44 -7
- package/README.md +67 -24
- package/dist/cli.d.ts +20 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +89 -0
- package/dist/cli.js.map +1 -0
- package/dist/federation/connection-manager.d.ts +13 -2
- package/dist/federation/connection-manager.d.ts.map +1 -1
- package/dist/federation/connection-manager.js +109 -10
- package/dist/federation/connection-manager.js.map +1 -1
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +58 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -0
- package/dist/ipc/ipc-server.d.ts +2 -0
- package/dist/ipc/ipc-server.d.ts.map +1 -1
- package/dist/ipc/ipc-server.js +48 -0
- package/dist/ipc/ipc-server.js.map +1 -1
- package/dist/map/map-client.d.ts +100 -0
- package/dist/map/map-client.d.ts.map +1 -1
- package/dist/map/map-client.js +61 -0
- package/dist/map/map-client.js.map +1 -1
- package/dist/mcp/mcp-proxy.d.ts +28 -0
- package/dist/mcp/mcp-proxy.d.ts.map +1 -0
- package/dist/mcp/mcp-proxy.js +280 -0
- package/dist/mcp/mcp-proxy.js.map +1 -0
- package/dist/mesh/delivery-bridge.d.ts +47 -0
- package/dist/mesh/delivery-bridge.d.ts.map +1 -0
- package/dist/mesh/delivery-bridge.js +73 -0
- package/dist/mesh/delivery-bridge.js.map +1 -0
- package/dist/mesh/mesh-connector.d.ts +29 -0
- package/dist/mesh/mesh-connector.d.ts.map +1 -0
- package/dist/mesh/mesh-connector.js +36 -0
- package/dist/mesh/mesh-connector.js.map +1 -0
- package/dist/mesh/mesh-transport.d.ts +70 -0
- package/dist/mesh/mesh-transport.d.ts.map +1 -0
- package/dist/mesh/mesh-transport.js +92 -0
- package/dist/mesh/mesh-transport.js.map +1 -0
- package/dist/mesh/type-mapper.d.ts +67 -0
- package/dist/mesh/type-mapper.d.ts.map +1 -0
- package/dist/mesh/type-mapper.js +165 -0
- package/dist/mesh/type-mapper.js.map +1 -0
- package/dist/types.d.ts +29 -2
- package/dist/types.d.ts.map +1 -1
- package/docs/CLAUDE-CODE-SWARM-PROPOSAL.md +137 -0
- package/package.json +10 -2
- package/src/cli.ts +94 -0
- package/src/federation/connection-manager.ts +125 -10
- package/src/index.ts +96 -5
- package/src/ipc/ipc-server.ts +58 -0
- package/src/map/map-client.ts +152 -0
- package/src/mcp/mcp-proxy.ts +326 -0
- package/src/mesh/delivery-bridge.ts +110 -0
- package/src/mesh/mesh-connector.ts +41 -0
- package/src/mesh/mesh-transport.ts +157 -0
- package/src/mesh/type-mapper.ts +239 -0
- package/src/types.ts +33 -1
- package/test/federation/integration.test.ts +37 -3
- package/test/federation/sdk-integration.test.ts +4 -8
- package/test/ipc-new-commands.test.ts +200 -0
- package/test/mcp-proxy.test.ts +191 -0
- package/test/mesh/delivery-bridge.test.ts +178 -0
- package/test/mesh/e2e-mesh.test.ts +527 -0
- package/test/mesh/e2e-real-meshpeer.test.ts +629 -0
- package/test/mesh/federation-mesh.test.ts +269 -0
- package/test/mesh/mesh-connector.test.ts +66 -0
- package/test/mesh/mesh-transport.test.ts +191 -0
- package/test/mesh/meshpeer-integration.test.ts +442 -0
- package/test/mesh/mock-mesh.ts +125 -0
- package/test/mesh/mock-meshpeer.ts +266 -0
- package/test/mesh/type-mapper.test.ts +226 -0
- 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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
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
|
-
| `
|
|
48
|
-
| `
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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/
|
|
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
|
package/dist/cli.js.map
ADDED
|
@@ -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
|
|
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,
|
|
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
|
-
|
|
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
|
-
//
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
|
|
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
|
|
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();
|