@silicaclaw/cli 2026.3.20-2 → 2026.3.20-21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/INSTALL.md +13 -7
  3. package/README.md +60 -12
  4. package/VERSION +1 -1
  5. package/apps/local-console/dist/apps/local-console/src/server.d.ts +139 -3
  6. package/apps/local-console/dist/apps/local-console/src/server.js +1029 -92
  7. package/apps/local-console/dist/packages/core/src/index.d.ts +2 -0
  8. package/apps/local-console/dist/packages/core/src/index.js +2 -0
  9. package/apps/local-console/dist/packages/core/src/privateCrypto.d.ts +17 -0
  10. package/apps/local-console/dist/packages/core/src/privateCrypto.js +40 -0
  11. package/apps/local-console/dist/packages/core/src/privateMessage.d.ts +23 -0
  12. package/apps/local-console/dist/packages/core/src/privateMessage.js +74 -0
  13. package/apps/local-console/dist/packages/core/src/profile.js +2 -0
  14. package/apps/local-console/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
  15. package/apps/local-console/dist/packages/core/src/publicProfileSummary.js +3 -0
  16. package/apps/local-console/dist/packages/core/src/types.d.ts +40 -0
  17. package/apps/local-console/dist/packages/network/src/relayPreview.d.ts +12 -0
  18. package/apps/local-console/dist/packages/network/src/relayPreview.js +108 -8
  19. package/apps/local-console/dist/packages/network/src/types.d.ts +4 -0
  20. package/apps/local-console/dist/packages/storage/src/repos.d.ts +27 -1
  21. package/apps/local-console/dist/packages/storage/src/repos.js +35 -1
  22. package/apps/local-console/public/app/app.js +502 -11
  23. package/apps/local-console/public/app/events.js +21 -0
  24. package/apps/local-console/public/app/network.js +144 -32
  25. package/apps/local-console/public/app/overview.js +57 -27
  26. package/apps/local-console/public/app/social.js +342 -105
  27. package/apps/local-console/public/app/styles.css +149 -43
  28. package/apps/local-console/public/app/template.js +196 -100
  29. package/apps/local-console/public/app/translations.js +438 -316
  30. package/apps/local-console/src/server.ts +1177 -90
  31. package/apps/public-explorer/public/app/template.js +2 -2
  32. package/apps/public-explorer/public/app/translations.js +36 -36
  33. package/docs/NEW_USER_OPERATIONS.md +5 -5
  34. package/docs/OPENCLAW_BRIDGE.md +7 -7
  35. package/docs/OPENCLAW_BRIDGE_ZH.md +6 -6
  36. package/node_modules/@silicaclaw/core/dist/packages/core/src/index.d.ts +2 -0
  37. package/node_modules/@silicaclaw/core/dist/packages/core/src/index.js +2 -0
  38. package/node_modules/@silicaclaw/core/dist/packages/core/src/privateCrypto.d.ts +17 -0
  39. package/node_modules/@silicaclaw/core/dist/packages/core/src/privateCrypto.js +40 -0
  40. package/node_modules/@silicaclaw/core/dist/packages/core/src/privateMessage.d.ts +23 -0
  41. package/node_modules/@silicaclaw/core/dist/packages/core/src/privateMessage.js +74 -0
  42. package/node_modules/@silicaclaw/core/dist/packages/core/src/profile.js +2 -0
  43. package/node_modules/@silicaclaw/core/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
  44. package/node_modules/@silicaclaw/core/dist/packages/core/src/publicProfileSummary.js +3 -0
  45. package/node_modules/@silicaclaw/core/dist/packages/core/src/types.d.ts +40 -0
  46. package/node_modules/@silicaclaw/core/package.json +2 -2
  47. package/node_modules/@silicaclaw/core/src/index.ts +2 -0
  48. package/node_modules/@silicaclaw/core/src/privateCrypto.ts +57 -0
  49. package/node_modules/@silicaclaw/core/src/privateMessage.ts +101 -0
  50. package/node_modules/@silicaclaw/core/src/profile.ts +2 -0
  51. package/node_modules/@silicaclaw/core/src/publicProfileSummary.ts +7 -0
  52. package/node_modules/@silicaclaw/core/src/types.ts +44 -0
  53. package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.d.ts +12 -0
  54. package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.js +108 -8
  55. package/node_modules/@silicaclaw/network/dist/packages/network/src/types.d.ts +4 -0
  56. package/node_modules/@silicaclaw/network/src/relayPreview.ts +120 -10
  57. package/node_modules/@silicaclaw/network/src/types.ts +2 -0
  58. package/node_modules/@silicaclaw/storage/dist/packages/core/src/index.d.ts +2 -0
  59. package/node_modules/@silicaclaw/storage/dist/packages/core/src/index.js +2 -0
  60. package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateCrypto.d.ts +17 -0
  61. package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateCrypto.js +40 -0
  62. package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateMessage.d.ts +23 -0
  63. package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateMessage.js +74 -0
  64. package/node_modules/@silicaclaw/storage/dist/packages/core/src/profile.js +2 -0
  65. package/node_modules/@silicaclaw/storage/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
  66. package/node_modules/@silicaclaw/storage/dist/packages/core/src/publicProfileSummary.js +3 -0
  67. package/node_modules/@silicaclaw/storage/dist/packages/core/src/types.d.ts +40 -0
  68. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.d.ts +27 -1
  69. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.js +35 -1
  70. package/node_modules/@silicaclaw/storage/package.json +2 -2
  71. package/node_modules/@silicaclaw/storage/src/repos.ts +59 -1
  72. package/openclaw-skills/silicaclaw-bridge-setup/SKILL.md +18 -0
  73. package/openclaw-skills/silicaclaw-bridge-setup/VERSION +1 -1
  74. package/openclaw-skills/silicaclaw-bridge-setup/manifest.json +2 -2
  75. package/openclaw-skills/silicaclaw-broadcast/SKILL.md +18 -0
  76. package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -1
  77. package/openclaw-skills/silicaclaw-broadcast/manifest.json +2 -2
  78. package/openclaw-skills/silicaclaw-network-config/SKILL.md +158 -0
  79. package/openclaw-skills/silicaclaw-network-config/VERSION +1 -0
  80. package/openclaw-skills/silicaclaw-network-config/agents/openai.yaml +6 -0
  81. package/openclaw-skills/silicaclaw-network-config/manifest.json +27 -0
  82. package/openclaw-skills/silicaclaw-network-config/references/network-modes.md +22 -0
  83. package/openclaw-skills/silicaclaw-network-config/references/owner-dialogue-cheatsheet-zh.md +47 -0
  84. package/openclaw-skills/silicaclaw-network-config/references/public-discovery.md +22 -0
  85. package/openclaw-skills/silicaclaw-owner-push/SKILL.md +18 -0
  86. package/openclaw-skills/silicaclaw-owner-push/VERSION +1 -1
  87. package/openclaw-skills/silicaclaw-owner-push/manifest.json +2 -2
  88. package/openclaw-skills/silicaclaw-owner-push/references/runtime-setup.md +3 -0
  89. package/openclaw-skills/silicaclaw-owner-push/scripts/owner-push-forwarder.mjs +151 -9
  90. package/package.json +1 -1
  91. package/packages/core/dist/packages/core/src/index.d.ts +2 -0
  92. package/packages/core/dist/packages/core/src/index.js +2 -0
  93. package/packages/core/dist/packages/core/src/privateCrypto.d.ts +17 -0
  94. package/packages/core/dist/packages/core/src/privateCrypto.js +40 -0
  95. package/packages/core/dist/packages/core/src/privateMessage.d.ts +23 -0
  96. package/packages/core/dist/packages/core/src/privateMessage.js +74 -0
  97. package/packages/core/dist/packages/core/src/profile.js +2 -0
  98. package/packages/core/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
  99. package/packages/core/dist/packages/core/src/publicProfileSummary.js +3 -0
  100. package/packages/core/dist/packages/core/src/types.d.ts +40 -0
  101. package/packages/core/package.json +2 -2
  102. package/packages/core/src/index.ts +2 -0
  103. package/packages/core/src/privateCrypto.ts +57 -0
  104. package/packages/core/src/privateMessage.ts +101 -0
  105. package/packages/core/src/profile.ts +2 -0
  106. package/packages/core/src/publicProfileSummary.ts +7 -0
  107. package/packages/core/src/types.ts +44 -0
  108. package/packages/network/dist/packages/network/src/relayPreview.d.ts +12 -0
  109. package/packages/network/dist/packages/network/src/relayPreview.js +108 -8
  110. package/packages/network/dist/packages/network/src/types.d.ts +4 -0
  111. package/packages/network/src/relayPreview.ts +120 -10
  112. package/packages/network/src/types.ts +2 -0
  113. package/packages/storage/dist/packages/core/src/index.d.ts +2 -0
  114. package/packages/storage/dist/packages/core/src/index.js +2 -0
  115. package/packages/storage/dist/packages/core/src/privateCrypto.d.ts +17 -0
  116. package/packages/storage/dist/packages/core/src/privateCrypto.js +40 -0
  117. package/packages/storage/dist/packages/core/src/privateMessage.d.ts +23 -0
  118. package/packages/storage/dist/packages/core/src/privateMessage.js +74 -0
  119. package/packages/storage/dist/packages/core/src/profile.js +2 -0
  120. package/packages/storage/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
  121. package/packages/storage/dist/packages/core/src/publicProfileSummary.js +3 -0
  122. package/packages/storage/dist/packages/core/src/types.d.ts +40 -0
  123. package/packages/storage/dist/packages/storage/src/repos.d.ts +27 -1
  124. package/packages/storage/dist/packages/storage/src/repos.js +35 -1
  125. package/packages/storage/package.json +2 -2
  126. package/packages/storage/src/repos.ts +59 -1
  127. package/scripts/silicaclaw-cli.mjs +4 -1
  128. package/scripts/silicaclaw-gateway.mjs +114 -2
  129. package/scripts/validate-openclaw-skill.mjs +19 -0
  130. package/node_modules/@silicaclaw/storage/dist/index.d.ts +0 -3
  131. package/node_modules/@silicaclaw/storage/dist/index.js +0 -19
  132. package/node_modules/@silicaclaw/storage/dist/jsonRepo.d.ts +0 -7
  133. package/node_modules/@silicaclaw/storage/dist/jsonRepo.js +0 -29
  134. package/node_modules/@silicaclaw/storage/dist/repos.d.ts +0 -61
  135. package/node_modules/@silicaclaw/storage/dist/repos.js +0 -67
  136. package/node_modules/@silicaclaw/storage/dist/socialRuntimeRepo.d.ts +0 -5
  137. package/node_modules/@silicaclaw/storage/dist/socialRuntimeRepo.js +0 -57
  138. package/packages/storage/dist/index.d.ts +0 -3
  139. package/packages/storage/dist/index.js +0 -19
  140. package/packages/storage/dist/jsonRepo.d.ts +0 -7
  141. package/packages/storage/dist/jsonRepo.js +0 -29
  142. package/packages/storage/dist/repos.d.ts +0 -61
  143. package/packages/storage/dist/repos.js +0 -67
  144. package/packages/storage/dist/socialRuntimeRepo.d.ts +0 -5
  145. package/packages/storage/dist/socialRuntimeRepo.js +0 -57
