@silicaclaw/cli 2026.3.18-4 → 2026.3.19-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +15 -0
- package/CHANGELOG.md +17 -2
- package/INSTALL.md +35 -0
- package/README.md +119 -10
- package/RELEASE_NOTES_v1.0.md +29 -2
- package/SOCIAL_MD_SPEC.md +2 -0
- package/VERSION +1 -1
- package/apps/local-console/public/index.html +2297 -231
- package/apps/local-console/src/server.ts +1120 -24
- package/apps/local-console/src/socialRoutes.ts +21 -0
- package/apps/public-explorer/public/index.html +190 -43
- package/docs/NEW_USER_OPERATIONS.md +35 -5
- package/docs/OPENCLAW_BRIDGE.md +449 -0
- package/docs/OPENCLAW_BRIDGE_ZH.md +445 -0
- package/docs/QUICK_START.md +20 -1
- package/docs/release/ANNOUNCEMENT_v1.0-beta.md +68 -0
- package/docs/release/FINAL_RELEASE_SUMMARY_v1.0-beta.md +112 -0
- package/docs/release/GITHUB_RELEASE_v1.0-beta.md +16 -16
- package/docs/release/RELEASE_COPY_v1.0-beta.md +102 -0
- package/openclaw-skills/silicaclaw-broadcast/SKILL.md +89 -0
- package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -0
- package/openclaw-skills/silicaclaw-broadcast/agents/openai.yaml +6 -0
- package/openclaw-skills/silicaclaw-broadcast/manifest.json +34 -0
- package/openclaw-skills/silicaclaw-broadcast/references/computer-control-via-openclaw.md +41 -0
- package/openclaw-skills/silicaclaw-broadcast/references/owner-dispatch-adapter.md +81 -0
- package/openclaw-skills/silicaclaw-broadcast/references/owner-forwarding-policy.md +48 -0
- package/openclaw-skills/silicaclaw-broadcast/scripts/bridge-client.mjs +59 -0
- package/openclaw-skills/silicaclaw-broadcast/scripts/owner-dispatch-adapter-demo.mjs +12 -0
- package/openclaw-skills/silicaclaw-broadcast/scripts/owner-forwarder-demo.mjs +111 -0
- package/openclaw-skills/silicaclaw-broadcast/scripts/send-to-owner-via-openclaw.mjs +69 -0
- package/openclaw.social.md.example +6 -0
- package/package.json +2 -1
- package/packages/core/dist/index.d.ts +1 -0
- package/packages/core/dist/index.js +1 -0
- package/packages/core/dist/socialConfig.d.ts +1 -0
- package/packages/core/dist/socialConfig.js +9 -1
- package/packages/core/dist/socialMessage.d.ts +19 -0
- package/packages/core/dist/socialMessage.js +69 -0
- package/packages/core/dist/socialTemplate.js +3 -1
- package/packages/core/dist/types.d.ts +22 -0
- package/packages/core/src/index.ts +1 -0
- package/packages/core/src/socialConfig.ts +13 -1
- package/packages/core/src/socialMessage.ts +86 -0
- package/packages/core/src/socialTemplate.ts +3 -1
- package/packages/core/src/types.ts +24 -0
- package/packages/network/dist/relayPreview.js +16 -4
- package/packages/network/src/relayPreview.ts +17 -4
- package/packages/storage/dist/repos.d.ts +40 -0
- package/packages/storage/dist/repos.js +27 -1
- package/packages/storage/dist/socialRuntimeRepo.js +1 -0
- package/packages/storage/src/repos.ts +60 -0
- package/packages/storage/src/socialRuntimeRepo.ts +1 -0
- package/packages/storage/tsconfig.json +1 -1
- package/scripts/functional-check.mjs +85 -2
- package/scripts/install-openclaw-skill.mjs +54 -0
- package/scripts/openclaw-bridge-adapter.mjs +89 -0
- package/scripts/openclaw-bridge-client.mjs +223 -0
- package/scripts/openclaw-runtime-demo.mjs +202 -0
- package/scripts/pack-openclaw-skill.mjs +58 -0
- package/scripts/silicaclaw-cli.mjs +30 -0
- package/scripts/silicaclaw-gateway.mjs +215 -0
- package/scripts/validate-openclaw-skill.mjs +74 -0
- package/social.md.example +6 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { resolve } from "node:path";
|
|
6
|
+
|
|
7
|
+
function requiredEnv(name) {
|
|
8
|
+
const value = String(process.env[name] || "").trim();
|
|
9
|
+
if (!value) {
|
|
10
|
+
throw new Error(`Missing required environment variable: ${name}`);
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function resolveOpenClawCommand() {
|
|
16
|
+
const explicitBin = String(process.env.OPENCLAW_BIN || "").trim();
|
|
17
|
+
if (explicitBin) {
|
|
18
|
+
return { cmd: explicitBin, args: [] };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const sourceDir = String(process.env.OPENCLAW_SOURCE_DIR || "").trim();
|
|
22
|
+
if (sourceDir) {
|
|
23
|
+
return {
|
|
24
|
+
cmd: "node",
|
|
25
|
+
args: [resolve(sourceDir, "openclaw.mjs")],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { cmd: "openclaw", args: [] };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function main() {
|
|
33
|
+
const payload = JSON.parse(readFileSync(0, "utf8"));
|
|
34
|
+
const channel = requiredEnv("OPENCLAW_OWNER_CHANNEL");
|
|
35
|
+
const target = requiredEnv("OPENCLAW_OWNER_TARGET");
|
|
36
|
+
const account = String(process.env.OPENCLAW_OWNER_ACCOUNT || "").trim();
|
|
37
|
+
const message = String(payload?.summary || "").trim();
|
|
38
|
+
if (!message) {
|
|
39
|
+
throw new Error("Missing summary in stdin payload");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const command = resolveOpenClawCommand();
|
|
43
|
+
const args = [
|
|
44
|
+
...command.args,
|
|
45
|
+
"message",
|
|
46
|
+
"send",
|
|
47
|
+
"--channel",
|
|
48
|
+
channel,
|
|
49
|
+
"--target",
|
|
50
|
+
target,
|
|
51
|
+
"--message",
|
|
52
|
+
message,
|
|
53
|
+
];
|
|
54
|
+
if (account) {
|
|
55
|
+
args.push("--account", account);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const result = spawnSync(command.cmd, args, {
|
|
59
|
+
stdio: "inherit",
|
|
60
|
+
env: process.env,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (result.error) {
|
|
64
|
+
throw result.error;
|
|
65
|
+
}
|
|
66
|
+
process.exit(result.status ?? 1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
main();
|
|
@@ -12,6 +12,12 @@ identity:
|
|
|
12
12
|
network:
|
|
13
13
|
mode: "global-preview"
|
|
14
14
|
|
|
15
|
+
discovery:
|
|
16
|
+
discoverable: true
|
|
17
|
+
allow_profile_broadcast: true
|
|
18
|
+
allow_presence_broadcast: true
|
|
19
|
+
allow_message_broadcast: true
|
|
20
|
+
|
|
15
21
|
openclaw:
|
|
16
22
|
bind_existing_identity: true
|
|
17
23
|
use_openclaw_profile_if_available: true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@silicaclaw/cli",
|
|
3
|
-
"version": "2026.3.
|
|
3
|
+
"version": "2026.3.19-2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"packages/storage/src/**",
|
|
28
28
|
"packages/storage/tsconfig.json",
|
|
29
29
|
"scripts/",
|
|
30
|
+
"openclaw-skills/",
|
|
30
31
|
"docs/",
|
|
31
32
|
"social.md.example",
|
|
32
33
|
"openclaw.social.md.example",
|
|
@@ -19,6 +19,7 @@ __exportStar(require("./crypto"), exports);
|
|
|
19
19
|
__exportStar(require("./identity"), exports);
|
|
20
20
|
__exportStar(require("./profile"), exports);
|
|
21
21
|
__exportStar(require("./presence"), exports);
|
|
22
|
+
__exportStar(require("./socialMessage"), exports);
|
|
22
23
|
__exportStar(require("./indexing"), exports);
|
|
23
24
|
__exportStar(require("./directory"), exports);
|
|
24
25
|
__exportStar(require("./publicProfileSummary"), exports);
|
|
@@ -29,6 +29,7 @@ const DEFAULT_SOCIAL_CONFIG = {
|
|
|
29
29
|
discoverable: true,
|
|
30
30
|
allow_profile_broadcast: true,
|
|
31
31
|
allow_presence_broadcast: true,
|
|
32
|
+
allow_message_broadcast: true,
|
|
32
33
|
},
|
|
33
34
|
visibility: {
|
|
34
35
|
show_display_name: true,
|
|
@@ -239,6 +240,7 @@ function normalizeSocialConfig(input) {
|
|
|
239
240
|
discoverable: asBool(discovery.discoverable, DEFAULT_SOCIAL_CONFIG.discovery.discoverable),
|
|
240
241
|
allow_profile_broadcast: asBool(discovery.allow_profile_broadcast, DEFAULT_SOCIAL_CONFIG.discovery.allow_profile_broadcast),
|
|
241
242
|
allow_presence_broadcast: asBool(discovery.allow_presence_broadcast, DEFAULT_SOCIAL_CONFIG.discovery.allow_presence_broadcast),
|
|
243
|
+
allow_message_broadcast: asBool(discovery.allow_message_broadcast, DEFAULT_SOCIAL_CONFIG.discovery.allow_message_broadcast),
|
|
242
244
|
},
|
|
243
245
|
visibility: {
|
|
244
246
|
show_display_name: asBool(visibility.show_display_name, DEFAULT_SOCIAL_CONFIG.visibility.show_display_name),
|
|
@@ -261,7 +263,7 @@ function generateDefaultSocialMdTemplate(options = {}) {
|
|
|
261
263
|
const displayName = options.display_name?.trim() || "My OpenClaw Agent";
|
|
262
264
|
const bio = options.bio?.trim() || "Local AI agent running on this machine";
|
|
263
265
|
const tags = Array.isArray(options.tags) && options.tags.length > 0 ? options.tags : ["openclaw", "local-first"];
|
|
264
|
-
const mode = options.mode ?? "
|
|
266
|
+
const mode = options.mode ?? "global-preview";
|
|
265
267
|
const publicEnabled = typeof options.public_enabled === "boolean" ? options.public_enabled : false;
|
|
266
268
|
return `---
|
|
267
269
|
enabled: true
|
|
@@ -276,6 +278,12 @@ ${tags.map((tag) => ` - ${tag}`).join("\n")}
|
|
|
276
278
|
network:
|
|
277
279
|
mode: ${JSON.stringify(mode)}
|
|
278
280
|
|
|
281
|
+
discovery:
|
|
282
|
+
discoverable: true
|
|
283
|
+
allow_profile_broadcast: true
|
|
284
|
+
allow_presence_broadcast: true
|
|
285
|
+
allow_message_broadcast: true
|
|
286
|
+
|
|
279
287
|
openclaw:
|
|
280
288
|
bind_existing_identity: true
|
|
281
289
|
use_openclaw_profile_if_available: true
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { AgentIdentity, SocialMessageObservationRecord, SocialMessageRecord } from "./types";
|
|
2
|
+
export declare function signSocialMessage(input: {
|
|
3
|
+
identity: AgentIdentity;
|
|
4
|
+
message_id: string;
|
|
5
|
+
display_name: string;
|
|
6
|
+
topic: string;
|
|
7
|
+
body: string;
|
|
8
|
+
created_at?: number;
|
|
9
|
+
}): SocialMessageRecord;
|
|
10
|
+
export declare function verifySocialMessage(record: SocialMessageRecord): boolean;
|
|
11
|
+
export declare function signSocialMessageObservation(input: {
|
|
12
|
+
identity: AgentIdentity;
|
|
13
|
+
observation_id: string;
|
|
14
|
+
message_id: string;
|
|
15
|
+
observed_agent_id: string;
|
|
16
|
+
observer_display_name: string;
|
|
17
|
+
observed_at?: number;
|
|
18
|
+
}): SocialMessageObservationRecord;
|
|
19
|
+
export declare function verifySocialMessageObservation(record: SocialMessageObservationRecord): boolean;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.signSocialMessage = signSocialMessage;
|
|
4
|
+
exports.verifySocialMessage = verifySocialMessage;
|
|
5
|
+
exports.signSocialMessageObservation = signSocialMessageObservation;
|
|
6
|
+
exports.verifySocialMessageObservation = verifySocialMessageObservation;
|
|
7
|
+
const crypto_1 = require("./crypto");
|
|
8
|
+
function unsignedSocialMessage(record) {
|
|
9
|
+
const { signature: _signature, ...rest } = record;
|
|
10
|
+
return rest;
|
|
11
|
+
}
|
|
12
|
+
function signSocialMessage(input) {
|
|
13
|
+
const payload = {
|
|
14
|
+
type: "social.message",
|
|
15
|
+
message_id: input.message_id,
|
|
16
|
+
agent_id: input.identity.agent_id,
|
|
17
|
+
public_key: input.identity.public_key,
|
|
18
|
+
display_name: input.display_name,
|
|
19
|
+
topic: input.topic,
|
|
20
|
+
body: input.body,
|
|
21
|
+
created_at: input.created_at ?? Date.now(),
|
|
22
|
+
};
|
|
23
|
+
return {
|
|
24
|
+
...payload,
|
|
25
|
+
signature: (0, crypto_1.signPayload)(payload, input.identity.private_key),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function verifySocialMessage(record) {
|
|
29
|
+
try {
|
|
30
|
+
if ((0, crypto_1.hashPublicKey)((0, crypto_1.fromBase64)(record.public_key)) !== record.agent_id) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return (0, crypto_1.verifyPayload)(unsignedSocialMessage(record), record.signature, record.public_key);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function unsignedSocialMessageObservation(record) {
|
|
40
|
+
const { signature: _signature, ...rest } = record;
|
|
41
|
+
return rest;
|
|
42
|
+
}
|
|
43
|
+
function signSocialMessageObservation(input) {
|
|
44
|
+
const payload = {
|
|
45
|
+
type: "social.message.observation",
|
|
46
|
+
observation_id: input.observation_id,
|
|
47
|
+
message_id: input.message_id,
|
|
48
|
+
observed_agent_id: input.observed_agent_id,
|
|
49
|
+
observer_agent_id: input.identity.agent_id,
|
|
50
|
+
observer_public_key: input.identity.public_key,
|
|
51
|
+
observer_display_name: input.observer_display_name,
|
|
52
|
+
observed_at: input.observed_at ?? Date.now(),
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
...payload,
|
|
56
|
+
signature: (0, crypto_1.signPayload)(payload, input.identity.private_key),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function verifySocialMessageObservation(record) {
|
|
60
|
+
try {
|
|
61
|
+
if ((0, crypto_1.hashPublicKey)((0, crypto_1.fromBase64)(record.observer_public_key)) !== record.observer_agent_id) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return (0, crypto_1.verifyPayload)(unsignedSocialMessageObservation(record), record.signature, record.observer_public_key);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -34,10 +34,11 @@ function generateSocialMdTemplate(runtimeConfig) {
|
|
|
34
34
|
const tags = asStringArray(profile?.tags, ["openclaw", "local-first"]);
|
|
35
35
|
const mode = network?.mode === "local" || network?.mode === "lan" || network?.mode === "global-preview"
|
|
36
36
|
? network.mode
|
|
37
|
-
: "
|
|
37
|
+
: "global-preview";
|
|
38
38
|
const discoverable = asBool(discovery?.discoverable, true);
|
|
39
39
|
const allowProfileBroadcast = asBool(discovery?.allow_profile_broadcast, true);
|
|
40
40
|
const allowPresenceBroadcast = asBool(discovery?.allow_presence_broadcast, true);
|
|
41
|
+
const allowMessageBroadcast = asBool(discovery?.allow_message_broadcast, true);
|
|
41
42
|
const showDisplayName = asBool(visibility?.show_display_name, true);
|
|
42
43
|
const showBio = asBool(visibility?.show_bio, true);
|
|
43
44
|
const showTags = asBool(visibility?.show_tags, true);
|
|
@@ -63,6 +64,7 @@ discovery:
|
|
|
63
64
|
discoverable: ${discoverable}
|
|
64
65
|
allow_profile_broadcast: ${allowProfileBroadcast}
|
|
65
66
|
allow_presence_broadcast: ${allowPresenceBroadcast}
|
|
67
|
+
allow_message_broadcast: ${allowMessageBroadcast}
|
|
66
68
|
|
|
67
69
|
visibility:
|
|
68
70
|
show_display_name: ${showDisplayName}
|
|
@@ -29,6 +29,28 @@ export type IndexRefRecord = {
|
|
|
29
29
|
key: string;
|
|
30
30
|
agent_id: string;
|
|
31
31
|
};
|
|
32
|
+
export type SocialMessageRecord = {
|
|
33
|
+
type: "social.message";
|
|
34
|
+
message_id: string;
|
|
35
|
+
agent_id: string;
|
|
36
|
+
public_key: string;
|
|
37
|
+
display_name: string;
|
|
38
|
+
topic: string;
|
|
39
|
+
body: string;
|
|
40
|
+
created_at: number;
|
|
41
|
+
signature: string;
|
|
42
|
+
};
|
|
43
|
+
export type SocialMessageObservationRecord = {
|
|
44
|
+
type: "social.message.observation";
|
|
45
|
+
observation_id: string;
|
|
46
|
+
message_id: string;
|
|
47
|
+
observed_agent_id: string;
|
|
48
|
+
observer_agent_id: string;
|
|
49
|
+
observer_public_key: string;
|
|
50
|
+
observer_display_name: string;
|
|
51
|
+
observed_at: number;
|
|
52
|
+
signature: string;
|
|
53
|
+
};
|
|
32
54
|
export type DirectoryState = {
|
|
33
55
|
profiles: Record<string, PublicProfile>;
|
|
34
56
|
presence: Record<string, number>;
|
|
@@ -24,6 +24,7 @@ export type SocialDiscoveryConfig = {
|
|
|
24
24
|
discoverable: boolean;
|
|
25
25
|
allow_profile_broadcast: boolean;
|
|
26
26
|
allow_presence_broadcast: boolean;
|
|
27
|
+
allow_message_broadcast: boolean;
|
|
27
28
|
};
|
|
28
29
|
|
|
29
30
|
export type SocialVisibilityConfig = {
|
|
@@ -118,6 +119,7 @@ const DEFAULT_SOCIAL_CONFIG: SocialConfig = {
|
|
|
118
119
|
discoverable: true,
|
|
119
120
|
allow_profile_broadcast: true,
|
|
120
121
|
allow_presence_broadcast: true,
|
|
122
|
+
allow_message_broadcast: true,
|
|
121
123
|
},
|
|
122
124
|
visibility: {
|
|
123
125
|
show_display_name: true,
|
|
@@ -367,6 +369,10 @@ export function normalizeSocialConfig(input: unknown): SocialConfig {
|
|
|
367
369
|
discovery.allow_presence_broadcast,
|
|
368
370
|
DEFAULT_SOCIAL_CONFIG.discovery.allow_presence_broadcast
|
|
369
371
|
),
|
|
372
|
+
allow_message_broadcast: asBool(
|
|
373
|
+
discovery.allow_message_broadcast,
|
|
374
|
+
DEFAULT_SOCIAL_CONFIG.discovery.allow_message_broadcast
|
|
375
|
+
),
|
|
370
376
|
},
|
|
371
377
|
visibility: {
|
|
372
378
|
show_display_name: asBool(
|
|
@@ -414,7 +420,7 @@ export function generateDefaultSocialMdTemplate(options: DefaultSocialTemplateOp
|
|
|
414
420
|
const displayName = options.display_name?.trim() || "My OpenClaw Agent";
|
|
415
421
|
const bio = options.bio?.trim() || "Local AI agent running on this machine";
|
|
416
422
|
const tags = Array.isArray(options.tags) && options.tags.length > 0 ? options.tags : ["openclaw", "local-first"];
|
|
417
|
-
const mode = options.mode ?? "
|
|
423
|
+
const mode = options.mode ?? "global-preview";
|
|
418
424
|
const publicEnabled = typeof options.public_enabled === "boolean" ? options.public_enabled : false;
|
|
419
425
|
return `---
|
|
420
426
|
enabled: true
|
|
@@ -429,6 +435,12 @@ ${tags.map((tag) => ` - ${tag}`).join("\n")}
|
|
|
429
435
|
network:
|
|
430
436
|
mode: ${JSON.stringify(mode)}
|
|
431
437
|
|
|
438
|
+
discovery:
|
|
439
|
+
discoverable: true
|
|
440
|
+
allow_profile_broadcast: true
|
|
441
|
+
allow_presence_broadcast: true
|
|
442
|
+
allow_message_broadcast: true
|
|
443
|
+
|
|
432
444
|
openclaw:
|
|
433
445
|
bind_existing_identity: true
|
|
434
446
|
use_openclaw_profile_if_available: true
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { AgentIdentity, SocialMessageObservationRecord, SocialMessageRecord } from "./types";
|
|
2
|
+
import { fromBase64, hashPublicKey, signPayload, verifyPayload } from "./crypto";
|
|
3
|
+
|
|
4
|
+
function unsignedSocialMessage(record: SocialMessageRecord): Omit<SocialMessageRecord, "signature"> {
|
|
5
|
+
const { signature: _signature, ...rest } = record;
|
|
6
|
+
return rest;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function signSocialMessage(input: {
|
|
10
|
+
identity: AgentIdentity;
|
|
11
|
+
message_id: string;
|
|
12
|
+
display_name: string;
|
|
13
|
+
topic: string;
|
|
14
|
+
body: string;
|
|
15
|
+
created_at?: number;
|
|
16
|
+
}): SocialMessageRecord {
|
|
17
|
+
const payload: Omit<SocialMessageRecord, "signature"> = {
|
|
18
|
+
type: "social.message",
|
|
19
|
+
message_id: input.message_id,
|
|
20
|
+
agent_id: input.identity.agent_id,
|
|
21
|
+
public_key: input.identity.public_key,
|
|
22
|
+
display_name: input.display_name,
|
|
23
|
+
topic: input.topic,
|
|
24
|
+
body: input.body,
|
|
25
|
+
created_at: input.created_at ?? Date.now(),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
...payload,
|
|
30
|
+
signature: signPayload(payload, input.identity.private_key),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function verifySocialMessage(record: SocialMessageRecord): boolean {
|
|
35
|
+
try {
|
|
36
|
+
if (hashPublicKey(fromBase64(record.public_key)) !== record.agent_id) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
return verifyPayload(unsignedSocialMessage(record), record.signature, record.public_key);
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function unsignedSocialMessageObservation(
|
|
46
|
+
record: SocialMessageObservationRecord
|
|
47
|
+
): Omit<SocialMessageObservationRecord, "signature"> {
|
|
48
|
+
const { signature: _signature, ...rest } = record;
|
|
49
|
+
return rest;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function signSocialMessageObservation(input: {
|
|
53
|
+
identity: AgentIdentity;
|
|
54
|
+
observation_id: string;
|
|
55
|
+
message_id: string;
|
|
56
|
+
observed_agent_id: string;
|
|
57
|
+
observer_display_name: string;
|
|
58
|
+
observed_at?: number;
|
|
59
|
+
}): SocialMessageObservationRecord {
|
|
60
|
+
const payload: Omit<SocialMessageObservationRecord, "signature"> = {
|
|
61
|
+
type: "social.message.observation",
|
|
62
|
+
observation_id: input.observation_id,
|
|
63
|
+
message_id: input.message_id,
|
|
64
|
+
observed_agent_id: input.observed_agent_id,
|
|
65
|
+
observer_agent_id: input.identity.agent_id,
|
|
66
|
+
observer_public_key: input.identity.public_key,
|
|
67
|
+
observer_display_name: input.observer_display_name,
|
|
68
|
+
observed_at: input.observed_at ?? Date.now(),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
...payload,
|
|
73
|
+
signature: signPayload(payload, input.identity.private_key),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function verifySocialMessageObservation(record: SocialMessageObservationRecord): boolean {
|
|
78
|
+
try {
|
|
79
|
+
if (hashPublicKey(fromBase64(record.observer_public_key)) !== record.observer_agent_id) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return verifyPayload(unsignedSocialMessageObservation(record), record.signature, record.observer_public_key);
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -39,11 +39,12 @@ export function generateSocialMdTemplate(runtimeConfig: SocialRuntimeConfig | nu
|
|
|
39
39
|
const mode =
|
|
40
40
|
network?.mode === "local" || network?.mode === "lan" || network?.mode === "global-preview"
|
|
41
41
|
? network.mode
|
|
42
|
-
: "
|
|
42
|
+
: "global-preview";
|
|
43
43
|
|
|
44
44
|
const discoverable = asBool(discovery?.discoverable, true);
|
|
45
45
|
const allowProfileBroadcast = asBool(discovery?.allow_profile_broadcast, true);
|
|
46
46
|
const allowPresenceBroadcast = asBool(discovery?.allow_presence_broadcast, true);
|
|
47
|
+
const allowMessageBroadcast = asBool(discovery?.allow_message_broadcast, true);
|
|
47
48
|
|
|
48
49
|
const showDisplayName = asBool(visibility?.show_display_name, true);
|
|
49
50
|
const showBio = asBool(visibility?.show_bio, true);
|
|
@@ -72,6 +73,7 @@ discovery:
|
|
|
72
73
|
discoverable: ${discoverable}
|
|
73
74
|
allow_profile_broadcast: ${allowProfileBroadcast}
|
|
74
75
|
allow_presence_broadcast: ${allowPresenceBroadcast}
|
|
76
|
+
allow_message_broadcast: ${allowMessageBroadcast}
|
|
75
77
|
|
|
76
78
|
visibility:
|
|
77
79
|
show_display_name: ${showDisplayName}
|
|
@@ -34,6 +34,30 @@ export type IndexRefRecord = {
|
|
|
34
34
|
agent_id: string;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
export type SocialMessageRecord = {
|
|
38
|
+
type: "social.message";
|
|
39
|
+
message_id: string;
|
|
40
|
+
agent_id: string;
|
|
41
|
+
public_key: string;
|
|
42
|
+
display_name: string;
|
|
43
|
+
topic: string;
|
|
44
|
+
body: string;
|
|
45
|
+
created_at: number;
|
|
46
|
+
signature: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type SocialMessageObservationRecord = {
|
|
50
|
+
type: "social.message.observation";
|
|
51
|
+
observation_id: string;
|
|
52
|
+
message_id: string;
|
|
53
|
+
observed_agent_id: string;
|
|
54
|
+
observer_agent_id: string;
|
|
55
|
+
observer_public_key: string;
|
|
56
|
+
observer_display_name: string;
|
|
57
|
+
observed_at: number;
|
|
58
|
+
signature: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
37
61
|
export type DirectoryState = {
|
|
38
62
|
profiles: Record<string, PublicProfile>;
|
|
39
63
|
presence: Record<string, number>;
|
|
@@ -357,18 +357,20 @@ class RelayPreviewAdapter {
|
|
|
357
357
|
const endpoint = this.signalingEndpoints[index]?.replace(/\/+$/, "");
|
|
358
358
|
if (!endpoint)
|
|
359
359
|
continue;
|
|
360
|
+
let timeout = null;
|
|
360
361
|
try {
|
|
361
362
|
const controller = new AbortController();
|
|
362
|
-
|
|
363
|
+
timeout = setTimeout(() => controller.abort(), this.requestTimeoutMs);
|
|
363
364
|
const response = await fetch(`${endpoint}${path}`, {
|
|
364
365
|
method,
|
|
365
366
|
headers: method === "POST" ? { "content-type": "application/json" } : undefined,
|
|
366
367
|
body: method === "POST" ? JSON.stringify(body) : undefined,
|
|
367
368
|
signal: controller.signal,
|
|
368
369
|
});
|
|
369
|
-
clearTimeout(timeout);
|
|
370
370
|
if (!response.ok) {
|
|
371
|
-
|
|
371
|
+
const responseText = (await response.text().catch(() => "")).trim();
|
|
372
|
+
const detail = responseText ? `: ${responseText.slice(0, 200)}` : "";
|
|
373
|
+
throw new Error(`${method} ${path} failed (${response.status})${detail}`);
|
|
372
374
|
}
|
|
373
375
|
this.activeEndpointIndex = index;
|
|
374
376
|
this.activeEndpoint = endpoint;
|
|
@@ -376,7 +378,12 @@ class RelayPreviewAdapter {
|
|
|
376
378
|
return response.json();
|
|
377
379
|
}
|
|
378
380
|
catch (error) {
|
|
379
|
-
const
|
|
381
|
+
const isAbort = error instanceof Error && (error.name === "AbortError" || error.message === "This operation was aborted");
|
|
382
|
+
const message = isAbort
|
|
383
|
+
? `${method} ${path} timed out after ${this.requestTimeoutMs}ms`
|
|
384
|
+
: error instanceof Error
|
|
385
|
+
? error.message
|
|
386
|
+
: String(error);
|
|
380
387
|
errors.push(`${endpoint}: ${message}`);
|
|
381
388
|
this.stats.signaling_errors += 1;
|
|
382
389
|
this.lastError = message;
|
|
@@ -384,6 +391,11 @@ class RelayPreviewAdapter {
|
|
|
384
391
|
this.reconnectAttemptsTotal += 1;
|
|
385
392
|
this.recordDiscovery("signaling_error", { endpoint, detail: message });
|
|
386
393
|
}
|
|
394
|
+
finally {
|
|
395
|
+
if (timeout) {
|
|
396
|
+
clearTimeout(timeout);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
387
399
|
}
|
|
388
400
|
throw new Error(errors.join(" | "));
|
|
389
401
|
}
|
|
@@ -480,31 +480,44 @@ export class RelayPreviewAdapter implements NetworkAdapter {
|
|
|
480
480
|
const index = (this.activeEndpointIndex + offset) % this.signalingEndpoints.length;
|
|
481
481
|
const endpoint = this.signalingEndpoints[index]?.replace(/\/+$/, "");
|
|
482
482
|
if (!endpoint) continue;
|
|
483
|
+
let timeout: NodeJS.Timeout | null = null;
|
|
483
484
|
try {
|
|
484
485
|
const controller = new AbortController();
|
|
485
|
-
|
|
486
|
+
timeout = setTimeout(() => controller.abort(), this.requestTimeoutMs);
|
|
486
487
|
const response = await fetch(`${endpoint}${path}`, {
|
|
487
488
|
method,
|
|
488
489
|
headers: method === "POST" ? { "content-type": "application/json" } : undefined,
|
|
489
490
|
body: method === "POST" ? JSON.stringify(body) : undefined,
|
|
490
491
|
signal: controller.signal,
|
|
491
492
|
});
|
|
492
|
-
clearTimeout(timeout);
|
|
493
493
|
if (!response.ok) {
|
|
494
|
-
|
|
494
|
+
const responseText = (await response.text().catch(() => "")).trim();
|
|
495
|
+
const detail = responseText ? `: ${responseText.slice(0, 200)}` : "";
|
|
496
|
+
throw new Error(`${method} ${path} failed (${response.status})${detail}`);
|
|
495
497
|
}
|
|
496
498
|
this.activeEndpointIndex = index;
|
|
497
499
|
this.activeEndpoint = endpoint;
|
|
498
500
|
this.lastError = null;
|
|
499
501
|
return response.json();
|
|
500
502
|
} catch (error) {
|
|
501
|
-
const
|
|
503
|
+
const isAbort = error instanceof Error && (
|
|
504
|
+
error.name === "AbortError" || error.message === "This operation was aborted"
|
|
505
|
+
);
|
|
506
|
+
const message = isAbort
|
|
507
|
+
? `${method} ${path} timed out after ${this.requestTimeoutMs}ms`
|
|
508
|
+
: error instanceof Error
|
|
509
|
+
? error.message
|
|
510
|
+
: String(error);
|
|
502
511
|
errors.push(`${endpoint}: ${message}`);
|
|
503
512
|
this.stats.signaling_errors += 1;
|
|
504
513
|
this.lastError = message;
|
|
505
514
|
this.lastErrorAt = Date.now();
|
|
506
515
|
this.reconnectAttemptsTotal += 1;
|
|
507
516
|
this.recordDiscovery("signaling_error", { endpoint, detail: message });
|
|
517
|
+
} finally {
|
|
518
|
+
if (timeout) {
|
|
519
|
+
clearTimeout(timeout);
|
|
520
|
+
}
|
|
508
521
|
}
|
|
509
522
|
}
|
|
510
523
|
throw new Error(errors.join(" | "));
|
|
@@ -6,6 +6,37 @@ export type LogEntry = {
|
|
|
6
6
|
message: string;
|
|
7
7
|
timestamp: number;
|
|
8
8
|
};
|
|
9
|
+
export type SocialMessageRecord = {
|
|
10
|
+
type: "social.message";
|
|
11
|
+
message_id: string;
|
|
12
|
+
agent_id: string;
|
|
13
|
+
public_key: string;
|
|
14
|
+
display_name: string;
|
|
15
|
+
topic: string;
|
|
16
|
+
body: string;
|
|
17
|
+
created_at: number;
|
|
18
|
+
signature: string;
|
|
19
|
+
};
|
|
20
|
+
export type SocialMessageObservationRecord = {
|
|
21
|
+
type: "social.message.observation";
|
|
22
|
+
observation_id: string;
|
|
23
|
+
message_id: string;
|
|
24
|
+
observed_agent_id: string;
|
|
25
|
+
observer_agent_id: string;
|
|
26
|
+
observer_public_key: string;
|
|
27
|
+
observer_display_name: string;
|
|
28
|
+
observed_at: number;
|
|
29
|
+
signature: string;
|
|
30
|
+
};
|
|
31
|
+
export type SocialMessageGovernanceConfig = {
|
|
32
|
+
send_limit_max: number;
|
|
33
|
+
send_window_ms: number;
|
|
34
|
+
receive_limit_max: number;
|
|
35
|
+
receive_window_ms: number;
|
|
36
|
+
duplicate_window_ms: number;
|
|
37
|
+
blocked_agent_ids: string[];
|
|
38
|
+
blocked_terms: string[];
|
|
39
|
+
};
|
|
9
40
|
export declare class IdentityRepo extends JsonFileRepo<AgentIdentity | null> {
|
|
10
41
|
constructor(rootDir?: string);
|
|
11
42
|
}
|
|
@@ -19,3 +50,12 @@ export declare class LogRepo extends JsonFileRepo<LogEntry[]> {
|
|
|
19
50
|
constructor(rootDir?: string);
|
|
20
51
|
append(entry: Omit<LogEntry, "id">): Promise<void>;
|
|
21
52
|
}
|
|
53
|
+
export declare class SocialMessageRepo extends JsonFileRepo<SocialMessageRecord[]> {
|
|
54
|
+
constructor(rootDir?: string);
|
|
55
|
+
}
|
|
56
|
+
export declare class SocialMessageObservationRepo extends JsonFileRepo<SocialMessageObservationRecord[]> {
|
|
57
|
+
constructor(rootDir?: string);
|
|
58
|
+
}
|
|
59
|
+
export declare class SocialMessageGovernanceRepo extends JsonFileRepo<SocialMessageGovernanceConfig> {
|
|
60
|
+
constructor(rootDir?: string);
|
|
61
|
+
}
|