@silicaclaw/cli 2026.3.20-4 → 2026.3.20-6

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 (27) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/INSTALL.md +2 -2
  3. package/README.md +2 -2
  4. package/VERSION +1 -1
  5. package/apps/local-console/dist/apps/local-console/src/server.d.ts +13 -2
  6. package/apps/local-console/dist/apps/local-console/src/server.js +149 -20
  7. package/apps/local-console/dist/packages/network/src/relayPreview.d.ts +4 -0
  8. package/apps/local-console/dist/packages/network/src/relayPreview.js +37 -6
  9. package/apps/local-console/public/app/app.js +45 -2
  10. package/apps/local-console/public/app/network.js +35 -4
  11. package/apps/local-console/public/app/social.js +1 -0
  12. package/apps/local-console/public/app/styles.css +35 -0
  13. package/apps/local-console/public/app/template.js +1 -0
  14. package/apps/local-console/public/app/translations.js +18 -6
  15. package/apps/local-console/src/server.ts +175 -16
  16. package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.d.ts +4 -0
  17. package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.js +37 -6
  18. package/node_modules/@silicaclaw/network/src/relayPreview.ts +41 -6
  19. package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -1
  20. package/openclaw-skills/silicaclaw-broadcast/manifest.json +1 -1
  21. package/openclaw-skills/silicaclaw-owner-push/VERSION +1 -1
  22. package/openclaw-skills/silicaclaw-owner-push/manifest.json +1 -1
  23. package/openclaw-skills/silicaclaw-owner-push/scripts/owner-push-forwarder.mjs +84 -1
  24. package/package.json +1 -1
  25. package/packages/network/dist/packages/network/src/relayPreview.d.ts +4 -0
  26. package/packages/network/dist/packages/network/src/relayPreview.js +37 -6
  27. package/packages/network/src/relayPreview.ts +41 -6
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { homedir } from "node:os";
5
5
  import { dirname, resolve } from "node:path";
6
6
  import { spawn } from "node:child_process";
@@ -12,9 +12,11 @@ const OWNER_FORWARD_CMD = String(process.env.OPENCLAW_OWNER_FORWARD_CMD || "").t
12
12
  const STATE_PATH = resolve(
13
13
  String(process.env.OPENCLAW_OWNER_FORWARD_STATE_PATH || resolve(homedir(), ".openclaw", "workspace", "state", "silicaclaw-owner-push.json"))
14
14
  );
15
+ const LOCK_PATH = `${STATE_PATH}.lock`;
15
16
  const LATEST_ONLY = String(process.env.OPENCLAW_FORWARD_LATEST_ONLY || "true").trim().toLowerCase() !== "false";
16
17
  const ONCE = process.argv.includes("--once");
17
18
  const VERBOSE = process.argv.includes("--verbose");
19
+ let lockFd = null;
18
20
 
