@silicaclaw/cli 2026.3.20-2 → 2026.3.20-20

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 +102 -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 +1018 -91
  7. package/apps/local-console/dist/packages/core/src/index.d.ts +2 -0
  8. package/apps/local-console/dist/packages/core/src/index.js +2 -0
  9. package/apps/local-console/dist/packages/core/src/privateCrypto.d.ts +17 -0
  10. package/apps/local-console/dist/packages/core/src/privateCrypto.js +40 -0
  11. package/apps/local-console/dist/packages/core/src/privateMessage.d.ts +23 -0
  12. package/apps/local-console/dist/packages/core/src/privateMessage.js +74 -0
  13. package/apps/local-console/dist/packages/core/src/profile.js +2 -0
  14. package/apps/local-console/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
  15. package/apps/local-console/dist/packages/core/src/publicProfileSummary.js +3 -0
  16. package/apps/local-console/dist/packages/core/src/types.d.ts +40 -0
  17. package/apps/local-console/dist/packages/network/src/relayPreview.d.ts +12 -0
  18. package/apps/local-console/dist/packages/network/src/relayPreview.js +108 -8
  19. package/apps/local-console/dist/packages/network/src/types.d.ts +4 -0
  20. package/apps/local-console/dist/packages/storage/src/repos.d.ts +27 -1
  21. package/apps/local-console/dist/packages/storage/src/repos.js +35 -1
  22. package/apps/local-console/public/app/app.js +472 -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 +430 -316
  30. package/apps/local-console/src/server.ts +1164 -89
  31. package/apps/public-explorer/public/app/template.js +2 -2
  32. package/apps/public-explorer/public/app/translations.js +36 -36
  33. package/docs/NEW_USER_OPERATIONS.md +5 -5
  34. package/docs/OPENCLAW_BRIDGE.md +7 -7
  35. package/docs/OPENCLAW_BRIDGE_ZH.md +6 -6
  36. package/node_modules/@silicaclaw/core/dist/packages/core/src/index.d.ts +2 -0
  37. package/node_modules/@silicaclaw/core/dist/packages/core/src/index.js +2 -0
  38. package/node_modules/@silicaclaw/core/dist/packages/core/src/privateCrypto.d.ts +17 -0
  39. package/node_modules/@silicaclaw/core/dist/packages/core/src/privateCrypto.js +40 -0
  40. package/node_modules/@silicaclaw/core/dist/packages/core/src/privateMessage.d.ts +23 -0
  41. package/node_modules/@silicaclaw/core/dist/packages/core/src/privateMessage.js +74 -0
  42. package/node_modules/@silicaclaw/core/dist/packages/core/src/profile.js +2 -0
  43. package/node_modules/@silicaclaw/core/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
  44. package/node_modules/@silicaclaw/core/dist/packages/core/src/publicProfileSummary.js +3 -0
  45. package/node_modules/@silicaclaw/core/dist/packages/core/src/types.d.ts +40 -0
  46. package/node_modules/@silicaclaw/core/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
@@ -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,13 +52,15 @@ 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'));
58
59
  setText('[data-tab="agent"] .tab-copy', t('labels.agentTabCopy'));
59
60
  setText('[data-tab="chat"] .tab-title', t('pageMeta.chat.title'));
60
61
  setText('[data-tab="chat"] .tab-copy', t('labels.chatTabCopy'));
62
+ setText('[data-tab="private"] .tab-title', t('pageMeta.private.title'));
63
+ setText('[data-tab="private"] .tab-copy', t('labels.privateTabCopy'));
61
64
  setText('[data-tab="skills"] .tab-title', t('pageMeta.skills.title'));
62
65
  setText('[data-tab="skills"] .tab-copy', t('labels.skillsTabCopy'));
63
66
  setText('[data-tab="profile"] .tab-title', t('pageMeta.profile.title'));
@@ -70,6 +73,10 @@ root.innerHTML = appTemplate;
70
73
  document.getElementById('sidebarToggleBtn').setAttribute('aria-label', t('labels.collapseSidebar'));
71
74
  document.querySelector('.sidebar-version').title = t('common.version');
72
75
  setText('.sidebar-version__label', t('common.version'));
