@silicaclaw/cli 2026.3.20-2 → 2026.3.20-21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/INSTALL.md +13 -7
  3. package/README.md +60 -12
  4. package/VERSION +1 -1
  5. package/apps/local-console/dist/apps/local-console/src/server.d.ts +139 -3
  6. package/apps/local-console/dist/apps/local-console/src/server.js +1029 -92
  7. package/apps/local-console/dist/packages/core/src/index.d.ts +2 -0
  8. package/apps/local-console/dist/packages/core/src/index.js +2 -0
  9. package/apps/local-console/dist/packages/core/src/privateCrypto.d.ts +17 -0
  10. package/apps/local-console/dist/packages/core/src/privateCrypto.js +40 -0
  11. package/apps/local-console/dist/packages/core/src/privateMessage.d.ts +23 -0
  12. package/apps/local-console/dist/packages/core/src/privateMessage.js +74 -0
  13. package/apps/local-console/dist/packages/core/src/profile.js +2 -0
  14. package/apps/local-console/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
  15. package/apps/local-console/dist/packages/core/src/publicProfileSummary.js +3 -0
  16. package/apps/local-console/dist/packages/core/src/types.d.ts +40 -0
  17. package/apps/local-console/dist/packages/network/src/relayPreview.d.ts +12 -0
  18. package/apps/local-console/dist/packages/network/src/relayPreview.js +108 -8
  19. package/apps/local-console/dist/packages/network/src/types.d.ts +4 -0
  20. package/apps/local-console/dist/packages/storage/src/repos.d.ts +27 -1
  21. package/apps/local-console/dist/packages/storage/src/repos.js +35 -1
  22. package/apps/local-console/public/app/app.js +502 -11
  23. package/apps/local-console/public/app/events.js +21 -0
  24. package/apps/local-console/public/app/network.js +144 -32
  25. package/apps/local-console/public/app/overview.js +57 -27
  26. package/apps/local-console/public/app/social.js +342 -105
  27. package/apps/local-console/public/app/styles.css +149 -43
  28. package/apps/local-console/public/app/template.js +196 -100
  29. package/apps/local-console/public/app/translations.js +438 -316
  30. package/apps/local-console/src/server.ts +1177 -90
  31. package/apps/public-explorer/public/app/template.js +2 -2
  32. package/apps/public-explorer/public/app/translations.js +36 -36
  33. package/docs/NEW_USER_OPERATIONS.md +5 -5
  34. package/docs/OPENCLAW_BRIDGE.md +7 -7
  35. package/docs/OPENCLAW_BRIDGE_ZH.md +6 -6
  36. package/node_modules/@silicaclaw/core/dist/packages/core/src/index.d.ts +2 -0
  37. package/node_modules/@silicaclaw/core/dist/packages/core/src/index.js +2 -0
  38. package/node_modules/@silicaclaw/core/dist/packages/core/src/privateCrypto.d.ts +17 -0
  39. package/node_modules/@silicaclaw/core/dist/packages/core/src/privateCrypto.js +40 -0
  40. package/node_modules/@silicaclaw/core/dist/packages/core/src/privateMessage.d.ts +23 -0
  41. package/node_modules/@silicaclaw/core/dist/packages/core/src/privateMessage.js +74 -0
  42. package/node_modules/@silicaclaw/core/dist/packages/core/src/profile.js +2 -0
  43. package/node_modules/@silicaclaw/core/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
  44. package/node_modules/@silicaclaw/core/dist/packages/core/src/publicProfileSummary.js +3 -0
  45. package/node_modules/@silicaclaw/core/dist/packages/core/src/types.d.ts +40 -0
  46. package/node_modules/@silicaclaw/core/package.json +2 -2
  47. package/node_modules/@silicaclaw/core/src/index.ts +2 -0
  48. package/node_modules/@silicaclaw/core/src/privateCrypto.ts +57 -0
  49. package/node_modules/@silicaclaw/core/src/privateMessage.ts +101 -0
  50. package/node_modules/@silicaclaw/core/src/profile.ts +2 -0
  51. package/node_modules/@silicaclaw/core/src/publicProfileSummary.ts +7 -0
  52. package/node_modules/@silicaclaw/core/src/types.ts +44 -0
  53. package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.d.ts +12 -0
  54. package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.js +108 -8
  55. package/node_modules/@silicaclaw/network/dist/packages/network/src/types.d.ts +4 -0
  56. package/node_modules/@silicaclaw/network/src/relayPreview.ts +120 -10
  57. package/node_modules/@silicaclaw/network/src/types.ts +2 -0
  58. package/node_modules/@silicaclaw/storage/dist/packages/core/src/index.d.ts +2 -0
  59. package/node_modules/@silicaclaw/storage/dist/packages/core/src/index.js +2 -0
  60. package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateCrypto.d.ts +17 -0
  61. package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateCrypto.js +40 -0
  62. package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateMessage.d.ts +23 -0
  63. package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateMessage.js +74 -0
  64. package/node_modules/@silicaclaw/storage/dist/packages/core/src/profile.js +2 -0
  65. package/node_modules/@silicaclaw/storage/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
  66. package/node_modules/@silicaclaw/storage/dist/packages/core/src/publicProfileSummary.js +3 -0
  67. package/node_modules/@silicaclaw/storage/dist/packages/core/src/types.d.ts +40 -0
  68. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.d.ts +27 -1
  69. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.js +35 -1
  70. package/node_modules/@silicaclaw/storage/package.json +2 -2
  71. package/node_modules/@silicaclaw/storage/src/repos.ts +59 -1
  72. package/openclaw-skills/silicaclaw-bridge-setup/SKILL.md +18 -0
  73. package/openclaw-skills/silicaclaw-bridge-setup/VERSION +1 -1
  74. package/openclaw-skills/silicaclaw-bridge-setup/manifest.json +2 -2
  75. package/openclaw-skills/silicaclaw-broadcast/SKILL.md +18 -0
  76. package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -1
  77. package/openclaw-skills/silicaclaw-broadcast/manifest.json +2 -2
  78. package/openclaw-skills/silicaclaw-network-config/SKILL.md +158 -0
  79. package/openclaw-skills/silicaclaw-network-config/VERSION +1 -0
  80. package/openclaw-skills/silicaclaw-network-config/agents/openai.yaml +6 -0
  81. package/openclaw-skills/silicaclaw-network-config/manifest.json +27 -0
  82. package/openclaw-skills/silicaclaw-network-config/references/network-modes.md +22 -0
  83. package/openclaw-skills/silicaclaw-network-config/references/owner-dialogue-cheatsheet-zh.md +47 -0
  84. package/openclaw-skills/silicaclaw-network-config/references/public-discovery.md +22 -0
  85. package/openclaw-skills/silicaclaw-owner-push/SKILL.md +18 -0
  86. package/openclaw-skills/silicaclaw-owner-push/VERSION +1 -1
  87. package/openclaw-skills/silicaclaw-owner-push/manifest.json +2 -2
  88. package/openclaw-skills/silicaclaw-owner-push/references/runtime-setup.md +3 -0
  89. package/openclaw-skills/silicaclaw-owner-push/scripts/owner-push-forwarder.mjs +151 -9
  90. package/package.json +1 -1
  91. package/packages/core/dist/packages/core/src/index.d.ts +2 -0
  92. package/packages/core/dist/packages/core/src/index.js +2 -0
  93. package/packages/core/dist/packages/core/src/privateCrypto.d.ts +17 -0
  94. package/packages/core/dist/packages/core/src/privateCrypto.js +40 -0
  95. package/packages/core/dist/packages/core/src/privateMessage.d.ts +23 -0
  96. package/packages/core/dist/packages/core/src/privateMessage.js +74 -0
  97. package/packages/core/dist/packages/core/src/profile.js +2 -0
  98. package/packages/core/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
  99. package/packages/core/dist/packages/core/src/publicProfileSummary.js +3 -0
  100. package/packages/core/dist/packages/core/src/types.d.ts +40 -0
  101. package/packages/core/package.json +2 -2
  102. package/packages/core/src/index.ts +2 -0
  103. package/packages/core/src/privateCrypto.ts +57 -0
  104. package/packages/core/src/privateMessage.ts +101 -0
  105. package/packages/core/src/profile.ts +2 -0
  106. package/packages/core/src/publicProfileSummary.ts +7 -0
  107. package/packages/core/src/types.ts +44 -0
  108. package/packages/network/dist/packages/network/src/relayPreview.d.ts +12 -0
  109. package/packages/network/dist/packages/network/src/relayPreview.js +108 -8
  110. package/packages/network/dist/packages/network/src/types.d.ts +4 -0
  111. package/packages/network/src/relayPreview.ts +120 -10
  112. package/packages/network/src/types.ts +2 -0
  113. package/packages/storage/dist/packages/core/src/index.d.ts +2 -0
  114. package/packages/storage/dist/packages/core/src/index.js +2 -0
  115. package/packages/storage/dist/packages/core/src/privateCrypto.d.ts +17 -0
  116. package/packages/storage/dist/packages/core/src/privateCrypto.js +40 -0
  117. package/packages/storage/dist/packages/core/src/privateMessage.d.ts +23 -0
  118. package/packages/storage/dist/packages/core/src/privateMessage.js +74 -0
  119. package/packages/storage/dist/packages/core/src/profile.js +2 -0
  120. package/packages/storage/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
  121. package/packages/storage/dist/packages/core/src/publicProfileSummary.js +3 -0
  122. package/packages/storage/dist/packages/core/src/types.d.ts +40 -0
  123. package/packages/storage/dist/packages/storage/src/repos.d.ts +27 -1
  124. package/packages/storage/dist/packages/storage/src/repos.js +35 -1
  125. package/packages/storage/package.json +2 -2
  126. package/packages/storage/src/repos.ts +59 -1
  127. package/scripts/silicaclaw-cli.mjs +4 -1
  128. package/scripts/silicaclaw-gateway.mjs +114 -2
  129. package/scripts/validate-openclaw-skill.mjs +19 -0
  130. package/node_modules/@silicaclaw/storage/dist/index.d.ts +0 -3
  131. package/node_modules/@silicaclaw/storage/dist/index.js +0 -19
  132. package/node_modules/@silicaclaw/storage/dist/jsonRepo.d.ts +0 -7
  133. package/node_modules/@silicaclaw/storage/dist/jsonRepo.js +0 -29
  134. package/node_modules/@silicaclaw/storage/dist/repos.d.ts +0 -61
  135. package/node_modules/@silicaclaw/storage/dist/repos.js +0 -67
  136. package/node_modules/@silicaclaw/storage/dist/socialRuntimeRepo.d.ts +0 -5
  137. package/node_modules/@silicaclaw/storage/dist/socialRuntimeRepo.js +0 -57
  138. package/packages/storage/dist/index.d.ts +0 -3
  139. package/packages/storage/dist/index.js +0 -19
  140. package/packages/storage/dist/jsonRepo.d.ts +0 -7
  141. package/packages/storage/dist/jsonRepo.js +0 -29
  142. package/packages/storage/dist/repos.d.ts +0 -61
  143. package/packages/storage/dist/repos.js +0 -67
  144. package/packages/storage/dist/socialRuntimeRepo.d.ts +0 -5
  145. package/packages/storage/dist/socialRuntimeRepo.js +0 -57
