@silicaclaw/cli 2026.3.20-5 → 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 CHANGED
@@ -2,6 +2,12 @@
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
+
5
11
  ### 2026.3.20-5
6
12
 
7
13
  - 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.2 \
214
+ --version 2026.3.20-beta.3 \
215
215
  --tags latest \
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."
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.2 \
286
+ --version 2026.3.20-beta.3 \
287
287
  --tags latest \
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."
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-5
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;
@@ -636,7 +642,7 @@ export declare class LocalNodeService {
636
642
  openclaw: {
637
643
  detected: boolean;
638
644
  running: boolean;
639
- 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";
640
646
  gateway_url: string;
641
647
  workspace_install_root: string;
642
648
  legacy_install_root: string;
@@ -389,8 +389,71 @@ function readOpenClawConfiguredGateway(workspaceRoot) {
389
389
  gateway_url: OPENCLAW_GATEWAY_URL,
390
390
  };
391
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
+ }
392
428
  function detectOpenClawRuntime(workspaceRoot) {
393
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);
394
457
  const result = (0, child_process_1.spawnSync)("ps", ["-Ao", "pid=,ppid=,command="], {
395
458
  encoding: "utf8",
396
459
  });
@@ -473,6 +536,12 @@ function detectOpenClawRuntime(workspaceRoot) {
473
536
  const allProcesses = Array.from(combinedProcesses.values());
474
537
  const gatewayReachable = gatewayListeners.length > 0;
475
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
+ }
476
545
  if (result.status !== 0)
477
546
  detectionNotes.push(String(result.stderr || "ps failed").trim());
478
547
  if (gatewayProbe.status !== 0 && gatewayLines.length === 0) {
@@ -481,24 +550,52 @@ function detectOpenClawRuntime(workspaceRoot) {
481
550
  const gatewayPort = preferredListener?.port || configuredGateway.gateway_port;
482
551
  const gatewayUrl = `http://${OPENCLAW_GATEWAY_HOST}:${gatewayPort}/`;
483
552
  return {
484
- running: allProcesses.length > 0 || gatewayReachable,
553
+ running: gatewayProbeOk || allProcesses.length > 0 || gatewayReachable,
485
554
  process_count: allProcesses.length,
486
555
  processes: allProcesses.slice(0, 10),
487
556
  detection_error: detectionNotes.filter(Boolean).join(" | ") || null,
488
557
  gateway_url: gatewayUrl,
489
558
  gateway_port: gatewayPort,
490
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,
491
580
  configured_gateway_url: configuredGateway.gateway_url,
492
581
  configured_gateway_port: configuredGateway.gateway_port,
493
582
  configured_gateway_bind: configuredGateway.gateway_bind,
494
583
  configured_gateway_config_path: configuredGateway.config_path,
495
- detection_mode: processes.length > 0 && gatewayReachable
496
- ? "process+gateway"
497
- : gatewayReachable
498
- ? "gateway"
499
- : processes.length > 0
500
- ? "process"
501
- : "not_running",
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",
502
599
  };
503
600
  }
504
601
  function detectOpenClawSkillInstallation() {
@@ -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 installed here, but not running yet.',
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: '这台电脑已经安装了 OpenClaw,但还没启动。',
1202
+ openclawRoleNotRunning: '这台电脑已经配置了 OpenClaw,但网关还没启动。',
1203
1203
  openclawRoleReadyToLearn: '这台电脑已经装了 OpenClaw。先学习广播技能,再继续。',
1204
1204
  openclawRoleLearned: '这台电脑已经可以学习广播,并转发有用更新。',
1205
1205
  openclawRoleLearningOnly: '已经能学习广播,但主人推送还没准备好。',
@@ -455,8 +455,85 @@ function readOpenClawConfiguredGateway(workspaceRoot: string) {
455
455
  } as const;
456
456
  }
457
457
 
458
+ function resolveOpenClawStatusCommand(workspaceRoot: string) {
459
+ const explicitBin = String(process.env.OPENCLAW_BIN || "").trim();
460
+ if (explicitBin) {
461
+ return { cmd: explicitBin, args: ["status"] } as const;
462
+ }
463
+
464
+ const configuredSourceDir = String(process.env.OPENCLAW_SOURCE_DIR || "").trim();
465
+ const defaultSourceDir = defaultOpenClawSourceDir(workspaceRoot);
466
+ const sourceDir = configuredSourceDir || defaultSourceDir;
467
+ const sourceEntry = existingPathOrNull(resolve(sourceDir, "openclaw.mjs"));
468
+ if (sourceEntry) {
469
+ return { cmd: process.execPath, args: [sourceEntry, "status"] } as const;
470
+ }
471
+
472
+ const commandPath = resolveExecutableInPath("openclaw");
473
+ if (commandPath) {
474
+ return { cmd: commandPath, args: ["status"] } as const;
475
+ }
476
+
477
+ return null;
478
+ }
479
+
480
+ function resolveOpenClawGatewayProbeCommand(workspaceRoot: string) {
481
+ const explicitBin = String(process.env.OPENCLAW_BIN || "").trim();
482
+ if (explicitBin) {
483
+ return { cmd: explicitBin, args: ["gateway", "probe"] } as const;
484
+ }
485
+
486
+ const configuredSourceDir = String(process.env.OPENCLAW_SOURCE_DIR || "").trim();
487
+ const defaultSourceDir = defaultOpenClawSourceDir(workspaceRoot);
488
+ const sourceDir = configuredSourceDir || defaultSourceDir;
489
+ const sourceEntry = existingPathOrNull(resolve(sourceDir, "openclaw.mjs"));
490
+ if (sourceEntry) {
491
+ return { cmd: process.execPath, args: [sourceEntry, "gateway", "probe"] } as const;
492
+ }
493
+
494
+ const commandPath = resolveExecutableInPath("openclaw");
495
+ if (commandPath) {
496
+ return { cmd: commandPath, args: ["gateway", "probe"] } as const;
497
+ }
498
+
499
+ return null;
500
+ }
501
+
458
502
  function detectOpenClawRuntime(workspaceRoot: string) {
459
503
  const configuredGateway = readOpenClawConfiguredGateway(workspaceRoot);
504
+ const statusCommand = resolveOpenClawStatusCommand(workspaceRoot);
505
+ const gatewayProbeCommand = resolveOpenClawGatewayProbeCommand(workspaceRoot);
506
+ const statusProbe = statusCommand
507
+ ? spawnSync(statusCommand.cmd, statusCommand.args, {
508
+ encoding: "utf8",
509
+ env: process.env,
510
+ })
511
+ : null;
512
+ const statusStdout = String(statusProbe?.stdout || "");
513
+ const statusStderr = String(statusProbe?.stderr || "");
514
+ const statusLooksConfigured = Boolean(
515
+ statusCommand &&
516
+ statusProbe &&
517
+ statusProbe.status === 0 &&
518
+ (
519
+ /\bChannels\b/i.test(statusStdout) ||
520
+ /\bSessions\b/i.test(statusStdout) ||
521
+ /\bNext steps:\b/i.test(statusStdout)
522
+ )
523
+ );
524
+ const gatewayStatusProbe = gatewayProbeCommand
525
+ ? spawnSync(gatewayProbeCommand.cmd, gatewayProbeCommand.args, {
526
+ encoding: "utf8",
527
+ env: process.env,
528
+ })
529
+ : null;
530
+ const gatewayStatusStdout = String(gatewayStatusProbe?.stdout || "");
531
+ const gatewayStatusStderr = String(gatewayStatusProbe?.stderr || "");
532
+ const gatewayProbeOk = Boolean(
533
+ gatewayProbeCommand &&
534
+ gatewayStatusProbe &&
535
+ gatewayStatusProbe.status === 0
536
+ );
460
537
  const result = spawnSync("ps", ["-Ao", "pid=,ppid=,command="], {
461
538
  encoding: "utf8",
462
539
  });
@@ -540,6 +617,12 @@ function detectOpenClawRuntime(workspaceRoot: string) {
540
617
  const allProcesses = Array.from(combinedProcesses.values());
541
618
  const gatewayReachable = gatewayListeners.length > 0;
542
619
  const detectionNotes = [];
620
+ if (statusProbe && statusProbe.status !== 0) {
621
+ detectionNotes.push(String(statusStderr || "openclaw status failed").trim());
622
+ }
623
+ if (gatewayStatusProbe && gatewayStatusProbe.status !== 0) {
624
+ detectionNotes.push(String(gatewayStatusStderr || "openclaw gateway probe failed").trim());
625
+ }
543
626
  if (result.status !== 0) detectionNotes.push(String(result.stderr || "ps failed").trim());
544
627
  if (gatewayProbe.status !== 0 && gatewayLines.length === 0) {
545
628
  detectionNotes.push(String(gatewayProbe.stderr || "lsof failed").trim());
@@ -548,19 +631,49 @@ function detectOpenClawRuntime(workspaceRoot: string) {
548
631
  const gatewayUrl = `http://${OPENCLAW_GATEWAY_HOST}:${gatewayPort}/`;
549
632
 
550
633
  return {
551
- running: allProcesses.length > 0 || gatewayReachable,
634
+ running: gatewayProbeOk || allProcesses.length > 0 || gatewayReachable,
552
635
  process_count: allProcesses.length,
553
636
  processes: allProcesses.slice(0, 10),
554
637
  detection_error: detectionNotes.filter(Boolean).join(" | ") || null,
555
638
  gateway_url: gatewayUrl,
556
639
  gateway_port: gatewayPort,
557
640
  gateway_reachable: gatewayReachable,
641
+ status_command: statusCommand ? [statusCommand.cmd, ...statusCommand.args].join(" ") : null,
642
+ status_ok: statusLooksConfigured,
643
+ status_summary: statusLooksConfigured
644
+ ? statusStdout
645
+ .split("\n")
646
+ .map((line) => line.trim())
647
+ .filter(Boolean)
648
+ .slice(0, 6)
649
+ .join(" | ")
650
+ : null,
651
+ gateway_probe_command: gatewayProbeCommand ? [gatewayProbeCommand.cmd, ...gatewayProbeCommand.args].join(" ") : null,
652
+ gateway_probe_ok: gatewayProbeOk,
653
+ gateway_probe_summary: gatewayProbeOk
654
+ ? gatewayStatusStdout
655
+ .split("\n")
656
+ .map((line) => line.trim())
657
+ .filter(Boolean)
658
+ .slice(0, 4)
659
+ .join(" | ")
660
+ : null,
558
661
  configured_gateway_url: configuredGateway.gateway_url,
559
662
  configured_gateway_port: configuredGateway.gateway_port,
560
663
  configured_gateway_bind: configuredGateway.gateway_bind,
561
664
  configured_gateway_config_path: configuredGateway.config_path,
562
665
  detection_mode:
563
- processes.length > 0 && gatewayReachable
666
+ gatewayProbeOk
667
+ ? (
668
+ processes.length > 0 && gatewayReachable
669
+ ? "gateway-probe+process+gateway"
670
+ : gatewayReachable
671
+ ? "gateway-probe+gateway"
672
+ : processes.length > 0
673
+ ? "gateway-probe+process"
674
+ : "gateway-probe"
675
+ )
676
+ : processes.length > 0 && gatewayReachable
564
677
  ? "process+gateway"
565
678
  : gatewayReachable
566
679
  ? "gateway"
@@ -833,11 +946,17 @@ type OpenClawBridgeStatus = {
833
946
  gateway_url: string;
834
947
  gateway_port: number;
835
948
  gateway_reachable: boolean;
949
+ status_command: string | null;
950
+ status_ok: boolean;
951
+ status_summary: string | null;
952
+ gateway_probe_command: string | null;
953
+ gateway_probe_ok: boolean;
954
+ gateway_probe_summary: string | null;
836
955
  configured_gateway_url: string;
837
956
  configured_gateway_port: number;
838
957
  configured_gateway_bind: string | null;
839
958
  configured_gateway_config_path: string | null;
840
- detection_mode: "process" | "gateway" | "process+gateway" | "not_running";
959
+ detection_mode: "gateway-probe" | "gateway-probe+process" | "gateway-probe+gateway" | "gateway-probe+process+gateway" | "process" | "gateway" | "process+gateway" | "not_running";
841
960
  };
842
961
  skill_learning: {
843
962
  available: boolean;
@@ -1 +1 @@
1
- 2026.3.20-beta.5
1
+ 2026.3.20-beta.6
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "silicaclaw-broadcast",
3
- "version": "2026.3.20-beta.5",
3
+ "version": "2026.3.20-beta.6",
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.2
1
+ 2026.3.20-beta.3
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "silicaclaw-owner-push",
3
- "version": "2026.3.20-beta.2",
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@silicaclaw/cli",
3
- "version": "2026.3.20-5",
3
+ "version": "2026.3.20-6",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"