@silicaclaw/cli 2026.3.20-2 → 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.
Files changed (50) hide show
  1. package/CHANGELOG.md +12 -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 +39 -0
  6. package/apps/local-console/dist/apps/local-console/src/server.js +220 -0
  7. package/apps/local-console/dist/packages/network/src/relayPreview.d.ts +1 -0
  8. package/apps/local-console/dist/packages/network/src/relayPreview.js +17 -0
  9. package/apps/local-console/public/app/app.js +274 -3
  10. package/apps/local-console/public/app/events.js +21 -0
  11. package/apps/local-console/public/app/network.js +111 -30
  12. package/apps/local-console/public/app/overview.js +49 -21
  13. package/apps/local-console/public/app/social.js +315 -93
  14. package/apps/local-console/public/app/styles.css +86 -0
  15. package/apps/local-console/public/app/template.js +56 -35
  16. package/apps/local-console/public/app/translations.js +394 -312
  17. package/apps/local-console/src/server.ts +251 -1
  18. package/apps/public-explorer/public/app/template.js +2 -2
  19. package/apps/public-explorer/public/app/translations.js +36 -36
  20. package/docs/NEW_USER_OPERATIONS.md +5 -5
  21. package/docs/OPENCLAW_BRIDGE.md +7 -7
  22. package/docs/OPENCLAW_BRIDGE_ZH.md +6 -6
  23. package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.d.ts +1 -0
  24. package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.js +17 -0
  25. package/node_modules/@silicaclaw/network/src/relayPreview.ts +17 -0
  26. package/openclaw-skills/silicaclaw-bridge-setup/SKILL.md +18 -0
  27. package/openclaw-skills/silicaclaw-bridge-setup/VERSION +1 -1
  28. package/openclaw-skills/silicaclaw-bridge-setup/manifest.json +2 -2
  29. package/openclaw-skills/silicaclaw-broadcast/SKILL.md +18 -0
  30. package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -1
  31. package/openclaw-skills/silicaclaw-broadcast/manifest.json +2 -2
  32. package/openclaw-skills/silicaclaw-network-config/SKILL.md +158 -0
  33. package/openclaw-skills/silicaclaw-network-config/VERSION +1 -0
  34. package/openclaw-skills/silicaclaw-network-config/agents/openai.yaml +6 -0
  35. package/openclaw-skills/silicaclaw-network-config/manifest.json +27 -0
  36. package/openclaw-skills/silicaclaw-network-config/references/network-modes.md +22 -0
  37. package/openclaw-skills/silicaclaw-network-config/references/owner-dialogue-cheatsheet-zh.md +47 -0
  38. package/openclaw-skills/silicaclaw-network-config/references/public-discovery.md +22 -0
  39. package/openclaw-skills/silicaclaw-owner-push/SKILL.md +18 -0
  40. package/openclaw-skills/silicaclaw-owner-push/VERSION +1 -1
  41. package/openclaw-skills/silicaclaw-owner-push/manifest.json +2 -2
  42. package/openclaw-skills/silicaclaw-owner-push/references/runtime-setup.md +3 -0
  43. package/openclaw-skills/silicaclaw-owner-push/scripts/owner-push-forwarder.mjs +67 -8
  44. package/package.json +1 -1
  45. package/packages/network/dist/packages/network/src/relayPreview.d.ts +1 -0
  46. package/packages/network/dist/packages/network/src/relayPreview.js +17 -0
  47. package/packages/network/src/relayPreview.ts +17 -0
  48. package/scripts/silicaclaw-cli.mjs +4 -1
  49. package/scripts/silicaclaw-gateway.mjs +108 -0
  50. package/scripts/validate-openclaw-skill.mjs +19 -0
@@ -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;
@@ -51,7 +52,7 @@ root.innerHTML = appTemplate;
51
52
  summary.setAttribute('data-i18n-closed-label', t('labels.show'));
52
53
  summary.setAttribute('data-i18n-open-label', t('labels.hide'));
53
54
  });
54
- setText('.sidebar-nav__label', t('common.control'));
55
+ setText('.nav-section__label', t('common.control'));
55
56
  setText('[data-tab="overview"] .tab-title', t('pageMeta.overview.title'));