@@ -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,246 @@ root.innerHTML = appTemplate;
296
336
  toast,
297
337
  writeUiCache,
298
338
  } = shell;
339
+ let appUpdatePollTimer = null;
340
+ let appUpdateCheckInFlight = false;
341
+ let appUpdatePendingTargetVersion = '';
342
+ let relayQueueCheckInFlight = false;
343
+ let lastRelayQueueCheckAt = 0;
344
+
345
+ function setAppUpdateUi({
346
+ hint,
347
+ buttonVisible = false,
348
+ buttonDisabled = false,
349
+ buttonText = t('actions.updateNow'),
350
+ checkVisible = false,
351
+ checkDisabled = false,
352
+ }) {
353
+ const hintEl = document.getElementById('brandUpdateHint');
354
+ const buttonEl = document.getElementById('brandUpdateBtn');
355
+ const checkEl = document.getElementById('brandCheckUpdateBtn');
356
+ if (hintEl) {
357
+ hintEl.textContent = hint;
358
+ hintEl.classList.toggle('hidden', !hint);
359
+ }
360
+ if (checkEl) {
361
+ checkEl.textContent = t('actions.checkUpdate');
362
+ checkEl.classList.toggle('hidden', !checkVisible);
363
+ checkEl.disabled = checkDisabled;
364
+ }
365
+ if (buttonEl) {
366
+ buttonEl.textContent = buttonText;
367
+ buttonEl.classList.toggle('hidden', !buttonVisible);
368
+ buttonEl.disabled = buttonDisabled;
369
+ }
370
+ }
371
+
372
+ function platformUpdateHint(platform) {
373
+ if (platform === 'darwin') return t('labels.versionPlatformMac');
374
+ if (platform === 'linux') return t('labels.versionPlatformLinux');
375
+ return t('labels.versionPlatformOther');
376
+ }
377
+
378
+ function setRelayQueueUi({ hint = '', tone = 'ok', visible = false }) {
379
+ const hintEl = document.getElementById('brandRelayHint');
380
+ if (!hintEl) return;
381
+ hintEl.textContent = hint;
382
+ hintEl.classList.toggle('hidden', !visible || !hint);
383
+ hintEl.classList.remove('warn', 'danger');
384
+ if (tone === 'warn' || tone === 'danger') {
385
+ hintEl.classList.add(tone);
386
+ }
387
+ }
388
+
389
+ async function refreshRelayQueueStatus({ force = false } = {}) {
390
+ const now = Date.now();
391
+ if (relayQueueCheckInFlight) return null;
392
+ if (!force && now - lastRelayQueueCheckAt < 15_000) return null;
393
+ relayQueueCheckInFlight = true;
394
+ try {
395
+ const result = await api('/api/peers');
396
+ const peers = result.data || {};
397
+ const peerItems = Array.isArray(peers.items) ? peers.items : [];
398
+ const relayQueueMax = peerItems.reduce((max, peer) => Math.max(max, Number(peer?.meta?.relay_queue_size || 0)), 0);
399
+ const signalQueueMax = peerItems.reduce((max, peer) => Math.max(max, Number(peer?.meta?.signal_queue_size || 0)), 0);
400
+ const queueMax = Math.max(relayQueueMax, signalQueueMax);
401
+ if (queueMax >= 100) {
402
+ setRelayQueueUi({ hint: t('labels.relayQueuesHigh'), tone: 'danger', visible: true });
403
+ } else if (queueMax >= 20) {
404
+ setRelayQueueUi({ hint: t('labels.relayQueuesWatch'), tone: 'warn', visible: true });
405
+ } else {
406
+ setRelayQueueUi({ hint: t('labels.relayQueuesHealthy'), tone: 'ok', visible: true });
407
+ }
408
+ lastRelayQueueCheckAt = now;
409
+ return { relayQueueMax, signalQueueMax };
410
+ } catch (_error) {
411
+ return null;
412
+ } finally {
413
+ relayQueueCheckInFlight = false;
414
+ }
415
+ }
416
+
417
+ async function refreshAppUpdateStatus({ silent = false } = {}) {
418
+ if (appUpdateCheckInFlight) return null;
419
+ appUpdateCheckInFlight = true;
420
+ try {
421
+ const result = await api('/api/app/update-status');
422
+ const status = result.data || {};
423
+ const currentVersion = String(status.current_version || '').trim();
424
+ const latestVersion = String(status.latest_version || '').trim();
425
+ const platformHint = platformUpdateHint(String(status.platform || ''));
426
+ const pendingTarget = String(appUpdatePendingTargetVersion || '').trim();
427
+ if (currentVersion) {
428
+ document.getElementById('brandVersion').textContent = currentVersion.startsWith('v') ? currentVersion : `v${currentVersion}`;
429
+ }
430
+ if (pendingTarget && currentVersion && currentVersion === pendingTarget) {
431
+ appUpdatePendingTargetVersion = '';
432
+ setAppUpdateUi({
433
+ hint: t('labels.versionUpdated', { version: currentVersion.startsWith('v') ? currentVersion : `v${currentVersion}` }),
434
+ buttonVisible: false,
435
+ buttonDisabled: false,
436
+ buttonText: t('actions.updateNow'),
437
+ checkVisible: true,
438
+ checkDisabled: false,
439
+ });
440
+ return status;
441
+ }
442
+ if (pendingTarget) {
443
+ setAppUpdateUi({
444
+ hint: t('labels.versionUpdatingTo', { version: pendingTarget.startsWith('v') ? pendingTarget : `v${pendingTarget}` }),
445
+ buttonVisible: true,
446
+ buttonDisabled: true,
447
+ buttonText: t('labels.versionUpdating'),
448
+ checkVisible: true,
449
+ checkDisabled: true,
450
+ });
451
+ return status;
452
+ }
453
+ if (status.update_available && status.latest_version) {
454
+ setAppUpdateUi({
455
+ hint: `${t('labels.versionUpdateReady', { version: `v${status.latest_version}` })} · ${platformHint}`,
456
+ buttonVisible: true,
457
+ buttonDisabled: false,
458
+ buttonText: t('actions.updateNowVersion', { version: latestVersion.startsWith('v') ? latestVersion : `v${latestVersion}` }),
459
+ checkVisible: true,
460
+ checkDisabled: false,
461
+ });
462
+ } else if (status.check_error) {
463
+ setAppUpdateUi({
464
+ hint: t('labels.versionCheckFailed'),
465
+ buttonVisible: false,
466
+ buttonDisabled: false,
467
+ buttonText: t('actions.updateNow'),
468
+ checkVisible: true,
469
+ checkDisabled: false,
470
+ });
471
+ if (!silent) {
472
+ setFeedback('networkFeedback', t('feedback.appUpdateCheckFailed'), 'warn');
473
+ }
474
+ } else {
475
+ setAppUpdateUi({
476
+ hint: `${t('labels.versionCurrent')} · ${platformHint}`,
477
+ buttonVisible: false,
478
+ buttonDisabled: false,
479
+ buttonText: t('actions.updateNow'),
480
+ checkVisible: true,
481
+ checkDisabled: false,
482
+ });
483
+ }
484
+ return status;
485
+ } catch (_error) {
486
+ setAppUpdateUi({
487
+ hint: t('labels.versionCheckFailed'),
488
+ buttonVisible: false,
489
+ buttonDisabled: false,
490
+ buttonText: t('actions.updateNow'),
491
+ checkVisible: true,
492
+ checkDisabled: false,
493
+ });
494
+ if (!silent) {
495
+ setFeedback('networkFeedback', t('feedback.appUpdateCheckFailed'), 'warn');
496
+ }
497
+ return null;
498
+ } finally {
499
+ appUpdateCheckInFlight = false;
500
+ }
501
+ }
502
+
503
+ function startAppUpdatePolling(targetVersion) {
504
+ if (appUpdatePollTimer) {
505
+ window.clearInterval(appUpdatePollTimer);
506
+ }
507
+ appUpdatePendingTargetVersion = String(targetVersion || '').trim();
508
+ let attempts = 0;
509
+ appUpdatePollTimer = window.setInterval(async () => {
510
+ attempts += 1;
511
+ const status = await refreshAppUpdateStatus({ silent: true });
512
+ if (status && !status.update_available && String(status.current_version || '') === String(targetVersion || '')) {
513
+ window.clearInterval(appUpdatePollTimer);
514
+ appUpdatePollTimer = null;
515
+ appUpdatePendingTargetVersion = '';
516
+ if (targetVersion) {
517
+ window.sessionStorage.setItem(APP_UPDATE_SESSION_KEY, String(targetVersion));
518
+ }
519
+ window.location.reload();
520
+ return;
521
+ }
522
+ if (attempts >= 24) {
523
+ window.clearInterval(appUpdatePollTimer);
524
+ appUpdatePollTimer = null;
525
+ appUpdatePendingTargetVersion = '';
526
+ setAppUpdateUi({
527
+ hint: t('labels.versionUpdateStillPending'),
528
+ buttonVisible: true,
529
+ buttonDisabled: false,
530
+ buttonText: t('actions.updateRetry'),
531
+ checkVisible: true,
532
+ checkDisabled: false,
533
+ });
534
+ }
535
+ }, 5000);
536
+ }
537
+
538
+ async function triggerAppUpdate() {
539
+ const buttonEl = document.getElementById('brandUpdateBtn');
540
+ setAppUpdateUi({
541
+ hint: t('labels.versionUpdating'),
542
+ buttonVisible: true,
543
+ buttonDisabled: true,
544
+ buttonText: t('labels.versionUpdating'),
545
+ checkVisible: true,
546
+ checkDisabled: true,
547
+ });
548
+ try {
549
+ const result = await api('/api/app/update', { method: 'POST' });
550
+ const data = result.data || {};
551
+ if (!data.started) {
552
+ appUpdatePendingTargetVersion = '';
553
+ setAppUpdateUi({
554
+ hint: t('labels.versionCurrent'),
555
+ buttonVisible: false,
556
+ buttonDisabled: false,
557
+ buttonText: t('actions.updateNow'),
558
+ checkVisible: true,
559
+ checkDisabled: false,
560
+ });
561
+ toast(t('feedback.appUpdateLatest'));
562
+ return;
563
+ }
564
+ toast(t('feedback.appUpdateStarted'));
565
+ startAppUpdatePolling(String(data.target_version || ''));
566
+ } catch (error) {
567
+ appUpdatePendingTargetVersion = '';
568
+ setAppUpdateUi({
569
+ hint: t('labels.versionCheckFailed'),
570
+ buttonVisible: true,
571
+ buttonDisabled: false,
572
+ buttonText: t('actions.updateRetry'),
573
+ checkVisible: true,
574
+ checkDisabled: false,
575
+ });
576
+ setFeedback('networkFeedback', error instanceof Error ? error.message : t('feedback.appUpdateFailed'), 'error');
577
+ }
578
+ }
299
579
  setLocale(currentLocale);
