@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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## v1.0 beta - 2026-03-20
4
4
 
5
+ ### 2026.3.20-4
6
+
7
+ - release build:
8
+ - prepared another fresh latest-channel package build without publishing
9
+ - regenerated the npm tarball through the verified release packing workflow
10
+
5
11
  ### 2026.3.20-3
6
12
 
7
13
  - release build:
package/INSTALL.md CHANGED
@@ -211,9 +211,9 @@ npx clawhub publish openclaw-skills/silicaclaw-broadcast \
211
211
  npx clawhub publish openclaw-skills/silicaclaw-owner-push \
212
212
  --slug silicaclaw-owner-push \
213
213
  --name "SilicaClaw Owner Push" \
214
- --version 2026.3.20-beta.1 \
214
+ --version 2026.3.20-beta.2 \
215
215
  --tags latest \
216
- --changelog "Added clearer safety boundaries and bounded local workflow guidance for high-signal monitoring and owner push summaries."
216
+ --changelog "Added latest-only owner push behavior with timestamp cursor state so only the newest qualifying broadcast is pushed and older messages are skipped."
217
217
  ```
218
218
 
219
219
  ClawHub expects each skill version to be valid semver, so use the versions from each skill's `manifest.json` and `VERSION`, not the npm CLI version format.
package/README.md CHANGED
@@ -283,9 +283,9 @@ npx clawhub publish openclaw-skills/silicaclaw-broadcast \
283
283
  npx clawhub publish openclaw-skills/silicaclaw-owner-push \
284
284
  --slug silicaclaw-owner-push \
285
285
  --name "SilicaClaw Owner Push" \
286
- --version 2026.3.20-beta.1 \
286
+ --version 2026.3.20-beta.2 \
287
287
  --tags latest \
288
- --changelog "Added clearer safety boundaries and bounded local workflow guidance for high-signal monitoring and owner push summaries."
288
+ --changelog "Added latest-only owner push behavior with timestamp cursor state so only the newest qualifying broadcast is pushed and older messages are skipped."
289
289
  ```
290
290
 
291
291
  ClawHub publishes the OpenClaw skill folders, not the npm CLI package.