19
21
  function parseListEnv(name) {
20
22
  return String(process.env[name] || "")
@@ -89,6 +91,85 @@ function saveState(state) {
89
91
  writeFileSync(STATE_PATH, JSON.stringify(state, null, 2), "utf8");
90
92
  }
91
93
 
94
+ function isPidRunning(pid) {
95
+ if (!pid || !Number.isFinite(pid) || pid <= 0) return false;
96
+ try {
97
+ process.kill(pid, 0);
98
+ return true;
99
+ } catch {
100
+ return false;
101
+ }
102
+ }
103
+
104
+ function releaseLock() {
105
+ if (lockFd !== null) {
106
+ try {
107
+ closeSync(lockFd);
108
+ } catch {
109
+ // ignore
110
+ }
111
+ try {
112
+ rmSync(LOCK_PATH, { force: true });
113
+ } catch {
114
+ // ignore
115
+ }
116
+ lockFd = null;
117
+ }
118
+ }
119
+
120
+ function acquireLock() {
121
+ mkdirSync(dirname(LOCK_PATH), { recursive: true });
122
+ try {
123
+ lockFd = openSync(LOCK_PATH, "wx");
124
+ writeFileSync(lockFd, JSON.stringify({
125
+ pid: process.pid,
126
+ started_at: new Date().toISOString(),
127
+ state_path: STATE_PATH,
128
+ }, null, 2), "utf8");
129
+ process.on("exit", releaseLock);
130
+ process.on("SIGINT", () => {
131
+ releaseLock();
132
+ process.exit(130);
133
+ });
134
+ process.on("SIGTERM", () => {
135
+ releaseLock();
136
+ process.exit(143);
137
+ });
138
+ return;
139
+ } catch {
140
+ // fall through
141
+ }
142
+
143
+ try {
144
+ const existing = JSON.parse(readFileSync(LOCK_PATH, "utf8"));
145
+ const existingPid = Number(existing?.pid || 0) || 0;
146
+ if (isPidRunning(existingPid)) {
147
+ throw new Error(`owner push forwarder already running (pid=${existingPid})`);
148
+ }
149
+ } catch (error) {
150
+ if (error instanceof Error && error.message.includes("already running")) {
151
+ throw error;
152
+ }
153
+ }
154
+
155
+ rmSync(LOCK_PATH, { force: true });
156
+ lockFd = openSync(LOCK_PATH, "wx");
157
+ writeFileSync(lockFd, JSON.stringify({
158
+ pid: process.pid,
159
+ started_at: new Date().toISOString(),
160
+ state_path: STATE_PATH,
161
+ }, null, 2), "utf8");
162
+ process.on("exit", releaseLock);
163
+ process.on("SIGINT", () => {
164
+ releaseLock();
165
+ process.exit(130);
166
+ });
167
+ process.on("SIGTERM", () => {
168
+ releaseLock();
169
+ process.exit(143);
170
+ });
171
+ }
172
+
92
173
  function trimState(state) {
93
174
  const recentIds = Array.isArray(state.seen_ids) ? state.seen_ids.slice(-500) : [];
94
175
  const pushedEntries = Object.entries(state.pushed_at || {}).slice(-500);
@@ -253,10 +334,12 @@ async function pollOnce(state) {
253
334
  }
254
335
 
255
336
  async function main() {
337
+ acquireLock();
256
338
  const state = loadState();
257
339
  if (VERBOSE) {
258
340
  console.log(`SilicaClaw owner push watching ${API_BASE}`);
259
341
  console.log(`State file: ${STATE_PATH}`);
342
+ console.log(`Lock file: ${LOCK_PATH}`);
260
343
  console.log(`Latest-only mode: ${LATEST_ONLY ? "on" : "off"}`);
261
344
  }
262
345
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@silicaclaw/cli",
3
- "version": "2026.3.20-4",
3
+ "version": "2026.3.20-6",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -22,6 +22,10 @@ type RelayPeer = {
22
22
  last_seen_at: number;
23
23
  messages_seen: number;
24
24
  reconnect_attempts: number;
25
+ meta?: {
26
+ signal_queue_size?: number;
27
+ relay_queue_size?: number;
28
+ };
25
29
  };
26
30
  type RelayDiagnostics = {
27
31
  adapter: "relay-preview";
@@ -109,7 +109,6 @@ class RelayPreviewAdapter {
109
109
  try {
110
110
  await this.joinRoom("start");
111
111
  this.started = true;
112
- await this.refreshPeers();
113
112
  await this.pollOnce();
114
113
  this.scheduleNextPoll(this.pollIntervalMs);
115
114
  this.recordDiscovery("signaling_connected", { endpoint: this.activeEndpoint });
@@ -258,8 +257,10 @@ class RelayPreviewAdapter {
258
257
  const payload = await this.get(`/peers?room=${encodeURIComponent(this.room)}`);
259
258
  this.lastPeerRefreshAt = Date.now();
260
259
  this.stats.peers_refresh_succeeded += 1;
261
- const peerIds = Array.isArray(payload?.peers) ? payload.peers : [];
262
- this.updatePeersFromList(peerIds);
260
+ const peerItems = Array.isArray(payload?.peer_details) && payload.peer_details.length
261
+ ? payload.peer_details
262
+ : Array.isArray(payload?.peers) ? payload.peers : [];
263
+ this.updatePeersFromList(peerItems);
263
264
  }
264
265
  onEnvelope(envelope) {
265
266
  this.stats.received_total += 1;
@@ -340,9 +341,13 @@ class RelayPreviewAdapter {
340
341
  }
341
342
  async joinRoom(reason) {
342
343
  this.stats.join_attempted += 1;
343
- await this.post("/join", { room: this.room, peer_id: this.peerId });
344
+ const payload = await this.post("/join", { room: this.room, peer_id: this.peerId });
344
345
  this.lastJoinAt = Date.now();
345
346
  this.stats.join_succeeded += 1;
347
+ if (Array.isArray(payload?.peers)) {
348
+ this.updatePeersFromList(payload.peers);
349
+ this.lastPeerRefreshAt = this.lastJoinAt;
350
+ }
346
351
  this.recordDiscovery("join_ok", { endpoint: this.activeEndpoint, detail: reason });
347
352
  }
348
353
  async maybeRefreshJoin(reason) {
@@ -407,13 +412,38 @@ class RelayPreviewAdapter {
407
412
  throw new Error(errors.join(" | "));
408
413
  }
409
414
  updatePeersFromList(values) {
410
- const peerIds = values.map((value) => String(value || "").trim()).filter(Boolean);
415
+ const parsedPeers = [];
416
+ for (const value of values) {
417
+ if (typeof value === "string") {
418
+ const peerId = String(value || "").trim();
419
+ if (peerId) {
420
+ parsedPeers.push({ peer_id: peerId });
421
+ }
422
+ continue;
423
+ }
424
+ if (value && typeof value === "object") {
425
+ const raw = value;
426
+ const peerId = String(raw.peer_id || "").trim();
427
+ if (!peerId) {
428
+ continue;
429
+ }
430
+ parsedPeers.push({
431
+ peer_id: peerId,
432
+ meta: {
433
+ signal_queue_size: Number(raw.signal_queue_size ?? 0),
434
+ relay_queue_size: Number(raw.relay_queue_size ?? 0),
435
+ },
436
+ });
437
+ }
438
+ }
439
+ const peerIds = parsedPeers.map((peer) => peer.peer_id);
411
440
  if (!peerIds.includes(this.peerId)) {
412
441
  void this.joinRoom("self_missing_from_peers").catch(() => { });
413
442
  }
414
443
  const now = Date.now();
415
444
  const next = new Map();
416
- for (const peerId of peerIds) {
445
+ for (const peerInfo of parsedPeers) {
446
+ const peerId = peerInfo.peer_id;
417
447
  if (peerId === this.peerId)
418
448
  continue;
419
449
  const existing = this.peers.get(peerId);
@@ -427,6 +457,7 @@ class RelayPreviewAdapter {
427
457
  last_seen_at: now,
428
458
  messages_seen: existing?.messages_seen ?? 0,
429
459
  reconnect_attempts: existing?.reconnect_attempts ?? 0,
460
+ meta: peerInfo.meta || existing?.meta,
430
461
  });
431
462
  }
432
463
  for (const peerId of this.peers.keys()) {
@@ -34,6 +34,10 @@ type RelayPeer = {
34
34
  last_seen_at: number;
35
35
  messages_seen: number;
36
36
  reconnect_attempts: number;
37
+ meta?: {
38
+ signal_queue_size?: number;
39
+ relay_queue_size?: number;
40
+ };
37
41
  };
38
42
 
39
43
  type RelayDiagnostics = {
@@ -227,7 +231,6 @@ export class RelayPreviewAdapter implements NetworkAdapter {
227
231
  try {
228
232
  await this.joinRoom("start");
229
233
  this.started = true;
230
- await this.refreshPeers();
231
234
  await this.pollOnce();
232
235
  this.scheduleNextPoll(this.pollIntervalMs);
233
236
  this.recordDiscovery("signaling_connected", { endpoint: this.activeEndpoint });
@@ -375,8 +378,10 @@ export class RelayPreviewAdapter implements NetworkAdapter {
375
378
  const payload = await this.get(`/peers?room=${encodeURIComponent(this.room)}`);
376
379
  this.lastPeerRefreshAt = Date.now();
377
380
  this.stats.peers_refresh_succeeded += 1;
378
- const peerIds = Array.isArray(payload?.peers) ? payload.peers : [];
379
- this.updatePeersFromList(peerIds);
381
+ const peerItems = Array.isArray(payload?.peer_details) && payload.peer_details.length
382
+ ? payload.peer_details
383
+ : Array.isArray(payload?.peers) ? payload.peers : [];
384
+ this.updatePeersFromList(peerItems);
380
385
  }
381
386
 
382
387
  private onEnvelope(envelope: unknown): void {
@@ -457,9 +462,13 @@ export class RelayPreviewAdapter implements NetworkAdapter {
457
462
 
458
463
  private async joinRoom(reason: string): Promise<void> {
459
464
  this.stats.join_attempted += 1;
460
- await this.post("/join", { room: this.room, peer_id: this.peerId });
465
+ const payload = await this.post("/join", { room: this.room, peer_id: this.peerId });
461
466
  this.lastJoinAt = Date.now();
462
467
  this.stats.join_succeeded += 1;
468
+ if (Array.isArray(payload?.peers)) {
469
+ this.updatePeersFromList(payload.peers);
470
+ this.lastPeerRefreshAt = this.lastJoinAt;
471
+ }
463
472
  this.recordDiscovery("join_ok", { endpoint: this.activeEndpoint, detail: reason });
464
473
  }
465
474
 
@@ -528,13 +537,38 @@ export class RelayPreviewAdapter implements NetworkAdapter {
528
537
  }
529
538
 
530
539
  private updatePeersFromList(values: unknown[]): void {
531
- const peerIds = values.map((value) => String(value || "").trim()).filter(Boolean);
540
+ const parsedPeers: Array<{ peer_id: string; meta?: RelayPeer["meta"] }> = [];
541
+ for (const value of values) {
542
+ if (typeof value === "string") {
543
+ const peerId = String(value || "").trim();
544
+ if (peerId) {
545
+ parsedPeers.push({ peer_id: peerId });
546
+ }
547
+ continue;
548
+ }
549
+ if (value && typeof value === "object") {
550
+ const raw = value as Record<string, unknown>;
551
+ const peerId = String(raw.peer_id || "").trim();
552
+ if (!peerId) {
553
+ continue;
554
+ }
555
+ parsedPeers.push({
556
+ peer_id: peerId,
557
+ meta: {
558
+ signal_queue_size: Number(raw.signal_queue_size ?? 0),
559
+ relay_queue_size: Number(raw.relay_queue_size ?? 0),
560
+ },
561
+ });
562
+ }
563
+ }
564
+ const peerIds = parsedPeers.map((peer) => peer.peer_id);
532
565
  if (!peerIds.includes(this.peerId)) {
533
566
  void this.joinRoom("self_missing_from_peers").catch(() => {});
534
567
  }
535
568
  const now = Date.now();
536
569
  const next = new Map<string, RelayPeer>();
537
- for (const peerId of peerIds) {
570
+ for (const peerInfo of parsedPeers) {
571
+ const peerId = peerInfo.peer_id;
538
572
  if (peerId === this.peerId) continue;
539
573
  const existing = this.peers.get(peerId);
540
574
  if (!existing) {
@@ -547,6 +581,7 @@ export class RelayPreviewAdapter implements NetworkAdapter {
547
581
  last_seen_at: now,
548
582
  messages_seen: existing?.messages_seen ?? 0,
549
583
  reconnect_attempts: existing?.reconnect_attempts ?? 0,
584
+ meta: peerInfo.meta || existing?.meta,
550
585
  });
551
586
  }
552
587
  for (const peerId of this.peers.keys()) {