300
580
  applyStaticTranslations();
301
581
 
@@ -309,6 +589,11 @@ root.innerHTML = appTemplate;
309
589
  let visibleRemotePublicCount = 0;
310
590
  let socialMessageFilter = 'all';
311
591
  let socialMessageGovernance = null;
592
+ let privateState = null;
593
+ let privateConversations = [];
594
+ let privateMessages = [];
595
+ let privateTarget = null;
596
+ let selectedPrivateConversationId = '';
312
597
  let overviewMode = 'lan';
313
598
  let onlyShowOnline = false;
314
599
  let agentsPage = 1;
@@ -392,7 +677,7 @@ root.innerHTML = appTemplate;
392
677
  }
393
678
  activeTab = tab;
394
679
  document.querySelectorAll('.tab').forEach((b) => b.classList.toggle('active', b.dataset.tab === tab));
395
- ['overview', 'agent', 'chat', 'skills', 'profile', 'network', 'social'].forEach((k) => {
680
+ ['overview', 'agent', 'chat', 'private', 'skills', 'profile', 'network', 'social'].forEach((k) => {
396
681
  document.getElementById(`view-${k}`).classList.toggle('active', k === tab);
397
682
  });
398
683
  const meta = pageMeta[tab] || pageMeta.overview;
@@ -404,6 +689,25 @@ root.innerHTML = appTemplate;
404
689
  document.getElementById('publicDiscoveryHint')?.classList.toggle('hidden', tab !== 'overview');
405
690
  if (tab === 'profile' && !profileController.isDirty() && !profileController.isSaving()) {
406
691
  refreshProfile().catch(() => {});
692
+ } else if (tab === 'overview') {
693
+ refreshOverview().catch(() => {});
694
+ refreshMessages().catch(() => {});
695
+ } else if (tab === 'agent') {
696
+ refreshOverview().catch(() => {});
697
+ } else if (tab === 'chat') {
698
+ refreshMessages().catch(() => {});
699
+ } else if (tab === 'private') {
700
+ refreshPrivate().catch(() => {});
701
+ } else if (tab === 'skills') {
702
+ refreshSkills().catch(() => {});
703
+ } else if (tab === 'network') {
704
+ refreshNetwork().catch(() => {});
705
+ refreshPeers().catch(() => {});
706
+ refreshDiscovery().catch(() => {});
707
+ refreshLogs().catch(() => {});
708
+ } else if (tab === 'social') {
709
+ refreshSocial().catch(() => {});
710
+ refreshMessages().catch(() => {});
407
711
  }
408
712
  }
409
713
 
@@ -417,6 +721,81 @@ root.innerHTML = appTemplate;
417
721
  });
