@vdoninja/ninja-p2p 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,17 +1,35 @@
1
1
  # ninja-p2p
2
2
 
3
- Reusable bot-to-bot P2P communication layer built on [VDO.Ninja](https://vdo.ninja) WebRTC data channels. It lets agents discover each other inside a room, announce skills and status, send room-wide or private messages, and keep lightweight message history without a central relay.
3
+ `ninja-p2p` is a small TypeScript library for peer-to-peer agent messaging on top of the [VDO.Ninja](https://vdo.ninja) SDK.
4
4
 
5
- ## What This Repo Is
5
+ It gives you a shared room, peer discovery, direct messages, group chat, topics, in-memory history, and a simple browser dashboard. The transport is WebRTC data channels. You do not need to open inbound ports just to let agents talk to each other.
6
6
 
7
- - A TypeScript/Node library for room-based peer discovery and messaging
8
- - A standalone `dashboard.html` monitor/chat client
9
- - A Codex skill at `skills/ninja-p2p`
10
- - An npm package published as `@vdoninja/ninja-p2p`
7
+ Package: [`@vdoninja/ninja-p2p`](https://www.npmjs.com/package/@vdoninja/ninja-p2p)
8
+ Support: https://discord.vdo.ninja
11
9
 
12
- ## Install The Library
10
+ ## What It Does
13
11
 
14
- Install it from npm:
12
+ - peers join the same room and discover each other
13
+ - direct messages between named peers
14
+ - room-wide chat
15
+ - topic-based pub/sub
16
+ - peer status, skills, and presence tracking
17
+ - in-memory history replay
18
+ - in-memory offline queue for peers that drop and reconnect
19
+ - a standalone `dashboard.html` room monitor/chat client
20
+
21
+ ## What It Does Not Do
22
+
23
+ - it is not a VPN
24
+ - it is not a generic TCP tunnel
25
+ - it is not a generic HTTP tunnel
26
+ - it does not provide durable storage
27
+ - it does not guarantee message delivery
28
+ - it does not turn the dashboard into a remote shell
29
+
30
+ This package is for agent coordination. If you want to expose a whole private network or front a public website, use a VPN or a tunnel made for that job.
31
+
32
+ ## Install
15
33
 
16
34
  ```bash
17
35
  npm install @vdoninja/ninja-p2p @roamhq/wrtc
@@ -19,10 +37,9 @@ npm install @vdoninja/ninja-p2p @roamhq/wrtc
19
37
 
20
38
  Notes:
21
39
 
22
- - `@vdoninja/sdk` is pulled in automatically as a dependency.
23
- - `ws` comes from `@vdoninja/sdk` in Node environments.
24
- - `@roamhq/wrtc` is recommended for Node-based bots that need WebRTC support.
25
- - This package is configured for public scoped publishing via `publishConfig.access = "public"`.
40
+ - `@vdoninja/sdk` is installed automatically.
41
+ - `ws` comes from `@vdoninja/sdk` in Node.
42
+ - `@roamhq/wrtc` is recommended for Node bots that need WebRTC support.
26
43
 
27
44
  ## Add The Codex Skill
28
45
 
@@ -60,49 +77,137 @@ const bridge = new VDOBridge({
60
77
 
61
78
  await bridge.connect();
62
79
 
63
- // Send to everyone in the room
64
80
  bridge.chat("Planner online");
65
-
66
- // Send a private message to a specific peer
67
81
  bridge.chat("sync now", "worker_bot");
68
-
69
- // Publish a topic event
70
82
  bridge.publishEvent("events", "status_change", { status: "busy" });
71
83
 
72
- // Listen for incoming chat
73
84
  bridge.bus.on("message:chat", (envelope) => {
74
85
  console.log(`${envelope.from.name}: ${envelope.payload.text}`);
75
86
  });
76
87
  ```
77
88
 
78
- ## Core Features
89
+ ## Human Operator Example
79
90
 
80
- - **Peer Registry**: Track peers, identity, skills, topics, and status
81
- - **Pub/Sub Messaging**: Broadcast, direct send, and topic fanout
82
- - **Offline Queue**: Queue messages for peers that are temporarily disconnected
83
- - **Message History**: Replay recent messages to late joiners
84
- - **Keyword Triggers**: Wake async bots from chat patterns
85
- - **Identity Protocol**: Peers announce skills and status changes
86
- - **Heartbeat**: Detect stale peers
87
- - **Browser Dashboard**: Monitor peers and chat from a single HTML file
91
+ One simple pattern is to put a human-operated process in the same room as the bots.
88
92
 
89
- ## Core Patterns
93
+ Agent:
90
94
 
91
- - Room-wide chat: `bridge.chat("hello")`
92
- - Private message: `bridge.chat("hello", "target_stream_id")`
93
- - Targeted command: `bridge.command("target_stream_id", "do_work", { jobId: 123 })`
94
- - Topic event: `bridge.publishEvent("events", "status_change", { status: "busy" })`
95
+ ```ts
96
+ import { VDOBridge } from "@vdoninja/ninja-p2p";
95
97
 
96
- ## Browser Dashboard
98
+ const worker = new VDOBridge({
99
+ room: "agents_room",
100
+ streamId: "worker_bot",
101
+ identity: {
102
+ streamId: "worker_bot",
103
+ role: "agent",
104
+ name: "Worker",
105
+ },
106
+ password: false,
107
+ skills: ["status", "say"],
108
+ });
97
109
 
98
- `dashboard.html` is a standalone single-file SPA that connects to the same VDO.Ninja room. Open it locally in a browser or host it anywhere static files are supported.
110
+ await worker.connect();
99
111
 
100
- Example:
112
+ worker.bus.on("message:command", (envelope) => {
113
+ const payload = envelope.payload as { command?: string; args?: { text?: string } };
114
+
115
+ if (payload.command === "status") {
116
+ worker.commandResponse(envelope, {
117
+ status: "idle",
118
+ peers: worker.peers.toJSON(),
119
+ });
120
+ return;
121
+ }
122
+
123
+ if (payload.command === "say") {
124
+ console.log(payload.args?.text ?? "");
125
+ worker.commandResponse(envelope, { ok: true });
126
+ return;
127
+ }
128
+
129
+ worker.commandResponse(envelope, undefined, `unknown command: ${payload.command ?? "?"}`);
130
+ });
131
+ ```
132
+
133
+ Operator:
134
+
135
+ ```ts
136
+ import { VDOBridge } from "@vdoninja/ninja-p2p";
137
+
138
+ const operator = new VDOBridge({
139
+ room: "agents_room",
140
+ streamId: "steve_operator",
141
+ identity: {
142
+ streamId: "steve_operator",
143
+ role: "operator",
144
+ name: "Steve",
145
+ },
146
+ password: false,
147
+ });
148
+
149
+ await operator.connect();
150
+
151
+ operator.command("worker_bot", "status");
152
+ operator.command("worker_bot", "say", { text: "hello from the operator" });
153
+
154
+ operator.bus.on("message:command_response", (envelope) => {
155
+ console.log(envelope.payload);
156
+ });
157
+ ```
158
+
159
+ The browser dashboard can also join the same room:
101
160
 
102
161
  ```text
103
162
  dashboard.html?room=agents_room&password=false&name=Steve&autoconnect=true
104
163
  ```
105
164
 
165
+ The dashboard can chat, DM a peer, and send simple slash-command messages like `/status`, `/health`, `/history`, and `/peers`.
166
+
167
+ ## Coordination Helpers
168
+
169
+ - `bridge.chat(text, to?)`
170
+ - `bridge.chatTopic(topic, text)`
171
+ - `bridge.command(targetStreamId, command, args?)`
172
+ - `bridge.commandResponse(message, result?, error?)`
173
+ - `bridge.publishEvent(topic, kind, data?)`
174
+ - `bridge.reply(message, type, payload)`
175
+ - `bridge.ack(message, payload?)`
176
+ - `bridge.requestHistory(targetStreamId, count?)`
177
+
178
+ These are lightweight coordination messages. They are useful, but they are not hard delivery guarantees.
179
+
180
+ ## Raw Data, Media, and Advanced SDK Access
181
+
182
+ This package focuses on data-channel messaging.
183
+
184
+ The underlying VDO.Ninja SDK goes further than that. It can also:
185
+
186
+ - publish and view audio/video tracks
187
+ - emit `track` events
188
+ - send binary payloads over the data channel
189
+
190
+ This wrapper exposes two escape hatches for that:
191
+
192
+ - `bridge.sendRaw(data, targetStreamId?)` sends arbitrary data without wrapping it in the message envelope
193
+ - `bridge.getSDK()` returns the underlying VDO.Ninja SDK instance after `connect()`
194
+
195
+ Example:
196
+
197
+ ```ts
198
+ const sdk = bridge.getSDK();
199
+
200
+ sdk?.addEventListener("track", (event) => {
201
+ const track = event.detail?.track;
202
+ console.log("track", track?.kind);
203
+ });
204
+
205
+ const chunk = new Uint8Array([1, 2, 3]).buffer;
206
+ bridge.sendRaw(chunk, "worker_bot");
207
+ ```
208
+
209
+ If you want to turn video into frames for ingestion, or build a file-transfer layer, do it on top of the SDK or on top of `sendRaw`. That is possible with the current stack, but it is not wrapped into a higher-level API in this package yet.
210
+
106
211
  ## Public API
107
212
 
108
213
  Main entrypoint:
@@ -118,21 +223,28 @@ import { VDOBridge } from "@vdoninja/ninja-p2p/vdo-bridge";
118
223
  import { createEnvelope } from "@vdoninja/ninja-p2p/protocol";
119
224
  ```
120
225
 
121
- ## Architecture
226
+ ## Files
122
227
 
123
- ```text
124
- VDOBridge
125
- |- PeerRegistry
126
- |- MessageBus
127
- `- Protocol helpers
128
- ```
228
+ - `src/vdo-bridge.ts`: connection lifecycle and SDK integration
229
+ - `src/message-bus.ts`: chat, direct messages, topics, history, offline queue
230
+ - `src/peer-registry.ts`: peer state and presence
231
+ - `src/protocol.ts`: message envelope format
232
+ - `dashboard.html`: browser monitor/chat client
233
+ - `skills/ninja-p2p`: Codex skill
129
234
 
130
235
  ## Tests
131
236
 
132
237
  ```bash
133
238
  npm test
239
+ npm run build
134
240
  ```
135
241
 
242
+ ## Support
243
+
244
+ - Discord: https://discord.vdo.ninja
245
+ - VDO.Ninja: https://vdo.ninja
246
+ - Social Stream Ninja: https://socialstream.ninja
247
+
136
248
  ## License
137
249
 
138
250
  MIT
@@ -10,7 +10,7 @@
10
10
  import { EventEmitter } from "node:events";
11
11
  import { MessageBus, type MessageBusOptions } from "./message-bus.js";
12
12
  import { PeerRegistry } from "./peer-registry.js";
13
- import { type AnnouncePayload, type MessageEnvelope, type PeerIdentity } from "./protocol.js";
13
+ import { type AnnouncePayload, type MessageEnvelope, type MessageType, type PeerIdentity } from "./protocol.js";
14
14
  export type VDOBridgeOptions = {
15
15
  room: string;
16
16
  streamId: string;
@@ -58,6 +58,18 @@ export declare class VDOBridge extends EventEmitter {
58
58
  command(targetStreamId: string, command: string, args?: unknown): MessageEnvelope;
59
59
  /** Publish an event to a topic. */
60
60
  publishEvent(topic: string, kind: string, data?: unknown): MessageEnvelope;
61
+ /** Send raw data through the underlying SDK without envelope wrapping. */
62
+ sendRaw(data: unknown, targetStreamId?: string): boolean;
63
+ /** Reply to a received message using its sender as the target. */
64
+ reply(message: MessageEnvelope, type: MessageType, payload: unknown): MessageEnvelope;
65
+ /** Acknowledge receipt of a received message. */
66
+ ack(message: MessageEnvelope, payload?: unknown): MessageEnvelope;
67
+ /** Respond to a command or request-style message. */
68
+ commandResponse(message: MessageEnvelope, result?: unknown, error?: string): MessageEnvelope;
69
+ /** Ask a peer to replay recent message history to this bridge. */
70
+ requestHistory(targetStreamId: string, count?: number): MessageEnvelope;
71
+ /** Access the underlying VDO.Ninja SDK instance for advanced media workflows. */
72
+ getSDK(): InstanceType<typeof import("@vdoninja/sdk")> | null;
61
73
  private wireSDKEvents;
62
74
  private startHeartbeat;
63
75
  private stopHeartbeat;
@@ -23,7 +23,7 @@ export class VDOBridge extends EventEmitter {
23
23
  skills;
24
24
  status = "idle";
25
25
  statusDetail = "";
26
- version = "0.1.0";
26
+ version = "0.1.1";
27
27
  constructor(options) {
28
28
  super();
29
29
  this.options = options;
@@ -149,6 +149,64 @@ export class VDOBridge extends EventEmitter {
149
149
  publishEvent(topic, kind, data) {
150
150
  return this.bus.publish(topic, "event", { kind, ...((data && typeof data === "object") ? data : { data }) });
151
151
  }
152
+ /** Send raw data through the underlying SDK without envelope wrapping. */
153
+ sendRaw(data, targetStreamId) {
154
+ if (!this.sdk)
155
+ return false;
156
+ try {
157
+ if (!targetStreamId) {
158
+ this.sdk.sendData(data);
159
+ return true;
160
+ }
161
+ const peer = this.peers.getPeer(targetStreamId);
162
+ if (peer?.uuid) {
163
+ this.sdk.sendData(data, { UUID: peer.uuid });
164
+ }
165
+ else {
166
+ this.sdk.sendData(data, { streamID: targetStreamId });
167
+ }
168
+ return true;
169
+ }
170
+ catch (err) {
171
+ this.emit("error", err);
172
+ return false;
173
+ }
174
+ }
175
+ /** Reply to a received message using its sender as the target. */
176
+ reply(message, type, payload) {
177
+ return this.bus.send(message.from.streamId, type, payload);
178
+ }
179
+ /** Acknowledge receipt of a received message. */
180
+ ack(message, payload) {
181
+ const ackPayload = { messageId: message.id };
182
+ if (payload !== undefined) {
183
+ ackPayload.data = payload;
184
+ }
185
+ return this.bus.send(message.from.streamId, "ack", ackPayload);
186
+ }
187
+ /** Respond to a command or request-style message. */
188
+ commandResponse(message, result, error) {
189
+ if (error) {
190
+ return this.bus.send(message.from.streamId, "command_response", {
191
+ requestId: message.id,
192
+ ok: false,
193
+ error,
194
+ });
195
+ }
196
+ return this.bus.send(message.from.streamId, "command_response", {
197
+ requestId: message.id,
198
+ ok: true,
199
+ result: result ?? null,
200
+ });
201
+ }
202
+ /** Ask a peer to replay recent message history to this bridge. */
203
+ requestHistory(targetStreamId, count = 50) {
204
+ return this.bus.send(targetStreamId, "history_request", { count });
205
+ }
206
+ /** Access the underlying VDO.Ninja SDK instance for advanced media workflows. */
207
+ getSDK() {
208
+ return this.sdk;
209
+ }
152
210
  // ── SDK Event Wiring ─────────────────────────────────────────────────────
153
211
  wireSDKEvents() {
154
212
  if (!this.sdk)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vdoninja/ninja-p2p",
3
- "version": "0.1.0",
4
- "description": "Reusable P2P agent communication module using VDO.Ninja WebRTC data channels",
3
+ "version": "0.1.1",
4
+ "description": "Open source P2P agent messaging and coordination over VDO.Ninja WebRTC data channels",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -42,7 +42,19 @@
42
42
  "build": "tsc -p tsconfig.json",
43
43
  "prepare": "npm run build"
44
44
  },
45
- "keywords": ["p2p", "webrtc", "vdo.ninja", "agent", "pub-sub", "data-channel", "bot"],
45
+ "keywords": [
46
+ "p2p",
47
+ "webrtc",
48
+ "vdo.ninja",
49
+ "agent",
50
+ "agent-mesh",
51
+ "pub-sub",
52
+ "data-channel",
53
+ "bot",
54
+ "nat-traversal",
55
+ "hole-punching",
56
+ "remote-control"
57
+ ],
46
58
  "author": "Steve Seguin",
47
59
  "license": "MIT",
48
60
  "publishConfig": {
@@ -1,41 +1,36 @@
1
1
  ---
2
2
  name: ninja-p2p
3
- description: Build bot-to-bot communication over VDO.Ninja WebRTC data channels. Use this skill when the user wants agents or services to meet in a room, discover peers, send private messages, broadcast events, or keep lightweight P2P presence without a central relay.
3
+ description: Use VDO.Ninja WebRTC data channels for agent messaging, peer discovery, room presence, direct messages, topics, and lightweight operator control.
4
4
  ---
5
5
 
6
6
  # ninja-p2p
7
7
 
8
- Use this skill when a project needs room-based peer discovery and direct messaging between bots over WebRTC.
8
+ Use this when a project needs agents to meet in a room and talk to each other over WebRTC.
9
9
 
10
- ## What This Repo Provides
10
+ ## What It Gives You
11
11
 
12
- - `VDOBridge` for connection lifecycle, room join, announce, heartbeat, and SDK wiring
13
- - `PeerRegistry` for presence, skills, topics, and status tracking
14
- - `MessageBus` for broadcast, direct messages, topic-based fanout, history replay, and offline queueing
15
- - `dashboard.html` for a browser-side room monitor and chat UI
12
+ - `VDOBridge` for connection lifecycle and SDK wiring
13
+ - `PeerRegistry` for presence, skills, topics, and status
14
+ - `MessageBus` for room chat, direct messages, topic fanout, history replay, and the in-memory offline queue
15
+ - `dashboard.html` for a browser room monitor/chat client
16
16
 
17
- ## Install The Library In A Project
18
-
19
- Install it from npm:
17
+ ## Install
20
18
 
21
19
  ```bash
22
20
  npm install @vdoninja/ninja-p2p @roamhq/wrtc
23
21
  ```
24
22
 
25
- `@vdoninja/sdk` is installed transitively. `ws` comes from the SDK in Node environments.
26
-
27
- ## Add This Skill To Codex
28
-
29
- Use Codex's built-in `skill-installer` helper and install the `skills/ninja-p2p` path from this repo, then restart Codex.
23
+ `@vdoninja/sdk` is installed automatically. `ws` comes from the SDK in Node.
30
24
 
31
- ## Default Integration Pattern
25
+ ## Default Pattern
32
26
 
33
- 1. Pick a shared `room` for the bots that should discover each other.
34
- 2. Give each bot a unique `streamId`.
27
+ 1. Pick a shared `room`.
28
+ 2. Give each peer a unique `streamId`.
35
29
  3. Connect with `VDOBridge`.
36
- 4. Use `bridge.chat(text)` for room-wide messages.
37
- 5. Use `bridge.chat(text, targetStreamId)` or `bridge.command(targetStreamId, command, args)` for private messages.
38
- 6. Use `bridge.publishEvent(topic, kind, data)` for topic fanout.
30
+ 4. Use `bridge.chat()` for room messages.
31
+ 5. Use `bridge.chat(..., targetStreamId)` or `bridge.command()` for direct messages.
32
+ 6. Use `bridge.publishEvent()` for topic messages.
33
+ 7. Use `bridge.commandResponse()`, `bridge.ack()`, or `bridge.reply()` when a peer should answer back.
39
34
 
40
35
  ## Example
41
36
 
@@ -68,7 +63,8 @@ bridge.bus.on("message:chat", (envelope) => {
68
63
 
69
64
  ## Notes
70
65
 
71
- - `room` and `streamId` should be stable and human-readable, but unique enough to avoid collisions.
66
+ - `room` and `streamId` should be stable and human-readable.
72
67
  - Private messages target peer `streamId`s.
73
68
  - Topic messages are broadcast and filtered on the receiver side.
74
- - The browser dashboard can sit in the same room as the bots for live inspection.
69
+ - The offline queue is in memory, not durable storage.
70
+ - `sendRaw()` and `getSDK()` are there if you need lower-level SDK access for binary data or media work.