@vdoninja/ninja-p2p 0.1.0 → 0.1.2
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/.agents/skills/ninja-p2p/SKILL.md +142 -0
- package/.agents/skills/ninja-p2p/agents/openai.yaml +5 -0
- package/.claude/skills/ninja-p2p/SKILL.md +130 -0
- package/.codex/skills/ninja-p2p/SKILL.md +142 -0
- package/.codex/skills/ninja-p2p/agents/openai.yaml +5 -0
- package/README.md +744 -57
- package/dashboard.html +595 -56
- package/dist/agent-state.d.ts +167 -0
- package/dist/agent-state.js +304 -0
- package/dist/cli-lib.d.ts +132 -0
- package/dist/cli-lib.js +602 -0
- package/dist/cli.d.ts +25 -0
- package/dist/cli.js +1337 -0
- package/dist/file-transfer.d.ts +54 -0
- package/dist/file-transfer.js +241 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -0
- package/dist/message-bus.d.ts +1 -0
- package/dist/message-bus.js +10 -3
- package/dist/peer-registry.d.ts +2 -1
- package/dist/peer-registry.js +4 -0
- package/dist/protocol.d.ts +57 -1
- package/dist/shared-folders.d.ts +31 -0
- package/dist/shared-folders.js +137 -0
- package/dist/vdo-bridge.d.ts +25 -1
- package/dist/vdo-bridge.js +109 -5
- package/package.json +39 -4
- package/skills/ninja-p2p/SKILL.md +0 -74
package/dist/vdo-bridge.d.ts
CHANGED
|
@@ -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 AgentProfile, type FileOfferPayload, type AnnouncePayload, type MessageEnvelope, type MessageType, type PeerIdentity } from "./protocol.js";
|
|
14
14
|
export type VDOBridgeOptions = {
|
|
15
15
|
room: string;
|
|
16
16
|
streamId: string;
|
|
@@ -23,6 +23,8 @@ export type VDOBridgeOptions = {
|
|
|
23
23
|
skills?: string[];
|
|
24
24
|
/** Topics to subscribe to. */
|
|
25
25
|
topics?: string[];
|
|
26
|
+
/** Optional agent profile advertised to peers. */
|
|
27
|
+
agentProfile?: AgentProfile;
|
|
26
28
|
/** Heartbeat interval in ms. Default: 30000. */
|
|
27
29
|
heartbeatMs?: number;
|
|
28
30
|
/** MessageBus options. */
|
|
@@ -40,14 +42,19 @@ export declare class VDOBridge extends EventEmitter {
|
|
|
40
42
|
private status;
|
|
41
43
|
private statusDetail;
|
|
42
44
|
private version;
|
|
45
|
+
private agentProfile;
|
|
46
|
+
private readonly viewedStreamIds;
|
|
43
47
|
constructor(options: VDOBridgeOptions);
|
|
44
48
|
connect(): Promise<void>;
|
|
45
49
|
disconnect(): Promise<void>;
|
|
46
50
|
isConnected(): boolean;
|
|
51
|
+
private emitBridgeError;
|
|
47
52
|
/** Update skills and broadcast the change. */
|
|
48
53
|
updateSkills(skills: string[]): void;
|
|
49
54
|
/** Update status and broadcast the change. */
|
|
50
55
|
updateStatus(status: string, detail?: string): void;
|
|
56
|
+
/** Update the advertised agent profile and broadcast the change. */
|
|
57
|
+
updateAgentProfile(profile: AgentProfile): void;
|
|
51
58
|
/** Get current announce payload. */
|
|
52
59
|
getAnnouncePayload(): AnnouncePayload;
|
|
53
60
|
/** Send a chat message to everyone or a specific peer. */
|
|
@@ -58,9 +65,26 @@ export declare class VDOBridge extends EventEmitter {
|
|
|
58
65
|
command(targetStreamId: string, command: string, args?: unknown): MessageEnvelope;
|
|
59
66
|
/** Publish an event to a topic. */
|
|
60
67
|
publishEvent(topic: string, kind: string, data?: unknown): MessageEnvelope;
|
|
68
|
+
/** Send a file to a connected peer. */
|
|
69
|
+
sendFile(targetStreamId: string, filePath: string): FileOfferPayload;
|
|
70
|
+
/** Send an image to a connected peer. */
|
|
71
|
+
sendImage(targetStreamId: string, filePath: string): FileOfferPayload;
|
|
72
|
+
/** Send raw data through the underlying SDK without envelope wrapping. */
|
|
73
|
+
sendRaw(data: unknown, targetStreamId?: string): boolean;
|
|
74
|
+
/** Reply to a received message using its sender as the target. */
|
|
75
|
+
reply(message: MessageEnvelope, type: MessageType, payload: unknown): MessageEnvelope;
|
|
76
|
+
/** Acknowledge receipt of a received message. */
|
|
77
|
+
ack(message: MessageEnvelope, payload?: unknown): MessageEnvelope;
|
|
78
|
+
/** Respond to a command or request-style message. */
|
|
79
|
+
commandResponse(message: MessageEnvelope, result?: unknown, error?: string): MessageEnvelope;
|
|
80
|
+
/** Ask a peer to replay recent message history to this bridge. */
|
|
81
|
+
requestHistory(targetStreamId: string, count?: number): MessageEnvelope;
|
|
82
|
+
/** Access the underlying VDO.Ninja SDK instance for advanced media workflows. */
|
|
83
|
+
getSDK(): InstanceType<typeof import("@vdoninja/sdk")> | null;
|
|
61
84
|
private wireSDKEvents;
|
|
62
85
|
private startHeartbeat;
|
|
63
86
|
private stopHeartbeat;
|
|
64
87
|
private respondPong;
|
|
65
88
|
private broadcastSkillUpdate;
|
|
89
|
+
private maybeViewPeer;
|
|
66
90
|
}
|
package/dist/vdo-bridge.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* Zero dependencies on stevesbot internals.
|
|
9
9
|
*/
|
|
10
10
|
import { EventEmitter } from "node:events";
|
|
11
|
+
import { sendFileFromPath } from "./file-transfer.js";
|
|
11
12
|
import { MessageBus } from "./message-bus.js";
|
|
12
13
|
import { PeerRegistry } from "./peer-registry.js";
|
|
13
14
|
import { createEnvelope, createInstanceId, envelopeToWire, parseEnvelope, } from "./protocol.js";
|
|
@@ -23,7 +24,9 @@ export class VDOBridge extends EventEmitter {
|
|
|
23
24
|
skills;
|
|
24
25
|
status = "idle";
|
|
25
26
|
statusDetail = "";
|
|
26
|
-
version = "0.1.
|
|
27
|
+
version = "0.1.2";
|
|
28
|
+
agentProfile;
|
|
29
|
+
viewedStreamIds = new Set();
|
|
27
30
|
constructor(options) {
|
|
28
31
|
super();
|
|
29
32
|
this.options = options;
|
|
@@ -34,6 +37,7 @@ export class VDOBridge extends EventEmitter {
|
|
|
34
37
|
instanceId: createInstanceId(),
|
|
35
38
|
};
|
|
36
39
|
this.skills = options.skills ?? [];
|
|
40
|
+
this.agentProfile = options.agentProfile;
|
|
37
41
|
this.peers = new PeerRegistry();
|
|
38
42
|
this.bus = new MessageBus(this.identity, this.peers, options.busOptions);
|
|
39
43
|
// Subscribe to requested topics
|
|
@@ -63,7 +67,7 @@ export class VDOBridge extends EventEmitter {
|
|
|
63
67
|
this.sdk.sendData(data, target ?? undefined);
|
|
64
68
|
}
|
|
65
69
|
catch (err) {
|
|
66
|
-
this.
|
|
70
|
+
this.emitBridgeError(err);
|
|
67
71
|
}
|
|
68
72
|
});
|
|
69
73
|
await this.sdk.connect();
|
|
@@ -100,6 +104,7 @@ export class VDOBridge extends EventEmitter {
|
|
|
100
104
|
catch { /* best effort */ }
|
|
101
105
|
this.connected = false;
|
|
102
106
|
this.sdk = null;
|
|
107
|
+
this.viewedStreamIds.clear();
|
|
103
108
|
this.peers.clear();
|
|
104
109
|
console.log("[P2P] Disconnected.");
|
|
105
110
|
this.emit("disconnected");
|
|
@@ -107,6 +112,13 @@ export class VDOBridge extends EventEmitter {
|
|
|
107
112
|
isConnected() {
|
|
108
113
|
return this.connected;
|
|
109
114
|
}
|
|
115
|
+
emitBridgeError(err) {
|
|
116
|
+
if (this.listenerCount("error") > 0) {
|
|
117
|
+
this.emit("error", err);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
console.error("[P2P] Bridge error:", err);
|
|
121
|
+
}
|
|
110
122
|
// ── Identity Management ──────────────────────────────────────────────────
|
|
111
123
|
/** Update skills and broadcast the change. */
|
|
112
124
|
updateSkills(skills) {
|
|
@@ -119,6 +131,11 @@ export class VDOBridge extends EventEmitter {
|
|
|
119
131
|
this.statusDetail = detail ?? "";
|
|
120
132
|
this.broadcastSkillUpdate();
|
|
121
133
|
}
|
|
134
|
+
/** Update the advertised agent profile and broadcast the change. */
|
|
135
|
+
updateAgentProfile(profile) {
|
|
136
|
+
this.agentProfile = profile;
|
|
137
|
+
this.broadcastSkillUpdate();
|
|
138
|
+
}
|
|
122
139
|
/** Get current announce payload. */
|
|
123
140
|
getAnnouncePayload() {
|
|
124
141
|
return {
|
|
@@ -127,6 +144,7 @@ export class VDOBridge extends EventEmitter {
|
|
|
127
144
|
statusDetail: this.statusDetail,
|
|
128
145
|
version: this.version,
|
|
129
146
|
topics: this.bus.getSubscriptions(),
|
|
147
|
+
agent: this.agentProfile,
|
|
130
148
|
};
|
|
131
149
|
}
|
|
132
150
|
// ── Convenience Methods ──────────────────────────────────────────────────
|
|
@@ -149,6 +167,72 @@ export class VDOBridge extends EventEmitter {
|
|
|
149
167
|
publishEvent(topic, kind, data) {
|
|
150
168
|
return this.bus.publish(topic, "event", { kind, ...((data && typeof data === "object") ? data : { data }) });
|
|
151
169
|
}
|
|
170
|
+
/** Send a file to a connected peer. */
|
|
171
|
+
sendFile(targetStreamId, filePath) {
|
|
172
|
+
return sendFileFromPath(this, targetStreamId, filePath, "file");
|
|
173
|
+
}
|
|
174
|
+
/** Send an image to a connected peer. */
|
|
175
|
+
sendImage(targetStreamId, filePath) {
|
|
176
|
+
return sendFileFromPath(this, targetStreamId, filePath, "image");
|
|
177
|
+
}
|
|
178
|
+
/** Send raw data through the underlying SDK without envelope wrapping. */
|
|
179
|
+
sendRaw(data, targetStreamId) {
|
|
180
|
+
if (!this.sdk)
|
|
181
|
+
return false;
|
|
182
|
+
try {
|
|
183
|
+
if (!targetStreamId) {
|
|
184
|
+
this.sdk.sendData(data, { allowFallback: true });
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
const peer = this.peers.getPeer(targetStreamId);
|
|
188
|
+
if (peer?.uuid) {
|
|
189
|
+
this.sdk.sendData(data, { UUID: peer.uuid, allowFallback: true });
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
this.sdk.sendData(data, { streamID: targetStreamId, allowFallback: true });
|
|
193
|
+
}
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
this.emitBridgeError(err);
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/** Reply to a received message using its sender as the target. */
|
|
202
|
+
reply(message, type, payload) {
|
|
203
|
+
return this.bus.send(message.from.streamId, type, payload);
|
|
204
|
+
}
|
|
205
|
+
/** Acknowledge receipt of a received message. */
|
|
206
|
+
ack(message, payload) {
|
|
207
|
+
const ackPayload = { messageId: message.id };
|
|
208
|
+
if (payload !== undefined) {
|
|
209
|
+
ackPayload.data = payload;
|
|
210
|
+
}
|
|
211
|
+
return this.bus.send(message.from.streamId, "ack", ackPayload);
|
|
212
|
+
}
|
|
213
|
+
/** Respond to a command or request-style message. */
|
|
214
|
+
commandResponse(message, result, error) {
|
|
215
|
+
if (error) {
|
|
216
|
+
return this.bus.send(message.from.streamId, "command_response", {
|
|
217
|
+
requestId: message.id,
|
|
218
|
+
ok: false,
|
|
219
|
+
error,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
return this.bus.send(message.from.streamId, "command_response", {
|
|
223
|
+
requestId: message.id,
|
|
224
|
+
ok: true,
|
|
225
|
+
result: result ?? null,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
/** Ask a peer to replay recent message history to this bridge. */
|
|
229
|
+
requestHistory(targetStreamId, count = 50) {
|
|
230
|
+
return this.bus.send(targetStreamId, "history_request", { count });
|
|
231
|
+
}
|
|
232
|
+
/** Access the underlying VDO.Ninja SDK instance for advanced media workflows. */
|
|
233
|
+
getSDK() {
|
|
234
|
+
return this.sdk;
|
|
235
|
+
}
|
|
152
236
|
// ── SDK Event Wiring ─────────────────────────────────────────────────────
|
|
153
237
|
wireSDKEvents() {
|
|
154
238
|
if (!this.sdk)
|
|
@@ -239,6 +323,7 @@ export class VDOBridge extends EventEmitter {
|
|
|
239
323
|
this.sdk.addEventListener("peerDisconnected", (event) => {
|
|
240
324
|
const uuid = event.detail?.uuid ?? "unknown";
|
|
241
325
|
const streamId = event.detail?.streamID ?? this.peers.streamIdForUuid(uuid) ?? uuid;
|
|
326
|
+
this.viewedStreamIds.delete(streamId);
|
|
242
327
|
this.peers.markDisconnected(streamId);
|
|
243
328
|
console.log(`[P2P] Peer disconnected: ${streamId}`);
|
|
244
329
|
this.emit("peer:disconnected", { streamId, uuid });
|
|
@@ -257,15 +342,20 @@ export class VDOBridge extends EventEmitter {
|
|
|
257
342
|
const list = event.detail?.list ?? [];
|
|
258
343
|
for (const entry of list) {
|
|
259
344
|
if (entry.streamID && entry.streamID !== this.options.streamId) {
|
|
260
|
-
|
|
261
|
-
this.sdk.view(entry.streamID, { audio: false, video: false });
|
|
345
|
+
this.maybeViewPeer(entry.streamID);
|
|
262
346
|
}
|
|
263
347
|
}
|
|
264
348
|
});
|
|
349
|
+
this.sdk.addEventListener("videoaddedtoroom", (event) => {
|
|
350
|
+
this.maybeViewPeer(event.detail?.streamID);
|
|
351
|
+
});
|
|
352
|
+
this.sdk.addEventListener("streamAdded", (event) => {
|
|
353
|
+
this.maybeViewPeer(event.detail?.streamID);
|
|
354
|
+
});
|
|
265
355
|
// Error handling
|
|
266
356
|
this.sdk.addEventListener("error", (event) => {
|
|
267
357
|
console.error("[P2P] SDK error:", event.detail?.error);
|
|
268
|
-
this.
|
|
358
|
+
this.emitBridgeError(event.detail?.error);
|
|
269
359
|
});
|
|
270
360
|
}
|
|
271
361
|
// ── Heartbeat ────────────────────────────────────────────────────────────
|
|
@@ -313,6 +403,20 @@ export class VDOBridge extends EventEmitter {
|
|
|
313
403
|
skills: this.skills,
|
|
314
404
|
status: this.status,
|
|
315
405
|
statusDetail: this.statusDetail,
|
|
406
|
+
agent: this.agentProfile,
|
|
316
407
|
});
|
|
317
408
|
}
|
|
409
|
+
maybeViewPeer(streamId) {
|
|
410
|
+
if (!this.sdk || !streamId || streamId === this.options.streamId || this.viewedStreamIds.has(streamId)) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
this.viewedStreamIds.add(streamId);
|
|
414
|
+
try {
|
|
415
|
+
this.sdk.view(streamId, { audio: false, video: false });
|
|
416
|
+
}
|
|
417
|
+
catch (err) {
|
|
418
|
+
this.viewedStreamIds.delete(streamId);
|
|
419
|
+
this.emitBridgeError(err);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
318
422
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vdoninja/ninja-p2p",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.2",
|
|
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",
|
|
8
|
+
"bin": {
|
|
9
|
+
"ninja-p2p": "./dist/cli.js"
|
|
10
|
+
},
|
|
8
11
|
"exports": {
|
|
9
12
|
".": {
|
|
10
13
|
"types": "./dist/index.d.ts",
|
|
@@ -22,6 +25,18 @@
|
|
|
22
25
|
"types": "./dist/peer-registry.d.ts",
|
|
23
26
|
"import": "./dist/peer-registry.js"
|
|
24
27
|
},
|
|
28
|
+
"./file-transfer": {
|
|
29
|
+
"types": "./dist/file-transfer.d.ts",
|
|
30
|
+
"import": "./dist/file-transfer.js"
|
|
31
|
+
},
|
|
32
|
+
"./agent-state": {
|
|
33
|
+
"types": "./dist/agent-state.d.ts",
|
|
34
|
+
"import": "./dist/agent-state.js"
|
|
35
|
+
},
|
|
36
|
+
"./shared-folders": {
|
|
37
|
+
"types": "./dist/shared-folders.d.ts",
|
|
38
|
+
"import": "./dist/shared-folders.js"
|
|
39
|
+
},
|
|
25
40
|
"./protocol": {
|
|
26
41
|
"types": "./dist/protocol.d.ts",
|
|
27
42
|
"import": "./dist/protocol.js"
|
|
@@ -33,16 +48,36 @@
|
|
|
33
48
|
"dist",
|
|
34
49
|
"dashboard.html",
|
|
35
50
|
"README.md",
|
|
36
|
-
"skills/ninja-p2p"
|
|
51
|
+
".codex/skills/ninja-p2p",
|
|
52
|
+
".agents/skills/ninja-p2p",
|
|
53
|
+
".claude/skills/ninja-p2p"
|
|
37
54
|
],
|
|
38
55
|
"sideEffects": false,
|
|
39
56
|
"scripts": {
|
|
40
57
|
"test": "tsx --test tests/**/*.test.ts",
|
|
41
58
|
"prebuild": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
42
59
|
"build": "tsc -p tsconfig.json",
|
|
60
|
+
"validate:live": "node ./scripts/validate-live.mjs",
|
|
61
|
+
"sync:docs": "node -e \"const fs=require('node:fs'); fs.mkdirSync('docs',{recursive:true}); fs.copyFileSync('dashboard.html','docs/index.html');\"",
|
|
43
62
|
"prepare": "npm run build"
|
|
44
63
|
},
|
|
45
|
-
"keywords": [
|
|
64
|
+
"keywords": [
|
|
65
|
+
"p2p",
|
|
66
|
+
"webrtc",
|
|
67
|
+
"vdo.ninja",
|
|
68
|
+
"agent",
|
|
69
|
+
"agent-mesh",
|
|
70
|
+
"cli",
|
|
71
|
+
"codex",
|
|
72
|
+
"claude-code",
|
|
73
|
+
"skill",
|
|
74
|
+
"pub-sub",
|
|
75
|
+
"data-channel",
|
|
76
|
+
"bot",
|
|
77
|
+
"nat-traversal",
|
|
78
|
+
"hole-punching",
|
|
79
|
+
"remote-control"
|
|
80
|
+
],
|
|
46
81
|
"author": "Steve Seguin",
|
|
47
82
|
"license": "MIT",
|
|
48
83
|
"publishConfig": {
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
---
|
|
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.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# ninja-p2p
|
|
7
|
-
|
|
8
|
-
Use this skill when a project needs room-based peer discovery and direct messaging between bots over WebRTC.
|
|
9
|
-
|
|
10
|
-
## What This Repo Provides
|
|
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
|
|
16
|
-
|
|
17
|
-
## Install The Library In A Project
|
|
18
|
-
|
|
19
|
-
Install it from npm:
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
npm install @vdoninja/ninja-p2p @roamhq/wrtc
|
|
23
|
-
```
|
|
24
|
-
|
|
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.
|
|
30
|
-
|
|
31
|
-
## Default Integration Pattern
|
|
32
|
-
|
|
33
|
-
1. Pick a shared `room` for the bots that should discover each other.
|
|
34
|
-
2. Give each bot a unique `streamId`.
|
|
35
|
-
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.
|
|
39
|
-
|
|
40
|
-
## Example
|
|
41
|
-
|
|
42
|
-
```ts
|
|
43
|
-
import { VDOBridge } from "@vdoninja/ninja-p2p";
|
|
44
|
-
|
|
45
|
-
const bridge = new VDOBridge({
|
|
46
|
-
room: "agents_room",
|
|
47
|
-
streamId: "planner_bot",
|
|
48
|
-
identity: {
|
|
49
|
-
streamId: "planner_bot",
|
|
50
|
-
role: "agent",
|
|
51
|
-
name: "Planner",
|
|
52
|
-
},
|
|
53
|
-
password: false,
|
|
54
|
-
skills: ["plan", "chat"],
|
|
55
|
-
topics: ["events"],
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
await bridge.connect();
|
|
59
|
-
|
|
60
|
-
bridge.chat("Planner online");
|
|
61
|
-
bridge.chat("sync now", "worker_bot");
|
|
62
|
-
bridge.publishEvent("events", "status_change", { status: "busy" });
|
|
63
|
-
|
|
64
|
-
bridge.bus.on("message:chat", (envelope) => {
|
|
65
|
-
console.log(`${envelope.from.name}: ${envelope.payload.text}`);
|
|
66
|
-
});
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Notes
|
|
70
|
-
|
|
71
|
-
- `room` and `streamId` should be stable and human-readable, but unique enough to avoid collisions.
|
|
72
|
-
- Private messages target peer `streamId`s.
|
|
73
|
-
- 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.
|