418
722
  }
419
723
 
724
+ function renderPrivate() {
725
+ const privateDeliveryLabel = (status) => {
726
+ if (status === 'direct-sent') return 'Direct';
727
+ if (status === 'fallback-sent') return 'Fallback';
728
+ if (status === 'received') return 'Received';
729
+ if (status === 'read') return 'Read';
730
+ return 'Sent';
731
+ };
732
+ document.getElementById('privateStateMeta').textContent = privateState?.enabled
733
+ ? `${privateConversations.length} conversation(s)`
734
+ : 'Private messaging unavailable';
735
+ document.getElementById('privateTargetName').value = privateTarget?.display_name || '';
736
+ document.getElementById('privateTargetAgentId').value = privateTarget?.agent_id || '';
737
+ document.getElementById('privateConversationList').innerHTML = privateConversations.length
738
+ ? privateConversations.map((item) => `
739
+ <button class="agent-card" type="button" data-private-conversation="${escapeHtml(item.conversation_id)}">
740
+ <div class="agent-card__avatar-fallback">${escapeHtml(((item.peer_display_name || item.peer_agent_id || '?')[0] || '?').toUpperCase())}</div>
741
+ <div class="agent-card__main">
742
+ <div class="agent-card__row">
743
+ <div class="agent-card__name">${escapeHtml(item.peer_display_name || item.peer_agent_id || 'Unknown')}</div>
744
+ <div class="agent-card__id mono">${escapeHtml(shortId(item.peer_agent_id || ''))}</div>
745
+ </div>
746
+ </div>
747
+ </button>
748
+ `).join('')
749
+ : `<div class="empty-state">No private conversations yet.</div>`;
750
+ document.getElementById('privateMessageList').innerHTML = privateMessages.length
751
+ ? privateMessages.map((item) => `
752
+ <div class="log-item">
753
+ <div style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
754
+ <div>
755
+ <strong>${item.is_self ? 'Me' : escapeHtml(privateTarget?.display_name || item.from_agent_id || 'Unknown')}</strong>
756
+ <span class="tag-chip" style="margin-left:8px;">${escapeHtml(privateDeliveryLabel(item.delivery_status))}</span>
757
+ </div>
758
+ <div class="mono" style="color:#90a2c3;">${new Date(item.created_at).toLocaleString()}</div>
759
+ </div>
760
+ <div style="margin-top:8px; line-height:1.6;">${formatMessageBody(item.body || '')}</div>
761
+ </div>
762
+ `).join('')
763
+ : `<div class="empty-state">No private messages yet.</div>`;
764
+ }
765
+
766
+ async function refreshPrivate() {
767
+ const [stateRes, conversationsRes] = await Promise.all([
768
+ api('/api/private/state'),
769
+ api('/api/private/conversations'),
770
+ ]);
771
+ privateState = stateRes.data || null;
772
+ privateConversations = Array.isArray(conversationsRes.data) ? conversationsRes.data : [];
773
+ if ((!privateTarget || privateTarget.agent_id === privateState?.agent_id) && privateConversations[0]) {
774
+ const first = privateConversations[0];
775
+ privateTarget = {
776
+ agent_id: first.peer_agent_id,
777
+ display_name: first.peer_display_name,
778
+ private_encryption_public_key: first.peer_public_key,
779
+ };
780
+ selectedPrivateConversationId = first.conversation_id;
781
+ }
782
+ const selectedConversation = privateConversations.find((item) => item.conversation_id === selectedPrivateConversationId);
783
+ if (selectedConversation) {
784
+ privateTarget = {
785
+ agent_id: selectedConversation.peer_agent_id,
786
+ display_name: selectedConversation.peer_display_name,
787
+ private_encryption_public_key: selectedConversation.peer_public_key,
788
+ };
789
+ }
790
+ if (selectedPrivateConversationId) {
791
+ const messageRes = await api(`/api/private/messages?conversation_id=${encodeURIComponent(selectedPrivateConversationId)}&limit=100`);
792
+ privateMessages = Array.isArray(messageRes.data) ? messageRes.data : [];
793
+ } else {
794
+ privateMessages = [];
795
+ }
796
+ renderPrivate();
797
+ }
798
+
420
799
  const renderSocialMessages = socialController.renderSocialMessages;
