@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
|
@@ -594,6 +594,40 @@
|
|
|
594
594
|
overflow: hidden;
|
|
595
595
|
text-overflow: ellipsis;
|
|
596
596
|
}
|
|
597
|
+
.sidebar-version__relay {
|
|
598
|
+
display: inline-flex;
|
|
599
|
+
align-items: center;
|
|
600
|
+
gap: 6px;
|
|
601
|
+
font-size: 10px;
|
|
602
|
+
line-height: 1.2;
|
|
603
|
+
color: var(--muted);
|
|
604
|
+
white-space: nowrap;
|
|
605
|
+
overflow: hidden;
|
|
606
|
+
text-overflow: ellipsis;
|
|
607
|
+
}
|
|
608
|
+
.sidebar-version__relay::before {
|
|
609
|
+
content: "";
|
|
610
|
+
width: 7px;
|
|
611
|
+
height: 7px;
|
|
612
|
+
border-radius: 999px;
|
|
613
|
+
background: var(--ok);
|
|
614
|
+
box-shadow: 0 0 0 4px color-mix(in srgb, var(--ok) 12%, transparent);
|
|
615
|
+
flex: 0 0 auto;
|
|
616
|
+
}
|
|
617
|
+
.sidebar-version__relay.warn {
|
|
618
|
+
color: color-mix(in srgb, var(--warn) 86%, var(--text));
|
|
619
|
+
}
|
|
620
|
+
.sidebar-version__relay.warn::before {
|
|
621
|
+
background: var(--warn);
|
|
622
|
+
box-shadow: 0 0 0 4px color-mix(in srgb, var(--warn) 12%, transparent);
|
|
623
|
+
}
|
|
624
|
+
.sidebar-version__relay.danger {
|
|
625
|
+
color: color-mix(in srgb, var(--danger) 86%, var(--text));
|
|
626
|
+
}
|
|
627
|
+
.sidebar-version__relay.danger::before {
|
|
628
|
+
background: var(--danger);
|
|
629
|
+
box-shadow: 0 0 0 4px color-mix(in srgb, var(--danger) 12%, transparent);
|
|
630
|
+
}
|
|
597
631
|
.sidebar-version__actions {
|
|
598
632
|
display: flex;
|
|
599
633
|
align-items: center;
|
|
@@ -869,6 +903,7 @@
|
|
|
869
903
|
.pill.ok { color: var(--ok); border-color: rgba(34, 197, 94, 0.45); background: rgba(34, 197, 94, 0.08); }
|
|
870
904
|
.pill.warn { color: var(--warn); border-color: rgba(245, 158, 11, 0.45); background: rgba(245, 158, 11, 0.08); }
|
|
871
905
|
.pill.danger { color: var(--danger); border-color: rgba(239, 68, 68, 0.42); background: rgba(239, 68, 68, 0.08); }
|
|
906
|
+
.pill.ok { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 42%, transparent); background: color-mix(in srgb, var(--ok) 10%, transparent); }
|
|
872
907
|
|
|
873
908
|
.notice {
|
|
874
909
|
display: none;
|
|
@@ -101,6 +101,7 @@ export const appTemplate = String.raw`<div class="app" id="appShell">
|
|
|
101
101
|
<span class="sidebar-version__label">Version</span>
|
|
102
102
|
<span class="sidebar-version__text" id="brandVersion">-</span>
|
|
103
103
|
<span class="sidebar-version__hint" id="brandUpdateHint">Checking for updates...</span>
|
|
104
|
+
<span class="sidebar-version__relay hidden" id="brandRelayHint">Relay queues are healthy.</span>
|
|
104
105
|
</div>
|
|
105
106
|
<div class="sidebar-version__actions">
|
|
106
107
|
<button class="sidebar-version__btn sidebar-version__btn--ghost hidden" id="brandCheckUpdateBtn" type="button">Check</button>
|
|
@@ -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
|
},
|
|
@@ -146,6 +146,9 @@ export const TRANSLATIONS = {
|
|
|
146
146
|
versionPlatformMac: 'macOS service will restart automatically',
|
|
147
147
|
versionPlatformLinux: 'Linux service will restart automatically',
|
|
148
148
|
versionPlatformOther: 'Local service will refresh after the update',
|
|
149
|
+
relayQueuesHealthy: 'Relay queues are healthy.',
|
|
150
|
+
relayQueuesWatch: 'Relay queues need attention.',
|
|
151
|
+
relayQueuesHigh: 'Relay queues are building up.',
|
|
149
152
|
networkEyebrow: 'Network',
|
|
150
153
|
connectionSummary: 'Connection',
|
|
151
154
|
quickActions: 'Broadcast',
|
|
@@ -216,6 +219,9 @@ export const TRANSLATIONS = {
|
|
|
216
219
|
duplicateWindowSeconds: 'Duplicate Window (seconds)',
|
|
217
220
|
blockedAgentIds: 'Blocked agent IDs (agent_id, comma separated)',
|
|
218
221
|
blockedTerms: 'Blocked Terms (comma separated)',
|
|
222
|
+
queueHealthy: 'Healthy',
|
|
223
|
+
queueWatch: 'Watch',
|
|
224
|
+
queueHigh: 'High',
|
|
219
225
|
},
|
|
220
226
|
hints: {
|
|
221
227
|
publicDiscoverySwitch: 'Use Profile -> Public Enabled as the single public visibility switch.',
|
|
@@ -585,7 +591,7 @@ export const TRANSLATIONS = {
|
|
|
585
591
|
openclawSkillNotInstalled: 'The SilicaClaw broadcast skill is not installed yet.',
|
|
586
592
|
openclawSkillInstallFailed: 'OpenClaw skill installation failed',
|
|
587
593
|
openclawRoleBroadcasterOnly: 'This computer is broadcaster-only. Install OpenClaw here to learn broadcasts.',
|
|
588
|
-
openclawRoleNotRunning: 'OpenClaw is
|
|
594
|
+
openclawRoleNotRunning: 'OpenClaw is configured here, but the gateway is not running yet.',
|
|
589
595
|
openclawRoleReadyToLearn: 'OpenClaw is installed here. Learn the broadcast skill to keep going.',
|
|
590
596
|
openclawRoleLearned: 'This computer is ready to learn broadcasts and forward useful updates.',
|
|
591
597
|
openclawRoleLearningOnly: 'Broadcast learning works, but owner forwarding is not ready yet.',
|
|
@@ -688,7 +694,7 @@ export const TRANSLATIONS = {
|
|
|
688
694
|
showLess: '收起',
|
|
689
695
|
showMoreCount: '再显示 {count} 个',
|
|
690
696
|
openclawNotInstalled: '本机未安装 OpenClaw',
|
|
691
|
-
openclawNotRunning: 'OpenClaw
|
|
697
|
+
openclawNotRunning: 'OpenClaw 网关未启动',
|
|
692
698
|
openclawSkillLearned: '技能已学会',
|
|
693
699
|
silicaClawSkillsInstalled: '技能已全部安装',
|
|
694
700
|
},
|
|
@@ -735,6 +741,9 @@ export const TRANSLATIONS = {
|
|
|
735
741
|
versionPlatformMac: 'macOS 服务会自动重启',
|
|
736
742
|
versionPlatformLinux: 'Linux 服务会自动重启',
|
|
737
743
|
versionPlatformOther: '更新后本地服务会自动刷新',
|
|
744
|
+
relayQueuesHealthy: 'Relay 队列正常。',
|
|
745
|
+
relayQueuesWatch: 'Relay 队列需要关注。',
|
|
746
|
+
relayQueuesHigh: 'Relay 队列正在堆积。',
|
|
738
747
|
profileEyebrow: '资料',
|
|
739
748
|
publicProfile: '公开资料',
|
|
740
749
|
publicProfileEditor: '公开资料编辑器',
|
|
@@ -818,6 +827,9 @@ export const TRANSLATIONS = {
|
|
|
818
827
|
duplicateWindowSeconds: '重复消息窗口(秒)',
|
|
819
828
|
blockedAgentIds: '已屏蔽代理 ID(agent_id,逗号分隔)',
|
|
820
829
|
blockedTerms: '已屏蔽词(逗号分隔)',
|
|
830
|
+
queueHealthy: '正常',
|
|
831
|
+
queueWatch: '注意',
|
|
832
|
+
queueHigh: '偏高',
|
|
821
833
|
},
|
|
822
834
|
hints: {
|
|
823
835
|
publicDiscoverySwitch: '使用资料 -> Public Enabled 作为唯一的公开可见性开关。',
|
|
@@ -1008,7 +1020,7 @@ export const TRANSLATIONS = {
|
|
|
1008
1020
|
homeDegraded: '降级',
|
|
1009
1021
|
homeRunning: '运行中',
|
|
1010
1022
|
homeStopped: '未启动',
|
|
1011
|
-
homeInstalledOnly: '
|
|
1023
|
+
homeInstalledOnly: '已检测到 OpenClaw 配置',
|
|
1012
1024
|
homeGlobalReady: '全网预览已启用',
|
|
1013
1025
|
homeNotGlobal: '当前不是全网预览',
|
|
1014
1026
|
homeBriefNetwork: '网络',
|
|
@@ -1020,7 +1032,7 @@ export const TRANSLATIONS = {
|
|
|
1020
1032
|
homeBriefActionBroadcast: '保持公开广播,让运行 OpenClaw 的机器可以学习这个代理。',
|
|
1021
1033
|
homeBriefActionStabilize: '先修复 relay 或广播健康度,再依赖主人转发链路。',
|
|
1022
1034
|
homeMetaRunning: '已经检测到本机 OpenClaw 进程。',
|
|
1023
|
-
homeMetaNotRunning: '要开始学习广播,前提是本机 OpenClaw
|
|
1035
|
+
homeMetaNotRunning: '要开始学习广播,前提是本机 OpenClaw 网关正在运行。',
|
|
1024
1036
|
homeMetaGlobal: '当前默认使用全网 relay 预览模式。',
|
|
1025
1037
|
homeMetaNotGlobal: '这台机器当前还没有走全网 relay 路径。',
|
|
1026
1038
|
homeMetaPeers: '从本机视角看到 {online} 个在线代理,累计发现 {discovered} 个代理。',
|
|
@@ -1187,7 +1199,7 @@ export const TRANSLATIONS = {
|
|
|
1187
1199
|
openclawSkillNotInstalled: 'SilicaClaw 广播技能还没有安装。',
|
|
1188
1200
|
openclawSkillInstallFailed: 'OpenClaw 技能安装失败',
|
|
1189
1201
|
openclawRoleBroadcasterOnly: '这台电脑当前只能广播。想学习广播,先在这里安装 OpenClaw。',
|
|
1190
|
-
openclawRoleNotRunning: '
|
|
1202
|
+
openclawRoleNotRunning: '这台电脑已经配置了 OpenClaw,但网关还没启动。',
|
|
1191
1203
|
openclawRoleReadyToLearn: '这台电脑已经装了 OpenClaw。先学习广播技能,再继续。',
|
|
1192
1204
|
openclawRoleLearned: '这台电脑已经可以学习广播,并转发有用更新。',
|
|
1193
1205
|
openclawRoleLearningOnly: '已经能学习广播,但主人推送还没准备好。',
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
PublicProfileSummary,
|
|
18
18
|
SignedProfileRecord,
|
|
19
19
|
buildPublicProfileSummary,
|
|
20
|
-
buildIndexRecords,
|
|
21
20
|
cleanupExpiredPresence,
|
|
22
21
|
createDefaultProfileInput,
|
|
23
22
|
createEmptyDirectoryState,
|
|
@@ -116,6 +115,12 @@ const SOCIAL_MESSAGE_MAX_AGE_MS = Number(process.env.SOCIAL_MESSAGE_MAX_AGE_MS |
|
|
|
116
115
|
const SOCIAL_MESSAGE_OBSERVATION_HISTORY_LIMIT = Number(process.env.SOCIAL_MESSAGE_OBSERVATION_HISTORY_LIMIT || 500);
|
|
117
116
|
const SOCIAL_MESSAGE_REPLAY_WINDOW_MS = Number(process.env.SOCIAL_MESSAGE_REPLAY_WINDOW_MS || 10 * 60_000);
|
|
118
117
|
const SOCIAL_MESSAGE_REPLAY_MAX_PER_BROADCAST = Number(process.env.SOCIAL_MESSAGE_REPLAY_MAX_PER_BROADCAST || 3);
|
|
118
|
+
const SOCIAL_MESSAGE_REPLAY_REFRESH_INTERVAL_MS = Number(
|
|
119
|
+
process.env.SOCIAL_MESSAGE_REPLAY_REFRESH_INTERVAL_MS || 120_000
|
|
120
|
+
);
|
|
121
|
+
const PROFILE_RELAY_REFRESH_INTERVAL_MS = Number(
|
|
122
|
+
process.env.PROFILE_RELAY_REFRESH_INTERVAL_MS || 120_000
|
|
123
|
+
);
|
|
119
124
|
const SOCIAL_MESSAGE_BLOCKED_AGENT_IDS = new Set(
|
|
120
125
|
dedupeStrings(parseListEnv(process.env.SOCIAL_MESSAGE_BLOCKED_AGENT_IDS || ""))
|
|
121
126
|
);
|
|
@@ -450,8 +455,85 @@ function readOpenClawConfiguredGateway(workspaceRoot: string) {
|
|
|
450
455
|
} as const;
|
|
451
456
|
}
|
|
452
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
|
+
|
|
453
502
|
function detectOpenClawRuntime(workspaceRoot: string) {
|
|
454
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
|
+
);
|
|
455
537
|
const result = spawnSync("ps", ["-Ao", "pid=,ppid=,command="], {
|
|
456
538
|
encoding: "utf8",
|
|
457
539
|
});
|
|
@@ -535,6 +617,12 @@ function detectOpenClawRuntime(workspaceRoot: string) {
|
|
|
535
617
|
const allProcesses = Array.from(combinedProcesses.values());
|
|
536
618
|
const gatewayReachable = gatewayListeners.length > 0;
|
|
537
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
|
+
}
|
|
538
626
|
if (result.status !== 0) detectionNotes.push(String(result.stderr || "ps failed").trim());
|
|
539
627
|
if (gatewayProbe.status !== 0 && gatewayLines.length === 0) {
|
|
540
628
|
detectionNotes.push(String(gatewayProbe.stderr || "lsof failed").trim());
|
|
@@ -543,19 +631,49 @@ function detectOpenClawRuntime(workspaceRoot: string) {
|
|
|
543
631
|
const gatewayUrl = `http://${OPENCLAW_GATEWAY_HOST}:${gatewayPort}/`;
|
|
544
632
|
|
|
545
633
|
return {
|
|
546
|
-
running: allProcesses.length > 0 || gatewayReachable,
|
|
634
|
+
running: gatewayProbeOk || allProcesses.length > 0 || gatewayReachable,
|
|
547
635
|
process_count: allProcesses.length,
|
|
548
636
|
processes: allProcesses.slice(0, 10),
|
|
549
637
|
detection_error: detectionNotes.filter(Boolean).join(" | ") || null,
|
|
550
638
|
gateway_url: gatewayUrl,
|
|
551
639
|
gateway_port: gatewayPort,
|
|
552
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,
|
|
553
661
|
configured_gateway_url: configuredGateway.gateway_url,
|
|
554
662
|
configured_gateway_port: configuredGateway.gateway_port,
|
|
555
663
|
configured_gateway_bind: configuredGateway.gateway_bind,
|
|
556
664
|
configured_gateway_config_path: configuredGateway.config_path,
|
|
557
665
|
detection_mode:
|
|
558
|
-
|
|
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
|
|
559
677
|
? "process+gateway"
|
|
560
678
|
: gatewayReachable
|
|
561
679
|
? "gateway"
|
|
@@ -828,11 +946,17 @@ type OpenClawBridgeStatus = {
|
|
|
828
946
|
gateway_url: string;
|
|
829
947
|
gateway_port: number;
|
|
830
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;
|
|
831
955
|
configured_gateway_url: string;
|
|
832
956
|
configured_gateway_port: number;
|
|
833
957
|
configured_gateway_bind: string | null;
|
|
834
958
|
configured_gateway_config_path: string | null;
|
|
835
|
-
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";
|
|
836
960
|
};
|
|
837
961
|
skill_learning: {
|
|
838
962
|
available: boolean;
|
|
@@ -922,6 +1046,10 @@ export class LocalNodeService {
|
|
|
922
1046
|
private broadcastCount = 0;
|
|
923
1047
|
private lastMessageAt = 0;
|
|
924
1048
|
private lastBroadcastAt = 0;
|
|
1049
|
+
private lastProfileBroadcastAt = 0;
|
|
1050
|
+
private lastProfileBroadcastSignature = "";
|
|
1051
|
+
private lastReplayBroadcastAt = 0;
|
|
1052
|
+
private lastReplayBroadcastSignature = "";
|
|
925
1053
|
private lastBroadcastErrorAt = 0;
|
|
926
1054
|
private lastBroadcastError: string | null = null;
|
|
927
1055
|
private broadcastFailureCount = 0;
|
|
@@ -2280,15 +2408,14 @@ export class LocalNodeService {
|
|
|
2280
2408
|
profile: this.profile,
|
|
2281
2409
|
};
|
|
2282
2410
|
const presenceRecord = signPresence(this.identity, Date.now());
|
|
2283
|
-
const
|
|
2284
|
-
const replayMessages = this.getReplayableSelfSocialMessages();
|
|
2411
|
+
const shouldPublishProfile = this.shouldPublishProfileRecord(profileRecord, reason, presenceRecord.timestamp);
|
|
2412
|
+
const replayMessages = this.getReplayableSelfSocialMessages(reason);
|
|
2285
2413
|
|
|
2286
2414
|
try {
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
for (const record of indexRecords) {
|
|
2290
|
-
await this.publish("index", record);
|
|
2415
|
+
if (shouldPublishProfile) {
|
|
2416
|
+
await this.publish("profile", profileRecord);
|
|
2291
2417
|
}
|
|
2418
|
+
await this.publish("presence", presenceRecord);
|
|
2292
2419
|
for (const message of replayMessages) {
|
|
2293
2420
|
await this.publish(SOCIAL_MESSAGE_TOPIC, message);
|
|
2294
2421
|
}
|
|
@@ -2311,19 +2438,37 @@ export class LocalNodeService {
|
|
|
2311
2438
|
|
|
2312
2439
|
this.directory = ingestProfileRecord(this.directory, profileRecord);
|
|
2313
2440
|
this.directory = ingestPresenceRecord(this.directory, presenceRecord);
|
|
2314
|
-
for (const record of indexRecords) {
|
|
2315
|
-
this.directory = ingestIndexRecord(this.directory, record);
|
|
2316
|
-
}
|
|
2317
2441
|
this.compactCacheInMemory();
|
|
2318
2442
|
await this.persistCache();
|
|
2319
2443
|
|
|
2320
2444
|
await this.log(
|
|
2321
2445
|
"info",
|
|
2322
|
-
`Broadcast sent (${
|
|
2446
|
+
`Broadcast sent (${shouldPublishProfile ? "profile + " : ""}presence, replayed_messages=${replayMessages.length}, reason=${reason})`
|
|
2323
2447
|
);
|
|
2324
2448
|
return { sent: true, reason };
|
|
2325
2449
|
}
|
|
2326
2450
|
|
|
2451
|
+
private shouldPublishProfileRecord(
|
|
2452
|
+
profileRecord: SignedProfileRecord,
|
|
2453
|
+
reason: string,
|
|
2454
|
+
now = Date.now()
|
|
2455
|
+
): boolean {
|
|
2456
|
+
if (reason !== "interval") {
|
|
2457
|
+
this.lastProfileBroadcastSignature = profileRecord.profile.signature;
|
|
2458
|
+
this.lastProfileBroadcastAt = now;
|
|
2459
|
+
return true;
|
|
2460
|
+
}
|
|
2461
|
+
const signature = profileRecord.profile.signature;
|
|
2462
|
+
const changedSinceLastPublish = signature !== this.lastProfileBroadcastSignature;
|
|
2463
|
+
const refreshDue = now - this.lastProfileBroadcastAt >= PROFILE_RELAY_REFRESH_INTERVAL_MS;
|
|
2464
|
+
if (!changedSinceLastPublish && !refreshDue) {
|
|
2465
|
+
return false;
|
|
2466
|
+
}
|
|
2467
|
+
this.lastProfileBroadcastSignature = signature;
|
|
2468
|
+
this.lastProfileBroadcastAt = now;
|
|
2469
|
+
return true;
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2327
2472
|
private async maybeRecoverFromBroadcastFailure(reason: string, errorMessage: string): Promise<void> {
|
|
2328
2473
|
const recoveryThreshold = 3;
|
|
2329
2474
|
const recoveryCooldownMs = 60_000;
|
|
@@ -3213,18 +3358,32 @@ export class LocalNodeService {
|
|
|
3213
3358
|
return this.socialMessages.some((item) => item.message_id === messageId);
|
|
3214
3359
|
}
|
|
3215
3360
|
|
|
3216
|
-
private getReplayableSelfSocialMessages(now = Date.now()): SocialMessageRecord[] {
|
|
3361
|
+
private getReplayableSelfSocialMessages(reason = "manual", now = Date.now()): SocialMessageRecord[] {
|
|
3217
3362
|
const maxCount = Math.max(0, SOCIAL_MESSAGE_REPLAY_MAX_PER_BROADCAST);
|
|
3218
3363
|
if (!this.identity || maxCount === 0) {
|
|
3219
3364
|
return [];
|
|
3220
3365
|
}
|
|
3221
|
-
|
|
3366
|
+
const replayable = this.socialMessages
|
|
3222
3367
|
.filter((item) => (
|
|
3223
3368
|
item.agent_id === this.identity?.agent_id &&
|
|
3224
3369
|
now - item.created_at <= SOCIAL_MESSAGE_REPLAY_WINDOW_MS
|
|
3225
3370
|
))
|
|
3226
3371
|
.sort((a, b) => a.created_at - b.created_at)
|
|
3227
3372
|
.slice(-maxCount);
|
|
3373
|
+
if (!replayable.length) {
|
|
3374
|
+
this.lastReplayBroadcastSignature = "";
|
|
3375
|
+
return [];
|
|
3376
|
+
}
|
|
3377
|
+
const signature = replayable.map((item) => item.message_id).join(",");
|
|
3378
|
+
const isIntervalReplay = reason === "interval";
|
|
3379
|
+
const changedSinceLastReplay = signature !== this.lastReplayBroadcastSignature;
|
|
3380
|
+
const refreshDue = now - this.lastReplayBroadcastAt >= SOCIAL_MESSAGE_REPLAY_REFRESH_INTERVAL_MS;
|
|
3381
|
+
if (isIntervalReplay && !changedSinceLastReplay && !refreshDue) {
|
|
3382
|
+
return [];
|
|
3383
|
+
}
|
|
3384
|
+
this.lastReplayBroadcastSignature = signature;
|
|
3385
|
+
this.lastReplayBroadcastAt = now;
|
|
3386
|
+
return replayable;
|
|
3228
3387
|
}
|
|
3229
3388
|
|
|
3230
3389
|
private hasRecentDuplicateMessage(agentId: string, body: string, topic: string, now = Date.now()): boolean {
|
|
@@ -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()) {
|
|
@@ -34,6 +34,10 @@ type RelayPeer = {
|
|
|
34
34
|
last_seen_at: number;
|
|
35
35
|
messages_seen: number;
|
|
36
36
|
reconnect_attempts: number;
|
|
37
|
+
meta?: {
|
|
38
|
+
signal_queue_size?: number;
|
|
39
|
+
relay_queue_size?: number;
|
|
40
|
+
};
|
|
37
41
|
};
|
|
38
42
|
|
|
39
43
|
type RelayDiagnostics = {
|
|
@@ -227,7 +231,6 @@ export class RelayPreviewAdapter implements NetworkAdapter {
|
|
|
227
231
|
try {
|
|
228
232
|
await this.joinRoom("start");
|
|
229
233
|
this.started = true;
|
|
230
|
-
await this.refreshPeers();
|
|
231
234
|
await this.pollOnce();
|
|
232
235
|
this.scheduleNextPoll(this.pollIntervalMs);
|
|
233
236
|
this.recordDiscovery("signaling_connected", { endpoint: this.activeEndpoint });
|
|
@@ -375,8 +378,10 @@ export class RelayPreviewAdapter implements NetworkAdapter {
|
|
|
375
378
|
const payload = await this.get(`/peers?room=${encodeURIComponent(this.room)}`);
|
|
376
379
|
this.lastPeerRefreshAt = Date.now();
|
|
377
380
|
this.stats.peers_refresh_succeeded += 1;
|
|
378
|
-
const
|
|
379
|
-
|
|
381
|
+
const peerItems = Array.isArray(payload?.peer_details) && payload.peer_details.length
|
|
382
|
+
? payload.peer_details
|
|
383
|
+
: Array.isArray(payload?.peers) ? payload.peers : [];
|
|
384
|
+
this.updatePeersFromList(peerItems);
|
|
380
385
|
}
|
|
381
386
|
|
|
382
387
|
private onEnvelope(envelope: unknown): void {
|
|
@@ -457,9 +462,13 @@ export class RelayPreviewAdapter implements NetworkAdapter {
|
|
|
457
462
|
|
|
458
463
|
private async joinRoom(reason: string): Promise<void> {
|
|
459
464
|
this.stats.join_attempted += 1;
|
|
460
|
-
await this.post("/join", { room: this.room, peer_id: this.peerId });
|
|
465
|
+
const payload = await this.post("/join", { room: this.room, peer_id: this.peerId });
|
|
461
466
|
this.lastJoinAt = Date.now();
|
|
462
467
|
this.stats.join_succeeded += 1;
|
|
468
|
+
if (Array.isArray(payload?.peers)) {
|
|
469
|
+
this.updatePeersFromList(payload.peers);
|
|
470
|
+
this.lastPeerRefreshAt = this.lastJoinAt;
|
|
471
|
+
}
|
|
463
472
|
this.recordDiscovery("join_ok", { endpoint: this.activeEndpoint, detail: reason });
|
|
464
473
|
}
|
|
465
474
|
|
|
@@ -528,13 +537,38 @@ export class RelayPreviewAdapter implements NetworkAdapter {
|
|
|
528
537
|
}
|
|
529
538
|
|
|
530
539
|
private updatePeersFromList(values: unknown[]): void {
|
|
531
|
-
const
|
|
540
|
+
const parsedPeers: Array<{ peer_id: string; meta?: RelayPeer["meta"] }> = [];
|
|
541
|
+
for (const value of values) {
|
|
542
|
+
if (typeof value === "string") {
|
|
543
|
+
const peerId = String(value || "").trim();
|
|
544
|
+
if (peerId) {
|
|
545
|
+
parsedPeers.push({ peer_id: peerId });
|
|
546
|
+
}
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
if (value && typeof value === "object") {
|
|
550
|
+
const raw = value as Record<string, unknown>;
|
|
551
|
+
const peerId = String(raw.peer_id || "").trim();
|
|
552
|
+
if (!peerId) {
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
parsedPeers.push({
|
|
556
|
+
peer_id: peerId,
|
|
557
|
+
meta: {
|
|
558
|
+
signal_queue_size: Number(raw.signal_queue_size ?? 0),
|
|
559
|
+
relay_queue_size: Number(raw.relay_queue_size ?? 0),
|
|
560
|
+
},
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
const peerIds = parsedPeers.map((peer) => peer.peer_id);
|
|
532
565
|
if (!peerIds.includes(this.peerId)) {
|
|
533
566
|
void this.joinRoom("self_missing_from_peers").catch(() => {});
|
|
534
567
|
}
|
|
535
568
|
const now = Date.now();
|
|
536
569
|
const next = new Map<string, RelayPeer>();
|
|
537
|
-
for (const
|
|
570
|
+
for (const peerInfo of parsedPeers) {
|
|
571
|
+
const peerId = peerInfo.peer_id;
|
|
538
572
|
if (peerId === this.peerId) continue;
|
|
539
573
|
const existing = this.peers.get(peerId);
|
|
540
574
|
if (!existing) {
|
|
@@ -547,6 +581,7 @@ export class RelayPreviewAdapter implements NetworkAdapter {
|
|
|
547
581
|
last_seen_at: now,
|
|
548
582
|
messages_seen: existing?.messages_seen ?? 0,
|
|
549
583
|
reconnect_attempts: existing?.reconnect_attempts ?? 0,
|
|
584
|
+
meta: peerInfo.meta || existing?.meta,
|
|
550
585
|
});
|
|
551
586
|
}
|
|
552
587
|
for (const peerId of this.peers.keys()) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
2026.3.20-beta.
|
|
1
|
+
2026.3.20-beta.6
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "silicaclaw-broadcast",
|
|
3
|
-
"version": "2026.3.20-beta.
|
|
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.
|
|
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": {
|