@silicaclaw/cli 1.0.0-beta.2 → 1.0.0-beta.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/INSTALL.md +36 -0
  3. package/README.md +40 -0
  4. package/apps/local-console/public/index.html +81 -63
  5. package/apps/local-console/src/server.ts +41 -21
  6. package/docs/CLOUDFLARE_RELAY.md +61 -0
  7. package/package.json +6 -1
  8. package/packages/core/dist/crypto.d.ts +6 -0
  9. package/packages/core/dist/crypto.js +50 -0
  10. package/packages/core/dist/directory.d.ts +17 -0
  11. package/packages/core/dist/directory.js +145 -0
  12. package/packages/core/dist/identity.d.ts +2 -0
  13. package/packages/core/dist/identity.js +18 -0
  14. package/packages/core/dist/index.d.ts +11 -0
  15. package/packages/core/dist/index.js +27 -0
  16. package/packages/core/dist/indexing.d.ts +6 -0
  17. package/packages/core/dist/indexing.js +43 -0
  18. package/packages/core/dist/presence.d.ts +4 -0
  19. package/packages/core/dist/presence.js +23 -0
  20. package/packages/core/dist/profile.d.ts +4 -0
  21. package/packages/core/dist/profile.js +39 -0
  22. package/packages/core/dist/publicProfileSummary.d.ts +70 -0
  23. package/packages/core/dist/publicProfileSummary.js +103 -0
  24. package/packages/core/dist/socialConfig.d.ts +99 -0
  25. package/packages/core/dist/socialConfig.js +288 -0
  26. package/packages/core/dist/socialResolver.d.ts +46 -0
  27. package/packages/core/dist/socialResolver.js +237 -0
  28. package/packages/core/dist/socialTemplate.d.ts +2 -0
  29. package/packages/core/dist/socialTemplate.js +88 -0
  30. package/packages/core/dist/types.d.ts +37 -0
  31. package/packages/core/dist/types.js +2 -0
  32. package/packages/core/src/socialConfig.ts +7 -6
  33. package/packages/core/src/socialResolver.ts +17 -5
  34. package/packages/network/dist/abstractions/messageEnvelope.d.ts +28 -0
  35. package/packages/network/dist/abstractions/messageEnvelope.js +36 -0
  36. package/packages/network/dist/abstractions/peerDiscovery.d.ts +43 -0
  37. package/packages/network/dist/abstractions/peerDiscovery.js +2 -0
  38. package/packages/network/dist/abstractions/topicCodec.d.ts +4 -0
  39. package/packages/network/dist/abstractions/topicCodec.js +2 -0
  40. package/packages/network/dist/abstractions/transport.d.ts +36 -0
  41. package/packages/network/dist/abstractions/transport.js +2 -0
  42. package/packages/network/dist/codec/jsonMessageEnvelopeCodec.d.ts +5 -0
  43. package/packages/network/dist/codec/jsonMessageEnvelopeCodec.js +24 -0
  44. package/packages/network/dist/codec/jsonTopicCodec.d.ts +5 -0
  45. package/packages/network/dist/codec/jsonTopicCodec.js +12 -0
  46. package/packages/network/dist/discovery/heartbeatPeerDiscovery.d.ts +28 -0
  47. package/packages/network/dist/discovery/heartbeatPeerDiscovery.js +144 -0
  48. package/packages/network/dist/index.d.ts +14 -0
  49. package/packages/network/dist/index.js +30 -0
  50. package/packages/network/dist/localEventBus.d.ts +9 -0
  51. package/packages/network/dist/localEventBus.js +47 -0
  52. package/packages/network/dist/mock.d.ts +8 -0
  53. package/packages/network/dist/mock.js +24 -0
  54. package/packages/network/dist/realPreview.d.ts +105 -0
  55. package/packages/network/dist/realPreview.js +327 -0
  56. package/packages/network/dist/relayPreview.d.ts +133 -0
  57. package/packages/network/dist/relayPreview.js +320 -0
  58. package/packages/network/dist/transport/udpLanBroadcastTransport.d.ts +23 -0
  59. package/packages/network/dist/transport/udpLanBroadcastTransport.js +153 -0
  60. package/packages/network/dist/types.d.ts +6 -0
  61. package/packages/network/dist/types.js +2 -0
  62. package/packages/network/dist/webrtcPreview.d.ts +163 -0
  63. package/packages/network/dist/webrtcPreview.js +844 -0
  64. package/packages/network/src/index.ts +1 -0
  65. package/packages/network/src/relayPreview.ts +425 -0
  66. package/packages/storage/dist/index.d.ts +3 -0
  67. package/packages/storage/dist/index.js +19 -0
  68. package/packages/storage/dist/jsonRepo.d.ts +7 -0
  69. package/packages/storage/dist/jsonRepo.js +29 -0
  70. package/packages/storage/dist/repos.d.ts +21 -0
  71. package/packages/storage/dist/repos.js +41 -0
  72. package/packages/storage/dist/socialRuntimeRepo.d.ts +5 -0
  73. package/packages/storage/dist/socialRuntimeRepo.js +52 -0
  74. package/packages/storage/src/socialRuntimeRepo.ts +3 -3
  75. package/packages/storage/tsconfig.json +6 -1
  76. package/scripts/quickstart.sh +286 -20
  77. package/scripts/silicaclaw-cli.mjs +271 -1
  78. package/scripts/silicaclaw-gateway.mjs +411 -0
  79. package/scripts/webrtc-signaling-server.mjs +52 -1