421
800
  const refreshMessages = socialController.refreshMessages;
422
801
 
@@ -430,14 +809,25 @@ root.innerHTML = appTemplate;
430
809
  const renderLogs = socialController.renderLogs;
431
810
  const refreshLogs = socialController.refreshLogs;
432
811
  const refreshSkills = socialController.refreshSkills;
812
+ let autoRefreshInFlight = false;
433
813
 
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) {
814
+ async function refreshActiveView() {
815
+ const tasks = [refreshPublicProfilePreview(), refreshRelayQueueStatus()];
816
+ if (activeTab === 'overview') {
817
+ tasks.push(refreshOverview(), refreshMessages());
818
+ } else if (activeTab === 'agent') {
819
+ tasks.push(refreshOverview());
820
+ } else if (activeTab === 'chat') {
821
+ tasks.push(refreshMessages());
822
+ } else if (activeTab === 'private') {
823
+ tasks.push(refreshPrivate());
824
+ } else if (activeTab === 'skills') {
825
+ tasks.push(refreshSkills());
826
+ } else if (activeTab === 'network') {
827
+ tasks.push(refreshNetwork(), refreshPeers(), refreshDiscovery(), refreshLogs());
828
+ } else if (activeTab === 'social') {
829
+ tasks.push(refreshSocial(), refreshMessages());
830
+ } else if (activeTab === 'profile' && !profileController.isDirty() && !profileController.isSaving()) {
441
831
  tasks.push(refreshProfile());
442
832
  }
443
833
  const results = await Promise.allSettled(tasks);
@@ -447,6 +837,22 @@ root.innerHTML = appTemplate;
447
837
  }
448
838
  }