76
+ document.getElementById('brandUpdateHint').textContent = t('labels.versionChecking');
77
+ document.getElementById('brandRelayHint').textContent = t('labels.relayQueuesHealthy');
78
+ document.getElementById('brandCheckUpdateBtn').textContent = t('actions.checkUpdate');
79
+ document.getElementById('brandUpdateBtn').textContent = t('actions.updateNow');
73
80
  document.getElementById('integrationStatusBar').textContent = t('social.barStatus', {
74
81
  connected: '-',
75
82
  mode: '-',
@@ -103,22 +110,38 @@ root.innerHTML = appTemplate;
103
110
  document.getElementById('chatBannerBody').textContent = t('hints.chatBannerBody');
104
111
  document.getElementById('chatBannerFeedLabel').textContent = t('hints.chatBannerFeed');
105
112
  document.getElementById('chatComposerTitle').textContent = t('actions.sendPublicMessage');
113
+ document.getElementById('privateBannerEyebrow').textContent = t('pageMeta.private.title');
114
+ document.getElementById('privateBannerTitle').textContent = t('pageMeta.private.body');
115
+ document.getElementById('privateBannerBody').textContent = t('pageMeta.private.body');
116
+ document.getElementById('privateBannerStateLabel').textContent = t('labels.state');
117
+ document.getElementById('privateConversationsTitle').textContent = t('pageMeta.private.title');
118
+ document.getElementById('privateComposerTitle').textContent = t('actions.sendPrivateMessage');
119
+ document.getElementById('privateTargetLabel').textContent = t('labels.target');
120
+ document.getElementById('privateTargetIdLabel').textContent = t('social.agentId');
121
+ document.getElementById('privateMessageSendBtn').textContent = t('actions.sendPrivateMessage');
122
+ document.getElementById('privateRefreshBtn').textContent = t('actions.refreshPrivate');
106
123
  document.getElementById('chatFeedHint').textContent = t('hints.chatFeedHint');
107
124
  document.getElementById('overviewGuideTitle').textContent = t('overview.guideTitle');
108
125
  document.getElementById('overviewGuideBody').textContent = t('overview.guideBody');
126
+ document.getElementById('overviewGuideStatus').textContent = t('overview.guideNeedSetup');
109
127
  document.getElementById('overviewStepProfileEyebrow').textContent = t('overview.stepLabel', { step: '1' });
110
128
  document.getElementById('overviewStepProfileTitle').textContent = t('overview.stepProfileTitle');
111
129
  document.getElementById('overviewStepProfileBody').textContent = t('overview.stepProfileBody');
130
+ document.getElementById('overviewStepProfileStatus').textContent = t('overview.stepIncomplete');
112
131
  document.getElementById('overviewStepProfileBtn').textContent = t('actions.editProfile');
113
132
  document.getElementById('overviewStepPublicEyebrow').textContent = t('overview.stepLabel', { step: '2' });
114
133
  document.getElementById('overviewStepPublicTitle').textContent = t('overview.stepPublicTitle');
115
134
  document.getElementById('overviewStepPublicBody').textContent = t('overview.stepPublicBody');
135
+ document.getElementById('overviewStepPublicStatus').textContent = t('overview.stepIncomplete');
116
136
  document.getElementById('overviewStepPublicBtn').textContent = t('actions.editProfile');
117
137
  document.getElementById('overviewStepBroadcastEyebrow').textContent = t('overview.stepLabel', { step: '3' });
118
138
  document.getElementById('overviewStepBroadcastTitle').textContent = t('overview.stepBroadcastTitle');
119
139
  document.getElementById('overviewStepBroadcastBody').textContent = t('overview.stepBroadcastBody');
140
+ document.getElementById('overviewStepBroadcastStatus').textContent = t('overview.stepWaiting');
120
141
  document.getElementById('overviewStepBroadcastBtn').textContent = t('actions.broadcastNow');
121
142
  document.getElementById('homeMissionEyebrow').textContent = t('hints.homeMissionEyebrow');
143
+ document.getElementById('homeMissionTitle').textContent = t('hints.homeMissionTitle');
144
+ document.getElementById('homeMissionBody').textContent = t('hints.homeMissionBody');
122
145
  document.getElementById('homeBriefTitle').textContent = t('hints.homeBriefTitle');
123
146
  document.getElementById('homeOpenAgentBtn').textContent = t('actions.openAgent');
124
147
  document.getElementById('homeOpenSocialBtn').textContent = t('pageMeta.social.title');
@@ -156,7 +179,7 @@ root.innerHTML = appTemplate;
156
179
  setText('#view-profile .profile-meta h4', t('labels.publicCard'), 0);
157
180
  setText('#view-profile .profile-meta h4', t('labels.publishStatus'), 1);
158
181
  setText('#view-profile .profile-meta h4', t('labels.publicProfilePreview'), 2);
159
- setText('#view-profile .profile-meta .field-hint', t('hints.signedPublicProfileHint'));
182
+ setText('#view-profile .profile-meta .field-hint', t('hints.signedPublicProfileHint'), 1);
160
183
  setText('#view-network .section-header__eyebrow', t('labels.networkEyebrow'));
161
184
  document.getElementById('networkBannerTitle').textContent = t('hints.networkBannerTitle');
162
185
  document.getElementById('networkBannerBody').textContent = t('hints.networkBannerBody');
@@ -190,6 +213,7 @@ root.innerHTML = appTemplate;
190
213
  document.getElementById('skillsBannerTitle').textContent = t('hints.skillsBannerTitle');
191
214
  document.getElementById('skillsBannerBody').textContent = t('hints.skillsBannerBody');
192
215
  document.getElementById('skillsBannerRuntimeLabel').textContent = t('hints.skillsBannerRuntime');
216
+ document.getElementById('skillsBannerRuntimeValue').textContent = t('hints.skillsRuntimeChecking');
193
217
  document.getElementById('skillsActionEyebrow').textContent = t('labels.skillsRecommendedAction');
194
218
  document.getElementById('skillsActionTitle').textContent = t('hints.skillsActionInstallTitle');
195
219
  document.getElementById('skillsActionBody').textContent = t('hints.skillsActionInstallBody');
@@ -200,6 +224,16 @@ root.innerHTML = appTemplate;
200
224
  document.getElementById('skillsJumpBundled').textContent = t('labels.skillsBundled');
201
225
  document.getElementById('skillsJumpInstalled').textContent = t('labels.skillsInstalled');
202
226
  document.getElementById('skillsJumpDialogue').textContent = t('labels.skillsDialogue');
227
+ document.getElementById('skillsSearchLabel').textContent = t('labels.skillsSearch');
228
+ document.getElementById('skillsSearchInput').placeholder = t('placeholders.skillsSearch');
229
+ document.getElementById('skillsFilterAll').textContent = t('labels.skillsFilterAll');
230
+ document.getElementById('skillsFilterAttention').textContent = t('labels.skillsFilterAttention');
231
+ document.getElementById('skillsFilterUpdates').textContent = t('labels.skillsFilterUpdates');
232
+ document.getElementById('skillsFilterInstalled').textContent = t('labels.skillsFilterInstalled');
233
+ document.getElementById('skillsFilterMeta').textContent = t('hints.skillsFilterMeta', {
234
+ count: '0',
235
+ filter: t('labels.skillsFilterAll'),
236
+ });
203
237
  document.getElementById('skillsFeaturedTitle').textContent = t('labels.skillsFeatured');
204
238
  document.getElementById('skillsFeaturedHint').textContent = t('hints.skillsFeaturedHint');
205
239
  document.getElementById('skillsBundledTitle').textContent = t('labels.skillsBundled');
@@ -219,6 +253,7 @@ root.innerHTML = appTemplate;
219
253
  document.getElementById('socialSkillLearningTitle').textContent = t('labels.openclawSkillLearning');
220
254
  document.getElementById('socialMessagePathTitle').textContent = t('labels.messagePath');
221
255
  document.getElementById('socialMessagePathHint').textContent = t('hints.socialMessagePathHint');
256
+ document.getElementById('socialOwnerDeliveryStatus').textContent = t('hints.checkingOwnerDelivery');
222
257
  document.getElementById('socialGovernanceTitle').textContent = t('labels.messageGovernance');
223
258
  document.getElementById('socialModerationTitle').textContent = t('labels.recentModeration');
224
259
  document.getElementById('socialAdvancedSummary').textContent = t('labels.advancedNetworkDetails');
@@ -240,6 +275,10 @@ root.innerHTML = appTemplate;
240
275
  setText('.hero-meta-item .label', t('labels.room'), 3);
241
276
  document.getElementById('publicDiscoveryHint').innerHTML = t('hints.publicDiscoverySwitch');
242
277
  document.getElementById('clearDiscoveryCacheBtn').textContent = t('actions.clearDiscoveryCache');
278
+ document.getElementById('overviewModeHint').textContent = t('overview.modeCurrentSource', {
279
+ mode: '-',
280
+ hint: t('overview.modeCacheHint'),
281
+ });
243
282
  document.getElementById('socialMessageTitle').textContent = t('overview.messageTitle');
244
283
  document.getElementById('socialMessageMeta').textContent = t('overview.messageMetaInitial');
245
284
  document.getElementById('socialMessageHint').textContent = t('overview.messageHint');
@@ -262,6 +301,7 @@ root.innerHTML = appTemplate;
262
301
  document.querySelector('label[for="governanceBlockedTermsInput"]').textContent = t('labels.blockedTerms');
263
302
  document.getElementById('startBroadcastBtn').textContent = t('actions.startBroadcast');
264
303
  document.getElementById('stopBroadcastBtn').textContent = t('actions.stopBroadcast');
304
+ document.getElementById('broadcastNowBtn').textContent = t('actions.broadcastNow');
265
305
  document.getElementById('quickGlobalPreviewBtn').textContent = t('actions.enablePreview');
266
306
  document.getElementById('refreshLogsBtn').textContent = t('actions.refreshLogs');
267
307
  document.getElementById('socialExportBtn').textContent = t('actions.exportTemplate');
@@ -296,6 +336,216 @@ root.innerHTML = appTemplate;
296
336
  toast,
297
337
  writeUiCache,
298
338
  } = shell;
339
+ let appUpdatePollTimer = null;
340
+ let appUpdateCheckInFlight = false;
341
+ let relayQueueCheckInFlight = false;
342
+ let lastRelayQueueCheckAt = 0;
343
+
344
+ function setAppUpdateUi({
345
+ hint,
346
+ buttonVisible = false,
347
+ buttonDisabled = false,
348
+ buttonText = t('actions.updateNow'),
349
+ checkVisible = false,
350
+ checkDisabled = false,
351
+ }) {
352
+ const hintEl = document.getElementById('brandUpdateHint');
353
+ const buttonEl = document.getElementById('brandUpdateBtn');
354
+ const checkEl = document.getElementById('brandCheckUpdateBtn');
355
+ if (hintEl) {
356
+ hintEl.textContent = hint;
357
+ hintEl.classList.toggle('hidden', !hint);
358
+ }
359
+ if (checkEl) {
360
+ checkEl.textContent = t('actions.checkUpdate');
361
+ checkEl.classList.toggle('hidden', !checkVisible);
362
+ checkEl.disabled = checkDisabled;
363
+ }
364
+ if (buttonEl) {
365
+ buttonEl.textContent = buttonText;
366
+ buttonEl.classList.toggle('hidden', !buttonVisible);
367
+ buttonEl.disabled = buttonDisabled;
368
+ }
369
+ }
370
+
371
+ function platformUpdateHint(platform) {
372
+ if (platform === 'darwin') return t('labels.versionPlatformMac');
373
+ if (platform === 'linux') return t('labels.versionPlatformLinux');
374
+ return t('labels.versionPlatformOther');
375
+ }
376
+
377
+ function setRelayQueueUi({ hint = '', tone = 'ok', visible = false }) {
378
+ const hintEl = document.getElementById('brandRelayHint');
379
+ if (!hintEl) return;
380
+ hintEl.textContent = hint;
381
+ hintEl.classList.toggle('hidden', !visible || !hint);
382
+ hintEl.classList.remove('warn', 'danger');
383
+ if (tone === 'warn' || tone === 'danger') {
384
+ hintEl.classList.add(tone);
385
+ }
386
+ }
387
+
388
+ async function refreshRelayQueueStatus({ force = false } = {}) {
389
+ const now = Date.now();
390
+ if (relayQueueCheckInFlight) return null;
391
+ if (!force && now - lastRelayQueueCheckAt < 15_000) return null;
392
+ relayQueueCheckInFlight = true;
393
+ try {
394
+ const result = await api('/api/peers');
395
+ const peers = result.data || {};
396
+ const peerItems = Array.isArray(peers.items) ? peers.items : [];
397
+ const relayQueueMax = peerItems.reduce((max, peer) => Math.max(max, Number(peer?.meta?.relay_queue_size || 0)), 0);
398
+ const signalQueueMax = peerItems.reduce((max, peer) => Math.max(max, Number(peer?.meta?.signal_queue_size || 0)), 0);
399
+ const queueMax = Math.max(relayQueueMax, signalQueueMax);
400
+ if (queueMax >= 100) {
401
+ setRelayQueueUi({ hint: t('labels.relayQueuesHigh'), tone: 'danger', visible: true });
402
+ } else if (queueMax >= 20) {
403
+ setRelayQueueUi({ hint: t('labels.relayQueuesWatch'), tone: 'warn', visible: true });
404
+ } else {
405
+ setRelayQueueUi({ hint: t('labels.relayQueuesHealthy'), tone: 'ok', visible: true });
406
+ }
407
+ lastRelayQueueCheckAt = now;
408
+ return { relayQueueMax, signalQueueMax };
409
+ } catch (_error) {
410
+ return null;
411
+ } finally {
412
+ relayQueueCheckInFlight = false;
413
+ }
414
+ }
415
+
416
+ async function refreshAppUpdateStatus({ silent = false } = {}) {
417
+ if (appUpdateCheckInFlight) return null;
418
+ appUpdateCheckInFlight = true;
419
+ try {
420
+ const result = await api('/api/app/update-status');
421
+ const status = result.data || {};
422
+ const currentVersion = String(status.current_version || '').trim();
423
+ const latestVersion = String(status.latest_version || '').trim();
424
+ const platformHint = platformUpdateHint(String(status.platform || ''));
425
+ if (currentVersion) {
426
+ document.getElementById('brandVersion').textContent = currentVersion.startsWith('v') ? currentVersion : `v${currentVersion}`;
427
+ }
428
+ if (status.update_available && status.latest_version) {
429
+ setAppUpdateUi({
430
+ hint: `${t('labels.versionUpdateReady', { version: `v${status.latest_version}` })} · ${platformHint}`,
431
+ buttonVisible: true,
432
+ buttonDisabled: false,
433
+ buttonText: t('actions.updateNowVersion', { version: latestVersion.startsWith('v') ? latestVersion : `v${latestVersion}` }),
434
+ checkVisible: true,
435
+ checkDisabled: false,
436
+ });
437
+ } else if (status.check_error) {
438
+ setAppUpdateUi({
439
+ hint: t('labels.versionCheckFailed'),
440
+ buttonVisible: false,
441
+ buttonDisabled: false,
442
+ buttonText: t('actions.updateNow'),
443
+ checkVisible: true,
444
+ checkDisabled: false,
445
+ });
446
+ if (!silent) {
447
+ setFeedback('networkFeedback', t('feedback.appUpdateCheckFailed'), 'warn');
448
+ }
449
+ } else {
450
+ setAppUpdateUi({
451
+ hint: `${t('labels.versionCurrent')} · ${platformHint}`,
452
+ buttonVisible: false,
453
+ buttonDisabled: false,
454
+ buttonText: t('actions.updateNow'),
455
+ checkVisible: true,
456
+ checkDisabled: false,
457
+ });
458
+ }
459
+ return status;
460
+ } catch (_error) {
461
+ setAppUpdateUi({
462
+ hint: t('labels.versionCheckFailed'),
463
+ buttonVisible: false,
464
+ buttonDisabled: false,
465
+ buttonText: t('actions.updateNow'),
466
+ checkVisible: true,
467
+ checkDisabled: false,
468
+ });
469
+ if (!silent) {
470
+ setFeedback('networkFeedback', t('feedback.appUpdateCheckFailed'), 'warn');
471
+ }
472
+ return null;
473
+ } finally {
474
+ appUpdateCheckInFlight = false;
475
+ }
476
+ }
477
+
478
+ function startAppUpdatePolling(targetVersion) {
479
+ if (appUpdatePollTimer) {
480
+ window.clearInterval(appUpdatePollTimer);
481
+ }
482
+ let attempts = 0;
483
+ appUpdatePollTimer = window.setInterval(async () => {
484
+ attempts += 1;
485
+ const status = await refreshAppUpdateStatus({ silent: true });
486
+ if (status && !status.update_available && String(status.current_version || '') === String(targetVersion || '')) {
487
+ window.clearInterval(appUpdatePollTimer);
488
+ appUpdatePollTimer = null;
489
+ if (targetVersion) {
490
+ window.sessionStorage.setItem(APP_UPDATE_SESSION_KEY, String(targetVersion));
491
+ }
492
+ window.location.reload();
493
+ return;
494
+ }
495
+ if (attempts >= 24) {
496
+ window.clearInterval(appUpdatePollTimer);
497
+ appUpdatePollTimer = null;
498
+ setAppUpdateUi({
499
+ hint: t('labels.versionCurrent'),
500
+ buttonVisible: false,
501
+ buttonDisabled: false,
502
+ buttonText: t('actions.updateNow'),
503
+ checkVisible: true,
504
+ checkDisabled: false,
505
+ });
506
+ }
507
+ }, 5000);
508
+ }
509
+
510
+ async function triggerAppUpdate() {
511
+ const buttonEl = document.getElementById('brandUpdateBtn');
512
+ setAppUpdateUi({
513
+ hint: t('labels.versionUpdating'),
514
+ buttonVisible: true,
515
+ buttonDisabled: true,
516
+ buttonText: t('labels.versionUpdating'),
517
+ checkVisible: true,
518
+ checkDisabled: true,
519
+ });
520
+ try {
521
+ const result = await api('/api/app/update', { method: 'POST' });
522
+ const data = result.data || {};
523
+ if (!data.started) {
524
+ setAppUpdateUi({
525
+ hint: t('labels.versionCurrent'),
526
+ buttonVisible: false,
527
+ buttonDisabled: false,
528
+ buttonText: t('actions.updateNow'),
529
+ checkVisible: true,
530
+ checkDisabled: false,
531
+ });
532
+ toast(t('feedback.appUpdateLatest'));
533
+ return;
534
+ }
535
+ toast(t('feedback.appUpdateStarted'));
536
+ startAppUpdatePolling(String(data.target_version || ''));
537
+ } catch (error) {
538
+ setAppUpdateUi({
539
+ hint: t('labels.versionCheckFailed'),
540
+ buttonVisible: true,
541
+ buttonDisabled: false,
542
+ buttonText: t('actions.updateNow'),
543
+ checkVisible: true,
544
+ checkDisabled: false,
545
+ });
546
+ setFeedback('networkFeedback', error instanceof Error ? error.message : t('feedback.appUpdateFailed'), 'error');
547
+ }
548
+ }
299
549
  setLocale(currentLocale);
300
550
  applyStaticTranslations();
301
551
 
@@ -309,6 +559,11 @@ root.innerHTML = appTemplate;
309
559
  let visibleRemotePublicCount = 0;
310
560
  let socialMessageFilter = 'all';
311
561
  let socialMessageGovernance = null;
562
+ let privateState = null;
563
+ let privateConversations = [];
564
+ let privateMessages = [];
565
+ let privateTarget = null;
566
+ let selectedPrivateConversationId = '';
312
567
  let overviewMode = 'lan';
313
568
  let onlyShowOnline = false;
314
569
  let agentsPage = 1;
@@ -392,7 +647,7 @@ root.innerHTML = appTemplate;
392
647
  }
