@silicaclaw/cli 2026.3.20-3 → 2026.3.20-4

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.
@@ -26,6 +26,9 @@ export function createSocialController({
26
26
  }) {
27
27
  const SKILLS_SECTION_LIMIT = 4;
28
28
  const SKILLS_DIALOGUE_LIMIT = 1;
29
+ let lastMessagesRenderKey = "";
30
+ let lastLogsRenderKey = "";
31
+ const sectionRenderCache = new Map();
29
32
  let skillsQuery = "";
30
33
  let skillsFilter = "all";
31
34
  const skillsExpanded = {
@@ -132,6 +135,23 @@ export function createSocialController({
132
135
  });
133
136
  }
134
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
+
135
155
  function renderSocialMessages() {
136
156
  const listEl = document.getElementById("socialMessageList");
137
157
  const metaEl = document.getElementById("socialMessageMeta");
@@ -145,10 +165,16 @@ export function createSocialController({
145
165
  seconds: String(Math.floor((governance.send_limit?.window_ms || 60000) / 1000)),
146
166
  })}`
147
167
  : t("overview.messageHint");
148
- hintEl.textContent = governanceHint;
149
168
  if (!socialMessagesCache.length) {
150
- metaEl.textContent = t("overview.noMessagesMeta");
151
- 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
+ }
152
178
  return;
153
179
  }
154
180
 
@@ -167,14 +193,21 @@ export function createSocialController({
167
193
  seconds: String(Math.floor((governance.send_limit?.window_ms || 60000) / 1000)),
168
194
  })}`
169
195
  : "";
170
- metaEl.textContent = `${baseMeta}${governanceMeta}`;
196
+ const nextMeta = `${baseMeta}${governanceMeta}`;
171
197
 
172
198
  if (!filteredMessages.length) {
173
- 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
+ }
174
207
  return;
175
208
  }
176
209
 