56
57
  setText('[data-tab="overview"] .tab-copy', t('labels.overviewTabCopy'));
57
58
  setText('[data-tab="agent"] .tab-title', t('pageMeta.agent.title'));
@@ -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: '-',
@@ -106,19 +110,25 @@ root.innerHTML = appTemplate;
106
110
  document.getElementById('chatFeedHint').textContent = t('hints.chatFeedHint');
107
111
  document.getElementById('overviewGuideTitle').textContent = t('overview.guideTitle');
108
112
  document.getElementById('overviewGuideBody').textContent = t('overview.guideBody');
113
+ document.getElementById('overviewGuideStatus').textContent = t('overview.guideNeedSetup');
109
114
  document.getElementById('overviewStepProfileEyebrow').textContent = t('overview.stepLabel', { step: '1' });
110
115
  document.getElementById('overviewStepProfileTitle').textContent = t('overview.stepProfileTitle');
111
116
  document.getElementById('overviewStepProfileBody').textContent = t('overview.stepProfileBody');
117
+ document.getElementById('overviewStepProfileStatus').textContent = t('overview.stepIncomplete');
112
118
  document.getElementById('overviewStepProfileBtn').textContent = t('actions.editProfile');
113
119
  document.getElementById('overviewStepPublicEyebrow').textContent = t('overview.stepLabel', { step: '2' });
114
120
  document.getElementById('overviewStepPublicTitle').textContent = t('overview.stepPublicTitle');
115
121
  document.getElementById('overviewStepPublicBody').textContent = t('overview.stepPublicBody');
122
+ document.getElementById('overviewStepPublicStatus').textContent = t('overview.stepIncomplete');
116
123
  document.getElementById('overviewStepPublicBtn').textContent = t('actions.editProfile');
117
124
  document.getElementById('overviewStepBroadcastEyebrow').textContent = t('overview.stepLabel', { step: '3' });
118
125
  document.getElementById('overviewStepBroadcastTitle').textContent = t('overview.stepBroadcastTitle');
119
126
  document.getElementById('overviewStepBroadcastBody').textContent = t('overview.stepBroadcastBody');
127
+ document.getElementById('overviewStepBroadcastStatus').textContent = t('overview.stepWaiting');
120
128
  document.getElementById('overviewStepBroadcastBtn').textContent = t('actions.broadcastNow');
121
129
  document.getElementById('homeMissionEyebrow').textContent = t('hints.homeMissionEyebrow');
130
+ document.getElementById('homeMissionTitle').textContent = t('hints.homeMissionTitle');
131
+ document.getElementById('homeMissionBody').textContent = t('hints.homeMissionBody');
122
132
  document.getElementById('homeBriefTitle').textContent = t('hints.homeBriefTitle');
123
133
  document.getElementById('homeOpenAgentBtn').textContent = t('actions.openAgent');
124
134
  document.getElementById('homeOpenSocialBtn').textContent = t('pageMeta.social.title');
@@ -156,7 +166,7 @@ root.innerHTML = appTemplate;
156
166
  setText('#view-profile .profile-meta h4', t('labels.publicCard'), 0);
157
167
  setText('#view-profile .profile-meta h4', t('labels.publishStatus'), 1);
158
168
  setText('#view-profile .profile-meta h4', t('labels.publicProfilePreview'), 2);
159
- setText('#view-profile .profile-meta .field-hint', t('hints.signedPublicProfileHint'));
169
+ setText('#view-profile .profile-meta .field-hint', t('hints.signedPublicProfileHint'), 1);
160
170
  setText('#view-network .section-header__eyebrow', t('labels.networkEyebrow'));
161
171
  document.getElementById('networkBannerTitle').textContent = t('hints.networkBannerTitle');
162
172
  document.getElementById('networkBannerBody').textContent = t('hints.networkBannerBody');
@@ -190,6 +200,7 @@ root.innerHTML = appTemplate;
190
200
  document.getElementById('skillsBannerTitle').textContent = t('hints.skillsBannerTitle');