393
648
  activeTab = tab;
394
649
  document.querySelectorAll('.tab').forEach((b) => b.classList.toggle('active', b.dataset.tab === tab));
395
- ['overview', 'agent', 'chat', 'skills', 'profile', 'network', 'social'].forEach((k) => {
650
+ ['overview', 'agent', 'chat', 'private', 'skills', 'profile', 'network', 'social'].forEach((k) => {
396
651
  document.getElementById(`view-${k}`).classList.toggle('active', k === tab);
397
652
  });
398
653
  const meta = pageMeta[tab] || pageMeta.overview;
@@ -404,6 +659,25 @@ root.innerHTML = appTemplate;
404
659
  document.getElementById('publicDiscoveryHint')?.classList.toggle('hidden', tab !== 'overview');
405
660
  if (tab === 'profile' && !profileController.isDirty() && !profileController.isSaving()) {
406
661
  refreshProfile().catch(() => {});
662
+ } else if (tab === 'overview') {
663
+ refreshOverview().catch(() => {});
664
+ refreshMessages().catch(() => {});
665
+ } else if (tab === 'agent') {
666
+ refreshOverview().catch(() => {});
667
+ } else if (tab === 'chat') {
668
+ refreshMessages().catch(() => {});
669
+ } else if (tab === 'private') {
670
+ refreshPrivate().catch(() => {});
671
+ } else if (tab === 'skills') {
672
+ refreshSkills().catch(() => {});
673
+ } else if (tab === 'network') {
674
+ refreshNetwork().catch(() => {});
675
+ refreshPeers().catch(() => {});
676
+ refreshDiscovery().catch(() => {});
677
+ refreshLogs().catch(() => {});
678
+ } else if (tab === 'social') {
679
+ refreshSocial().catch(() => {});
680
+ refreshMessages().catch(() => {});
407
681
  }
408
682
  }
409
683
 
@@ -417,6 +691,81 @@ root.innerHTML = appTemplate;
417
691
  });
