multi-agent-protocol 0.0.3 → 0.0.6
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 +4 -0
- package/docs/00-design-specification.md +36 -0
- package/docs/01-open-questions.md +0 -5
- package/docs/02-wire-protocol.md +1 -1
- package/docs/07-federation.md +81 -5
- package/docs/09-authentication.md +748 -0
- package/docs/10-environment-awareness.md +242 -0
- package/docs/10-mail-protocol.md +553 -0
- package/docs/11-anp-inspired-improvements.md +1079 -0
- package/docs/12-anp-implementation-plan.md +641 -0
- package/docs/agent-iam-integration.md +877 -0
- package/docs/agentic-mesh-integration-draft.md +459 -0
- package/docs/git-transport-draft.md +251 -0
- package/package.json +5 -4
- package/schema/meta.json +200 -2
- package/schema/schema.json +1252 -13
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
# Implementation Guide: Agentic-Mesh Transport for MAP SDK
|
|
2
|
+
|
|
3
|
+
This guide describes how to implement agentic-mesh as a transport option for the Multi-Agent Protocol (MAP) TypeScript SDK.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
**Goal:** Allow MAP SDK clients to connect over agentic-mesh encrypted tunnels (Nebula/Tailscale/Headscale).
|
|
8
|
+
|
|
9
|
+
**Architecture:**
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
13
|
+
│ multi-agent-protocol/ts-sdk │
|
|
14
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
15
|
+
│ │
|
|
16
|
+
│ ┌─────────────┐ │
|
|
17
|
+
│ │ MAPClient │ Standard MAP SDK client │
|
|
18
|
+
│ └──────┬──────┘ │
|
|
19
|
+
│ │ │
|
|
20
|
+
│ ▼ │
|
|
21
|
+
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
22
|
+
│ │ Transport Layer (pluggable) │ │
|
|
23
|
+
│ ├─────────────────────────────────────────────────────────┤ │
|
|
24
|
+
│ │ WebSocketTransport │ StdioTransport │ AgenticMeshTransport │
|
|
25
|
+
│ │ │ │ (NEW) │
|
|
26
|
+
│ └─────────────────────────────────────────────────────────┘ │
|
|
27
|
+
│ │ │
|
|
28
|
+
└──────────────────────────────────────────────┼──────────────────┘
|
|
29
|
+
│
|
|
30
|
+
▼
|
|
31
|
+
┌────────────────────────────┐
|
|
32
|
+
│ agentic-mesh TransportAdapter │
|
|
33
|
+
│ (Nebula/Tailscale tunnels) │
|
|
34
|
+
└────────────────────────────┘
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Step 1: Create Transport Interface
|
|
40
|
+
|
|
41
|
+
**File:** `multi-agent-protocol/ts-sdk/src/transports/types.ts`
|
|
42
|
+
|
|
43
|
+
Define the base transport interface that all transports implement:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import type { MAPFrame } from '../types'
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Transport interface for MAP protocol communication.
|
|
50
|
+
*/
|
|
51
|
+
export interface Transport {
|
|
52
|
+
/** Connect to the remote endpoint */
|
|
53
|
+
connect(): Promise<void>
|
|
54
|
+
|
|
55
|
+
/** Disconnect from the remote endpoint */
|
|
56
|
+
disconnect(): Promise<void>
|
|
57
|
+
|
|
58
|
+
/** Send a frame to the remote endpoint */
|
|
59
|
+
send(frame: MAPFrame): Promise<void>
|
|
60
|
+
|
|
61
|
+
/** Async iterator for receiving frames */
|
|
62
|
+
receive(): AsyncIterable<MAPFrame>
|
|
63
|
+
|
|
64
|
+
/** Whether currently connected */
|
|
65
|
+
readonly isConnected: boolean
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Base transport configuration.
|
|
70
|
+
*/
|
|
71
|
+
export interface TransportConfig {
|
|
72
|
+
/** Connection timeout in milliseconds */
|
|
73
|
+
timeout?: number
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Step 2: Create Agentic-Mesh Transport
|
|
80
|
+
|
|
81
|
+
**File:** `multi-agent-protocol/ts-sdk/src/transports/agentic-mesh.ts`
|
|
82
|
+
|
|
83
|
+
### Reusable Components from agentic-mesh
|
|
84
|
+
|
|
85
|
+
| Component | Location | Purpose |
|
|
86
|
+
|-----------|----------|---------|
|
|
87
|
+
| `TunnelStream` | [`src/map/stream/tunnel-stream.ts:90`](../src/map/stream/tunnel-stream.ts#L90) | NDJSON framing over transport |
|
|
88
|
+
| `createNdjsonFramer` | [`src/map/stream/tunnel-stream.ts:16`](../src/map/stream/tunnel-stream.ts#L16) | JSON encode/decode utilities |
|
|
89
|
+
| `TransportAdapter` | [`src/transports/types.ts`](../src/transports/types.ts) | Nebula/Tailscale abstraction |
|
|
90
|
+
| `MapFrame` | [`src/map/types.ts:685`](../src/map/types.ts#L685) | Request/Response/Notification types |
|
|
91
|
+
|
|
92
|
+
### Implementation
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import type { Transport, TransportConfig } from './types'
|
|
96
|
+
import type { MAPFrame } from '../types'
|
|
97
|
+
|
|
98
|
+
// Import from agentic-mesh
|
|
99
|
+
import { TunnelStream } from 'agentic-mesh/map/stream'
|
|
100
|
+
import type { TransportAdapter, PeerEndpoint } from 'agentic-mesh/transports'
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Configuration for agentic-mesh transport.
|
|
104
|
+
*/
|
|
105
|
+
export interface AgenticMeshTransportConfig extends TransportConfig {
|
|
106
|
+
/** The agentic-mesh transport adapter (Nebula, Tailscale, Headscale) */
|
|
107
|
+
transport: TransportAdapter
|
|
108
|
+
|
|
109
|
+
/** Remote peer to connect to */
|
|
110
|
+
peer: PeerEndpoint
|
|
111
|
+
|
|
112
|
+
/** Local peer ID for identification */
|
|
113
|
+
localPeerId: string
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* MAP transport over agentic-mesh encrypted tunnels.
|
|
118
|
+
*/
|
|
119
|
+
export class AgenticMeshTransport implements Transport {
|
|
120
|
+
private stream: TunnelStream | null = null
|
|
121
|
+
private readonly config: AgenticMeshTransportConfig
|
|
122
|
+
|
|
123
|
+
constructor(config: AgenticMeshTransportConfig) {
|
|
124
|
+
this.config = config
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
get isConnected(): boolean {
|
|
128
|
+
return this.stream?.isOpen ?? false
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async connect(): Promise<void> {
|
|
132
|
+
// Start underlying transport if needed
|
|
133
|
+
if (!this.config.transport.isRunning) {
|
|
134
|
+
await this.config.transport.start()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Create tunnel stream over the mesh transport
|
|
138
|
+
this.stream = new TunnelStream({
|
|
139
|
+
transport: this.config.transport,
|
|
140
|
+
peerId: this.config.peer.id,
|
|
141
|
+
streamId: `map-${this.config.localPeerId}-${Date.now()}`,
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
await this.stream.open()
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async disconnect(): Promise<void> {
|
|
148
|
+
if (this.stream) {
|
|
149
|
+
await this.stream.close()
|
|
150
|
+
this.stream = null
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async send(frame: MAPFrame): Promise<void> {
|
|
155
|
+
if (!this.stream?.isOpen) {
|
|
156
|
+
throw new Error('Transport not connected')
|
|
157
|
+
}
|
|
158
|
+
await this.stream.write(frame)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async *receive(): AsyncIterable<MAPFrame> {
|
|
162
|
+
if (!this.stream) return
|
|
163
|
+
|
|
164
|
+
for await (const frame of this.stream) {
|
|
165
|
+
yield frame
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Create an agentic-mesh transport.
|
|
172
|
+
*/
|
|
173
|
+
export function createAgenticMeshTransport(
|
|
174
|
+
config: AgenticMeshTransportConfig
|
|
175
|
+
): AgenticMeshTransport {
|
|
176
|
+
return new AgenticMeshTransport(config)
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Step 3: Export Transport Module
|
|
183
|
+
|
|
184
|
+
**File:** `multi-agent-protocol/ts-sdk/src/transports/index.ts`
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
export * from './types'
|
|
188
|
+
export * from './agentic-mesh'
|
|
189
|
+
|
|
190
|
+
// Re-export other transports
|
|
191
|
+
export * from './websocket'
|
|
192
|
+
export * from './stdio'
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Step 4: Wire into MAP Client
|
|
198
|
+
|
|
199
|
+
**File:** `multi-agent-protocol/ts-sdk/src/client.ts`
|
|
200
|
+
|
|
201
|
+
Modify the MAP client to accept a transport:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
import type { Transport } from './transports'
|
|
205
|
+
import type {
|
|
206
|
+
ConnectParams,
|
|
207
|
+
ConnectResult,
|
|
208
|
+
MAPFrame,
|
|
209
|
+
MAPRequestFrame,
|
|
210
|
+
MAPResponseFrame,
|
|
211
|
+
} from './types'
|
|
212
|
+
|
|
213
|
+
export class MAPClient {
|
|
214
|
+
private readonly transport: Transport
|
|
215
|
+
private readonly pendingRequests = new Map<
|
|
216
|
+
string | number,
|
|
217
|
+
{ resolve: (result: unknown) => void; reject: (error: Error) => void }
|
|
218
|
+
>()
|
|
219
|
+
private nextRequestId = 1
|
|
220
|
+
|
|
221
|
+
constructor(transport: Transport) {
|
|
222
|
+
this.transport = transport
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async connect(params: ConnectParams): Promise<ConnectResult> {
|
|
226
|
+
// Connect transport
|
|
227
|
+
await this.transport.connect()
|
|
228
|
+
|
|
229
|
+
// Start receiving frames in background
|
|
230
|
+
this.startReceiving()
|
|
231
|
+
|
|
232
|
+
// Send MAP connect request
|
|
233
|
+
return this.request('map/connect', params)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async disconnect(): Promise<void> {
|
|
237
|
+
await this.request('map/disconnect', {})
|
|
238
|
+
await this.transport.disconnect()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Send a JSON-RPC request and wait for response.
|
|
243
|
+
*/
|
|
244
|
+
async request<T>(method: string, params: unknown): Promise<T> {
|
|
245
|
+
const id = this.nextRequestId++
|
|
246
|
+
|
|
247
|
+
const frame: MAPRequestFrame = {
|
|
248
|
+
jsonrpc: '2.0',
|
|
249
|
+
id,
|
|
250
|
+
method,
|
|
251
|
+
params,
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return new Promise((resolve, reject) => {
|
|
255
|
+
this.pendingRequests.set(id, { resolve: resolve as any, reject })
|
|
256
|
+
this.transport.send(frame).catch(reject)
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Start receiving frames from transport.
|
|
262
|
+
*/
|
|
263
|
+
private async startReceiving(): Promise<void> {
|
|
264
|
+
try {
|
|
265
|
+
for await (const frame of this.transport.receive()) {
|
|
266
|
+
this.handleFrame(frame)
|
|
267
|
+
}
|
|
268
|
+
} catch (error) {
|
|
269
|
+
// Handle disconnect
|
|
270
|
+
console.error('Transport error:', error)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Handle incoming frame.
|
|
276
|
+
*/
|
|
277
|
+
private handleFrame(frame: MAPFrame): void {
|
|
278
|
+
// Response frame
|
|
279
|
+
if ('id' in frame && ('result' in frame || 'error' in frame)) {
|
|
280
|
+
const pending = this.pendingRequests.get(frame.id)
|
|
281
|
+
if (pending) {
|
|
282
|
+
this.pendingRequests.delete(frame.id)
|
|
283
|
+
if ('error' in frame) {
|
|
284
|
+
pending.reject(new Error(frame.error.message))
|
|
285
|
+
} else {
|
|
286
|
+
pending.resolve(frame.result)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Notification frame
|
|
293
|
+
if ('method' in frame && !('id' in frame)) {
|
|
294
|
+
this.handleNotification(frame)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private handleNotification(frame: MAPNotificationFrame): void {
|
|
299
|
+
// Handle events, messages, etc.
|
|
300
|
+
// Emit to subscribers
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Step 5: Usage Example
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
import { MAPClient } from '@anthropic/multi-agent-protocol'
|
|
311
|
+
import { AgenticMeshTransport } from '@anthropic/multi-agent-protocol/transports/agentic-mesh'
|
|
312
|
+
import { createNebulaTransport } from 'agentic-mesh'
|
|
313
|
+
|
|
314
|
+
async function main() {
|
|
315
|
+
// 1. Create the encrypted mesh transport
|
|
316
|
+
const nebulaTransport = createNebulaTransport({
|
|
317
|
+
configPath: '/etc/nebula/config.yml',
|
|
318
|
+
certPath: '/etc/nebula/host.crt',
|
|
319
|
+
keyPath: '/etc/nebula/host.key',
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
// 2. Create MAP transport wrapper
|
|
323
|
+
const transport = new AgenticMeshTransport({
|
|
324
|
+
transport: nebulaTransport,
|
|
325
|
+
peer: {
|
|
326
|
+
id: 'map-server',
|
|
327
|
+
nebulaIp: '10.0.0.1',
|
|
328
|
+
port: 4242
|
|
329
|
+
},
|
|
330
|
+
localPeerId: 'my-client',
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
// 3. Create MAP client with transport
|
|
334
|
+
const client = new MAPClient(transport)
|
|
335
|
+
|
|
336
|
+
// 4. Connect using standard MAP protocol
|
|
337
|
+
const session = await client.connect({
|
|
338
|
+
participantType: 'client',
|
|
339
|
+
name: 'My Dashboard',
|
|
340
|
+
capabilities: {
|
|
341
|
+
observation: { canObserve: true, canQuery: true },
|
|
342
|
+
},
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
console.log('Connected:', session.sessionId)
|
|
346
|
+
|
|
347
|
+
// 5. Use MAP SDK normally
|
|
348
|
+
const agents = await client.request('map/agents.list', {})
|
|
349
|
+
console.log('Agents:', agents)
|
|
350
|
+
|
|
351
|
+
// 6. Subscribe to events
|
|
352
|
+
const subscription = await client.request('map/subscribe', {
|
|
353
|
+
filter: { eventTypes: ['agent_registered', 'agent_state_changed'] },
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
// 7. Disconnect when done
|
|
357
|
+
await client.disconnect()
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
main().catch(console.error)
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Key Code Pointers
|
|
366
|
+
|
|
367
|
+
### agentic-mesh Source Files
|
|
368
|
+
|
|
369
|
+
| What | File | Line |
|
|
370
|
+
|------|------|------|
|
|
371
|
+
| NDJSON framing | [`src/map/stream/tunnel-stream.ts`](../src/map/stream/tunnel-stream.ts) | 16-67 |
|
|
372
|
+
| TunnelStream class | [`src/map/stream/tunnel-stream.ts`](../src/map/stream/tunnel-stream.ts) | 90-200 |
|
|
373
|
+
| MapFrame types | [`src/map/types.ts`](../src/map/types.ts) | 682-702 |
|
|
374
|
+
| JSON-RPC handling | [`src/map/connection/base.ts`](../src/map/connection/base.ts) | 1-150 |
|
|
375
|
+
| TransportAdapter interface | [`src/transports/types.ts`](../src/transports/types.ts) | 1-50 |
|
|
376
|
+
| Nebula transport | [`src/transports/nebula-transport.ts`](../src/transports/nebula-transport.ts) | - |
|
|
377
|
+
| Tailscale transport | [`src/transports/tailscale-transport.ts`](../src/transports/tailscale-transport.ts) | - |
|
|
378
|
+
| PeerConnection (reference) | [`src/map/connection/peer.ts`](../src/map/connection/peer.ts) | - |
|
|
379
|
+
|
|
380
|
+
### Existing Exports
|
|
381
|
+
|
|
382
|
+
agentic-mesh already exports what's needed:
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
// src/map/index.ts
|
|
386
|
+
export * from './stream' // TunnelStream, createNdjsonFramer
|
|
387
|
+
export * from './types' // MapFrame, all MAP types
|
|
388
|
+
export * from './connection' // BaseConnection
|
|
389
|
+
|
|
390
|
+
// src/index.ts
|
|
391
|
+
export * from './map' // All MAP exports
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## Files to Create
|
|
397
|
+
|
|
398
|
+
| File | Purpose |
|
|
399
|
+
|------|---------|
|
|
400
|
+
| `ts-sdk/src/transports/types.ts` | Transport interface definition |
|
|
401
|
+
| `ts-sdk/src/transports/agentic-mesh.ts` | Agentic-mesh transport implementation |
|
|
402
|
+
| `ts-sdk/src/transports/index.ts` | Export all transports |
|
|
403
|
+
|
|
404
|
+
## Files to Modify
|
|
405
|
+
|
|
406
|
+
| File | Change |
|
|
407
|
+
|------|--------|
|
|
408
|
+
| `ts-sdk/src/client.ts` | Accept transport in constructor |
|
|
409
|
+
| `ts-sdk/package.json` | Add `agentic-mesh` as optional peer dependency |
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## Testing
|
|
414
|
+
|
|
415
|
+
Create tests for the transport:
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
// ts-sdk/tests/transports/agentic-mesh.test.ts
|
|
419
|
+
|
|
420
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
421
|
+
import { AgenticMeshTransport } from '../../src/transports/agentic-mesh'
|
|
422
|
+
|
|
423
|
+
describe('AgenticMeshTransport', () => {
|
|
424
|
+
it('should connect via tunnel stream', async () => {
|
|
425
|
+
const mockTransport = {
|
|
426
|
+
isRunning: false,
|
|
427
|
+
start: vi.fn(),
|
|
428
|
+
connect: vi.fn(),
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const transport = new AgenticMeshTransport({
|
|
432
|
+
transport: mockTransport as any,
|
|
433
|
+
peer: { id: 'test-peer', nebulaIp: '10.0.0.1', port: 4242 },
|
|
434
|
+
localPeerId: 'local',
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
await transport.connect()
|
|
438
|
+
expect(mockTransport.start).toHaveBeenCalled()
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
it('should send frames via stream', async () => {
|
|
442
|
+
// Test frame sending
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
it('should receive frames via async iterator', async () => {
|
|
446
|
+
// Test frame receiving
|
|
447
|
+
})
|
|
448
|
+
})
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## Summary
|
|
454
|
+
|
|
455
|
+
1. **Define transport interface** - Standard interface for all MAP transports
|
|
456
|
+
2. **Implement AgenticMeshTransport** - Wraps TunnelStream for MAP frames
|
|
457
|
+
3. **Export from ts-sdk** - Make transport available to SDK users
|
|
458
|
+
4. **Wire into MAPClient** - Accept transport in constructor
|
|
459
|
+
5. **Use normally** - Standard MAP SDK API over encrypted mesh tunnels
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# Git Transport Integration Guide
|
|
2
|
+
|
|
3
|
+
This guide explains how git transport is integrated with agentic-mesh and how to use it from the MAP side.
|
|
4
|
+
|
|
5
|
+
## Architecture Overview
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌──────────────────────────────────────────────────────────────────┐
|
|
9
|
+
│ MAP Client/Agent │
|
|
10
|
+
│ │
|
|
11
|
+
│ Option A: CLI Option B: Programmatic │
|
|
12
|
+
│ ───────────────── ─────────────────────── │
|
|
13
|
+
│ git fetch mesh://peer-b/ client.sync('peer-b') │
|
|
14
|
+
│ │ │ │
|
|
15
|
+
│ ▼ ▼ │
|
|
16
|
+
│ git-remote-mesh helper GitSyncClient │
|
|
17
|
+
│ │ │ │
|
|
18
|
+
│ └────────────┬───────────────────┘ │
|
|
19
|
+
│ ▼ │
|
|
20
|
+
│ GitTransportService (HTTP :3456) │
|
|
21
|
+
│ │ │
|
|
22
|
+
│ ▼ │
|
|
23
|
+
│ MeshPeer.sendGitMessage() │
|
|
24
|
+
│ │ │
|
|
25
|
+
│ ▼ │
|
|
26
|
+
│ PeerConnection.sendGitMessage() │
|
|
27
|
+
└──────────────────────┬───────────────────────────────────────────┘
|
|
28
|
+
│ MAP Protocol (NDJSON over transport)
|
|
29
|
+
▼
|
|
30
|
+
┌──────────────────────────────────────────────────────────────────┐
|
|
31
|
+
│ Remote Peer │
|
|
32
|
+
│ │
|
|
33
|
+
│ PeerConnection receives 'git/message' │
|
|
34
|
+
│ │ │
|
|
35
|
+
│ ▼ │
|
|
36
|
+
│ MeshPeer.git:message event │
|
|
37
|
+
│ │ │
|
|
38
|
+
│ ▼ │
|
|
39
|
+
│ GitTransportService.handleRemoteMessage() │
|
|
40
|
+
│ │ │
|
|
41
|
+
│ ▼ │
|
|
42
|
+
│ GitProtocolHandler (spawns native git) │
|
|
43
|
+
└──────────────────────────────────────────────────────────────────┘
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Key Integration Points
|
|
47
|
+
|
|
48
|
+
### 1. Enable Git in MeshPeer Config
|
|
49
|
+
|
|
50
|
+
**File:** `src/map/types.ts` (lines 45-52)
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
const peer = createMeshPeer({
|
|
54
|
+
peerId: 'my-agent',
|
|
55
|
+
git: {
|
|
56
|
+
enabled: true,
|
|
57
|
+
httpPort: 3456, // Port for git-remote-mesh helper
|
|
58
|
+
repoPath: '/path/to/repo', // Default repo path
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 2. Git Service Lifecycle
|
|
64
|
+
|
|
65
|
+
**File:** `src/map/mesh-peer.ts`
|
|
66
|
+
|
|
67
|
+
The git service is initialized in the constructor (lines 74-84) and started/stopped with the peer:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// Start (line 170-173)
|
|
71
|
+
if (this.gitService) {
|
|
72
|
+
this.gitService.setPeerSender(this.createGitPeerSender())
|
|
73
|
+
await this.gitService.start()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Stop (line 195-197)
|
|
77
|
+
if (this.gitService) {
|
|
78
|
+
await this.gitService.stop()
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 3. Message Routing
|
|
83
|
+
|
|
84
|
+
**File:** `src/map/mesh-peer.ts` (lines 225-241)
|
|
85
|
+
|
|
86
|
+
Git messages are sent via `createGitPeerSender()`:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
private createGitPeerSender(): PeerMessageSender {
|
|
90
|
+
return {
|
|
91
|
+
sendToPeer: async (peerId: string, message: AnyGitMessage) => {
|
|
92
|
+
const conn = this.peerConnections.get(peerId)
|
|
93
|
+
if (!conn) throw new Error(`No connection to peer ${peerId}`)
|
|
94
|
+
await conn.sendGitMessage(message)
|
|
95
|
+
},
|
|
96
|
+
isConnected: (peerId: string) => this.peerConnections.has(peerId),
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 4. Receiving Git Messages
|
|
102
|
+
|
|
103
|
+
**File:** `src/map/mesh-peer.ts` (lines 354-359)
|
|
104
|
+
|
|
105
|
+
When a peer connection receives a git message, it's forwarded to the git service:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
conn.on('git:message', (gitMessage) => {
|
|
109
|
+
if (this.gitService) {
|
|
110
|
+
this.gitService.handleRemoteMessage(peerId, gitMessage)
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 5. PeerConnection Git Support
|
|
116
|
+
|
|
117
|
+
**File:** `src/map/connection/peer.ts`
|
|
118
|
+
|
|
119
|
+
Git messages use a dedicated method type:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
const GIT_MESSAGE_METHOD = 'git/message' as const // line 32
|
|
123
|
+
|
|
124
|
+
// Send (lines 224-232)
|
|
125
|
+
async sendGitMessage(message: AnyGitMessage): Promise<void> {
|
|
126
|
+
await this.stream.notify(GIT_MESSAGE_METHOD, message)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Receive (line 296)
|
|
130
|
+
this.emit('git:message', message)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Usage from MAP Side
|
|
134
|
+
|
|
135
|
+
### Option A: Using GitSyncClient (Recommended)
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import { createMeshPeer } from 'agentic-mesh/map'
|
|
139
|
+
|
|
140
|
+
const peer = createMeshPeer({
|
|
141
|
+
peerId: 'agent-a',
|
|
142
|
+
git: { enabled: true, repoPath: '/my/repo' },
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
await peer.start()
|
|
146
|
+
await peer.connectToPeer('agent-b', endpoint)
|
|
147
|
+
|
|
148
|
+
// Create sync client
|
|
149
|
+
const client = peer.git!.createSyncClient('/my/repo')
|
|
150
|
+
|
|
151
|
+
// Sync operations
|
|
152
|
+
await client.sync('agent-b', { branch: 'main', bidirectional: true })
|
|
153
|
+
await client.pull('agent-b', 'main')
|
|
154
|
+
await client.push('agent-b', 'feature-branch')
|
|
155
|
+
await client.clone('agent-b', '/new/repo')
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Option B: Using Standard Git Commands
|
|
159
|
+
|
|
160
|
+
Requires `git-remote-mesh` in PATH:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# Install globally
|
|
164
|
+
npm install -g agentic-mesh
|
|
165
|
+
|
|
166
|
+
# Or add to PATH
|
|
167
|
+
export PATH="$PATH:./node_modules/.bin"
|
|
168
|
+
|
|
169
|
+
# Use mesh:// URLs
|
|
170
|
+
git remote add agent-b mesh://agent-b-id/
|
|
171
|
+
git fetch agent-b
|
|
172
|
+
git push agent-b main
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Option C: Direct Protocol Access
|
|
176
|
+
|
|
177
|
+
For low-level control:
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// List refs from remote peer
|
|
181
|
+
const refs = await peer.git!.protocolHandler.listRefs({
|
|
182
|
+
refPrefix: 'refs/heads/',
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// Fetch pack data
|
|
186
|
+
const pack = await peer.git!.protocolHandler.uploadPack({
|
|
187
|
+
wants: ['abc123...'],
|
|
188
|
+
haves: ['def456...'],
|
|
189
|
+
})
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Wire Protocol Messages
|
|
193
|
+
|
|
194
|
+
**File:** `src/git/types.ts`
|
|
195
|
+
|
|
196
|
+
| Message Type | Direction | Purpose |
|
|
197
|
+
|--------------|-----------|---------|
|
|
198
|
+
| `git/list-refs` | Request/Response | List remote refs |
|
|
199
|
+
| `git/upload-pack` | Request/Response | Fetch pack data |
|
|
200
|
+
| `git/receive-pack` | Request/Response | Push pack data |
|
|
201
|
+
| `git/pack-stream` | Notification | Start binary stream |
|
|
202
|
+
| `git/pack-chunk` | Notification | Stream chunk |
|
|
203
|
+
| `git/pack-complete` | Notification | End stream |
|
|
204
|
+
| `git/error` | Response | Error response |
|
|
205
|
+
|
|
206
|
+
## Binary Streaming
|
|
207
|
+
|
|
208
|
+
**File:** `src/git/transport-service.ts` (lines 451-465, 501-545)
|
|
209
|
+
|
|
210
|
+
Large packs (>1MB by default) are automatically streamed:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
// Config (lines 67-71)
|
|
214
|
+
streaming: {
|
|
215
|
+
enabled: true,
|
|
216
|
+
threshold: 1024 * 1024, // 1MB
|
|
217
|
+
chunkSize: 64 * 1024, // 64KB chunks
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Flow:
|
|
222
|
+
1. Response sent with `streaming: true`, no `packData`
|
|
223
|
+
2. `git/pack-stream` message initiates transfer
|
|
224
|
+
3. `git/pack-chunk` messages send data (base64 encoded)
|
|
225
|
+
4. `git/pack-complete` finalizes with checksum
|
|
226
|
+
|
|
227
|
+
## File Reference
|
|
228
|
+
|
|
229
|
+
| File | Purpose |
|
|
230
|
+
|------|---------|
|
|
231
|
+
| `src/git/types.ts` | Type definitions, message types |
|
|
232
|
+
| `src/git/protocol-handler.ts` | Native git operations |
|
|
233
|
+
| `src/git/transport-service.ts` | HTTP server + peer routing |
|
|
234
|
+
| `src/git/sync-client.ts` | High-level sync API |
|
|
235
|
+
| `src/git/pack-streamer.ts` | Binary streaming |
|
|
236
|
+
| `src/git/git-remote-mesh.ts` | CLI remote helper |
|
|
237
|
+
| `src/map/mesh-peer.ts` | MeshPeer integration |
|
|
238
|
+
| `src/map/connection/peer.ts` | Git message handling |
|
|
239
|
+
|
|
240
|
+
## Testing
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# Unit tests
|
|
244
|
+
npm test -- git-transport
|
|
245
|
+
|
|
246
|
+
# Integration tests (requires git)
|
|
247
|
+
npm test -- tests/integration/git-transport
|
|
248
|
+
|
|
249
|
+
# All tests
|
|
250
|
+
npm test
|
|
251
|
+
```
|