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