449
839
 
840
+ async function refreshAuto() {
841
+ if (document.hidden || autoRefreshInFlight) {
842
+ return;
843
+ }
844
+ autoRefreshInFlight = true;
845
+ try {
846
+ await refreshActiveView();
847
+ } finally {
848
+ autoRefreshInFlight = false;
849
+ }
850
+ }
851
+
852
+ async function refreshAll() {
853
+ await refreshActiveView();
854
+ }
855
+
450
856
  bindAppEvents({
451
857
  api,
452
858
  applyTheme,
@@ -486,8 +892,93 @@ root.innerHTML = appTemplate;
486
892
  toPrettyJson,
487
893
  });
488
894
 
895
+ document.getElementById('agentsWrap').addEventListener('click', (event) => {
896
+ const button = event.target?.closest?.('[data-private-agent]');
897
+ if (!button) return;
898
+ privateTarget = {
899
+ agent_id: String(button.getAttribute('data-private-agent') || ''),
900
+ display_name: String(button.getAttribute('data-private-name') || ''),
901
+ private_encryption_public_key: String(button.getAttribute('data-private-key') || ''),
902
+ };
903
+ selectedPrivateConversationId = [privateState?.agent_id || '', privateTarget.agent_id].sort().join(':');
904
+ switchTab('private');
905
+ });
906
+
907
+ document.getElementById('privateConversationList').addEventListener('click', async (event) => {
908
+ const button = event.target?.closest?.('[data-private-conversation]');
909
+ if (!button) return;
910
+ selectedPrivateConversationId = String(button.getAttribute('data-private-conversation') || '');
911
+ const selectedConversation = privateConversations.find((item) => item.conversation_id === selectedPrivateConversationId);
912
+ if (selectedConversation) {
913
+ privateTarget = {
914
+ agent_id: selectedConversation.peer_agent_id,
915
+ display_name: selectedConversation.peer_display_name,
916
+ private_encryption_public_key: selectedConversation.peer_public_key,
917
+ };
918
+ }
919
+ await refreshPrivate();
920
+ });
921
+
922
+ document.getElementById('privateRefreshBtn').addEventListener('click', async () => {
923
+ await refreshPrivate();
924
+ });
925
+
926
+ document.getElementById('privateMessageSendBtn').addEventListener('click', async () => {
927
+ const body = String(document.getElementById('privateMessageInput').value || '').trim();
928
+ if (!privateTarget?.agent_id || !privateTarget?.private_encryption_public_key) {
929
+ setFeedback('privateFeedback', 'Pick an agent with private messaging support first.', 'warn');
930
+ return;
931
+ }
932
+ if (privateTarget.agent_id === privateState?.agent_id) {
933
+ setFeedback('privateFeedback', 'Private messages must target another agent.', 'warn');
934
+ return;
935
+ }
936
+ if (!body) {
937
+ setFeedback('privateFeedback', t('feedback.messageEmpty'), 'warn');
938
+ return;
939
+ }
940
+ setFeedback('privateFeedback', 'Sending private message...');
941
+ try {
942
+ const result = await api('/api/private/messages/send', {
943
+ method: 'POST',
944
+ body: JSON.stringify({
945
+ to_agent_id: privateTarget.agent_id,
946
+ recipient_encryption_public_key: privateTarget.private_encryption_public_key,
947
+ body,
948
+ }),
949
+ });
950
+ document.getElementById('privateMessageInput').value = '';
951
+ setFeedback('privateFeedback', result.meta?.message || 'Private message sent.');
952
+ await refreshPrivate();
953
+ } catch (error) {
954
+ setFeedback('privateFeedback', error instanceof Error ? error.message : 'Private message failed.', 'error');
955
+ }
956
+ });
957
+
489
958
  applyTheme(localStorage.getItem('silicaclaw_theme_mode') || 'dark');