@@ -0,0 +1,37 @@
1
+ export type AgentIdentity = {
2
+ agent_id: string;
3
+ public_key: string;
4
+ private_key: string;
5
+ created_at: number;
6
+ };
7
+ export type PublicProfile = {
8
+ agent_id: string;
9
+ display_name: string;
10
+ bio: string;
11
+ tags: string[];
12
+ avatar_url?: string;
13
+ public_enabled: boolean;
14
+ updated_at: number;
15
+ signature: string;
16
+ };
17
+ export type SignedProfileRecord = {
18
+ type: "profile";
19
+ profile: PublicProfile;
20
+ };
21
+ export type PresenceRecord = {
22
+ type: "presence";
23
+ agent_id: string;
24
+ timestamp: number;
25
+ signature: string;
26
+ };
27
+ export type IndexRefRecord = {
28
+ type: "index";
29
+ key: string;
30
+ agent_id: string;
31
+ };
32
+ export type DirectoryState = {
33
+ profiles: Record<string, PublicProfile>;
34
+ presence: Record<string, number>;
35
+ index: Record<string, string[]>;
36
+ };
37
+ export type ProfileInput = Omit<PublicProfile, "signature" | "updated_at">;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -5,7 +5,7 @@ export type SocialIdentityConfig = {
5
5
  tags: string[];
6
6
  };
7
7
 
8
- export type SocialNetworkAdapter = "mock" | "local-event-bus" | "real-preview" | "webrtc-preview";
8
+ export type SocialNetworkAdapter = "mock" | "local-event-bus" | "real-preview" | "webrtc-preview" | "relay-preview";
9
9
  export type SocialNetworkMode = "local" | "lan" | "global-preview";
10
10
 
11
11
  export type SocialNetworkConfig = {
@@ -104,13 +104,13 @@ const DEFAULT_SOCIAL_CONFIG: SocialConfig = {
104
104
  tags: [],
105
105
  },
106
106
  network: {
107
- mode: "lan",
107
+ mode: "global-preview",
108
108
  namespace: "silicaclaw.preview",
109
- adapter: "real-preview",
109
+ adapter: "relay-preview",
110
110
  port: 44123,
111
111
  signaling_url: "http://localhost:4510",
112
112
  signaling_urls: [],
113
- room: "silicaclaw-room",
113
+ room: "silicaclaw-global-preview",
114
114
  seed_peers: [],
115
115
  bootstrap_hints: [],
116
116
  },
@@ -298,7 +298,8 @@ function asAdapter(value: unknown, fallback: SocialNetworkAdapter): SocialNetwor
298
298
  value === "mock" ||
299
299
  value === "local-event-bus" ||
300
300
  value === "real-preview" ||
301
- value === "webrtc-preview"
301
+ value === "webrtc-preview" ||
302
+ value === "relay-preview"
302
303
  ) {
303
304
  return value;
304
305
  }
@@ -315,7 +316,7 @@ function asMode(value: unknown, fallback: SocialNetworkMode): SocialNetworkMode
315
316
  function adapterForMode(mode: SocialNetworkMode): SocialNetworkAdapter {
316
317
  if (mode === "local") return "local-event-bus";
317
318
  if (mode === "lan") return "real-preview";
318
- return "webrtc-preview";
319
+ return "relay-preview";
319
320
  }
