@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.
- package/CHANGELOG.md +12 -0
- package/INSTALL.md +2 -2
- package/README.md +2 -2
- package/VERSION +1 -1
- package/apps/local-console/dist/apps/local-console/src/server.d.ts +13 -2
- package/apps/local-console/dist/apps/local-console/src/server.js +149 -20
- package/apps/local-console/dist/packages/network/src/relayPreview.d.ts +4 -0
- package/apps/local-console/dist/packages/network/src/relayPreview.js +37 -6
- package/apps/local-console/public/app/app.js +45 -2
- package/apps/local-console/public/app/network.js +35 -4
- package/apps/local-console/public/app/social.js +1 -0
- package/apps/local-console/public/app/styles.css +35 -0
- package/apps/local-console/public/app/template.js +1 -0
- package/apps/local-console/public/app/translations.js +18 -6
- package/apps/local-console/src/server.ts +175 -16
- package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.d.ts +4 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.js +37 -6
- package/node_modules/@silicaclaw/network/src/relayPreview.ts +41 -6
- package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -1
- package/openclaw-skills/silicaclaw-broadcast/manifest.json +1 -1
- package/openclaw-skills/silicaclaw-owner-push/VERSION +1 -1
- package/openclaw-skills/silicaclaw-owner-push/manifest.json +1 -1
- package/openclaw-skills/silicaclaw-owner-push/scripts/owner-push-forwarder.mjs +84 -1
- package/package.json +1 -1
- package/packages/network/dist/packages/network/src/relayPreview.d.ts +4 -0
- package/packages/network/dist/packages/network/src/relayPreview.js +37 -6
- package/packages/network/src/relayPreview.ts +41 -6
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## v1.0 beta - 2026-03-20
|
|
4
4
|
|
|
5
|
+
### 2026.3.20-6
|
|
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-5
|
|
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-4
|
|
6
18
|
|
|
7
19
|
- release build:
|
package/INSTALL.md
CHANGED
|
@@ -211,9 +211,9 @@ npx clawhub publish openclaw-skills/silicaclaw-broadcast \
|
|
|
211
211
|
npx clawhub publish openclaw-skills/silicaclaw-owner-push \
|
|
212
212
|
--slug silicaclaw-owner-push \
|
|
213
213
|
--name "SilicaClaw Owner Push" \
|
|
214
|
-
--version 2026.3.20-beta.
|
|
214
|
+
--version 2026.3.20-beta.3 \
|
|
215
215
|
--tags latest \
|
|
216
|
-
--changelog "Added
|
|
216
|
+
--changelog "Added single-instance lock protection so owner push avoids duplicate notifications when multiple forwarders start at the same time."
|
|
217
217
|
```
|
|
218
218
|
|
|
219
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
|
@@ -283,9 +283,9 @@ npx clawhub publish openclaw-skills/silicaclaw-broadcast \
|
|
|
283
283
|
npx clawhub publish openclaw-skills/silicaclaw-owner-push \
|
|
284
284
|
--slug silicaclaw-owner-push \
|
|
285
285
|
--name "SilicaClaw Owner Push" \
|
|
286
|
-
--version 2026.3.20-beta.
|
|
286
|
+
--version 2026.3.20-beta.3 \
|
|
287
287
|
--tags latest \
|
|
288
|
-
--changelog "Added
|
|
288
|
+
--changelog "Added single-instance lock protection so owner push avoids duplicate notifications when multiple forwarders start at the same time."
|
|
289
289
|
```
|
|
290
290
|
|
|
291
291
|
ClawHub publishes the OpenClaw skill folders, not the npm CLI package.
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
v2026.3.20-
|
|
1
|
+
v2026.3.20-6
|
|
@@ -72,11 +72,17 @@ type OpenClawBridgeStatus = {
|
|
|
72
72
|
gateway_url: string;
|
|
73
73
|
gateway_port: number;
|
|
74
74
|
gateway_reachable: boolean;
|
|
75
|
+
status_command: string | null;
|
|
76
|
+
status_ok: boolean;
|
|
77
|
+
status_summary: string | null;
|
|
78
|
+
gateway_probe_command: string | null;
|
|
79
|
+
gateway_probe_ok: boolean;
|
|
80
|
+
gateway_probe_summary: string | null;
|
|
75
81
|
configured_gateway_url: string;
|
|
76
82
|
configured_gateway_port: number;
|
|
77
83
|
configured_gateway_bind: string | null;
|
|
78
84
|
configured_gateway_config_path: string | null;
|
|
79
|
-
detection_mode: "process" | "gateway" | "process+gateway" | "not_running";
|
|
85
|
+
detection_mode: "gateway-probe" | "gateway-probe+process" | "gateway-probe+gateway" | "gateway-probe+process+gateway" | "process" | "gateway" | "process+gateway" | "not_running";
|
|
80
86
|
};
|
|
81
87
|
skill_learning: {
|
|
82
88
|
available: boolean;
|
|
@@ -162,6 +168,10 @@ export declare class LocalNodeService {
|
|
|
162
168
|
private broadcastCount;
|
|
163
169
|
private lastMessageAt;
|
|
164
170
|
private lastBroadcastAt;
|
|
171
|
+
private lastProfileBroadcastAt;
|
|
172
|
+
private lastProfileBroadcastSignature;
|
|
173
|
+
private lastReplayBroadcastAt;
|
|
174
|
+
private lastReplayBroadcastSignature;
|
|
165
175
|
private lastBroadcastErrorAt;
|
|
166
176
|
private lastBroadcastError;
|
|
167
177
|
private broadcastFailureCount;
|
|
@@ -632,7 +642,7 @@ export declare class LocalNodeService {
|
|
|
632
642
|
openclaw: {
|
|
633
643
|
detected: boolean;
|
|
634
644
|
running: boolean;
|
|
635
|
-
detection_mode: "process+gateway" | "gateway" | "process" | "not_running";
|
|
645
|
+
detection_mode: "gateway" | "gateway-probe+process+gateway" | "gateway-probe+gateway" | "gateway-probe+process" | "gateway-probe" | "process+gateway" | "process" | "not_running";
|
|
636
646
|
gateway_url: string;
|
|
637
647
|
workspace_install_root: string;
|
|
638
648
|
legacy_install_root: string;
|
|
@@ -754,6 +764,7 @@ export declare class LocalNodeService {
|
|
|
754
764
|
reason: string;
|
|
755
765
|
error?: string;
|
|
756
766
|
}>;
|
|
767
|
+
private shouldPublishProfileRecord;
|
|
757
768
|
private maybeRecoverFromBroadcastFailure;
|
|
758
769
|
private hydrateFromDisk;
|
|
759
770
|
private applySocialConfigOnCurrentState;
|
|
@@ -63,6 +63,8 @@ const SOCIAL_MESSAGE_MAX_AGE_MS = Number(process.env.SOCIAL_MESSAGE_MAX_AGE_MS |
|
|
|
63
63
|
const SOCIAL_MESSAGE_OBSERVATION_HISTORY_LIMIT = Number(process.env.SOCIAL_MESSAGE_OBSERVATION_HISTORY_LIMIT || 500);
|
|
64
64
|
const SOCIAL_MESSAGE_REPLAY_WINDOW_MS = Number(process.env.SOCIAL_MESSAGE_REPLAY_WINDOW_MS || 10 * 60_000);
|
|
65
65
|
const SOCIAL_MESSAGE_REPLAY_MAX_PER_BROADCAST = Number(process.env.SOCIAL_MESSAGE_REPLAY_MAX_PER_BROADCAST || 3);
|
|
66
|
+
const SOCIAL_MESSAGE_REPLAY_REFRESH_INTERVAL_MS = Number(process.env.SOCIAL_MESSAGE_REPLAY_REFRESH_INTERVAL_MS || 120_000);
|
|
67
|
+
const PROFILE_RELAY_REFRESH_INTERVAL_MS = Number(process.env.PROFILE_RELAY_REFRESH_INTERVAL_MS || 120_000);
|
|
66
68
|
const SOCIAL_MESSAGE_BLOCKED_AGENT_IDS = new Set(dedupeStrings(parseListEnv(process.env.SOCIAL_MESSAGE_BLOCKED_AGENT_IDS || "")));
|
|
67
69
|
const SOCIAL_MESSAGE_BLOCKED_TERMS = dedupeStrings(parseListEnv(process.env.SOCIAL_MESSAGE_BLOCKED_TERMS || ""))
|
|
68
70
|
.map((term) => term.trim().toLowerCase())
|
|
@@ -387,8 +389,71 @@ function readOpenClawConfiguredGateway(workspaceRoot) {
|
|
|
387
389
|
gateway_url: OPENCLAW_GATEWAY_URL,
|
|
388
390
|
};
|
|
389
391
|
}
|
|
392
|
+
function resolveOpenClawStatusCommand(workspaceRoot) {
|
|
393
|
+
const explicitBin = String(process.env.OPENCLAW_BIN || "").trim();
|
|
394
|
+
if (explicitBin) {
|
|
395
|
+
return { cmd: explicitBin, args: ["status"] };
|
|
396
|
+
}
|
|
397
|
+
const configuredSourceDir = String(process.env.OPENCLAW_SOURCE_DIR || "").trim();
|
|
398
|
+
const defaultSourceDir = defaultOpenClawSourceDir(workspaceRoot);
|
|
399
|
+
const sourceDir = configuredSourceDir || defaultSourceDir;
|
|
400
|
+
const sourceEntry = existingPathOrNull((0, path_1.resolve)(sourceDir, "openclaw.mjs"));
|
|
401
|
+
if (sourceEntry) {
|
|
402
|
+
return { cmd: process.execPath, args: [sourceEntry, "status"] };
|
|
403
|
+
}
|
|
404
|
+
const commandPath = resolveExecutableInPath("openclaw");
|
|
405
|
+
if (commandPath) {
|
|
406
|
+
return { cmd: commandPath, args: ["status"] };
|
|
407
|
+
}
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
function resolveOpenClawGatewayProbeCommand(workspaceRoot) {
|
|
411
|
+
const explicitBin = String(process.env.OPENCLAW_BIN || "").trim();
|
|
412
|
+
if (explicitBin) {
|
|
413
|
+
return { cmd: explicitBin, args: ["gateway", "probe"] };
|
|
414
|
+
}
|
|
415
|
+
const configuredSourceDir = String(process.env.OPENCLAW_SOURCE_DIR || "").trim();
|
|
416
|
+
const defaultSourceDir = defaultOpenClawSourceDir(workspaceRoot);
|
|
417
|
+
const sourceDir = configuredSourceDir || defaultSourceDir;
|
|
418
|
+
const sourceEntry = existingPathOrNull((0, path_1.resolve)(sourceDir, "openclaw.mjs"));
|
|
419
|
+
if (sourceEntry) {
|
|
420
|
+
return { cmd: process.execPath, args: [sourceEntry, "gateway", "probe"] };
|
|
421
|
+
}
|
|
422
|
+
const commandPath = resolveExecutableInPath("openclaw");
|
|
423
|
+
if (commandPath) {
|
|
424
|
+
return { cmd: commandPath, args: ["gateway", "probe"] };
|
|
425
|
+
}
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
390
428
|
function detectOpenClawRuntime(workspaceRoot) {
|
|
391
429
|
const configuredGateway = readOpenClawConfiguredGateway(workspaceRoot);
|
|
430
|
+
const statusCommand = resolveOpenClawStatusCommand(workspaceRoot);
|
|
431
|
+
const gatewayProbeCommand = resolveOpenClawGatewayProbeCommand(workspaceRoot);
|
|
432
|
+
const statusProbe = statusCommand
|
|
433
|
+
? (0, child_process_1.spawnSync)(statusCommand.cmd, statusCommand.args, {
|
|
434
|
+
encoding: "utf8",
|
|
435
|
+
env: process.env,
|
|
436
|
+
})
|
|
437
|
+
: null;
|
|
438
|
+
const statusStdout = String(statusProbe?.stdout || "");
|
|
439
|
+
const statusStderr = String(statusProbe?.stderr || "");
|
|
440
|
+
const statusLooksConfigured = Boolean(statusCommand &&
|
|
441
|
+
statusProbe &&
|
|
442
|
+
statusProbe.status === 0 &&
|
|
443
|
+
(/\bChannels\b/i.test(statusStdout) ||
|
|
444
|
+
/\bSessions\b/i.test(statusStdout) ||
|
|
445
|
+
/\bNext steps:\b/i.test(statusStdout)));
|
|
446
|
+
const gatewayStatusProbe = gatewayProbeCommand
|
|
447
|
+
? (0, child_process_1.spawnSync)(gatewayProbeCommand.cmd, gatewayProbeCommand.args, {
|
|
448
|
+
encoding: "utf8",
|
|
449
|
+
env: process.env,
|
|
450
|
+
})
|
|
451
|
+
: null;
|
|
452
|
+
const gatewayStatusStdout = String(gatewayStatusProbe?.stdout || "");
|
|
453
|
+
const gatewayStatusStderr = String(gatewayStatusProbe?.stderr || "");
|
|
454
|
+
const gatewayProbeOk = Boolean(gatewayProbeCommand &&
|
|
455
|
+
gatewayStatusProbe &&
|
|
456
|
+
gatewayStatusProbe.status === 0);
|
|
392
457
|
const result = (0, child_process_1.spawnSync)("ps", ["-Ao", "pid=,ppid=,command="], {
|
|
393
458
|
encoding: "utf8",
|
|
394
459
|
});
|
|
@@ -471,6 +536,12 @@ function detectOpenClawRuntime(workspaceRoot) {
|
|
|
471
536
|
const allProcesses = Array.from(combinedProcesses.values());
|
|
472
537
|
const gatewayReachable = gatewayListeners.length > 0;
|
|
473
538
|
const detectionNotes = [];
|
|
539
|
+
if (statusProbe && statusProbe.status !== 0) {
|
|
540
|
+
detectionNotes.push(String(statusStderr || "openclaw status failed").trim());
|
|
541
|
+
}
|
|
542
|
+
if (gatewayStatusProbe && gatewayStatusProbe.status !== 0) {
|
|
543
|
+
detectionNotes.push(String(gatewayStatusStderr || "openclaw gateway probe failed").trim());
|
|
544
|
+
}
|
|
474
545
|
if (result.status !== 0)
|
|
475
546
|
detectionNotes.push(String(result.stderr || "ps failed").trim());
|
|
476
547
|
if (gatewayProbe.status !== 0 && gatewayLines.length === 0) {
|
|
@@ -479,24 +550,52 @@ function detectOpenClawRuntime(workspaceRoot) {
|
|
|
479
550
|
const gatewayPort = preferredListener?.port || configuredGateway.gateway_port;
|
|
480
551
|
const gatewayUrl = `http://${OPENCLAW_GATEWAY_HOST}:${gatewayPort}/`;
|
|
481
552
|
return {
|
|
482
|
-
running: allProcesses.length > 0 || gatewayReachable,
|
|
553
|
+
running: gatewayProbeOk || allProcesses.length > 0 || gatewayReachable,
|
|
483
554
|
process_count: allProcesses.length,
|
|
484
555
|
processes: allProcesses.slice(0, 10),
|
|
485
556
|
detection_error: detectionNotes.filter(Boolean).join(" | ") || null,
|
|
486
557
|
gateway_url: gatewayUrl,
|
|
487
558
|
gateway_port: gatewayPort,
|
|
488
559
|
gateway_reachable: gatewayReachable,
|
|
560
|
+
status_command: statusCommand ? [statusCommand.cmd, ...statusCommand.args].join(" ") : null,
|
|
561
|
+
status_ok: statusLooksConfigured,
|
|
562
|
+
status_summary: statusLooksConfigured
|
|
563
|
+
? statusStdout
|
|
564
|
+
.split("\n")
|
|
565
|
+
.map((line) => line.trim())
|
|
566
|
+
.filter(Boolean)
|
|
567
|
+
.slice(0, 6)
|
|
568
|
+
.join(" | ")
|
|
569
|
+
: null,
|
|
570
|
+
gateway_probe_command: gatewayProbeCommand ? [gatewayProbeCommand.cmd, ...gatewayProbeCommand.args].join(" ") : null,
|
|
571
|
+
gateway_probe_ok: gatewayProbeOk,
|
|
572
|
+
gateway_probe_summary: gatewayProbeOk
|
|
573
|
+
? gatewayStatusStdout
|
|
574
|
+
.split("\n")
|
|
575
|
+
.map((line) => line.trim())
|
|
576
|
+
.filter(Boolean)
|
|
577
|
+
.slice(0, 4)
|
|
578
|
+
.join(" | ")
|
|
579
|
+
: null,
|
|
489
580
|
configured_gateway_url: configuredGateway.gateway_url,
|
|
490
581
|
configured_gateway_port: configuredGateway.gateway_port,
|
|
491
582
|
configured_gateway_bind: configuredGateway.gateway_bind,
|
|
492
583
|
configured_gateway_config_path: configuredGateway.config_path,
|
|
493
|
-
detection_mode:
|
|
494
|
-
?
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
584
|
+
detection_mode: gatewayProbeOk
|
|
585
|
+
? (processes.length > 0 && gatewayReachable
|
|
586
|
+
? "gateway-probe+process+gateway"
|
|
587
|
+
: gatewayReachable
|
|
588
|
+
? "gateway-probe+gateway"
|
|
589
|
+
: processes.length > 0
|
|
590
|
+
? "gateway-probe+process"
|
|
591
|
+
: "gateway-probe")
|
|
592
|
+
: processes.length > 0 && gatewayReachable
|
|
593
|
+
? "process+gateway"
|
|
594
|
+
: gatewayReachable
|
|
595
|
+
? "gateway"
|
|
596
|
+
: processes.length > 0
|
|
597
|
+
? "process"
|
|
598
|
+
: "not_running",
|
|
500
599
|
};
|
|
501
600
|
}
|
|
502
601
|
function detectOpenClawSkillInstallation() {
|
|
@@ -706,6 +805,10 @@ class LocalNodeService {
|
|
|
706
805
|
broadcastCount = 0;
|
|
707
806
|
lastMessageAt = 0;
|
|
708
807
|
lastBroadcastAt = 0;
|
|
808
|
+
lastProfileBroadcastAt = 0;
|
|
809
|
+
lastProfileBroadcastSignature = "";
|
|
810
|
+
lastReplayBroadcastAt = 0;
|
|
811
|
+
lastReplayBroadcastSignature = "";
|
|
709
812
|
lastBroadcastErrorAt = 0;
|
|
710
813
|
lastBroadcastError = null;
|
|
711
814
|
broadcastFailureCount = 0;
|
|
@@ -1955,14 +2058,13 @@ class LocalNodeService {
|
|
|
1955
2058
|
profile: this.profile,
|
|
1956
2059
|
};
|
|
1957
2060
|
const presenceRecord = (0, core_1.signPresence)(this.identity, Date.now());
|
|
1958
|
-
const
|
|
1959
|
-
const replayMessages = this.getReplayableSelfSocialMessages();
|
|
2061
|
+
const shouldPublishProfile = this.shouldPublishProfileRecord(profileRecord, reason, presenceRecord.timestamp);
|
|
2062
|
+
const replayMessages = this.getReplayableSelfSocialMessages(reason);
|
|
1960
2063
|
try {
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
for (const record of indexRecords) {
|
|
1964
|
-
await this.publish("index", record);
|
|
2064
|
+
if (shouldPublishProfile) {
|
|
2065
|
+
await this.publish("profile", profileRecord);
|
|
1965
2066
|
}
|
|
2067
|
+
await this.publish("presence", presenceRecord);
|
|
1966
2068
|
for (const message of replayMessages) {
|
|
1967
2069
|
await this.publish(SOCIAL_MESSAGE_TOPIC, message);
|
|
1968
2070
|
}
|
|
@@ -1984,14 +2086,27 @@ class LocalNodeService {
|
|
|
1984
2086
|
this.consecutiveBroadcastFailures = 0;
|
|
1985
2087
|
this.directory = (0, core_1.ingestProfileRecord)(this.directory, profileRecord);
|
|
1986
2088
|
this.directory = (0, core_1.ingestPresenceRecord)(this.directory, presenceRecord);
|
|
1987
|
-
for (const record of indexRecords) {
|
|
1988
|
-
this.directory = (0, core_1.ingestIndexRecord)(this.directory, record);
|
|
1989
|
-
}
|
|
1990
2089
|
this.compactCacheInMemory();
|
|
1991
2090
|
await this.persistCache();
|
|
1992
|
-
await this.log("info", `Broadcast sent (${
|
|
2091
|
+
await this.log("info", `Broadcast sent (${shouldPublishProfile ? "profile + " : ""}presence, replayed_messages=${replayMessages.length}, reason=${reason})`);
|
|
1993
2092
|
return { sent: true, reason };
|
|
1994
2093
|
}
|
|
2094
|
+
shouldPublishProfileRecord(profileRecord, reason, now = Date.now()) {
|
|
2095
|
+
if (reason !== "interval") {
|
|
2096
|
+
this.lastProfileBroadcastSignature = profileRecord.profile.signature;
|
|
2097
|
+
this.lastProfileBroadcastAt = now;
|
|
2098
|
+
return true;
|
|
2099
|
+
}
|
|
2100
|
+
const signature = profileRecord.profile.signature;
|
|
2101
|
+
const changedSinceLastPublish = signature !== this.lastProfileBroadcastSignature;
|
|
2102
|
+
const refreshDue = now - this.lastProfileBroadcastAt >= PROFILE_RELAY_REFRESH_INTERVAL_MS;
|
|
2103
|
+
if (!changedSinceLastPublish && !refreshDue) {
|
|
2104
|
+
return false;
|
|
2105
|
+
}
|
|
2106
|
+
this.lastProfileBroadcastSignature = signature;
|
|
2107
|
+
this.lastProfileBroadcastAt = now;
|
|
2108
|
+
return true;
|
|
2109
|
+
}
|
|
1995
2110
|
async maybeRecoverFromBroadcastFailure(reason, errorMessage) {
|
|
1996
2111
|
const recoveryThreshold = 3;
|
|
1997
2112
|
const recoveryCooldownMs = 60_000;
|
|
@@ -2776,16 +2891,30 @@ class LocalNodeService {
|
|
|
2776
2891
|
hasSocialMessage(messageId) {
|
|
2777
2892
|
return this.socialMessages.some((item) => item.message_id === messageId);
|
|
2778
2893
|
}
|
|
2779
|
-
getReplayableSelfSocialMessages(now = Date.now()) {
|
|
2894
|
+
getReplayableSelfSocialMessages(reason = "manual", now = Date.now()) {
|
|
2780
2895
|
const maxCount = Math.max(0, SOCIAL_MESSAGE_REPLAY_MAX_PER_BROADCAST);
|
|
2781
2896
|
if (!this.identity || maxCount === 0) {
|
|
2782
2897
|
return [];
|
|
2783
2898
|
}
|
|
2784
|
-
|
|
2899
|
+
const replayable = this.socialMessages
|
|
2785
2900
|
.filter((item) => (item.agent_id === this.identity?.agent_id &&
|
|
2786
2901
|
now - item.created_at <= SOCIAL_MESSAGE_REPLAY_WINDOW_MS))
|
|
2787
2902
|
.sort((a, b) => a.created_at - b.created_at)
|
|
2788
2903
|
.slice(-maxCount);
|
|
2904
|
+
if (!replayable.length) {
|
|
2905
|
+
this.lastReplayBroadcastSignature = "";
|
|
2906
|
+
return [];
|
|
2907
|
+
}
|
|
2908
|
+
const signature = replayable.map((item) => item.message_id).join(",");
|
|
2909
|
+
const isIntervalReplay = reason === "interval";
|
|
2910
|
+
const changedSinceLastReplay = signature !== this.lastReplayBroadcastSignature;
|
|
2911
|
+
const refreshDue = now - this.lastReplayBroadcastAt >= SOCIAL_MESSAGE_REPLAY_REFRESH_INTERVAL_MS;
|
|
2912
|
+
if (isIntervalReplay && !changedSinceLastReplay && !refreshDue) {
|
|
2913
|
+
return [];
|
|
2914
|
+
}
|
|
2915
|
+
this.lastReplayBroadcastSignature = signature;
|
|
2916
|
+
this.lastReplayBroadcastAt = now;
|
|
2917
|
+
return replayable;
|
|
2789
2918
|
}
|
|
2790
2919
|
hasRecentDuplicateMessage(agentId, body, topic, now = Date.now()) {
|
|
2791
2920
|
return this.socialMessages.some((item) => (item.agent_id === agentId &&
|
|
@@ -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
|
|
262
|
-
|
|
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
|
|
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
|
|
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()) {
|
|
@@ -72,6 +72,7 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
|
|
|
72
72
|
document.querySelector('.sidebar-version').title = t('common.version');
|
|
73
73
|
setText('.sidebar-version__label', t('common.version'));
|
|
74
74
|
document.getElementById('brandUpdateHint').textContent = t('labels.versionChecking');
|
|
75
|
+
document.getElementById('brandRelayHint').textContent = t('labels.relayQueuesHealthy');
|
|
75
76
|
document.getElementById('brandCheckUpdateBtn').textContent = t('actions.checkUpdate');
|
|
76
77
|
document.getElementById('brandUpdateBtn').textContent = t('actions.updateNow');
|
|
77
78
|
document.getElementById('integrationStatusBar').textContent = t('social.barStatus', {
|
|
@@ -325,6 +326,8 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
|
|
|
325
326
|
} = shell;
|
|
326
327
|
let appUpdatePollTimer = null;
|
|
327
328
|
let appUpdateCheckInFlight = false;
|
|
329
|
+
let relayQueueCheckInFlight = false;
|
|
330
|
+
let lastRelayQueueCheckAt = 0;
|
|
328
331
|
|
|
329
332
|
function setAppUpdateUi({
|
|
330
333
|
hint,
|
|
@@ -359,6 +362,45 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
|
|
|
359
362
|
return t('labels.versionPlatformOther');
|
|
360
363
|
}
|
|
361
364
|
|
|
365
|
+
function setRelayQueueUi({ hint = '', tone = 'ok', visible = false }) {
|
|
366
|
+
const hintEl = document.getElementById('brandRelayHint');
|
|
367
|
+
if (!hintEl) return;
|
|
368
|
+
hintEl.textContent = hint;
|
|
369
|
+
hintEl.classList.toggle('hidden', !visible || !hint);
|
|
370
|
+
hintEl.classList.remove('warn', 'danger');
|
|
371
|
+
if (tone === 'warn' || tone === 'danger') {
|
|
372
|
+
hintEl.classList.add(tone);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async function refreshRelayQueueStatus({ force = false } = {}) {
|
|
377
|
+
const now = Date.now();
|
|
378
|
+
if (relayQueueCheckInFlight) return null;
|
|
379
|
+
if (!force && now - lastRelayQueueCheckAt < 15_000) return null;
|
|
380
|
+
relayQueueCheckInFlight = true;
|
|
381
|
+
try {
|
|
382
|
+
const result = await api('/api/peers');
|
|
383
|
+
const peers = result.data || {};
|
|
384
|
+
const peerItems = Array.isArray(peers.items) ? peers.items : [];
|
|
385
|
+
const relayQueueMax = peerItems.reduce((max, peer) => Math.max(max, Number(peer?.meta?.relay_queue_size || 0)), 0);
|
|
386
|
+
const signalQueueMax = peerItems.reduce((max, peer) => Math.max(max, Number(peer?.meta?.signal_queue_size || 0)), 0);
|
|
387
|
+
const queueMax = Math.max(relayQueueMax, signalQueueMax);
|
|
388
|
+
if (queueMax >= 100) {
|
|
389
|
+
setRelayQueueUi({ hint: t('labels.relayQueuesHigh'), tone: 'danger', visible: true });
|
|
390
|
+
} else if (queueMax >= 20) {
|
|
391
|
+
setRelayQueueUi({ hint: t('labels.relayQueuesWatch'), tone: 'warn', visible: true });
|
|
392
|
+
} else {
|
|
393
|
+
setRelayQueueUi({ hint: t('labels.relayQueuesHealthy'), tone: 'ok', visible: true });
|
|
394
|
+
}
|
|
395
|
+
lastRelayQueueCheckAt = now;
|
|
396
|
+
return { relayQueueMax, signalQueueMax };
|
|
397
|
+
} catch (_error) {
|
|
398
|
+
return null;
|
|
399
|
+
} finally {
|
|
400
|
+
relayQueueCheckInFlight = false;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
362
404
|
async function refreshAppUpdateStatus({ silent = false } = {}) {
|
|
363
405
|
if (appUpdateCheckInFlight) return null;
|
|
364
406
|
appUpdateCheckInFlight = true;
|
|
@@ -646,7 +688,7 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
|
|
|
646
688
|
let autoRefreshInFlight = false;
|
|
647
689
|
|
|
648
690
|
async function refreshActiveView() {
|
|
649
|
-
const tasks = [refreshPublicProfilePreview()];
|
|
691
|
+
const tasks = [refreshPublicProfilePreview(), refreshRelayQueueStatus()];
|
|
650
692
|
if (activeTab === 'overview') {
|
|
651
693
|
tasks.push(refreshOverview(), refreshMessages(), refreshSocial());
|
|
652
694
|
} else if (activeTab === 'agent') {
|
|
@@ -682,7 +724,7 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
|
|
|
682
724
|
}
|
|
683
725
|
|
|
684
726
|
async function refreshAll() {
|
|
685
|
-
const tasks = [refreshOverview(), refreshNetwork(), refreshSocial(), refreshSkills(), refreshPublicProfilePreview(), refreshMessages()];
|
|
727
|
+
const tasks = [refreshOverview(), refreshNetwork(), refreshSocial(), refreshSkills(), refreshPublicProfilePreview(), refreshMessages(), refreshRelayQueueStatus({ force: true })];
|
|
686
728
|
if (activeTab === 'network') {
|
|
687
729
|
tasks.push(refreshPeers(), refreshDiscovery(), refreshLogs());
|
|
688
730
|
}
|
|
@@ -756,6 +798,7 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
|
|
|
756
798
|
if (!document.hidden) {
|
|
757
799
|
refreshAuto().catch(() => {});
|
|
758
800
|
refreshAppUpdateStatus({ silent: true }).catch(() => {});
|
|
801
|
+
refreshRelayQueueStatus({ force: true }).catch(() => {});
|
|
759
802
|
}
|
|
760
803
|
});
|
|
761
804
|
setInterval(refreshAuto, 4000);
|
|
@@ -17,6 +17,18 @@ export function createNetworkController({
|
|
|
17
17
|
let lastPeersRenderKey = "";
|
|
18
18
|
let lastDiscoveryRenderKey = "";
|
|
19
19
|
|
|
20
|
+
function queueState(value) {
|
|
21
|
+
const n = Number(value || 0);
|
|
22
|
+
if (n >= 100) return { tone: "danger", label: t("labels.queueHigh") };
|
|
23
|
+
if (n >= 20) return { tone: "warn", label: t("labels.queueWatch") };
|
|
24
|
+
return { tone: "ok", label: t("labels.queueHealthy") };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function queueBadge(value) {
|
|
28
|
+
const state = queueState(value);
|
|
29
|
+
return `<span class="pill ${state.tone}">${Number(value || 0)} · ${state.label}</span>`;
|
|
30
|
+
}
|
|
31
|
+
|
|
20
32
|
async function refreshNetwork() {
|
|
21
33
|
const [cfg, sts, rtp] = await Promise.all([api("/api/network/config"), api("/api/network/stats"), api("/api/runtime/paths")]);
|
|
22
34
|
const c = cfg.data;
|
|
@@ -183,6 +195,11 @@ export function createNetworkController({
|
|
|
183
195
|
const peers = peerRes.data || {};
|
|
184
196
|
const ds = statsRes.data?.adapter_discovery_stats || {};
|
|
185
197
|
const summary = peers.diagnostics_summary || {};
|
|
198
|
+
const peerItems = Array.isArray(peers.items) ? peers.items : [];
|
|
199
|
+
const relayQueueTotal = peerItems.reduce((sum, peer) => sum + Number(peer?.meta?.relay_queue_size || 0), 0);
|
|
200
|
+
const relayQueueMax = peerItems.reduce((max, peer) => Math.max(max, Number(peer?.meta?.relay_queue_size || 0)), 0);
|
|
201
|
+
const signalQueueTotal = peerItems.reduce((sum, peer) => sum + Number(peer?.meta?.signal_queue_size || 0), 0);
|
|
202
|
+
const signalQueueMax = peerItems.reduce((max, peer) => Math.max(max, Number(peer?.meta?.signal_queue_size || 0)), 0);
|
|
186
203
|
const peerCardsHtml = [
|
|
187
204
|
[t("network.total"), peers.total || 0],
|
|
188
205
|
[t("overview.online"), peers.online || 0],
|
|
@@ -198,6 +215,10 @@ export function createNetworkController({
|
|
|
198
215
|
[t("network.seedPeers"), summary.seed_peers_count ?? 0],
|
|
199
216
|
[t("network.discoveryEvents"), summary.discovery_events_total ?? 0],
|
|
200
217
|
[t("network.activeWebrtcPeers"), summary.active_webrtc_peers ?? "-"],
|
|
218
|
+
["Relay queue", queueBadge(relayQueueTotal)],
|
|
219
|
+
["Max relay queue", queueBadge(relayQueueMax)],
|
|
220
|
+
["Signal queue", queueBadge(signalQueueTotal)],
|
|
221
|
+
["Max signal queue", queueBadge(signalQueueMax)],
|
|
201
222
|
[t("network.observeCalls"), ds.observe_calls || 0],
|
|
202
223
|
[t("network.heartbeats"), ds.heartbeat_sent || 0],
|
|
203
224
|
[t("network.peersAdded"), ds.peers_added || 0],
|
|
@@ -213,9 +234,9 @@ export function createNetworkController({
|
|
|
213
234
|
? `<div class="empty-state">${t("network.noPeersDiscovered")}</div>`
|
|
214
235
|
: `
|
|
215
236
|
<table class="table">
|
|
216
|
-
<thead><tr><th>${t("network.peer")}</th><th>${t("network.status")}</th><th>${t("network.lastSeen")}</th><th>${t("network.staleSince")}</th><th>${t("network.messages")}</th><th>${t("network.firstSeen")}</th><th>${t("network.meta")}</th></tr></thead>
|
|
237
|
+
<thead><tr><th>${t("network.peer")}</th><th>${t("network.status")}</th><th>${t("network.lastSeen")}</th><th>${t("network.staleSince")}</th><th>${t("network.messages")}</th><th>${t("network.firstSeen")}</th><th>Relay Q</th><th>Signal Q</th><th>${t("network.meta")}</th></tr></thead>
|
|
217
238
|
<tbody>
|
|
218
|
-
${
|
|
239
|
+
${peerItems.map((peer) => `
|
|
219
240
|
<tr>
|
|
220
241
|
<td class="mono">${shortId(peer.peer_id)}</td>
|
|
221
242
|
<td class="${peer.status === "online" ? "online" : peer.status === "offline" ? "offline" : "stale"}">${peerStatusText(peer.status)}</td>
|
|
@@ -223,6 +244,8 @@ export function createNetworkController({
|
|
|
223
244
|
<td>${peer.stale_since_at ? ago(peer.stale_since_at) : "-"}</td>
|
|
224
245
|
<td>${peer.messages_seen || 0}</td>
|
|
225
246
|
<td>${new Date(peer.first_seen_at).toLocaleTimeString()}</td>
|
|
247
|
+
<td>${queueBadge(Number(peer?.meta?.relay_queue_size || 0))}</td>
|
|
248
|
+
<td>${queueBadge(Number(peer?.meta?.signal_queue_size || 0))}</td>
|
|
226
249
|
<td class="mono">${peer.meta ? JSON.stringify(peer.meta) : "-"}</td>
|
|
227
250
|
</tr>
|
|
228
251
|
`).join("")}
|
|
@@ -248,14 +271,22 @@ export function createNetworkController({
|
|
|
248
271
|
peers_added: ds.peers_added || 0,
|
|
249
272
|
peers_removed: ds.peers_removed || 0,
|
|
250
273
|
},
|
|
251
|
-
|
|
252
|
-
|
|
274
|
+
queues: {
|
|
275
|
+
relay_total: relayQueueTotal,
|
|
276
|
+
relay_max: relayQueueMax,
|
|
277
|
+
signal_total: signalQueueTotal,
|
|
278
|
+
signal_max: signalQueueMax,
|
|
279
|
+
},
|
|
280
|
+
items: peerItems
|
|
281
|
+
? peerItems.map((peer) => [
|
|
253
282
|
peer.peer_id,
|
|
254
283
|
peer.status || "",
|
|
255
284
|
peer.last_seen_at || 0,
|
|
256
285
|
peer.stale_since_at || 0,
|
|
257
286
|
peer.messages_seen || 0,
|
|
258
287
|
peer.first_seen_at || 0,
|
|
288
|
+
Number(peer?.meta?.relay_queue_size || 0),
|
|
289
|
+
Number(peer?.meta?.signal_queue_size || 0),
|
|
259
290
|
peer.meta ? JSON.stringify(peer.meta) : "",
|
|
260
291
|
])
|
|
261
292
|
: [],
|
|
@@ -446,6 +446,7 @@ export function createSocialController({
|
|
|
446
446
|
[t("social.ownerForwardReady"), ownerDelivery.ready ? t("common.yes") : t("common.no")],
|
|
447
447
|
[t("social.ownerForwardCommand"), ownerDelivery.forward_command_configured ? t("common.yes") : t("common.no")],
|
|
448
448
|
[t("social.openclawDetectionMode"), bridge.openclaw_runtime?.detection_mode || "-"],
|
|
449
|
+
["Gateway probe", bridge.openclaw_runtime?.gateway_probe_ok ? t("common.yes") : t("common.no")],
|
|
449
450
|
[t("social.openclawGateway"), bridge.openclaw_runtime?.gateway_url || "-"],
|
|
450
451
|
[t("social.installMode"), skillLearning.install_mode || "-"],
|
|
451
452
|
[t("social.installedPath"), skillInstalled ? installedSkillPath : "-"],
|