490
959
  hydrateCachedShell();
960
+ document.getElementById('brandUpdateBtn').addEventListener('click', () => {
961
+ triggerAppUpdate().catch(() => {});
962
+ });
963
+ document.getElementById('brandCheckUpdateBtn').addEventListener('click', () => {
964
+ refreshAppUpdateStatus().catch(() => {});
965
+ });
491
966
  refreshAll();
967
+ refreshAppUpdateStatus({ silent: true }).catch(() => {});
968
+ const updatedVersion = window.sessionStorage.getItem(APP_UPDATE_SESSION_KEY);
969
+ if (updatedVersion) {
970
+ window.sessionStorage.removeItem(APP_UPDATE_SESSION_KEY);
971
+ toast(t('feedback.appUpdatedTo', { version: updatedVersion.startsWith('v') ? updatedVersion : `v${updatedVersion}` }));
972
+ }
492
973
  exportSocialTemplate().catch(() => {});
493
- setInterval(refreshAll, 4000);
974
+ document.addEventListener('visibilitychange', () => {
975
+ if (!document.hidden) {
976
+ refreshAuto().catch(() => {});
977
+ refreshAppUpdateStatus({ silent: true }).catch(() => {});
978
+ refreshRelayQueueStatus({ force: true }).catch(() => {});
979
+ }
980
+ });
981
+ setInterval(refreshAuto, 4000);
982
+ setInterval(() => {
983
+ refreshAppUpdateStatus({ silent: true }).catch(() => {});
984
+ }, 15 * 60 * 1000);