@silicaclaw/cli 2026.3.20-1 → 2026.3.20-10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/INSTALL.md +13 -7
  3. package/README.md +60 -12
  4. package/VERSION +1 -1
  5. package/apps/local-console/dist/apps/local-console/src/server.d.ts +129 -2
  6. package/apps/local-console/dist/apps/local-console/src/server.js +887 -91
  7. package/apps/local-console/dist/packages/core/src/index.d.ts +2 -0
  8. package/apps/local-console/dist/packages/core/src/index.js +2 -0
  9. package/apps/local-console/dist/packages/core/src/privateCrypto.d.ts +17 -0
  10. package/apps/local-console/dist/packages/core/src/privateCrypto.js +40 -0
  11. package/apps/local-console/dist/packages/core/src/privateMessage.d.ts +23 -0
  12. package/apps/local-console/dist/packages/core/src/privateMessage.js +74 -0
  13. package/apps/local-console/dist/packages/core/src/profile.js +2 -0
  14. package/apps/local-console/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
  15. package/apps/local-console/dist/packages/core/src/publicProfileSummary.js +3 -0
  16. package/apps/local-console/dist/packages/core/src/types.d.ts +40 -0
  17. package/apps/local-console/dist/packages/network/src/relayPreview.d.ts +12 -0
  18. package/apps/local-console/dist/packages/network/src/relayPreview.js +108 -8
  19. package/apps/local-console/dist/packages/network/src/types.d.ts +4 -0
  20. package/apps/local-console/dist/packages/storage/src/repos.d.ts +13 -1
  21. package/apps/local-console/dist/packages/storage/src/repos.js +19 -1
  22. package/apps/local-console/public/app/app.js +465 -11
  23. package/apps/local-console/public/app/events.js +21 -0
  24. package/apps/local-console/public/app/network.js +144 -32
  25. package/apps/local-console/public/app/overview.js +60 -52
  26. package/apps/local-console/public/app/social.js +316 -93
  27. package/apps/local-console/public/app/styles.css +127 -1
  28. package/apps/local-console/public/app/template.js +121 -35
  29. package/apps/local-console/public/app/translations.js +430 -316
  30. package/apps/local-console/src/server.ts +1024 -89
  31. package/apps/public-explorer/public/app/template.js +2 -2
  32. package/apps/public-explorer/public/app/translations.js +36 -36
  33. package/docs/NEW_USER_OPERATIONS.md +5 -5
  34. package/docs/OPENCLAW_BRIDGE.md +7 -7
  35. package/docs/OPENCLAW_BRIDGE_ZH.md +6 -6
  36. package/node_modules/@silicaclaw/core/dist/packages/core/src/index.d.ts +2 -0
  37. package/node_modules/@silicaclaw/core/dist/packages/core/src/index.js +2 -0
  38. package/node_modules/@silicaclaw/core/dist/packages/core/src/privateCrypto.d.ts +17 -0
  39. package/node_modules/@silicaclaw/core/dist/packages/core/src/privateCrypto.js +40 -0
  40. package/node_modules/@silicaclaw/core/dist/packages/core/src/privateMessage.d.ts +23 -0
  41. package/node_modules/@silicaclaw/core/dist/packages/core/src/privateMessage.js +74 -0
  42. package/node_modules/@silicaclaw/core/dist/packages/core/src/profile.js +2 -0
  43. package/node_modules/@silicaclaw/core/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
  44. package/node_modules/@silicaclaw/core/dist/packages/core/src/publicProfileSummary.js +3 -0
  45. package/node_modules/@silicaclaw/core/dist/packages/core/src/types.d.ts +40 -0
  46. package/node_modules/@silicaclaw/core/src/index.ts +2 -0
  47. package/node_modules/@silicaclaw/core/src/privateCrypto.ts +57 -0
  48. package/node_modules/@silicaclaw/core/src/privateMessage.ts +101 -0
  49. package/node_modules/@silicaclaw/core/src/profile.ts +2 -0
  50. package/node_modules/@silicaclaw/core/src/publicProfileSummary.ts +7 -0
  51. package/node_modules/@silicaclaw/core/src/types.ts +44 -0
  52. package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.d.ts +12 -0
  53. package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.js +108 -8
  54. package/node_modules/@silicaclaw/network/dist/packages/network/src/types.d.ts +4 -0
  55. package/node_modules/@silicaclaw/network/src/relayPreview.ts +120 -10
  56. package/node_modules/@silicaclaw/network/src/types.ts +2 -0
  57. package/node_modules/@silicaclaw/storage/dist/packages/core/src/index.d.ts +2 -0
  58. package/node_modules/@silicaclaw/storage/dist/packages/core/src/index.js +2 -0
  59. package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateCrypto.d.ts +17 -0
  60. package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateCrypto.js +40 -0
  61. package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateMessage.d.ts +23 -0
  62. package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateMessage.js +74 -0
  63. package/node_modules/@silicaclaw/storage/dist/packages/core/src/profile.js +2 -0
  64. package/node_modules/@silicaclaw/storage/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
  65. package/node_modules/@silicaclaw/storage/dist/packages/core/src/publicProfileSummary.js +3 -0
  66. package/node_modules/@silicaclaw/storage/dist/packages/core/src/types.d.ts +40 -0
  67. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.d.ts +13 -1
  68. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.js +19 -1
  69. package/node_modules/@silicaclaw/storage/src/repos.ts +31 -1
  70. package/openclaw-skills/silicaclaw-bridge-setup/SKILL.md +18 -0
  71. package/openclaw-skills/silicaclaw-bridge-setup/VERSION +1 -1
  72. package/openclaw-skills/silicaclaw-bridge-setup/manifest.json +2 -2
  73. package/openclaw-skills/silicaclaw-broadcast/SKILL.md +18 -0
  74. package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -1
  75. package/openclaw-skills/silicaclaw-broadcast/manifest.json +2 -2
  76. package/openclaw-skills/silicaclaw-network-config/SKILL.md +158 -0
  77. package/openclaw-skills/silicaclaw-network-config/VERSION +1 -0
  78. package/openclaw-skills/silicaclaw-network-config/agents/openai.yaml +6 -0
  79. package/openclaw-skills/silicaclaw-network-config/manifest.json +27 -0
  80. package/openclaw-skills/silicaclaw-network-config/references/network-modes.md +22 -0
  81. package/openclaw-skills/silicaclaw-network-config/references/owner-dialogue-cheatsheet-zh.md +47 -0
  82. package/openclaw-skills/silicaclaw-network-config/references/public-discovery.md +22 -0
  83. package/openclaw-skills/silicaclaw-owner-push/SKILL.md +18 -0
  84. package/openclaw-skills/silicaclaw-owner-push/VERSION +1 -1
  85. package/openclaw-skills/silicaclaw-owner-push/manifest.json +2 -2
  86. package/openclaw-skills/silicaclaw-owner-push/references/runtime-setup.md +3 -0
  87. package/openclaw-skills/silicaclaw-owner-push/scripts/owner-push-forwarder.mjs +151 -9
  88. package/package.json +1 -1
  89. package/packages/core/dist/packages/core/src/index.d.ts +2 -0
  90. package/packages/core/dist/packages/core/src/index.js +2 -0
  91. package/packages/core/dist/packages/core/src/privateCrypto.d.ts +17 -0
  92. package/packages/core/dist/packages/core/src/privateCrypto.js +40 -0
  93. package/packages/core/dist/packages/core/src/privateMessage.d.ts +23 -0
  94. package/packages/core/dist/packages/core/src/privateMessage.js +74 -0
  95. package/packages/core/dist/packages/core/src/profile.js +2 -0
  96. package/packages/core/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
  97. package/packages/core/dist/packages/core/src/publicProfileSummary.js +3 -0
  98. package/packages/core/dist/packages/core/src/types.d.ts +40 -0
  99. package/packages/core/src/index.ts +2 -0
  100. package/packages/core/src/privateCrypto.ts +57 -0
  101. package/packages/core/src/privateMessage.ts +101 -0
  102. package/packages/core/src/profile.ts +2 -0
  103. package/packages/core/src/publicProfileSummary.ts +7 -0
  104. package/packages/core/src/types.ts +44 -0
  105. package/packages/network/dist/packages/network/src/relayPreview.d.ts +12 -0
  106. package/packages/network/dist/packages/network/src/relayPreview.js +108 -8
  107. package/packages/network/dist/packages/network/src/types.d.ts +4 -0
  108. package/packages/network/src/relayPreview.ts +120 -10
  109. package/packages/network/src/types.ts +2 -0
  110. package/packages/storage/dist/packages/core/src/index.d.ts +2 -0
  111. package/packages/storage/dist/packages/core/src/index.js +2 -0
  112. package/packages/storage/dist/packages/core/src/privateCrypto.d.ts +17 -0
  113. package/packages/storage/dist/packages/core/src/privateCrypto.js +40 -0
  114. package/packages/storage/dist/packages/core/src/privateMessage.d.ts +23 -0
  115. package/packages/storage/dist/packages/core/src/privateMessage.js +74 -0
  116. package/packages/storage/dist/packages/core/src/profile.js +2 -0
  117. package/packages/storage/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
  118. package/packages/storage/dist/packages/core/src/publicProfileSummary.js +3 -0
  119. package/packages/storage/dist/packages/core/src/types.d.ts +40 -0
  120. package/packages/storage/dist/packages/storage/src/repos.d.ts +13 -1
  121. package/packages/storage/dist/packages/storage/src/repos.js +19 -1
  122. package/packages/storage/src/repos.ts +31 -1
  123. package/scripts/silicaclaw-cli.mjs +59 -6
  124. package/scripts/silicaclaw-gateway.mjs +108 -0
  125. package/scripts/validate-openclaw-skill.mjs +19 -0