package/VERSION CHANGED
@@ -1 +1 @@
1
- v2026.3.20-3
1
+ v2026.3.20-4
@@ -374,6 +374,23 @@ export declare class LocalNodeService {
374
374
  adapter_stats: any;
375
375
  adapter_transport_stats: any;
376
376
  adapter_discovery_stats: any;
377
+ runtime_diagnostics: {
378
+ memory_mib: {
379
+ rss: number;
380
+ heap_used: number;
381
+ heap_total: number;
382
+ external: number;
383
+ };
384
+ directory: {
385
+ profile_count: number;
386
+ presence_count: number;
387
+ index_key_count: number;
388
+ };
389
+ social: {
390
+ message_count: number;
391
+ observation_count: number;
392
+ };
393
+ };
377
394
  adapter_diagnostics_summary: {
378
395
  started: boolean;
379
396
  startup_error: string | null;
@@ -471,6 +488,22 @@ export declare class LocalNodeService {
471
488
  social_lookup_paths: string[];
472
489
  social_source_path: string | null;
473
490
  };
491
+ getAppUpdateStatus(): {
492
+ latest_version: string;
493
+ update_available: boolean;
494
+ current_version: string;
495
+ channel: string;
496
+ platform: NodeJS.Platform;
497
+ checked_at: number;
498
+ can_update: boolean;
499
+ check_error: string | null;
500
+ };
501
+ startAppUpdate(): {
502
+ started: boolean;
503
+ target_version: string;
504
+ platform: string;
505
+ reason?: string;
506
+ };
474
507
  getIntegrationSummary(): {
475
508
  connected: boolean;
476
509
  discoverable: boolean;
@@ -733,6 +766,7 @@ export declare class LocalNodeService {
733
766
  private clearNetworkReconnectTimer;
734
767
  private startNetworkAdapterWithRetry;
735
768
  private scheduleNetworkReconnect;
769
+ private pruneRemoteProfilesInMemory;
736
770
  private compactCacheInMemory;
737
771
  private publish;
738
772
  private persistCache;
@@ -37,6 +37,7 @@ const DEFAULT_BRIDGE_API_BASE = silicaclaw_defaults_json_1.default.bridge.api_ba
37
37
  const OPENCLAW_GATEWAY_PORT = silicaclaw_defaults_json_1.default.ports.openclaw_gateway;
38
38
  const OPENCLAW_GATEWAY_URL = `http://${OPENCLAW_GATEWAY_HOST}:${OPENCLAW_GATEWAY_PORT}/`;
39
39
  const NETWORK_PEER_REMOVE_AFTER_MS = Number(process.env.NETWORK_PEER_REMOVE_AFTER_MS || 180_000);
40
+ const DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT = Number(process.env.DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT || 1000);
40
41
  const NETWORK_UDP_BIND_ADDRESS = process.env.NETWORK_UDP_BIND_ADDRESS || "0.0.0.0";
41
42
  const NETWORK_UDP_BROADCAST_ADDRESS = process.env.NETWORK_UDP_BROADCAST_ADDRESS || "255.255.255.255";
42
43
  const NETWORK_PEER_ID = process.env.NETWORK_PEER_ID;
@@ -101,6 +102,9 @@ function normalizeVersionText(value) {
101
102
  const text = String(value || "").trim();
102
103
  return text.startsWith("v") ? text.slice(1) : text;
103
104
  }
105
+ function formatBytesToMiB(value) {
106
+ return Math.round((value / (1024 * 1024)) * 10) / 10;
107
+ }
104
108
  function tokenizeVersion(value) {
105
109
  return normalizeVersionText(value)
106
110
  .split(/[^0-9A-Za-z]+/)
@@ -133,6 +137,9 @@ function compareVersionTokens(left, right) {
133
137
  }
134
138
  return 0;
135
139
  }
140
+ function userNpmCacheDir() {
141
+ return (0, path_1.resolve)((0, os_1.homedir)(), ".silicaclaw", "npm-cache");
142
+ }
136
143
  function resolveWorkspaceRoot(cwd = process.cwd()) {
137
144
  if ((0, fs_1.existsSync)((0, path_1.resolve)(cwd, "apps", "local-console", "package.json"))) {
138
145
  return cwd;
@@ -968,6 +975,7 @@ class LocalNodeService {
968
975
  const relayCapable = this.adapterMode === "webrtc-preview" || this.adapterMode === "relay-preview";
969
976
  const peers = diagnostics?.peers?.items ?? [];
970
977
  const online = peers.filter((peer) => peer.status === "online").length;
978
+ const memory = process.memoryUsage();
971
979
  return {
972
980
  adapter: this.adapterMode,
973
981
  mode: this.networkMode,
@@ -991,6 +999,23 @@ class LocalNodeService {
991
999
  adapter_stats: diagnostics?.stats ?? null,
992
1000
  adapter_transport_stats: diagnostics?.transport_stats ?? null,
993
1001
  adapter_discovery_stats: diagnostics?.discovery_stats ?? null,
1002
+ runtime_diagnostics: {
1003
+ memory_mib: {
1004
+ rss: formatBytesToMiB(memory.rss),
1005
+ heap_used: formatBytesToMiB(memory.heapUsed),
1006
+ heap_total: formatBytesToMiB(memory.heapTotal),
1007
+ external: formatBytesToMiB(memory.external),
1008
+ },
1009
+ directory: {
1010
+ profile_count: Object.keys(this.directory.profiles).length,
1011
+ presence_count: Object.keys(this.directory.presence).length,
1012
+ index_key_count: Object.keys(this.directory.index).length,
1013
+ },
1014
+ social: {
1015
+ message_count: this.socialMessages.length,
1016
+ observation_count: this.socialMessageObservations.length,
1017
+ },
1018
+ },
994
1019
  adapter_diagnostics_summary: relayCapable || diagnostics
995
1020
  ? {
996
1021
  started: this.networkStarted,
@@ -1102,6 +1127,87 @@ class LocalNodeService {
1102
1127
  social_source_path: this.socialSourcePath,
1103
1128
  };
1104
1129
  }
1130
+ getAppUpdateStatus() {
1131
+ const currentVersion = normalizeVersionText(this.appVersion) || "unknown";
1132
+ const fallback = {
1133
+ current_version: currentVersion,
1134
+ latest_version: currentVersion,
1135
+ update_available: false,
1136
+ channel: "latest",
1137
+ platform: process.platform,
1138
+ checked_at: Date.now(),
1139
+ can_update: true,
1140
+ check_error: null,
1141
+ };
1142
+ try {
1143
+ const result = (0, child_process_1.spawnSync)("npm", ["view", "@silicaclaw/cli", "dist-tags", "--json"], {
1144
+ cwd: this.projectRoot,
1145
+ encoding: "utf8",
1146
+ env: {
1147
+ ...process.env,
1148
+ SILICACLAW_WORKSPACE_DIR: this.projectRoot,
1149
+ SILICACLAW_APP_DIR: this.workspaceRoot,
1150
+ npm_config_cache: process.env.npm_config_cache || userNpmCacheDir(),
1151
+ },
1152
+ });
1153
+ if ((result.status ?? 1) !== 0) {
1154
+ return {
1155
+ ...fallback,
1156
+ check_error: String(result.stderr || result.stdout || "npm view failed").trim() || "npm view failed",
1157
+ };
1158
+ }
1159
+ const tags = JSON.parse(String(result.stdout || "{}").trim() || "{}");
1160
+ const latestVersion = normalizeVersionText(tags.latest || currentVersion) || currentVersion;
1161
+ return {
1162
+ ...fallback,
1163
+ latest_version: latestVersion,
1164
+ update_available: compareVersionTokens(latestVersion, currentVersion) > 0,
1165
+ };
1166
+ }
1167
+ catch (error) {
1168
+ return {
1169
+ ...fallback,
1170
+ check_error: error instanceof Error ? error.message : String(error),
1171
+ };
1172
+ }
1173
+ }
1174
+ startAppUpdate() {
1175
+ const status = this.getAppUpdateStatus();
1176
+ if (!status.update_available || !status.latest_version) {
1177
+ return {
1178
+ started: false,
1179
+ target_version: status.latest_version || status.current_version,
1180
+ platform: process.platform,
1181
+ reason: status.check_error || "already_current",
1182
+ };
1183
+ }
1184
+ const scriptPath = (0, path_1.resolve)(this.workspaceRoot, "scripts", "silicaclaw-cli.mjs");
1185
+ if (!(0, fs_1.existsSync)(scriptPath)) {
1186
+ return {
1187
+ started: false,
1188
+ target_version: status.latest_version,
1189
+ platform: process.platform,
1190
+ reason: "missing_cli_script",
1191
+ };
1192
+ }
1193
+ const child = (0, child_process_1.spawn)(process.execPath, [scriptPath, "update"], {
1194
+ cwd: this.projectRoot,
1195
+ detached: true,
1196
+ stdio: "ignore",
1197
+ env: {
1198
+ ...process.env,
1199
+ SILICACLAW_WORKSPACE_DIR: this.projectRoot,
1200
+ SILICACLAW_APP_DIR: this.workspaceRoot,
1201
+ npm_config_cache: process.env.npm_config_cache || userNpmCacheDir(),
1202
+ },
1203
+ });
1204
+ child.unref();
1205
+ return {
1206
+ started: true,
1207
+ target_version: status.latest_version,
1208
+ platform: process.platform,
1209
+ };
1210
+ }
1105
1211
  getIntegrationSummary() {
1106
1212
  const status = this.getIntegrationStatus();
1107
1213
  const runtimeGenerated = Boolean(this.socialRuntime && this.socialRuntime.last_loaded_at > 0);
@@ -2290,9 +2396,58 @@ class LocalNodeService {
2290
2396
  }, delayMs);
2291
2397
  this.networkReconnectDelayMs = Math.min(30_000, Math.max(5_000, Math.floor(delayMs * 1.5)));
2292
2398
  }
2399
+ pruneRemoteProfilesInMemory(now = Date.now()) {
2400
+ if (!Number.isFinite(DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT) || DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT <= 0) {
2401
+ return 0;
2402
+ }
2403
+ const selfAgentId = this.profile?.agent_id || this.identity?.agent_id || "";
2404
+ const remoteProfiles = Object.values(this.directory.profiles).filter((profile) => profile.agent_id !== selfAgentId);
2405
+ if (remoteProfiles.length <= DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT) {
2406
+ return 0;
2407
+ }
2408
+ const onlineRemoteProfiles = remoteProfiles.filter((profile) => (0, core_1.isAgentOnline)(this.directory.presence[profile.agent_id], now, PRESENCE_TTL_MS));
2409
+ const offlineRemoteProfiles = remoteProfiles
2410
+ .filter((profile) => !(0, core_1.isAgentOnline)(this.directory.presence[profile.agent_id], now, PRESENCE_TTL_MS))
2411
+ .sort((a, b) => (b.updated_at || 0) - (a.updated_at || 0));
2412
+ const keepOfflineCount = Math.max(0, DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT - onlineRemoteProfiles.length);
2413
+ const keptRemoteProfiles = [
2414
+ ...onlineRemoteProfiles,
2415
+ ...offlineRemoteProfiles.slice(0, keepOfflineCount),
2416
+ ];
2417
+ const keptRemoteIds = new Set(keptRemoteProfiles.map((profile) => profile.agent_id));
2418
+ const removedIds = remoteProfiles
2419
+ .map((profile) => profile.agent_id)
2420
+ .filter((agentId) => !keptRemoteIds.has(agentId));
2421
+ if (removedIds.length === 0) {
2422
+ return 0;
2423
+ }
2424
+ const next = (0, core_1.createEmptyDirectoryState)();
2425
+ const selfProfile = selfAgentId ? this.directory.profiles[selfAgentId] : null;
2426
+ if (selfProfile) {
2427
+ next.profiles[selfAgentId] = selfProfile;
2428
+ const selfPresence = this.directory.presence[selfAgentId];
2429
+ if (typeof selfPresence === "number" && Number.isFinite(selfPresence)) {
2430
+ next.presence[selfAgentId] = selfPresence;
2431
+ }
2432
+ const rebuilt = (0, core_1.rebuildIndexForProfile)(next, selfProfile);
2433
+ next.index = rebuilt.index;
2434
+ }
2435
+ for (const profile of keptRemoteProfiles) {
2436
+ next.profiles[profile.agent_id] = profile;
2437
+ const seenAt = this.directory.presence[profile.agent_id];
2438
+ if (typeof seenAt === "number" && Number.isFinite(seenAt)) {
2439
+ next.presence[profile.agent_id] = seenAt;
2440
+ }
2441
+ const rebuilt = (0, core_1.rebuildIndexForProfile)(next, profile);
2442
+ next.index = rebuilt.index;
2443
+ }
2444
+ this.directory = (0, core_1.dedupeIndex)(next);
2445
+ return removedIds.length;
2446
+ }
2293
2447
  compactCacheInMemory() {
2294
2448
  const cleaned = (0, core_1.cleanupExpiredPresence)(this.directory, Date.now(), PRESENCE_TTL_MS);
2295
2449
  this.directory = (0, core_1.dedupeIndex)(cleaned.state);
2450
+ this.pruneRemoteProfilesInMemory();
2296
2451
  return cleaned.removed;
2297
2452
  }
2298
2453
  async publish(topic, data) {
@@ -2940,6 +3095,36 @@ async function main() {
2940
3095
  app.get("/api/runtime/paths", (_req, res) => {
2941
3096
  sendOk(res, node.getRuntimePaths());
2942
3097
  });
3098
+ app.get("/api/app/update-status", (_req, res) => {
3099
+ sendOk(res, node.getAppUpdateStatus());
3100
+ });
3101
+ app.post("/api/app/update", asyncRoute(async (_req, res) => {
3102
+ const status = node.getAppUpdateStatus();
3103
+ if (!status.update_available || !status.latest_version) {
3104
+ sendOk(res, {
3105
+ started: false,
3106
+ current_version: status.current_version,
3107
+ latest_version: status.latest_version,
3108
+ platform: status.platform,
3109
+ reason: status.check_error || "already_current",
3110
+ }, { message: "Already on the latest version" });
3111
+ return;
3112
+ }
3113
+ sendOk(res, {
3114
+ started: true,
3115
+ current_version: status.current_version,
3116
+ target_version: status.latest_version,
3117
+ platform: status.platform,
3118
+ }, { message: `Updating to ${status.latest_version}` });
3119
+ setTimeout(() => {
3120
+ try {
3121
+ node.startAppUpdate();
3122
+ }
3123
+ catch {
3124
+ // best effort after response has been sent
3125
+ }
3126
+ }, 150);
3127
+ }));
2943
3128
  app.put("/api/profile", asyncRoute(async (req, res) => {
2944
3129
  const body = req.body;
2945
3130
  const tags = Array.isArray(body.tags)
@@ -27,6 +27,7 @@ if (!root) {
27
27
  throw new Error("Missing root element: app-root");
28
28
  }
29
29
  root.innerHTML = appTemplate;
30
+ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
30
31
 
31
32
  const i18n = createI18n(TRANSLATIONS);
32
33
  const DEFAULT_LOCALE = i18n.DEFAULT_LOCALE;
@@ -70,6 +71,9 @@ root.innerHTML = appTemplate;
70
71
  document.getElementById('sidebarToggleBtn').setAttribute('aria-label', t('labels.collapseSidebar'));
71
72
  document.querySelector('.sidebar-version').title = t('common.version');
72
73
  setText('.sidebar-version__label', t('common.version'));
74
+ document.getElementById('brandUpdateHint').textContent = t('labels.versionChecking');
75
+ document.getElementById('brandCheckUpdateBtn').textContent = t('actions.checkUpdate');
76
+ document.getElementById('brandUpdateBtn').textContent = t('actions.updateNow');
73
77
  document.getElementById('integrationStatusBar').textContent = t('social.barStatus', {
74
78
  connected: '-',
75
79
  mode: '-',
@@ -319,6 +323,175 @@ root.innerHTML = appTemplate;
319
323
  toast,
320
324
  writeUiCache,
321
325
  } = shell;
326
+ let appUpdatePollTimer = null;
327
+ let appUpdateCheckInFlight = false;
328
+
329
+ function setAppUpdateUi({
330
+ hint,
331
+ buttonVisible = false,
332
+ buttonDisabled = false,
333
+ buttonText = t('actions.updateNow'),
334
+ checkVisible = false,
335
+ checkDisabled = false,
336
+ }) {
337
+ const hintEl = document.getElementById('brandUpdateHint');
338
+ const buttonEl = document.getElementById('brandUpdateBtn');
339
+ const checkEl = document.getElementById('brandCheckUpdateBtn');
340
+ if (hintEl) {
341
+ hintEl.textContent = hint;
342
+ hintEl.classList.toggle('hidden', !hint);
343
+ }
344
+ if (checkEl) {
345
+ checkEl.textContent = t('actions.checkUpdate');
346
+ checkEl.classList.toggle('hidden', !checkVisible);
347
+ checkEl.disabled = checkDisabled;
348
+ }
349
+ if (buttonEl) {
350
+ buttonEl.textContent = buttonText;
351
+ buttonEl.classList.toggle('hidden', !buttonVisible);
352
+ buttonEl.disabled = buttonDisabled;
353
+ }
354
+ }
355
+
356
+ function platformUpdateHint(platform) {
357
+ if (platform === 'darwin') return t('labels.versionPlatformMac');
358
+ if (platform === 'linux') return t('labels.versionPlatformLinux');
359
+ return t('labels.versionPlatformOther');
360
+ }
361
+
362
+ async function refreshAppUpdateStatus({ silent = false } = {}) {
363
+ if (appUpdateCheckInFlight) return null;
364
+ appUpdateCheckInFlight = true;
365
+ try {
366
+ const result = await api('/api/app/update-status');
367
+ const status = result.data || {};
368
+ const currentVersion = String(status.current_version || '').trim();
369
+ const latestVersion = String(status.latest_version || '').trim();
370
+ const platformHint = platformUpdateHint(String(status.platform || ''));
371
+ if (currentVersion) {
372
+ document.getElementById('brandVersion').textContent = currentVersion.startsWith('v') ? currentVersion : `v${currentVersion}`;
373
+ }
374
+ if (status.update_available && status.latest_version) {
375
+ setAppUpdateUi({
376
+ hint: `${t('labels.versionUpdateReady', { version: `v${status.latest_version}` })} · ${platformHint}`,
377
+ buttonVisible: true,
378
+ buttonDisabled: false,
379
+ buttonText: t('actions.updateNowVersion', { version: latestVersion.startsWith('v') ? latestVersion : `v${latestVersion}` }),
380
+ checkVisible: true,
381
+ checkDisabled: false,
382
+ });
383
+ } else if (status.check_error) {
384
+ setAppUpdateUi({
385
+ hint: t('labels.versionCheckFailed'),
386
+ buttonVisible: false,
387
+ buttonDisabled: false,
388
+ buttonText: t('actions.updateNow'),
389
+ checkVisible: true,
390
+ checkDisabled: false,
391
+ });
392
+ if (!silent) {
393
+ setFeedback('networkFeedback', t('feedback.appUpdateCheckFailed'), 'warn');
394
+ }
395
+ } else {
396
+ setAppUpdateUi({
397
+ hint: `${t('labels.versionCurrent')} · ${platformHint}`,
398
+ buttonVisible: false,
399
+ buttonDisabled: false,
400
+ buttonText: t('actions.updateNow'),
401
+ checkVisible: true,
402
+ checkDisabled: false,
403
+ });
404
+ }
405
+ return status;
406
+ } catch (_error) {
407
+ setAppUpdateUi({
408
+ hint: t('labels.versionCheckFailed'),
409
+ buttonVisible: false,
410
+ buttonDisabled: false,
411
+ buttonText: t('actions.updateNow'),
412
+ checkVisible: true,
413
+ checkDisabled: false,
414
+ });
415
+ if (!silent) {
416
+ setFeedback('networkFeedback', t('feedback.appUpdateCheckFailed'), 'warn');
417
+ }
418
+ return null;
419
+ } finally {
420
+ appUpdateCheckInFlight = false;
421
+ }
422
+ }
423
+
424
+ function startAppUpdatePolling(targetVersion) {
425
+ if (appUpdatePollTimer) {
426
+ window.clearInterval(appUpdatePollTimer);
427
+ }
428
+ let attempts = 0;
429
+ appUpdatePollTimer = window.setInterval(async () => {
430
+ attempts += 1;
431
+ const status = await refreshAppUpdateStatus({ silent: true });
432
+ if (status && !status.update_available && String(status.current_version || '') === String(targetVersion || '')) {
433
+ window.clearInterval(appUpdatePollTimer);
434
+ appUpdatePollTimer = null;
435
+ if (targetVersion) {
436
+ window.sessionStorage.setItem(APP_UPDATE_SESSION_KEY, String(targetVersion));
437
+ }
438
+ window.location.reload();
439
+ return;
440
+ }
441
+ if (attempts >= 24) {
442
+ window.clearInterval(appUpdatePollTimer);
443
+ appUpdatePollTimer = null;
444
+ setAppUpdateUi({
445
+ hint: t('labels.versionCurrent'),
446
+ buttonVisible: false,
447
+ buttonDisabled: false,
448
+ buttonText: t('actions.updateNow'),
449
+ checkVisible: true,
450
+ checkDisabled: false,
451
+ });
452
+ }
453
+ }, 5000);
454
+ }
455
+
456
+ async function triggerAppUpdate() {
457
+ const buttonEl = document.getElementById('brandUpdateBtn');
458
+ setAppUpdateUi({
459
+ hint: t('labels.versionUpdating'),
460
+ buttonVisible: true,
461
+ buttonDisabled: true,
462
+ buttonText: t('labels.versionUpdating'),
463
+ checkVisible: true,
464
+ checkDisabled: true,
465
+ });
466
+ try {
467
+ const result = await api('/api/app/update', { method: 'POST' });
468
+ const data = result.data || {};
469
+ if (!data.started) {
470
+ setAppUpdateUi({
471
+ hint: t('labels.versionCurrent'),
472
+ buttonVisible: false,
473
+ buttonDisabled: false,
474
+ buttonText: t('actions.updateNow'),
475
+ checkVisible: true,
476
+ checkDisabled: false,
477
+ });
478
+ toast(t('feedback.appUpdateLatest'));
479
+ return;
480
+ }
481
+ toast(t('feedback.appUpdateStarted'));
482
+ startAppUpdatePolling(String(data.target_version || ''));
483
+ } catch (error) {
484
+ setAppUpdateUi({
485
+ hint: t('labels.versionCheckFailed'),
486
+ buttonVisible: true,
487
+ buttonDisabled: false,
488
+ buttonText: t('actions.updateNow'),
489
+ checkVisible: true,
490
+ checkDisabled: false,
491
+ });
492
+ setFeedback('networkFeedback', error instanceof Error ? error.message : t('feedback.appUpdateFailed'), 'error');
493
+ }
494
+ }
322
495
  setLocale(currentLocale);
323
496
  applyStaticTranslations();
324
497
 
@@ -427,6 +600,23 @@ root.innerHTML = appTemplate;
427
600
  document.getElementById('publicDiscoveryHint')?.classList.toggle('hidden', tab !== 'overview');
428
601
  if (tab === 'profile' && !profileController.isDirty() && !profileController.isSaving()) {
429
602
  refreshProfile().catch(() => {});
603
+ } else if (tab === 'overview') {
604
+ refreshOverview().catch(() => {});
605
+ refreshMessages().catch(() => {});
606
+ } else if (tab === 'agent') {
607
+ refreshOverview().catch(() => {});
608
+ } else if (tab === 'chat') {
609
+ refreshMessages().catch(() => {});
610
+ } else if (tab === 'skills') {
611
+ refreshSkills().catch(() => {});
612
+ } else if (tab === 'network') {
613
+ refreshNetwork().catch(() => {});
614
+ refreshPeers().catch(() => {});
615
+ refreshDiscovery().catch(() => {});
616
+ refreshLogs().catch(() => {});
617
+ } else if (tab === 'social') {
618
+ refreshSocial().catch(() => {});
619
+ refreshMessages().catch(() => {});
430
620
  }
431
621
  }
432
622
 
@@ -453,6 +643,43 @@ root.innerHTML = appTemplate;
453
643
  const renderLogs = socialController.renderLogs;
454
644
  const refreshLogs = socialController.refreshLogs;
455
645
  const refreshSkills = socialController.refreshSkills;
646
+ let autoRefreshInFlight = false;
647
+
648
+ async function refreshActiveView() {
649
+ const tasks = [refreshPublicProfilePreview()];
650
+ if (activeTab === 'overview') {
651
+ tasks.push(refreshOverview(), refreshMessages(), refreshSocial());
652
+ } else if (activeTab === 'agent') {
653
+ tasks.push(refreshOverview());
654
+ } else if (activeTab === 'chat') {
655
+ tasks.push(refreshMessages());
656
+ } else if (activeTab === 'skills') {
657
+ tasks.push(refreshSkills());
658
+ } else if (activeTab === 'network') {
659
+ tasks.push(refreshNetwork(), refreshPeers(), refreshDiscovery(), refreshLogs());
660
+ } else if (activeTab === 'social') {
661
+ tasks.push(refreshSocial(), refreshMessages());
662
+ } else if (activeTab === 'profile' && !profileController.isDirty() && !profileController.isSaving()) {
663
+ tasks.push(refreshProfile());
664
+ }
665
+ const results = await Promise.allSettled(tasks);
666
+ const firstError = results.find((result) => result.status === 'rejected');
667
+ if (firstError && firstError.status === 'rejected') {
668
+ setFeedback('networkFeedback', firstError.reason instanceof Error ? firstError.reason.message : t('common.unknownError'), 'error');
669
+ }
670
+ }
671
+
672
+ async function refreshAuto() {
673
+ if (document.hidden || autoRefreshInFlight) {
674
+ return;
675
+ }
676
+ autoRefreshInFlight = true;
677
+ try {
678
+ await refreshActiveView();
679
+ } finally {
680
+ autoRefreshInFlight = false;
681
+ }
682
+ }
456
683
 
457
684
  async function refreshAll() {
458
685
  const tasks = [refreshOverview(), refreshNetwork(), refreshSocial(), refreshSkills(), refreshPublicProfilePreview(), refreshMessages()];
@@ -511,6 +738,27 @@ root.innerHTML = appTemplate;
511
738
 
512
739
  applyTheme(localStorage.getItem('silicaclaw_theme_mode') || 'dark');
513
740
  hydrateCachedShell();
741
+ document.getElementById('brandUpdateBtn').addEventListener('click', () => {
742
+ triggerAppUpdate().catch(() => {});
743
+ });
744
+ document.getElementById('brandCheckUpdateBtn').addEventListener('click', () => {
745
+ refreshAppUpdateStatus().catch(() => {});
746
+ });
514
747
  refreshAll();
748
+ refreshAppUpdateStatus({ silent: true }).catch(() => {});
749
+ const updatedVersion = window.sessionStorage.getItem(APP_UPDATE_SESSION_KEY);
750
+ if (updatedVersion) {
751
+ window.sessionStorage.removeItem(APP_UPDATE_SESSION_KEY);
752
+ toast(t('feedback.appUpdatedTo', { version: updatedVersion.startsWith('v') ? updatedVersion : `v${updatedVersion}` }));
753
+ }
515
754
  exportSocialTemplate().catch(() => {});
516
- setInterval(refreshAll, 4000);
755
+ document.addEventListener('visibilitychange', () => {
756
+ if (!document.hidden) {
757
+ refreshAuto().catch(() => {});
758
+ refreshAppUpdateStatus({ silent: true }).catch(() => {});
759
+ }
760
+ });
761
+ setInterval(refreshAuto, 4000);
762
+ setInterval(() => {
763
+ refreshAppUpdateStatus({ silent: true }).catch(() => {});
764
+ }, 15 * 60 * 1000);