418
692
  }
419
693
 
694
+ function renderPrivate() {
695
+ const privateDeliveryLabel = (status) => {
696
+ if (status === 'direct-sent') return 'Direct';
697
+ if (status === 'fallback-sent') return 'Fallback';
698
+ if (status === 'received') return 'Received';
699
+ if (status === 'read') return 'Read';
700
+ return 'Sent';
701
+ };
702
+ document.getElementById('privateStateMeta').textContent = privateState?.enabled
703
+ ? `${privateConversations.length} conversation(s)`
704
+ : 'Private messaging unavailable';
705
+ document.getElementById('privateTargetName').value = privateTarget?.display_name || '';
706
+ document.getElementById('privateTargetAgentId').value = privateTarget?.agent_id || '';
707
+ document.getElementById('privateConversationList').innerHTML = privateConversations.length
708
+ ? privateConversations.map((item) => `
709
+ <button class="agent-card" type="button" data-private-conversation="${escapeHtml(item.conversation_id)}">
710
+ <div class="agent-card__avatar-fallback">${escapeHtml(((item.peer_display_name || item.peer_agent_id || '?')[0] || '?').toUpperCase())}</div>
711
+ <div class="agent-card__main">
712
+ <div class="agent-card__row">
713
+ <div class="agent-card__name">${escapeHtml(item.peer_display_name || item.peer_agent_id || 'Unknown')}</div>
714
+ <div class="agent-card__id mono">${escapeHtml(shortId(item.peer_agent_id || ''))}</div>
715
+ </div>
716
+ </div>
717
+ </button>
718
+ `).join('')
719
+ : `<div class="empty-state">No private conversations yet.</div>`;
720
+ document.getElementById('privateMessageList').innerHTML = privateMessages.length
721
+ ? privateMessages.map((item) => `
722
+ <div class="log-item">
723
+ <div style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
724
+ <div>
725
+ <strong>${item.is_self ? 'Me' : escapeHtml(privateTarget?.display_name || item.from_agent_id || 'Unknown')}</strong>
726
+ <span class="tag-chip" style="margin-left:8px;">${escapeHtml(privateDeliveryLabel(item.delivery_status))}</span>
727
+ </div>
728
+ <div class="mono" style="color:#90a2c3;">${new Date(item.created_at).toLocaleString()}</div>
729
+ </div>
730
+ <div style="margin-top:8px; line-height:1.6;">${formatMessageBody(item.body || '')}</div>
731
+ </div>
732
+ `).join('')
733
+ : `<div class="empty-state">No private messages yet.</div>`;
734
+ }
735
+
736
+ async function refreshPrivate() {
737
+ const [stateRes, conversationsRes] = await Promise.all([
738
+ api('/api/private/state'),
739
+ api('/api/private/conversations'),
740
+ ]);
741
+ privateState = stateRes.data || null;
742
+ privateConversations = Array.isArray(conversationsRes.data) ? conversationsRes.data : [];
743
+ if ((!privateTarget || privateTarget.agent_id === privateState?.agent_id) && privateConversations[0]) {
744
+ const first = privateConversations[0];
745
+ privateTarget = {
746
+ agent_id: first.peer_agent_id,
747
+ display_name: first.peer_display_name,
748
+ private_encryption_public_key: first.peer_public_key,
749
+ };
750
+ selectedPrivateConversationId = first.conversation_id;
751
+ }
752
+ const selectedConversation = privateConversations.find((item) => item.conversation_id === selectedPrivateConversationId);
753
+ if (selectedConversation) {
754
+ privateTarget = {
755
+ agent_id: selectedConversation.peer_agent_id,
756
+ display_name: selectedConversation.peer_display_name,
757
+ private_encryption_public_key: selectedConversation.peer_public_key,
758
+ };
759
+ }
760
+ if (selectedPrivateConversationId) {
761
+ const messageRes = await api(`/api/private/messages?conversation_id=${encodeURIComponent(selectedPrivateConversationId)}&limit=100`);
762
+ privateMessages = Array.isArray(messageRes.data) ? messageRes.data : [];
763
+ } else {
764
+ privateMessages = [];
765
+ }
766
+ renderPrivate();
767
+ }
768
+
420
769
  const renderSocialMessages = socialController.renderSocialMessages;