191
201
  document.getElementById('skillsBannerBody').textContent = t('hints.skillsBannerBody');
192
202
  document.getElementById('skillsBannerRuntimeLabel').textContent = t('hints.skillsBannerRuntime');
203
+ document.getElementById('skillsBannerRuntimeValue').textContent = t('hints.skillsRuntimeChecking');
193
204
  document.getElementById('skillsActionEyebrow').textContent = t('labels.skillsRecommendedAction');
194
205
  document.getElementById('skillsActionTitle').textContent = t('hints.skillsActionInstallTitle');
195
206
  document.getElementById('skillsActionBody').textContent = t('hints.skillsActionInstallBody');
@@ -200,6 +211,16 @@ root.innerHTML = appTemplate;
200
211
  document.getElementById('skillsJumpBundled').textContent = t('labels.skillsBundled');
201
212
  document.getElementById('skillsJumpInstalled').textContent = t('labels.skillsInstalled');
202
213
  document.getElementById('skillsJumpDialogue').textContent = t('labels.skillsDialogue');
214
+ document.getElementById('skillsSearchLabel').textContent = t('labels.skillsSearch');
215
+ document.getElementById('skillsSearchInput').placeholder = t('placeholders.skillsSearch');
216
+ document.getElementById('skillsFilterAll').textContent = t('labels.skillsFilterAll');
217
+ document.getElementById('skillsFilterAttention').textContent = t('labels.skillsFilterAttention');
218
+ document.getElementById('skillsFilterUpdates').textContent = t('labels.skillsFilterUpdates');
219
+ document.getElementById('skillsFilterInstalled').textContent = t('labels.skillsFilterInstalled');
220
+ document.getElementById('skillsFilterMeta').textContent = t('hints.skillsFilterMeta', {
221
+ count: '0',
222
+ filter: t('labels.skillsFilterAll'),
223
+ });
203
224
  document.getElementById('skillsFeaturedTitle').textContent = t('labels.skillsFeatured');
204
225
  document.getElementById('skillsFeaturedHint').textContent = t('hints.skillsFeaturedHint');
205
226
  document.getElementById('skillsBundledTitle').textContent = t('labels.skillsBundled');
@@ -219,6 +240,7 @@ root.innerHTML = appTemplate;
219
240
  document.getElementById('socialSkillLearningTitle').textContent = t('labels.openclawSkillLearning');
220
241
  document.getElementById('socialMessagePathTitle').textContent = t('labels.messagePath');
221
242
  document.getElementById('socialMessagePathHint').textContent = t('hints.socialMessagePathHint');
243
+ document.getElementById('socialOwnerDeliveryStatus').textContent = t('hints.checkingOwnerDelivery');
222
244
  document.getElementById('socialGovernanceTitle').textContent = t('labels.messageGovernance');
223
245
  document.getElementById('socialModerationTitle').textContent = t('labels.recentModeration');
224
246
  document.getElementById('socialAdvancedSummary').textContent = t('labels.advancedNetworkDetails');
@@ -240,6 +262,10 @@ root.innerHTML = appTemplate;
240
262
  setText('.hero-meta-item .label', t('labels.room'), 3);
241
263
  document.getElementById('publicDiscoveryHint').innerHTML = t('hints.publicDiscoverySwitch');
242
264
  document.getElementById('clearDiscoveryCacheBtn').textContent = t('actions.clearDiscoveryCache');
265
+ document.getElementById('overviewModeHint').textContent = t('overview.modeCurrentSource', {
266
+ mode: '-',
267
+ hint: t('overview.modeCacheHint'),
268
+ });
243
269
  document.getElementById('socialMessageTitle').textContent = t('overview.messageTitle');
244
270
  document.getElementById('socialMessageMeta').textContent = t('overview.messageMetaInitial');
245
271
  document.getElementById('socialMessageHint').textContent = t('overview.messageHint');
@@ -262,6 +288,7 @@ root.innerHTML = appTemplate;
262
288
  document.querySelector('label[for="governanceBlockedTermsInput"]').textContent = t('labels.blockedTerms');
263
289
  document.getElementById('startBroadcastBtn').textContent = t('actions.startBroadcast');
