@silicaclaw/cli 2026.3.20-5 → 2026.3.20-7
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 +12 -2
- package/apps/local-console/dist/apps/local-console/src/server.js +147 -13
- package/apps/local-console/public/app/social.js +1 -0
- package/apps/local-console/public/app/translations.js +6 -6
- package/apps/local-console/src/server.ts +167 -8
- 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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## v1.0 beta - 2026-03-20
|
|
4
4
|
|
|
5
|
+
### 2026.3.20-7
|
|
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-6
|
|
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-5
|
|
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-7
|
|
@@ -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;
|
|
@@ -204,11 +210,15 @@ export declare class LocalNodeService {
|
|
|
204
210
|
private networkReconnectTimer;
|
|
205
211
|
private networkReconnectDelayMs;
|
|
206
212
|
private appVersion;
|
|
213
|
+
private openclawRuntimeCache;
|
|
214
|
+
private openclawBridgeStatusCache;
|
|
207
215
|
constructor(options?: {
|
|
208
216
|
workspaceRoot?: string;
|
|
209
217
|
projectRoot?: string;
|
|
210
218
|
storageRoot?: string;
|
|
211
219
|
});
|
|
220
|
+
private getCachedOpenClawRuntime;
|
|
221
|
+
private invalidateOpenClawCaches;
|
|
212
222
|
start(): Promise<void>;
|
|
213
223
|
stop(): Promise<void>;
|
|
214
224
|
private ensureLocalDirectoryBaseline;
|
|
@@ -636,7 +646,7 @@ export declare class LocalNodeService {
|
|
|
636
646
|
openclaw: {
|
|
637
647
|
detected: boolean;
|
|
638
648
|
running: boolean;
|
|
639
|
-
detection_mode: "process+gateway" | "gateway" | "process" | "not_running";
|
|
649
|
+
detection_mode: "gateway" | "gateway-probe+process+gateway" | "gateway-probe+gateway" | "gateway-probe+process" | "gateway-probe" | "process+gateway" | "process" | "not_running";
|
|
640
650
|
gateway_url: string;
|
|
641
651
|
workspace_install_root: string;
|
|
642
652
|
legacy_install_root: string;
|
|
@@ -36,6 +36,8 @@ const DEFAULT_GLOBAL_ROOM = silicaclaw_defaults_json_1.default.network.global_pr
|
|
|
36
36
|
const DEFAULT_BRIDGE_API_BASE = silicaclaw_defaults_json_1.default.bridge.api_base;
|
|
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
|
+
const OPENCLAW_RUNTIME_CACHE_MS = 15_000;
|
|
40
|
+
const OPENCLAW_BRIDGE_STATUS_CACHE_MS = 5_000;
|
|
39
41
|
const NETWORK_PEER_REMOVE_AFTER_MS = Number(process.env.NETWORK_PEER_REMOVE_AFTER_MS || 180_000);
|
|
40
42
|
const DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT = Number(process.env.DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT || 1000);
|
|
41
43
|
const NETWORK_UDP_BIND_ADDRESS = process.env.NETWORK_UDP_BIND_ADDRESS || "0.0.0.0";
|
|
@@ -142,6 +144,9 @@ function compareVersionTokens(left, right) {
|
|
|
142
144
|
function userNpmCacheDir() {
|
|
143
145
|
return (0, path_1.resolve)((0, os_1.homedir)(), ".silicaclaw", "npm-cache");
|
|
144
146
|
}
|
|
147
|
+
function userShimPath() {
|
|
148
|
+
return (0, path_1.resolve)((0, os_1.homedir)(), ".silicaclaw", "bin", "silicaclaw");
|
|
149
|
+
}
|
|
145
150
|
function resolveWorkspaceRoot(cwd = process.cwd()) {
|
|
146
151
|
if ((0, fs_1.existsSync)((0, path_1.resolve)(cwd, "apps", "local-console", "package.json"))) {
|
|
147
152
|
return cwd;
|
|
@@ -389,8 +394,71 @@ function readOpenClawConfiguredGateway(workspaceRoot) {
|
|
|
389
394
|
gateway_url: OPENCLAW_GATEWAY_URL,
|
|
390
395
|
};
|
|
391
396
|
}
|
|
397
|
+
function resolveOpenClawStatusCommand(workspaceRoot) {
|
|
398
|
+
const explicitBin = String(process.env.OPENCLAW_BIN || "").trim();
|
|
399
|
+
if (explicitBin) {
|
|
400
|
+
return { cmd: explicitBin, args: ["status"] };
|
|
401
|
+
}
|
|
402
|
+
const configuredSourceDir = String(process.env.OPENCLAW_SOURCE_DIR || "").trim();
|
|
403
|
+
const defaultSourceDir = defaultOpenClawSourceDir(workspaceRoot);
|
|
404
|
+
const sourceDir = configuredSourceDir || defaultSourceDir;
|
|
405
|
+
const sourceEntry = existingPathOrNull((0, path_1.resolve)(sourceDir, "openclaw.mjs"));
|
|
406
|
+
if (sourceEntry) {
|
|
407
|
+
return { cmd: process.execPath, args: [sourceEntry, "status"] };
|
|
408
|
+
}
|
|
409
|
+
const commandPath = resolveExecutableInPath("openclaw");
|
|
410
|
+
if (commandPath) {
|
|
411
|
+
return { cmd: commandPath, args: ["status"] };
|
|
412
|
+
}
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
function resolveOpenClawGatewayProbeCommand(workspaceRoot) {
|
|
416
|
+
const explicitBin = String(process.env.OPENCLAW_BIN || "").trim();
|
|
417
|
+
if (explicitBin) {
|
|
418
|
+
return { cmd: explicitBin, args: ["gateway", "probe"] };
|
|
419
|
+
}
|
|
420
|
+
const configuredSourceDir = String(process.env.OPENCLAW_SOURCE_DIR || "").trim();
|
|
421
|
+
const defaultSourceDir = defaultOpenClawSourceDir(workspaceRoot);
|
|
422
|
+
const sourceDir = configuredSourceDir || defaultSourceDir;
|
|
423
|
+
const sourceEntry = existingPathOrNull((0, path_1.resolve)(sourceDir, "openclaw.mjs"));
|
|
424
|
+
if (sourceEntry) {
|
|
425
|
+
return { cmd: process.execPath, args: [sourceEntry, "gateway", "probe"] };
|
|
426
|
+
}
|
|
427
|
+
const commandPath = resolveExecutableInPath("openclaw");
|
|
428
|
+
if (commandPath) {
|
|
429
|
+
return { cmd: commandPath, args: ["gateway", "probe"] };
|
|
430
|
+
}
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
392
433
|
function detectOpenClawRuntime(workspaceRoot) {
|
|
393
434
|
const configuredGateway = readOpenClawConfiguredGateway(workspaceRoot);
|
|
435
|
+
const statusCommand = resolveOpenClawStatusCommand(workspaceRoot);
|
|
436
|
+
const gatewayProbeCommand = resolveOpenClawGatewayProbeCommand(workspaceRoot);
|
|
437
|
+
const statusProbe = statusCommand
|
|
438
|
+
? (0, child_process_1.spawnSync)(statusCommand.cmd, statusCommand.args, {
|
|
439
|
+
encoding: "utf8",
|
|
440
|
+
env: process.env,
|
|
441
|
+
})
|
|
442
|
+
: null;
|
|
443
|
+
const statusStdout = String(statusProbe?.stdout || "");
|
|
444
|
+
const statusStderr = String(statusProbe?.stderr || "");
|
|
445
|
+
const statusLooksConfigured = Boolean(statusCommand &&
|
|
446
|
+
statusProbe &&
|
|
447
|
+
statusProbe.status === 0 &&
|
|
448
|
+
(/\bChannels\b/i.test(statusStdout) ||
|
|
449
|
+
/\bSessions\b/i.test(statusStdout) ||
|
|
450
|
+
/\bNext steps:\b/i.test(statusStdout)));
|
|
451
|
+
const gatewayStatusProbe = gatewayProbeCommand
|
|
452
|
+
? (0, child_process_1.spawnSync)(gatewayProbeCommand.cmd, gatewayProbeCommand.args, {
|
|
453
|
+
encoding: "utf8",
|
|
454
|
+
env: process.env,
|
|
455
|
+
})
|
|
456
|
+
: null;
|
|
457
|
+
const gatewayStatusStdout = String(gatewayStatusProbe?.stdout || "");
|
|
458
|
+
const gatewayStatusStderr = String(gatewayStatusProbe?.stderr || "");
|
|
459
|
+
const gatewayProbeOk = Boolean(gatewayProbeCommand &&
|
|
460
|
+
gatewayStatusProbe &&
|
|
461
|
+
gatewayStatusProbe.status === 0);
|
|
394
462
|
const result = (0, child_process_1.spawnSync)("ps", ["-Ao", "pid=,ppid=,command="], {
|
|
395
463
|
encoding: "utf8",
|
|
396
464
|
});
|
|
@@ -473,6 +541,12 @@ function detectOpenClawRuntime(workspaceRoot) {
|
|
|
473
541
|
const allProcesses = Array.from(combinedProcesses.values());
|
|
474
542
|
const gatewayReachable = gatewayListeners.length > 0;
|
|
475
543
|
const detectionNotes = [];
|
|
544
|
+
if (statusProbe && statusProbe.status !== 0) {
|
|
545
|
+
detectionNotes.push(String(statusStderr || "openclaw status failed").trim());
|
|
546
|
+
}
|
|
547
|
+
if (gatewayStatusProbe && gatewayStatusProbe.status !== 0) {
|
|
548
|
+
detectionNotes.push(String(gatewayStatusStderr || "openclaw gateway probe failed").trim());
|
|
549
|
+
}
|
|
476
550
|
if (result.status !== 0)
|
|
477
551
|
detectionNotes.push(String(result.stderr || "ps failed").trim());
|
|
478
552
|
if (gatewayProbe.status !== 0 && gatewayLines.length === 0) {
|
|
@@ -481,24 +555,52 @@ function detectOpenClawRuntime(workspaceRoot) {
|
|
|
481
555
|
const gatewayPort = preferredListener?.port || configuredGateway.gateway_port;
|
|
482
556
|
const gatewayUrl = `http://${OPENCLAW_GATEWAY_HOST}:${gatewayPort}/`;
|
|
483
557
|
return {
|
|
484
|
-
running: allProcesses.length > 0 || gatewayReachable,
|
|
558
|
+
running: gatewayProbeOk || allProcesses.length > 0 || gatewayReachable,
|
|
485
559
|
process_count: allProcesses.length,
|
|
486
560
|
processes: allProcesses.slice(0, 10),
|
|
487
561
|
detection_error: detectionNotes.filter(Boolean).join(" | ") || null,
|
|
488
562
|
gateway_url: gatewayUrl,
|
|
489
563
|
gateway_port: gatewayPort,
|
|
490
564
|
gateway_reachable: gatewayReachable,
|
|
565
|
+
status_command: statusCommand ? [statusCommand.cmd, ...statusCommand.args].join(" ") : null,
|
|
566
|
+
status_ok: statusLooksConfigured,
|
|
567
|
+
status_summary: statusLooksConfigured
|
|
568
|
+
? statusStdout
|
|
569
|
+
.split("\n")
|
|
570
|
+
.map((line) => line.trim())
|
|
571
|
+
.filter(Boolean)
|
|
572
|
+
.slice(0, 6)
|
|
573
|
+
.join(" | ")
|
|
574
|
+
: null,
|
|
575
|
+
gateway_probe_command: gatewayProbeCommand ? [gatewayProbeCommand.cmd, ...gatewayProbeCommand.args].join(" ") : null,
|
|
576
|
+
gateway_probe_ok: gatewayProbeOk,
|
|
577
|
+
gateway_probe_summary: gatewayProbeOk
|
|
578
|
+
? gatewayStatusStdout
|
|
579
|
+
.split("\n")
|
|
580
|
+
.map((line) => line.trim())
|
|
581
|
+
.filter(Boolean)
|
|
582
|
+
.slice(0, 4)
|
|
583
|
+
.join(" | ")
|
|
584
|
+
: null,
|
|
491
585
|
configured_gateway_url: configuredGateway.gateway_url,
|
|
492
586
|
configured_gateway_port: configuredGateway.gateway_port,
|
|
493
587
|
configured_gateway_bind: configuredGateway.gateway_bind,
|
|
494
588
|
configured_gateway_config_path: configuredGateway.config_path,
|
|
495
|
-
detection_mode:
|
|
496
|
-
?
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
589
|
+
detection_mode: gatewayProbeOk
|
|
590
|
+
? (processes.length > 0 && gatewayReachable
|
|
591
|
+
? "gateway-probe+process+gateway"
|
|
592
|
+
: gatewayReachable
|
|
593
|
+
? "gateway-probe+gateway"
|
|
594
|
+
: processes.length > 0
|
|
595
|
+
? "gateway-probe+process"
|
|
596
|
+
: "gateway-probe")
|
|
597
|
+
: processes.length > 0 && gatewayReachable
|
|
598
|
+
? "process+gateway"
|
|
599
|
+
: gatewayReachable
|
|
600
|
+
? "gateway"
|
|
601
|
+
: processes.length > 0
|
|
602
|
+
? "process"
|
|
603
|
+
: "not_running",
|
|
502
604
|
};
|
|
503
605
|
}
|
|
504
606
|
function detectOpenClawSkillInstallation() {
|
|
@@ -755,6 +857,8 @@ class LocalNodeService {
|
|
|
755
857
|
networkReconnectTimer = null;
|
|
756
858
|
networkReconnectDelayMs = 5_000;
|
|
757
859
|
appVersion = "unknown";
|
|
860
|
+
openclawRuntimeCache = null;
|
|
861
|
+
openclawBridgeStatusCache = null;
|
|
758
862
|
constructor(options) {
|
|
759
863
|
this.workspaceRoot = options?.workspaceRoot || resolveWorkspaceRoot();
|
|
760
864
|
this.projectRoot = options?.projectRoot || resolveProjectRoot(this.workspaceRoot);
|
|
@@ -795,6 +899,22 @@ class LocalNodeService {
|
|
|
795
899
|
this.adapterMode = resolved.mode;
|
|
796
900
|
this.networkPort = resolved.port;
|
|
797
901
|
}
|
|
902
|
+
getCachedOpenClawRuntime() {
|
|
903
|
+
const now = Date.now();
|
|
904
|
+
if (this.openclawRuntimeCache && this.openclawRuntimeCache.expiresAt > now) {
|
|
905
|
+
return this.openclawRuntimeCache.value;
|
|
906
|
+
}
|
|
907
|
+
const value = detectOpenClawRuntime(this.projectRoot);
|
|
908
|
+
this.openclawRuntimeCache = {
|
|
909
|
+
value,
|
|
910
|
+
expiresAt: now + OPENCLAW_RUNTIME_CACHE_MS,
|
|
911
|
+
};
|
|
912
|
+
return value;
|
|
913
|
+
}
|
|
914
|
+
invalidateOpenClawCaches() {
|
|
915
|
+
this.openclawRuntimeCache = null;
|
|
916
|
+
this.openclawBridgeStatusCache = null;
|
|
917
|
+
}
|
|
798
918
|
async start() {
|
|
799
919
|
await this.hydrateFromDisk();
|
|
800
920
|
this.bindNetworkSubscriptions();
|
|
@@ -1187,8 +1307,10 @@ class LocalNodeService {
|
|
|
1187
1307
|
reason: status.check_error || "already_current",
|
|
1188
1308
|
};
|
|
1189
1309
|
}
|
|
1310
|
+
const shimPath = userShimPath();
|
|
1190
1311
|
const scriptPath = (0, path_1.resolve)(this.workspaceRoot, "scripts", "silicaclaw-cli.mjs");
|
|
1191
|
-
|
|
1312
|
+
const useShim = (0, fs_1.existsSync)(shimPath);
|
|
1313
|
+
if (!useShim && !(0, fs_1.existsSync)(scriptPath)) {
|
|
1192
1314
|
return {
|
|
1193
1315
|
started: false,
|
|
1194
1316
|
target_version: status.latest_version,
|
|
@@ -1196,7 +1318,9 @@ class LocalNodeService {
|
|
|
1196
1318
|
reason: "missing_cli_script",
|
|
1197
1319
|
};
|
|
1198
1320
|
}
|
|
1199
|
-
const
|
|
1321
|
+
const command = useShim ? shimPath : process.execPath;
|
|
1322
|
+
const args = useShim ? ["update"] : [scriptPath, "update"];
|
|
1323
|
+
const child = (0, child_process_1.spawn)(command, args, {
|
|
1200
1324
|
cwd: this.projectRoot,
|
|
1201
1325
|
detached: true,
|
|
1202
1326
|
stdio: "ignore",
|
|
@@ -1490,9 +1614,13 @@ class LocalNodeService {
|
|
|
1490
1614
|
};
|
|
1491
1615
|
}
|
|
1492
1616
|
getOpenClawBridgeStatus() {
|
|
1617
|
+
const now = Date.now();
|
|
1618
|
+
if (this.openclawBridgeStatusCache && this.openclawBridgeStatusCache.expiresAt > now) {
|
|
1619
|
+
return this.openclawBridgeStatusCache.value;
|
|
1620
|
+
}
|
|
1493
1621
|
const integration = this.getIntegrationStatus();
|
|
1494
1622
|
const openclawInstallation = detectOpenClawInstallation(this.projectRoot);
|
|
1495
|
-
const openclawRuntime =
|
|
1623
|
+
const openclawRuntime = this.getCachedOpenClawRuntime();
|
|
1496
1624
|
const skillInstallation = detectOpenClawSkillInstallation();
|
|
1497
1625
|
const ownerDelivery = detectOwnerDeliveryStatus({
|
|
1498
1626
|
workspaceRoot: this.projectRoot,
|
|
@@ -1500,7 +1628,7 @@ class LocalNodeService {
|
|
|
1500
1628
|
openclawRunning: openclawRuntime.running,
|
|
1501
1629
|
skillInstalled: skillInstallation.installed,
|
|
1502
1630
|
});
|
|
1503
|
-
|
|
1631
|
+
const value = {
|
|
1504
1632
|
enabled: this.socialConfig.enabled,
|
|
1505
1633
|
connected_to_silicaclaw: integration.connected_to_silicaclaw,
|
|
1506
1634
|
public_enabled: integration.public_enabled,
|
|
@@ -1556,6 +1684,11 @@ class LocalNodeService {
|
|
|
1556
1684
|
install_skill: "/api/openclaw/bridge/skill-install",
|
|
1557
1685
|
},
|
|
1558
1686
|
};
|
|
1687
|
+
this.openclawBridgeStatusCache = {
|
|
1688
|
+
value,
|
|
1689
|
+
expiresAt: now + OPENCLAW_BRIDGE_STATUS_CACHE_MS,
|
|
1690
|
+
};
|
|
1691
|
+
return value;
|
|
1559
1692
|
}
|
|
1560
1693
|
async installOpenClawSkill(skillName) {
|
|
1561
1694
|
const scriptPath = (0, path_1.resolve)(this.workspaceRoot, "scripts", "install-openclaw-skill.mjs");
|
|
@@ -1569,6 +1702,7 @@ class LocalNodeService {
|
|
|
1569
1702
|
maxBuffer: 1024 * 1024,
|
|
1570
1703
|
});
|
|
1571
1704
|
const parsed = JSON.parse(String(stdout || "{}"));
|
|
1705
|
+
this.invalidateOpenClawCaches();
|
|
1572
1706
|
return {
|
|
1573
1707
|
...parsed,
|
|
1574
1708
|
bridge: this.getOpenClawBridgeStatus(),
|
|
@@ -1588,7 +1722,7 @@ class LocalNodeService {
|
|
|
1588
1722
|
const workspaceSkillDir = (0, path_1.resolve)(homeDir, "workspace", "skills");
|
|
1589
1723
|
const legacySkillDir = (0, path_1.resolve)(homeDir, "skills");
|
|
1590
1724
|
const openclawSourceDir = defaultOpenClawSourceDir(this.projectRoot);
|
|
1591
|
-
const openclawRuntime =
|
|
1725
|
+
const openclawRuntime = this.getCachedOpenClawRuntime();
|
|
1592
1726
|
return {
|
|
1593
1727
|
bridge_api_base: DEFAULT_BRIDGE_API_BASE,
|
|
1594
1728
|
openclaw_detected: detectOpenClawInstallation(this.projectRoot).detected,
|
|
@@ -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 : "-"],
|
|
@@ -86,7 +86,7 @@ export const TRANSLATIONS = {
|
|
|
86
86
|
showLess: 'Show Less',
|
|
87
87
|
showMoreCount: 'Show {count} More',
|
|
88
88
|
openclawNotInstalled: 'OpenClaw Not Installed Here',
|
|
89
|
-
openclawNotRunning: 'OpenClaw Not Running',
|
|
89
|
+
openclawNotRunning: 'OpenClaw Gateway Not Running',
|
|
90
90
|
openclawSkillLearned: 'Skill Already Learned',
|
|
91
91
|
silicaClawSkillsInstalled: 'Skills Already Installed',
|
|
92
92
|
},
|
|
@@ -591,7 +591,7 @@ export const TRANSLATIONS = {
|
|
|
591
591
|
openclawSkillNotInstalled: 'The SilicaClaw broadcast skill is not installed yet.',
|
|
592
592
|
openclawSkillInstallFailed: 'OpenClaw skill installation failed',
|
|
593
593
|
openclawRoleBroadcasterOnly: 'This computer is broadcaster-only. Install OpenClaw here to learn broadcasts.',
|
|
594
|
-
openclawRoleNotRunning: 'OpenClaw is
|
|
594
|
+
openclawRoleNotRunning: 'OpenClaw is configured here, but the gateway is not running yet.',
|
|
595
595
|
openclawRoleReadyToLearn: 'OpenClaw is installed here. Learn the broadcast skill to keep going.',
|
|
596
596
|
openclawRoleLearned: 'This computer is ready to learn broadcasts and forward useful updates.',
|
|
597
597
|
openclawRoleLearningOnly: 'Broadcast learning works, but owner forwarding is not ready yet.',
|
|
@@ -694,7 +694,7 @@ export const TRANSLATIONS = {
|
|
|
694
694
|
showLess: '收起',
|
|
695
695
|
showMoreCount: '再显示 {count} 个',
|
|
696
696
|
openclawNotInstalled: '本机未安装 OpenClaw',
|
|
697
|
-
openclawNotRunning: 'OpenClaw
|
|
697
|
+
openclawNotRunning: 'OpenClaw 网关未启动',
|
|
698
698
|
openclawSkillLearned: '技能已学会',
|
|
699
699
|
silicaClawSkillsInstalled: '技能已全部安装',
|
|
700
700
|
},
|
|
@@ -1020,7 +1020,7 @@ export const TRANSLATIONS = {
|
|
|
1020
1020
|
homeDegraded: '降级',
|
|
1021
1021
|
homeRunning: '运行中',
|
|
1022
1022
|
homeStopped: '未启动',
|
|
1023
|
-
homeInstalledOnly: '
|
|
1023
|
+
homeInstalledOnly: '已检测到 OpenClaw 配置',
|
|
1024
1024
|
homeGlobalReady: '全网预览已启用',
|
|
1025
1025
|
homeNotGlobal: '当前不是全网预览',
|
|
1026
1026
|
homeBriefNetwork: '网络',
|
|
@@ -1032,7 +1032,7 @@ export const TRANSLATIONS = {
|
|
|
1032
1032
|
homeBriefActionBroadcast: '保持公开广播,让运行 OpenClaw 的机器可以学习这个代理。',
|
|
1033
1033
|
homeBriefActionStabilize: '先修复 relay 或广播健康度,再依赖主人转发链路。',
|
|
1034
1034
|
homeMetaRunning: '已经检测到本机 OpenClaw 进程。',
|
|
1035
|
-
homeMetaNotRunning: '要开始学习广播,前提是本机 OpenClaw
|
|
1035
|
+
homeMetaNotRunning: '要开始学习广播,前提是本机 OpenClaw 网关正在运行。',
|
|
1036
1036
|
homeMetaGlobal: '当前默认使用全网 relay 预览模式。',
|
|
1037
1037
|
homeMetaNotGlobal: '这台机器当前还没有走全网 relay 路径。',
|
|
1038
1038
|
homeMetaPeers: '从本机视角看到 {online} 个在线代理,累计发现 {discovered} 个代理。',
|
|
@@ -1199,7 +1199,7 @@ export const TRANSLATIONS = {
|
|
|
1199
1199
|
openclawSkillNotInstalled: 'SilicaClaw 广播技能还没有安装。',
|
|
1200
1200
|
openclawSkillInstallFailed: 'OpenClaw 技能安装失败',
|
|
1201
1201
|
openclawRoleBroadcasterOnly: '这台电脑当前只能广播。想学习广播,先在这里安装 OpenClaw。',
|
|
1202
|
-
openclawRoleNotRunning: '
|
|
1202
|
+
openclawRoleNotRunning: '这台电脑已经配置了 OpenClaw,但网关还没启动。',
|
|
1203
1203
|
openclawRoleReadyToLearn: '这台电脑已经装了 OpenClaw。先学习广播技能,再继续。',
|
|
1204
1204
|
openclawRoleLearned: '这台电脑已经可以学习广播,并转发有用更新。',
|
|
1205
1205
|
openclawRoleLearningOnly: '已经能学习广播,但主人推送还没准备好。',
|
|
@@ -88,6 +88,8 @@ const DEFAULT_GLOBAL_ROOM = defaults.network.global_preview.room;
|
|
|
88
88
|
const DEFAULT_BRIDGE_API_BASE = defaults.bridge.api_base;
|
|
89
89
|
const OPENCLAW_GATEWAY_PORT = defaults.ports.openclaw_gateway;
|
|
90
90
|
const OPENCLAW_GATEWAY_URL = `http://${OPENCLAW_GATEWAY_HOST}:${OPENCLAW_GATEWAY_PORT}/`;
|
|
91
|
+
const OPENCLAW_RUNTIME_CACHE_MS = 15_000;
|
|
92
|
+
const OPENCLAW_BRIDGE_STATUS_CACHE_MS = 5_000;
|
|
91
93
|
const NETWORK_PEER_REMOVE_AFTER_MS = Number(process.env.NETWORK_PEER_REMOVE_AFTER_MS || 180_000);
|
|
92
94
|
const DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT = Number(process.env.DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT || 1000);
|
|
93
95
|
const NETWORK_UDP_BIND_ADDRESS = process.env.NETWORK_UDP_BIND_ADDRESS || "0.0.0.0";
|
|
@@ -200,6 +202,10 @@ function userNpmCacheDir(): string {
|
|
|
200
202
|
return resolve(homedir(), ".silicaclaw", "npm-cache");
|
|
201
203
|
}
|
|
202
204
|
|
|
205
|
+
function userShimPath(): string {
|
|
206
|
+
return resolve(homedir(), ".silicaclaw", "bin", "silicaclaw");
|
|
207
|
+
}
|
|
208
|
+
|
|
203
209
|
function resolveWorkspaceRoot(cwd = process.cwd()): string {
|
|
204
210
|
if (existsSync(resolve(cwd, "apps", "local-console", "package.json"))) {
|
|
205
211
|
return cwd;
|
|
@@ -455,8 +461,85 @@ function readOpenClawConfiguredGateway(workspaceRoot: string) {
|
|
|
455
461
|
} as const;
|
|
456
462
|
}
|
|
457
463
|
|
|
464
|
+
function resolveOpenClawStatusCommand(workspaceRoot: string) {
|
|
465
|
+
const explicitBin = String(process.env.OPENCLAW_BIN || "").trim();
|
|
466
|
+
if (explicitBin) {
|
|
467
|
+
return { cmd: explicitBin, args: ["status"] } as const;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const configuredSourceDir = String(process.env.OPENCLAW_SOURCE_DIR || "").trim();
|
|
471
|
+
const defaultSourceDir = defaultOpenClawSourceDir(workspaceRoot);
|
|
472
|
+
const sourceDir = configuredSourceDir || defaultSourceDir;
|
|
473
|
+
const sourceEntry = existingPathOrNull(resolve(sourceDir, "openclaw.mjs"));
|
|
474
|
+
if (sourceEntry) {
|
|
475
|
+
return { cmd: process.execPath, args: [sourceEntry, "status"] } as const;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const commandPath = resolveExecutableInPath("openclaw");
|
|
479
|
+
if (commandPath) {
|
|
480
|
+
return { cmd: commandPath, args: ["status"] } as const;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function resolveOpenClawGatewayProbeCommand(workspaceRoot: string) {
|
|
487
|
+
const explicitBin = String(process.env.OPENCLAW_BIN || "").trim();
|
|
488
|
+
if (explicitBin) {
|
|
489
|
+
return { cmd: explicitBin, args: ["gateway", "probe"] } as const;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const configuredSourceDir = String(process.env.OPENCLAW_SOURCE_DIR || "").trim();
|
|
493
|
+
const defaultSourceDir = defaultOpenClawSourceDir(workspaceRoot);
|
|
494
|
+
const sourceDir = configuredSourceDir || defaultSourceDir;
|
|
495
|
+
const sourceEntry = existingPathOrNull(resolve(sourceDir, "openclaw.mjs"));
|
|
496
|
+
if (sourceEntry) {
|
|
497
|
+
return { cmd: process.execPath, args: [sourceEntry, "gateway", "probe"] } as const;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const commandPath = resolveExecutableInPath("openclaw");
|
|
501
|
+
if (commandPath) {
|
|
502
|
+
return { cmd: commandPath, args: ["gateway", "probe"] } as const;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
|
|
458
508
|
function detectOpenClawRuntime(workspaceRoot: string) {
|
|
459
509
|
const configuredGateway = readOpenClawConfiguredGateway(workspaceRoot);
|
|
510
|
+
const statusCommand = resolveOpenClawStatusCommand(workspaceRoot);
|
|
511
|
+
const gatewayProbeCommand = resolveOpenClawGatewayProbeCommand(workspaceRoot);
|
|
512
|
+
const statusProbe = statusCommand
|
|
513
|
+
? spawnSync(statusCommand.cmd, statusCommand.args, {
|
|
514
|
+
encoding: "utf8",
|
|
515
|
+
env: process.env,
|
|
516
|
+
})
|
|
517
|
+
: null;
|
|
518
|
+
const statusStdout = String(statusProbe?.stdout || "");
|
|
519
|
+
const statusStderr = String(statusProbe?.stderr || "");
|
|
520
|
+
const statusLooksConfigured = Boolean(
|
|
521
|
+
statusCommand &&
|
|
522
|
+
statusProbe &&
|
|
523
|
+
statusProbe.status === 0 &&
|
|
524
|
+
(
|
|
525
|
+
/\bChannels\b/i.test(statusStdout) ||
|
|
526
|
+
/\bSessions\b/i.test(statusStdout) ||
|
|
527
|
+
/\bNext steps:\b/i.test(statusStdout)
|
|
528
|
+
)
|
|
529
|
+
);
|
|
530
|
+
const gatewayStatusProbe = gatewayProbeCommand
|
|
531
|
+
? spawnSync(gatewayProbeCommand.cmd, gatewayProbeCommand.args, {
|
|
532
|
+
encoding: "utf8",
|
|
533
|
+
env: process.env,
|
|
534
|
+
})
|
|
535
|
+
: null;
|
|
536
|
+
const gatewayStatusStdout = String(gatewayStatusProbe?.stdout || "");
|
|
537
|
+
const gatewayStatusStderr = String(gatewayStatusProbe?.stderr || "");
|
|
538
|
+
const gatewayProbeOk = Boolean(
|
|
539
|
+
gatewayProbeCommand &&
|
|
540
|
+
gatewayStatusProbe &&
|
|
541
|
+
gatewayStatusProbe.status === 0
|
|
542
|
+
);
|
|
460
543
|
const result = spawnSync("ps", ["-Ao", "pid=,ppid=,command="], {
|
|
461
544
|
encoding: "utf8",
|
|
462
545
|
});
|
|
@@ -540,6 +623,12 @@ function detectOpenClawRuntime(workspaceRoot: string) {
|
|
|
540
623
|
const allProcesses = Array.from(combinedProcesses.values());
|
|
541
624
|
const gatewayReachable = gatewayListeners.length > 0;
|
|
542
625
|
const detectionNotes = [];
|
|
626
|
+
if (statusProbe && statusProbe.status !== 0) {
|
|
627
|
+
detectionNotes.push(String(statusStderr || "openclaw status failed").trim());
|
|
628
|
+
}
|
|
629
|
+
if (gatewayStatusProbe && gatewayStatusProbe.status !== 0) {
|
|
630
|
+
detectionNotes.push(String(gatewayStatusStderr || "openclaw gateway probe failed").trim());
|
|
631
|
+
}
|
|
543
632
|
if (result.status !== 0) detectionNotes.push(String(result.stderr || "ps failed").trim());
|
|
544
633
|
if (gatewayProbe.status !== 0 && gatewayLines.length === 0) {
|
|
545
634
|
detectionNotes.push(String(gatewayProbe.stderr || "lsof failed").trim());
|
|
@@ -548,19 +637,49 @@ function detectOpenClawRuntime(workspaceRoot: string) {
|
|
|
548
637
|
const gatewayUrl = `http://${OPENCLAW_GATEWAY_HOST}:${gatewayPort}/`;
|
|
549
638
|
|
|
550
639
|
return {
|
|
551
|
-
running: allProcesses.length > 0 || gatewayReachable,
|
|
640
|
+
running: gatewayProbeOk || allProcesses.length > 0 || gatewayReachable,
|
|
552
641
|
process_count: allProcesses.length,
|
|
553
642
|
processes: allProcesses.slice(0, 10),
|
|
554
643
|
detection_error: detectionNotes.filter(Boolean).join(" | ") || null,
|
|
555
644
|
gateway_url: gatewayUrl,
|
|
556
645
|
gateway_port: gatewayPort,
|
|
557
646
|
gateway_reachable: gatewayReachable,
|
|
647
|
+
status_command: statusCommand ? [statusCommand.cmd, ...statusCommand.args].join(" ") : null,
|
|
648
|
+
status_ok: statusLooksConfigured,
|
|
649
|
+
status_summary: statusLooksConfigured
|
|
650
|
+
? statusStdout
|
|
651
|
+
.split("\n")
|
|
652
|
+
.map((line) => line.trim())
|
|
653
|
+
.filter(Boolean)
|
|
654
|
+
.slice(0, 6)
|
|
655
|
+
.join(" | ")
|
|
656
|
+
: null,
|
|
657
|
+
gateway_probe_command: gatewayProbeCommand ? [gatewayProbeCommand.cmd, ...gatewayProbeCommand.args].join(" ") : null,
|
|
658
|
+
gateway_probe_ok: gatewayProbeOk,
|
|
659
|
+
gateway_probe_summary: gatewayProbeOk
|
|
660
|
+
? gatewayStatusStdout
|
|
661
|
+
.split("\n")
|
|
662
|
+
.map((line) => line.trim())
|
|
663
|
+
.filter(Boolean)
|
|
664
|
+
.slice(0, 4)
|
|
665
|
+
.join(" | ")
|
|
666
|
+
: null,
|
|
558
667
|
configured_gateway_url: configuredGateway.gateway_url,
|
|
559
668
|
configured_gateway_port: configuredGateway.gateway_port,
|
|
560
669
|
configured_gateway_bind: configuredGateway.gateway_bind,
|
|
561
670
|
configured_gateway_config_path: configuredGateway.config_path,
|
|
562
671
|
detection_mode:
|
|
563
|
-
|
|
672
|
+
gatewayProbeOk
|
|
673
|
+
? (
|
|
674
|
+
processes.length > 0 && gatewayReachable
|
|
675
|
+
? "gateway-probe+process+gateway"
|
|
676
|
+
: gatewayReachable
|
|
677
|
+
? "gateway-probe+gateway"
|
|
678
|
+
: processes.length > 0
|
|
679
|
+
? "gateway-probe+process"
|
|
680
|
+
: "gateway-probe"
|
|
681
|
+
)
|
|
682
|
+
: processes.length > 0 && gatewayReachable
|
|
564
683
|
? "process+gateway"
|
|
565
684
|
: gatewayReachable
|
|
566
685
|
? "gateway"
|
|
@@ -833,11 +952,17 @@ type OpenClawBridgeStatus = {
|
|
|
833
952
|
gateway_url: string;
|
|
834
953
|
gateway_port: number;
|
|
835
954
|
gateway_reachable: boolean;
|
|
955
|
+
status_command: string | null;
|
|
956
|
+
status_ok: boolean;
|
|
957
|
+
status_summary: string | null;
|
|
958
|
+
gateway_probe_command: string | null;
|
|
959
|
+
gateway_probe_ok: boolean;
|
|
960
|
+
gateway_probe_summary: string | null;
|
|
836
961
|
configured_gateway_url: string;
|
|
837
962
|
configured_gateway_port: number;
|
|
838
963
|
configured_gateway_bind: string | null;
|
|
839
964
|
configured_gateway_config_path: string | null;
|
|
840
|
-
detection_mode: "process" | "gateway" | "process+gateway" | "not_running";
|
|
965
|
+
detection_mode: "gateway-probe" | "gateway-probe+process" | "gateway-probe+gateway" | "gateway-probe+process+gateway" | "process" | "gateway" | "process+gateway" | "not_running";
|
|
841
966
|
};
|
|
842
967
|
skill_learning: {
|
|
843
968
|
available: boolean;
|
|
@@ -978,6 +1103,8 @@ export class LocalNodeService {
|
|
|
978
1103
|
private networkReconnectTimer: NodeJS.Timeout | null = null;
|
|
979
1104
|
private networkReconnectDelayMs = 5_000;
|
|
980
1105
|
private appVersion = "unknown";
|
|
1106
|
+
private openclawRuntimeCache: { value: ReturnType<typeof detectOpenClawRuntime>; expiresAt: number } | null = null;
|
|
1107
|
+
private openclawBridgeStatusCache: { value: OpenClawBridgeStatus; expiresAt: number } | null = null;
|
|
981
1108
|
|
|
982
1109
|
constructor(options?: { workspaceRoot?: string; projectRoot?: string; storageRoot?: string }) {
|
|
983
1110
|
this.workspaceRoot = options?.workspaceRoot || resolveWorkspaceRoot();
|
|
@@ -1023,6 +1150,24 @@ export class LocalNodeService {
|
|
|
1023
1150
|
this.networkPort = resolved.port;
|
|
1024
1151
|
}
|
|
1025
1152
|
|
|
1153
|
+
private getCachedOpenClawRuntime() {
|
|
1154
|
+
const now = Date.now();
|
|
1155
|
+
if (this.openclawRuntimeCache && this.openclawRuntimeCache.expiresAt > now) {
|
|
1156
|
+
return this.openclawRuntimeCache.value;
|
|
1157
|
+
}
|
|
1158
|
+
const value = detectOpenClawRuntime(this.projectRoot);
|
|
1159
|
+
this.openclawRuntimeCache = {
|
|
1160
|
+
value,
|
|
1161
|
+
expiresAt: now + OPENCLAW_RUNTIME_CACHE_MS,
|
|
1162
|
+
};
|
|
1163
|
+
return value;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
private invalidateOpenClawCaches() {
|
|
1167
|
+
this.openclawRuntimeCache = null;
|
|
1168
|
+
this.openclawBridgeStatusCache = null;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1026
1171
|
async start(): Promise<void> {
|
|
1027
1172
|
await this.hydrateFromDisk();
|
|
1028
1173
|
|
|
@@ -1431,8 +1576,10 @@ export class LocalNodeService {
|
|
|
1431
1576
|
reason: status.check_error || "already_current",
|
|
1432
1577
|
};
|
|
1433
1578
|
}
|
|
1579
|
+
const shimPath = userShimPath();
|
|
1434
1580
|
const scriptPath = resolve(this.workspaceRoot, "scripts", "silicaclaw-cli.mjs");
|
|
1435
|
-
|
|
1581
|
+
const useShim = existsSync(shimPath);
|
|
1582
|
+
if (!useShim && !existsSync(scriptPath)) {
|
|
1436
1583
|
return {
|
|
1437
1584
|
started: false,
|
|
1438
1585
|
target_version: status.latest_version,
|
|
@@ -1440,7 +1587,9 @@ export class LocalNodeService {
|
|
|
1440
1587
|
reason: "missing_cli_script",
|
|
1441
1588
|
};
|
|
1442
1589
|
}
|
|
1443
|
-
const
|
|
1590
|
+
const command = useShim ? shimPath : process.execPath;
|
|
1591
|
+
const args = useShim ? ["update"] : [scriptPath, "update"];
|
|
1592
|
+
const child = spawn(command, args, {
|
|
1444
1593
|
cwd: this.projectRoot,
|
|
1445
1594
|
detached: true,
|
|
1446
1595
|
stdio: "ignore",
|
|
@@ -1778,9 +1927,13 @@ export class LocalNodeService {
|
|
|
1778
1927
|
}
|
|
1779
1928
|
|
|
1780
1929
|
getOpenClawBridgeStatus(): OpenClawBridgeStatus {
|
|
1930
|
+
const now = Date.now();
|
|
1931
|
+
if (this.openclawBridgeStatusCache && this.openclawBridgeStatusCache.expiresAt > now) {
|
|
1932
|
+
return this.openclawBridgeStatusCache.value;
|
|
1933
|
+
}
|
|
1781
1934
|
const integration = this.getIntegrationStatus();
|
|
1782
1935
|
const openclawInstallation = detectOpenClawInstallation(this.projectRoot);
|
|
1783
|
-
const openclawRuntime =
|
|
1936
|
+
const openclawRuntime = this.getCachedOpenClawRuntime();
|
|
1784
1937
|
const skillInstallation = detectOpenClawSkillInstallation();
|
|
1785
1938
|
const ownerDelivery = detectOwnerDeliveryStatus({
|
|
1786
1939
|
workspaceRoot: this.projectRoot,
|
|
@@ -1788,7 +1941,7 @@ export class LocalNodeService {
|
|
|
1788
1941
|
openclawRunning: openclawRuntime.running,
|
|
1789
1942
|
skillInstalled: skillInstallation.installed,
|
|
1790
1943
|
});
|
|
1791
|
-
|
|
1944
|
+
const value: OpenClawBridgeStatus = {
|
|
1792
1945
|
enabled: this.socialConfig.enabled,
|
|
1793
1946
|
connected_to_silicaclaw: integration.connected_to_silicaclaw,
|
|
1794
1947
|
public_enabled: integration.public_enabled,
|
|
@@ -1844,6 +1997,11 @@ export class LocalNodeService {
|
|
|
1844
1997
|
install_skill: "/api/openclaw/bridge/skill-install",
|
|
1845
1998
|
},
|
|
1846
1999
|
};
|
|
2000
|
+
this.openclawBridgeStatusCache = {
|
|
2001
|
+
value,
|
|
2002
|
+
expiresAt: now + OPENCLAW_BRIDGE_STATUS_CACHE_MS,
|
|
2003
|
+
};
|
|
2004
|
+
return value;
|
|
1847
2005
|
}
|
|
1848
2006
|
|
|
1849
2007
|
async installOpenClawSkill(skillName?: string) {
|
|
@@ -1858,6 +2016,7 @@ export class LocalNodeService {
|
|
|
1858
2016
|
maxBuffer: 1024 * 1024,
|
|
1859
2017
|
});
|
|
1860
2018
|
const parsed = JSON.parse(String(stdout || "{}"));
|
|
2019
|
+
this.invalidateOpenClawCaches();
|
|
1861
2020
|
return {
|
|
1862
2021
|
...parsed,
|
|
1863
2022
|
bridge: this.getOpenClawBridgeStatus(),
|
|
@@ -1879,7 +2038,7 @@ export class LocalNodeService {
|
|
|
1879
2038
|
const workspaceSkillDir = resolve(homeDir, "workspace", "skills");
|
|
1880
2039
|
const legacySkillDir = resolve(homeDir, "skills");
|
|
1881
2040
|
const openclawSourceDir = defaultOpenClawSourceDir(this.projectRoot);
|
|
1882
|
-
const openclawRuntime =
|
|
2041
|
+
const openclawRuntime = this.getCachedOpenClawRuntime();
|
|
1883
2042
|
|
|
1884
2043
|
return {
|
|
1885
2044
|
bridge_api_base: DEFAULT_BRIDGE_API_BASE,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
2026.3.20-beta.
|
|
1
|
+
2026.3.20-beta.7
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "silicaclaw-broadcast",
|
|
3
|
-
"version": "2026.3.20-beta.
|
|
3
|
+
"version": "2026.3.20-beta.7",
|
|
4
4
|
"display_name": "SilicaClaw Broadcast",
|
|
5
5
|
"description": "Official OpenClaw skill for a bounded local SilicaClaw broadcast workflow: read public broadcasts, publish public broadcasts, and optionally forward owner-relevant summaries through OpenClaw's native channel.",
|
|
6
6
|
"entrypoints": {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
2026.3.20-beta.
|
|
1
|
+
2026.3.20-beta.3
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "silicaclaw-owner-push",
|
|
3
|
-
"version": "2026.3.20-beta.
|
|
3
|
+
"version": "2026.3.20-beta.3",
|
|
4
4
|
"display_name": "SilicaClaw Owner Push",
|
|
5
5
|
"description": "Official OpenClaw skill for a bounded local SilicaClaw monitoring workflow: watch public broadcasts, filter owner-relevant updates, and push concise summaries through OpenClaw's native owner channel.",
|
|
6
6
|
"entrypoints": {
|
|
@@ -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
|
|