177
- listEl.innerHTML = filteredMessages
210
+ const nextHtml = filteredMessages
178
211
  .map((item) => {
179
212
  const visibleRemoteCount = getVisibleRemotePublicCount();
180
213
  const avatarUrl = String(item.avatar_url || "").trim();
@@ -234,6 +267,25 @@ export function createSocialController({
234
267
  `;
235
268
  })
236
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;
237
289
  }
238
290
 
239
291
  async function refreshMessages() {
@@ -278,19 +330,29 @@ export function createSocialController({
278
330
  const publicDiscoveryText = status.public_enabled ? t("social.publicDiscoveryEnabled") : t("social.publicDiscoveryDisabled");
279
331
 
280
332
  const namespaceText = `${status.connected_to_silicaclaw ? t("social.connectedToSilicaClaw") : t("social.notConnected")} · ${publicDiscoveryText} · ${t("social.mode")} ${mode}`;
281
- document.getElementById("socialStatusLine").textContent = summaryLine;
282
- document.getElementById("socialStatusSubline").textContent = namespaceText;
333
+ setCachedContent("socialStatusLine", summaryLine, "text");
334
+ setCachedContent("socialStatusSubline", namespaceText, "text");
283
335
  const bar = document.getElementById("integrationStatusBar");
284
- bar.className = `integration-strip ${status.connected_to_silicaclaw && status.public_enabled ? "ok" : "warn"}${getActiveTab() === "overview" ? "" : " hidden"}`;
285
- 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", {
286
338
  connected: status.connected_to_silicaclaw ? t("common.yes") : t("common.no"),
287
339
  mode,
288
340
  public: status.public_enabled ? t("common.on") : t("common.off"),
289
341
  });
290
- 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
+ }
291
353
  writeUiCache("silicaclaw_ui_social", {
292
- integrationStatusText: bar.textContent,
293
- integrationStatusClassName: bar.className,
354
+ integrationStatusText: barText,
355
+ integrationStatusClassName: barClassName,
294
356
  socialStatusLineText: summaryLine,
295
357
  socialStatusSublineText: namespaceText,
296
358
  });
@@ -298,14 +360,14 @@ export function createSocialController({
298
360
  if (!status.configured && status.configured_reason) reasons.push(t("social.configuredReason", { reason: status.configured_reason }));
299
361
  if (!status.running && status.running_reason) reasons.push(t("social.runningReason", { reason: status.running_reason }));
300
362
  if (!status.discoverable && status.discoverable_reason) reasons.push(t("social.discoverableReasonFull", { reason: status.discoverable_reason }));
301
- document.getElementById("socialStateHint").textContent = reasons.length ? reasons.join(" · ") : t("hints.allIntegrationChecksPassed");
363
+ setCachedContent("socialStateHint", reasons.length ? reasons.join(" · ") : t("hints.allIntegrationChecksPassed"), "text");
302
364
  const modeSelect = document.getElementById("socialModeSelect");
303
365
  const displayedSelectedMode = getSocialModeDirty() && getSocialModePending() ? getSocialModePending() : selectedMode;
304
366
  if (modeSelect && displayedSelectedMode !== "-") modeSelect.value = displayedSelectedMode;
305
367
  renderSocialModeHint(displayedSelectedMode, mode, !!social.network_requires_restart, getSocialModeDirty());
306
368
  setSocialModePendingState(getSocialModeDirty());
307
369
 
308
- document.getElementById("socialPrimaryCards").innerHTML = [
370
+ setCachedContent("socialPrimaryCards", [
309
371
  [t("social.configured"), status.configured ? t("common.yes") : t("common.no")],
310
372
  [t("social.running"), status.running ? t("common.yes") : t("common.no")],
311
373
  [t("social.discoverable"), discoverable ? t("common.yes") : t("common.no")],
@@ -313,9 +375,9 @@ export function createSocialController({
313
375
  [t("social.networkMode"), mode],
314
376
  [t("labels.adapter"), effectiveAdapter],
315
377
  [t("social.discoverableReason"), status.discoverable_reason || "-"],
316
- ].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(""));
317
379
 
318
- document.getElementById("socialIntegrationCards").innerHTML = [
380
+ setCachedContent("socialIntegrationCards", [
319
381
  [t("social.connected"), bridge.connected_to_silicaclaw ? t("common.yes") : t("common.no")],
320
382
  [t("social.messageBroadcast"), bridge.message_broadcast_enabled ? t("common.on") : t("common.off")],
321
383
  [t("social.displayName"), status.display_name || t("overview.unnamed")],
@@ -323,9 +385,9 @@ export function createSocialController({
323
385
  [t("social.socialFound"), summary.social_md_found ? t("common.yes") : t("common.no")],
324
386
  [t("social.socialSource"), summary.social_md_source_path || "-"],
325
387
  [t("social.reuseOpenClawIdentity"), summary.reused_openclaw_identity ? t("common.yes") : t("common.no")],
326
- ].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(""));
327
389
 
328
- document.getElementById("socialMessagePathCards").innerHTML = [
390
+ setCachedContent("socialMessagePathCards", [
329
391
  [t("social.messageBroadcast"), bridge.message_broadcast_enabled ? t("common.on") : t("common.off")],
330
392
  [t("social.publicDiscovery"), status.public_enabled ? t("common.on") : t("common.off")],
331
393
  [t("social.namespace"), effectiveNamespace],
@@ -334,7 +396,7 @@ export function createSocialController({
334
396
  [t("network.lastPoll"), networkDiag.last_poll_at ? new Date(networkDiag.last_poll_at).toLocaleTimeString() : "-"],
335
397
  [t("network.lastPublish"), networkDiag.last_publish_at ? new Date(networkDiag.last_publish_at).toLocaleTimeString() : "-"],
336
398
  [t("network.lastError"), networkDiag.last_error || t("network.none")],
337
- ].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(""));
338
400
 
339
401
  const skillLearning = bridge.skill_learning || {};
340
402
  const ownerDelivery = bridge.owner_delivery || {};
@@ -362,21 +424,21 @@ export function createSocialController({
362
424
  ownerDeliveryHeadline = t("feedback.openclawRoleNotRunning");
363
425
  ownerDeliveryBody = ownerDelivery.reason || "-";
364
426
  }
365
- document.getElementById("socialOwnerDeliveryStatus").className = `feedback ${ownerDeliveryTone}`;
366
- document.getElementById("socialOwnerDeliveryStatus").textContent = ownerDeliveryHeadline;
367
- document.getElementById("socialOwnerDeliverySubline").textContent = [
427
+ setCachedContent("socialOwnerDeliveryStatus", `feedback ${ownerDeliveryTone}`, "class");
428
+ setCachedContent("socialOwnerDeliveryStatus", ownerDeliveryHeadline, "text");
429
+ setCachedContent("socialOwnerDeliverySubline", [
368
430
  `${t("social.broadcastReadable")}: ${ownerDelivery.bridge_messages_readable ? t("common.yes") : t("common.no")}`,
369
431
  `${t("social.ownerForwardCommand")}: ${ownerDelivery.forward_command_configured ? t("common.yes") : t("common.no")}`,
370
432
  `${t("social.ownerForwardReady")}: ${ownerDelivery.ready ? t("common.yes") : t("common.no")}`,
371
- ].join(" · ");
372
- document.getElementById("socialOwnerDeliveryReason").textContent = ownerDeliveryBody;
373
- document.getElementById("socialCapabilityCards").innerHTML = [
433
+ ].join(" · "), "text");
434
+ setCachedContent("socialOwnerDeliveryReason", ownerDeliveryBody, "text");
435
+ setCachedContent("socialCapabilityCards", [
374
436
  [t("socialCapability.publicBroadcast"), bridge.message_broadcast_enabled ? t("common.yes") : t("common.no")],
375
437
  [t("socialCapability.monitorBroadcasts"), ownerDelivery.bridge_messages_readable ? t("common.yes") : t("common.no")],
376
438
  [t("socialCapability.autoPushToOwner"), ownerDelivery.ready ? t("common.yes") : t("common.no")],
377
439
  [t("socialCapability.ownerPrivateBoundary"), t("socialCapability.ownerPrivateBoundaryValue")],
378
- ].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("");
379
- 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", [
380
442
  [t("social.openclawInstalled"), openclawDetected ? t("common.yes") : t("common.no")],
381
443
  [t("social.running"), openclawRunning ? t("common.yes") : t("common.no")],
382
444
  [t("social.skillInstalled"), skillInstalled ? t("common.yes") : t("common.no")],
@@ -387,13 +449,13 @@ export function createSocialController({
387
449
  [t("social.openclawGateway"), bridge.openclaw_runtime?.gateway_url || "-"],
388
450
  [t("social.installMode"), skillLearning.install_mode || "-"],
389
451
  [t("social.installedPath"), skillInstalled ? installedSkillPath : "-"],
390
- ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${escapeHtml(v)}</div></div>`).join("");
391
- document.getElementById("openclawSkillPath").textContent = ownerDelivery.forward_command
452
+ ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${escapeHtml(v)}</div></div>`).join(""));
453
+ setCachedContent("openclawSkillPath", ownerDelivery.forward_command
392
454
  ? `${ownerDelivery.forward_command}${ownerDelivery.owner_channel ? ` · ${ownerDelivery.owner_channel}` : ""}${ownerDelivery.owner_target ? ` · ${ownerDelivery.owner_target}` : ""}`
393
455
  : skillInstalled
394
456
  ? installedSkillPath
395
- : `${installAction.recommended_command || "-"}${bridge.openclaw_runtime?.gateway_url ? ` · detect ${bridge.openclaw_runtime.gateway_url}` : ""}`;
396
- document.getElementById("openclawSkillHint").textContent = !openclawDetected
457
+ : `${installAction.recommended_command || "-"}${bridge.openclaw_runtime?.gateway_url ? ` · detect ${bridge.openclaw_runtime.gateway_url}` : ""}`, "text");
458
+ setCachedContent("openclawSkillHint", !openclawDetected
397
459
  ? t("feedback.openclawRoleBroadcasterOnly")
398
460
  : !openclawRunning
399
461
  ? t("feedback.openclawRoleNotRunning")
@@ -401,11 +463,11 @@ export function createSocialController({
401
463
  ? t("feedback.openclawRoleReadyToLearn")
402
464
  : ownerDelivery.ready
403
465
  ? t("feedback.openclawRoleOwnerReady")
404
- : ownerDelivery.bridge_messages_readable && !ownerDelivery.forward_command_configured
405
- ? t("feedback.openclawRoleLearningOnly")
406
- : ownerDelivery.bridge_messages_readable
407
- ? t("feedback.openclawRoleNeedsOwnerRoute")
408
- : t("feedback.openclawRoleLearned");
466
+ : ownerDelivery.bridge_messages_readable && !ownerDelivery.forward_command_configured
467
+ ? t("feedback.openclawRoleLearningOnly")
468
+ : ownerDelivery.bridge_messages_readable
469
+ ? t("feedback.openclawRoleNeedsOwnerRoute")
470
+ : t("feedback.openclawRoleLearned"), "text");
409
471
  const skillInstallBtn = document.getElementById("openclawSkillInstallBtn");
410
472
  skillInstallBtn.textContent = !openclawDetected
411
473
  ? t("actions.openclawNotInstalled")
@@ -426,33 +488,33 @@ export function createSocialController({
426
488
  document.getElementById("governanceDuplicateWindowInput").value = String(Math.floor((policy.duplicate_window_ms ?? 180000) / 1000));
427
489
  document.getElementById("governanceBlockedAgentsInput").value = blockedAgentIds.join(", ");
428
490
  document.getElementById("governanceBlockedTermsInput").value = blockedTerms.join(", ");
429
- document.getElementById("socialGovernanceCards").innerHTML = [
491
+ setCachedContent("socialGovernanceCards", [
430
492
  [t("labels.sendLimit"), `${policy.send_limit?.max ?? "-"} / ${Math.floor((policy.send_limit?.window_ms ?? 60000) / 1000)}s`],
431
493
  [t("labels.receiveLimit"), `${policy.receive_limit?.max ?? "-"} / ${Math.floor((policy.receive_limit?.window_ms ?? 60000) / 1000)}s`],
432
494
  [t("labels.duplicateWindowSeconds"), `${Math.floor((policy.duplicate_window_ms ?? 0) / 1000)}s`],
433
495
  [t("labels.blockedAgentIds"), blockedAgentIds.length],
434
496
  [t("labels.blockedTerms"), blockedTerms.length],
435
- ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join("");
497
+ ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join(""));
436
498
 
437
499
  const moderationEvents = Array.isArray(governance.recent_events) ? governance.recent_events : [];
438
- document.getElementById("socialModerationList").innerHTML = moderationEvents.length === 0
500
+ setCachedContent("socialModerationList", moderationEvents.length === 0
439
501
  ? `<div class="empty-state">${t("network.noModerationEvents")}</div>`
440
502
  : moderationEvents.map((event) => `
441
503
  <div class="log-item">
442
504
  <div class="log-${event.level || "warn"}">[${String(event.level || "warn").toUpperCase()}] ${escapeHtml(event.message || "-")}</div>
443
505
  <div class="mono" style="color:#90a2c3;">${new Date(event.timestamp).toLocaleString()}</div>
444
506
  </div>
445
- `).join("");
507
+ `).join(""));
446
508
 
447
- document.getElementById("socialAdvancedCards").innerHTML = [
509
+ setCachedContent("socialAdvancedCards", [
448
510
  [t("labels.adapter"), effectiveAdapter],
449
511
  [t("social.namespace"), effectiveNamespace],
450
512
  [t("labels.room"), effectiveRoom],
451
513
  [t("social.bridgeStatus"), bridge.connected_to_silicaclaw ? t("common.yes") : t("common.no")],
452
514
  [t("social.messageBroadcast"), bridge.message_broadcast_enabled ? t("common.on") : t("common.off")],
453
515
  [t("social.restartRequired"), social.network_requires_restart ? t("common.yes") : t("common.no")],
454
- ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join("");
455
- document.getElementById("socialAdvancedWrap").textContent = toPrettyJson({
516
+ ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join(""));
517
+ setCachedContent("socialAdvancedWrap", toPrettyJson({
456
518
  effective_runtime: {
457
519
  mode: effectiveMode,
458
520
  adapter: effectiveAdapter,
@@ -474,17 +536,17 @@ export function createSocialController({
474
536
  social_md_found: summary.social_md_found,
475
537
  social_md_source_path: summary.social_md_source_path,
476
538
  },
477
- });
539
+ }), "text");
478
540
 
479
- document.getElementById("socialSourceWrap").textContent = toPrettyJson({
541
+ setCachedContent("socialSourceWrap", toPrettyJson({
480
542
  found: social.found,
481
543
  source_path: social.source_path,
482
544
  parse_error: social.parse_error,
483
- });
484
- document.getElementById("socialRawWrap").textContent = toPrettyJson({
545
+ }), "text");
546
+ setCachedContent("socialRawWrap", toPrettyJson({
485
547
  raw_frontmatter: social.raw_frontmatter || null,
486
- });
487
- document.getElementById("socialRuntimeWrap").textContent = toPrettyJson(runtime);
548
+ }), "text");
549
+ setCachedContent("socialRuntimeWrap", toPrettyJson(runtime), "text");
488
550
  }
489
551
 
490
552
  async function exportSocialTemplate() {
@@ -499,23 +561,43 @@ export function createSocialController({
499
561
  const logLevelFilter = getLogLevelFilter();
500
562
  const el = document.getElementById("logList");
501
563
  if (!logsCache.length) {
502
- el.innerHTML = `<div class="empty-state">${t("network.noLogsYet")}</div>`;
564
+ const nextHtml = `<div class="empty-state">${t("network.noLogsYet")}</div>`;
565
+ if (nextHtml !== lastLogsRenderKey) {
566
+ el.innerHTML = nextHtml;
567
+ lastLogsRenderKey = nextHtml;
568
+ }
503
569
  return;
504
570
  }
505
571
  const filtered = logLevelFilter === "all" ? logsCache : logsCache.filter((item) => String(item.level || "").toLowerCase() === logLevelFilter);
506
572
  if (!filtered.length) {
507
- el.innerHTML = `<div class="empty-state">${t("network.noLogsForLevel", { level: logLevelFilter })}</div>`;
573
+ const nextHtml = `<div class="empty-state">${t("network.noLogsForLevel", { level: logLevelFilter })}</div>`;
574
+ if (nextHtml !== lastLogsRenderKey) {
575
+ el.innerHTML = nextHtml;
576
+ lastLogsRenderKey = nextHtml;
577
+ }
508
578
  return;
509
579
  }
510
- el.innerHTML = filtered.map((item) => `
580
+ const nextHtml = filtered.map((item) => `
511
581
  <div class="log-item">
512
582
  <div class="log-${item.level}">[${String(item.level).toUpperCase()}] ${item.message}</div>
513
583
  <div class="mono" style="color:#90a2c3;">${new Date(item.timestamp).toLocaleString()}</div>
514
584
  </div>
515
585
  `).join("");
586
+ const renderKey = JSON.stringify({
587
+ level: logLevelFilter,
588
+ items: filtered.map((item) => [item.timestamp, item.level, item.message]),
589
+ });
590
+ if (renderKey === lastLogsRenderKey) {
591
+ return;
592
+ }
593
+ el.innerHTML = nextHtml;
594
+ lastLogsRenderKey = renderKey;
516
595
  }
517
596
 
518
597
  async function refreshLogs() {
598
+ if (getActiveTab() !== "network") {
599
+ return;
600
+ }
519
601
  setLogsCache((await api("/api/logs")).data || []);
520
602
  renderLogs();
521
603
  }
@@ -586,6 +586,54 @@
586
586
  overflow: hidden;
587
587
  text-overflow: ellipsis;
588
588
  }
589
+ .sidebar-version__hint {
590
+ font-size: 10px;
591
+ line-height: 1.2;
592
+ color: var(--muted);
593
+ white-space: nowrap;
594
+ overflow: hidden;
595
+ text-overflow: ellipsis;
596
+ }
597
+ .sidebar-version__actions {
598
+ display: flex;
599
+ align-items: center;
600
+ gap: 10px;
601
+ flex: 0 0 auto;
602
+ }
603
+ .sidebar-version__btn {
604
+ appearance: none;
605
+ border: 1px solid color-mix(in srgb, var(--accent) 38%, var(--border));
606
+ background: color-mix(in srgb, var(--accent) 16%, transparent);
607
+ color: var(--text-strong);
608
+ border-radius: 999px;
609
+ padding: 6px 10px;
610
+ font-size: 11px;
611
+ line-height: 1;
612
+ font-weight: 700;
613
+ cursor: pointer;
614
+ transition: background .18s ease, border-color .18s ease, opacity .18s ease;
615
+ }
616
+ .sidebar-version__btn:hover:not(:disabled) {
617
+ background: color-mix(in srgb, var(--accent) 22%, transparent);
618
+ border-color: color-mix(in srgb, var(--accent) 52%, var(--border));
619
+ }
620
+ .sidebar-version__btn--ghost {
621
+ background: transparent;
622
+ border-color: color-mix(in srgb, var(--border) 88%, transparent);
623
+ color: var(--muted);
624
+ }
625
+ .sidebar-version__btn--ghost:hover:not(:disabled) {
626
+ background: color-mix(in srgb, var(--bg-elevated) 72%, transparent);
627
+ border-color: color-mix(in srgb, var(--border) 100%, transparent);
628
+ }
629
+ .sidebar-version__btn:disabled {
630
+ opacity: .55;
631
+ cursor: wait;
632
+ }
633
+ .sidebar-version__btn.hidden,
634
+ .sidebar-version__hint.hidden {
635
+ display: none;
636
+ }
589
637
  .sidebar-version__status {
590
638
  width: 8px;
591
639
  height: 8px;
@@ -609,6 +657,9 @@
609
657
  .app.nav-collapsed .sidebar-version__copy {
610
658
  display: none;
611
659
  }
660
+ .app.nav-collapsed .sidebar-version__btn {
661
+ display: none;
662
+ }
612
663
 
613
664
  .main {
614
665
  display: flex;
@@ -100,8 +100,13 @@ export const appTemplate = String.raw`<div class="app" id="appShell">
100
100
  <div class="sidebar-version__copy">
101
101
  <span class="sidebar-version__label">Version</span>
102
102
  <span class="sidebar-version__text" id="brandVersion">-</span>
103
+ <span class="sidebar-version__hint" id="brandUpdateHint">Checking for updates...</span>
104
+ </div>
105
+ <div class="sidebar-version__actions">
106
+ <button class="sidebar-version__btn sidebar-version__btn--ghost hidden" id="brandCheckUpdateBtn" type="button">Check</button>
107
+ <button class="sidebar-version__btn hidden" id="brandUpdateBtn" type="button">Update</button>
108
+ <span class="sidebar-version__status" id="brandStatusDot" aria-hidden="true"></span>
103
109
  </div>
104
- <span class="sidebar-version__status" id="brandStatusDot" aria-hidden="true"></span>
105
110
  </div>
106
111
  </div>
107
112
  </div>
@@ -56,6 +56,9 @@ export const TRANSLATIONS = {
56
56
  },
57
57
  actions: {
58
58
  broadcastNow: 'Announce Agent Now',
59
+ checkUpdate: 'Check',
60
+ updateNow: 'Update',
61
+ updateNowVersion: 'Update {version}',
59
62
  editProfile: 'Edit Profile',
60
63
  openNetwork: 'Network',
61
64
  openAgent: 'Directory',
@@ -135,6 +138,14 @@ export const TRANSLATIONS = {
135
138
  publishStatus: 'Publish Status',
136
139
  publicProfilePreview: 'Public Profile Preview',
137
140
  goPublic: 'Go public',
141
+ versionChecking: 'Checking for updates...',
142
+ versionCurrent: 'Up to date',
143
+ versionUpdateReady: 'Update {version} ready',
144
+ versionUpdating: 'Updating...',
145
+ versionCheckFailed: 'Could not check updates',
146
+ versionPlatformMac: 'macOS service will restart automatically',
147
+ versionPlatformLinux: 'Linux service will restart automatically',
148
+ versionPlatformOther: 'Local service will refresh after the update',
138
149
  networkEyebrow: 'Network',
139
150
  connectionSummary: 'Connection',
140
151
  quickActions: 'Broadcast',
@@ -533,6 +544,11 @@ export const TRANSLATIONS = {
533
544
  templateCopied: 'Template copied to clipboard.',
534
545
  preparingDownload: 'Preparing download...',
535
546
  runtimeUpdated: 'Mode updated.',
547
+ appUpdateStarted: 'Update started. SilicaClaw will refresh shortly.',
548
+ appUpdatedTo: 'Updated to {version}.',
549
+ appUpdateLatest: 'Already on the latest version.',
550
+ appUpdateCheckFailed: 'Could not check for updates.',
551
+ appUpdateFailed: 'Could not start the update.',
536
552
  copyPreviewFailed: 'Copy preview failed',
537
553
  logsRefreshed: 'Logs refreshed',
538
554
  crossPreviewEnabled: 'Cross-network preview enabled',
@@ -642,6 +658,9 @@ export const TRANSLATIONS = {
642
658
  },
643
659
  actions: {
644
660
  broadcastNow: '立即公告代理',
661
+ checkUpdate: '检查',
662
+ updateNow: '立即更新',
663
+ updateNowVersion: '更新到 {version}',
645
664
  editProfile: '编辑资料',
646
665
  openNetwork: '打开网络页',
647
666
  openAgent: '打开代理目录',
@@ -708,6 +727,14 @@ export const TRANSLATIONS = {
708
727
  discoveredAgents: '公开代理目录',
709
728
  onlyShowOnline: '只显示在线',
710
729
  nodeSnapshot: '本机代理快照',
730
+ versionChecking: '正在检查更新...',
731
+ versionCurrent: '已是最新版本',
732
+ versionUpdateReady: '可更新到 {version}',
733
+ versionUpdating: '正在更新...',
734
+ versionCheckFailed: '暂时无法检查更新',
735
+ versionPlatformMac: 'macOS 服务会自动重启',
736
+ versionPlatformLinux: 'Linux 服务会自动重启',
737
+ versionPlatformOther: '更新后本地服务会自动刷新',
711
738
  profileEyebrow: '资料',
712
739
  publicProfile: '公开资料',
713
740
  publicProfileEditor: '公开资料编辑器',
@@ -1119,6 +1146,11 @@ export const TRANSLATIONS = {
1119
1146
  templateCopied: '模板已复制到剪贴板。',
1120
1147
  preparingDownload: '正在准备下载...',
1121
1148
  runtimeUpdated: '模式已更新。',
1149
+ appUpdateStarted: '更新已开始,SilicaClaw 很快会自动刷新。',
1150
+ appUpdatedTo: '已更新到 {version}。',
1151
+ appUpdateLatest: '当前已经是最新版本。',
1152
+ appUpdateCheckFailed: '暂时无法检查更新。',
1153
+ appUpdateFailed: '无法开始更新。',
1122
1154
  copyPreviewFailed: '复制预览失败',
1123
1155
  logsRefreshed: '日志已刷新',
1124
1156
  crossPreviewEnabled: '跨网络预览已启用',