264
290
  document.getElementById('stopBroadcastBtn').textContent = t('actions.stopBroadcast');
291
+ document.getElementById('broadcastNowBtn').textContent = t('actions.broadcastNow');
265
292
  document.getElementById('quickGlobalPreviewBtn').textContent = t('actions.enablePreview');
266
293
  document.getElementById('refreshLogsBtn').textContent = t('actions.refreshLogs');
267
294
  document.getElementById('socialExportBtn').textContent = t('actions.exportTemplate');
@@ -296,6 +323,175 @@ root.innerHTML = appTemplate;
296
323
  toast,
297
324
  writeUiCache,
298
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
+ }
299
495
  setLocale(currentLocale);
300
496
  applyStaticTranslations();
301
497
 
@@ -404,6 +600,23 @@ root.innerHTML = appTemplate;
404
600
  document.getElementById('publicDiscoveryHint')?.classList.toggle('hidden', tab !== 'overview');
405
601
  if (tab === 'profile' && !profileController.isDirty() && !profileController.isSaving()) {
406
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(() => {});
407
620
  }
408
621
  }
409
622
 
@@ -430,6 +643,43 @@ root.innerHTML = appTemplate;
430
643
  const renderLogs = socialController.renderLogs;
431
644
  const refreshLogs = socialController.refreshLogs;
432
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
+ }
433
683
 
434
684
  async function refreshAll() {
435
685
  const tasks = [refreshOverview(), refreshNetwork(), refreshSocial(), refreshSkills(), refreshPublicProfilePreview(), refreshMessages()];
@@ -488,6 +738,27 @@ root.innerHTML = appTemplate;
488
738
 
489
739
  applyTheme(localStorage.getItem('silicaclaw_theme_mode') || 'dark');
490
740
  hydrateCachedShell();
741
+ document.getElementById('brandUpdateBtn').addEventListener('click', () => {
742
+ triggerAppUpdate().catch(() => {});
743
+ });
744
+ document.getElementById('brandCheckUpdateBtn').addEventListener('click', () => {
745
+ refreshAppUpdateStatus().catch(() => {});
746
+ });
491
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
+ }
492
754
  exportSocialTemplate().catch(() => {});
493
- 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);
@@ -319,6 +319,27 @@ export function bindAppEvents({
319
319
  });
320
320
  });
321
321
 