@@ -31,6 +31,7 @@ class RelayPreviewAdapter {
31
31
  started = false;
32
32
  poller = null;
33
33
  handlers = new Map();
34
+ directHandlers = new Map();
34
35
  peers = new Map();
35
36
  seenMessageIds = new Set();
36
37
  activeEndpoint = "";
@@ -109,7 +110,6 @@ class RelayPreviewAdapter {
109
110
  try {
110
111
  await this.joinRoom("start");
111
112
  this.started = true;
112
- await this.refreshPeers();
113
113
  await this.pollOnce();
114
114
  this.scheduleNextPoll(this.pollIntervalMs);
115
115
  this.recordDiscovery("signaling_connected", { endpoint: this.activeEndpoint });
@@ -166,6 +166,42 @@ class RelayPreviewAdapter {
166
166
  }
167
167
  this.handlers.get(key)?.add(handler);
168
168
  }
169
+ async sendDirect(peerId, topic, data) {
170
+ if (!this.started)
171
+ return;
172
+ const targetPeerId = String(peerId || "").trim();
173
+ if (!targetPeerId)
174
+ return;
175
+ await this.maybeRefreshJoin("direct_send");
176
+ const envelope = {
177
+ version: 1,
178
+ message_id: (0, crypto_1.randomUUID)(),
179
+ topic: `${this.namespace}:${topic}`,
180
+ source_peer_id: this.peerId,
181
+ timestamp: Date.now(),
182
+ payload: this.topicCodec.encode(topic, data),
183
+ };
184
+ const raw = this.envelopeCodec.encode(envelope);
185
+ if (raw.length > this.maxMessageBytes) {
186
+ this.stats.dropped_oversized += 1;
187
+ return;
188
+ }
189
+ await this.post("/direct/send", {
190
+ room: this.room,
191
+ from_peer_id: this.peerId,
192
+ to_peer_id: targetPeerId,
193
+ envelope,
194
+ });
195
+ this.lastPublishAt = Date.now();
196
+ this.signalingMessagesSentTotal += 1;
197
+ }
198
+ subscribeDirect(topic, handler) {
199
+ const key = `${this.namespace}:${topic}`;
200
+ if (!this.directHandlers.has(key)) {
201
+ this.directHandlers.set(key, new Set());
202
+ }
203
+ this.directHandlers.get(key)?.add(handler);
204
+ }
169
205
  getDiagnostics() {
170
206
  const peerItems = Array.from(this.peers.values()).sort((a, b) => b.last_seen_at - a.last_seen_at);
171
207
  return {
@@ -235,6 +271,15 @@ class RelayPreviewAdapter {
235
271
  this.signalingMessagesReceivedTotal += 1;
236
272
  this.onEnvelope(message?.envelope);
237
273
  }
274
+ let directMessages = Array.isArray(payload?.direct_messages) ? payload.direct_messages : null;
275
+ if (!directMessages) {
276
+ const directPayload = await this.get(`/direct/poll?room=${encodeURIComponent(this.room)}&peer_id=${encodeURIComponent(this.peerId)}`);
277
+ directMessages = Array.isArray(directPayload?.messages) ? directPayload.messages : [];
278
+ }
279
+ for (const message of directMessages) {
280
+ this.signalingMessagesReceivedTotal += 1;
281
+ this.onDirectEnvelope(message?.envelope, { peerId: String(message?.from_peer_id || "") || undefined });
282
+ }
238
283
  if (Array.isArray(payload?.peers)) {
239
284
  this.updatePeersFromList(payload.peers);
240
285
  }
@@ -258,10 +303,18 @@ class RelayPreviewAdapter {
258
303
  const payload = await this.get(`/peers?room=${encodeURIComponent(this.room)}`);
259
304
  this.lastPeerRefreshAt = Date.now();
260
305
  this.stats.peers_refresh_succeeded += 1;
261
- const peerIds = Array.isArray(payload?.peers) ? payload.peers : [];
262
- this.updatePeersFromList(peerIds);
306
+ const peerItems = Array.isArray(payload?.peer_details) && payload.peer_details.length
307
+ ? payload.peer_details
308
+ : Array.isArray(payload?.peers) ? payload.peers : [];
309
+ this.updatePeersFromList(peerItems);
263
310
  }
264
311
  onEnvelope(envelope) {
312
+ this.dispatchEnvelope(envelope, this.handlers);
313
+ }
314
+ onDirectEnvelope(envelope, meta) {
315
+ this.dispatchEnvelope(envelope, this.directHandlers, meta);
316
+ }
317
+ dispatchEnvelope(envelope, handlersByTopic, meta) {
265
318
  this.stats.received_total += 1;
266
319
  const validated = (0, messageEnvelope_1.validateNetworkMessageEnvelope)(envelope, {
267
320
  max_future_drift_ms: this.maxFutureDriftMs,
@@ -300,7 +353,7 @@ class RelayPreviewAdapter {
300
353
  this.stats.received_validated += 1;
301
354
  const topicKey = message.topic;
302
355
  const topic = topicKey.slice(this.namespace.length + 1);
303
- const handlers = this.handlers.get(topicKey);
356
+ const handlers = handlersByTopic.get(topicKey);
304
357
  if (!handlers || handlers.size === 0)
305
358
  return;
306
359
  const peer = this.peers.get(message.source_peer_id);
@@ -318,7 +371,7 @@ class RelayPreviewAdapter {
318
371
  }
319
372
  for (const handler of handlers) {
320
373
  try {
321
- handler(payload);
374
+ handler(payload, meta || { peerId: message.source_peer_id });
322
375
  this.stats.delivered_total += 1;
323
376
  }
324
377
  catch {
@@ -340,15 +393,20 @@ class RelayPreviewAdapter {
340
393
  }
341
394
  async joinRoom(reason) {
342
395
  this.stats.join_attempted += 1;
343
- await this.post("/join", { room: this.room, peer_id: this.peerId });
396
+ const payload = await this.post("/join", { room: this.room, peer_id: this.peerId });
344
397
  this.lastJoinAt = Date.now();
345
398
  this.stats.join_succeeded += 1;
399
+ if (Array.isArray(payload?.peers)) {
400
+ this.updatePeersFromList(payload.peers);
401
+ this.lastPeerRefreshAt = this.lastJoinAt;
402
+ }
346
403
  this.recordDiscovery("join_ok", { endpoint: this.activeEndpoint, detail: reason });
347
404
  }
348
405
  async maybeRefreshJoin(reason) {
349
406
  if (!this.lastJoinAt || Date.now() - this.lastJoinAt > Math.max(45_000, this.pollIntervalMs * 6)) {
350
407
  await this.joinRoom(reason);
351
408
  }
409
+ this.ensurePollingAlive(reason);
352
410
  }
353
411
  async get(path) {
354
412
  return this.requestJson("GET", path);
@@ -406,13 +464,38 @@ class RelayPreviewAdapter {
406
464
  throw new Error(errors.join(" | "));
407
465
  }
408
466
  updatePeersFromList(values) {
409
- const peerIds = values.map((value) => String(value || "").trim()).filter(Boolean);
467
+ const parsedPeers = [];
468
+ for (const value of values) {
469
+ if (typeof value === "string") {
470
+ const peerId = String(value || "").trim();
471
+ if (peerId) {
472
+ parsedPeers.push({ peer_id: peerId });
473
+ }
474
+ continue;
475
+ }
476
+ if (value && typeof value === "object") {
477
+ const raw = value;
478
+ const peerId = String(raw.peer_id || "").trim();
479
+ if (!peerId) {
480
+ continue;
481
+ }
482
+ parsedPeers.push({
483
+ peer_id: peerId,
484
+ meta: {
485
+ signal_queue_size: Number(raw.signal_queue_size ?? 0),
486
+ relay_queue_size: Number(raw.relay_queue_size ?? 0),
487
+ },
488
+ });
489
+ }
490
+ }
491
+ const peerIds = parsedPeers.map((peer) => peer.peer_id);
410
492
  if (!peerIds.includes(this.peerId)) {
411
493
  void this.joinRoom("self_missing_from_peers").catch(() => { });
412
494
  }
413
495
  const now = Date.now();
414
496
  const next = new Map();
415
- for (const peerId of peerIds) {
497
+ for (const peerInfo of parsedPeers) {
498
+ const peerId = peerInfo.peer_id;
416
499
  if (peerId === this.peerId)
417
500
  continue;
418
501
  const existing = this.peers.get(peerId);
@@ -426,6 +509,7 @@ class RelayPreviewAdapter {
426
509
  last_seen_at: now,
427
510
  messages_seen: existing?.messages_seen ?? 0,
428
511
  reconnect_attempts: existing?.reconnect_attempts ?? 0,
512
+ meta: peerInfo.meta || existing?.meta,
429
513
  });
430
514
  }
431
515
  for (const peerId of this.peers.keys()) {
@@ -444,5 +528,21 @@ class RelayPreviewAdapter {
444
528
  this.pollOnce().catch(() => { });
445
529
  }, Math.max(1000, delayMs + jitterMs));
446
530
  }
531
+ ensurePollingAlive(reason) {
532
+ if (!this.started)
533
+ return;
534
+ const pollStaleMs = Math.max(45_000, this.pollIntervalMs * 6);
535
+ const pollMissing = !this.poller;
536
+ const pollStale = Boolean(this.lastPollAt) && Date.now() - this.lastPollAt > pollStaleMs;
537
+ if (!pollMissing && !pollStale) {
538
+ return;
539
+ }
540
+ this.recordDiscovery("poll_recover_scheduled", {
541
+ endpoint: this.activeEndpoint,
542
+ detail: `${reason}:${pollMissing ? "missing" : "stale"}`,
543
+ });
544
+ this.currentPollDelayMs = this.pollIntervalMs;
545
+ this.scheduleNextPoll(0);
546
+ }
447
547
  }
448
548
  exports.RelayPreviewAdapter = RelayPreviewAdapter;
@@ -3,4 +3,8 @@ export interface NetworkAdapter {
3
3
  stop(): Promise<void>;
4
4
  publish(topic: string, data: any): Promise<void>;
5
5
  subscribe(topic: string, handler: (data: any) => void): void;
6
+ sendDirect?(peerId: string, topic: string, data: any): Promise<void>;
7
+ subscribeDirect?(topic: string, handler: (data: any, meta?: {
8
+ peerId?: string;
9
+ }) => void): void;
6
10
  }
@@ -34,6 +34,10 @@ type RelayPeer = {
34
34
  last_seen_at: number;
35
35
  messages_seen: number;
36
36
  reconnect_attempts: number;
37
+ meta?: {
38
+ signal_queue_size?: number;
39
+ relay_queue_size?: number;
40
+ };
37
41
  };
38
42
 
39
43
  type RelayDiagnostics = {
@@ -144,7 +148,8 @@ export class RelayPreviewAdapter implements NetworkAdapter {
144
148
 
145
149
  private started = false;
146
150
  private poller: NodeJS.Timeout | null = null;
147
- private handlers = new Map<string, Set<(data: any) => void>>();
151
+ private handlers = new Map<string, Set<(data: any, meta?: { peerId?: string }) => void>>();
152
+ private directHandlers = new Map<string, Set<(data: any, meta?: { peerId?: string }) => void>>();
148
153
  private peers = new Map<string, RelayPeer>();
149
154
  private seenMessageIds = new Set<string>();
150
155
  private activeEndpoint = "";
@@ -227,7 +232,6 @@ export class RelayPreviewAdapter implements NetworkAdapter {
227
232
  try {
228
233
  await this.joinRoom("start");
229
234
  this.started = true;
230
- await this.refreshPeers();
231
235
  await this.pollOnce();
232
236
  this.scheduleNextPoll(this.pollIntervalMs);
233
237
  this.recordDiscovery("signaling_connected", { endpoint: this.activeEndpoint });
@@ -281,7 +285,43 @@ export class RelayPreviewAdapter implements NetworkAdapter {
281
285
  if (!this.handlers.has(key)) {
282
286
  this.handlers.set(key, new Set());
283
287
  }
284
- this.handlers.get(key)?.add(handler);
288
+ this.handlers.get(key)?.add(handler as (data: any, meta?: { peerId?: string }) => void);
289
+ }
290
+
291
+ async sendDirect(peerId: string, topic: string, data: any): Promise<void> {
292
+ if (!this.started) return;
293
+ const targetPeerId = String(peerId || "").trim();
294
+ if (!targetPeerId) return;
295
+ await this.maybeRefreshJoin("direct_send");
296
+ const envelope: NetworkMessageEnvelope = {
297
+ version: 1,
298
+ message_id: randomUUID(),
299
+ topic: `${this.namespace}:${topic}`,
300
+ source_peer_id: this.peerId,
301
+ timestamp: Date.now(),
302
+ payload: this.topicCodec.encode(topic, data),
303
+ };
304
+ const raw = this.envelopeCodec.encode(envelope);
305
+ if (raw.length > this.maxMessageBytes) {
306
+ this.stats.dropped_oversized += 1;
307
+ return;
308
+ }
309
+ await this.post("/direct/send", {
310
+ room: this.room,
311
+ from_peer_id: this.peerId,
312
+ to_peer_id: targetPeerId,
313
+ envelope,
314
+ });
315
+ this.lastPublishAt = Date.now();
316
+ this.signalingMessagesSentTotal += 1;
317
+ }
318
+
319
+ subscribeDirect(topic: string, handler: (data: any, meta?: { peerId?: string }) => void): void {
320
+ const key = `${this.namespace}:${topic}`;
321
+ if (!this.directHandlers.has(key)) {
322
+ this.directHandlers.set(key, new Set());
323
+ }
324
+ this.directHandlers.get(key)?.add(handler);
285
325
  }
286
326
 
287
327
  getDiagnostics(): RelayDiagnostics {
@@ -354,6 +394,15 @@ export class RelayPreviewAdapter implements NetworkAdapter {
354
394
  this.signalingMessagesReceivedTotal += 1;
355
395
  this.onEnvelope(message?.envelope);
356
396
  }
397
+ let directMessages = Array.isArray(payload?.direct_messages) ? payload.direct_messages : null;
398
+ if (!directMessages) {
399
+ const directPayload = await this.get(`/direct/poll?room=${encodeURIComponent(this.room)}&peer_id=${encodeURIComponent(this.peerId)}`);
400
+ directMessages = Array.isArray(directPayload?.messages) ? directPayload.messages : [];
401
+ }
402
+ for (const message of directMessages) {
403
+ this.signalingMessagesReceivedTotal += 1;
404
+ this.onDirectEnvelope(message?.envelope, { peerId: String(message?.from_peer_id || "") || undefined });
405
+ }
357
406
  if (Array.isArray(payload?.peers)) {
358
407
  this.updatePeersFromList(payload.peers);
359
408
  } else if (!this.lastPeerRefreshAt || Date.now() - this.lastPeerRefreshAt >= this.peerRefreshIntervalMs) {
@@ -375,11 +424,25 @@ export class RelayPreviewAdapter implements NetworkAdapter {
375
424
  const payload = await this.get(`/peers?room=${encodeURIComponent(this.room)}`);
376
425
  this.lastPeerRefreshAt = Date.now();
377
426
  this.stats.peers_refresh_succeeded += 1;
378
- const peerIds = Array.isArray(payload?.peers) ? payload.peers : [];
379
- this.updatePeersFromList(peerIds);
427
+ const peerItems = Array.isArray(payload?.peer_details) && payload.peer_details.length
428
+ ? payload.peer_details
429
+ : Array.isArray(payload?.peers) ? payload.peers : [];
430
+ this.updatePeersFromList(peerItems);
380
431
  }
381
432
 
382
433
  private onEnvelope(envelope: unknown): void {
434
+ this.dispatchEnvelope(envelope, this.handlers);
435
+ }
436
+
437
+ private onDirectEnvelope(envelope: unknown, meta?: { peerId?: string }): void {
438
+ this.dispatchEnvelope(envelope, this.directHandlers, meta);
439
+ }
440
+
441
+ private dispatchEnvelope(
442
+ envelope: unknown,
443
+ handlersByTopic: Map<string, Set<(data: any, meta?: { peerId?: string }) => void>>,
444
+ meta?: { peerId?: string }
445
+ ): void {
383
446
  this.stats.received_total += 1;
384
447
  const validated = validateNetworkMessageEnvelope(envelope, {
385
448
  max_future_drift_ms: this.maxFutureDriftMs,
@@ -416,7 +479,7 @@ export class RelayPreviewAdapter implements NetworkAdapter {
416
479
 
417
480
  const topicKey = message.topic;
418
481
  const topic = topicKey.slice(this.namespace.length + 1);
419
- const handlers = this.handlers.get(topicKey);
482
+ const handlers = handlersByTopic.get(topicKey);
420
483
  if (!handlers || handlers.size === 0) return;
421
484
 
422
485
  const peer = this.peers.get(message.source_peer_id);
@@ -434,7 +497,7 @@ export class RelayPreviewAdapter implements NetworkAdapter {
434
497
  }
435
498
  for (const handler of handlers) {
436
499
  try {
437
- handler(payload);
500
+ handler(payload, meta || { peerId: message.source_peer_id });
438
501
  this.stats.delivered_total += 1;
439
502
  } catch {
440
503
  this.stats.dropped_handler_error += 1;
@@ -457,9 +520,13 @@ export class RelayPreviewAdapter implements NetworkAdapter {
457
520
 
458
521
  private async joinRoom(reason: string): Promise<void> {
459
522
  this.stats.join_attempted += 1;
460
- await this.post("/join", { room: this.room, peer_id: this.peerId });
523
+ const payload = await this.post("/join", { room: this.room, peer_id: this.peerId });
461
524
  this.lastJoinAt = Date.now();
462
525
  this.stats.join_succeeded += 1;
526
+ if (Array.isArray(payload?.peers)) {
527
+ this.updatePeersFromList(payload.peers);
528
+ this.lastPeerRefreshAt = this.lastJoinAt;
529
+ }
463
530
  this.recordDiscovery("join_ok", { endpoint: this.activeEndpoint, detail: reason });
464
531
  }
465
532
 
@@ -467,6 +534,7 @@ export class RelayPreviewAdapter implements NetworkAdapter {
467
534
  if (!this.lastJoinAt || Date.now() - this.lastJoinAt > Math.max(45_000, this.pollIntervalMs * 6)) {
468
535
  await this.joinRoom(reason);
469
536
  }
537
+ this.ensurePollingAlive(reason);
470
538
  }
471
539
 
472
540
  private async get(path: string): Promise<any> {
@@ -527,13 +595,38 @@ export class RelayPreviewAdapter implements NetworkAdapter {
527
595
  }
528
596
 
529
597
  private updatePeersFromList(values: unknown[]): void {
530
- const peerIds = values.map((value) => String(value || "").trim()).filter(Boolean);
598
+ const parsedPeers: Array<{ peer_id: string; meta?: RelayPeer["meta"] }> = [];
599
+ for (const value of values) {
600
+ if (typeof value === "string") {
601
+ const peerId = String(value || "").trim();
602
+ if (peerId) {
603
+ parsedPeers.push({ peer_id: peerId });
604
+ }
605
+ continue;
606
+ }
607
+ if (value && typeof value === "object") {
608
+ const raw = value as Record<string, unknown>;
609
+ const peerId = String(raw.peer_id || "").trim();
610
+ if (!peerId) {
611
+ continue;
612
+ }
613
+ parsedPeers.push({
614
+ peer_id: peerId,
615
+ meta: {
616
+ signal_queue_size: Number(raw.signal_queue_size ?? 0),
617
+ relay_queue_size: Number(raw.relay_queue_size ?? 0),
618
+ },
619
+ });
620
+ }
621
+ }
622
+ const peerIds = parsedPeers.map((peer) => peer.peer_id);
531
623
  if (!peerIds.includes(this.peerId)) {
532
624
  void this.joinRoom("self_missing_from_peers").catch(() => {});
533
625
  }
534
626
  const now = Date.now();
535
627
  const next = new Map<string, RelayPeer>();
536
- for (const peerId of peerIds) {
628
+ for (const peerInfo of parsedPeers) {
629
+ const peerId = peerInfo.peer_id;
537
630
  if (peerId === this.peerId) continue;
538
631
  const existing = this.peers.get(peerId);
539
632
  if (!existing) {
@@ -546,6 +639,7 @@ export class RelayPreviewAdapter implements NetworkAdapter {
546
639
  last_seen_at: now,
547
640
  messages_seen: existing?.messages_seen ?? 0,
548
641
  reconnect_attempts: existing?.reconnect_attempts ?? 0,
642
+ meta: peerInfo.meta || existing?.meta,
549
643
  });
550
644
  }
551
645
  for (const peerId of this.peers.keys()) {
@@ -565,4 +659,20 @@ export class RelayPreviewAdapter implements NetworkAdapter {
565
659
  this.pollOnce().catch(() => {});
566
660
  }, Math.max(1000, delayMs + jitterMs));
567
661
  }
662
+
663
+ private ensurePollingAlive(reason: string): void {
664
+ if (!this.started) return;
665
+ const pollStaleMs = Math.max(45_000, this.pollIntervalMs * 6);
666
+ const pollMissing = !this.poller;
667
+ const pollStale = Boolean(this.lastPollAt) && Date.now() - this.lastPollAt > pollStaleMs;
668
+ if (!pollMissing && !pollStale) {
669
+ return;
670
+ }
671
+ this.recordDiscovery("poll_recover_scheduled", {
672
+ endpoint: this.activeEndpoint,
673
+ detail: `${reason}:${pollMissing ? "missing" : "stale"}`,
674
+ });
675
+ this.currentPollDelayMs = this.pollIntervalMs;
676
+ this.scheduleNextPoll(0);
677
+ }
568
678
  }
@@ -3,4 +3,6 @@ export interface NetworkAdapter {
3
3
  stop(): Promise<void>;
4
4
  publish(topic: string, data: any): Promise<void>;
5
5
  subscribe(topic: string, handler: (data: any) => void): void;
6
+ sendDirect?(peerId: string, topic: string, data: any): Promise<void>;
7
+ subscribeDirect?(topic: string, handler: (data: any, meta?: { peerId?: string }) => void): void;
6
8
  }
@@ -4,6 +4,8 @@ export * from "./identity";
4
4
  export * from "./profile";
5
5
  export * from "./presence";
6
6
  export * from "./socialMessage";
7
+ export * from "./privateCrypto";
8
+ export * from "./privateMessage";
7
9
  export * from "./indexing";
8
10
  export * from "./directory";
9
11
  export * from "./publicProfileSummary";
@@ -20,6 +20,8 @@ __exportStar(require("./identity"), exports);
20
20
  __exportStar(require("./profile"), exports);
21
21
  __exportStar(require("./presence"), exports);
22
22
  __exportStar(require("./socialMessage"), exports);
23
+ __exportStar(require("./privateCrypto"), exports);
24
+ __exportStar(require("./privateMessage"), exports);
23
25
  __exportStar(require("./indexing"), exports);
24
26
  __exportStar(require("./directory"), exports);
25
27
  __exportStar(require("./publicProfileSummary"), exports);
@@ -0,0 +1,17 @@
1
+ import { PrivateEncryptionKeyPair } from "./types";
2
+ export declare function createPrivateEncryptionKeyPair(now?: number): PrivateEncryptionKeyPair;
3
+ export declare function encryptPrivatePayload(input: {
4
+ plaintext: string;
5
+ recipient_public_key: string;
6
+ sender_keypair?: PrivateEncryptionKeyPair | null;
7
+ }): {
8
+ ciphertext: string;
9
+ nonce: string;
10
+ sender_encryption_public_key: string;
11
+ };
12
+ export declare function decryptPrivatePayload(input: {
13
+ ciphertext: string;
14
+ nonce: string;
15
+ sender_encryption_public_key: string;
16
+ recipient_private_key: string;
17
+ }): string | null;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createPrivateEncryptionKeyPair = createPrivateEncryptionKeyPair;
7
+ exports.encryptPrivatePayload = encryptPrivatePayload;
8
+ exports.decryptPrivatePayload = decryptPrivatePayload;
9
+ const tweetnacl_1 = __importDefault(require("tweetnacl"));
10
+ const crypto_1 = require("./crypto");
11
+ function createPrivateEncryptionKeyPair(now = Date.now()) {
12
+ const pair = tweetnacl_1.default.box.keyPair();
13
+ return {
14
+ public_key: (0, crypto_1.toBase64)(pair.publicKey),
15
+ private_key: (0, crypto_1.toBase64)(pair.secretKey),
16
+ created_at: now,
17
+ };
18
+ }
19
+ function encryptPrivatePayload(input) {
20
+ const sender = input.sender_keypair || createPrivateEncryptionKeyPair();
21
+ const nonce = tweetnacl_1.default.randomBytes(tweetnacl_1.default.box.nonceLength);
22
+ const message = Buffer.from(String(input.plaintext || ""), "utf8");
23
+ const ciphertext = tweetnacl_1.default.box(new Uint8Array(message), nonce, (0, crypto_1.fromBase64)(input.recipient_public_key), (0, crypto_1.fromBase64)(sender.private_key));
24
+ return {
25
+ ciphertext: (0, crypto_1.toBase64)(ciphertext),
26
+ nonce: (0, crypto_1.toBase64)(nonce),
27
+ sender_encryption_public_key: sender.public_key,
28
+ };
29
+ }
30
+ function decryptPrivatePayload(input) {
31
+ try {
32
+ const opened = tweetnacl_1.default.box.open((0, crypto_1.fromBase64)(input.ciphertext), (0, crypto_1.fromBase64)(input.nonce), (0, crypto_1.fromBase64)(input.sender_encryption_public_key), (0, crypto_1.fromBase64)(input.recipient_private_key));
33
+ if (!opened)
34
+ return null;
35
+ return Buffer.from(opened).toString("utf8");
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ }
@@ -0,0 +1,23 @@
1
+ import { AgentIdentity, PrivateMessageReceiptRecord, PrivateMessageRecord } from "./types";
2
+ export declare function signPrivateMessage(input: {
3
+ identity: AgentIdentity;
4
+ message_id: string;
5
+ conversation_id: string;
6
+ to_agent_id: string;
7
+ sender_encryption_public_key: string;
8
+ recipient_encryption_public_key: string;
9
+ ciphertext: string;
10
+ nonce: string;
11
+ created_at?: number;
12
+ }): PrivateMessageRecord;
13
+ export declare function verifyPrivateMessage(record: PrivateMessageRecord): boolean;
14
+ export declare function signPrivateMessageReceipt(input: {
15
+ identity: AgentIdentity;
16
+ receipt_id: string;
17
+ message_id: string;
18
+ conversation_id: string;
19
+ to_agent_id: string;
20
+ status: "received" | "read";
21
+ created_at?: number;
22
+ }): PrivateMessageReceiptRecord;
23
+ export declare function verifyPrivateMessageReceipt(record: PrivateMessageReceiptRecord): boolean;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.signPrivateMessage = signPrivateMessage;
4
+ exports.verifyPrivateMessage = verifyPrivateMessage;
5
+ exports.signPrivateMessageReceipt = signPrivateMessageReceipt;
6
+ exports.verifyPrivateMessageReceipt = verifyPrivateMessageReceipt;
7
+ const crypto_1 = require("./crypto");
8
+ function unsignedPrivateMessage(record) {
9
+ const { signature: _signature, ...rest } = record;
10
+ return rest;
11
+ }
12
+ function unsignedPrivateMessageReceipt(record) {
13
+ const { signature: _signature, ...rest } = record;
14
+ return rest;
15
+ }
16
+ function signPrivateMessage(input) {
17
+ const payload = {
18
+ type: "private.message",
19
+ message_id: input.message_id,
20
+ conversation_id: input.conversation_id,
21
+ from_agent_id: input.identity.agent_id,
22
+ to_agent_id: input.to_agent_id,
23
+ sender_public_key: input.identity.public_key,
24
+ sender_encryption_public_key: input.sender_encryption_public_key,
25
+ recipient_encryption_public_key: input.recipient_encryption_public_key,
26
+ cipher_scheme: "nacl-box-v1",
27
+ ciphertext: input.ciphertext,
28
+ nonce: input.nonce,
29
+ created_at: input.created_at ?? Date.now(),
30
+ };
31
+ return {
32
+ ...payload,
33
+ signature: (0, crypto_1.signPayload)(payload, input.identity.private_key),
34
+ };
35
+ }
36
+ function verifyPrivateMessage(record) {
37
+ try {
38
+ if ((0, crypto_1.hashPublicKey)((0, crypto_1.fromBase64)(record.sender_public_key)) !== record.from_agent_id) {
39
+ return false;
40
+ }
41
+ return (0, crypto_1.verifyPayload)(unsignedPrivateMessage(record), record.signature, record.sender_public_key);
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ }
47
+ function signPrivateMessageReceipt(input) {
48
+ const payload = {
49
+ type: "private.message.receipt",
50
+ receipt_id: input.receipt_id,
51
+ message_id: input.message_id,
52
+ conversation_id: input.conversation_id,
53
+ from_agent_id: input.identity.agent_id,
54
+ to_agent_id: input.to_agent_id,
55
+ sender_public_key: input.identity.public_key,
56
+ status: input.status,
57
+ created_at: input.created_at ?? Date.now(),
58
+ };
59
+ return {
60
+ ...payload,
61
+ signature: (0, crypto_1.signPayload)(payload, input.identity.private_key),
62
+ };
63
+ }
64
+ function verifyPrivateMessageReceipt(record) {
65
+ try {
66
+ if ((0, crypto_1.hashPublicKey)((0, crypto_1.fromBase64)(record.sender_public_key)) !== record.from_agent_id) {
67
+ return false;
68
+ }
69
+ return (0, crypto_1.verifyPayload)(unsignedPrivateMessageReceipt(record), record.signature, record.sender_public_key);
70
+ }
71
+ catch {
72
+ return false;
73
+ }
74
+ }
@@ -15,6 +15,7 @@ function signProfile(input, identity) {
15
15
  bio: input.bio,
16
16
  tags: input.tags,
17
17
  avatar_url: input.avatar_url,
18
+ private_encryption_public_key: input.private_encryption_public_key,
18
19
  public_enabled: input.public_enabled,
19
20
  updated_at: Date.now(),
20
21
  };
@@ -34,6 +35,7 @@ function createDefaultProfileInput(agentId) {
34
35
  bio: "",
35
36
  tags: [],
36
37
  avatar_url: "",
38
+ private_encryption_public_key: "",
37
39
  public_enabled: false,
38
40
  };
39
41
  }
@@ -7,9 +7,11 @@ export type ProfileVisibility = {
7
7
  };
8
8
  export type PublicProfileSummary = {
9
9
  agent_id: string;
10
+ is_self: boolean;
10
11
  display_name: string;
11
12
  bio: string;
12
13
  avatar_url?: string;
14
+ private_encryption_public_key?: string;
13
15
  public_enabled: boolean;
14
16
  updated_at: number;
15
17
  online: boolean;
@@ -30,6 +32,7 @@ export type PublicProfileSummary = {
30
32
  display_name: string;
31
33
  bio: string;
32
34
  avatar_url?: string;
35
+ private_encryption_public_key?: string;
33
36
  tags: string[];
34
37
  public_enabled: boolean;
35
38
  profile_version: string;
@@ -57,6 +60,7 @@ export type PublicProfileSummary = {
57
60
  export declare function deriveCapabilitiesSummary(tags: string[]): string[];
58
61
  export declare function buildPublicProfileSummary(args: {
59
62
  profile: PublicProfile;
63
+ is_self?: boolean;
60
64
  online: boolean;
61
65
  last_seen_at: number | null;
62
66
  network_mode?: string;