@silicaclaw/cli 2026.3.20-13 → 2026.3.20-14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## v1.0 beta - 2026-03-20
4
4
 
5
+ ### 2026.3.20-14
6
+
7
+ - release build:
8
+ - prepared another fresh latest-channel package build without publishing
9
+ - regenerated the npm tarball through the verified release packing workflow
10
+
5
11
  ### 2026.3.20-13
6
12
 
7
13
  - release build:
package/VERSION CHANGED
@@ -1 +1 @@
1
- v2026.3.20-13
1
+ v2026.3.20-14
@@ -623,6 +623,7 @@ export declare class LocalNodeService {
623
623
  getIdentity(): AgentIdentity | null;
624
624
  getSocialMessages(limit?: number, options?: {
625
625
  agent_id?: string | null;
626
+ offset?: number | null;
626
627
  }): {
627
628
  total: number;
628
629
  items: SocialMessageView[];
@@ -656,7 +657,10 @@ export declare class LocalNodeService {
656
657
  last_message_at: number | null;
657
658
  unread_count: number;
658
659
  }>;
659
- getPrivateMessages(conversationId: string, limit?: number): PrivateMessageView[];
660
+ getPrivateMessages(conversationId: string, limit?: number, offset?: number): {
661
+ total: number;
662
+ items: PrivateMessageView[];
663
+ };
660
664
  sendPrivateMessage(input: {
661
665
  to_agent_id: string;
662
666
  recipient_encryption_public_key: string;
@@ -1580,6 +1580,7 @@ class LocalNodeService {
1580
1580
  }
1581
1581
  getSocialMessages(limit = 50, options) {
1582
1582
  const resolvedLimit = Math.max(1, Math.min(200, Number(limit) || 50));
1583
+ const resolvedOffset = Math.max(0, Number(options?.offset) || 0);
1583
1584
  this.ensureLocalDirectoryBaseline();
1584
1585
  this.compactCacheInMemory();
1585
1586
  const agentId = String(options?.agent_id || "").trim();
@@ -1588,7 +1589,7 @@ class LocalNodeService {
1588
1589
  : this.socialMessages;
1589
1590
  return {
1590
1591
  total: filtered.length,
1591
- items: filtered.slice(0, resolvedLimit).map((message) => {
1592
+ items: filtered.slice(resolvedOffset, resolvedOffset + resolvedLimit).map((message) => {
1592
1593
  const profile = this.directory.profiles[message.agent_id];
1593
1594
  const lastSeenAt = this.directory.presence[message.agent_id] ?? 0;
1594
1595
  const observations = this.socialMessageObservations.filter((item) => item.message_id === message.message_id);
@@ -1650,11 +1651,12 @@ class LocalNodeService {
1650
1651
  }
1651
1652
  return Array.from(conversations.values()).sort((a, b) => (b.last_message_at || 0) - (a.last_message_at || 0));
1652
1653
  }
1653
- getPrivateMessages(conversationId, limit = PRIVATE_MESSAGE_QUERY_LIMIT) {
1654
+ getPrivateMessages(conversationId, limit = PRIVATE_MESSAGE_QUERY_LIMIT, offset = 0) {
1654
1655
  const normalizedConversationId = String(conversationId || "").trim();
1655
1656
  const resolvedLimit = Math.max(1, Math.min(PRIVATE_MESSAGE_QUERY_LIMIT, Number(limit) || PRIVATE_MESSAGE_QUERY_LIMIT));
1657
+ const resolvedOffset = Math.max(0, Number(offset) || 0);
1656
1658
  const receiptsByMessageId = new Map(this.privateMessageReceipts.map((receipt) => [receipt.message_id, receipt.status]));
1657
- return this.privateMessages
1659
+ const filtered = this.privateMessages
1658
1660
  .filter((message) => {
1659
1661
  if (message.from_agent_id === message.to_agent_id) {
1660
1662
  return false;
@@ -1665,20 +1667,24 @@ class LocalNodeService {
1665
1667
  }
1666
1668
  return !normalizedConversationId || message.conversation_id === normalizedConversationId;
1667
1669
  })
1668
- .sort((a, b) => a.created_at - b.created_at)
1669
- .slice(-resolvedLimit)
1670
- .map((message) => ({
1671
- message_id: message.message_id,
1672
- conversation_id: message.conversation_id,
1673
- from_agent_id: message.from_agent_id,
1674
- to_agent_id: message.to_agent_id,
1675
- body: this.decryptPrivateMessageBody(message),
1676
- created_at: message.created_at,
1677
- is_self: message.from_agent_id === this.identity?.agent_id,
1678
- delivery_status: receiptsByMessageId.get(message.message_id) ||
1679
- this.privateMessageDeliveryStatusCache.get(message.message_id) ||
1680
- (message.from_agent_id === this.identity?.agent_id ? "fallback-sent" : "sent"),
1681
- }));
1670
+ .sort((a, b) => a.created_at - b.created_at);
1671
+ const total = filtered.length;
1672
+ const paged = filtered.slice(Math.max(0, total - resolvedOffset - resolvedLimit), Math.max(0, total - resolvedOffset));
1673
+ return {
1674
+ total,
1675
+ items: paged.map((message) => ({
1676
+ message_id: message.message_id,
1677
+ conversation_id: message.conversation_id,
1678
+ from_agent_id: message.from_agent_id,
1679
+ to_agent_id: message.to_agent_id,
1680
+ body: this.decryptPrivateMessageBody(message),
1681
+ created_at: message.created_at,
1682
+ is_self: message.from_agent_id === this.identity?.agent_id,
1683
+ delivery_status: receiptsByMessageId.get(message.message_id) ||
1684
+ this.privateMessageDeliveryStatusCache.get(message.message_id) ||
1685
+ (message.from_agent_id === this.identity?.agent_id ? "fallback-sent" : "sent"),
1686
+ })),
1687
+ };
1682
1688
  }
1683
1689
  async sendPrivateMessage(input) {
1684
1690
  if (!this.identity || !this.privateEncryptionKeyPair) {
@@ -1727,7 +1733,7 @@ class LocalNodeService {
1727
1733
  await this.publish(PRIVATE_MESSAGE_TOPIC, message);
1728
1734
  }
1729
1735
  this.privateMessageDeliveryStatusCache.set(message.message_id, reason);
1730
- const view = this.getPrivateMessages(message.conversation_id).find((item) => item.message_id === message.message_id);
1736
+ const view = this.getPrivateMessages(message.conversation_id, PRIVATE_MESSAGE_QUERY_LIMIT, 0).items.find((item) => item.message_id === message.message_id);
1731
1737
  if (view) {
1732
1738
  view.delivery_status = reason;
1733
1739
  }
@@ -3714,7 +3720,7 @@ async function main() {
3714
3720
  catch {
3715
3721
  // best effort after response has been sent
3716
3722
  }
3717
- }, 150);
3723
+ }, 1200);
3718
3724
  }));
3719
3725
  app.put("/api/profile", asyncRoute(async (req, res) => {
3720
3726
  const body = req.body;
@@ -3811,8 +3817,9 @@ async function main() {
3811
3817
  }));
3812
3818
  app.get("/api/messages", (req, res) => {
3813
3819
  const limit = Number(req.query.limit ?? 50);
3820
+ const offset = Number(req.query.offset ?? 0);
3814
3821
  const agentId = String(req.query.agent_id ?? "").trim();
3815
- sendOk(res, node.getSocialMessages(limit, { agent_id: agentId || null }));
3822
+ sendOk(res, node.getSocialMessages(limit, { agent_id: agentId || null, offset }));
3816
3823
  });
3817
3824
  app.get("/api/private/state", (_req, res) => {
3818
3825
  sendOk(res, node.getPrivateMessagingState());
@@ -3823,7 +3830,8 @@ async function main() {
3823
3830
  app.get("/api/private/messages", (req, res) => {
3824
3831
  const conversationId = String(req.query.conversation_id ?? "").trim();
3825
3832
  const limit = Number(req.query.limit ?? PRIVATE_MESSAGE_QUERY_LIMIT);
3826
- sendOk(res, node.getPrivateMessages(conversationId, limit));
3833
+ const offset = Number(req.query.offset ?? 0);
3834
+ sendOk(res, node.getPrivateMessages(conversationId, limit, offset));
3827
3835
  });
3828
3836
  app.post("/api/private/messages/send", asyncRoute(async (req, res) => {
3829
3837
  const result = await node.sendPrivateMessage({
@@ -3850,8 +3858,9 @@ async function main() {
3850
3858
  });
3851
3859
  app.get("/api/openclaw/bridge/messages", (req, res) => {
3852
3860
  const limit = Number(req.query.limit ?? 50);
3861
+ const offset = Number(req.query.offset ?? 0);
3853
3862
  const agentId = String(req.query.agent_id ?? "").trim();
3854
- sendOk(res, node.getSocialMessages(limit, { agent_id: agentId || null }));
3863
+ sendOk(res, node.getSocialMessages(limit, { agent_id: agentId || null, offset }));
3855
3864
  });
3856
3865
  app.post("/api/openclaw/bridge/message", asyncRoute(async (req, res) => {
3857
3866
  const body = String(req.body?.body || "");
@@ -3967,9 +3976,16 @@ async function main() {
3967
3976
  let html = (0, fs_1.readFileSync)(staticIndexFile, "utf8");
3968
3977
  html = html.replace("</body>", `${renderBootstrapScript(payload)}\n</body>`);
3969
3978
  res.setHeader("Content-Type", "text/html; charset=utf-8");
3979
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
3970
3980
  res.send(html);
3971
3981
  });
3972
- app.use(express_1.default.static(staticDir));
3982
+ app.use(express_1.default.static(staticDir, {
3983
+ etag: false,
3984
+ lastModified: false,
3985
+ setHeaders: (res) => {
3986
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
3987
+ },
3988
+ }));
3973
3989
  app.use((error, _req, res, _next) => {
3974
3990
  const message = error instanceof Error ? error.message : "Unknown error";
3975
3991
  sendError(res, 500, "INTERNAL_ERROR", message);
@@ -28,6 +28,7 @@ if (!root) {
28
28
  }
29
29
  root.innerHTML = appTemplate;
30
30
  const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
31
+ const PAGING_STATE_STORAGE_KEY = 'silicaclaw_ui_paging_state';
31
32
 
32
33
  const i18n = createI18n(TRANSLATIONS);
33
34
  const DEFAULT_LOCALE = i18n.DEFAULT_LOCALE;
@@ -52,7 +53,9 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
52
53
  summary.setAttribute('data-i18n-closed-label', t('labels.show'));
53
54
  summary.setAttribute('data-i18n-open-label', t('labels.hide'));
54
55
  });
55
- setText('.nav-section__label', t('common.control'));
56
+ setText('.nav-section__label', t('common.workspace'), 0);
57
+ setText('.nav-section__label', t('common.messages'), 1);
58
+ setText('.nav-section__label', t('common.networkGroup'), 2);
56
59
  setText('[data-tab="overview"] .tab-title', t('pageMeta.overview.title'));
57
60
  setText('[data-tab="overview"] .tab-copy', t('labels.overviewTabCopy'));
58
61
  setText('[data-tab="agent"] .tab-title', t('pageMeta.agent.title'));
@@ -120,6 +123,12 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
120
123
  document.getElementById('privateTargetIdLabel').textContent = t('social.agentId');
121
124
  document.getElementById('privateMessageSendBtn').textContent = t('actions.sendPrivateMessage');
122
125
  document.getElementById('privateRefreshBtn').textContent = t('actions.refreshPrivate');
126
+ document.getElementById('socialMessagePrevPageBtn').textContent = t('overview.prevPage');
127
+ document.getElementById('socialMessageNextPageBtn').textContent = t('overview.nextPage');
128
+ document.getElementById('privateConversationPrevPageBtn').textContent = t('overview.prevPage');
129
+ document.getElementById('privateConversationNextPageBtn').textContent = t('overview.nextPage');
130
+ document.getElementById('privateMessagePrevPageBtn').textContent = t('overview.prevPage');
131
+ document.getElementById('privateMessageNextPageBtn').textContent = t('overview.nextPage');
123
132
  document.getElementById('chatFeedHint').textContent = t('hints.chatFeedHint');
124
133
  document.getElementById('overviewGuideTitle').textContent = t('overview.guideTitle');
125
134
  document.getElementById('overviewGuideBody').textContent = t('overview.guideBody');
@@ -552,6 +561,7 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
552
561
  let activeTab = 'overview';
553
562
  let logsCache = [];
554
563
  let socialMessagesCache = [];
564
+ let socialMessagePage = 1;
555
565
  let logLevelFilter = 'all';
556
566
  let socialTemplate = '';
557
567
  let socialModeDirty = false;
@@ -562,6 +572,11 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
562
572
  let privateState = null;
563
573
  let privateConversations = [];
564
574
  let privateMessages = [];
575
+ let privateConversationPage = 1;
576
+ const PRIVATE_CONVERSATION_PAGE_SIZE = 12;
577
+ let privateMessagesTotal = 0;
578
+ let privateMessagePage = 1;
579
+ const PRIVATE_MESSAGE_PAGE_SIZE = 20;
565
580
  let privateTarget = null;
566
581
  let selectedPrivateConversationId = '';
567
582
  let overviewMode = 'lan';
@@ -570,6 +585,29 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
570
585
  const AGENTS_PAGE_SIZE = 10;
571
586
  const pageMeta = TRANSLATIONS[currentLocale].pageMeta || TRANSLATIONS[DEFAULT_LOCALE].pageMeta;
572
587
 
588
+ function loadPagingState() {
589
+ try {
590
+ const raw = localStorage.getItem(PAGING_STATE_STORAGE_KEY);
591
+ if (!raw) return null;
592
+ return JSON.parse(raw);
593
+ } catch {
594
+ return null;
595
+ }
596
+ }
597
+
598
+ function savePagingState() {
599
+ try {
600
+ localStorage.setItem(PAGING_STATE_STORAGE_KEY, JSON.stringify({
601
+ socialMessagePage,
602
+ privateConversationPage,
603
+ privateMessagePage,
604
+ selectedPrivateConversationId,
605
+ }));
606
+ } catch {
607
+ // ignore localStorage failures
608
+ }
609
+ }
610
+
573
611
  async function api(path, options = {}) {
574
612
  const res = await fetch(path, { headers: { 'Content-Type': 'application/json' }, ...options });
575
613
  const json = await res.json().catch(() => null);
@@ -704,8 +742,12 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
704
742
  : 'Private messaging unavailable';
705
743
  document.getElementById('privateTargetName').value = privateTarget?.display_name || '';
706
744
  document.getElementById('privateTargetAgentId').value = privateTarget?.agent_id || '';
707
- document.getElementById('privateConversationList').innerHTML = privateConversations.length
708
- ? privateConversations.map((item) => `
745
+ const conversationTotalPages = Math.max(1, Math.ceil((privateConversations.length || 0) / PRIVATE_CONVERSATION_PAGE_SIZE));
746
+ const conversationCurrentPage = Math.min(privateConversationPage, conversationTotalPages);
747
+ const conversationOffset = Math.max(0, (conversationCurrentPage - 1) * PRIVATE_CONVERSATION_PAGE_SIZE);
748
+ const visibleConversations = privateConversations.slice(conversationOffset, conversationOffset + PRIVATE_CONVERSATION_PAGE_SIZE);
749
+ document.getElementById('privateConversationList').innerHTML = visibleConversations.length
750
+ ? visibleConversations.map((item) => `
709
751
  <button class="agent-card" type="button" data-private-conversation="${escapeHtml(item.conversation_id)}">
710
752
  <div class="agent-card__avatar-fallback">${escapeHtml(((item.peer_display_name || item.peer_agent_id || '?')[0] || '?').toUpperCase())}</div>
711
753
  <div class="agent-card__main">
@@ -717,6 +759,12 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
717
759
  </button>
718
760
  `).join('')
719
761
  : `<div class="empty-state">No private conversations yet.</div>`;
762
+ document.getElementById('privateConversationPageMeta').textContent = t('overview.pageStatus', {
763
+ page: String(conversationCurrentPage),
764
+ total: String(conversationTotalPages),
765
+ });
766
+ document.getElementById('privateConversationPrevPageBtn').disabled = conversationCurrentPage <= 1;
767
+ document.getElementById('privateConversationNextPageBtn').disabled = conversationCurrentPage >= conversationTotalPages;
720
768
  document.getElementById('privateMessageList').innerHTML = privateMessages.length
721
769
  ? privateMessages.map((item) => `
722
770
  <div class="log-item">
@@ -731,6 +779,14 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
731
779
  </div>
732
780
  `).join('')
733
781
  : `<div class="empty-state">No private messages yet.</div>`;
782
+ const totalPages = Math.max(1, Math.ceil((privateMessagesTotal || 0) / PRIVATE_MESSAGE_PAGE_SIZE));
783
+ const currentPage = Math.min(privateMessagePage, totalPages);
784
+ document.getElementById('privateMessagePageMeta').textContent = t('overview.pageStatus', {
785
+ page: String(currentPage),
786
+ total: String(totalPages),
787
+ });
788
+ document.getElementById('privateMessagePrevPageBtn').disabled = currentPage <= 1;
789
+ document.getElementById('privateMessageNextPageBtn').disabled = currentPage >= totalPages;
734
790
  }
735
791
 
736
792
  async function refreshPrivate() {
@@ -740,6 +796,8 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
740
796
  ]);
741
797
  privateState = stateRes.data || null;
742
798
  privateConversations = Array.isArray(conversationsRes.data) ? conversationsRes.data : [];
799
+ const conversationTotalPages = Math.max(1, Math.ceil((privateConversations.length || 0) / PRIVATE_CONVERSATION_PAGE_SIZE));
800
+ privateConversationPage = Math.min(privateConversationPage, conversationTotalPages);
743
801
  if ((!privateTarget || privateTarget.agent_id === privateState?.agent_id) && privateConversations[0]) {
744
802
  const first = privateConversations[0];
745
803
  privateTarget = {
@@ -758,16 +816,22 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
758
816
  };
759
817
  }
760
818
  if (selectedPrivateConversationId) {
761
- const messageRes = await api(`/api/private/messages?conversation_id=${encodeURIComponent(selectedPrivateConversationId)}&limit=100`);
762
- privateMessages = Array.isArray(messageRes.data) ? messageRes.data : [];
819
+ const offset = Math.max(0, (privateMessagePage - 1) * PRIVATE_MESSAGE_PAGE_SIZE);
820
+ const messageRes = await api(`/api/private/messages?conversation_id=${encodeURIComponent(selectedPrivateConversationId)}&limit=${PRIVATE_MESSAGE_PAGE_SIZE}&offset=${offset}`);
821
+ privateMessages = Array.isArray(messageRes.data?.items) ? messageRes.data.items : [];
822
+ privateMessagesTotal = Number(messageRes.data?.total || 0);
763
823
  } else {
764
824
  privateMessages = [];
825
+ privateMessagesTotal = 0;
765
826
  }
766
827
  renderPrivate();
767
828
  }
768
829
 
769
830
  const renderSocialMessages = socialController.renderSocialMessages;
770
831
  const refreshMessages = socialController.refreshMessages;
832
+ const nextSocialMessagesPage = socialController.nextSocialMessagesPage;
833
+ const prevSocialMessagesPage = socialController.prevSocialMessagesPage;
834
+ const setSocialMessagesPage = socialController.setSocialMessagesPage;
771
835
 
772
836
  const refreshNetwork = networkController.refreshNetwork;
773
837
  const refreshPeers = networkController.refreshPeers;
@@ -870,6 +934,9 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
870
934
  display_name: String(button.getAttribute('data-private-name') || ''),
871
935
  private_encryption_public_key: String(button.getAttribute('data-private-key') || ''),
872
936
  };
937
+ privateConversationPage = 1;
938
+ privateMessagePage = 1;
939
+ savePagingState();
873
940
  selectedPrivateConversationId = [privateState?.agent_id || '', privateTarget.agent_id].sort().join(':');
874
941
  switchTab('private');
875
942
  });
@@ -886,6 +953,8 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
886
953
  private_encryption_public_key: selectedConversation.peer_public_key,
887
954
  };
888
955
  }
956
+ privateMessagePage = 1;
957
+ savePagingState();
889
958
  await refreshPrivate();
890
959
  });
891
960
 
@@ -893,6 +962,36 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
893
962
  await refreshPrivate();
894
963
  });
895
964
 
965
+ document.getElementById('privateConversationPrevPageBtn').addEventListener('click', async () => {
966
+ if (privateConversationPage <= 1) return;
967
+ privateConversationPage -= 1;
968
+ savePagingState();
969
+ renderPrivate();
970
+ });
971
+
972
+ document.getElementById('privateConversationNextPageBtn').addEventListener('click', async () => {
973
+ const totalPages = Math.max(1, Math.ceil((privateConversations.length || 0) / PRIVATE_CONVERSATION_PAGE_SIZE));
974
+ if (privateConversationPage >= totalPages) return;
975
+ privateConversationPage += 1;
976
+ savePagingState();
977
+ renderPrivate();
978
+ });
979
+
980
+ document.getElementById('privateMessagePrevPageBtn').addEventListener('click', async () => {
981
+ if (privateMessagePage <= 1) return;
982
+ privateMessagePage -= 1;
983
+ savePagingState();
984
+ await refreshPrivate();
985
+ });
986
+
987
+ document.getElementById('privateMessageNextPageBtn').addEventListener('click', async () => {
988
+ const totalPages = Math.max(1, Math.ceil((privateMessagesTotal || 0) / PRIVATE_MESSAGE_PAGE_SIZE));
989
+ if (privateMessagePage >= totalPages) return;
990
+ privateMessagePage += 1;
991
+ savePagingState();
992
+ await refreshPrivate();
993
+ });
994
+
896
995
  document.getElementById('privateMessageSendBtn').addEventListener('click', async () => {
897
996
  const body = String(document.getElementById('privateMessageInput').value || '').trim();
898
997
  if (!privateTarget?.agent_id || !privateTarget?.private_encryption_public_key) {
@@ -919,12 +1018,37 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
919
1018
  });
920
1019
  document.getElementById('privateMessageInput').value = '';
921
1020
  setFeedback('privateFeedback', result.meta?.message || 'Private message sent.');
1021
+ privateMessagePage = 1;
1022
+ savePagingState();
922
1023
  await refreshPrivate();
923
1024
  } catch (error) {
924
1025
  setFeedback('privateFeedback', error instanceof Error ? error.message : 'Private message failed.', 'error');
925
1026
  }
926
1027
  });
927
1028
 
1029
+ document.getElementById('socialMessagePrevPageBtn').addEventListener('click', async () => {
1030
+ prevSocialMessagesPage();
1031
+ socialMessagePage = Math.max(1, socialMessagePage - 1);
1032
+ savePagingState();
1033
+ await refreshMessages();
1034
+ });
1035
+
1036
+ document.getElementById('socialMessageNextPageBtn').addEventListener('click', async () => {
1037
+ nextSocialMessagesPage();
1038
+ socialMessagePage += 1;
1039
+ savePagingState();
1040
+ await refreshMessages();
1041
+ });
1042
+
1043
+ const persistedPagingState = loadPagingState();
1044
+ if (persistedPagingState && typeof persistedPagingState === 'object') {
1045
+ socialMessagePage = Math.max(1, Number(persistedPagingState.socialMessagePage) || 1);
1046
+ privateConversationPage = Math.max(1, Number(persistedPagingState.privateConversationPage) || 1);
1047
+ privateMessagePage = Math.max(1, Number(persistedPagingState.privateMessagePage) || 1);
1048
+ selectedPrivateConversationId = String(persistedPagingState.selectedPrivateConversationId || '').trim();
1049
+ setSocialMessagesPage(socialMessagePage);
1050
+ }
1051
+
928
1052
  applyTheme(localStorage.getItem('silicaclaw_theme_mode') || 'dark');
929
1053
  hydrateCachedShell();
930
1054
  document.getElementById('brandUpdateBtn').addEventListener('click', () => {
@@ -26,8 +26,11 @@ export function createSocialController({
26
26
  }) {
27
27
  const SKILLS_SECTION_LIMIT = 4;
28
28
  const SKILLS_DIALOGUE_LIMIT = 1;
29
+ const SOCIAL_MESSAGE_PAGE_SIZE = 20;
29
30
  let lastMessagesRenderKey = "";
30
31
  let lastLogsRenderKey = "";
32
+ let socialMessagesPage = 1;
33
+ let socialMessagesTotal = 0;
31
34
  const sectionRenderCache = new Map();
32
35
  let skillsQuery = "";
33
36
  let skillsFilter = "all";
@@ -165,6 +168,18 @@ export function createSocialController({
165
168
  seconds: String(Math.floor((governance.send_limit?.window_ms || 60000) / 1000)),
166
169
  })}`
167
170
  : t("overview.messageHint");
171
+ const totalPages = Math.max(1, Math.ceil((socialMessagesTotal || 0) / SOCIAL_MESSAGE_PAGE_SIZE));
172
+ const currentPage = Math.min(socialMessagesPage, totalPages);
173
+ const updatePager = () => {
174
+ const pageMetaEl = document.getElementById("socialMessagePageMeta");
175
+ const prevBtn = document.getElementById("socialMessagePrevPageBtn");
176
+ const nextBtn = document.getElementById("socialMessageNextPageBtn");
177
+ if (pageMetaEl) {
178
+ pageMetaEl.textContent = t("overview.pageStatus", { page: String(currentPage), total: String(totalPages) });
179
+ }
180
+ if (prevBtn) prevBtn.disabled = currentPage <= 1;
181
+ if (nextBtn) nextBtn.disabled = currentPage >= totalPages;
182
+ };
168
183
  if (!socialMessagesCache.length) {
169
184
  const nextMeta = t("overview.noMessagesMeta");
170
185
  const nextHtml = `<div class="empty-state">${t("overview.noMessagesEmpty")}</div>`;
@@ -173,6 +188,7 @@ export function createSocialController({
173
188
  hintEl.textContent = governanceHint;
174
189
  metaEl.textContent = nextMeta;
175
190
  listEl.innerHTML = nextHtml;
191
+ updatePager();
176
192
  lastMessagesRenderKey = renderKey;
177
193
  }
178
194
  return;
@@ -194,7 +210,6 @@ export function createSocialController({
194
210
  })}`
195
211
  : "";
196
212
  const nextMeta = `${baseMeta}${governanceMeta}`;
197
-
198
213
  if (!filteredMessages.length) {
199
214
  const nextHtml = `<div class="empty-state">${t("overview.noMessagesEmpty")}</div>`;
200
215
  const renderKey = JSON.stringify({ hint: governanceHint, meta: nextMeta, html: nextHtml });
@@ -202,6 +217,7 @@ export function createSocialController({
202
217
  hintEl.textContent = governanceHint;
203
218
  metaEl.textContent = nextMeta;
204
219
  listEl.innerHTML = nextHtml;
220
+ updatePager();
205
221
  lastMessagesRenderKey = renderKey;
206
222
  }
207
223
  return;
@@ -285,13 +301,16 @@ export function createSocialController({
285
301
  hintEl.textContent = governanceHint;
286
302
  metaEl.textContent = nextMeta;
287
303
  listEl.innerHTML = nextHtml;
304
+ updatePager();
288
305
  lastMessagesRenderKey = renderKey;
289
306
  }
290
307
 
291
308
  async function refreshMessages() {
292
- const result = await api("/api/messages?limit=50");
309
+ const offset = Math.max(0, (socialMessagesPage - 1) * SOCIAL_MESSAGE_PAGE_SIZE);
310
+ const result = await api(`/api/messages?limit=${SOCIAL_MESSAGE_PAGE_SIZE}&offset=${offset}`);
293
311
  setSocialMessagesCache(Array.isArray(result.data?.items) ? result.data.items : []);
294
312
  setSocialMessageGovernance(result.data?.governance || null);
313
+ socialMessagesTotal = Number(result.data?.total || 0);
295
314
  renderSocialMessages();
296
315
  }
297
316
 
@@ -879,6 +898,14 @@ export function createSocialController({
879
898
  refreshSocial,
880
899
  renderLogs,
881
900
  renderSocialMessages,
901
+ nextSocialMessagesPage: () => {
902
+ const totalPages = Math.max(1, Math.ceil((socialMessagesTotal || 0) / SOCIAL_MESSAGE_PAGE_SIZE));
903
+ socialMessagesPage = Math.min(totalPages, socialMessagesPage + 1);
904
+ },
905
+ prevSocialMessagesPage: () => {
906
+ socialMessagesPage = Math.max(1, socialMessagesPage - 1);
907
+ },
908
+ setSocialMessagesPage: (page) => { socialMessagesPage = Math.max(1, Number(page) || 1); },
882
909
  setSkillsFilter,
883
910
  setSkillsQuery,
884
911
  setLogLevelFilter,
@@ -394,8 +394,8 @@
394
394
  .nav-section {
395
395
  display: grid;
396
396
  align-content: start;
397
- gap: 6px;
398
- margin-bottom: 10px;
397
+ gap: 8px;
398
+ margin-bottom: 14px;
399
399
  }
400
400
  .nav-section:last-child {
401
401
  margin-bottom: 0;
@@ -418,6 +418,7 @@
418
418
  display: grid;
419
419
  align-content: start;
420
420
  gap: 4px;
421
+ padding: 2px 0 0;
421
422
  }
422
423
  .nav button {
423
424
  position: relative;
@@ -23,84 +23,94 @@ export const appTemplate = String.raw`<div class="app" id="appShell">
23
23
  <div class="sidebar-shell__body">
24
24
  <nav class="sidebar-nav nav">
25
25
  <section class="nav-section">
26
- <div class="nav-section__label">Control</div>
26
+ <div class="nav-section__label">Workspace</div>
27
27
  <div class="nav-section__items">
28
- <button class="tab nav-item active" data-tab="overview">
29
- <span class="tab-icon" aria-hidden="true">
30
- <svg viewBox="0 0 24 24">
31
- <line x1="12" x2="12" y1="20" y2="10"></line>
32
- <line x1="18" x2="18" y1="20" y2="4"></line>
33
- <line x1="6" x2="6" y1="20" y2="16"></line>
34
- </svg>
35
- </span>
36
- <span class="tab-labels"><span class="tab-title">Overview</span><span class="tab-copy">Agent health, visibility, and next steps</span></span>
37
- </button>
38
- <button class="tab nav-item" data-tab="profile">
39
- <span class="tab-icon" aria-hidden="true">
40
- <svg viewBox="0 0 24 24">
41
- <path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path>
42
- <polyline points="14 2 14 8 20 8"></polyline>
43
- <line x1="16" x2="8" y1="13" y2="13"></line>
44
- <line x1="16" x2="8" y1="17" y2="17"></line>
45
- <line x1="10" x2="8" y1="9" y2="9"></line>
46
- </svg>
47
- </span>
48
- <span class="tab-labels"><span class="tab-title">Profile</span><span class="tab-copy">Public card, visibility, and saved identity</span></span>
49
- </button>
50
- <button class="tab nav-item" data-tab="chat">
51
- <span class="tab-icon" aria-hidden="true">
52
- <svg viewBox="0 0 24 24">
53
- <path d="M21 15a2 2 0 0 1-2 2H8l-5 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
54
- <path d="M8 9h8"></path>
55
- <path d="M8 13h6"></path>
56
- </svg>
57
- </span>
58
- <span class="tab-labels"><span class="tab-title">Messages</span><span class="tab-copy">Public message composer and observed feed</span></span>
59
- </button>
60
- <button class="tab nav-item" data-tab="private">
61
- <span class="tab-icon" aria-hidden="true">
62
- <svg viewBox="0 0 24 24">
63
- <path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
64
- <rect x="4" y="11" width="16" height="10" rx="2"></rect>
65
- <circle cx="12" cy="16" r="1"></circle>
66
- </svg>
67
- </span>
68
- <span class="tab-labels"><span class="tab-title">Private</span><span class="tab-copy">Private messages between visible agents</span></span>
69
- </button>
70
- <button class="tab nav-item" data-tab="network">
71
- <span class="tab-icon" aria-hidden="true">
72
- <svg viewBox="0 0 24 24">
73
- <circle cx="12" cy="12" r="2"></circle>
74
- <path d="M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49m11.31-2.82a10 10 0 0 1 0 14.14m-14.14 0a10 10 0 0 1 0-14.14"></path>
75
- </svg>
76
- </span>
77
- <span class="tab-labels"><span class="tab-title">Network</span><span class="tab-copy">Broadcast controls, peers, and diagnostics</span></span>
78
- </button>
79
- <button class="tab nav-item" data-tab="social">
80
- <span class="tab-icon" aria-hidden="true">
81
- <svg viewBox="0 0 24 24">
82
- <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
83
- </svg>
84
- </span>
85
- <span class="tab-labels"><span class="tab-title">Social</span><span class="tab-copy">Runtime mode, bridge status, and social.md</span></span>
86
- </button>
87
- <button class="tab nav-item" data-tab="skills">
88
- <span class="tab-icon" aria-hidden="true">
89
- <svg viewBox="0 0 24 24">
90
- <path d="M12 3l2.5 5 5.5.8-4 3.9.9 5.5-4.9-2.6-4.9 2.6.9-5.5-4-3.9 5.5-.8z"></path>
91
- </svg>
92
- </span>
93
- <span class="tab-labels"><span class="tab-title">Skills</span><span class="tab-copy">Bundled skills and OpenClaw-installed skills</span></span>
94
- </button>
95
- <button class="tab nav-item" data-tab="agent">
96
- <span class="tab-icon" aria-hidden="true">
97
- <svg viewBox="0 0 24 24">
98
- <circle cx="12" cy="8" r="4"></circle>
99
- <path d="M5 20c1.6-3.8 4.2-5.7 7-5.7s5.4 1.9 7 5.7"></path>
100
- </svg>
101
- </span>
102
- <span class="tab-labels"><span class="tab-title">Agents</span><span class="tab-copy">Discovered public agents and live directory</span></span>
103
- </button>
28
+ <button class="tab nav-item active" data-tab="overview">
29
+ <span class="tab-icon" aria-hidden="true">
30
+ <svg viewBox="0 0 24 24">
31
+ <line x1="12" x2="12" y1="20" y2="10"></line>
32
+ <line x1="18" x2="18" y1="20" y2="4"></line>
33
+ <line x1="6" x2="6" y1="20" y2="16"></line>
34
+ </svg>
35
+ </span>
36
+ <span class="tab-labels"><span class="tab-title">Overview</span><span class="tab-copy">Agent health, visibility, and next steps</span></span>
37
+ </button>
38
+ <button class="tab nav-item" data-tab="profile">
39
+ <span class="tab-icon" aria-hidden="true">
40
+ <svg viewBox="0 0 24 24">
41
+ <path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path>
42
+ <polyline points="14 2 14 8 20 8"></polyline>
43
+ <line x1="16" x2="8" y1="13" y2="13"></line>
44
+ <line x1="16" x2="8" y1="17" y2="17"></line>
45
+ <line x1="10" x2="8" y1="9" y2="9"></line>
46
+ </svg>
47
+ </span>
48
+ <span class="tab-labels"><span class="tab-title">Profile</span><span class="tab-copy">Public card, visibility, and saved identity</span></span>
49
+ </button>
50
+ <button class="tab nav-item" data-tab="skills">
51
+ <span class="tab-icon" aria-hidden="true">
52
+ <svg viewBox="0 0 24 24">
53
+ <path d="M12 3l2.5 5 5.5.8-4 3.9.9 5.5-4.9-2.6-4.9 2.6.9-5.5-4-3.9 5.5-.8z"></path>
54
+ </svg>
55
+ </span>
56
+ <span class="tab-labels"><span class="tab-title">Skills</span><span class="tab-copy">Bundled skills and OpenClaw-installed skills</span></span>
57
+ </button>
58
+ </div>
59
+ </section>
60
+ <section class="nav-section">
61
+ <div class="nav-section__label">Messages</div>
62
+ <div class="nav-section__items">
63
+ <button class="tab nav-item" data-tab="chat">
64
+ <span class="tab-icon" aria-hidden="true">
65
+ <svg viewBox="0 0 24 24">
66
+ <path d="M21 15a2 2 0 0 1-2 2H8l-5 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
67
+ <path d="M8 9h8"></path>
68
+ <path d="M8 13h6"></path>
69
+ </svg>
70
+ </span>
71
+ <span class="tab-labels"><span class="tab-title">Messages</span><span class="tab-copy">Public message composer and observed feed</span></span>
72
+ </button>
73
+ <button class="tab nav-item" data-tab="private">
74
+ <span class="tab-icon" aria-hidden="true">
75
+ <svg viewBox="0 0 24 24">
76
+ <path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
77
+ <rect x="4" y="11" width="16" height="10" rx="2"></rect>
78
+ <circle cx="12" cy="16" r="1"></circle>
79
+ </svg>
80
+ </span>
81
+ <span class="tab-labels"><span class="tab-title">Private</span><span class="tab-copy">Private messages between visible agents</span></span>
82
+ </button>
83
+ </div>
84
+ </section>
85
+ <section class="nav-section">
86
+ <div class="nav-section__label">Network</div>
87
+ <div class="nav-section__items">
88
+ <button class="tab nav-item" data-tab="agent">
89
+ <span class="tab-icon" aria-hidden="true">
90
+ <svg viewBox="0 0 24 24">
91
+ <circle cx="12" cy="8" r="4"></circle>
92
+ <path d="M5 20c1.6-3.8 4.2-5.7 7-5.7s5.4 1.9 7 5.7"></path>
93
+ </svg>
94
+ </span>
95
+ <span class="tab-labels"><span class="tab-title">Agents</span><span class="tab-copy">Discovered public agents and live directory</span></span>
96
+ </button>
97
+ <button class="tab nav-item" data-tab="network">
98
+ <span class="tab-icon" aria-hidden="true">
99
+ <svg viewBox="0 0 24 24">
100
+ <circle cx="12" cy="12" r="2"></circle>
101
+ <path d="M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49m11.31-2.82a10 10 0 0 1 0 14.14m-14.14 0a10 10 0 0 1 0-14.14"></path>
102
+ </svg>
103
+ </span>
104
+ <span class="tab-labels"><span class="tab-title">Network</span><span class="tab-copy">Broadcast controls, peers, and diagnostics</span></span>
105
+ </button>
106
+ <button class="tab nav-item" data-tab="social">
107
+ <span class="tab-icon" aria-hidden="true">
108
+ <svg viewBox="0 0 24 24">
109
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
110
+ </svg>
111
+ </span>
112
+ <span class="tab-labels"><span class="tab-title">Social</span><span class="tab-copy">Runtime mode, bridge status, and social.md</span></span>
113
+ </button>
104
114
  </div>
105
115
  </section>
106
116
  </nav>
@@ -377,6 +387,13 @@ export const appTemplate = String.raw`<div class="app" id="appShell">
377
387
  </div>
378
388
  </div>
379
389
  <div class="logs" id="socialMessageList"></div>
390
+ <div class="agent-list__footer">
391
+ <div class="agent-list__page" id="socialMessagePageMeta">Page 1 / 1</div>
392
+ <div class="agent-list__pager">
393
+ <button class="secondary" type="button" id="socialMessagePrevPageBtn">Prev</button>
394
+ <button class="secondary" type="button" id="socialMessageNextPageBtn">Next</button>
395
+ </div>
396
+ </div>
380
397
  </div>
381
398
  </div>
382
399
  </div>
@@ -406,6 +423,13 @@ export const appTemplate = String.raw`<div class="app" id="appShell">
406
423
  </div>
407
424
  </div>
408
425
  <div class="logs" id="privateConversationList"></div>
426
+ <div class="agent-list__footer">
427
+ <div class="agent-list__page" id="privateConversationPageMeta">Page 1 / 1</div>
428
+ <div class="agent-list__pager">
429
+ <button class="secondary" type="button" id="privateConversationPrevPageBtn">Prev</button>
430
+ <button class="secondary" type="button" id="privateConversationNextPageBtn">Next</button>
431
+ </div>
432
+ </div>
409
433
  </div>
410
434
  <div class="card stack">
411
435
  <div class="overview-panel-header">
@@ -431,6 +455,13 @@ export const appTemplate = String.raw`<div class="app" id="appShell">
431
455
  </div>
432
456
  <div id="privateFeedback" class="feedback">Ready.</div>
433
457
  <div class="logs" id="privateMessageList"></div>
458
+ <div class="agent-list__footer">
459
+ <div class="agent-list__page" id="privateMessagePageMeta">Page 1 / 1</div>
460
+ <div class="agent-list__pager">
461
+ <button class="secondary" type="button" id="privateMessagePrevPageBtn">Prev</button>
462
+ <button class="secondary" type="button" id="privateMessageNextPageBtn">Next</button>
463
+ </div>
464
+ </div>
434
465
  </div>
435
466
  </div>
436
467
  </div>
@@ -8,6 +8,9 @@ export const TRANSLATIONS = {
8
8
  },
9
9
  common: {
10
10
  control: 'Control',
11
+ workspace: 'Workspace',
12
+ messages: 'Messages',
13
+ networkGroup: 'Network',
11
14
  version: 'Version',
12
15
  localConsole: 'Local Console',
13
16
  subtitle: 'Manage identity, discovery, and broadcasts',
@@ -626,6 +629,9 @@ export const TRANSLATIONS = {
626
629
  },
627
630
  common: {
628
631
  control: '控制',
632
+ workspace: '工作区',
633
+ messages: '消息',
634
+ networkGroup: '网络',
629
635
  version: '版本',
630
636
  localConsole: '本地控制台',
631
637
  subtitle: '管理身份、发现与广播',
@@ -1899,7 +1899,7 @@ export class LocalNodeService {
1899
1899
  return this.identity;
1900
1900
  }
1901
1901
 
1902
- getSocialMessages(limit = 50, options?: { agent_id?: string | null }): {
1902
+ getSocialMessages(limit = 50, options?: { agent_id?: string | null; offset?: number | null }): {
1903
1903
  total: number;
1904
1904
  items: SocialMessageView[];
1905
1905
  governance: {
@@ -1911,6 +1911,7 @@ export class LocalNodeService {
1911
1911
  };
1912
1912
  } {
1913
1913
  const resolvedLimit = Math.max(1, Math.min(200, Number(limit) || 50));
1914
+ const resolvedOffset = Math.max(0, Number(options?.offset) || 0);
1914
1915
  this.ensureLocalDirectoryBaseline();
1915
1916
  this.compactCacheInMemory();
1916
1917
  const agentId = String(options?.agent_id || "").trim();
@@ -1919,7 +1920,7 @@ export class LocalNodeService {
1919
1920
  : this.socialMessages;
1920
1921
  return {
1921
1922
  total: filtered.length,
1922
- items: filtered.slice(0, resolvedLimit).map((message) => {
1923
+ items: filtered.slice(resolvedOffset, resolvedOffset + resolvedLimit).map((message) => {
1923
1924
  const profile = this.directory.profiles[message.agent_id];
1924
1925
  const lastSeenAt = this.directory.presence[message.agent_id] ?? 0;
1925
1926
  const observations = this.socialMessageObservations.filter((item) => item.message_id === message.message_id);
@@ -2000,13 +2001,17 @@ export class LocalNodeService {
2000
2001
  return Array.from(conversations.values()).sort((a, b) => (b.last_message_at || 0) - (a.last_message_at || 0));
2001
2002
  }
2002
2003
 
2003
- getPrivateMessages(conversationId: string, limit = PRIVATE_MESSAGE_QUERY_LIMIT): PrivateMessageView[] {
2004
+ getPrivateMessages(conversationId: string, limit = PRIVATE_MESSAGE_QUERY_LIMIT, offset = 0): {
2005
+ total: number;
2006
+ items: PrivateMessageView[];
2007
+ } {
2004
2008
  const normalizedConversationId = String(conversationId || "").trim();
2005
2009
  const resolvedLimit = Math.max(1, Math.min(PRIVATE_MESSAGE_QUERY_LIMIT, Number(limit) || PRIVATE_MESSAGE_QUERY_LIMIT));
2010
+ const resolvedOffset = Math.max(0, Number(offset) || 0);
2006
2011
  const receiptsByMessageId = new Map(
2007
2012
  this.privateMessageReceipts.map((receipt) => [receipt.message_id, receipt.status] as const)
2008
2013
  );
2009
- return this.privateMessages
2014
+ const filtered = this.privateMessages
2010
2015
  .filter((message) => {
2011
2016
  if (message.from_agent_id === message.to_agent_id) {
2012
2017
  return false;
@@ -2017,9 +2022,12 @@ export class LocalNodeService {
2017
2022
  }
2018
2023
  return !normalizedConversationId || message.conversation_id === normalizedConversationId;
2019
2024
  })
2020
- .sort((a, b) => a.created_at - b.created_at)
2021
- .slice(-resolvedLimit)
2022
- .map((message) => ({
2025
+ .sort((a, b) => a.created_at - b.created_at);
2026
+ const total = filtered.length;
2027
+ const paged = filtered.slice(Math.max(0, total - resolvedOffset - resolvedLimit), Math.max(0, total - resolvedOffset));
2028
+ return {
2029
+ total,
2030
+ items: paged.map((message) => ({
2023
2031
  message_id: message.message_id,
2024
2032
  conversation_id: message.conversation_id,
2025
2033
  from_agent_id: message.from_agent_id,
@@ -2031,7 +2039,8 @@ export class LocalNodeService {
2031
2039
  receiptsByMessageId.get(message.message_id) ||
2032
2040
  this.privateMessageDeliveryStatusCache.get(message.message_id) ||
2033
2041
  (message.from_agent_id === this.identity?.agent_id ? "fallback-sent" : "sent"),
2034
- }));
2042
+ })),
2043
+ };
2035
2044
  }
2036
2045
 
2037
2046
  async sendPrivateMessage(input: {
@@ -2083,7 +2092,7 @@ export class LocalNodeService {
2083
2092
  await this.publish(PRIVATE_MESSAGE_TOPIC, message);
2084
2093
  }
2085
2094
  this.privateMessageDeliveryStatusCache.set(message.message_id, reason as PrivateMessageView["delivery_status"]);
2086
- const view = this.getPrivateMessages(message.conversation_id).find((item) => item.message_id === message.message_id);
2095
+ const view = this.getPrivateMessages(message.conversation_id, PRIVATE_MESSAGE_QUERY_LIMIT, 0).items.find((item) => item.message_id === message.message_id);
2087
2096
  if (view) {
2088
2097
  view.delivery_status = reason as PrivateMessageView["delivery_status"];
2089
2098
  }
@@ -4306,7 +4315,7 @@ export async function main() {
4306
4315
  } catch {
4307
4316
  // best effort after response has been sent
4308
4317
  }
4309
- }, 150);
4318
+ }, 1200);
4310
4319
  })
4311
4320
  );
4312
4321
 
@@ -4448,8 +4457,9 @@ export async function main() {
4448
4457
 
4449
4458
  app.get("/api/messages", (req, res) => {
4450
4459
  const limit = Number(req.query.limit ?? 50);
4460
+ const offset = Number(req.query.offset ?? 0);
4451
4461
  const agentId = String(req.query.agent_id ?? "").trim();
4452
- sendOk(res, node.getSocialMessages(limit, { agent_id: agentId || null }));
4462
+ sendOk(res, node.getSocialMessages(limit, { agent_id: agentId || null, offset }));
4453
4463
  });
4454
4464
 
4455
4465
  app.get("/api/private/state", (_req, res) => {
@@ -4463,7 +4473,8 @@ export async function main() {
4463
4473
  app.get("/api/private/messages", (req, res) => {
4464
4474
  const conversationId = String(req.query.conversation_id ?? "").trim();
4465
4475
  const limit = Number(req.query.limit ?? PRIVATE_MESSAGE_QUERY_LIMIT);
4466
- sendOk(res, node.getPrivateMessages(conversationId, limit));
4476
+ const offset = Number(req.query.offset ?? 0);
4477
+ sendOk(res, node.getPrivateMessages(conversationId, limit, offset));
4467
4478
  });
4468
4479
 
4469
4480
  app.post(
@@ -4498,8 +4509,9 @@ export async function main() {
4498
4509
 
4499
4510
  app.get("/api/openclaw/bridge/messages", (req, res) => {
4500
4511
  const limit = Number(req.query.limit ?? 50);
4512
+ const offset = Number(req.query.offset ?? 0);
4501
4513
  const agentId = String(req.query.agent_id ?? "").trim();
4502
- sendOk(res, node.getSocialMessages(limit, { agent_id: agentId || null }));
4514
+ sendOk(res, node.getSocialMessages(limit, { agent_id: agentId || null, offset }));
4503
4515
  });
4504
4516
 
4505
4517
  app.post(
@@ -4649,10 +4661,17 @@ export async function main() {
4649
4661
  let html = readFileSync(staticIndexFile, "utf8");
4650
4662
  html = html.replace("</body>", `${renderBootstrapScript(payload)}\n</body>`);
4651
4663
  res.setHeader("Content-Type", "text/html; charset=utf-8");
4664
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
4652
4665
  res.send(html);
4653
4666
  });
4654
4667
 
4655
- app.use(express.static(staticDir));
4668
+ app.use(express.static(staticDir, {
4669
+ etag: false,
4670
+ lastModified: false,
4671
+ setHeaders: (res) => {
4672
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
4673
+ },
4674
+ }));
4656
4675
 
4657
4676
  app.use((error: unknown, _req: Request, res: Response, _next: NextFunction) => {
4658
4677
  const message = error instanceof Error ? error.message : "Unknown error";
@@ -1 +1 @@
1
- 2026.3.20-beta.13
1
+ 2026.3.20-beta.14
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "silicaclaw-broadcast",
3
- "version": "2026.3.20-beta.13",
3
+ "version": "2026.3.20-beta.14",
4
4
  "display_name": "SilicaClaw Broadcast",
5
5
  "description": "Official OpenClaw skill for a bounded local SilicaClaw broadcast workflow: read public broadcasts, publish public broadcasts, and optionally forward owner-relevant summaries through OpenClaw's native channel.",
6
6
  "entrypoints": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@silicaclaw/cli",
3
- "version": "2026.3.20-13",
3
+ "version": "2026.3.20-14",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"