@@ -319,6 +319,27 @@ export function bindAppEvents({
319
319
  });
320
320
  });
321
321
 
322
+ document.getElementById("skillsSearchInput").addEventListener("input", async (event) => {
323
+ socialController.setSkillsQuery(String(event.target?.value || ""));
324
+ await socialController.rerenderSkills();
325
+ });
326
+
327
+ document.querySelectorAll("[data-skills-filter]").forEach((btn) => {
328
+ btn.addEventListener("click", async () => {
329
+ socialController.setSkillsFilter(String(btn.getAttribute("data-skills-filter") || "all"));
330
+ await socialController.rerenderSkills();
331
+ });
332
+ });
333
+
334
+ document.querySelectorAll("#skillsBundledFooter, #skillsInstalledFooter, #skillsDialogueFooter").forEach((footer) => {
335
+ footer.addEventListener("click", async (event) => {
336
+ const btn = event.target instanceof Element ? event.target.closest("[data-skills-toggle]") : null;
337
+ if (!btn) return;
338
+ socialController.toggleSkillsExpanded(String(btn.getAttribute("data-skills-toggle") || ""));
339
+ await socialController.rerenderSkills();
340
+ });
341
+ });
342
+
322
343
  document.getElementById("saveGovernanceBtn").addEventListener("click", async () => {
323
344
  setFeedback("socialGovernanceFeedback", t("common.saving"));
324
345
  try {
@@ -13,6 +13,21 @@ export function createNetworkController({
13
13
  room: "",
14
14
  };
15
15
  let quickConnectDefaults = { ...fallbackQuickConnectDefaults };
16
+ let lastNetworkRenderKey = "";
17
+ let lastPeersRenderKey = "";
18
+ let lastDiscoveryRenderKey = "";
19
+
20
+ function queueState(value) {
21
+ const n = Number(value || 0);
22
+ if (n >= 100) return { tone: "danger", label: t("labels.queueHigh") };
23
+ if (n >= 20) return { tone: "warn", label: t("labels.queueWatch") };
24
+ return { tone: "ok", label: t("labels.queueHealthy") };
25
+ }
26
+
27
+ function queueBadge(value) {
28
+ const state = queueState(value);
29
+ return `<span class="pill ${state.tone}">${Number(value || 0)} · ${state.label}</span>`;
30
+ }
16
31
 
17
32
  async function refreshNetwork() {
18
33
  const [cfg, sts, rtp] = await Promise.all([api("/api/network/config"), api("/api/network/stats"), api("/api/runtime/paths")]);
@@ -25,6 +40,10 @@ export function createNetworkController({
25
40
  const transportStats = s.adapter_transport_stats || {};
26
41
  const d = s.adapter_discovery_stats || {};
27
42
  const dx = s.adapter_diagnostics_summary || {};
43
+ const runtimeDiag = s.runtime_diagnostics || {};
44
+ const runtimeMemory = runtimeDiag.memory_mib || {};
45
+ const runtimeDirectory = runtimeDiag.directory || {};
46
+ const runtimeSocial = runtimeDiag.social || {};
28
47
  const ac = s.adapter_config || c.adapter_config || {};
29
48
  quickConnectDefaults = {
30
49
  signalingUrl: String(
@@ -59,7 +78,7 @@ export function createNetworkController({
59
78
  heroRoomText: dx.room || "-",
60
79
  pillAdapterText: `${t("labels.adapter")}: ${c.adapter || "-"}`,
61
80
  });
62
- document.getElementById("networkCards").innerHTML = [
81
+ const networkCardsHtml = [
63
82
  [t("labels.adapter"), c.adapter],
64
83
  [t("labels.namespace"), c.namespace || "-"],
65
84
  [t("labels.port"), c.port ?? "-"],
@@ -75,12 +94,18 @@ export function createNetworkController({
75
94
  [t("network.sent"), msg.broadcast_total ?? 0],
76
95
  [t("network.peers"), p.total ?? 0],
77
96
  [t("network.onlinePeers"), p.online ?? 0],
97
+ ["RSS", runtimeMemory.rss ?? "-"],
98
+ ["Heap", runtimeMemory.heap_used ?? "-"],
99
+ ["Profiles", runtimeDirectory.profile_count ?? "-"],
100
+ ["Index keys", runtimeDirectory.index_key_count ?? "-"],
101
+ ["Messages", runtimeSocial.message_count ?? "-"],
102
+ ["Observations", runtimeSocial.observation_count ?? "-"],
78
103
  [t("network.activeWebrtcPeers"), dx.active_webrtc_peers ?? "-"],
79
104
  [t("network.reconnectAttempts"), dx.reconnect_attempts_total ?? "-"],
80
105
  [t("network.lastInbound"), ago(msg.last_message_at)],
81
106
  [t("network.lastOutbound"), ago(msg.last_broadcast_at)],
82
107
  ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join("");
83
- document.getElementById("networkSummaryList").innerHTML = [
108
+ const networkSummaryHtml = [
84
109
  [t("labels.mode"), describeCurrentMode(t, c.mode || "lan")],
85
110
  [t("network.relayHealth"), relayHealth],
86
111
  [t("network.currentRelay"), dx.signaling_url || "-"],
@@ -93,7 +118,7 @@ export function createNetworkController({
93
118
 
94
119
  const comp = c.components || {};
95
120
  const lim = c.limits || {};
96
- document.getElementById("networkComponents").textContent = [
121
+ const networkComponentsText = [
97
122
  `demo_mode: ${c.demo_mode || "-"}`,
98
123
  `transport: ${comp.transport || "-"}`,
99
124
  `discovery: ${comp.discovery || "-"}`,
@@ -130,12 +155,39 @@ export function createNetworkController({
130
155
  `last_discovery_event_at: ${dx.last_discovery_event_at ? new Date(dx.last_discovery_event_at).toISOString() : "-"}`,
131
156
  ].join("\n");
132
157
 
133
- document.getElementById("networkConfigSnapshot").textContent = toPrettyJson({
158
+ const networkConfigSnapshotText = toPrettyJson({
134
159
  config: c,
135
160
  adapter_config: ac,
136
161
  runtime_paths: runtimePaths,
137
162
  });
138
- document.getElementById("networkStatsSnapshot").textContent = toPrettyJson({ stats: s });
163
+ const networkStatsSnapshotText = toPrettyJson({ stats: s });
164
+ const renderKey = JSON.stringify({
165
+ adapter: c.adapter || "",
166
+ mode: c.mode || "",
167
+ namespace: c.namespace || "",
168
+ port: c.port ?? null,
169
+ signaling_url: dx.signaling_url || "",
170
+ room: dx.room || "",
171
+ relay_health: relayHealth,
172
+ last_poll_at: dx.last_poll_at || 0,
173
+ last_publish_at: dx.last_publish_at || 0,
174
+ last_error: dx.last_error || "",
175
+ msg_received_total: msg.received_total ?? 0,
176
+ msg_broadcast_total: msg.broadcast_total ?? 0,
177
+ peers_total: p.total ?? 0,
178
+ peers_online: p.online ?? 0,
179
+ reconnect_attempts_total: dx.reconnect_attempts_total ?? 0,
180
+ active_webrtc_peers: dx.active_webrtc_peers ?? 0,
181
+ });
182
+ if (renderKey === lastNetworkRenderKey) {
183
+ return;
184
+ }
185
+ document.getElementById("networkCards").innerHTML = networkCardsHtml;
186
+ document.getElementById("networkSummaryList").innerHTML = networkSummaryHtml;
187
+ document.getElementById("networkComponents").textContent = networkComponentsText;
188
+ document.getElementById("networkConfigSnapshot").textContent = networkConfigSnapshotText;
189
+ document.getElementById("networkStatsSnapshot").textContent = networkStatsSnapshotText;
190
+ lastNetworkRenderKey = renderKey;
139
191
  }
140
192
 
141
193
  async function refreshPeers() {
@@ -143,8 +195,12 @@ export function createNetworkController({
143
195
  const peers = peerRes.data || {};
144
196
  const ds = statsRes.data?.adapter_discovery_stats || {};
145
197
  const summary = peers.diagnostics_summary || {};
146
-
147
- document.getElementById("peerCards").innerHTML = [
198
+ const peerItems = Array.isArray(peers.items) ? peers.items : [];
199
+ const relayQueueTotal = peerItems.reduce((sum, peer) => sum + Number(peer?.meta?.relay_queue_size || 0), 0);
200
+ const relayQueueMax = peerItems.reduce((max, peer) => Math.max(max, Number(peer?.meta?.relay_queue_size || 0)), 0);
201
+ const signalQueueTotal = peerItems.reduce((sum, peer) => sum + Number(peer?.meta?.signal_queue_size || 0), 0);
202
+ const signalQueueMax = peerItems.reduce((max, peer) => Math.max(max, Number(peer?.meta?.signal_queue_size || 0)), 0);
203
+ const peerCardsHtml = [
148
204
  [t("network.total"), peers.total || 0],
149
205
  [t("overview.online"), peers.online || 0],
150
206
  [t("network.stale"), peers.stale || 0],
@@ -159,26 +215,28 @@ export function createNetworkController({
159
215
  [t("network.seedPeers"), summary.seed_peers_count ?? 0],
160
216
  [t("network.discoveryEvents"), summary.discovery_events_total ?? 0],
161
217
  [t("network.activeWebrtcPeers"), summary.active_webrtc_peers ?? "-"],
218
+ ["Relay queue", queueBadge(relayQueueTotal)],
219
+ ["Max relay queue", queueBadge(relayQueueMax)],
220
+ ["Signal queue", queueBadge(signalQueueTotal)],
221
+ ["Max signal queue", queueBadge(signalQueueMax)],
162
222
  [t("network.observeCalls"), ds.observe_calls || 0],
163
223
  [t("network.heartbeats"), ds.heartbeat_sent || 0],
164
224
  [t("network.peersAdded"), ds.peers_added || 0],
165
225
  [t("network.peersRemoved"), ds.peers_removed || 0],
166
226
  ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join("");
227
+ const peerStatsText = toPrettyJson({
228
+ discovery_stats: ds,
229
+ diagnostics_summary: summary,
230
+ adapter_stats: statsRes.data?.adapter_stats || {},
231
+ });
167
232
 
168
- if (!peers.items || !peers.items.length) {
169
- document.getElementById("peerTableWrap").innerHTML = `<div class="empty-state">${t("network.noPeersDiscovered")}</div>`;
170
- document.getElementById("peerStatsWrap").textContent = toPrettyJson({
171
- discovery_stats: ds,
172
- diagnostics_summary: summary,
173
- });
174
- return;
175
- }
176
-
177
- document.getElementById("peerTableWrap").innerHTML = `
233
+ const peerTableHtml = !peers.items || !peers.items.length
234
+ ? `<div class="empty-state">${t("network.noPeersDiscovered")}</div>`
235
+ : `
178
236
  <table class="table">
179
- <thead><tr><th>${t("network.peer")}</th><th>${t("network.status")}</th><th>${t("network.lastSeen")}</th><th>${t("network.staleSince")}</th><th>${t("network.messages")}</th><th>${t("network.firstSeen")}</th><th>${t("network.meta")}</th></tr></thead>
237
+ <thead><tr><th>${t("network.peer")}</th><th>${t("network.status")}</th><th>${t("network.lastSeen")}</th><th>${t("network.staleSince")}</th><th>${t("network.messages")}</th><th>${t("network.firstSeen")}</th><th>Relay Q</th><th>Signal Q</th><th>${t("network.meta")}</th></tr></thead>
180
238
  <tbody>
181
- ${peers.items.map((peer) => `
239
+ ${peerItems.map((peer) => `
182
240
  <tr>
183
241
  <td class="mono">${shortId(peer.peer_id)}</td>
184
242
  <td class="${peer.status === "online" ? "online" : peer.status === "offline" ? "offline" : "stale"}">${peerStatusText(peer.status)}</td>
@@ -186,25 +244,67 @@ export function createNetworkController({
186
244
  <td>${peer.stale_since_at ? ago(peer.stale_since_at) : "-"}</td>
187
245
  <td>${peer.messages_seen || 0}</td>
188
246
  <td>${new Date(peer.first_seen_at).toLocaleTimeString()}</td>
247
+ <td>${queueBadge(Number(peer?.meta?.relay_queue_size || 0))}</td>
248
+ <td>${queueBadge(Number(peer?.meta?.signal_queue_size || 0))}</td>
189
249
  <td class="mono">${peer.meta ? JSON.stringify(peer.meta) : "-"}</td>
190
250
  </tr>
191
251
  `).join("")}
192
252
  </tbody>
193
253
  </table>
194
254
  `;
195
- document.getElementById("peerStatsWrap").textContent = toPrettyJson({
196
- discovery_stats: ds,
197
- diagnostics_summary: summary,
198
- adapter_stats: statsRes.data?.adapter_stats || {},
255
+ const renderKey = JSON.stringify({
256
+ total: peers.total || 0,
257
+ online: peers.online || 0,
258
+ stale: peers.stale || 0,
259
+ namespace: peers.namespace || "",
260
+ summary: {
261
+ signaling_url: summary.signaling_url || "",
262
+ room: summary.room || "",
263
+ last_join_at: summary.last_join_at || 0,
264
+ last_poll_at: summary.last_poll_at || 0,
265
+ last_publish_at: summary.last_publish_at || 0,
266
+ last_error: summary.last_error || "",
267
+ },
268
+ ds: {
269
+ observe_calls: ds.observe_calls || 0,
270
+ heartbeat_sent: ds.heartbeat_sent || 0,
271
+ peers_added: ds.peers_added || 0,
272
+ peers_removed: ds.peers_removed || 0,
273
+ },
274
+ queues: {
275
+ relay_total: relayQueueTotal,
276
+ relay_max: relayQueueMax,
277
+ signal_total: signalQueueTotal,
278
+ signal_max: signalQueueMax,
279
+ },
280
+ items: peerItems
281
+ ? peerItems.map((peer) => [
282
+ peer.peer_id,
283
+ peer.status || "",
284
+ peer.last_seen_at || 0,
285
+ peer.stale_since_at || 0,
286
+ peer.messages_seen || 0,
287
+ peer.first_seen_at || 0,
288
+ Number(peer?.meta?.relay_queue_size || 0),
289
+ Number(peer?.meta?.signal_queue_size || 0),
290
+ peer.meta ? JSON.stringify(peer.meta) : "",
291
+ ])
292
+ : [],
199
293
  });
294
+ if (renderKey === lastPeersRenderKey) {
295
+ return;
296
+ }
297
+ document.getElementById("peerCards").innerHTML = peerCardsHtml;
298
+ document.getElementById("peerTableWrap").innerHTML = peerTableHtml;
299
+ document.getElementById("peerStatsWrap").textContent = peerStatsText;
300
+ lastPeersRenderKey = renderKey;
200
301
  }
201
302
 
202
303
  async function refreshDiscovery() {
203
304
  const eventsRes = await api("/api/discovery/events");
204
305
  const payload = eventsRes.data || {};
205
306
  const items = Array.isArray(payload.items) ? payload.items : [];
206
-
207
- document.getElementById("discoveryCards").innerHTML = [
307
+ const discoveryCardsHtml = [
208
308
  [t("labels.adapter"), payload.adapter || "-"],
209
309
  [t("labels.namespace"), payload.namespace || "-"],
210
310
  [t("network.eventsTotal"), payload.total ?? 0],
@@ -212,11 +312,9 @@ export function createNetworkController({
212
312
  [t("network.signalingEndpoints"), (payload.signaling_endpoints || []).length || 0],
213
313
  [t("network.seedPeers"), payload.seed_peers_count ?? 0],
214
314
  ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join("");
215
-
216
- if (!items.length) {
217
- document.getElementById("discoveryEventList").innerHTML = `<div class="empty-state">${t("network.noDiscoveryEvents")}</div>`;
218
- } else {
219
- document.getElementById("discoveryEventList").innerHTML = items
315
+ const discoveryEventListHtml = !items.length
316
+ ? `<div class="empty-state">${t("network.noDiscoveryEvents")}</div>`
317
+ : items
220
318
  .slice()
221
319
  .reverse()
222
320
  .map((event) => `
@@ -227,9 +325,23 @@ export function createNetworkController({
227
325
  </div>
228
326
  `)
229
327
  .join("");
328
+ const discoverySnapshotText = toPrettyJson(payload);
329
+ const renderKey = JSON.stringify({
330
+ adapter: payload.adapter || "",
331
+ namespace: payload.namespace || "",
332
+ total: payload.total ?? 0,
333
+ last_event_at: payload.last_event_at || 0,
334
+ signaling_endpoints: payload.signaling_endpoints || [],
335
+ seed_peers_count: payload.seed_peers_count ?? 0,
336
+ items: items.map((event) => [event.type || "", event.peer_id || "", event.endpoint || "", event.detail || "", event.at || 0]),
337
+ });
338
+ if (renderKey === lastDiscoveryRenderKey) {
339
+ return;
230
340
  }
231
-
232
- document.getElementById("discoverySnapshot").textContent = toPrettyJson(payload);
341
+ document.getElementById("discoveryCards").innerHTML = discoveryCardsHtml;
342
+ document.getElementById("discoveryEventList").innerHTML = discoveryEventListHtml;
343
+ document.getElementById("discoverySnapshot").textContent = discoverySnapshotText;
344
+ lastDiscoveryRenderKey = renderKey;
233
345
  }
234
346
 
235
347
  return {
@@ -7,6 +7,8 @@ export function createOverviewController({
7
7
  t,
8
8
  writeUiCache,
9
9
  }) {
10
+ let lastAgentsRenderKey = "";
11
+
10
12
  function renderOverviewGuide(overview, profile) {
11
13
  const hasDisplayName = Boolean(String(profile?.display_name || overview?.display_name || "").trim());
12
14
  const hasBio = Boolean(String(profile?.bio || "").trim());
@@ -64,12 +66,11 @@ export function createOverviewController({
64
66
  setOverviewMode,
65
67
  setVisibleRemotePublicCount,
66
68
  }) {
67
- const [overviewRes, discoveredRes, peerRes, profileRes, bridgeRes, networkCfgRes, networkStatsRes] = await Promise.allSettled([
69
+ const [overviewRes, discoveredRes, peerRes, profileRes, networkCfgRes, networkStatsRes] = await Promise.allSettled([
68
70
  api("/api/overview"),
69
71
  api("/api/search?q="),
70
72
  api("/api/peers"),
71
73
  api("/api/profile"),
72
- api("/api/openclaw/bridge"),
73
74
  api("/api/network/config"),
74
75
  api("/api/network/stats"),
75
76
  ]);
@@ -80,7 +81,6 @@ export function createOverviewController({
80
81
  const allProfiles = discoveredRes.status === "fulfilled" ? discoveredRes.value.data || [] : [];
81
82
  const peers = peerRes.status === "fulfilled" ? peerRes.value.data || {} : {};
82
83
  const currentProfile = profileRes.status === "fulfilled" ? profileRes.value.data || {} : {};
83
- const bridge = bridgeRes.status === "fulfilled" ? bridgeRes.value.data || {} : {};
84
84
  const networkCfg = networkCfgRes.status === "fulfilled" ? networkCfgRes.value.data || {} : {};
85
85
  const networkStats = networkStatsRes.status === "fulfilled" ? networkStatsRes.value.data || {} : {};
86
86
  const all = Array.isArray(allProfiles) ? allProfiles.slice() : [];
@@ -108,15 +108,15 @@ export function createOverviewController({
108
108
  const snapshotHtml = `
109
109
  <div class="snapshot-card">
110
110
  <div class="snapshot-card__identity">
111
- <div class="snapshot-card__label">Current Node</div>
111
+ <div class="snapshot-card__label">${t("overview.snapshotCurrentNode")}</div>
112
112
  <div class="snapshot-card__title">${escapeHtml(o.display_name || t("overview.unnamed"))}</div>
113
113
  <div class="snapshot-card__subtle mono">${escapeHtml(o.agent_id || "-")}</div>
114
114
  </div>
115
115
  <div class="snapshot-card__grid">
116
- <div class="snapshot-card__item"><div class="label">Version</div><span class="value-inline">${escapeHtml(o.app_version || "-")}</span></div>
117
- <div class="snapshot-card__item"><div class="label">Public</div><span class="value-inline">${o.public_enabled ? t("common.on") : t("common.off")}</span></div>
118
- <div class="snapshot-card__item"><div class="label">Broadcast</div><span class="value-inline">${o.broadcast_enabled ? t("common.on") : t("common.off")}</span></div>
119
- <div class="snapshot-card__item"><div class="label">Last Broadcast</div><span class="value-inline">${escapeHtml(ago(o.last_broadcast_at))}</span></div>
116
+ <div class="snapshot-card__item"><div class="label">${t("overview.snapshotVersion")}</div><span class="value-inline">${escapeHtml(o.app_version || "-")}</span></div>
117
+ <div class="snapshot-card__item"><div class="label">${t("overview.snapshotPublic")}</div><span class="value-inline">${o.public_enabled ? t("common.on") : t("common.off")}</span></div>
118
+ <div class="snapshot-card__item"><div class="label">${t("overview.snapshotBroadcast")}</div><span class="value-inline">${o.broadcast_enabled ? t("common.on") : t("common.off")}</span></div>
119
+ <div class="snapshot-card__item"><div class="label">${t("overview.snapshotLastBroadcast")}</div><span class="value-inline">${escapeHtml(ago(o.last_broadcast_at))}</span></div>
120
120
  </div>
121
121
  </div>
122
122
  `;
@@ -143,9 +143,10 @@ export function createOverviewController({
143
143
  document.getElementById("pillBroadcast").textContent = pillBroadcastText;
144
144
  document.getElementById("pillBroadcast").className = pillBroadcastClassName;
145
145
 
146
- const openclawRunning = !!bridge.openclaw_runtime?.running;
147
- const openclawDetected = !!bridge.openclaw_installation?.detected || openclawRunning || !!bridge.openclaw_runtime?.gateway_reachable;
148
- const skillInstalled = !!bridge.skill_learning?.installed;
146
+ const openclaw = o.openclaw || {};
147
+ const openclawRunning = !!openclaw.running;
148
+ const openclawDetected = !!openclaw.detected || openclawRunning;
149
+ const skillInstalled = !!openclaw.skill_installed;
149
150
  const globalMode = heroModeText === "global-preview";
150
151
  const lastNetworkError = String(networkDiag.last_error || o.last_broadcast_error || "").trim();
151
152
  const broadcastHealthy = o.broadcast_enabled && !lastNetworkError;
@@ -175,7 +176,7 @@ export function createOverviewController({
175
176
  document.getElementById("homePriorityGrid").innerHTML = [
176
177
  [t("overview.homeOpenClaw"), openclawRunning ? t("overview.homeRunning") : openclawDetected ? t("overview.homeInstalledOnly") : t("overview.homeStopped"), openclawRunning ? t("overview.homeMetaRunning") : t("overview.homeMetaNotRunning")],
177
178
  [t("overview.homeGlobalMode"), globalMode ? t("overview.homeGlobalReady") : t("overview.homeNotGlobal"), globalMode ? t("overview.homeMetaGlobal") : t("overview.homeMetaNotGlobal")],
178
- [t("overview.homeBroadcastHealth"), broadcastHealthy ? t("overview.homeHealthy") : t("overview.homeDegraded"), lastNetworkError || `Last broadcast ${ago(o.last_broadcast_at)}`],
179
+ [t("overview.homeBroadcastHealth"), broadcastHealthy ? t("overview.homeHealthy") : t("overview.homeDegraded"), lastNetworkError || t("overview.lastBroadcastAgo", { value: ago(o.last_broadcast_at) })],
179
180
  [t("overview.homePeers"), String(all.filter((agent) => !agent.is_self && agent.online).length), t("overview.homeMetaPeers", { online: String(onlineCount), discovered: String(discoveredCount) })],
180
181
  ].map(([label, value, meta]) => `
181
182
  <div class="priority-card">
@@ -221,8 +222,17 @@ export function createOverviewController({
221
222
  if (!filtered.length) {
222
223
  const agentsCountHintText = t("overview.agentsZero");
223
224
  const agentsWrapHtml = `<div class="label">${t("overview.noDiscoveredAgents")}</div>`;
224
- document.getElementById("agentsCountHint").textContent = agentsCountHintText;
225
- document.getElementById("agentsWrap").innerHTML = agentsWrapHtml;
225
+ const renderKey = JSON.stringify({
226
+ state: "empty",
227
+ hint: agentsCountHintText,
228
+ page: agentsPage,
229
+ onlineOnly: getOnlyShowOnline(),
230
+ });
231
+ if (renderKey !== lastAgentsRenderKey) {
232
+ document.getElementById("agentsCountHint").textContent = agentsCountHintText;
233
+ document.getElementById("agentsWrap").innerHTML = agentsWrapHtml;
234
+ lastAgentsRenderKey = renderKey;
235
+ }
226
236
  writeUiCache("silicaclaw_ui_overview", {
227
237
  overviewCardsHtml,
228
238
  brandVersionText,
@@ -270,6 +280,9 @@ export function createOverviewController({
270
280
  ${renderTags(a)}
271
281
  </div>
272
282
  <div class="${a.online ? "online" : "offline"}">${a.online ? t("overview.online") : t("overview.offline")}</div>
283
+ <div class="agent-card__actions">
284
+ ${!a.is_self ? `<button class="secondary" type="button" data-private-agent="${escapeHtml(a.agent_id)}" data-private-name="${escapeHtml(a.display_name || "")}" data-private-key="${escapeHtml(a.private_encryption_public_key || "")}" ${a.private_encryption_public_key ? "" : "disabled title=\"Private messaging unavailable for this agent yet\""}>${t("actions.messageAgentPrivately")}</button>` : ""}
285
+ </div>
273
286
  <div class="agent-card__meta"><div class="agent-card__updated">${ago(a.updated_at)}</div></div>
274
287
  </div>
275
288
  `).join("")}
@@ -282,20 +295,37 @@ export function createOverviewController({
282
295
  </div>
283
296
  </div>
284
297
  `;
285
- document.getElementById("agentsCountHint").textContent = agentsCountHintText;
286
- document.getElementById("agentsWrap").innerHTML = agentsWrapHtml;
287
- document.getElementById("agentsPrevPageBtn")?.addEventListener("click", async () => {
288
- if (agentsPage <= 1) return;
289
- agentsPage -= 1;
290
- onPageChange(agentsPage);
291
- await refreshOverview({ getAgentsPage, getOnlyShowOnline, onPageChange, setOverviewMode, setVisibleRemotePublicCount });
292
- });
293
- document.getElementById("agentsNextPageBtn")?.addEventListener("click", async () => {
294
- if (agentsPage >= totalAgentPages) return;
295
- agentsPage += 1;
296
- onPageChange(agentsPage);
297
- await refreshOverview({ getAgentsPage, getOnlyShowOnline, onPageChange, setOverviewMode, setVisibleRemotePublicCount });
298
+ const renderKey = JSON.stringify({
299
+ state: "list",
300
+ hint: agentsCountHintText,
301
+ page: agentsPage,
302
+ totalPages: totalAgentPages,
303
+ onlineOnly: getOnlyShowOnline(),
304
+ items: pagedAgents.map((agent) => [
305
+ agent.agent_id,
306
+ agent.updated_at,
307
+ agent.online ? 1 : 0,
308
+ agent.display_name || "",
309
+ agent.bio || "",
310
+ ]),
298
311
  });
312
+ if (renderKey !== lastAgentsRenderKey) {
313
+ document.getElementById("agentsCountHint").textContent = agentsCountHintText;
314
+ document.getElementById("agentsWrap").innerHTML = agentsWrapHtml;
315
+ document.getElementById("agentsPrevPageBtn")?.addEventListener("click", async () => {
316
+ if (agentsPage <= 1) return;
317
+ agentsPage -= 1;
318
+ onPageChange(agentsPage);
319
+ await refreshOverview({ getAgentsPage, getOnlyShowOnline, onPageChange, setOverviewMode, setVisibleRemotePublicCount });
320
+ });
321
+ document.getElementById("agentsNextPageBtn")?.addEventListener("click", async () => {
322
+ if (agentsPage >= totalAgentPages) return;
323
+ agentsPage += 1;
324
+ onPageChange(agentsPage);
325
+ await refreshOverview({ getAgentsPage, getOnlyShowOnline, onPageChange, setOverviewMode, setVisibleRemotePublicCount });
326
+ });
327
+ lastAgentsRenderKey = renderKey;
328
+ }
299
329
  writeUiCache("silicaclaw_ui_overview", {
300
330
  overviewCardsHtml,
301
331
  brandVersionText,