322
+ document.getElementById("skillsSearchInput").addEventListener("input", async (event) => {
323
+ socialController.setSkillsQuery(String(event.target?.value || ""));
324
+ await refreshSkills();
325
+ });
326
+
327
+ document.querySelectorAll("[data-skills-filter]").forEach((btn) => {
328
+ btn.addEventListener("click", async () => {
329
+ socialController.setSkillsFilter(String(btn.getAttribute("data-skills-filter") || "all"));
330
+ await refreshSkills();
331
+ });
332
+ });
333
+
334
+ document.querySelectorAll("#skillsBundledFooter, #skillsInstalledFooter, #skillsDialogueFooter").forEach((footer) => {
335
+ footer.addEventListener("click", async (event) => {
336
+ const btn = event.target instanceof Element ? event.target.closest("[data-skills-toggle]") : null;
337
+ if (!btn) return;
338
+ socialController.toggleSkillsExpanded(String(btn.getAttribute("data-skills-toggle") || ""));
339
+ await refreshSkills();
340
+ });
341
+ });
342
+
322
343
  document.getElementById("saveGovernanceBtn").addEventListener("click", async () => {
323
344
  setFeedback("socialGovernanceFeedback", t("common.saving"));
324
345
  try {
@@ -13,6 +13,9 @@ export function createNetworkController({
13
13
  room: "",
14
14
  };
15
15
  let quickConnectDefaults = { ...fallbackQuickConnectDefaults };
16
+ let lastNetworkRenderKey = "";
17
+ let lastPeersRenderKey = "";
18
+ let lastDiscoveryRenderKey = "";
16
19
 
17
20
  async function refreshNetwork() {
18
21
  const [cfg, sts, rtp] = await Promise.all([api("/api/network/config"), api("/api/network/stats"), api("/api/runtime/paths")]);
@@ -25,6 +28,10 @@ export function createNetworkController({
25
28
  const transportStats = s.adapter_transport_stats || {};
26
29
  const d = s.adapter_discovery_stats || {};
27
30
  const dx = s.adapter_diagnostics_summary || {};
31
+ const runtimeDiag = s.runtime_diagnostics || {};
32
+ const runtimeMemory = runtimeDiag.memory_mib || {};
33
+ const runtimeDirectory = runtimeDiag.directory || {};
34
+ const runtimeSocial = runtimeDiag.social || {};
28
35
  const ac = s.adapter_config || c.adapter_config || {};
29
36
  quickConnectDefaults = {
30
37
  signalingUrl: String(
@@ -59,7 +66,7 @@ export function createNetworkController({
59
66
  heroRoomText: dx.room || "-",
60
67
  pillAdapterText: `${t("labels.adapter")}: ${c.adapter || "-"}`,
61
68
  });
62
- document.getElementById("networkCards").innerHTML = [
69
+ const networkCardsHtml = [
63
70
  [t("labels.adapter"), c.adapter],
64
71
  [t("labels.namespace"), c.namespace || "-"],
65
72
  [t("labels.port"), c.port ?? "-"],
@@ -75,12 +82,18 @@ export function createNetworkController({
75
82
  [t("network.sent"), msg.broadcast_total ?? 0],
76
83
  [t("network.peers"), p.total ?? 0],
77
84
  [t("network.onlinePeers"), p.online ?? 0],
85
+ ["RSS", runtimeMemory.rss ?? "-"],
86
+ ["Heap", runtimeMemory.heap_used ?? "-"],
87
+ ["Profiles", runtimeDirectory.profile_count ?? "-"],
88
+ ["Index keys", runtimeDirectory.index_key_count ?? "-"],
89
+ ["Messages", runtimeSocial.message_count ?? "-"],
90
+ ["Observations", runtimeSocial.observation_count ?? "-"],
78
91
  [t("network.activeWebrtcPeers"), dx.active_webrtc_peers ?? "-"],
79
92
  [t("network.reconnectAttempts"), dx.reconnect_attempts_total ?? "-"],
80
93
  [t("network.lastInbound"), ago(msg.last_message_at)],
81
94
  [t("network.lastOutbound"), ago(msg.last_broadcast_at)],
82
95
  ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join("");
83
- document.getElementById("networkSummaryList").innerHTML = [
96
+ const networkSummaryHtml = [
84
97
  [t("labels.mode"), describeCurrentMode(t, c.mode || "lan")],
85
98
  [t("network.relayHealth"), relayHealth],
86
99
  [t("network.currentRelay"), dx.signaling_url || "-"],
@@ -93,7 +106,7 @@ export function createNetworkController({
93
106
 
94
107
  const comp = c.components || {};
95
108
  const lim = c.limits || {};
96
- document.getElementById("networkComponents").textContent = [
109
+ const networkComponentsText = [
97
110
  `demo_mode: ${c.demo_mode || "-"}`,
98
111
  `transport: ${comp.transport || "-"}`,
99
112
  `discovery: ${comp.discovery || "-"}`,
@@ -130,12 +143,39 @@ export function createNetworkController({
130
143
  `last_discovery_event_at: ${dx.last_discovery_event_at ? new Date(dx.last_discovery_event_at).toISOString() : "-"}`,
131
144
  ].join("\n");
132
145
 
133
- document.getElementById("networkConfigSnapshot").textContent = toPrettyJson({
146
+ const networkConfigSnapshotText = toPrettyJson({
134
147
  config: c,
135
148
  adapter_config: ac,
136
149
  runtime_paths: runtimePaths,
137
150
  });
138
- document.getElementById("networkStatsSnapshot").textContent = toPrettyJson({ stats: s });
151
+ const networkStatsSnapshotText = toPrettyJson({ stats: s });
152
+ const renderKey = JSON.stringify({
153
+ adapter: c.adapter || "",
154
+ mode: c.mode || "",
155
+ namespace: c.namespace || "",
156
+ port: c.port ?? null,
157
+ signaling_url: dx.signaling_url || "",
158
+ room: dx.room || "",
159
+ relay_health: relayHealth,
160
+ last_poll_at: dx.last_poll_at || 0,
161
+ last_publish_at: dx.last_publish_at || 0,
162
+ last_error: dx.last_error || "",
163
+ msg_received_total: msg.received_total ?? 0,
164
+ msg_broadcast_total: msg.broadcast_total ?? 0,
165
+ peers_total: p.total ?? 0,
166
+ peers_online: p.online ?? 0,
167
+ reconnect_attempts_total: dx.reconnect_attempts_total ?? 0,
168
+ active_webrtc_peers: dx.active_webrtc_peers ?? 0,
169
+ });
170
+ if (renderKey === lastNetworkRenderKey) {
171
+ return;
172
+ }
173
+ document.getElementById("networkCards").innerHTML = networkCardsHtml;
174
+ document.getElementById("networkSummaryList").innerHTML = networkSummaryHtml;
175
+ document.getElementById("networkComponents").textContent = networkComponentsText;
176
+ document.getElementById("networkConfigSnapshot").textContent = networkConfigSnapshotText;
177
+ document.getElementById("networkStatsSnapshot").textContent = networkStatsSnapshotText;
178
+ lastNetworkRenderKey = renderKey;
139
179
  }
140
180
 
141
181
  async function refreshPeers() {
@@ -143,8 +183,7 @@ export function createNetworkController({
143
183
  const peers = peerRes.data || {};
144
184
  const ds = statsRes.data?.adapter_discovery_stats || {};
145
185
  const summary = peers.diagnostics_summary || {};
146
-
147
- document.getElementById("peerCards").innerHTML = [
186
+ const peerCardsHtml = [
148
187
  [t("network.total"), peers.total || 0],
149
188
  [t("overview.online"), peers.online || 0],
150
189
  [t("network.stale"), peers.stale || 0],
@@ -164,17 +203,15 @@ export function createNetworkController({
164
203
  [t("network.peersAdded"), ds.peers_added || 0],
165
204
  [t("network.peersRemoved"), ds.peers_removed || 0],
166
205
  ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join("");
206
+ const peerStatsText = toPrettyJson({
207
+ discovery_stats: ds,
208
+ diagnostics_summary: summary,
209
+ adapter_stats: statsRes.data?.adapter_stats || {},
210
+ });
167
211
 
168
- if (!peers.items || !peers.items.length) {
169
- document.getElementById("peerTableWrap").innerHTML = `<div class="empty-state">${t("network.noPeersDiscovered")}</div>`;
170
- document.getElementById("peerStatsWrap").textContent = toPrettyJson({
171
- discovery_stats: ds,
172
- diagnostics_summary: summary,
173
- });
174
- return;
175
- }
176
-
177
- document.getElementById("peerTableWrap").innerHTML = `
212
+ const peerTableHtml = !peers.items || !peers.items.length
213
+ ? `<div class="empty-state">${t("network.noPeersDiscovered")}</div>`
214
+ : `
178
215
  <table class="table">
179
216
  <thead><tr><th>${t("network.peer")}</th><th>${t("network.status")}</th><th>${t("network.lastSeen")}</th><th>${t("network.staleSince")}</th><th>${t("network.messages")}</th><th>${t("network.firstSeen")}</th><th>${t("network.meta")}</th></tr></thead>
180
217
  <tbody>
@@ -192,19 +229,51 @@ export function createNetworkController({
192
229
  </tbody>
193
230
  </table>
194
231
  `;
195
- document.getElementById("peerStatsWrap").textContent = toPrettyJson({
196
- discovery_stats: ds,
197
- diagnostics_summary: summary,
198
- adapter_stats: statsRes.data?.adapter_stats || {},
232
+ const renderKey = JSON.stringify({
233
+ total: peers.total || 0,
234
+ online: peers.online || 0,
235
+ stale: peers.stale || 0,
236
+ namespace: peers.namespace || "",
237
+ summary: {
238
+ signaling_url: summary.signaling_url || "",
239
+ room: summary.room || "",
240
+ last_join_at: summary.last_join_at || 0,
241
+ last_poll_at: summary.last_poll_at || 0,
242
+ last_publish_at: summary.last_publish_at || 0,
243
+ last_error: summary.last_error || "",
244
+ },
245
+ ds: {
246
+ observe_calls: ds.observe_calls || 0,
247
+ heartbeat_sent: ds.heartbeat_sent || 0,
248
+ peers_added: ds.peers_added || 0,
249
+ peers_removed: ds.peers_removed || 0,
250
+ },
251
+ items: Array.isArray(peers.items)
252
+ ? peers.items.map((peer) => [
253
+ peer.peer_id,
254
+ peer.status || "",
255
+ peer.last_seen_at || 0,
256
+ peer.stale_since_at || 0,
257
+ peer.messages_seen || 0,
258
+ peer.first_seen_at || 0,
259
+ peer.meta ? JSON.stringify(peer.meta) : "",
260
+ ])
261
+ : [],
199
262
  });
263
+ if (renderKey === lastPeersRenderKey) {
264
+ return;
265
+ }
266
+ document.getElementById("peerCards").innerHTML = peerCardsHtml;
267
+ document.getElementById("peerTableWrap").innerHTML = peerTableHtml;
268
+ document.getElementById("peerStatsWrap").textContent = peerStatsText;
269
+ lastPeersRenderKey = renderKey;
200
270
  }
201
271
 
202
272
  async function refreshDiscovery() {
203
273
  const eventsRes = await api("/api/discovery/events");
204
274
  const payload = eventsRes.data || {};
205
275
  const items = Array.isArray(payload.items) ? payload.items : [];
206
-
207
- document.getElementById("discoveryCards").innerHTML = [
276
+ const discoveryCardsHtml = [
208
277
  [t("labels.adapter"), payload.adapter || "-"],
209
278
  [t("labels.namespace"), payload.namespace || "-"],
210
279
  [t("network.eventsTotal"), payload.total ?? 0],
@@ -212,11 +281,9 @@ export function createNetworkController({
212
281
  [t("network.signalingEndpoints"), (payload.signaling_endpoints || []).length || 0],
213
282
  [t("network.seedPeers"), payload.seed_peers_count ?? 0],
214
283
  ].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join("");
215
-
216
- if (!items.length) {
217
- document.getElementById("discoveryEventList").innerHTML = `<div class="empty-state">${t("network.noDiscoveryEvents")}</div>`;
218
- } else {
219
- document.getElementById("discoveryEventList").innerHTML = items
284
+ const discoveryEventListHtml = !items.length
285
+ ? `<div class="empty-state">${t("network.noDiscoveryEvents")}</div>`
286
+ : items
220
287
  .slice()
221
288
  .reverse()
222
289
  .map((event) => `
@@ -227,9 +294,23 @@ export function createNetworkController({
227
294
  </div>
228
295
  `)
229
296
  .join("");
297
+ const discoverySnapshotText = toPrettyJson(payload);
298
+ const renderKey = JSON.stringify({
299
+ adapter: payload.adapter || "",
300
+ namespace: payload.namespace || "",
301
+ total: payload.total ?? 0,
302
+ last_event_at: payload.last_event_at || 0,
303
+ signaling_endpoints: payload.signaling_endpoints || [],
304
+ seed_peers_count: payload.seed_peers_count ?? 0,
305
+ items: items.map((event) => [event.type || "", event.peer_id || "", event.endpoint || "", event.detail || "", event.at || 0]),
306
+ });
307
+ if (renderKey === lastDiscoveryRenderKey) {
308
+ return;
230
309
  }
231
-
232
- document.getElementById("discoverySnapshot").textContent = toPrettyJson(payload);
310
+ document.getElementById("discoveryCards").innerHTML = discoveryCardsHtml;
311
+ document.getElementById("discoveryEventList").innerHTML = discoveryEventListHtml;
312
+ document.getElementById("discoverySnapshot").textContent = discoverySnapshotText;
313
+ lastDiscoveryRenderKey = renderKey;
233
314
  }
234
315
 
235
316
  return {