@silicaclaw/cli 2026.3.19-19 → 2026.3.19-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 +12 -0
- package/INSTALL.md +12 -8
- package/README.md +16 -12
- package/VERSION +1 -1
- package/apps/local-console/dist/apps/local-console/src/server.d.ts +2 -0
- package/apps/local-console/dist/apps/local-console/src/server.js +25 -1
- package/apps/local-console/public/app/social.js +17 -1
- package/apps/local-console/public/app/translations.js +8 -4
- package/apps/local-console/src/server.ts +32 -1
- package/dist/apps/local-console/src/server.d.ts +1 -0
- package/dist/apps/local-console/src/server.js +555 -0
- package/docs/NEW_USER_INSTALL.md +13 -10
- package/docs/NEW_USER_OPERATIONS.md +3 -3
- package/node_modules/@silicaclaw/storage/dist/config/silicaclaw-defaults.json +19 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/crypto.d.ts +6 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/crypto.js +50 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/directory.d.ts +17 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/directory.js +145 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/identity.d.ts +2 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/identity.js +18 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/index.d.ts +12 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/index.js +28 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/indexing.d.ts +6 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/indexing.js +43 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/presence.d.ts +4 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/presence.js +23 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/profile.d.ts +4 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/profile.js +39 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/publicProfileSummary.d.ts +70 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/publicProfileSummary.js +103 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialConfig.d.ts +100 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialConfig.js +300 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialMessage.d.ts +19 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialMessage.js +69 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialResolver.d.ts +46 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialResolver.js +237 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialTemplate.d.ts +2 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialTemplate.js +90 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/types.d.ts +59 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/types.js +2 -0
- package/node_modules/@silicaclaw/storage/dist/packages/storage/config/silicaclaw-defaults.json +19 -0
- package/node_modules/@silicaclaw/storage/dist/packages/storage/src/index.d.ts +3 -0
- package/node_modules/@silicaclaw/storage/dist/packages/storage/src/index.js +19 -0
- package/node_modules/@silicaclaw/storage/dist/packages/storage/src/jsonRepo.d.ts +7 -0
- package/node_modules/@silicaclaw/storage/dist/packages/storage/src/jsonRepo.js +29 -0
- package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.d.ts +61 -0
- package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.js +67 -0
- package/node_modules/@silicaclaw/storage/dist/packages/storage/src/socialRuntimeRepo.d.ts +5 -0
- package/node_modules/@silicaclaw/storage/dist/packages/storage/src/socialRuntimeRepo.js +57 -0
- package/node_modules/@silicaclaw/storage/tsconfig.json +1 -6
- package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -1
- package/openclaw-skills/silicaclaw-broadcast/manifest.json +1 -1
- package/package.json +3 -1
- package/packages/storage/dist/config/silicaclaw-defaults.json +19 -0
- package/packages/storage/dist/packages/core/src/crypto.d.ts +6 -0
- package/packages/storage/dist/packages/core/src/crypto.js +50 -0
- package/packages/storage/dist/packages/core/src/directory.d.ts +17 -0
- package/packages/storage/dist/packages/core/src/directory.js +145 -0
- package/packages/storage/dist/packages/core/src/identity.d.ts +2 -0
- package/packages/storage/dist/packages/core/src/identity.js +18 -0
- package/packages/storage/dist/packages/core/src/index.d.ts +12 -0
- package/packages/storage/dist/packages/core/src/index.js +28 -0
- package/packages/storage/dist/packages/core/src/indexing.d.ts +6 -0
- package/packages/storage/dist/packages/core/src/indexing.js +43 -0
- package/packages/storage/dist/packages/core/src/presence.d.ts +4 -0
- package/packages/storage/dist/packages/core/src/presence.js +23 -0
- package/packages/storage/dist/packages/core/src/profile.d.ts +4 -0
- package/packages/storage/dist/packages/core/src/profile.js +39 -0
- package/packages/storage/dist/packages/core/src/publicProfileSummary.d.ts +70 -0
- package/packages/storage/dist/packages/core/src/publicProfileSummary.js +103 -0
- package/packages/storage/dist/packages/core/src/socialConfig.d.ts +100 -0
- package/packages/storage/dist/packages/core/src/socialConfig.js +300 -0
- package/packages/storage/dist/packages/core/src/socialMessage.d.ts +19 -0
- package/packages/storage/dist/packages/core/src/socialMessage.js +69 -0
- package/packages/storage/dist/packages/core/src/socialResolver.d.ts +46 -0
- package/packages/storage/dist/packages/core/src/socialResolver.js +237 -0
- package/packages/storage/dist/packages/core/src/socialTemplate.d.ts +2 -0
- package/packages/storage/dist/packages/core/src/socialTemplate.js +90 -0
- package/packages/storage/dist/packages/core/src/types.d.ts +59 -0
- package/packages/storage/dist/packages/core/src/types.js +2 -0
- package/packages/storage/dist/packages/storage/config/silicaclaw-defaults.json +19 -0
- package/packages/storage/dist/packages/storage/src/index.d.ts +3 -0
- package/packages/storage/dist/packages/storage/src/index.js +19 -0
- package/packages/storage/dist/packages/storage/src/jsonRepo.d.ts +7 -0
- package/packages/storage/dist/packages/storage/src/jsonRepo.js +29 -0
- package/packages/storage/dist/packages/storage/src/repos.d.ts +61 -0
- package/packages/storage/dist/packages/storage/src/repos.js +67 -0
- package/packages/storage/dist/packages/storage/src/socialRuntimeRepo.d.ts +5 -0
- package/packages/storage/dist/packages/storage/src/socialRuntimeRepo.js +57 -0
- package/packages/storage/tsconfig.json +1 -6
- package/scripts/quickstart.sh +1 -1
- package/scripts/silicaclaw-cli.mjs +2 -1
- package/scripts/silicaclaw-gateway.mjs +209 -32
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const express_1 = __importDefault(require("express"));
|
|
7
|
+
const cors_1 = __importDefault(require("cors"));
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
const fs_1 = require("fs");
|
|
10
|
+
const core_1 = require("@silicaclaw/core");
|
|
11
|
+
const network_1 = require("@silicaclaw/network");
|
|
12
|
+
const storage_1 = require("@silicaclaw/storage");
|
|
13
|
+
const BROADCAST_INTERVAL_MS = 10_000;
|
|
14
|
+
const PRESENCE_TTL_MS = Number(process.env.PRESENCE_TTL_MS || 30_000);
|
|
15
|
+
class LocalNodeService {
|
|
16
|
+
identityRepo = new storage_1.IdentityRepo(process.cwd());
|
|
17
|
+
profileRepo = new storage_1.ProfileRepo(process.cwd());
|
|
18
|
+
cacheRepo = new storage_1.CacheRepo(process.cwd());
|
|
19
|
+
logRepo = new storage_1.LogRepo(process.cwd());
|
|
20
|
+
identity = null;
|
|
21
|
+
profile = null;
|
|
22
|
+
directory = (0, core_1.createEmptyDirectoryState)();
|
|
23
|
+
receivedCount = 0;
|
|
24
|
+
broadcastCount = 0;
|
|
25
|
+
lastMessageAt = 0;
|
|
26
|
+
lastBroadcastAt = 0;
|
|
27
|
+
broadcaster = null;
|
|
28
|
+
broadcastEnabled = true;
|
|
29
|
+
receivedByTopic = {};
|
|
30
|
+
publishedByTopic = {};
|
|
31
|
+
initState = {
|
|
32
|
+
identity_auto_created: false,
|
|
33
|
+
profile_auto_created: false,
|
|
34
|
+
initialized_at: 0,
|
|
35
|
+
};
|
|
36
|
+
network;
|
|
37
|
+
adapterMode;
|
|
38
|
+
networkNamespace;
|
|
39
|
+
networkPort;
|
|
40
|
+
constructor() {
|
|
41
|
+
this.networkNamespace = process.env.NETWORK_NAMESPACE || "silicaclaw.preview";
|
|
42
|
+
this.networkPort = Number(process.env.NETWORK_PORT || 44123);
|
|
43
|
+
const mode = process.env.NETWORK_ADAPTER;
|
|
44
|
+
if (mode === "mock") {
|
|
45
|
+
this.network = new network_1.MockNetworkAdapter();
|
|
46
|
+
this.adapterMode = "mock";
|
|
47
|
+
this.networkPort = null;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (mode === "real-preview") {
|
|
51
|
+
this.network = new network_1.RealNetworkAdapterPreview({
|
|
52
|
+
namespace: this.networkNamespace,
|
|
53
|
+
transport: new network_1.UdpLanBroadcastTransport({
|
|
54
|
+
port: this.networkPort,
|
|
55
|
+
}),
|
|
56
|
+
});
|
|
57
|
+
this.adapterMode = "real-preview";
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
this.network = new network_1.LocalEventBusAdapter();
|
|
61
|
+
this.adapterMode = "local-event-bus";
|
|
62
|
+
this.networkPort = null;
|
|
63
|
+
}
|
|
64
|
+
async start() {
|
|
65
|
+
await this.hydrateFromDisk();
|
|
66
|
+
await this.network.start();
|
|
67
|
+
this.network.subscribe("profile", (data) => {
|
|
68
|
+
this.onMessage("profile", data);
|
|
69
|
+
});
|
|
70
|
+
this.network.subscribe("presence", (data) => {
|
|
71
|
+
this.onMessage("presence", data);
|
|
72
|
+
});
|
|
73
|
+
this.network.subscribe("index", (data) => {
|
|
74
|
+
this.onMessage("index", data);
|
|
75
|
+
});
|
|
76
|
+
this.startBroadcastLoop();
|
|
77
|
+
await this.log("info", "Local node started");
|
|
78
|
+
}
|
|
79
|
+
async stop() {
|
|
80
|
+
if (this.broadcaster) {
|
|
81
|
+
clearInterval(this.broadcaster);
|
|
82
|
+
this.broadcaster = null;
|
|
83
|
+
}
|
|
84
|
+
await this.network.stop();
|
|
85
|
+
}
|
|
86
|
+
getOverview() {
|
|
87
|
+
this.compactCacheInMemory();
|
|
88
|
+
const profiles = Object.values(this.directory.profiles);
|
|
89
|
+
const onlineCount = profiles.filter((profile) => (0, core_1.isAgentOnline)(this.directory.presence[profile.agent_id], Date.now(), PRESENCE_TTL_MS)).length;
|
|
90
|
+
return {
|
|
91
|
+
agent_id: this.identity?.agent_id ?? "",
|
|
92
|
+
public_enabled: Boolean(this.profile?.public_enabled),
|
|
93
|
+
broadcast_enabled: this.broadcastEnabled,
|
|
94
|
+
last_broadcast_at: this.lastBroadcastAt,
|
|
95
|
+
discovered_count: profiles.length,
|
|
96
|
+
online_count: onlineCount,
|
|
97
|
+
offline_count: Math.max(0, profiles.length - onlineCount),
|
|
98
|
+
init_state: this.initState,
|
|
99
|
+
presence_ttl_ms: PRESENCE_TTL_MS,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
getNetworkSummary() {
|
|
103
|
+
const diagnostics = this.getRealAdapterDiagnostics();
|
|
104
|
+
const peerCount = diagnostics?.peers.total ?? 0;
|
|
105
|
+
return {
|
|
106
|
+
status: "running",
|
|
107
|
+
adapter: this.adapterMode,
|
|
108
|
+
received_count: this.receivedCount,
|
|
109
|
+
broadcast_count: this.broadcastCount,
|
|
110
|
+
last_message_at: this.lastMessageAt,
|
|
111
|
+
last_broadcast_at: this.lastBroadcastAt,
|
|
112
|
+
received_by_topic: this.receivedByTopic,
|
|
113
|
+
published_by_topic: this.publishedByTopic,
|
|
114
|
+
peers_discovered: peerCount,
|
|
115
|
+
namespace: diagnostics?.namespace ?? this.networkNamespace,
|
|
116
|
+
port: this.networkPort,
|
|
117
|
+
components: diagnostics?.components ?? {
|
|
118
|
+
transport: "-",
|
|
119
|
+
discovery: "-",
|
|
120
|
+
envelope_codec: "-",
|
|
121
|
+
topic_codec: "-",
|
|
122
|
+
},
|
|
123
|
+
real_preview_stats: diagnostics?.stats ?? null,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
getNetworkConfig() {
|
|
127
|
+
const diagnostics = this.getRealAdapterDiagnostics();
|
|
128
|
+
return {
|
|
129
|
+
adapter: this.adapterMode,
|
|
130
|
+
namespace: diagnostics?.namespace ?? this.networkNamespace,
|
|
131
|
+
port: this.networkPort,
|
|
132
|
+
components: diagnostics?.components ?? {
|
|
133
|
+
transport: "-",
|
|
134
|
+
discovery: "-",
|
|
135
|
+
envelope_codec: "-",
|
|
136
|
+
topic_codec: "-",
|
|
137
|
+
},
|
|
138
|
+
limits: diagnostics?.limits ?? null,
|
|
139
|
+
demo_mode: this.adapterMode === "real-preview" ? "lan-preview" : "local-process",
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
getNetworkStats() {
|
|
143
|
+
const diagnostics = this.getRealAdapterDiagnostics();
|
|
144
|
+
const peers = diagnostics?.peers.items ?? [];
|
|
145
|
+
const online = peers.filter((peer) => peer.status === "online").length;
|
|
146
|
+
return {
|
|
147
|
+
adapter: this.adapterMode,
|
|
148
|
+
message_counters: {
|
|
149
|
+
received_total: this.receivedCount,
|
|
150
|
+
broadcast_total: this.broadcastCount,
|
|
151
|
+
last_message_at: this.lastMessageAt,
|
|
152
|
+
last_broadcast_at: this.lastBroadcastAt,
|
|
153
|
+
received_by_topic: this.receivedByTopic,
|
|
154
|
+
published_by_topic: this.publishedByTopic,
|
|
155
|
+
},
|
|
156
|
+
peer_counters: {
|
|
157
|
+
total: peers.length,
|
|
158
|
+
online,
|
|
159
|
+
stale: Math.max(0, peers.length - online),
|
|
160
|
+
},
|
|
161
|
+
adapter_stats: diagnostics?.stats ?? null,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
getPeersSummary() {
|
|
165
|
+
const diagnostics = this.getRealAdapterDiagnostics();
|
|
166
|
+
if (!diagnostics) {
|
|
167
|
+
return {
|
|
168
|
+
adapter: this.adapterMode,
|
|
169
|
+
namespace: this.networkNamespace,
|
|
170
|
+
total: 0,
|
|
171
|
+
online: 0,
|
|
172
|
+
stale: 0,
|
|
173
|
+
items: [],
|
|
174
|
+
stats: null,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
adapter: diagnostics.adapter,
|
|
179
|
+
namespace: diagnostics.namespace,
|
|
180
|
+
total: diagnostics.peers.total,
|
|
181
|
+
online: diagnostics.peers.online,
|
|
182
|
+
stale: diagnostics.peers.stale,
|
|
183
|
+
items: diagnostics.peers.items,
|
|
184
|
+
stats: diagnostics.stats,
|
|
185
|
+
components: diagnostics.components,
|
|
186
|
+
limits: diagnostics.limits,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
getDirectory() {
|
|
190
|
+
this.compactCacheInMemory();
|
|
191
|
+
return this.directory;
|
|
192
|
+
}
|
|
193
|
+
search(keyword) {
|
|
194
|
+
this.compactCacheInMemory();
|
|
195
|
+
return (0, core_1.searchDirectory)(this.directory, keyword, { presenceTTLms: PRESENCE_TTL_MS }).map((profile) => {
|
|
196
|
+
const lastSeenAt = this.directory.presence[profile.agent_id] ?? 0;
|
|
197
|
+
return {
|
|
198
|
+
...profile,
|
|
199
|
+
last_seen_at: lastSeenAt,
|
|
200
|
+
online: (0, core_1.isAgentOnline)(lastSeenAt, Date.now(), PRESENCE_TTL_MS),
|
|
201
|
+
};
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
getProfile() {
|
|
205
|
+
return this.profile;
|
|
206
|
+
}
|
|
207
|
+
getIdentity() {
|
|
208
|
+
return this.identity;
|
|
209
|
+
}
|
|
210
|
+
async getLogs() {
|
|
211
|
+
return this.logRepo.get();
|
|
212
|
+
}
|
|
213
|
+
async ensureIdentity() {
|
|
214
|
+
if (this.identity) {
|
|
215
|
+
return this.identity;
|
|
216
|
+
}
|
|
217
|
+
const identity = (0, core_1.createIdentity)();
|
|
218
|
+
this.identity = identity;
|
|
219
|
+
await this.identityRepo.set(identity);
|
|
220
|
+
this.initState.identity_auto_created = true;
|
|
221
|
+
const seededProfile = (0, core_1.signProfile)((0, core_1.createDefaultProfileInput)(identity.agent_id), identity);
|
|
222
|
+
this.profile = seededProfile;
|
|
223
|
+
await this.profileRepo.set(seededProfile);
|
|
224
|
+
this.initState.profile_auto_created = true;
|
|
225
|
+
await this.log("info", `Identity created automatically: ${identity.agent_id.slice(0, 12)}`);
|
|
226
|
+
return identity;
|
|
227
|
+
}
|
|
228
|
+
async updateProfile(input) {
|
|
229
|
+
const identity = await this.ensureIdentity();
|
|
230
|
+
const base = this.profile ?? (0, core_1.signProfile)((0, core_1.createDefaultProfileInput)(identity.agent_id), identity);
|
|
231
|
+
const next = (0, core_1.signProfile)({
|
|
232
|
+
agent_id: identity.agent_id,
|
|
233
|
+
display_name: input.display_name ?? base.display_name,
|
|
234
|
+
bio: input.bio ?? base.bio,
|
|
235
|
+
tags: input.tags ?? base.tags,
|
|
236
|
+
avatar_url: input.avatar_url ?? base.avatar_url,
|
|
237
|
+
public_enabled: input.public_enabled ?? base.public_enabled,
|
|
238
|
+
}, identity);
|
|
239
|
+
this.profile = next;
|
|
240
|
+
this.directory = (0, core_1.ingestProfileRecord)(this.directory, { type: "profile", profile: next });
|
|
241
|
+
await this.profileRepo.set(next);
|
|
242
|
+
await this.persistCache();
|
|
243
|
+
await this.log("info", `Profile updated (public=${next.public_enabled})`);
|
|
244
|
+
if (next.public_enabled && this.broadcastEnabled) {
|
|
245
|
+
await this.broadcastNow("profile_update");
|
|
246
|
+
}
|
|
247
|
+
return next;
|
|
248
|
+
}
|
|
249
|
+
async refreshCache() {
|
|
250
|
+
const removed = this.compactCacheInMemory();
|
|
251
|
+
await this.persistCache();
|
|
252
|
+
await this.log("info", `Cache refreshed (expired presence removed=${removed})`);
|
|
253
|
+
return {
|
|
254
|
+
removed_presence: removed,
|
|
255
|
+
profile_count: Object.keys(this.directory.profiles).length,
|
|
256
|
+
index_key_count: Object.keys(this.directory.index).length,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
async setBroadcastEnabled(enabled) {
|
|
260
|
+
this.broadcastEnabled = enabled;
|
|
261
|
+
if (enabled) {
|
|
262
|
+
this.startBroadcastLoop();
|
|
263
|
+
await this.log("info", "Broadcast loop enabled");
|
|
264
|
+
if (this.profile?.public_enabled) {
|
|
265
|
+
await this.broadcastNow("manual_start");
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
if (this.broadcaster) {
|
|
270
|
+
clearInterval(this.broadcaster);
|
|
271
|
+
this.broadcaster = null;
|
|
272
|
+
}
|
|
273
|
+
await this.log("warn", "Broadcast loop paused");
|
|
274
|
+
}
|
|
275
|
+
return { broadcast_enabled: this.broadcastEnabled };
|
|
276
|
+
}
|
|
277
|
+
async broadcastNow(reason = "manual") {
|
|
278
|
+
if (!this.identity || !this.profile) {
|
|
279
|
+
return { sent: false, reason: "missing_identity_or_profile" };
|
|
280
|
+
}
|
|
281
|
+
if (!this.profile.public_enabled) {
|
|
282
|
+
return { sent: false, reason: "public_disabled" };
|
|
283
|
+
}
|
|
284
|
+
if (!this.broadcastEnabled) {
|
|
285
|
+
return { sent: false, reason: "broadcast_paused" };
|
|
286
|
+
}
|
|
287
|
+
const profileRecord = {
|
|
288
|
+
type: "profile",
|
|
289
|
+
profile: this.profile,
|
|
290
|
+
};
|
|
291
|
+
const presenceRecord = (0, core_1.signPresence)(this.identity, Date.now());
|
|
292
|
+
const indexRecords = (0, core_1.buildIndexRecords)(this.profile);
|
|
293
|
+
await this.publish("profile", profileRecord);
|
|
294
|
+
await this.publish("presence", presenceRecord);
|
|
295
|
+
for (const record of indexRecords) {
|
|
296
|
+
await this.publish("index", record);
|
|
297
|
+
}
|
|
298
|
+
this.lastBroadcastAt = Date.now();
|
|
299
|
+
this.broadcastCount += 1;
|
|
300
|
+
this.directory = (0, core_1.ingestProfileRecord)(this.directory, profileRecord);
|
|
301
|
+
this.directory = (0, core_1.ingestPresenceRecord)(this.directory, presenceRecord);
|
|
302
|
+
for (const record of indexRecords) {
|
|
303
|
+
this.directory = (0, core_1.ingestIndexRecord)(this.directory, record);
|
|
304
|
+
}
|
|
305
|
+
this.compactCacheInMemory();
|
|
306
|
+
await this.persistCache();
|
|
307
|
+
await this.log("info", `Broadcast sent (${indexRecords.length} index refs, reason=${reason})`);
|
|
308
|
+
return { sent: true, reason };
|
|
309
|
+
}
|
|
310
|
+
async hydrateFromDisk() {
|
|
311
|
+
this.initState = {
|
|
312
|
+
identity_auto_created: false,
|
|
313
|
+
profile_auto_created: false,
|
|
314
|
+
initialized_at: Date.now(),
|
|
315
|
+
};
|
|
316
|
+
this.identity = await this.identityRepo.get();
|
|
317
|
+
if (!this.identity) {
|
|
318
|
+
this.identity = (0, core_1.createIdentity)();
|
|
319
|
+
this.initState.identity_auto_created = true;
|
|
320
|
+
await this.identityRepo.set(this.identity);
|
|
321
|
+
await this.log("info", "identity.json missing, auto-generated identity");
|
|
322
|
+
}
|
|
323
|
+
this.profile = await this.profileRepo.get();
|
|
324
|
+
if (!this.profile || this.profile.agent_id !== this.identity.agent_id) {
|
|
325
|
+
this.profile = (0, core_1.signProfile)((0, core_1.createDefaultProfileInput)(this.identity.agent_id), this.identity);
|
|
326
|
+
this.initState.profile_auto_created = true;
|
|
327
|
+
await this.profileRepo.set(this.profile);
|
|
328
|
+
await this.log("info", "profile.json missing/invalid, auto-generated default profile");
|
|
329
|
+
}
|
|
330
|
+
this.directory = (0, core_1.dedupeIndex)(await this.cacheRepo.get());
|
|
331
|
+
this.directory = (0, core_1.ingestProfileRecord)(this.directory, { type: "profile", profile: this.profile });
|
|
332
|
+
this.compactCacheInMemory();
|
|
333
|
+
await this.persistCache();
|
|
334
|
+
}
|
|
335
|
+
async onMessage(topic, data) {
|
|
336
|
+
this.receivedCount += 1;
|
|
337
|
+
this.receivedByTopic[topic] = (this.receivedByTopic[topic] ?? 0) + 1;
|
|
338
|
+
this.lastMessageAt = Date.now();
|
|
339
|
+
if (topic === "profile") {
|
|
340
|
+
const record = data;
|
|
341
|
+
if (!record?.profile?.agent_id || !record?.profile?.signature) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (record.profile.agent_id === this.identity?.agent_id && this.identity) {
|
|
345
|
+
if (!(0, core_1.verifyProfile)(record.profile, this.identity.public_key)) {
|
|
346
|
+
await this.log("warn", "Rejected self profile with invalid signature");
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
this.directory = (0, core_1.ingestProfileRecord)(this.directory, record);
|
|
351
|
+
this.compactCacheInMemory();
|
|
352
|
+
await this.persistCache();
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (topic === "presence") {
|
|
356
|
+
const record = data;
|
|
357
|
+
if (!record?.agent_id || !record?.signature || typeof record.timestamp !== "number") {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (record.agent_id === this.identity?.agent_id && this.identity) {
|
|
361
|
+
if (!(0, core_1.verifyPresence)(record, this.identity.public_key)) {
|
|
362
|
+
await this.log("warn", "Rejected invalid self presence signature");
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
this.directory = (0, core_1.ingestPresenceRecord)(this.directory, record);
|
|
367
|
+
this.compactCacheInMemory();
|
|
368
|
+
await this.persistCache();
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const record = data;
|
|
372
|
+
if (!record?.key || !record?.agent_id) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
this.directory = (0, core_1.ingestIndexRecord)(this.directory, record);
|
|
376
|
+
this.directory = (0, core_1.dedupeIndex)(this.directory);
|
|
377
|
+
await this.persistCache();
|
|
378
|
+
}
|
|
379
|
+
startBroadcastLoop() {
|
|
380
|
+
if (this.broadcaster) {
|
|
381
|
+
clearInterval(this.broadcaster);
|
|
382
|
+
}
|
|
383
|
+
if (!this.broadcastEnabled) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
this.broadcaster = setInterval(async () => {
|
|
387
|
+
await this.broadcastNow("interval");
|
|
388
|
+
}, BROADCAST_INTERVAL_MS);
|
|
389
|
+
}
|
|
390
|
+
compactCacheInMemory() {
|
|
391
|
+
const cleaned = (0, core_1.cleanupExpiredPresence)(this.directory, Date.now(), PRESENCE_TTL_MS);
|
|
392
|
+
this.directory = (0, core_1.dedupeIndex)(cleaned.state);
|
|
393
|
+
return cleaned.removed;
|
|
394
|
+
}
|
|
395
|
+
async publish(topic, data) {
|
|
396
|
+
await this.network.publish(topic, data);
|
|
397
|
+
this.publishedByTopic[topic] = (this.publishedByTopic[topic] ?? 0) + 1;
|
|
398
|
+
}
|
|
399
|
+
async persistCache() {
|
|
400
|
+
await this.cacheRepo.set(this.directory);
|
|
401
|
+
}
|
|
402
|
+
async log(level, message) {
|
|
403
|
+
await this.logRepo.append({
|
|
404
|
+
level,
|
|
405
|
+
message,
|
|
406
|
+
timestamp: Date.now(),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
getRealAdapterDiagnostics() {
|
|
410
|
+
if (typeof this.network.getDiagnostics !== "function") {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
return this.network.getDiagnostics();
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
function sendOk(res, data, meta) {
|
|
417
|
+
res.json({ ok: true, data, meta });
|
|
418
|
+
}
|
|
419
|
+
function sendError(res, status, code, message, details) {
|
|
420
|
+
const error = { code, message };
|
|
421
|
+
if (details !== undefined) {
|
|
422
|
+
error.details = details;
|
|
423
|
+
}
|
|
424
|
+
res.status(status).json({ ok: false, error });
|
|
425
|
+
}
|
|
426
|
+
function asyncRoute(handler) {
|
|
427
|
+
return (req, res, next) => {
|
|
428
|
+
Promise.resolve(handler(req, res)).catch(next);
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
function resolveLocalConsoleStaticDir() {
|
|
432
|
+
const candidates = [
|
|
433
|
+
(0, path_1.resolve)(process.cwd(), "public"),
|
|
434
|
+
(0, path_1.resolve)(process.cwd(), "apps", "local-console", "public"),
|
|
435
|
+
(0, path_1.resolve)(__dirname, "..", "public"),
|
|
436
|
+
(0, path_1.resolve)(__dirname, "..", "..", "apps", "local-console", "public"),
|
|
437
|
+
];
|
|
438
|
+
for (const dir of candidates) {
|
|
439
|
+
if ((0, fs_1.existsSync)((0, path_1.resolve)(dir, "index.html"))) {
|
|
440
|
+
return dir;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return candidates[0];
|
|
444
|
+
}
|
|
445
|
+
async function main() {
|
|
446
|
+
const app = (0, express_1.default)();
|
|
447
|
+
const port = Number(process.env.PORT || 4310);
|
|
448
|
+
const staticDir = resolveLocalConsoleStaticDir();
|
|
449
|
+
const node = new LocalNodeService();
|
|
450
|
+
await node.start();
|
|
451
|
+
app.use((0, cors_1.default)({ origin: true }));
|
|
452
|
+
app.use(express_1.default.json());
|
|
453
|
+
app.get("/api/identity", (_req, res) => {
|
|
454
|
+
sendOk(res, node.getIdentity());
|
|
455
|
+
});
|
|
456
|
+
app.post("/api/identity/create", asyncRoute(async (_req, res) => {
|
|
457
|
+
const identity = await node.ensureIdentity();
|
|
458
|
+
sendOk(res, identity, { message: "Identity is ready" });
|
|
459
|
+
}));
|
|
460
|
+
app.get("/api/profile", (_req, res) => {
|
|
461
|
+
sendOk(res, node.getProfile());
|
|
462
|
+
});
|
|
463
|
+
app.put("/api/profile", asyncRoute(async (req, res) => {
|
|
464
|
+
const body = req.body;
|
|
465
|
+
const tags = Array.isArray(body.tags)
|
|
466
|
+
? body.tags.map((tag) => String(tag).trim()).filter(Boolean)
|
|
467
|
+
: undefined;
|
|
468
|
+
const profile = await node.updateProfile({
|
|
469
|
+
...body,
|
|
470
|
+
tags,
|
|
471
|
+
display_name: body.display_name?.toString() ?? undefined,
|
|
472
|
+
bio: body.bio?.toString() ?? undefined,
|
|
473
|
+
avatar_url: body.avatar_url?.toString() ?? undefined,
|
|
474
|
+
public_enabled: typeof body.public_enabled === "boolean" ? body.public_enabled : undefined,
|
|
475
|
+
});
|
|
476
|
+
sendOk(res, profile, { message: "Profile saved" });
|
|
477
|
+
}));
|
|
478
|
+
app.get("/api/overview", (_req, res) => {
|
|
479
|
+
sendOk(res, node.getOverview());
|
|
480
|
+
});
|
|
481
|
+
app.get("/api/network", (_req, res) => {
|
|
482
|
+
sendOk(res, node.getNetworkSummary());
|
|
483
|
+
});
|
|
484
|
+
app.get("/api/network/config", (_req, res) => {
|
|
485
|
+
sendOk(res, node.getNetworkConfig());
|
|
486
|
+
});
|
|
487
|
+
app.get("/api/network/stats", (_req, res) => {
|
|
488
|
+
sendOk(res, node.getNetworkStats());
|
|
489
|
+
});
|
|
490
|
+
app.get("/api/peers", (_req, res) => {
|
|
491
|
+
sendOk(res, node.getPeersSummary());
|
|
492
|
+
});
|
|
493
|
+
app.post("/api/broadcast/start", asyncRoute(async (_req, res) => {
|
|
494
|
+
const summary = await node.setBroadcastEnabled(true);
|
|
495
|
+
sendOk(res, summary, { message: "Broadcast started" });
|
|
496
|
+
}));
|
|
497
|
+
app.post("/api/broadcast/stop", asyncRoute(async (_req, res) => {
|
|
498
|
+
const summary = await node.setBroadcastEnabled(false);
|
|
499
|
+
sendOk(res, summary, { message: "Broadcast stopped" });
|
|
500
|
+
}));
|
|
501
|
+
app.post("/api/broadcast/now", asyncRoute(async (_req, res) => {
|
|
502
|
+
const result = await node.broadcastNow("manual_button");
|
|
503
|
+
sendOk(res, result, {
|
|
504
|
+
message: result.sent ? "Broadcast published" : `Broadcast skipped: ${result.reason}`,
|
|
505
|
+
});
|
|
506
|
+
}));
|
|
507
|
+
app.post("/api/cache/refresh", asyncRoute(async (_req, res) => {
|
|
508
|
+
const result = await node.refreshCache();
|
|
509
|
+
sendOk(res, result, { message: "Cache refreshed" });
|
|
510
|
+
}));
|
|
511
|
+
app.get("/api/logs", asyncRoute(async (_req, res) => {
|
|
512
|
+
sendOk(res, await node.getLogs());
|
|
513
|
+
}));
|
|
514
|
+
app.get("/api/search", (req, res) => {
|
|
515
|
+
const q = String(req.query.q ?? "");
|
|
516
|
+
sendOk(res, node.search(q));
|
|
517
|
+
});
|
|
518
|
+
app.get("/api/agents/:agentId", (req, res) => {
|
|
519
|
+
const state = node.getDirectory();
|
|
520
|
+
const agentId = req.params.agentId;
|
|
521
|
+
const profile = state.profiles[agentId];
|
|
522
|
+
if (!profile) {
|
|
523
|
+
sendError(res, 404, "AGENT_NOT_FOUND", "Agent not found", { agent_id: agentId });
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const lastSeenAt = state.presence[agentId] ?? 0;
|
|
527
|
+
sendOk(res, {
|
|
528
|
+
profile,
|
|
529
|
+
last_seen_at: lastSeenAt,
|
|
530
|
+
online: (0, core_1.isAgentOnline)(lastSeenAt, Date.now(), PRESENCE_TTL_MS),
|
|
531
|
+
presence_ttl_ms: PRESENCE_TTL_MS,
|
|
532
|
+
});
|
|
533
|
+
});
|
|
534
|
+
app.get("/api/health", (_req, res) => {
|
|
535
|
+
sendOk(res, { ok: true });
|
|
536
|
+
});
|
|
537
|
+
app.use(express_1.default.static(staticDir));
|
|
538
|
+
app.use((error, _req, res, _next) => {
|
|
539
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
540
|
+
sendError(res, 500, "INTERNAL_ERROR", message);
|
|
541
|
+
});
|
|
542
|
+
app.listen(port, () => {
|
|
543
|
+
// eslint-disable-next-line no-console
|
|
544
|
+
console.log(`SilicaClaw local-console running: http://localhost:${port}`);
|
|
545
|
+
});
|
|
546
|
+
process.on("SIGINT", async () => {
|
|
547
|
+
await node.stop();
|
|
548
|
+
process.exit(0);
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
main().catch((error) => {
|
|
552
|
+
// eslint-disable-next-line no-console
|
|
553
|
+
console.error(error);
|
|
554
|
+
process.exit(1);
|
|
555
|
+
});
|
package/docs/NEW_USER_INSTALL.md
CHANGED
|
@@ -20,9 +20,12 @@ npm -v
|
|
|
20
20
|
No global install is required.
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
npx -y @silicaclaw/cli@
|
|
23
|
+
npx -y @silicaclaw/cli@beta onboard
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
Use `@beta` for first-time setup right now. As of March 19, 2026, npm dist-tags are
|
|
27
|
+
`latest = 1.0.0-beta.0` and `beta = 2026.3.19-19`, so `@latest` is currently older.
|
|
28
|
+
|
|
26
29
|
The onboarding flow will help you:
|
|
27
30
|
|
|
28
31
|
- check your environment
|
|
@@ -57,17 +60,17 @@ In the page, confirm:
|
|
|
57
60
|
If you use `npx` only:
|
|
58
61
|
|
|
59
62
|
```bash
|
|
60
|
-
npx -y @silicaclaw/cli@
|
|
61
|
-
npx -y @silicaclaw/cli@
|
|
62
|
-
npx -y @silicaclaw/cli@
|
|
63
|
-
npx -y @silicaclaw/cli@
|
|
64
|
-
npx -y @silicaclaw/cli@
|
|
63
|
+
npx -y @silicaclaw/cli@beta install
|
|
64
|
+
npx -y @silicaclaw/cli@beta start
|
|
65
|
+
npx -y @silicaclaw/cli@beta status
|
|
66
|
+
npx -y @silicaclaw/cli@beta stop
|
|
67
|
+
npx -y @silicaclaw/cli@beta update
|
|
65
68
|
```
|
|
66
69
|
|
|
67
70
|
Recommended once per machine:
|
|
68
71
|
|
|
69
72
|
```bash
|
|
70
|
-
npx -y @silicaclaw/cli@
|
|
73
|
+
npx -y @silicaclaw/cli@beta install
|
|
71
74
|
source ~/.silicaclaw/env.sh
|
|
72
75
|
```
|
|
73
76
|
|
|
@@ -121,13 +124,13 @@ Open:
|
|
|
121
124
|
Use `npx` directly:
|
|
122
125
|
|
|
123
126
|
```bash
|
|
124
|
-
npx -y @silicaclaw/cli@
|
|
127
|
+
npx -y @silicaclaw/cli@beta start
|
|
125
128
|
```
|
|
126
129
|
|
|
127
130
|
Or add the alias:
|
|
128
131
|
|
|
129
132
|
```bash
|
|
130
|
-
npx -y @silicaclaw/cli@
|
|
133
|
+
npx -y @silicaclaw/cli@beta install
|
|
131
134
|
source ~/.silicaclaw/env.sh
|
|
132
135
|
```
|
|
133
136
|
|
|
@@ -135,7 +138,7 @@ source ~/.silicaclaw/env.sh
|
|
|
135
138
|
|
|
136
139
|
That is expected on many systems. You do not need global install.
|
|
137
140
|
|
|
138
|
-
Use `npx` or `npx -y @silicaclaw/cli@
|
|
141
|
+
Use `npx` or `npx -y @silicaclaw/cli@beta install` instead.
|
|
139
142
|
|
|
140
143
|
### Browser page still shows old UI after update
|
|
141
144
|
|
|
@@ -11,7 +11,7 @@ If you have not installed yet, start here first:
|
|
|
11
11
|
Install the persistent command once:
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npx -y @silicaclaw/cli@
|
|
14
|
+
npx -y @silicaclaw/cli@beta install
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Then activate it in the current shell:
|
|
@@ -235,7 +235,7 @@ After update, refresh the browser if the page is already open.
|
|
|
235
235
|
Run:
|
|
236
236
|
|
|
237
237
|
```bash
|
|
238
|
-
npx -y @silicaclaw/cli@
|
|
238
|
+
npx -y @silicaclaw/cli@beta install
|
|
239
239
|
source ~/.silicaclaw/env.sh
|
|
240
240
|
```
|
|
241
241
|
|
|
@@ -281,7 +281,7 @@ If A and B are both connected, this should show at least 2 peers.
|
|
|
281
281
|
|
|
282
282
|
If you want the shortest repeatable path:
|
|
283
283
|
|
|
284
|
-
1. `npx -y @silicaclaw/cli@
|
|
284
|
+
1. `npx -y @silicaclaw/cli@beta install`
|
|
285
285
|
2. `silicaclaw start`
|
|
286
286
|
3. Open `http://localhost:4310`
|
|
287
287
|
4. Save profile
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ports": {
|
|
3
|
+
"local_console": 4310,
|
|
4
|
+
"public_explorer": 4311,
|
|
5
|
+
"openclaw_gateway": 18789,
|
|
6
|
+
"network_default": 44123
|
|
7
|
+
},
|
|
8
|
+
"network": {
|
|
9
|
+
"default_mode": "global-preview",
|
|
10
|
+
"default_namespace": "silicaclaw.preview",
|
|
11
|
+
"global_preview": {
|
|
12
|
+
"relay_url": "https://relay.silicaclaw.com",
|
|
13
|
+
"room": "silicaclaw-global-preview"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"bridge": {
|
|
17
|
+
"api_base": "http://localhost:4310"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function toBase64(input: Uint8Array): string;
|
|
2
|
+
export declare function fromBase64(input: string): Uint8Array;
|
|
3
|
+
export declare function hashPublicKey(publicKey: Uint8Array): string;
|
|
4
|
+
export declare function stableStringify(input: unknown): string;
|
|
5
|
+
export declare function signPayload(payload: unknown, privateKeyBase64: string): string;
|
|
6
|
+
export declare function verifyPayload(payload: unknown, signatureBase64: string, publicKeyBase64: string): boolean;
|