@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.
- package/CHANGELOG.md +54 -0
- package/INSTALL.md +13 -7
- package/README.md +60 -12
- package/VERSION +1 -1
- package/apps/local-console/dist/apps/local-console/src/server.d.ts +129 -2
- package/apps/local-console/dist/apps/local-console/src/server.js +887 -91
- package/apps/local-console/dist/packages/core/src/index.d.ts +2 -0
- package/apps/local-console/dist/packages/core/src/index.js +2 -0
- package/apps/local-console/dist/packages/core/src/privateCrypto.d.ts +17 -0
- package/apps/local-console/dist/packages/core/src/privateCrypto.js +40 -0
- package/apps/local-console/dist/packages/core/src/privateMessage.d.ts +23 -0
- package/apps/local-console/dist/packages/core/src/privateMessage.js +74 -0
- package/apps/local-console/dist/packages/core/src/profile.js +2 -0
- package/apps/local-console/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
- package/apps/local-console/dist/packages/core/src/publicProfileSummary.js +3 -0
- package/apps/local-console/dist/packages/core/src/types.d.ts +40 -0
- package/apps/local-console/dist/packages/network/src/relayPreview.d.ts +12 -0
- package/apps/local-console/dist/packages/network/src/relayPreview.js +108 -8
- package/apps/local-console/dist/packages/network/src/types.d.ts +4 -0
- package/apps/local-console/dist/packages/storage/src/repos.d.ts +13 -1
- package/apps/local-console/dist/packages/storage/src/repos.js +19 -1
- package/apps/local-console/public/app/app.js +465 -11
- package/apps/local-console/public/app/events.js +21 -0
- package/apps/local-console/public/app/network.js +144 -32
- package/apps/local-console/public/app/overview.js +60 -52
- package/apps/local-console/public/app/social.js +316 -93
- package/apps/local-console/public/app/styles.css +127 -1
- package/apps/local-console/public/app/template.js +121 -35
- package/apps/local-console/public/app/translations.js +430 -316
- package/apps/local-console/src/server.ts +1024 -89
- package/apps/public-explorer/public/app/template.js +2 -2
- package/apps/public-explorer/public/app/translations.js +36 -36
- package/docs/NEW_USER_OPERATIONS.md +5 -5
- package/docs/OPENCLAW_BRIDGE.md +7 -7
- package/docs/OPENCLAW_BRIDGE_ZH.md +6 -6
- package/node_modules/@silicaclaw/core/dist/packages/core/src/index.d.ts +2 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/index.js +2 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/privateCrypto.d.ts +17 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/privateCrypto.js +40 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/privateMessage.d.ts +23 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/privateMessage.js +74 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/profile.js +2 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/publicProfileSummary.js +3 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/types.d.ts +40 -0
- package/node_modules/@silicaclaw/core/src/index.ts +2 -0
- package/node_modules/@silicaclaw/core/src/privateCrypto.ts +57 -0
- package/node_modules/@silicaclaw/core/src/privateMessage.ts +101 -0
- package/node_modules/@silicaclaw/core/src/profile.ts +2 -0
- package/node_modules/@silicaclaw/core/src/publicProfileSummary.ts +7 -0
- package/node_modules/@silicaclaw/core/src/types.ts +44 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.d.ts +12 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.js +108 -8
- package/node_modules/@silicaclaw/network/dist/packages/network/src/types.d.ts +4 -0
- package/node_modules/@silicaclaw/network/src/relayPreview.ts +120 -10
- package/node_modules/@silicaclaw/network/src/types.ts +2 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/index.d.ts +2 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/index.js +2 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateCrypto.d.ts +17 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateCrypto.js +40 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateMessage.d.ts +23 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateMessage.js +74 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/profile.js +2 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/publicProfileSummary.js +3 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/types.d.ts +40 -0
- package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.d.ts +13 -1
- package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.js +19 -1
- package/node_modules/@silicaclaw/storage/src/repos.ts +31 -1
- package/openclaw-skills/silicaclaw-bridge-setup/SKILL.md +18 -0
- package/openclaw-skills/silicaclaw-bridge-setup/VERSION +1 -1
- package/openclaw-skills/silicaclaw-bridge-setup/manifest.json +2 -2
- package/openclaw-skills/silicaclaw-broadcast/SKILL.md +18 -0
- package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -1
- package/openclaw-skills/silicaclaw-broadcast/manifest.json +2 -2
- package/openclaw-skills/silicaclaw-network-config/SKILL.md +158 -0
- package/openclaw-skills/silicaclaw-network-config/VERSION +1 -0
- package/openclaw-skills/silicaclaw-network-config/agents/openai.yaml +6 -0
- package/openclaw-skills/silicaclaw-network-config/manifest.json +27 -0
- package/openclaw-skills/silicaclaw-network-config/references/network-modes.md +22 -0
- package/openclaw-skills/silicaclaw-network-config/references/owner-dialogue-cheatsheet-zh.md +47 -0
- package/openclaw-skills/silicaclaw-network-config/references/public-discovery.md +22 -0
- package/openclaw-skills/silicaclaw-owner-push/SKILL.md +18 -0
- package/openclaw-skills/silicaclaw-owner-push/VERSION +1 -1
- package/openclaw-skills/silicaclaw-owner-push/manifest.json +2 -2
- package/openclaw-skills/silicaclaw-owner-push/references/runtime-setup.md +3 -0
- package/openclaw-skills/silicaclaw-owner-push/scripts/owner-push-forwarder.mjs +151 -9
- package/package.json +1 -1
- package/packages/core/dist/packages/core/src/index.d.ts +2 -0
- package/packages/core/dist/packages/core/src/index.js +2 -0
- package/packages/core/dist/packages/core/src/privateCrypto.d.ts +17 -0
- package/packages/core/dist/packages/core/src/privateCrypto.js +40 -0
- package/packages/core/dist/packages/core/src/privateMessage.d.ts +23 -0
- package/packages/core/dist/packages/core/src/privateMessage.js +74 -0
- package/packages/core/dist/packages/core/src/profile.js +2 -0
- package/packages/core/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
- package/packages/core/dist/packages/core/src/publicProfileSummary.js +3 -0
- package/packages/core/dist/packages/core/src/types.d.ts +40 -0
- package/packages/core/src/index.ts +2 -0
- package/packages/core/src/privateCrypto.ts +57 -0
- package/packages/core/src/privateMessage.ts +101 -0
- package/packages/core/src/profile.ts +2 -0
- package/packages/core/src/publicProfileSummary.ts +7 -0
- package/packages/core/src/types.ts +44 -0
- package/packages/network/dist/packages/network/src/relayPreview.d.ts +12 -0
- package/packages/network/dist/packages/network/src/relayPreview.js +108 -8
- package/packages/network/dist/packages/network/src/types.d.ts +4 -0
- package/packages/network/src/relayPreview.ts +120 -10
- package/packages/network/src/types.ts +2 -0
- package/packages/storage/dist/packages/core/src/index.d.ts +2 -0
- package/packages/storage/dist/packages/core/src/index.js +2 -0
- package/packages/storage/dist/packages/core/src/privateCrypto.d.ts +17 -0
- package/packages/storage/dist/packages/core/src/privateCrypto.js +40 -0
- package/packages/storage/dist/packages/core/src/privateMessage.d.ts +23 -0
- package/packages/storage/dist/packages/core/src/privateMessage.js +74 -0
- package/packages/storage/dist/packages/core/src/profile.js +2 -0
- package/packages/storage/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
- package/packages/storage/dist/packages/core/src/publicProfileSummary.js +3 -0
- package/packages/storage/dist/packages/core/src/types.d.ts +40 -0
- package/packages/storage/dist/packages/storage/src/repos.d.ts +13 -1
- package/packages/storage/dist/packages/storage/src/repos.js +19 -1
- package/packages/storage/src/repos.ts +31 -1
- package/scripts/silicaclaw-cli.mjs +59 -6
- package/scripts/silicaclaw-gateway.mjs +108 -0
- 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
|
|
262
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
379
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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;
|