320
321
 
321
322
  export function normalizeSocialConfig(input: unknown): SocialConfig {
@@ -257,13 +257,25 @@ export function resolveProfileInputWithSocial(args: {
257
257
  const baseAvatarUrl = existingProfile?.avatar_url || "";
258
258
  const baseTags = existingProfile?.tags || [];
259
259
 
260
+ // Preserve values saved from local-console first; only fall back to social/openclaw defaults
261
+ // when local profile fields are empty.
262
+ const displayName = baseDisplayName || socialConfig.identity.display_name || openclawProfile?.display_name || "";
263
+ const bio = baseBio || socialConfig.identity.bio || openclawProfile?.bio || "";
264
+ const avatarUrl = baseAvatarUrl || socialConfig.identity.avatar_url || openclawProfile?.avatar_url || "";
265
+ const tags =
266
+ baseTags.length > 0
267
+ ? baseTags
268
+ : socialConfig.identity.tags.length > 0
269
+ ? socialConfig.identity.tags
270
+ : openclawProfile?.tags || [];
271
+
260
272
  return {
261
273
  agent_id: agentId,
262
- display_name: socialConfig.identity.display_name || openclawProfile?.display_name || baseDisplayName,
263
- bio: socialConfig.identity.bio || openclawProfile?.bio || baseBio,
264
- avatar_url: socialConfig.identity.avatar_url || openclawProfile?.avatar_url || baseAvatarUrl,
265
- tags: socialConfig.identity.tags.length > 0 ? socialConfig.identity.tags : openclawProfile?.tags || baseTags,
266
- public_enabled: socialConfig.public_enabled,
274
+ display_name: displayName,
275
+ bio,
276
+ avatar_url: avatarUrl,
277
+ tags,
278
+ public_enabled: existingProfile?.public_enabled ?? socialConfig.public_enabled,
267
279
  };
268
280
  }
269
281
 
@@ -0,0 +1,28 @@
1
+ export type NetworkMessageEnvelope<TPayload = unknown> = {
2
+ version: 1;
3
+ message_id: string;
4
+ topic: string;
5
+ source_peer_id: string;
6
+ timestamp: number;
7
+ payload: TPayload;
8
+ };
9
+ export type DecodedNetworkMessage = {
10
+ envelope: NetworkMessageEnvelope;
11
+ raw: Buffer;
12
+ };
13
+ export interface MessageEnvelopeCodec {
14
+ encode(envelope: NetworkMessageEnvelope): Buffer;
15
+ decode(raw: Buffer): DecodedNetworkMessage | null;
16
+ }
17
+ export type EnvelopeValidationOptions = {
18
+ now?: number;
19
+ max_future_drift_ms: number;
20
+ max_past_drift_ms: number;
21
+ };
22
+ export type EnvelopeValidationResult = {
23
+ ok: boolean;
24
+ reason?: "not_object" | "invalid_version" | "invalid_message_id" | "invalid_topic" | "invalid_source_peer_id" | "invalid_timestamp" | "missing_payload" | "timestamp_future_drift" | "timestamp_past_drift";
25
+ envelope?: NetworkMessageEnvelope;
26
+ drift_ms?: number;
27
+ };
28
+ export declare function validateNetworkMessageEnvelope(value: unknown, options: EnvelopeValidationOptions): EnvelopeValidationResult;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateNetworkMessageEnvelope = validateNetworkMessageEnvelope;
4
+ function validateNetworkMessageEnvelope(value, options) {
5
+ if (typeof value !== "object" || value === null) {
6
+ return { ok: false, reason: "not_object" };
7
+ }
8
+ const envelope = value;
9
+ if (envelope.version !== 1) {
10
+ return { ok: false, reason: "invalid_version" };
11
+ }
12
+ if (typeof envelope.message_id !== "string" || envelope.message_id.trim().length === 0) {
13
+ return { ok: false, reason: "invalid_message_id" };
14
+ }
15
+ if (typeof envelope.topic !== "string" || envelope.topic.trim().length === 0) {
16
+ return { ok: false, reason: "invalid_topic" };
17
+ }
18
+ if (typeof envelope.source_peer_id !== "string" || envelope.source_peer_id.trim().length === 0) {
19
+ return { ok: false, reason: "invalid_source_peer_id" };
20
+ }
21
+ if (!Number.isFinite(envelope.timestamp)) {
22
+ return { ok: false, reason: "invalid_timestamp" };
23
+ }
24
+ if (!("payload" in envelope)) {
25
+ return { ok: false, reason: "missing_payload" };
26
+ }
27
+ const now = options.now ?? Date.now();
28
+ const drift = Number(envelope.timestamp) - now;
29
+ if (drift > options.max_future_drift_ms) {
30
+ return { ok: false, reason: "timestamp_future_drift", drift_ms: drift };
31
+ }
32
+ if (drift < -options.max_past_drift_ms) {
33
+ return { ok: false, reason: "timestamp_past_drift", drift_ms: drift };
34
+ }
35
+ return { ok: true, envelope: envelope, drift_ms: drift };
36
+ }
@@ -0,0 +1,43 @@
1
+ import { NetworkMessageEnvelope } from "./messageEnvelope";
2
+ export type PeerStatus = "online" | "stale";
3
+ export type PeerSnapshot = {
4
+ peer_id: string;
5
+ first_seen_at: number;
6
+ last_seen_at: number;
7
+ status: PeerStatus;
8
+ stale_since_at?: number;
9
+ messages_seen: number;
10
+ meta?: Record<string, unknown>;
11
+ };
12
+ export type PeerDiscoveryContext = {
13
+ self_peer_id: string;
14
+ publishControl: (topic: string, payload: unknown) => Promise<void>;
15
+ };
16
+ export type PeerDiscoveryStats = {
17
+ observe_calls: number;
18
+ peers_added: number;
19
+ peers_removed: number;
20
+ peers_marked_stale: number;
21
+ heartbeat_sent: number;
22
+ heartbeat_send_errors: number;
23
+ reconcile_runs: number;
24
+ last_observed_at: number;
25
+ last_heartbeat_at: number;
26
+ last_reconcile_at: number;
27
+ last_error_at: number;
28
+ };
29
+ export type PeerDiscoveryConfigSnapshot = {
30
+ discovery: string;
31
+ heartbeat_topic?: string;
32
+ heartbeat_interval_ms?: number;
33
+ stale_after_ms?: number;
34
+ remove_after_ms?: number;
35
+ };
36
+ export interface PeerDiscovery {
37
+ start(context: PeerDiscoveryContext): Promise<void>;
38
+ stop(): Promise<void>;
39
+ observeEnvelope(envelope: NetworkMessageEnvelope): void;
40
+ listPeers(): PeerSnapshot[];
41
+ getStats?(): PeerDiscoveryStats;
42
+ getConfig?(): PeerDiscoveryConfigSnapshot;
43
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,4 @@
1
+ export interface TopicCodec {
2
+ encode(topic: string, payload: unknown): unknown;
3
+ decode(topic: string, payload: unknown): unknown;
4
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,36 @@
1
+ export type TransportMessageMeta = {
2
+ remote_address: string;
3
+ remote_port: number;
4
+ transport: string;
5
+ };
6
+ export type TransportLifecycleState = "stopped" | "starting" | "running" | "stopping" | "error";
7
+ export type TransportStats = {
8
+ starts: number;
9
+ stops: number;
10
+ start_errors: number;
11
+ stop_errors: number;
12
+ sent_messages: number;
13
+ sent_bytes: number;
14
+ send_errors: number;
15
+ received_messages: number;
16
+ received_bytes: number;
17
+ receive_errors: number;
18
+ last_sent_at: number;
19
+ last_received_at: number;
20
+ last_error_at: number;
21
+ };
22
+ export type TransportConfigSnapshot = {
23
+ transport: string;
24
+ state: TransportLifecycleState;
25
+ bind_address?: string;
26
+ broadcast_address?: string;
27
+ port?: number;
28
+ };
29
+ export interface NetworkTransport {
30
+ start(): Promise<void>;
31
+ stop(): Promise<void>;
32
+ send(data: Buffer): Promise<void>;
33
+ onMessage(handler: (data: Buffer, meta: TransportMessageMeta) => void): () => void;
34
+ getStats?(): TransportStats;
35
+ getConfig?(): TransportConfigSnapshot;
36
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,5 @@
1
+ import { DecodedNetworkMessage, MessageEnvelopeCodec, NetworkMessageEnvelope } from "../abstractions/messageEnvelope";
2
+ export declare class JsonMessageEnvelopeCodec implements MessageEnvelopeCodec {
3
+ encode(envelope: NetworkMessageEnvelope): Buffer;
4
+ decode(raw: Buffer): DecodedNetworkMessage | null;
5
+ }
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JsonMessageEnvelopeCodec = void 0;
4
+ class JsonMessageEnvelopeCodec {
5
+ encode(envelope) {
6
+ return Buffer.from(JSON.stringify(envelope), "utf8");
7
+ }
8
+ decode(raw) {
9
+ try {
10
+ const parsed = JSON.parse(raw.toString("utf8"));
11
+ if (typeof parsed !== "object" || parsed === null) {
12
+ return null;
13
+ }
14
+ return {
15
+ envelope: parsed,
16
+ raw,
17
+ };
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ }
23
+ }
24
+ exports.JsonMessageEnvelopeCodec = JsonMessageEnvelopeCodec;
@@ -0,0 +1,5 @@
1
+ import { TopicCodec } from "../abstractions/topicCodec";
2
+ export declare class JsonTopicCodec implements TopicCodec {
3
+ encode(_topic: string, payload: unknown): unknown;
4
+ decode(_topic: string, payload: unknown): unknown;
5
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JsonTopicCodec = void 0;
4
+ class JsonTopicCodec {
5
+ encode(_topic, payload) {
6
+ return payload;
7
+ }
8
+ decode(_topic, payload) {
9
+ return payload;
10
+ }
11
+ }
12
+ exports.JsonTopicCodec = JsonTopicCodec;
@@ -0,0 +1,28 @@
1
+ import { PeerDiscovery, PeerDiscoveryConfigSnapshot, PeerDiscoveryContext, PeerDiscoveryStats, PeerSnapshot } from "../abstractions/peerDiscovery";
2
+ import { NetworkMessageEnvelope } from "../abstractions/messageEnvelope";
3
+ type HeartbeatPeerDiscoveryOptions = {
4
+ heartbeatIntervalMs?: number;
5
+ staleAfterMs?: number;
6
+ removeAfterMs?: number;
7
+ topic?: string;
8
+ };
9
+ export declare class HeartbeatPeerDiscovery implements PeerDiscovery {
10
+ private peers;
11
+ private timer;
12
+ private context;
13
+ private heartbeatIntervalMs;
14
+ private staleAfterMs;
15
+ private removeAfterMs;
16
+ private topic;
17
+ private stats;
18
+ constructor(options?: HeartbeatPeerDiscoveryOptions);
19
+ start(context: PeerDiscoveryContext): Promise<void>;
20
+ stop(): Promise<void>;
21
+ observeEnvelope(envelope: NetworkMessageEnvelope): void;
22
+ listPeers(): PeerSnapshot[];
23
+ getStats(): PeerDiscoveryStats;
24
+ getConfig(): PeerDiscoveryConfigSnapshot;
25
+ private sendHeartbeat;
26
+ private reconcilePeerHealth;
27
+ }
28
+ export {};
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HeartbeatPeerDiscovery = void 0;
4
+ class HeartbeatPeerDiscovery {
5
+ peers = new Map();
6
+ timer = null;
7
+ context = null;
8
+ heartbeatIntervalMs;
9
+ staleAfterMs;
10
+ removeAfterMs;
11
+ topic;
12
+ stats = {
13
+ observe_calls: 0,
14
+ peers_added: 0,
15
+ peers_removed: 0,
16
+ peers_marked_stale: 0,
17
+ heartbeat_sent: 0,
18
+ heartbeat_send_errors: 0,
19
+ reconcile_runs: 0,
20
+ last_observed_at: 0,
21
+ last_heartbeat_at: 0,
22
+ last_reconcile_at: 0,
23
+ last_error_at: 0,
24
+ };
25
+ constructor(options = {}) {
26
+ this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? 12_000;
27
+ this.staleAfterMs = options.staleAfterMs ?? 45_000;
28
+ this.removeAfterMs = options.removeAfterMs ?? 180_000;
29
+ this.topic = options.topic ?? "__discovery/heartbeat";
30
+ }
31
+ async start(context) {
32
+ this.context = context;
33
+ this.reconcilePeerHealth();
34
+ await this.sendHeartbeat();
35
+ this.timer = setInterval(async () => {
36
+ await this.sendHeartbeat();
37
+ this.reconcilePeerHealth();
38
+ }, this.heartbeatIntervalMs);
39
+ }
40
+ async stop() {
41
+ if (this.timer) {
42
+ clearInterval(this.timer);
43
+ this.timer = null;
44
+ }
45
+ }
46
+ observeEnvelope(envelope) {
47
+ this.stats.observe_calls += 1;
48
+ this.stats.last_observed_at = Date.now();
49
+ if (!this.context) {
50
+ return;
51
+ }
52
+ if (envelope.source_peer_id === this.context.self_peer_id) {
53
+ return;
54
+ }
55
+ const now = Date.now();
56
+ const existing = this.peers.get(envelope.source_peer_id);
57
+ if (!existing) {
58
+ this.stats.peers_added += 1;
59
+ }
60
+ this.peers.set(envelope.source_peer_id, {
61
+ peer_id: envelope.source_peer_id,
62
+ first_seen_at: existing?.first_seen_at ?? now,
63
+ last_seen_at: now,
64
+ status: "online",
65
+ stale_since_at: undefined,
66
+ messages_seen: (existing?.messages_seen ?? 0) + 1,
67
+ meta: envelope.topic === this.topic && typeof envelope.payload === "object" && envelope.payload !== null
68
+ ? envelope.payload
69
+ : existing?.meta,
70
+ });
71
+ }
72
+ listPeers() {
73
+ this.reconcilePeerHealth();
74
+ return Array.from(this.peers.values()).sort((a, b) => {
75
+ const score = (p) => (p.status === "online" ? 1 : 0);
76
+ const byStatus = score(b) - score(a);
77
+ if (byStatus !== 0) {
78
+ return byStatus;
79
+ }
80
+ return b.last_seen_at - a.last_seen_at;
81
+ });
82
+ }
83
+ getStats() {
84
+ return { ...this.stats };
85
+ }
86
+ getConfig() {
87
+ return {
88
+ discovery: "heartbeat-peer-discovery",
89
+ heartbeat_topic: this.topic,
90
+ heartbeat_interval_ms: this.heartbeatIntervalMs,
91
+ stale_after_ms: this.staleAfterMs,
92
+ remove_after_ms: this.removeAfterMs,
93
+ };
94
+ }
95
+ async sendHeartbeat() {
96
+ if (!this.context) {
97
+ return;
98
+ }
99
+ try {
100
+ await this.context.publishControl(this.topic, {
101
+ kind: "heartbeat",
102
+ at: Date.now(),
103
+ });
104
+ this.stats.heartbeat_sent += 1;
105
+ this.stats.last_heartbeat_at = Date.now();
106
+ }
107
+ catch {
108
+ this.stats.heartbeat_send_errors += 1;
109
+ this.stats.last_error_at = Date.now();
110
+ }
111
+ }
112
+ reconcilePeerHealth() {
113
+ const now = Date.now();
114
+ this.stats.reconcile_runs += 1;
115
+ this.stats.last_reconcile_at = now;
116
+ for (const [peerId, peer] of this.peers.entries()) {
117
+ const age = now - peer.last_seen_at;
118
+ if (age > this.removeAfterMs) {
119
+ this.peers.delete(peerId);
120
+ this.stats.peers_removed += 1;
121
+ continue;
122
+ }
123
+ if (age > this.staleAfterMs) {
124
+ if (peer.status !== "stale") {
125
+ this.stats.peers_marked_stale += 1;
126
+ }
127
+ this.peers.set(peerId, {
128
+ ...peer,
129
+ status: "stale",
130
+ stale_since_at: peer.stale_since_at ?? now,
131
+ });
132
+ continue;
133
+ }
134
+ if (peer.status !== "online") {
135
+ this.peers.set(peerId, {
136
+ ...peer,
137
+ status: "online",
138
+ stale_since_at: undefined,
139
+ });
140
+ }
141
+ }
142
+ }
143
+ }
144
+ exports.HeartbeatPeerDiscovery = HeartbeatPeerDiscovery;
@@ -0,0 +1,14 @@
1
+ export * from "./types";
2
+ export * from "./mock";
3
+ export * from "./localEventBus";
4
+ export * from "./realPreview";
5
+ export * from "./webrtcPreview";
6
+ export * from "./relayPreview";
7
+ export * from "./abstractions/messageEnvelope";
8
+ export * from "./abstractions/topicCodec";
9
+ export * from "./abstractions/transport";
10
+ export * from "./abstractions/peerDiscovery";
11
+ export * from "./codec/jsonMessageEnvelopeCodec";
12
+ export * from "./codec/jsonTopicCodec";
13
+ export * from "./discovery/heartbeatPeerDiscovery";
14
+ export * from "./transport/udpLanBroadcastTransport";
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types"), exports);
18
+ __exportStar(require("./mock"), exports);
19
+ __exportStar(require("./localEventBus"), exports);
20
+ __exportStar(require("./realPreview"), exports);
21
+ __exportStar(require("./webrtcPreview"), exports);
22
+ __exportStar(require("./relayPreview"), exports);
23
+ __exportStar(require("./abstractions/messageEnvelope"), exports);
24
+ __exportStar(require("./abstractions/topicCodec"), exports);
25
+ __exportStar(require("./abstractions/transport"), exports);
26
+ __exportStar(require("./abstractions/peerDiscovery"), exports);
27
+ __exportStar(require("./codec/jsonMessageEnvelopeCodec"), exports);
28
+ __exportStar(require("./codec/jsonTopicCodec"), exports);
29
+ __exportStar(require("./discovery/heartbeatPeerDiscovery"), exports);
30
+ __exportStar(require("./transport/udpLanBroadcastTransport"), exports);
@@ -0,0 +1,9 @@
1
+ import { NetworkAdapter } from "./types";
2
+ export declare class LocalEventBusAdapter implements NetworkAdapter {
3
+ private started;
4
+ private emitter;
5
+ start(): Promise<void>;
6
+ stop(): Promise<void>;
7
+ publish(topic: string, data: any): Promise<void>;
8
+ subscribe(topic: string, handler: (data: any) => void): void;
9
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocalEventBusAdapter = void 0;
4
+ const events_1 = require("events");
5
+ const CHANNEL_NAME = "silicaclaw-local-event-bus";
6
+ function getNodeBus() {
7
+ const g = globalThis;
8
+ if (!g.__silicaclaw_bus) {
9
+ g.__silicaclaw_bus = new events_1.EventEmitter();
10
+ }
11
+ return g.__silicaclaw_bus;
12
+ }
13
+ class LocalEventBusAdapter {
14
+ started = false;
15
+ emitter = getNodeBus();
16
+ async start() {
17
+ this.started = true;
18
+ }
19
+ async stop() {
20
+ this.started = false;
21
+ }
22
+ async publish(topic, data) {
23
+ if (!this.started) {
24
+ return;
25
+ }
26
+ if (typeof BroadcastChannel !== "undefined") {
27
+ const channel = new BroadcastChannel(CHANNEL_NAME);
28
+ channel.postMessage({ topic, data });
29
+ channel.close();
30
+ return;
31
+ }
32
+ setImmediate(() => this.emitter.emit(topic, data));
33
+ }
34
+ subscribe(topic, handler) {
35
+ if (typeof BroadcastChannel !== "undefined") {
36
+ const channel = new BroadcastChannel(CHANNEL_NAME);
37
+ channel.onmessage = (event) => {
38
+ if (event.data?.topic === topic) {
39
+ handler(event.data.data);
40
+ }
41
+ };
42
+ return;
43
+ }
44
+ this.emitter.on(topic, handler);
45
+ }
46
+ }
47
+ exports.LocalEventBusAdapter = LocalEventBusAdapter;
@@ -0,0 +1,8 @@
1
+ import { NetworkAdapter } from "./types";
2
+ export declare class MockNetworkAdapter implements NetworkAdapter {
3
+ private started;
4
+ start(): Promise<void>;
5
+ stop(): Promise<void>;
6
+ publish(topic: string, data: any): Promise<void>;
7
+ subscribe(topic: string, handler: (data: any) => void): void;
8
+ }
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MockNetworkAdapter = void 0;
4
+ const events_1 = require("events");
5
+ const bus = new events_1.EventEmitter();
6
+ class MockNetworkAdapter {
7
+ started = false;
8
+ async start() {
9
+ this.started = true;
10
+ }
11
+ async stop() {
12
+ this.started = false;
13
+ }
14
+ async publish(topic, data) {
15
+ if (!this.started) {
16
+ return;
17
+ }
18
+ setImmediate(() => bus.emit(topic, data));
19
+ }
20
+ subscribe(topic, handler) {
21
+ bus.on(topic, handler);
22
+ }
23
+ }
24
+ exports.MockNetworkAdapter = MockNetworkAdapter;