@silicaclaw/cli 2026.3.20-2 → 2026.3.20-21
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 +108 -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 +139 -3
- package/apps/local-console/dist/apps/local-console/src/server.js +1029 -92
- 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 +27 -1
- package/apps/local-console/dist/packages/storage/src/repos.js +35 -1
- package/apps/local-console/public/app/app.js +502 -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 +57 -27
- package/apps/local-console/public/app/social.js +342 -105
- package/apps/local-console/public/app/styles.css +149 -43
- package/apps/local-console/public/app/template.js +196 -100
- package/apps/local-console/public/app/translations.js +438 -316
- package/apps/local-console/src/server.ts +1177 -90
- 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/package.json +2 -2
- 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 +27 -1
- package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.js +35 -1
- package/node_modules/@silicaclaw/storage/package.json +2 -2
- package/node_modules/@silicaclaw/storage/src/repos.ts +59 -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/package.json +2 -2
- 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 +27 -1
- package/packages/storage/dist/packages/storage/src/repos.js +35 -1
- package/packages/storage/package.json +2 -2
- package/packages/storage/src/repos.ts +59 -1
- package/scripts/silicaclaw-cli.mjs +4 -1
- package/scripts/silicaclaw-gateway.mjs +114 -2
- package/scripts/validate-openclaw-skill.mjs +19 -0
- package/node_modules/@silicaclaw/storage/dist/index.d.ts +0 -3
- package/node_modules/@silicaclaw/storage/dist/index.js +0 -19
- package/node_modules/@silicaclaw/storage/dist/jsonRepo.d.ts +0 -7
- package/node_modules/@silicaclaw/storage/dist/jsonRepo.js +0 -29
- package/node_modules/@silicaclaw/storage/dist/repos.d.ts +0 -61
- package/node_modules/@silicaclaw/storage/dist/repos.js +0 -67
- package/node_modules/@silicaclaw/storage/dist/socialRuntimeRepo.d.ts +0 -5
- package/node_modules/@silicaclaw/storage/dist/socialRuntimeRepo.js +0 -57
- package/packages/storage/dist/index.d.ts +0 -3
- package/packages/storage/dist/index.js +0 -19
- package/packages/storage/dist/jsonRepo.d.ts +0 -7
- package/packages/storage/dist/jsonRepo.js +0 -29
- package/packages/storage/dist/repos.d.ts +0 -61
- package/packages/storage/dist/repos.js +0 -67
- package/packages/storage/dist/socialRuntimeRepo.d.ts +0 -5
- package/packages/storage/dist/socialRuntimeRepo.js +0 -57
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import nacl from "tweetnacl";
|
|
2
|
+
import { fromBase64, toBase64 } from "./crypto";
|
|
3
|
+
import { PrivateEncryptionKeyPair } from "./types";
|
|
4
|
+
|
|
5
|
+
export function createPrivateEncryptionKeyPair(now = Date.now()): PrivateEncryptionKeyPair {
|
|
6
|
+
const pair = nacl.box.keyPair();
|
|
7
|
+
return {
|
|
8
|
+
public_key: toBase64(pair.publicKey),
|
|
9
|
+
private_key: toBase64(pair.secretKey),
|
|
10
|
+
created_at: now,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function encryptPrivatePayload(input: {
|
|
15
|
+
plaintext: string;
|
|
16
|
+
recipient_public_key: string;
|
|
17
|
+
sender_keypair?: PrivateEncryptionKeyPair | null;
|
|
18
|
+
}): {
|
|
19
|
+
ciphertext: string;
|
|
20
|
+
nonce: string;
|
|
21
|
+
sender_encryption_public_key: string;
|
|
22
|
+
} {
|
|
23
|
+
const sender = input.sender_keypair || createPrivateEncryptionKeyPair();
|
|
24
|
+
const nonce = nacl.randomBytes(nacl.box.nonceLength);
|
|
25
|
+
const message = Buffer.from(String(input.plaintext || ""), "utf8");
|
|
26
|
+
const ciphertext = nacl.box(
|
|
27
|
+
new Uint8Array(message),
|
|
28
|
+
nonce,
|
|
29
|
+
fromBase64(input.recipient_public_key),
|
|
30
|
+
fromBase64(sender.private_key),
|
|
31
|
+
);
|
|
32
|
+
return {
|
|
33
|
+
ciphertext: toBase64(ciphertext),
|
|
34
|
+
nonce: toBase64(nonce),
|
|
35
|
+
sender_encryption_public_key: sender.public_key,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function decryptPrivatePayload(input: {
|
|
40
|
+
ciphertext: string;
|
|
41
|
+
nonce: string;
|
|
42
|
+
sender_encryption_public_key: string;
|
|
43
|
+
recipient_private_key: string;
|
|
44
|
+
}): string | null {
|
|
45
|
+
try {
|
|
46
|
+
const opened = nacl.box.open(
|
|
47
|
+
fromBase64(input.ciphertext),
|
|
48
|
+
fromBase64(input.nonce),
|
|
49
|
+
fromBase64(input.sender_encryption_public_key),
|
|
50
|
+
fromBase64(input.recipient_private_key),
|
|
51
|
+
);
|
|
52
|
+
if (!opened) return null;
|
|
53
|
+
return Buffer.from(opened).toString("utf8");
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AgentIdentity,
|
|
3
|
+
PrivateMessageReceiptRecord,
|
|
4
|
+
PrivateMessageRecord,
|
|
5
|
+
} from "./types";
|
|
6
|
+
import { fromBase64, hashPublicKey, signPayload, verifyPayload } from "./crypto";
|
|
7
|
+
|
|
8
|
+
function unsignedPrivateMessage(
|
|
9
|
+
record: PrivateMessageRecord
|
|
10
|
+
): Omit<PrivateMessageRecord, "signature"> {
|
|
11
|
+
const { signature: _signature, ...rest } = record;
|
|
12
|
+
return rest;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function unsignedPrivateMessageReceipt(
|
|
16
|
+
record: PrivateMessageReceiptRecord
|
|
17
|
+
): Omit<PrivateMessageReceiptRecord, "signature"> {
|
|
18
|
+
const { signature: _signature, ...rest } = record;
|
|
19
|
+
return rest;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function signPrivateMessage(input: {
|
|
23
|
+
identity: AgentIdentity;
|
|
24
|
+
message_id: string;
|
|
25
|
+
conversation_id: string;
|
|
26
|
+
to_agent_id: string;
|
|
27
|
+
sender_encryption_public_key: string;
|
|
28
|
+
recipient_encryption_public_key: string;
|
|
29
|
+
ciphertext: string;
|
|
30
|
+
nonce: string;
|
|
31
|
+
created_at?: number;
|
|
32
|
+
}): PrivateMessageRecord {
|
|
33
|
+
const payload: Omit<PrivateMessageRecord, "signature"> = {
|
|
34
|
+
type: "private.message",
|
|
35
|
+
message_id: input.message_id,
|
|
36
|
+
conversation_id: input.conversation_id,
|
|
37
|
+
from_agent_id: input.identity.agent_id,
|
|
38
|
+
to_agent_id: input.to_agent_id,
|
|
39
|
+
sender_public_key: input.identity.public_key,
|
|
40
|
+
sender_encryption_public_key: input.sender_encryption_public_key,
|
|
41
|
+
recipient_encryption_public_key: input.recipient_encryption_public_key,
|
|
42
|
+
cipher_scheme: "nacl-box-v1",
|
|
43
|
+
ciphertext: input.ciphertext,
|
|
44
|
+
nonce: input.nonce,
|
|
45
|
+
created_at: input.created_at ?? Date.now(),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
...payload,
|
|
50
|
+
signature: signPayload(payload, input.identity.private_key),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function verifyPrivateMessage(record: PrivateMessageRecord): boolean {
|
|
55
|
+
try {
|
|
56
|
+
if (hashPublicKey(fromBase64(record.sender_public_key)) !== record.from_agent_id) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return verifyPayload(unsignedPrivateMessage(record), record.signature, record.sender_public_key);
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function signPrivateMessageReceipt(input: {
|
|
66
|
+
identity: AgentIdentity;
|
|
67
|
+
receipt_id: string;
|
|
68
|
+
message_id: string;
|
|
69
|
+
conversation_id: string;
|
|
70
|
+
to_agent_id: string;
|
|
71
|
+
status: "received" | "read";
|
|
72
|
+
created_at?: number;
|
|
73
|
+
}): PrivateMessageReceiptRecord {
|
|
74
|
+
const payload: Omit<PrivateMessageReceiptRecord, "signature"> = {
|
|
75
|
+
type: "private.message.receipt",
|
|
76
|
+
receipt_id: input.receipt_id,
|
|
77
|
+
message_id: input.message_id,
|
|
78
|
+
conversation_id: input.conversation_id,
|
|
79
|
+
from_agent_id: input.identity.agent_id,
|
|
80
|
+
to_agent_id: input.to_agent_id,
|
|
81
|
+
sender_public_key: input.identity.public_key,
|
|
82
|
+
status: input.status,
|
|
83
|
+
created_at: input.created_at ?? Date.now(),
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
...payload,
|
|
88
|
+
signature: signPayload(payload, input.identity.private_key),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function verifyPrivateMessageReceipt(record: PrivateMessageReceiptRecord): boolean {
|
|
93
|
+
try {
|
|
94
|
+
if (hashPublicKey(fromBase64(record.sender_public_key)) !== record.from_agent_id) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
return verifyPayload(unsignedPrivateMessageReceipt(record), record.signature, record.sender_public_key);
|
|
98
|
+
} catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -13,6 +13,7 @@ export function signProfile(input: ProfileInput, identity: AgentIdentity): Publi
|
|
|
13
13
|
bio: input.bio,
|
|
14
14
|
tags: input.tags,
|
|
15
15
|
avatar_url: input.avatar_url,
|
|
16
|
+
private_encryption_public_key: input.private_encryption_public_key,
|
|
16
17
|
public_enabled: input.public_enabled,
|
|
17
18
|
updated_at: Date.now(),
|
|
18
19
|
};
|
|
@@ -34,6 +35,7 @@ export function createDefaultProfileInput(agentId: string): ProfileInput {
|
|
|
34
35
|
bio: "",
|
|
35
36
|
tags: [],
|
|
36
37
|
avatar_url: "",
|
|
38
|
+
private_encryption_public_key: "",
|
|
37
39
|
public_enabled: false,
|
|
38
40
|
};
|
|
39
41
|
}
|
|
@@ -10,9 +10,11 @@ export type ProfileVisibility = {
|
|
|
10
10
|
|
|
11
11
|
export type PublicProfileSummary = {
|
|
12
12
|
agent_id: string;
|
|
13
|
+
is_self: boolean;
|
|
13
14
|
display_name: string;
|
|
14
15
|
bio: string;
|
|
15
16
|
avatar_url?: string;
|
|
17
|
+
private_encryption_public_key?: string;
|
|
16
18
|
public_enabled: boolean;
|
|
17
19
|
updated_at: number;
|
|
18
20
|
online: boolean;
|
|
@@ -33,6 +35,7 @@ export type PublicProfileSummary = {
|
|
|
33
35
|
display_name: string;
|
|
34
36
|
bio: string;
|
|
35
37
|
avatar_url?: string;
|
|
38
|
+
private_encryption_public_key?: string;
|
|
36
39
|
tags: string[];
|
|
37
40
|
public_enabled: boolean;
|
|
38
41
|
profile_version: string;
|
|
@@ -73,6 +76,7 @@ export function deriveCapabilitiesSummary(tags: string[]): string[] {
|
|
|
73
76
|
|
|
74
77
|
export function buildPublicProfileSummary(args: {
|
|
75
78
|
profile: PublicProfile;
|
|
79
|
+
is_self?: boolean;
|
|
76
80
|
online: boolean;
|
|
77
81
|
last_seen_at: number | null;
|
|
78
82
|
network_mode?: string;
|
|
@@ -130,9 +134,11 @@ export function buildPublicProfileSummary(args: {
|
|
|
130
134
|
|
|
131
135
|
return {
|
|
132
136
|
agent_id: args.profile.agent_id,
|
|
137
|
+
is_self: Boolean(args.is_self),
|
|
133
138
|
display_name: args.profile.display_name,
|
|
134
139
|
bio: args.profile.bio,
|
|
135
140
|
avatar_url: args.profile.avatar_url,
|
|
141
|
+
private_encryption_public_key: args.profile.private_encryption_public_key,
|
|
136
142
|
public_enabled: args.profile.public_enabled,
|
|
137
143
|
updated_at: args.profile.updated_at,
|
|
138
144
|
online: args.online,
|
|
@@ -153,6 +159,7 @@ export function buildPublicProfileSummary(args: {
|
|
|
153
159
|
display_name: args.profile.display_name,
|
|
154
160
|
bio: args.profile.bio,
|
|
155
161
|
avatar_url: args.profile.avatar_url,
|
|
162
|
+
private_encryption_public_key: args.profile.private_encryption_public_key,
|
|
156
163
|
tags,
|
|
157
164
|
public_enabled: args.profile.public_enabled,
|
|
158
165
|
profile_version: args.profile_version ?? "v1",
|
|
@@ -5,12 +5,19 @@ export type AgentIdentity = {
|
|
|
5
5
|
created_at: number;
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
+
export type PrivateEncryptionKeyPair = {
|
|
9
|
+
public_key: string;
|
|
10
|
+
private_key: string;
|
|
11
|
+
created_at: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
8
14
|
export type PublicProfile = {
|
|
9
15
|
agent_id: string;
|
|
10
16
|
display_name: string;
|
|
11
17
|
bio: string;
|
|
12
18
|
tags: string[];
|
|
13
19
|
avatar_url?: string;
|
|
20
|
+
private_encryption_public_key?: string;
|
|
14
21
|
public_enabled: boolean;
|
|
15
22
|
updated_at: number;
|
|
16
23
|
signature: string;
|
|
@@ -58,6 +65,43 @@ export type SocialMessageObservationRecord = {
|
|
|
58
65
|
signature: string;
|
|
59
66
|
};
|
|
60
67
|
|
|
68
|
+
export type PrivateMessageRecord = {
|
|
69
|
+
type: "private.message";
|
|
70
|
+
message_id: string;
|
|
71
|
+
conversation_id: string;
|
|
72
|
+
from_agent_id: string;
|
|
73
|
+
to_agent_id: string;
|
|
74
|
+
sender_public_key: string;
|
|
75
|
+
sender_encryption_public_key: string;
|
|
76
|
+
recipient_encryption_public_key: string;
|
|
77
|
+
cipher_scheme: "nacl-box-v1";
|
|
78
|
+
ciphertext: string;
|
|
79
|
+
nonce: string;
|
|
80
|
+
created_at: number;
|
|
81
|
+
signature: string;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export type PrivateMessageReceiptRecord = {
|
|
85
|
+
type: "private.message.receipt";
|
|
86
|
+
receipt_id: string;
|
|
87
|
+
message_id: string;
|
|
88
|
+
conversation_id: string;
|
|
89
|
+
from_agent_id: string;
|
|
90
|
+
to_agent_id: string;
|
|
91
|
+
sender_public_key: string;
|
|
92
|
+
status: "received" | "read";
|
|
93
|
+
created_at: number;
|
|
94
|
+
signature: string;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export type PrivateConversationSummary = {
|
|
98
|
+
conversation_id: string;
|
|
99
|
+
peer_agent_id: string;
|
|
100
|
+
last_message_at: number | null;
|
|
101
|
+
last_message_preview: string;
|
|
102
|
+
unread_count: number;
|
|
103
|
+
};
|
|
104
|
+
|
|
61
105
|
export type DirectoryState = {
|
|
62
106
|
profiles: Record<string, PublicProfile>;
|
|
63
107
|
presence: Record<string, number>;
|
|
@@ -22,6 +22,10 @@ type RelayPeer = {
|
|
|
22
22
|
last_seen_at: number;
|
|
23
23
|
messages_seen: number;
|
|
24
24
|
reconnect_attempts: number;
|
|
25
|
+
meta?: {
|
|
26
|
+
signal_queue_size?: number;
|
|
27
|
+
relay_queue_size?: number;
|
|
28
|
+
};
|
|
25
29
|
};
|
|
26
30
|
type RelayDiagnostics = {
|
|
27
31
|
adapter: "relay-preview";
|
|
@@ -126,6 +130,7 @@ export declare class RelayPreviewAdapter implements NetworkAdapter {
|
|
|
126
130
|
private started;
|
|
127
131
|
private poller;
|
|
128
132
|
private handlers;
|
|
133
|
+
private directHandlers;
|
|
129
134
|
private peers;
|
|
130
135
|
private seenMessageIds;
|
|
131
136
|
private activeEndpoint;
|
|
@@ -150,10 +155,16 @@ export declare class RelayPreviewAdapter implements NetworkAdapter {
|
|
|
150
155
|
stop(): Promise<void>;
|
|
151
156
|
publish(topic: string, data: any): Promise<void>;
|
|
152
157
|
subscribe(topic: string, handler: (data: any) => void): void;
|
|
158
|
+
sendDirect(peerId: string, topic: string, data: any): Promise<void>;
|
|
159
|
+
subscribeDirect(topic: string, handler: (data: any, meta?: {
|
|
160
|
+
peerId?: string;
|
|
161
|
+
}) => void): void;
|
|
153
162
|
getDiagnostics(): RelayDiagnostics;
|
|
154
163
|
private pollOnce;
|
|
155
164
|
private refreshPeers;
|
|
156
165
|
private onEnvelope;
|
|
166
|
+
private onDirectEnvelope;
|
|
167
|
+
private dispatchEnvelope;
|
|
157
168
|
private recordDiscovery;
|
|
158
169
|
private joinRoom;
|
|
159
170
|
private maybeRefreshJoin;
|
|
@@ -162,5 +173,6 @@ export declare class RelayPreviewAdapter implements NetworkAdapter {
|
|
|
162
173
|
private requestJson;
|
|
163
174
|
private updatePeersFromList;
|
|
164
175
|
private scheduleNextPoll;
|
|
176
|
+
private ensurePollingAlive;
|
|
165
177
|
}
|
|
166
178
|
export {};
|
|
@@ -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
|
}
|