@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.
Files changed (63) hide show
  1. package/ARCHITECTURE.md +15 -0
  2. package/CHANGELOG.md +17 -2
  3. package/INSTALL.md +35 -0
  4. package/README.md +119 -10
  5. package/RELEASE_NOTES_v1.0.md +29 -2
  6. package/SOCIAL_MD_SPEC.md +2 -0
  7. package/VERSION +1 -1
  8. package/apps/local-console/public/index.html +2297 -231
  9. package/apps/local-console/src/server.ts +1120 -24
  10. package/apps/local-console/src/socialRoutes.ts +21 -0
  11. package/apps/public-explorer/public/index.html +190 -43
  12. package/docs/NEW_USER_OPERATIONS.md +35 -5
  13. package/docs/OPENCLAW_BRIDGE.md +449 -0
  14. package/docs/OPENCLAW_BRIDGE_ZH.md +445 -0
  15. package/docs/QUICK_START.md +20 -1
  16. package/docs/release/ANNOUNCEMENT_v1.0-beta.md +68 -0
  17. package/docs/release/FINAL_RELEASE_SUMMARY_v1.0-beta.md +112 -0
  18. package/docs/release/GITHUB_RELEASE_v1.0-beta.md +16 -16
  19. package/docs/release/RELEASE_COPY_v1.0-beta.md +102 -0
  20. package/openclaw-skills/silicaclaw-broadcast/SKILL.md +89 -0
  21. package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -0
  22. package/openclaw-skills/silicaclaw-broadcast/agents/openai.yaml +6 -0
  23. package/openclaw-skills/silicaclaw-broadcast/manifest.json +34 -0
  24. package/openclaw-skills/silicaclaw-broadcast/references/computer-control-via-openclaw.md +41 -0
  25. package/openclaw-skills/silicaclaw-broadcast/references/owner-dispatch-adapter.md +81 -0
  26. package/openclaw-skills/silicaclaw-broadcast/references/owner-forwarding-policy.md +48 -0
  27. package/openclaw-skills/silicaclaw-broadcast/scripts/bridge-client.mjs +59 -0
  28. package/openclaw-skills/silicaclaw-broadcast/scripts/owner-dispatch-adapter-demo.mjs +12 -0
  29. package/openclaw-skills/silicaclaw-broadcast/scripts/owner-forwarder-demo.mjs +111 -0
  30. package/openclaw-skills/silicaclaw-broadcast/scripts/send-to-owner-via-openclaw.mjs +69 -0
  31. package/openclaw.social.md.example +6 -0
  32. package/package.json +2 -1
  33. package/packages/core/dist/index.d.ts +1 -0
  34. package/packages/core/dist/index.js +1 -0
  35. package/packages/core/dist/socialConfig.d.ts +1 -0
  36. package/packages/core/dist/socialConfig.js +9 -1
  37. package/packages/core/dist/socialMessage.d.ts +19 -0
  38. package/packages/core/dist/socialMessage.js +69 -0
  39. package/packages/core/dist/socialTemplate.js +3 -1
  40. package/packages/core/dist/types.d.ts +22 -0
  41. package/packages/core/src/index.ts +1 -0
  42. package/packages/core/src/socialConfig.ts +13 -1
  43. package/packages/core/src/socialMessage.ts +86 -0
  44. package/packages/core/src/socialTemplate.ts +3 -1
  45. package/packages/core/src/types.ts +24 -0
  46. package/packages/network/dist/relayPreview.js +16 -4
  47. package/packages/network/src/relayPreview.ts +17 -4
  48. package/packages/storage/dist/repos.d.ts +40 -0
  49. package/packages/storage/dist/repos.js +27 -1
  50. package/packages/storage/dist/socialRuntimeRepo.js +1 -0
  51. package/packages/storage/src/repos.ts +60 -0
  52. package/packages/storage/src/socialRuntimeRepo.ts +1 -0
  53. package/packages/storage/tsconfig.json +1 -1
  54. package/scripts/functional-check.mjs +85 -2
  55. package/scripts/install-openclaw-skill.mjs +54 -0
  56. package/scripts/openclaw-bridge-adapter.mjs +89 -0
  57. package/scripts/openclaw-bridge-client.mjs +223 -0
  58. package/scripts/openclaw-runtime-demo.mjs +202 -0
  59. package/scripts/pack-openclaw-skill.mjs +58 -0
  60. package/scripts/silicaclaw-cli.mjs +30 -0
  61. package/scripts/silicaclaw-gateway.mjs +215 -0
  62. package/scripts/validate-openclaw-skill.mjs +74 -0
  63. 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.18-4",
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",
@@ -3,6 +3,7 @@ export * from "./crypto";
3
3
  export * from "./identity";
4
4
  export * from "./profile";
5
5
  export * from "./presence";
6
+ export * from "./socialMessage";
6
7
  export * from "./indexing";
7
8
  export * from "./directory";
8
9
  export * from "./publicProfileSummary";
@@ -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);
@@ -21,6 +21,7 @@ export type SocialDiscoveryConfig = {
21
21
  discoverable: boolean;
22
22
  allow_profile_broadcast: boolean;
23
23
  allow_presence_broadcast: boolean;
24
+ allow_message_broadcast: boolean;
24
25
  };
25
26
  export type SocialVisibilityConfig = {
26
27
  show_display_name: boolean;
@@ -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 ?? "lan";
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
- : "lan";
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>;
@@ -3,6 +3,7 @@ export * from "./crypto";
3
3
  export * from "./identity";
4
4
  export * from "./profile";
5
5
  export * from "./presence";
6
+ export * from "./socialMessage";
6
7
  export * from "./indexing";
7
8
  export * from "./directory";
8
9
  export * from "./publicProfileSummary";
@@ -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 ?? "lan";
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
- : "lan";
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
- const timeout = setTimeout(() => controller.abort(), this.requestTimeoutMs);
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
- throw new Error(`${method} ${path} failed (${response.status})`);
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 message = error instanceof Error ? error.message : String(error);
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
- const timeout = setTimeout(() => controller.abort(), this.requestTimeoutMs);
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
- throw new Error(`${method} ${path} failed (${response.status})`);
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 message = error instanceof Error ? error.message : String(error);
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
+ }