@silicaclaw/cli 2026.3.20-1 → 2026.3.20-10

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 (125) hide show
  1. package/CHANGELOG.md +54 -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 +129 -2
  6. package/apps/local-console/dist/apps/local-console/src/server.js +887 -91
  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 +13 -1
  21. package/apps/local-console/dist/packages/storage/src/repos.js +19 -1
  22. package/apps/local-console/public/app/app.js +465 -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 +60 -52
  26. package/apps/local-console/public/app/social.js +316 -93
  27. package/apps/local-console/public/app/styles.css +127 -1
  28. package/apps/local-console/public/app/template.js +121 -35
  29. package/apps/local-console/public/app/translations.js +430 -316
  30. package/apps/local-console/src/server.ts +1024 -89
  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/src/index.ts +2 -0
  47. package/node_modules/@silicaclaw/core/src/privateCrypto.ts +57 -0
  48. package/node_modules/@silicaclaw/core/src/privateMessage.ts +101 -0
  49. package/node_modules/@silicaclaw/core/src/profile.ts +2 -0
  50. package/node_modules/@silicaclaw/core/src/publicProfileSummary.ts +7 -0
  51. package/node_modules/@silicaclaw/core/src/types.ts +44 -0
  52. package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.d.ts +12 -0
  53. package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.js +108 -8
  54. package/node_modules/@silicaclaw/network/dist/packages/network/src/types.d.ts +4 -0
  55. package/node_modules/@silicaclaw/network/src/relayPreview.ts +120 -10
  56. package/node_modules/@silicaclaw/network/src/types.ts +2 -0
  57. package/node_modules/@silicaclaw/storage/dist/packages/core/src/index.d.ts +2 -0
  58. package/node_modules/@silicaclaw/storage/dist/packages/core/src/index.js +2 -0
  59. package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateCrypto.d.ts +17 -0
  60. package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateCrypto.js +40 -0
  61. package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateMessage.d.ts +23 -0
  62. package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateMessage.js +74 -0
  63. package/node_modules/@silicaclaw/storage/dist/packages/core/src/profile.js +2 -0
  64. package/node_modules/@silicaclaw/storage/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
  65. package/node_modules/@silicaclaw/storage/dist/packages/core/src/publicProfileSummary.js +3 -0
  66. package/node_modules/@silicaclaw/storage/dist/packages/core/src/types.d.ts +40 -0
  67. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.d.ts +13 -1
  68. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.js +19 -1
  69. package/node_modules/@silicaclaw/storage/src/repos.ts +31 -1
  70. package/openclaw-skills/silicaclaw-bridge-setup/SKILL.md +18 -0
  71. package/openclaw-skills/silicaclaw-bridge-setup/VERSION +1 -1
  72. package/openclaw-skills/silicaclaw-bridge-setup/manifest.json +2 -2
  73. package/openclaw-skills/silicaclaw-broadcast/SKILL.md +18 -0
  74. package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -1
  75. package/openclaw-skills/silicaclaw-broadcast/manifest.json +2 -2
  76. package/openclaw-skills/silicaclaw-network-config/SKILL.md +158 -0
  77. package/openclaw-skills/silicaclaw-network-config/VERSION +1 -0
  78. package/openclaw-skills/silicaclaw-network-config/agents/openai.yaml +6 -0
  79. package/openclaw-skills/silicaclaw-network-config/manifest.json +27 -0
  80. package/openclaw-skills/silicaclaw-network-config/references/network-modes.md +22 -0
  81. package/openclaw-skills/silicaclaw-network-config/references/owner-dialogue-cheatsheet-zh.md +47 -0
  82. package/openclaw-skills/silicaclaw-network-config/references/public-discovery.md +22 -0
  83. package/openclaw-skills/silicaclaw-owner-push/SKILL.md +18 -0
  84. package/openclaw-skills/silicaclaw-owner-push/VERSION +1 -1
  85. package/openclaw-skills/silicaclaw-owner-push/manifest.json +2 -2
  86. package/openclaw-skills/silicaclaw-owner-push/references/runtime-setup.md +3 -0
  87. package/openclaw-skills/silicaclaw-owner-push/scripts/owner-push-forwarder.mjs +151 -9
  88. package/package.json +1 -1
  89. package/packages/core/dist/packages/core/src/index.d.ts +2 -0
  90. package/packages/core/dist/packages/core/src/index.js +2 -0
  91. package/packages/core/dist/packages/core/src/privateCrypto.d.ts +17 -0
  92. package/packages/core/dist/packages/core/src/privateCrypto.js +40 -0
  93. package/packages/core/dist/packages/core/src/privateMessage.d.ts +23 -0
  94. package/packages/core/dist/packages/core/src/privateMessage.js +74 -0
  95. package/packages/core/dist/packages/core/src/profile.js +2 -0
  96. package/packages/core/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
  97. package/packages/core/dist/packages/core/src/publicProfileSummary.js +3 -0
  98. package/packages/core/dist/packages/core/src/types.d.ts +40 -0
  99. package/packages/core/src/index.ts +2 -0
  100. package/packages/core/src/privateCrypto.ts +57 -0
  101. package/packages/core/src/privateMessage.ts +101 -0
  102. package/packages/core/src/profile.ts +2 -0
  103. package/packages/core/src/publicProfileSummary.ts +7 -0
  104. package/packages/core/src/types.ts +44 -0
  105. package/packages/network/dist/packages/network/src/relayPreview.d.ts +12 -0
  106. package/packages/network/dist/packages/network/src/relayPreview.js +108 -8
  107. package/packages/network/dist/packages/network/src/types.d.ts +4 -0
  108. package/packages/network/src/relayPreview.ts +120 -10
  109. package/packages/network/src/types.ts +2 -0
  110. package/packages/storage/dist/packages/core/src/index.d.ts +2 -0
  111. package/packages/storage/dist/packages/core/src/index.js +2 -0
  112. package/packages/storage/dist/packages/core/src/privateCrypto.d.ts +17 -0
  113. package/packages/storage/dist/packages/core/src/privateCrypto.js +40 -0
  114. package/packages/storage/dist/packages/core/src/privateMessage.d.ts +23 -0
  115. package/packages/storage/dist/packages/core/src/privateMessage.js +74 -0
  116. package/packages/storage/dist/packages/core/src/profile.js +2 -0
  117. package/packages/storage/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
  118. package/packages/storage/dist/packages/core/src/publicProfileSummary.js +3 -0
  119. package/packages/storage/dist/packages/core/src/types.d.ts +40 -0
  120. package/packages/storage/dist/packages/storage/src/repos.d.ts +13 -1
  121. package/packages/storage/dist/packages/storage/src/repos.js +19 -1
  122. package/packages/storage/src/repos.ts +31 -1
  123. package/scripts/silicaclaw-cli.mjs +59 -6
  124. package/scripts/silicaclaw-gateway.mjs +108 -0
  125. package/scripts/validate-openclaw-skill.mjs +19 -0