421
770
  const refreshMessages = socialController.refreshMessages;
422
771
 
@@ -430,14 +779,25 @@ root.innerHTML = appTemplate;
430
779
  const renderLogs = socialController.renderLogs;
431
780
  const refreshLogs = socialController.refreshLogs;
432
781
  const refreshSkills = socialController.refreshSkills;
782
+ let autoRefreshInFlight = false;
433
783
 
434
- async function refreshAll() {
435
- const tasks = [refreshOverview(), refreshNetwork(), refreshSocial(), refreshSkills(), refreshPublicProfilePreview(), refreshMessages()];
436
- if (activeTab === 'network') {
437
- tasks.push(refreshPeers(), refreshDiscovery(), refreshLogs());
438
- }
439
- const shouldRefreshProfile = !(activeTab === 'profile' && profileController.isDirty());
440
- if (shouldRefreshProfile) {
784
+ async function refreshActiveView() {
785
+ const tasks = [refreshPublicProfilePreview(), refreshRelayQueueStatus()];
786
+ if (activeTab === 'overview') {
787
+ tasks.push(refreshOverview(), refreshMessages());
788
+ } else if (activeTab === 'agent') {
789
+ tasks.push(refreshOverview());
790
+ } else if (activeTab === 'chat') {
791
+ tasks.push(refreshMessages());
792
+ } else if (activeTab === 'private') {
793
+ tasks.push(refreshPrivate());
794
+ } else if (activeTab === 'skills') {
795
+ tasks.push(refreshSkills());
796
+ } else if (activeTab === 'network') {
797
+ tasks.push(refreshNetwork(), refreshPeers(), refreshDiscovery(), refreshLogs());
798
+ } else if (activeTab === 'social') {
799
+ tasks.push(refreshSocial(), refreshMessages());
800
+ } else if (activeTab === 'profile' && !profileController.isDirty() && !profileController.isSaving()) {
441
801
  tasks.push(refreshProfile());
442
802
  }
443
803
  const results = await Promise.allSettled(tasks);
@@ -447,6 +807,22 @@ root.innerHTML = appTemplate;
447
807
  }
448
808
  }
449
809
 
810
+ async function refreshAuto() {
811
+ if (document.hidden || autoRefreshInFlight) {
812
+ return;
813
+ }
814
+ autoRefreshInFlight = true;
815
+ try {
816
+ await refreshActiveView();
817
+ } finally {
818
+ autoRefreshInFlight = false;
819
+ }
820
+ }
821
+
822
+ async function refreshAll() {
823
+ await refreshActiveView();
824
+ }
825
+
450
826
  bindAppEvents({
451
827
  api,
452
828
  applyTheme,
@@ -486,8 +862,93 @@ root.innerHTML = appTemplate;
486
862
  toPrettyJson,
487
863
  });
488
864
 
865
+ document.getElementById('agentsWrap').addEventListener('click', (event) => {
866
+ const button = event.target?.closest?.('[data-private-agent]');
867
+ if (!button) return;
868
+ privateTarget = {
869
+ agent_id: String(button.getAttribute('data-private-agent') || ''),
870
+ display_name: String(button.getAttribute('data-private-name') || ''),
871
+ private_encryption_public_key: String(button.getAttribute('data-private-key') || ''),
872
+ };
873
+ selectedPrivateConversationId = [privateState?.agent_id || '', privateTarget.agent_id].sort().join(':');
874
+ switchTab('private');
875
+ });
876
+
877
+ document.getElementById('privateConversationList').addEventListener('click', async (event) => {
878
+ const button = event.target?.closest?.('[data-private-conversation]');
879
+ if (!button) return;
880
+ selectedPrivateConversationId = String(button.getAttribute('data-private-conversation') || '');
881
+ const selectedConversation = privateConversations.find((item) => item.conversation_id === selectedPrivateConversationId);
882
+ if (selectedConversation) {
883
+ privateTarget = {
884
+ agent_id: selectedConversation.peer_agent_id,
885
+ display_name: selectedConversation.peer_display_name,
886
+ private_encryption_public_key: selectedConversation.peer_public_key,
887
+ };
888
+ }
889
+ await refreshPrivate();
890
+ });
891
+
892
+ document.getElementById('privateRefreshBtn').addEventListener('click', async () => {
893
+ await refreshPrivate();
894
+ });
895
+
896
+ document.getElementById('privateMessageSendBtn').addEventListener('click', async () => {
897
+ const body = String(document.getElementById('privateMessageInput').value || '').trim();
898
+ if (!privateTarget?.agent_id || !privateTarget?.private_encryption_public_key) {
899
+ setFeedback('privateFeedback', 'Pick an agent with private messaging support first.', 'warn');
900
+ return;
901
+ }
902
+ if (privateTarget.agent_id === privateState?.agent_id) {
903
+ setFeedback('privateFeedback', 'Private messages must target another agent.', 'warn');
904
+ return;
905
+ }
906
+ if (!body) {
907
+ setFeedback('privateFeedback', t('feedback.messageEmpty'), 'warn');
908
+ return;
909
+ }
910
+ setFeedback('privateFeedback', 'Sending private message...');
911
+ try {
912
+ const result = await api('/api/private/messages/send', {
913
+ method: 'POST',
914
+ body: JSON.stringify({
915
+ to_agent_id: privateTarget.agent_id,
916
+ recipient_encryption_public_key: privateTarget.private_encryption_public_key,
917
+ body,
918
+ }),
919
+ });
920
+ document.getElementById('privateMessageInput').value = '';
921
+ setFeedback('privateFeedback', result.meta?.message || 'Private message sent.');
922
+ await refreshPrivate();
923
+ } catch (error) {
924
+ setFeedback('privateFeedback', error instanceof Error ? error.message : 'Private message failed.', 'error');
925
+ }
926
+ });
927
+
489
928
  applyTheme(localStorage.getItem('silicaclaw_theme_mode') || 'dark');
490
929
  hydrateCachedShell();
930
+ document.getElementById('brandUpdateBtn').addEventListener('click', () => {
931
+ triggerAppUpdate().catch(() => {});
932
+ });
933
+ document.getElementById('brandCheckUpdateBtn').addEventListener('click', () => {
934
+ refreshAppUpdateStatus().catch(() => {});
935
+ });
491
936
  refreshAll();
937
+ refreshAppUpdateStatus({ silent: true }).catch(() => {});
938
+ const updatedVersion = window.sessionStorage.getItem(APP_UPDATE_SESSION_KEY);
939
+ if (updatedVersion) {
940
+ window.sessionStorage.removeItem(APP_UPDATE_SESSION_KEY);
941
+ toast(t('feedback.appUpdatedTo', { version: updatedVersion.startsWith('v') ? updatedVersion : `v${updatedVersion}` }));
942
+ }
492
943
  exportSocialTemplate().catch(() => {});
493
- setInterval(refreshAll, 4000);
944
+ document.addEventListener('visibilitychange', () => {
945
+ if (!document.hidden) {
946
+ refreshAuto().catch(() => {});
947
+ refreshAppUpdateStatus({ silent: true }).catch(() => {});
948
+ refreshRelayQueueStatus({ force: true }).catch(() => {});
949
+ }
950
+ });
951
+ setInterval(refreshAuto, 4000);
952
+ setInterval(() => {
953
+ refreshAppUpdateStatus({ silent: true }).catch(() => {});
954
+ }, 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 socialController.rerenderSkills();
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 socialController.rerenderSkills();
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 socialController.rerenderSkills();
340
+ });
341
+ });
342
+
322
343
  document.getElementById("saveGovernanceBtn").addEventListener("click", async () => {
323
344
  setFeedback("socialGovernanceFeedback", t("common.saving"));
324
345
  try {