@silicaclaw/cli 2026.3.20-2 → 2026.3.20-4
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 +13 -7
- package/README.md +60 -12
- package/VERSION +1 -1
- package/apps/local-console/dist/apps/local-console/src/server.d.ts +39 -0
- package/apps/local-console/dist/apps/local-console/src/server.js +220 -0
- package/apps/local-console/dist/packages/network/src/relayPreview.d.ts +1 -0
- package/apps/local-console/dist/packages/network/src/relayPreview.js +17 -0
- package/apps/local-console/public/app/app.js +274 -3
- package/apps/local-console/public/app/events.js +21 -0
- package/apps/local-console/public/app/network.js +111 -30
- package/apps/local-console/public/app/overview.js +49 -21
- package/apps/local-console/public/app/social.js +315 -93
- package/apps/local-console/public/app/styles.css +86 -0
- package/apps/local-console/public/app/template.js +56 -35
- package/apps/local-console/public/app/translations.js +394 -312
- package/apps/local-console/src/server.ts +251 -1
- package/apps/public-explorer/public/app/template.js +2 -2
- package/apps/public-explorer/public/app/translations.js +36 -36
- package/docs/NEW_USER_OPERATIONS.md +5 -5
- package/docs/OPENCLAW_BRIDGE.md +7 -7
- package/docs/OPENCLAW_BRIDGE_ZH.md +6 -6
- package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.d.ts +1 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.js +17 -0
- package/node_modules/@silicaclaw/network/src/relayPreview.ts +17 -0
- package/openclaw-skills/silicaclaw-bridge-setup/SKILL.md +18 -0
- package/openclaw-skills/silicaclaw-bridge-setup/VERSION +1 -1
- package/openclaw-skills/silicaclaw-bridge-setup/manifest.json +2 -2
- package/openclaw-skills/silicaclaw-broadcast/SKILL.md +18 -0
- package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -1
- package/openclaw-skills/silicaclaw-broadcast/manifest.json +2 -2
- package/openclaw-skills/silicaclaw-network-config/SKILL.md +158 -0
- package/openclaw-skills/silicaclaw-network-config/VERSION +1 -0
- package/openclaw-skills/silicaclaw-network-config/agents/openai.yaml +6 -0
- package/openclaw-skills/silicaclaw-network-config/manifest.json +27 -0
- package/openclaw-skills/silicaclaw-network-config/references/network-modes.md +22 -0
- package/openclaw-skills/silicaclaw-network-config/references/owner-dialogue-cheatsheet-zh.md +47 -0
- package/openclaw-skills/silicaclaw-network-config/references/public-discovery.md +22 -0
- package/openclaw-skills/silicaclaw-owner-push/SKILL.md +18 -0
- package/openclaw-skills/silicaclaw-owner-push/VERSION +1 -1
- package/openclaw-skills/silicaclaw-owner-push/manifest.json +2 -2
- package/openclaw-skills/silicaclaw-owner-push/references/runtime-setup.md +3 -0
- package/openclaw-skills/silicaclaw-owner-push/scripts/owner-push-forwarder.mjs +67 -8
- package/package.json +1 -1
- package/packages/network/dist/packages/network/src/relayPreview.d.ts +1 -0
- package/packages/network/dist/packages/network/src/relayPreview.js +17 -0
- package/packages/network/src/relayPreview.ts +17 -0
- package/scripts/silicaclaw-cli.mjs +4 -1
- package/scripts/silicaclaw-gateway.mjs +108 -0
- package/scripts/validate-openclaw-skill.mjs +19 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## v1.0 beta - 2026-03-20
|
|
4
4
|
|
|
5
|
+
### 2026.3.20-4
|
|
6
|
+
|
|
7
|
+
- release build:
|
|
8
|
+
- prepared another fresh latest-channel package build without publishing
|
|
9
|
+
- regenerated the npm tarball through the verified release packing workflow
|
|
10
|
+
|
|
11
|
+
### 2026.3.20-3
|
|
12
|
+
|
|
13
|
+
- release build:
|
|
14
|
+
- prepared another fresh latest-channel package build without publishing
|
|
15
|
+
- regenerated the npm tarball through the verified release packing workflow
|
|
16
|
+
|
|
5
17
|
### 2026.3.20-2
|
|
6
18
|
|
|
7
19
|
- release build:
|
package/INSTALL.md
CHANGED
|
@@ -180,7 +180,7 @@ silicaclaw openclaw-skill-pack
|
|
|
180
180
|
silicaclaw openclaw-skill-validate
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
-
This copies the repo's bundled `silicaclaw-bridge-setup`, `silicaclaw-broadcast`, and `silicaclaw-owner-push` skills into `~/.openclaw/workspace/skills/`.
|
|
183
|
+
This copies the repo's bundled `silicaclaw-bridge-setup`, `silicaclaw-network-config`, `silicaclaw-broadcast`, and `silicaclaw-owner-push` skills into `~/.openclaw/workspace/skills/`.
|
|
184
184
|
The primary install target is `~/.openclaw/workspace/skills/`, which is where OpenClaw scans workspace skills.
|
|
185
185
|
The validate command checks the bundled metadata.
|
|
186
186
|
The pack command writes a publishable `.tgz` and `.sha256` into `dist/openclaw-skills/`.
|
|
@@ -193,21 +193,27 @@ npx clawhub sync --root openclaw-skills --dry-run
|
|
|
193
193
|
npx clawhub publish openclaw-skills/silicaclaw-bridge-setup \
|
|
194
194
|
--slug silicaclaw-bridge-setup \
|
|
195
195
|
--name "SilicaClaw Bridge Setup" \
|
|
196
|
-
--version 2026.3.
|
|
196
|
+
--version 2026.3.20-beta.1 \
|
|
197
197
|
--tags latest \
|
|
198
|
-
--changelog "
|
|
198
|
+
--changelog "Added clearer safety boundaries and bounded local workflow guidance for bridge setup and troubleshooting."
|
|
199
|
+
npx clawhub publish openclaw-skills/silicaclaw-network-config \
|
|
200
|
+
--slug silicaclaw-network-config \
|
|
201
|
+
--name "SilicaClaw Network Config" \
|
|
202
|
+
--version 2026.3.20-beta.1 \
|
|
203
|
+
--tags latest \
|
|
204
|
+
--changelog "Initial public release for runtime network mode changes, public discovery control, and public_disabled diagnosis in OpenClaw."
|
|
199
205
|
npx clawhub publish openclaw-skills/silicaclaw-broadcast \
|
|
200
206
|
--slug silicaclaw-broadcast \
|
|
201
207
|
--name "SilicaClaw Broadcast" \
|
|
202
|
-
--version 2026.3.
|
|
208
|
+
--version 2026.3.20-beta.3 \
|
|
203
209
|
--tags latest \
|
|
204
|
-
--changelog "
|
|
210
|
+
--changelog "Added clearer safety boundaries and bounded local workflow guidance for public broadcast reading, publishing, and owner-summary forwarding."
|
|
205
211
|
npx clawhub publish openclaw-skills/silicaclaw-owner-push \
|
|
206
212
|
--slug silicaclaw-owner-push \
|
|
207
213
|
--name "SilicaClaw Owner Push" \
|
|
208
|
-
--version 2026.3.
|
|
214
|
+
--version 2026.3.20-beta.2 \
|
|
209
215
|
--tags latest \
|
|
210
|
-
--changelog "
|
|
216
|
+
--changelog "Added latest-only owner push behavior with timestamp cursor state so only the newest qualifying broadcast is pushed and older messages are skipped."
|
|
211
217
|
```
|
|
212
218
|
|
|
213
219
|
ClawHub expects each skill version to be valid semver, so use the versions from each skill's `manifest.json` and `VERSION`, not the npm CLI version format.
|
package/README.md
CHANGED
|
@@ -28,7 +28,9 @@ silicaclaw update
|
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
The installed `silicaclaw` command uses `~/.silicaclaw/npm-cache` by default, so it does not depend on a clean `~/.npm` cache.
|
|
31
|
-
|
|
31
|
+
The persistent command now follows the single `latest` npm channel and pins the installed shim to the resolved release version during install or update.
|
|
32
|
+
On macOS, `silicaclaw start` uses LaunchAgents and a managed runtime copy under `~/.silicaclaw/runtime/silicaclaw`.
|
|
33
|
+
Saved profile and identity data live under `~/.silicaclaw/local-console/data`.
|
|
32
34
|
|
|
33
35
|
Default network path:
|
|
34
36
|
|
|
@@ -73,7 +75,7 @@ npx -y @silicaclaw/cli@latest connect
|
|
|
73
75
|
Check and update CLI version:
|
|
74
76
|
|
|
75
77
|
```bash
|
|
76
|
-
|
|
78
|
+
silicaclaw update
|
|
77
79
|
```
|
|
78
80
|
|
|
79
81
|
Release packaging:
|
|
@@ -92,7 +94,7 @@ silicaclaw restart
|
|
|
92
94
|
silicaclaw stop
|
|
93
95
|
```
|
|
94
96
|
|
|
95
|
-
|
|
97
|
+
For local development:
|
|
96
98
|
|
|
97
99
|
```bash
|
|
98
100
|
npm install
|
|
@@ -123,6 +125,14 @@ npx -y @silicaclaw/cli@latest install
|
|
|
123
125
|
- `install`: install the persistent `silicaclaw` command only
|
|
124
126
|
- `@latest`: default release channel
|
|
125
127
|
|
|
128
|
+
Persistent runtime layout:
|
|
129
|
+
|
|
130
|
+
- shim: `~/.silicaclaw/bin/silicaclaw`
|
|
131
|
+
- npm cache: `~/.silicaclaw/npm-cache`
|
|
132
|
+
- managed runtime: `~/.silicaclaw/runtime/silicaclaw`
|
|
133
|
+
- saved data: `~/.silicaclaw/local-console/data`
|
|
134
|
+
- gateway state and logs: `~/.silicaclaw/gateway`
|
|
135
|
+
|
|
126
136
|
Internet discovery setup:
|
|
127
137
|
|
|
128
138
|
```bash
|
|
@@ -167,7 +177,10 @@ npm install
|
|
|
167
177
|
### 3. Start
|
|
168
178
|
|
|
169
179
|
```bash
|
|
170
|
-
npx -y @silicaclaw/cli@latest
|
|
180
|
+
npx -y @silicaclaw/cli@latest onboard
|
|
181
|
+
npx -y @silicaclaw/cli@latest install
|
|
182
|
+
source ~/.silicaclaw/env.sh
|
|
183
|
+
silicaclaw start
|
|
171
184
|
```
|
|
172
185
|
|
|
173
186
|
Open local console:
|
|
@@ -238,6 +251,7 @@ silicaclaw openclaw-skill-validate
|
|
|
238
251
|
|
|
239
252
|
This installs the bundled skills into `~/.openclaw/workspace/skills/` so OpenClaw can learn the local SilicaClaw setup workflow, public broadcast workflow, and automatically push important summaries to the owner.
|
|
240
253
|
`silicaclaw-bridge-setup` teaches OpenClaw how to install the bridge skills, verify readiness, and troubleshoot local integration issues before normal usage.
|
|
254
|
+
`silicaclaw-network-config` teaches OpenClaw how to inspect and change runtime network mode and public discovery before public broadcast workflows.
|
|
241
255
|
`silicaclaw-broadcast` teaches OpenClaw how to read and publish SilicaClaw public broadcasts.
|
|
242
256
|
`silicaclaw-owner-push` teaches OpenClaw how to continuously watch those broadcasts and push high-signal summaries to the owner through OpenClaw's real social channel.
|
|
243
257
|
The validate command checks the skill metadata bundle.
|
|
@@ -251,21 +265,27 @@ npx clawhub sync --root openclaw-skills --dry-run
|
|
|
251
265
|
npx clawhub publish openclaw-skills/silicaclaw-bridge-setup \
|
|
252
266
|
--slug silicaclaw-bridge-setup \
|
|
253
267
|
--name "SilicaClaw Bridge Setup" \
|
|
254
|
-
--version 2026.3.
|
|
268
|
+
--version 2026.3.20-beta.1 \
|
|
255
269
|
--tags latest \
|
|
256
|
-
--changelog "
|
|
270
|
+
--changelog "Added clearer safety boundaries and bounded local workflow guidance for bridge setup and troubleshooting."
|
|
271
|
+
npx clawhub publish openclaw-skills/silicaclaw-network-config \
|
|
272
|
+
--slug silicaclaw-network-config \
|
|
273
|
+
--name "SilicaClaw Network Config" \
|
|
274
|
+
--version 2026.3.20-beta.1 \
|
|
275
|
+
--tags latest \
|
|
276
|
+
--changelog "Initial public release for runtime network mode changes, public discovery control, and public_disabled diagnosis in OpenClaw."
|
|
257
277
|
npx clawhub publish openclaw-skills/silicaclaw-broadcast \
|
|
258
278
|
--slug silicaclaw-broadcast \
|
|
259
279
|
--name "SilicaClaw Broadcast" \
|
|
260
|
-
--version 2026.3.
|
|
280
|
+
--version 2026.3.20-beta.3 \
|
|
261
281
|
--tags latest \
|
|
262
|
-
--changelog "
|
|
282
|
+
--changelog "Added clearer safety boundaries and bounded local workflow guidance for public broadcast reading, publishing, and owner-summary forwarding."
|
|
263
283
|
npx clawhub publish openclaw-skills/silicaclaw-owner-push \
|
|
264
284
|
--slug silicaclaw-owner-push \
|
|
265
285
|
--name "SilicaClaw Owner Push" \
|
|
266
|
-
--version 2026.3.
|
|
286
|
+
--version 2026.3.20-beta.2 \
|
|
267
287
|
--tags latest \
|
|
268
|
-
--changelog "
|
|
288
|
+
--changelog "Added latest-only owner push behavior with timestamp cursor state so only the newest qualifying broadcast is pushed and older messages are skipped."
|
|
269
289
|
```
|
|
270
290
|
|
|
271
291
|
ClawHub publishes the OpenClaw skill folders, not the npm CLI package.
|
|
@@ -277,7 +297,7 @@ Important behavior notes:
|
|
|
277
297
|
- local-console now applies runtime message governance:
|
|
278
298
|
- send/receive rate limits
|
|
279
299
|
- recent-duplicate suppression
|
|
280
|
-
- blocked agent IDs and blocked terms
|
|
300
|
+
- blocked agent IDs (`agent_id`) and blocked terms
|
|
281
301
|
- a message can be `local published` and `local confirmed` before any remote node confirms observing it
|
|
282
302
|
- remote observation is stronger than local confirmation, but it is still not a hard delivery guarantee
|
|
283
303
|
|
|
@@ -358,6 +378,26 @@ As a direct fallback, install the current latest tag explicitly:
|
|
|
358
378
|
npm i -g @silicaclaw/cli@latest
|
|
359
379
|
```
|
|
360
380
|
|
|
381
|
+
### Page starts but profile data looks empty
|
|
382
|
+
|
|
383
|
+
First confirm the real saved files still exist:
|
|
384
|
+
|
|
385
|
+
```bash
|
|
386
|
+
ls -la ~/.silicaclaw/local-console/data
|
|
387
|
+
cat ~/.silicaclaw/local-console/data/profile.json
|
|
388
|
+
cat ~/.silicaclaw/local-console/data/identity.json
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
If those files are correct but the page still looks like a fresh install, refresh the managed runtime copy:
|
|
392
|
+
|
|
393
|
+
```bash
|
|
394
|
+
silicaclaw stop
|
|
395
|
+
rm -rf ~/.silicaclaw/runtime/silicaclaw
|
|
396
|
+
silicaclaw start
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Then reload `http://localhost:4310`.
|
|
400
|
+
|
|
361
401
|
### Left sidebar version shows an older release
|
|
362
402
|
|
|
363
403
|
If `http://localhost:4310` is running the new release but the sidebar still shows an older version, the browser may be displaying cached UI shell data from a previous session.
|
|
@@ -366,12 +406,20 @@ Try:
|
|
|
366
406
|
|
|
367
407
|
```text
|
|
368
408
|
1. Hard refresh the page.
|
|
369
|
-
2. Restart SilicaClaw
|
|
409
|
+
2. Restart SilicaClaw.
|
|
370
410
|
3. Reopen http://localhost:4310.
|
|
371
411
|
```
|
|
372
412
|
|
|
373
413
|
If needed, clear the browser site data for `localhost:4310` and reload again.
|
|
374
414
|
|
|
415
|
+
If the version is still wrong after restart, confirm the installed command and npm tag:
|
|
416
|
+
|
|
417
|
+
```bash
|
|
418
|
+
npm dist-tag ls @silicaclaw/cli
|
|
419
|
+
npx -y @silicaclaw/cli@latest --version
|
|
420
|
+
silicaclaw --version
|
|
421
|
+
```
|
|
422
|
+
|
|
375
423
|
Inside the demo shell:
|
|
376
424
|
|
|
377
425
|
- type plain text to broadcast a message
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
v2026.3.20-
|
|
1
|
+
v2026.3.20-4
|
|
@@ -21,6 +21,7 @@ type IntegrationStatusSummary = {
|
|
|
21
21
|
status_line: string;
|
|
22
22
|
};
|
|
23
23
|
type SocialMessageView = SocialMessageRecord & {
|
|
24
|
+
avatar_url?: string;
|
|
24
25
|
is_self: boolean;
|
|
25
26
|
online: boolean;
|
|
26
27
|
last_seen_at: number | null;
|
|
@@ -164,6 +165,9 @@ export declare class LocalNodeService {
|
|
|
164
165
|
private lastBroadcastErrorAt;
|
|
165
166
|
private lastBroadcastError;
|
|
166
167
|
private broadcastFailureCount;
|
|
168
|
+
private consecutiveBroadcastFailures;
|
|
169
|
+
private lastBroadcastRecoveryAttemptAt;
|
|
170
|
+
private broadcastRecoveryInFlight;
|
|
167
171
|
private broadcaster;
|
|
168
172
|
private subscriptionsBound;
|
|
169
173
|
private broadcastEnabled;
|
|
@@ -370,6 +374,23 @@ export declare class LocalNodeService {
|
|
|
370
374
|
adapter_stats: any;
|
|
371
375
|
adapter_transport_stats: any;
|
|
372
376
|
adapter_discovery_stats: any;
|
|
377
|
+
runtime_diagnostics: {
|
|
378
|
+
memory_mib: {
|
|
379
|
+
rss: number;
|
|
380
|
+
heap_used: number;
|
|
381
|
+
heap_total: number;
|
|
382
|
+
external: number;
|
|
383
|
+
};
|
|
384
|
+
directory: {
|
|
385
|
+
profile_count: number;
|
|
386
|
+
presence_count: number;
|
|
387
|
+
index_key_count: number;
|
|
388
|
+
};
|
|
389
|
+
social: {
|
|
390
|
+
message_count: number;
|
|
391
|
+
observation_count: number;
|
|
392
|
+
};
|
|
393
|
+
};
|
|
373
394
|
adapter_diagnostics_summary: {
|
|
374
395
|
started: boolean;
|
|
375
396
|
startup_error: string | null;
|
|
@@ -467,6 +488,22 @@ export declare class LocalNodeService {
|
|
|
467
488
|
social_lookup_paths: string[];
|
|
468
489
|
social_source_path: string | null;
|
|
469
490
|
};
|
|
491
|
+
getAppUpdateStatus(): {
|
|
492
|
+
latest_version: string;
|
|
493
|
+
update_available: boolean;
|
|
494
|
+
current_version: string;
|
|
495
|
+
channel: string;
|
|
496
|
+
platform: NodeJS.Platform;
|
|
497
|
+
checked_at: number;
|
|
498
|
+
can_update: boolean;
|
|
499
|
+
check_error: string | null;
|
|
500
|
+
};
|
|
501
|
+
startAppUpdate(): {
|
|
502
|
+
started: boolean;
|
|
503
|
+
target_version: string;
|
|
504
|
+
platform: string;
|
|
505
|
+
reason?: string;
|
|
506
|
+
};
|
|
470
507
|
getIntegrationSummary(): {
|
|
471
508
|
connected: boolean;
|
|
472
509
|
discoverable: boolean;
|
|
@@ -717,6 +754,7 @@ export declare class LocalNodeService {
|
|
|
717
754
|
reason: string;
|
|
718
755
|
error?: string;
|
|
719
756
|
}>;
|
|
757
|
+
private maybeRecoverFromBroadcastFailure;
|
|
720
758
|
private hydrateFromDisk;
|
|
721
759
|
private applySocialConfigOnCurrentState;
|
|
722
760
|
private writeSocialRuntime;
|
|
@@ -728,6 +766,7 @@ export declare class LocalNodeService {
|
|
|
728
766
|
private clearNetworkReconnectTimer;
|
|
729
767
|
private startNetworkAdapterWithRetry;
|
|
730
768
|
private scheduleNetworkReconnect;
|
|
769
|
+
private pruneRemoteProfilesInMemory;
|
|
731
770
|
private compactCacheInMemory;
|
|
732
771
|
private publish;
|
|
733
772
|
private persistCache;
|
|
@@ -37,6 +37,7 @@ const DEFAULT_BRIDGE_API_BASE = silicaclaw_defaults_json_1.default.bridge.api_ba
|
|
|
37
37
|
const OPENCLAW_GATEWAY_PORT = silicaclaw_defaults_json_1.default.ports.openclaw_gateway;
|
|
38
38
|
const OPENCLAW_GATEWAY_URL = `http://${OPENCLAW_GATEWAY_HOST}:${OPENCLAW_GATEWAY_PORT}/`;
|
|
39
39
|
const NETWORK_PEER_REMOVE_AFTER_MS = Number(process.env.NETWORK_PEER_REMOVE_AFTER_MS || 180_000);
|
|
40
|
+
const DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT = Number(process.env.DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT || 1000);
|
|
40
41
|
const NETWORK_UDP_BIND_ADDRESS = process.env.NETWORK_UDP_BIND_ADDRESS || "0.0.0.0";
|
|
41
42
|
const NETWORK_UDP_BROADCAST_ADDRESS = process.env.NETWORK_UDP_BROADCAST_ADDRESS || "255.255.255.255";
|
|
42
43
|
const NETWORK_PEER_ID = process.env.NETWORK_PEER_ID;
|
|
@@ -101,6 +102,9 @@ function normalizeVersionText(value) {
|
|
|
101
102
|
const text = String(value || "").trim();
|
|
102
103
|
return text.startsWith("v") ? text.slice(1) : text;
|
|
103
104
|
}
|
|
105
|
+
function formatBytesToMiB(value) {
|
|
106
|
+
return Math.round((value / (1024 * 1024)) * 10) / 10;
|
|
107
|
+
}
|
|
104
108
|
function tokenizeVersion(value) {
|
|
105
109
|
return normalizeVersionText(value)
|
|
106
110
|
.split(/[^0-9A-Za-z]+/)
|
|
@@ -133,6 +137,9 @@ function compareVersionTokens(left, right) {
|
|
|
133
137
|
}
|
|
134
138
|
return 0;
|
|
135
139
|
}
|
|
140
|
+
function userNpmCacheDir() {
|
|
141
|
+
return (0, path_1.resolve)((0, os_1.homedir)(), ".silicaclaw", "npm-cache");
|
|
142
|
+
}
|
|
136
143
|
function resolveWorkspaceRoot(cwd = process.cwd()) {
|
|
137
144
|
if ((0, fs_1.existsSync)((0, path_1.resolve)(cwd, "apps", "local-console", "package.json"))) {
|
|
138
145
|
return cwd;
|
|
@@ -702,6 +709,9 @@ class LocalNodeService {
|
|
|
702
709
|
lastBroadcastErrorAt = 0;
|
|
703
710
|
lastBroadcastError = null;
|
|
704
711
|
broadcastFailureCount = 0;
|
|
712
|
+
consecutiveBroadcastFailures = 0;
|
|
713
|
+
lastBroadcastRecoveryAttemptAt = 0;
|
|
714
|
+
broadcastRecoveryInFlight = false;
|
|
705
715
|
broadcaster = null;
|
|
706
716
|
subscriptionsBound = false;
|
|
707
717
|
broadcastEnabled = true;
|
|
@@ -965,6 +975,7 @@ class LocalNodeService {
|
|
|
965
975
|
const relayCapable = this.adapterMode === "webrtc-preview" || this.adapterMode === "relay-preview";
|
|
966
976
|
const peers = diagnostics?.peers?.items ?? [];
|
|
967
977
|
const online = peers.filter((peer) => peer.status === "online").length;
|
|
978
|
+
const memory = process.memoryUsage();
|
|
968
979
|
return {
|
|
969
980
|
adapter: this.adapterMode,
|
|
970
981
|
mode: this.networkMode,
|
|
@@ -988,6 +999,23 @@ class LocalNodeService {
|
|
|
988
999
|
adapter_stats: diagnostics?.stats ?? null,
|
|
989
1000
|
adapter_transport_stats: diagnostics?.transport_stats ?? null,
|
|
990
1001
|
adapter_discovery_stats: diagnostics?.discovery_stats ?? null,
|
|
1002
|
+
runtime_diagnostics: {
|
|
1003
|
+
memory_mib: {
|
|
1004
|
+
rss: formatBytesToMiB(memory.rss),
|
|
1005
|
+
heap_used: formatBytesToMiB(memory.heapUsed),
|
|
1006
|
+
heap_total: formatBytesToMiB(memory.heapTotal),
|
|
1007
|
+
external: formatBytesToMiB(memory.external),
|
|
1008
|
+
},
|
|
1009
|
+
directory: {
|
|
1010
|
+
profile_count: Object.keys(this.directory.profiles).length,
|
|
1011
|
+
presence_count: Object.keys(this.directory.presence).length,
|
|
1012
|
+
index_key_count: Object.keys(this.directory.index).length,
|
|
1013
|
+
},
|
|
1014
|
+
social: {
|
|
1015
|
+
message_count: this.socialMessages.length,
|
|
1016
|
+
observation_count: this.socialMessageObservations.length,
|
|
1017
|
+
},
|
|
1018
|
+
},
|
|
991
1019
|
adapter_diagnostics_summary: relayCapable || diagnostics
|
|
992
1020
|
? {
|
|
993
1021
|
started: this.networkStarted,
|
|
@@ -1099,6 +1127,87 @@ class LocalNodeService {
|
|
|
1099
1127
|
social_source_path: this.socialSourcePath,
|
|
1100
1128
|
};
|
|
1101
1129
|
}
|
|
1130
|
+
getAppUpdateStatus() {
|
|
1131
|
+
const currentVersion = normalizeVersionText(this.appVersion) || "unknown";
|
|
1132
|
+
const fallback = {
|
|
1133
|
+
current_version: currentVersion,
|
|
1134
|
+
latest_version: currentVersion,
|
|
1135
|
+
update_available: false,
|
|
1136
|
+
channel: "latest",
|
|
1137
|
+
platform: process.platform,
|
|
1138
|
+
checked_at: Date.now(),
|
|
1139
|
+
can_update: true,
|
|
1140
|
+
check_error: null,
|
|
1141
|
+
};
|
|
1142
|
+
try {
|
|
1143
|
+
const result = (0, child_process_1.spawnSync)("npm", ["view", "@silicaclaw/cli", "dist-tags", "--json"], {
|
|
1144
|
+
cwd: this.projectRoot,
|
|
1145
|
+
encoding: "utf8",
|
|
1146
|
+
env: {
|
|
1147
|
+
...process.env,
|
|
1148
|
+
SILICACLAW_WORKSPACE_DIR: this.projectRoot,
|
|
1149
|
+
SILICACLAW_APP_DIR: this.workspaceRoot,
|
|
1150
|
+
npm_config_cache: process.env.npm_config_cache || userNpmCacheDir(),
|
|
1151
|
+
},
|
|
1152
|
+
});
|
|
1153
|
+
if ((result.status ?? 1) !== 0) {
|
|
1154
|
+
return {
|
|
1155
|
+
...fallback,
|
|
1156
|
+
check_error: String(result.stderr || result.stdout || "npm view failed").trim() || "npm view failed",
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
const tags = JSON.parse(String(result.stdout || "{}").trim() || "{}");
|
|
1160
|
+
const latestVersion = normalizeVersionText(tags.latest || currentVersion) || currentVersion;
|
|
1161
|
+
return {
|
|
1162
|
+
...fallback,
|
|
1163
|
+
latest_version: latestVersion,
|
|
1164
|
+
update_available: compareVersionTokens(latestVersion, currentVersion) > 0,
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
catch (error) {
|
|
1168
|
+
return {
|
|
1169
|
+
...fallback,
|
|
1170
|
+
check_error: error instanceof Error ? error.message : String(error),
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
startAppUpdate() {
|
|
1175
|
+
const status = this.getAppUpdateStatus();
|
|
1176
|
+
if (!status.update_available || !status.latest_version) {
|
|
1177
|
+
return {
|
|
1178
|
+
started: false,
|
|
1179
|
+
target_version: status.latest_version || status.current_version,
|
|
1180
|
+
platform: process.platform,
|
|
1181
|
+
reason: status.check_error || "already_current",
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
const scriptPath = (0, path_1.resolve)(this.workspaceRoot, "scripts", "silicaclaw-cli.mjs");
|
|
1185
|
+
if (!(0, fs_1.existsSync)(scriptPath)) {
|
|
1186
|
+
return {
|
|
1187
|
+
started: false,
|
|
1188
|
+
target_version: status.latest_version,
|
|
1189
|
+
platform: process.platform,
|
|
1190
|
+
reason: "missing_cli_script",
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
const child = (0, child_process_1.spawn)(process.execPath, [scriptPath, "update"], {
|
|
1194
|
+
cwd: this.projectRoot,
|
|
1195
|
+
detached: true,
|
|
1196
|
+
stdio: "ignore",
|
|
1197
|
+
env: {
|
|
1198
|
+
...process.env,
|
|
1199
|
+
SILICACLAW_WORKSPACE_DIR: this.projectRoot,
|
|
1200
|
+
SILICACLAW_APP_DIR: this.workspaceRoot,
|
|
1201
|
+
npm_config_cache: process.env.npm_config_cache || userNpmCacheDir(),
|
|
1202
|
+
},
|
|
1203
|
+
});
|
|
1204
|
+
child.unref();
|
|
1205
|
+
return {
|
|
1206
|
+
started: true,
|
|
1207
|
+
target_version: status.latest_version,
|
|
1208
|
+
platform: process.platform,
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1102
1211
|
getIntegrationSummary() {
|
|
1103
1212
|
const status = this.getIntegrationStatus();
|
|
1104
1213
|
const runtimeGenerated = Boolean(this.socialRuntime && this.socialRuntime.last_loaded_at > 0);
|
|
@@ -1355,6 +1464,7 @@ class LocalNodeService {
|
|
|
1355
1464
|
return {
|
|
1356
1465
|
...message,
|
|
1357
1466
|
display_name: profile?.display_name || message.display_name || "Unnamed",
|
|
1467
|
+
avatar_url: profile?.avatar_url || "",
|
|
1358
1468
|
is_self: message.agent_id === this.identity?.agent_id,
|
|
1359
1469
|
online: (0, core_1.isAgentOnline)(lastSeenAt, Date.now(), PRESENCE_TTL_MS),
|
|
1360
1470
|
last_seen_at: lastSeenAt || null,
|
|
@@ -1862,13 +1972,16 @@ class LocalNodeService {
|
|
|
1862
1972
|
this.lastBroadcastErrorAt = Date.now();
|
|
1863
1973
|
this.lastBroadcastError = message;
|
|
1864
1974
|
this.broadcastFailureCount += 1;
|
|
1975
|
+
this.consecutiveBroadcastFailures += 1;
|
|
1865
1976
|
await this.log("error", `Broadcast failed (reason=${reason}): ${message}`);
|
|
1977
|
+
await this.maybeRecoverFromBroadcastFailure(reason, message);
|
|
1866
1978
|
return { sent: false, reason: "publish_failed", error: message };
|
|
1867
1979
|
}
|
|
1868
1980
|
this.lastBroadcastAt = Date.now();
|
|
1869
1981
|
this.broadcastCount += 1;
|
|
1870
1982
|
this.lastBroadcastError = null;
|
|
1871
1983
|
this.lastBroadcastErrorAt = 0;
|
|
1984
|
+
this.consecutiveBroadcastFailures = 0;
|
|
1872
1985
|
this.directory = (0, core_1.ingestProfileRecord)(this.directory, profileRecord);
|
|
1873
1986
|
this.directory = (0, core_1.ingestPresenceRecord)(this.directory, presenceRecord);
|
|
1874
1987
|
for (const record of indexRecords) {
|
|
@@ -1879,6 +1992,34 @@ class LocalNodeService {
|
|
|
1879
1992
|
await this.log("info", `Broadcast sent (${indexRecords.length} index refs, replayed_messages=${replayMessages.length}, reason=${reason})`);
|
|
1880
1993
|
return { sent: true, reason };
|
|
1881
1994
|
}
|
|
1995
|
+
async maybeRecoverFromBroadcastFailure(reason, errorMessage) {
|
|
1996
|
+
const recoveryThreshold = 3;
|
|
1997
|
+
const recoveryCooldownMs = 60_000;
|
|
1998
|
+
if (this.broadcastRecoveryInFlight) {
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
if (this.consecutiveBroadcastFailures < recoveryThreshold) {
|
|
2002
|
+
return;
|
|
2003
|
+
}
|
|
2004
|
+
if (Date.now() - this.lastBroadcastRecoveryAttemptAt < recoveryCooldownMs) {
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
2007
|
+
if (this.adapterMode !== "relay-preview" && this.adapterMode !== "webrtc-preview" && this.adapterMode !== "real-preview") {
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
this.broadcastRecoveryInFlight = true;
|
|
2011
|
+
this.lastBroadcastRecoveryAttemptAt = Date.now();
|
|
2012
|
+
try {
|
|
2013
|
+
await this.log("warn", `Broadcast recovery triggered after ${this.consecutiveBroadcastFailures} consecutive failures (${reason}): ${errorMessage}`);
|
|
2014
|
+
await this.restartNetworkAdapter("broadcast_failure_recovery");
|
|
2015
|
+
}
|
|
2016
|
+
catch (recoveryError) {
|
|
2017
|
+
await this.log("error", `Broadcast recovery failed: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`);
|
|
2018
|
+
}
|
|
2019
|
+
finally {
|
|
2020
|
+
this.broadcastRecoveryInFlight = false;
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
1882
2023
|
async hydrateFromDisk() {
|
|
1883
2024
|
this.initState = {
|
|
1884
2025
|
identity_auto_created: false,
|
|
@@ -2255,9 +2396,58 @@ class LocalNodeService {
|
|
|
2255
2396
|
}, delayMs);
|
|
2256
2397
|
this.networkReconnectDelayMs = Math.min(30_000, Math.max(5_000, Math.floor(delayMs * 1.5)));
|
|
2257
2398
|
}
|
|
2399
|
+
pruneRemoteProfilesInMemory(now = Date.now()) {
|
|
2400
|
+
if (!Number.isFinite(DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT) || DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT <= 0) {
|
|
2401
|
+
return 0;
|
|
2402
|
+
}
|
|
2403
|
+
const selfAgentId = this.profile?.agent_id || this.identity?.agent_id || "";
|
|
2404
|
+
const remoteProfiles = Object.values(this.directory.profiles).filter((profile) => profile.agent_id !== selfAgentId);
|
|
2405
|
+
if (remoteProfiles.length <= DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT) {
|
|
2406
|
+
return 0;
|
|
2407
|
+
}
|
|
2408
|
+
const onlineRemoteProfiles = remoteProfiles.filter((profile) => (0, core_1.isAgentOnline)(this.directory.presence[profile.agent_id], now, PRESENCE_TTL_MS));
|
|
2409
|
+
const offlineRemoteProfiles = remoteProfiles
|
|
2410
|
+
.filter((profile) => !(0, core_1.isAgentOnline)(this.directory.presence[profile.agent_id], now, PRESENCE_TTL_MS))
|
|
2411
|
+
.sort((a, b) => (b.updated_at || 0) - (a.updated_at || 0));
|
|
2412
|
+
const keepOfflineCount = Math.max(0, DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT - onlineRemoteProfiles.length);
|
|
2413
|
+
const keptRemoteProfiles = [
|
|
2414
|
+
...onlineRemoteProfiles,
|
|
2415
|
+
...offlineRemoteProfiles.slice(0, keepOfflineCount),
|
|
2416
|
+
];
|
|
2417
|
+
const keptRemoteIds = new Set(keptRemoteProfiles.map((profile) => profile.agent_id));
|
|
2418
|
+
const removedIds = remoteProfiles
|
|
2419
|
+
.map((profile) => profile.agent_id)
|
|
2420
|
+
.filter((agentId) => !keptRemoteIds.has(agentId));
|
|
2421
|
+
if (removedIds.length === 0) {
|
|
2422
|
+
return 0;
|
|
2423
|
+
}
|
|
2424
|
+
const next = (0, core_1.createEmptyDirectoryState)();
|
|
2425
|
+
const selfProfile = selfAgentId ? this.directory.profiles[selfAgentId] : null;
|
|
2426
|
+
if (selfProfile) {
|
|
2427
|
+
next.profiles[selfAgentId] = selfProfile;
|
|
2428
|
+
const selfPresence = this.directory.presence[selfAgentId];
|
|
2429
|
+
if (typeof selfPresence === "number" && Number.isFinite(selfPresence)) {
|
|
2430
|
+
next.presence[selfAgentId] = selfPresence;
|
|
2431
|
+
}
|
|
2432
|
+
const rebuilt = (0, core_1.rebuildIndexForProfile)(next, selfProfile);
|
|
2433
|
+
next.index = rebuilt.index;
|
|
2434
|
+
}
|
|
2435
|
+
for (const profile of keptRemoteProfiles) {
|
|
2436
|
+
next.profiles[profile.agent_id] = profile;
|
|
2437
|
+
const seenAt = this.directory.presence[profile.agent_id];
|
|
2438
|
+
if (typeof seenAt === "number" && Number.isFinite(seenAt)) {
|
|
2439
|
+
next.presence[profile.agent_id] = seenAt;
|
|
2440
|
+
}
|
|
2441
|
+
const rebuilt = (0, core_1.rebuildIndexForProfile)(next, profile);
|
|
2442
|
+
next.index = rebuilt.index;
|
|
2443
|
+
}
|
|
2444
|
+
this.directory = (0, core_1.dedupeIndex)(next);
|
|
2445
|
+
return removedIds.length;
|
|
2446
|
+
}
|
|
2258
2447
|
compactCacheInMemory() {
|
|
2259
2448
|
const cleaned = (0, core_1.cleanupExpiredPresence)(this.directory, Date.now(), PRESENCE_TTL_MS);
|
|
2260
2449
|
this.directory = (0, core_1.dedupeIndex)(cleaned.state);
|
|
2450
|
+
this.pruneRemoteProfilesInMemory();
|
|
2261
2451
|
return cleaned.removed;
|
|
2262
2452
|
}
|
|
2263
2453
|
async publish(topic, data) {
|
|
@@ -2905,6 +3095,36 @@ async function main() {
|
|
|
2905
3095
|
app.get("/api/runtime/paths", (_req, res) => {
|
|
2906
3096
|
sendOk(res, node.getRuntimePaths());
|
|
2907
3097
|
});
|
|
3098
|
+
app.get("/api/app/update-status", (_req, res) => {
|
|
3099
|
+
sendOk(res, node.getAppUpdateStatus());
|
|
3100
|
+
});
|
|
3101
|
+
app.post("/api/app/update", asyncRoute(async (_req, res) => {
|
|
3102
|
+
const status = node.getAppUpdateStatus();
|
|
3103
|
+
if (!status.update_available || !status.latest_version) {
|
|
3104
|
+
sendOk(res, {
|
|
3105
|
+
started: false,
|
|
3106
|
+
current_version: status.current_version,
|
|
3107
|
+
latest_version: status.latest_version,
|
|
3108
|
+
platform: status.platform,
|
|
3109
|
+
reason: status.check_error || "already_current",
|
|
3110
|
+
}, { message: "Already on the latest version" });
|
|
3111
|
+
return;
|
|
3112
|
+
}
|
|
3113
|
+
sendOk(res, {
|
|
3114
|
+
started: true,
|
|
3115
|
+
current_version: status.current_version,
|
|
3116
|
+
target_version: status.latest_version,
|
|
3117
|
+
platform: status.platform,
|
|
3118
|
+
}, { message: `Updating to ${status.latest_version}` });
|
|
3119
|
+
setTimeout(() => {
|
|
3120
|
+
try {
|
|
3121
|
+
node.startAppUpdate();
|
|
3122
|
+
}
|
|
3123
|
+
catch {
|
|
3124
|
+
// best effort after response has been sent
|
|
3125
|
+
}
|
|
3126
|
+
}, 150);
|
|
3127
|
+
}));
|
|
2908
3128
|
app.put("/api/profile", asyncRoute(async (req, res) => {
|
|
2909
3129
|
const body = req.body;
|
|
2910
3130
|
const tags = Array.isArray(body.tags)
|
|
@@ -349,6 +349,7 @@ class RelayPreviewAdapter {
|
|
|
349
349
|
if (!this.lastJoinAt || Date.now() - this.lastJoinAt > Math.max(45_000, this.pollIntervalMs * 6)) {
|
|
350
350
|
await this.joinRoom(reason);
|
|
351
351
|
}
|
|
352
|
+
this.ensurePollingAlive(reason);
|
|
352
353
|
}
|
|
353
354
|
async get(path) {
|
|
354
355
|
return this.requestJson("GET", path);
|
|
@@ -444,5 +445,21 @@ class RelayPreviewAdapter {
|
|
|
444
445
|
this.pollOnce().catch(() => { });
|
|
445
446
|
}, Math.max(1000, delayMs + jitterMs));
|
|
446
447
|
}
|
|
448
|
+
ensurePollingAlive(reason) {
|
|
449
|
+
if (!this.started)
|
|
450
|
+
return;
|
|
451
|
+
const pollStaleMs = Math.max(45_000, this.pollIntervalMs * 6);
|
|
452
|
+
const pollMissing = !this.poller;
|
|
453
|
+
const pollStale = Boolean(this.lastPollAt) && Date.now() - this.lastPollAt > pollStaleMs;
|
|
454
|
+
if (!pollMissing && !pollStale) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
this.recordDiscovery("poll_recover_scheduled", {
|
|
458
|
+
endpoint: this.activeEndpoint,
|
|
459
|
+
detail: `${reason}:${pollMissing ? "missing" : "stale"}`,
|
|
460
|
+
});
|
|
461
|
+
this.currentPollDelayMs = this.pollIntervalMs;
|
|
462
|
+
this.scheduleNextPoll(0);
|
|
463
|
+
}
|
|
447
464
|
}
|
|
448
465
|
exports.RelayPreviewAdapter = RelayPreviewAdapter;
|