@@ -24,6 +24,134 @@ export function createSocialController({
24
24
  toPrettyJson,
25
25
  writeUiCache,
26
26
  }) {
27
+ const SKILLS_SECTION_LIMIT = 4;
28
+ const SKILLS_DIALOGUE_LIMIT = 1;
29
+ let lastMessagesRenderKey = "";
30
+ let lastLogsRenderKey = "";
31
+ const sectionRenderCache = new Map();
32
+ let skillsQuery = "";
33
+ let skillsFilter = "all";
34
+ const skillsExpanded = {
35
+ bundled: false,
36
+ installed: false,
37
+ dialogue: false,
38
+ };
39
+
40
+ function skillModeLabel(mode) {
41
+ if (mode === "workspace") return t("labels.skillsModeWorkspace");
42
+ if (mode === "legacy") return t("labels.skillsModeLegacy");
43
+ if (mode === "bundled") return t("labels.skillsModeBundled");
44
+ if (mode === "installed") return t("labels.skillsModeInstalled");
45
+ return mode ? String(mode) : t("labels.skillsModeGeneric");
46
+ }
47
+
48
+ function normalizeSkillSearchText(value) {
49
+ return String(value || "").trim().toLowerCase();
50
+ }
51
+
52
+ function setSkillsQuery(value) {
53
+ skillsQuery = normalizeSkillSearchText(value);
54
+ }
55
+
56
+ function setSkillsFilter(value) {
57
+ const next = String(value || "").trim();
58
+ skillsFilter = ["all", "attention", "updates", "installed"].includes(next) ? next : "all";
59
+ }
60
+
61
+ function toggleSkillsExpanded(section) {
62
+ if (!(section in skillsExpanded)) return;
63
+ skillsExpanded[section] = !skillsExpanded[section];
64
+ }
65
+
66
+ function skillMatchesSearch(skill) {
67
+ if (!skillsQuery) return true;
68
+ const haystack = [
69
+ skill.display_name,
70
+ skill.name,
71
+ skill.description,
72
+ skill.version,
73
+ ...(Array.isArray(skill.capabilities) ? skill.capabilities : []),
74
+ ...(Array.isArray(skill.owner_dialogue_examples_zh) ? skill.owner_dialogue_examples_zh : []),
75
+ ].map((item) => normalizeSkillSearchText(item)).join(" ");
76
+ return haystack.includes(skillsQuery);
77
+ }
78
+
79
+ function skillMatchesFilter(skill, section) {
80
+ if (skillsFilter === "all") return true;
81
+ if (skillsFilter === "updates") return Boolean(skill.update_available);
82
+ if (skillsFilter === "installed") {
83
+ return section === "installed" ? true : Boolean(skill.installed_in_openclaw);
84
+ }
85
+ if (skillsFilter === "attention") {
86
+ return section === "installed"
87
+ ? Boolean(skill.update_available)
88
+ : Boolean(skill.update_available || !skill.installed_in_openclaw);
89
+ }
90
+ return true;
91
+ }
92
+
93
+ function renderSkillsSectionFooter({ footerId, section, totalCount, visibleCount, limit }) {
94
+ const footer = document.getElementById(footerId);
95
+ if (!footer) return;
96
+ if (totalCount <= limit) {
97
+ footer.innerHTML = "";
98
+ return;
99
+ }
100
+ const expanded = skillsExpanded[section];
101
+ const hiddenCount = Math.max(totalCount - visibleCount, 0);
102
+ footer.innerHTML = `
103
+ <button class="secondary skills-section__toggle" type="button" data-skills-toggle="${section}">
104
+ ${expanded ? t("actions.showLess") : t("actions.showMoreCount", { count: String(hiddenCount) })}
105
+ </button>
106
+ `;
107
+ }
108
+
109
+ function renderFilteredSkillCards({ skills, section, gridId, footerId, renderer, limit }) {
110
+ const filtered = skills.filter((skill) => skillMatchesSearch(skill) && skillMatchesFilter(skill, section));
111
+ const expanded = skillsExpanded[section];
112
+ const visible = expanded ? filtered : filtered.slice(0, limit);
113
+ document.getElementById(gridId).innerHTML = visible.length
114
+ ? visible.map((skill) => renderer(skill)).join("")
115
+ : `<div class="skills-empty">${t("hints.skillsNoFilterMatch")}</div>`;
116
+ renderSkillsSectionFooter({
117
+ footerId,
118
+ section,
119
+ totalCount: filtered.length,
120
+ visibleCount: visible.length,
121
+ limit,
122
+ });
123
+ return filtered.length;
124
+ }
125
+
126
+ function renderSkillsFilterMeta({ bundledCount, installedCount, dialogueCount }) {
127
+ const matchedTotal = bundledCount + installedCount + dialogueCount;
128
+ const filterLabel = t(`labels.skillsFilter${skillsFilter.charAt(0).toUpperCase()}${skillsFilter.slice(1)}`);
129
+ document.getElementById("skillsFilterMeta").textContent = t("hints.skillsFilterMeta", {
130
+ count: String(matchedTotal),
131
+ filter: filterLabel,
132
+ });
133
+ document.querySelectorAll("[data-skills-filter]").forEach((btn) => {
134
+ btn.classList.toggle("active", btn.getAttribute("data-skills-filter") === skillsFilter);
135
+ });
136
+ }
137
+
138
+ function setCachedContent(id, value, mode = "html") {
139
+ const cacheKey = `${mode}:${id}`;
140
+ if (sectionRenderCache.get(cacheKey) === value) {
141
+ return;
142
+ }
143
+ const el = document.getElementById(id);
144
+ if (!el) return;
145
+ if (mode === "text") {
146
+ el.textContent = value;
147
+ } else if (mode === "class") {
148
+ el.className = value;
149
+ } else {
150
+ el.innerHTML = value;
151
+ }
152
+ sectionRenderCache.set(cacheKey, value);
153
+ }
154
+
27
155
  function renderSocialMessages() {
28
156
  const listEl = document.getElementById("socialMessageList");
29
157
  const metaEl = document.getElementById("socialMessageMeta");
@@ -37,10 +165,16 @@ export function createSocialController({
37
165
  seconds: String(Math.floor((governance.send_limit?.window_ms || 60000) / 1000)),
38
166
  })}`
39
167
  : t("overview.messageHint");
40
- hintEl.textContent = governanceHint;
41
168
  if (!socialMessagesCache.length) {
42
- metaEl.textContent = t("overview.noMessagesMeta");
43
- listEl.innerHTML = `<div class="empty-state">${t("overview.noMessagesEmpty")}</div>`;
169
+ const nextMeta = t("overview.noMessagesMeta");
170
+ const nextHtml = `<div class="empty-state">${t("overview.noMessagesEmpty")}</div>`;
171
+ const renderKey = JSON.stringify({ hint: governanceHint, meta: nextMeta, html: nextHtml });
172
+ if (renderKey !== lastMessagesRenderKey) {
173
+ hintEl.textContent = governanceHint;
174
+ metaEl.textContent = nextMeta;
175
+ listEl.innerHTML = nextHtml;
176
+ lastMessagesRenderKey = renderKey;
177
+ }
44
178
  return;
45
179
  }
46
180
 
@@ -59,16 +193,28 @@ export function createSocialController({
59
193
  seconds: String(Math.floor((governance.send_limit?.window_ms || 60000) / 1000)),
60
194
  })}`
61
195
  : "";
62
- metaEl.textContent = `${baseMeta}${governanceMeta}`;
196
+ const nextMeta = `${baseMeta}${governanceMeta}`;
63
197
 
64
198
  if (!filteredMessages.length) {
65
- listEl.innerHTML = `<div class="empty-state">${t("overview.noMessagesEmpty")}</div>`;
199
+ const nextHtml = `<div class="empty-state">${t("overview.noMessagesEmpty")}</div>`;
200
+ const renderKey = JSON.stringify({ hint: governanceHint, meta: nextMeta, html: nextHtml });
201
+ if (renderKey !== lastMessagesRenderKey) {
202
+ hintEl.textContent = governanceHint;
203
+ metaEl.textContent = nextMeta;
204
+ listEl.innerHTML = nextHtml;
205
+ lastMessagesRenderKey = renderKey;
206
+ }
66
207
  return;
67
208
  }
68
209
 
69
- listEl.innerHTML = filteredMessages
210
+ const nextHtml = filteredMessages
70
211
  .map((item) => {
71
212
  const visibleRemoteCount = getVisibleRemotePublicCount();
213
+ const avatarUrl = String(item.avatar_url || "").trim();
214
+ const displayName = String(item.display_name || t("overview.unnamed")).trim() || t("overview.unnamed");
215
+ const avatar = avatarUrl
216
+ ? `<img class="agent-card__avatar" src="${escapeHtml(avatarUrl)}" alt="${escapeHtml(displayName)}" loading="lazy" />`
217
+ : `<div class="agent-card__avatar-fallback">${escapeHtml((displayName[0] || "?").toUpperCase())}</div>`;
72
218
  const selfStatusChips = item.is_self
73
219
  ? `
74
220
  <span class="tag-chip" style="margin-left:8px;">${t("overview.selfMessagePublished")}</span>
@@ -101,14 +247,17 @@ export function createSocialController({
101
247
  return `
102
248
  <div class="log-item">
103
249
  <div style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
104
- <div>
105
- <strong>${escapeHtml(item.display_name || t("overview.unnamed"))}</strong>
106
- <span class="mono" style="color:#90a2c3; margin-left:8px;">${escapeHtml(shortId(item.agent_id || ""))}</span>
107
- <span class="tag-chip" style="margin-left:8px;">${escapeHtml(item.topic || t("labels.globalTopic"))}</span>
108
- ${item.is_self ? `<span class="tag-chip" style="margin-left:8px;">${t("overview.messageFilterSelf")}</span>` : ""}
109
- <span class="tag-chip" style="margin-left:8px;">${item.online ? t("overview.online") : t("overview.offline")}</span>
110
- ${observationChip}
111
- ${selfStatusChips}
250
+ <div style="display:flex; align-items:flex-start; gap:10px; min-width:0;">
251
+ ${avatar}
252
+ <div style="min-width:0;">
253
+ <strong>${escapeHtml(displayName)}</strong>
254
+ <span class="mono" style="color:#90a2c3; margin-left:8px;">${escapeHtml(shortId(item.agent_id || ""))}</span>
255
+ <span class="tag-chip" style="margin-left:8px;">${escapeHtml(item.topic || t("labels.globalTopic"))}</span>
256
+ ${item.is_self ? `<span class="tag-chip" style="margin-left:8px;">${t("overview.messageFilterSelf")}</span>` : ""}
257
+ <span class="tag-chip" style="margin-left:8px;">${item.online ? t("overview.online") : t("overview.offline")}</span>
258
+ ${observationChip}
259
+ ${selfStatusChips}
260
+ </div>
112
261
  </div>
113
262
  <div class="mono" style="color:#90a2c3;">${new Date(item.created_at).toLocaleString()}</div>
114
263
  </div>
@@ -118,6 +267,25 @@ export function createSocialController({
118
267
  `;
119
268
  })
120
269
  .join("");
270
+ const renderKey = JSON.stringify({
271
+ hint: governanceHint,
272
+ meta: nextMeta,
273
+ filter: socialMessageFilter,
274
+ messages: filteredMessages.map((item) => [
275
+ item.message_id,
276
+ item.updated_at || item.created_at,
277
+ item.remote_observation_count || 0,
278
+ item.online ? 1 : 0,
279
+ ]),
280
+ visibleRemoteCount: getVisibleRemotePublicCount(),
281
+ });
282
+ if (renderKey === lastMessagesRenderKey) {
283
+ return;
284
+ }
285
+ hintEl.textContent = governanceHint;
286
+ metaEl.textContent = nextMeta;
287
+ listEl.innerHTML = nextHtml;
288
+ lastMessagesRenderKey = renderKey;
121
289
  }
122
290
 
123
291
  async function refreshMessages() {
@@ -162,19 +330,29 @@ export function createSocialController({
162
330
  const publicDiscoveryText = status.public_enabled ? t("social.publicDiscoveryEnabled") : t("social.publicDiscoveryDisabled");
163
331
 
164
332
  const namespaceText = `${status.connected_to_silicaclaw ? t("social.connectedToSilicaClaw") : t("social.notConnected")} · ${publicDiscoveryText} · ${t("social.mode")} ${mode}`;
165
- document.getElementById("socialStatusLine").textContent = summaryLine;
166
- document.getElementById("socialStatusSubline").textContent = namespaceText;
333
+ setCachedContent("socialStatusLine", summaryLine, "text");
334
+ setCachedContent("socialStatusSubline", namespaceText, "text");
167
335
  const bar = document.getElementById("integrationStatusBar");
168
- bar.className = `integration-strip ${status.connected_to_silicaclaw && status.public_enabled ? "ok" : "warn"}${getActiveTab() === "overview" ? "" : " hidden"}`;
169
- bar.textContent = t("social.barStatus", {
336
+ const barClassName = `integration-strip ${status.connected_to_silicaclaw && status.public_enabled ? "ok" : "warn"}${getActiveTab() === "overview" ? "" : " hidden"}`;
337
+ const barText = t("social.barStatus", {
170
338
  connected: status.connected_to_silicaclaw ? t("common.yes") : t("common.no"),
171
339
  mode,
172
340
  public: status.public_enabled ? t("common.on") : t("common.off"),
173
341
  });
174
- document.getElementById("brandStatusDot").className = `sidebar-version__status ${status.connected_to_silicaclaw ? "ok" : "warn"}`;
342
+ if (bar.className !== barClassName) {
343
+ bar.className = barClassName;
344
+ }
345
+ if (bar.textContent !== barText) {
346
+ bar.textContent = barText;
347
+ }
348
+ const brandStatusDot = document.getElementById("brandStatusDot");
349
+ const brandStatusClassName = `sidebar-version__status ${status.connected_to_silicaclaw ? "ok" : "warn"}`;
350
+ if (brandStatusDot.className !== brandStatusClassName) {
351
+ brandStatusDot.className = brandStatusClassName;
352
+ }
175
353
  writeUiCache("silicaclaw_ui_social", {
176
- integrationStatusText: bar.textContent,
177
- integrationStatusClassName: bar.className,
354
+ integrationStatusText: barText,
355
+ integrationStatusClassName: barClassName,
178
356
  socialStatusLineText: summaryLine,
179
357
  socialStatusSublineText: namespaceText,
180
358
  });
@@ -182,14 +360,14 @@ export function createSocialController({
182
360
  if (!status.configured && status.configured_reason) reasons.push(t("social.configuredReason", { reason: status.configured_reason }));
183
361
  if (!status.running && status.running_reason) reasons.push(t("social.runningReason", { reason: status.running_reason }));
184
362
  if (!status.discoverable && status.discoverable_reason) reasons.push(t("social.discoverableReasonFull", { reason: status.discoverable_reason }));
185
- document.getElementById("socialStateHint").textContent = reasons.length ? reasons.join(" · ") : t("hints.allIntegrationChecksPassed");
363
+ setCachedContent("socialStateHint", reasons.length ? reasons.join(" · ") : t("hints.allIntegrationChecksPassed"), "text");
186
364
  const modeSelect = document.getElementById("socialModeSelect");
187
365
  const displayedSelectedMode = getSocialModeDirty() && getSocialModePending() ? getSocialModePending() : selectedMode;
188
366
  if (modeSelect && displayedSelectedMode !== "-") modeSelect.value = displayedSelectedMode;
189
367
  renderSocialModeHint(displayedSelectedMode, mode, !!social.network_requires_restart, getSocialModeDirty());
190
368
  setSocialModePendingState(getSocialModeDirty());
191
369
 
192
- document.getElementById("socialPrimaryCards").innerHTML = [
370
+ setCachedContent("socialPrimaryCards", [
193
371
  [t("social.configured"), status.configured ? t("common.yes") : t("common.no")],
194
372
  [t("social.running"), status.running ? t("common.yes") : t("common.no")],
195
373
  [t("social.discoverable"), discoverable ? t("common.yes") : t("common.no")],
@@ -197,9 +375,9 @@ export function createSocialController({
197
375
  [t("social.networkMode"), mode],
198
376
  [t("labels.adapter"), effectiveAdapter],
199
377
  [t("social.discoverableReason"), status.discoverable_reason || "-"],
200
- ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join("");
378
+ ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join(""));
201
379
 
202
- document.getElementById("socialIntegrationCards").innerHTML = [
380
+ setCachedContent("socialIntegrationCards", [
203
381
  [t("social.connected"), bridge.connected_to_silicaclaw ? t("common.yes") : t("common.no")],
204
382
  [t("social.messageBroadcast"), bridge.message_broadcast_enabled ? t("common.on") : t("common.off")],
205
383
  [t("social.displayName"), status.display_name || t("overview.unnamed")],
@@ -207,9 +385,9 @@ export function createSocialController({
207
385
  [t("social.socialFound"), summary.social_md_found ? t("common.yes") : t("common.no")],
208
386
  [t("social.socialSource"), summary.social_md_source_path || "-"],
209
387
  [t("social.reuseOpenClawIdentity"), summary.reused_openclaw_identity ? t("common.yes") : t("common.no")],
210
- ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join("");
388
+ ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join(""));
211
389
 
212
- document.getElementById("socialMessagePathCards").innerHTML = [
390
+ setCachedContent("socialMessagePathCards", [
213
391
  [t("social.messageBroadcast"), bridge.message_broadcast_enabled ? t("common.on") : t("common.off")],
214
392
  [t("social.publicDiscovery"), status.public_enabled ? t("common.on") : t("common.off")],
215
393
  [t("social.namespace"), effectiveNamespace],
@@ -218,7 +396,7 @@ export function createSocialController({
218
396
  [t("network.lastPoll"), networkDiag.last_poll_at ? new Date(networkDiag.last_poll_at).toLocaleTimeString() : "-"],
219
397
  [t("network.lastPublish"), networkDiag.last_publish_at ? new Date(networkDiag.last_publish_at).toLocaleTimeString() : "-"],
220
398
  [t("network.lastError"), networkDiag.last_error || t("network.none")],
221
- ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${escapeHtml(String(v))}</div></div>`).join("");
399
+ ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${escapeHtml(String(v))}</div></div>`).join(""));
222
400
 
223
401
  const skillLearning = bridge.skill_learning || {};
224
402
  const ownerDelivery = bridge.owner_delivery || {};
@@ -246,21 +424,21 @@ export function createSocialController({
246
424
  ownerDeliveryHeadline = t("feedback.openclawRoleNotRunning");
247
425
  ownerDeliveryBody = ownerDelivery.reason || "-";
248
426
  }
249
- document.getElementById("socialOwnerDeliveryStatus").className = `feedback ${ownerDeliveryTone}`;
250
- document.getElementById("socialOwnerDeliveryStatus").textContent = ownerDeliveryHeadline;
251
- document.getElementById("socialOwnerDeliverySubline").textContent = [
427
+ setCachedContent("socialOwnerDeliveryStatus", `feedback ${ownerDeliveryTone}`, "class");
428
+ setCachedContent("socialOwnerDeliveryStatus", ownerDeliveryHeadline, "text");
429
+ setCachedContent("socialOwnerDeliverySubline", [
252
430
  `${t("social.broadcastReadable")}: ${ownerDelivery.bridge_messages_readable ? t("common.yes") : t("common.no")}`,
253
431
  `${t("social.ownerForwardCommand")}: ${ownerDelivery.forward_command_configured ? t("common.yes") : t("common.no")}`,
254
432
  `${t("social.ownerForwardReady")}: ${ownerDelivery.ready ? t("common.yes") : t("common.no")}`,
255
- ].join(" · ");
256
- document.getElementById("socialOwnerDeliveryReason").textContent = ownerDeliveryBody;
257
- document.getElementById("socialCapabilityCards").innerHTML = [
433
+ ].join(" · "), "text");
434
+ setCachedContent("socialOwnerDeliveryReason", ownerDeliveryBody, "text");
435
+ setCachedContent("socialCapabilityCards", [
258
436
  [t("socialCapability.publicBroadcast"), bridge.message_broadcast_enabled ? t("common.yes") : t("common.no")],
259
437
  [t("socialCapability.monitorBroadcasts"), ownerDelivery.bridge_messages_readable ? t("common.yes") : t("common.no")],
260
438
  [t("socialCapability.autoPushToOwner"), ownerDelivery.ready ? t("common.yes") : t("common.no")],
261
439
  [t("socialCapability.ownerPrivateBoundary"), t("socialCapability.ownerPrivateBoundaryValue")],
262
- ].map(([k, v]) => `<div class="card"><div class="label">${escapeHtml(String(k))}</div><div class="value" style="font-size:17px;">${escapeHtml(String(v))}</div></div>`).join("");
263
- document.getElementById("openclawSkillCards").innerHTML = [
440
+ ].map(([k, v]) => `<div class="card"><div class="label">${escapeHtml(String(k))}</div><div class="value" style="font-size:17px;">${escapeHtml(String(v))}</div></div>`).join(""));
441
+ setCachedContent("openclawSkillCards", [
264
442
  [t("social.openclawInstalled"), openclawDetected ? t("common.yes") : t("common.no")],
265
443
  [t("social.running"), openclawRunning ? t("common.yes") : t("common.no")],
266
444
  [t("social.skillInstalled"), skillInstalled ? t("common.yes") : t("common.no")],
@@ -268,16 +446,17 @@ export function createSocialController({
268
446
  [t("social.ownerForwardReady"), ownerDelivery.ready ? t("common.yes") : t("common.no")],
269
447
  [t("social.ownerForwardCommand"), ownerDelivery.forward_command_configured ? t("common.yes") : t("common.no")],
270
448
  [t("social.openclawDetectionMode"), bridge.openclaw_runtime?.detection_mode || "-"],
449
+ ["Gateway probe", bridge.openclaw_runtime?.gateway_probe_ok ? t("common.yes") : t("common.no")],
271
450
  [t("social.openclawGateway"), bridge.openclaw_runtime?.gateway_url || "-"],
272
451
  [t("social.installMode"), skillLearning.install_mode || "-"],
273
452
  [t("social.installedPath"), skillInstalled ? installedSkillPath : "-"],
274
- ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${escapeHtml(v)}</div></div>`).join("");
275
- document.getElementById("openclawSkillPath").textContent = ownerDelivery.forward_command
453
+ ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${escapeHtml(v)}</div></div>`).join(""));
454
+ setCachedContent("openclawSkillPath", ownerDelivery.forward_command
276
455
  ? `${ownerDelivery.forward_command}${ownerDelivery.owner_channel ? ` · ${ownerDelivery.owner_channel}` : ""}${ownerDelivery.owner_target ? ` · ${ownerDelivery.owner_target}` : ""}`
277
456
  : skillInstalled
278
457
  ? installedSkillPath
279
- : `${installAction.recommended_command || "-"}${bridge.openclaw_runtime?.gateway_url ? ` · detect ${bridge.openclaw_runtime.gateway_url}` : ""}`;
280
- document.getElementById("openclawSkillHint").textContent = !openclawDetected
458
+ : `${installAction.recommended_command || "-"}${bridge.openclaw_runtime?.gateway_url ? ` · detect ${bridge.openclaw_runtime.gateway_url}` : ""}`, "text");
459
+ setCachedContent("openclawSkillHint", !openclawDetected
281
460
  ? t("feedback.openclawRoleBroadcasterOnly")
282
461
  : !openclawRunning
283
462
  ? t("feedback.openclawRoleNotRunning")
@@ -285,11 +464,11 @@ export function createSocialController({
285
464
  ? t("feedback.openclawRoleReadyToLearn")
286
465
  : ownerDelivery.ready
287
466
  ? t("feedback.openclawRoleOwnerReady")
288
- : ownerDelivery.bridge_messages_readable && !ownerDelivery.forward_command_configured
289
- ? t("feedback.openclawRoleLearningOnly")
290
- : ownerDelivery.bridge_messages_readable
291
- ? t("feedback.openclawRoleNeedsOwnerRoute")
292
- : t("feedback.openclawRoleLearned");
467
+ : ownerDelivery.bridge_messages_readable && !ownerDelivery.forward_command_configured
468
+ ? t("feedback.openclawRoleLearningOnly")
469
+ : ownerDelivery.bridge_messages_readable
470
+ ? t("feedback.openclawRoleNeedsOwnerRoute")
471
+ : t("feedback.openclawRoleLearned"), "text");
293
472
  const skillInstallBtn = document.getElementById("openclawSkillInstallBtn");
294
473
  skillInstallBtn.textContent = !openclawDetected
295
474
  ? t("actions.openclawNotInstalled")
@@ -310,33 +489,33 @@ export function createSocialController({
310
489
  document.getElementById("governanceDuplicateWindowInput").value = String(Math.floor((policy.duplicate_window_ms ?? 180000) / 1000));
311
490
  document.getElementById("governanceBlockedAgentsInput").value = blockedAgentIds.join(", ");
312
491
  document.getElementById("governanceBlockedTermsInput").value = blockedTerms.join(", ");
313
- document.getElementById("socialGovernanceCards").innerHTML = [
492
+ setCachedContent("socialGovernanceCards", [
314
493
  [t("labels.sendLimit"), `${policy.send_limit?.max ?? "-"} / ${Math.floor((policy.send_limit?.window_ms ?? 60000) / 1000)}s`],
315
494
  [t("labels.receiveLimit"), `${policy.receive_limit?.max ?? "-"} / ${Math.floor((policy.receive_limit?.window_ms ?? 60000) / 1000)}s`],
316
495
  [t("labels.duplicateWindowSeconds"), `${Math.floor((policy.duplicate_window_ms ?? 0) / 1000)}s`],
317
496
  [t("labels.blockedAgentIds"), blockedAgentIds.length],
318
497
  [t("labels.blockedTerms"), blockedTerms.length],
319
- ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join("");
498
+ ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join(""));
320
499
 
321
500
  const moderationEvents = Array.isArray(governance.recent_events) ? governance.recent_events : [];
322
- document.getElementById("socialModerationList").innerHTML = moderationEvents.length === 0
501
+ setCachedContent("socialModerationList", moderationEvents.length === 0
323
502
  ? `<div class="empty-state">${t("network.noModerationEvents")}</div>`
324
503
  : moderationEvents.map((event) => `
325
504
  <div class="log-item">
326
505
  <div class="log-${event.level || "warn"}">[${String(event.level || "warn").toUpperCase()}] ${escapeHtml(event.message || "-")}</div>
327
506
  <div class="mono" style="color:#90a2c3;">${new Date(event.timestamp).toLocaleString()}</div>
328
507
  </div>
329
- `).join("");
508
+ `).join(""));
330
509
 
331
- document.getElementById("socialAdvancedCards").innerHTML = [
510
+ setCachedContent("socialAdvancedCards", [
332
511
  [t("labels.adapter"), effectiveAdapter],
333
512
  [t("social.namespace"), effectiveNamespace],
334
513
  [t("labels.room"), effectiveRoom],
335
514
  [t("social.bridgeStatus"), bridge.connected_to_silicaclaw ? t("common.yes") : t("common.no")],
336
515
  [t("social.messageBroadcast"), bridge.message_broadcast_enabled ? t("common.on") : t("common.off")],
337
516
  [t("social.restartRequired"), social.network_requires_restart ? t("common.yes") : t("common.no")],
338
- ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join("");
339
- document.getElementById("socialAdvancedWrap").textContent = toPrettyJson({
517
+ ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join(""));
518
+ setCachedContent("socialAdvancedWrap", toPrettyJson({
340
519
  effective_runtime: {
341
520
  mode: effectiveMode,
342
521
  adapter: effectiveAdapter,
@@ -358,17 +537,17 @@ export function createSocialController({
358
537
  social_md_found: summary.social_md_found,
359
538
  social_md_source_path: summary.social_md_source_path,
360
539
  },
361
- });
540
+ }), "text");
362
541
 
363
- document.getElementById("socialSourceWrap").textContent = toPrettyJson({
542
+ setCachedContent("socialSourceWrap", toPrettyJson({
364
543
  found: social.found,
365
544
  source_path: social.source_path,
366
545
  parse_error: social.parse_error,
367
- });
368
- document.getElementById("socialRawWrap").textContent = toPrettyJson({
546
+ }), "text");
547
+ setCachedContent("socialRawWrap", toPrettyJson({
369
548
  raw_frontmatter: social.raw_frontmatter || null,
370
- });
371
- document.getElementById("socialRuntimeWrap").textContent = toPrettyJson(runtime);
549
+ }), "text");
550
+ setCachedContent("socialRuntimeWrap", toPrettyJson(runtime), "text");
372
551
  }
373
552
 
374
553
  async function exportSocialTemplate() {
@@ -383,23 +562,43 @@ export function createSocialController({
383
562
  const logLevelFilter = getLogLevelFilter();
384
563
  const el = document.getElementById("logList");
385
564
  if (!logsCache.length) {
386
- el.innerHTML = `<div class="empty-state">${t("network.noLogsYet")}</div>`;
565
+ const nextHtml = `<div class="empty-state">${t("network.noLogsYet")}</div>`;
566
+ if (nextHtml !== lastLogsRenderKey) {
567
+ el.innerHTML = nextHtml;
568
+ lastLogsRenderKey = nextHtml;
569
+ }
387
570
  return;
388
571
  }
389
572
  const filtered = logLevelFilter === "all" ? logsCache : logsCache.filter((item) => String(item.level || "").toLowerCase() === logLevelFilter);
390
573
  if (!filtered.length) {
391
- el.innerHTML = `<div class="empty-state">${t("network.noLogsForLevel", { level: logLevelFilter })}</div>`;
574
+ const nextHtml = `<div class="empty-state">${t("network.noLogsForLevel", { level: logLevelFilter })}</div>`;
575
+ if (nextHtml !== lastLogsRenderKey) {
576
+ el.innerHTML = nextHtml;
577
+ lastLogsRenderKey = nextHtml;
578
+ }
392
579
  return;
393
580
  }
394
- el.innerHTML = filtered.map((item) => `
581
+ const nextHtml = filtered.map((item) => `
395
582
  <div class="log-item">
396
583
  <div class="log-${item.level}">[${String(item.level).toUpperCase()}] ${item.message}</div>
397
584
  <div class="mono" style="color:#90a2c3;">${new Date(item.timestamp).toLocaleString()}</div>
398
585
  </div>
399
586
  `).join("");
587
+ const renderKey = JSON.stringify({
588
+ level: logLevelFilter,
589
+ items: filtered.map((item) => [item.timestamp, item.level, item.message]),
590
+ });
591
+ if (renderKey === lastLogsRenderKey) {
592
+ return;
593
+ }
594
+ el.innerHTML = nextHtml;
595
+ lastLogsRenderKey = renderKey;
400
596
  }
401
597
 
402
598
  async function refreshLogs() {
599
+ if (getActiveTab() !== "network") {
600
+ return;
601
+ }
403
602
  setLogsCache((await api("/api/logs")).data || []);
404
603
  renderLogs();
405
604
  }
@@ -425,8 +624,8 @@ export function createSocialController({
425
624
  <div class="skill-card">
426
625
  <div class="skill-card__top">
427
626
  <div>
428
- <div class="skill-card__eyebrow">${escapeHtml(options.eyebrow || skill.install_mode || "skill")}</div>
429
- <div class="skill-card__title">${escapeHtml(skill.display_name || skill.name || "Skill")}</div>
627
+ <div class="skill-card__eyebrow">${escapeHtml(skillModeLabel(options.eyebrow || skill.install_mode || "skill"))}</div>
628
+ <div class="skill-card__title">${escapeHtml(skill.display_name || skill.name || t("labels.skillsModeGeneric"))}</div>
430
629
  </div>
431
630
  <div class="skill-card__version mono">${escapeHtml(versionText)}</div>
432
631
  </div>
@@ -476,7 +675,7 @@ export function createSocialController({
476
675
  <div class="skill-card">
477
676
  <div class="skill-card__top">
478
677
  <div>
479
- <div class="skill-card__eyebrow">${escapeHtml(skill.display_name || skill.name || "Skill")}</div>
678
+ <div class="skill-card__eyebrow">${escapeHtml(skill.display_name || skill.name || t("labels.skillsModeGeneric"))}</div>
480
679
  <div class="skill-card__title">${escapeHtml(t("labels.skillsDialogueExamples"))}</div>
481
680
  </div>
482
681
  <div class="skill-card__version mono">${escapeHtml(skill.version || "-")}</div>
@@ -604,36 +803,57 @@ export function createSocialController({
604
803
  `).join("")
605
804
  : `<div class="skills-empty">${t("hints.skillsNoBundled")}</div>`;
606
805
 
607
- document.getElementById("skillsBundledGrid").innerHTML = bundledSorted.length
608
- ? bundledSorted.map((skill) => renderSkillCard(skill, {
609
- eyebrow: skill.install_mode === "workspace" || skill.install_mode === "legacy" ? skill.install_mode : "bundled",
610
- statusText: skill.update_available
611
- ? t("labels.skillsUpdateAvailable")
612
- : skill.installed_in_openclaw
613
- ? `${t("labels.skillsStatus")}: ${t("common.yes")}`
614
- : t("hints.skillsNotInstalled"),
615
- installable: true,
616
- updateAvailable: skill.update_available,
617
- installedVersion: skill.installed_version,
618
- bundledVersion: skill.version,
619
- })).join("")
620
- : `<div class="skills-empty">${t("hints.skillsNoBundled")}</div>`;
806
+ const bundledMatchCount = renderFilteredSkillCards({
807
+ skills: bundledSorted,
808
+ section: "bundled",
809
+ gridId: "skillsBundledGrid",
810
+ footerId: "skillsBundledFooter",
811
+ limit: SKILLS_SECTION_LIMIT,
812
+ renderer: (skill) => renderSkillCard(skill, {
813
+ eyebrow: skill.install_mode === "workspace" || skill.install_mode === "legacy" ? skill.install_mode : "bundled",
814
+ statusText: skill.update_available
815
+ ? t("labels.skillsUpdateAvailable")
816
+ : skill.installed_in_openclaw
817
+ ? `${t("labels.skillsStatus")}: ${t("common.yes")}`
818
+ : t("hints.skillsNotInstalled"),
819
+ installable: true,
820
+ updateAvailable: skill.update_available,
821
+ installedVersion: skill.installed_version,
822
+ bundledVersion: skill.version,
823
+ }),
824
+ });
825
+
826
+ const installedMatchCount = renderFilteredSkillCards({
827
+ skills: installedSorted,
828
+ section: "installed",
829
+ gridId: "skillsInstalledGrid",
830
+ footerId: "skillsInstalledFooter",
831
+ limit: SKILLS_SECTION_LIMIT,
832
+ renderer: (skill) => renderSkillCard(skill, {
833
+ eyebrow: skill.install_mode || "installed",
834
+ statusText: skill.update_available
835
+ ? t("labels.skillsUpdateAvailable")
836
+ : `${t("labels.skillsStatus")}: ${escapeHtml(skillModeLabel(skill.install_mode || "installed"))}`,
837
+ updateAvailable: skill.update_available,
838
+ installedVersion: skill.version,
839
+ bundledVersion: skill.bundled_version,
840
+ }),
841
+ });
842
+
843
+ const dialogueMatchCount = renderFilteredSkillCards({
844
+ skills: featuredSkills,
845
+ section: "dialogue",
846
+ gridId: "skillsDialogueGrid",
847
+ footerId: "skillsDialogueFooter",
848
+ limit: SKILLS_DIALOGUE_LIMIT,
849
+ renderer: (skill) => renderDialogueCard(skill),
850
+ });
621
851
 
622
- document.getElementById("skillsInstalledGrid").innerHTML = installedSorted.length
623
- ? installedSorted.map((skill) => renderSkillCard(skill, {
624
- eyebrow: skill.install_mode || "installed",
625
- statusText: skill.update_available
626
- ? t("labels.skillsUpdateAvailable")
627
- : `${t("labels.skillsStatus")}: ${escapeHtml(String(skill.install_mode || "-"))}`,
628
- updateAvailable: skill.update_available,
629
- installedVersion: skill.version,
630
- bundledVersion: skill.bundled_version,
631
- })).join("")
632
- : `<div class="skills-empty">${t("hints.skillsNoInstalled")}</div>`;
633
-
634
- document.getElementById("skillsDialogueGrid").innerHTML = featuredSkills.length
635
- ? featuredSkills.map((skill) => renderDialogueCard(skill)).join("")
636
- : `<div class="skills-empty">${t("hints.skillsNoDialogueExamples")}</div>`;
852
+ renderSkillsFilterMeta({
853
+ bundledCount: bundledMatchCount,
854
+ installedCount: installedMatchCount,
855
+ dialogueCount: dialogueMatchCount,
856
+ });
637
857
 
638
858
  const installBtn = document.getElementById("skillsInstallBtn");
639
859
  installBtn.textContent = !openclawDetected
@@ -659,6 +879,9 @@ export function createSocialController({
659
879
  refreshSocial,
660
880
  renderLogs,
661
881
  renderSocialMessages,
882
+ setSkillsFilter,
883
+ setSkillsQuery,
662
884
  setLogLevelFilter,
885
+ toggleSkillsExpanded,
663
886
  };
664
887
  }