@silicaclaw/cli 2026.3.20-17 → 2026.3.20-19

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,18 @@
2
2
 
3
3
  ## v1.0 beta - 2026-03-20
4
4
 
5
+ ### 2026.3.20-19
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
+
11
+ ### 2026.3.20-18
12
+
13
+ - release build:
14
+ - prepared another fresh latest-channel package build without publishing
15
+ - regenerated the npm tarball through the verified release packing workflow
16
+
5
17
  ### 2026.3.20-17
6
18
 
7
19
  - release build:
package/VERSION CHANGED
@@ -1 +1 @@
1
- v2026.3.20-17
1
+ v2026.3.20-19
@@ -1,5 +1,5 @@
1
1
  import { AgentIdentity, DirectoryState, ProfileInput, PublicProfile, PublicProfileSummary, SocialConfig, SocialMessageRecord, SocialRuntimeConfig } from "@silicaclaw/core";
2
- import { SocialMessageGovernanceConfig } from "@silicaclaw/storage";
2
+ import { PrivateMessagingRuntimeState, SocialMessageGovernanceConfig } from "@silicaclaw/storage";
3
3
  type InitState = {
4
4
  identity_auto_created: boolean;
5
5
  profile_auto_created: boolean;
@@ -170,6 +170,7 @@ export declare class LocalNodeService {
170
170
  private privateMessageRepo;
171
171
  private privateMessageReceiptRepo;
172
172
  private privateEncryptionKeyRepo;
173
+ private privateMessagingRuntimeRepo;
173
174
  private socialRuntimeRepo;
174
175
  private identity;
175
176
  private profile;
@@ -179,7 +180,9 @@ export declare class LocalNodeService {
179
180
  private privateMessages;
180
181
  private privateMessageReceipts;
181
182
  private privateEncryptionKeyPair;
183
+ private privateMessagingRuntime;
182
184
  private privatePeerRoutes;
185
+ private privatePeerEncryptionKeys;
183
186
  private privateMessageBodyCache;
184
187
  private privateMessageDeliveryStatusCache;
185
188
  private messageGovernance;
@@ -646,6 +649,7 @@ export declare class LocalNodeService {
646
649
  encryption_public_key: string;
647
650
  conversation_count: number;
648
651
  message_count: number;
652
+ runtime: PrivateMessagingRuntimeState | null;
649
653
  };
650
654
  getPrivateConversations(): Array<{
651
655
  conversation_id: string;
@@ -850,6 +854,8 @@ export declare class LocalNodeService {
850
854
  private persistPrivateMessageReceipts;
851
855
  private flushPrivatePersistence;
852
856
  private flushPrivateMessagesPersist;
857
+ private hydratePrivateMessageBodyCache;
858
+ private buildPersistedPrivateMessages;
853
859
  private flushPrivateMessageReceiptsPersist;
854
860
  private log;
855
861
  private getAdapterDiagnostics;
@@ -857,6 +863,8 @@ export declare class LocalNodeService {
857
863
  private toPublicProfileSummary;
858
864
  private mergeMessageOnlyAgentSummaries;
859
865
  private fingerprintPublicKey;
866
+ private buildPrivateMessagingRuntimeState;
867
+ private refreshPrivateMessagingRuntime;
860
868
  private getOnboardingSummary;
861
869
  private getDefaultDisplayName;
862
870
  private getModeExplainer;
@@ -776,6 +776,7 @@ class LocalNodeService {
776
776
  privateMessageRepo;
777
777
  privateMessageReceiptRepo;
778
778
  privateEncryptionKeyRepo;
779
+ privateMessagingRuntimeRepo;
779
780
  socialRuntimeRepo;
780
781
  identity = null;
781
782
  profile = null;
@@ -785,7 +786,9 @@ class LocalNodeService {
785
786
  privateMessages = [];
786
787
  privateMessageReceipts = [];
787
788
  privateEncryptionKeyPair = null;
789
+ privateMessagingRuntime = null;
788
790
  privatePeerRoutes = {};
791
+ privatePeerEncryptionKeys = {};
789
792
  privateMessageBodyCache = new Map();
790
793
  privateMessageDeliveryStatusCache = new Map();
791
794
  messageGovernance;
@@ -862,6 +865,7 @@ class LocalNodeService {
862
865
  this.privateMessageRepo = new storage_1.PrivateMessageRepo(this.storageRoot);
863
866
  this.privateMessageReceiptRepo = new storage_1.PrivateMessageReceiptRepo(this.storageRoot);
864
867
  this.privateEncryptionKeyRepo = new storage_1.PrivateEncryptionKeyRepo(this.storageRoot);
868
+ this.privateMessagingRuntimeRepo = new storage_1.PrivateMessagingRuntimeRepo(this.storageRoot);
865
869
  this.socialRuntimeRepo = new storage_1.SocialRuntimeRepo(this.storageRoot);
866
870
  this.messageGovernance = this.defaultMessageGovernance();
867
871
  let loadedSocial = (0, core_1.loadSocialConfig)(this.projectRoot);
@@ -1623,6 +1627,7 @@ class LocalNodeService {
1623
1627
  encryption_public_key: this.privateEncryptionKeyPair?.public_key || "",
1624
1628
  conversation_count: this.getPrivateConversations().length,
1625
1629
  message_count: this.privateMessages.length,
1630
+ runtime: this.privateMessagingRuntime,
1626
1631
  };
1627
1632
  }
1628
1633
  getPrivateConversations() {
@@ -1638,12 +1643,13 @@ class LocalNodeService {
1638
1643
  const peerProfile = this.directory.profiles[peerAgentId];
1639
1644
  const current = conversations.get(message.conversation_id);
1640
1645
  const nextLast = Math.max(current?.last_message_at || 0, message.created_at || 0) || null;
1646
+ const learnedPeerKey = this.privatePeerEncryptionKeys[peerAgentId] || "";
1641
1647
  conversations.set(message.conversation_id, {
1642
1648
  conversation_id: message.conversation_id,
1643
1649
  peer_agent_id: peerAgentId,
1644
1650
  peer_display_name: peerProfile?.display_name || peerAgentId,
1645
1651
  peer_avatar_url: peerProfile?.avatar_url || "",
1646
- peer_public_key: peerProfile?.private_encryption_public_key || "",
1652
+ peer_public_key: learnedPeerKey || peerProfile?.private_encryption_public_key || "",
1647
1653
  last_message_at: nextLast,
1648
1654
  unread_count: current?.unread_count || 0,
1649
1655
  });
@@ -1665,8 +1671,8 @@ class LocalNodeService {
1665
1671
  }
1666
1672
  return !normalizedConversationId || message.conversation_id === normalizedConversationId;
1667
1673
  })
1668
- .sort((a, b) => a.created_at - b.created_at)
1669
- .slice(-resolvedLimit)
1674
+ .sort((a, b) => b.created_at - a.created_at)
1675
+ .slice(0, resolvedLimit)
1670
1676
  .map((message) => ({
1671
1677
  message_id: message.message_id,
1672
1678
  conversation_id: message.conversation_id,
@@ -1685,7 +1691,9 @@ class LocalNodeService {
1685
1691
  return { sent: false, reason: "missing_identity_or_private_key" };
1686
1692
  }
1687
1693
  const toAgentId = String(input.to_agent_id || "").trim();
1688
- const recipientKey = String(input.recipient_encryption_public_key || "").trim();
1694
+ const learnedRecipientKey = this.privatePeerEncryptionKeys[toAgentId] || "";
1695
+ const profileRecipientKey = this.directory.profiles[toAgentId]?.private_encryption_public_key || "";
1696
+ const recipientKey = String(learnedRecipientKey || input.recipient_encryption_public_key || profileRecipientKey || "").trim();
1689
1697
  const body = String(input.body || "").trim();
1690
1698
  if (toAgentId === this.identity.agent_id) {
1691
1699
  return { sent: false, reason: "self_private_message_not_allowed" };
@@ -1717,6 +1725,7 @@ class LocalNodeService {
1717
1725
  if (toPeerId && typeof this.network.sendDirect === "function") {
1718
1726
  try {
1719
1727
  await this.network.sendDirect(toPeerId, PRIVATE_MESSAGE_TOPIC, message);
1728
+ await this.publish(PRIVATE_MESSAGE_TOPIC, message);
1720
1729
  reason = "direct-sent";
1721
1730
  }
1722
1731
  catch {
@@ -2345,8 +2354,11 @@ class LocalNodeService {
2345
2354
  };
2346
2355
  this.socialMessages = this.normalizeSocialMessages(await this.socialMessageRepo.get());
2347
2356
  this.socialMessageObservations = this.normalizeSocialMessageObservations(await this.socialMessageObservationRepo.get());
2348
- this.privateMessages = this.normalizePrivateMessages(await this.privateMessageRepo.get());
2357
+ const storedPrivateMessages = await this.privateMessageRepo.get();
2358
+ this.hydratePrivateMessageBodyCache(storedPrivateMessages);
2359
+ this.privateMessages = this.normalizePrivateMessages(storedPrivateMessages);
2349
2360
  this.privateMessageReceipts = this.normalizePrivateMessageReceipts(await this.privateMessageReceiptRepo.get());
2361
+ await this.refreshPrivateMessagingRuntime();
2350
2362
  this.directory = (0, core_1.ingestProfileRecord)(this.directory, { type: "profile", profile: this.profile });
2351
2363
  this.compactCacheInMemory();
2352
2364
  await this.persistCache();
@@ -2439,7 +2451,7 @@ class LocalNodeService {
2439
2451
  return;
2440
2452
  }
2441
2453
  }
2442
- if (meta?.peerId && record.profile.agent_id) {
2454
+ if (meta?.peerId && record.profile.agent_id && !this.privatePeerRoutes[record.profile.agent_id]) {
2443
2455
  this.privatePeerRoutes[record.profile.agent_id] = meta.peerId;
2444
2456
  }
2445
2457
  this.directory = (0, core_1.ingestProfileRecord)(this.directory, record);
@@ -2458,7 +2470,7 @@ class LocalNodeService {
2458
2470
  return;
2459
2471
  }
2460
2472
  }
2461
- if (meta?.peerId && record.agent_id) {
2473
+ if (meta?.peerId && record.agent_id && !this.privatePeerRoutes[record.agent_id]) {
2462
2474
  this.privatePeerRoutes[record.agent_id] = meta.peerId;
2463
2475
  }
2464
2476
  this.directory = (0, core_1.ingestPresenceRecord)(this.directory, record);
@@ -2475,7 +2487,7 @@ class LocalNodeService {
2475
2487
  await this.log("warn", `Rejected social message with invalid signature (${record.message_id.slice(0, 10)})`);
2476
2488
  return;
2477
2489
  }
2478
- if (meta?.peerId && record.agent_id) {
2490
+ if (meta?.peerId && record.agent_id && !this.privatePeerRoutes[record.agent_id]) {
2479
2491
  this.privatePeerRoutes[record.agent_id] = meta.peerId;
2480
2492
  }
2481
2493
  if (this.hasSocialMessage(record.message_id)) {
@@ -2519,6 +2531,12 @@ class LocalNodeService {
2519
2531
  if (!record || !(0, core_1.verifyPrivateMessage)(record)) {
2520
2532
  return;
2521
2533
  }
2534
+ if (meta?.peerId && record.from_agent_id) {
2535
+ this.privatePeerRoutes[record.from_agent_id] = meta.peerId;
2536
+ }
2537
+ if (record.from_agent_id && record.sender_encryption_public_key) {
2538
+ this.privatePeerEncryptionKeys[record.from_agent_id] = record.sender_encryption_public_key;
2539
+ }
2522
2540
  if (record.to_agent_id !== this.identity?.agent_id || this.hasPrivateMessage(record.message_id)) {
2523
2541
  return;
2524
2542
  }
@@ -2531,6 +2549,9 @@ class LocalNodeService {
2531
2549
  if (!receipt || !(0, core_1.verifyPrivateMessageReceipt)(receipt)) {
2532
2550
  return;
2533
2551
  }
2552
+ if (meta?.peerId && receipt.from_agent_id) {
2553
+ this.privatePeerRoutes[receipt.from_agent_id] = meta.peerId;
2554
+ }
2534
2555
  if (receipt.to_agent_id !== this.identity?.agent_id) {
2535
2556
  return;
2536
2557
  }
@@ -2840,7 +2861,37 @@ class LocalNodeService {
2840
2861
  return;
2841
2862
  }
2842
2863
  this.privateMessagesPersistDirty = false;
2843
- await this.privateMessageRepo.set(this.privateMessages);
2864
+ await this.privateMessageRepo.set(this.buildPersistedPrivateMessages());
2865
+ }
2866
+ hydratePrivateMessageBodyCache(items) {
2867
+ if (!Array.isArray(items)) {
2868
+ return;
2869
+ }
2870
+ for (const item of items) {
2871
+ if (typeof item !== "object" || item === null) {
2872
+ continue;
2873
+ }
2874
+ const record = item;
2875
+ const messageId = String(record.message_id || "").trim();
2876
+ const localPlaintext = typeof record.local_plaintext === "string" ? record.local_plaintext : "";
2877
+ if (messageId && localPlaintext) {
2878
+ this.privateMessageBodyCache.set(messageId, localPlaintext);
2879
+ }
2880
+ }
2881
+ }
2882
+ buildPersistedPrivateMessages() {
2883
+ return this.privateMessages.map((message) => {
2884
+ const localPlaintext = message.from_agent_id === this.identity?.agent_id
2885
+ ? this.privateMessageBodyCache.get(message.message_id) || ""
2886
+ : "";
2887
+ if (!localPlaintext) {
2888
+ return { ...message };
2889
+ }
2890
+ return {
2891
+ ...message,
2892
+ local_plaintext: localPlaintext,
2893
+ };
2894
+ });
2844
2895
  }
2845
2896
  async flushPrivateMessageReceiptsPersist() {
2846
2897
  if (this.privateMessageReceiptsPersistTimer) {
@@ -2975,6 +3026,45 @@ class LocalNodeService {
2975
3026
  const digest = (0, crypto_1.createHash)("sha256").update(publicKey, "utf8").digest("hex");
2976
3027
  return `${digest.slice(0, 12)}:${digest.slice(-8)}`;
2977
3028
  }
3029
+ buildPrivateMessagingRuntimeState() {
3030
+ const warnings = [];
3031
+ const keypair = this.privateEncryptionKeyPair;
3032
+ const selfSentMessages = this.privateMessages.filter((message) => message.from_agent_id === this.identity?.agent_id);
3033
+ let cachedPlaintextCount = 0;
3034
+ for (const message of selfSentMessages) {
3035
+ if (this.privateMessageBodyCache.get(message.message_id)) {
3036
+ cachedPlaintextCount += 1;
3037
+ }
3038
+ }
3039
+ if (!keypair?.public_key || !keypair?.private_key) {
3040
+ warnings.push("missing_private_encryption_keypair");
3041
+ }
3042
+ if (selfSentMessages.length > 0 && cachedPlaintextCount === 0) {
3043
+ warnings.push("missing_local_plaintext_cache_for_self_messages");
3044
+ }
3045
+ if (selfSentMessages.length > 0 && cachedPlaintextCount < selfSentMessages.length) {
3046
+ warnings.push("partial_local_plaintext_cache_for_self_messages");
3047
+ }
3048
+ return {
3049
+ schema_version: 1,
3050
+ app_version: this.appVersion,
3051
+ last_started_at: Date.now(),
3052
+ encryption_public_key: keypair?.public_key || "",
3053
+ encryption_public_key_fingerprint: keypair?.public_key ? this.fingerprintPublicKey(keypair.public_key) : "",
3054
+ message_count: this.privateMessages.length,
3055
+ self_sent_count: selfSentMessages.length,
3056
+ cached_plaintext_count: cachedPlaintextCount,
3057
+ warnings,
3058
+ };
3059
+ }
3060
+ async refreshPrivateMessagingRuntime() {
3061
+ const runtime = this.buildPrivateMessagingRuntimeState();
3062
+ this.privateMessagingRuntime = runtime;
3063
+ await this.privateMessagingRuntimeRepo.set(runtime);
3064
+ for (const warning of runtime.warnings) {
3065
+ await this.log("warn", `Private messaging startup check: ${warning}`);
3066
+ }
3067
+ }
2978
3068
  getOnboardingSummary() {
2979
3069
  const summary = this.getIntegrationSummary();
2980
3070
  const publicEnabled = Boolean(this.profile?.public_enabled);
@@ -3282,6 +3372,7 @@ class LocalNodeService {
3282
3372
  this.ingestPrivateMessageReceipt(receipt);
3283
3373
  try {
3284
3374
  await this.network.sendDirect(replyPeerId, PRIVATE_MESSAGE_RECEIPT_TOPIC, receipt);
3375
+ await this.publish(PRIVATE_MESSAGE_RECEIPT_TOPIC, receipt);
3285
3376
  }
3286
3377
  catch {
3287
3378
  await this.publish(PRIVATE_MESSAGE_RECEIPT_TOPIC, receipt);
@@ -40,6 +40,17 @@ export type SocialMessageGovernanceConfig = {
40
40
  export type PrivateMessageDecryptedContent = {
41
41
  body: string;
42
42
  };
43
+ export type PrivateMessagingRuntimeState = {
44
+ schema_version: number;
45
+ app_version: string;
46
+ last_started_at: number;
47
+ encryption_public_key: string;
48
+ encryption_public_key_fingerprint: string;
49
+ message_count: number;
50
+ self_sent_count: number;
51
+ cached_plaintext_count: number;
52
+ warnings: string[];
53
+ };
43
54
  export declare class IdentityRepo extends JsonFileRepo<AgentIdentity | null> {
44
55
  constructor(rootDir?: string);
45
56
  }
@@ -71,3 +82,6 @@ export declare class PrivateMessageReceiptRepo extends JsonFileRepo<PrivateMessa
71
82
  export declare class PrivateEncryptionKeyRepo extends JsonFileRepo<PrivateEncryptionKeyPair | null> {
72
83
  constructor(rootDir?: string);
73
84
  }
85
+ export declare class PrivateMessagingRuntimeRepo extends JsonFileRepo<PrivateMessagingRuntimeState> {
86
+ constructor(rootDir?: string);
87
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PrivateEncryptionKeyRepo = exports.PrivateMessageReceiptRepo = exports.PrivateMessageRepo = exports.SocialMessageGovernanceRepo = exports.SocialMessageObservationRepo = exports.SocialMessageRepo = exports.LogRepo = exports.CacheRepo = exports.ProfileRepo = exports.IdentityRepo = void 0;
3
+ exports.PrivateMessagingRuntimeRepo = exports.PrivateEncryptionKeyRepo = exports.PrivateMessageReceiptRepo = exports.PrivateMessageRepo = exports.SocialMessageGovernanceRepo = exports.SocialMessageObservationRepo = exports.SocialMessageRepo = exports.LogRepo = exports.CacheRepo = exports.ProfileRepo = exports.IdentityRepo = void 0;
4
4
  const path_1 = require("path");
5
5
  const core_1 = require("@silicaclaw/core");
6
6
  const jsonRepo_1 = require("./jsonRepo");
@@ -83,3 +83,19 @@ class PrivateEncryptionKeyRepo extends jsonRepo_1.JsonFileRepo {
83
83
  }
84
84
  }
85
85
  exports.PrivateEncryptionKeyRepo = PrivateEncryptionKeyRepo;
86
+ class PrivateMessagingRuntimeRepo extends jsonRepo_1.JsonFileRepo {
87
+ constructor(rootDir = process.cwd()) {
88
+ super((0, path_1.resolve)(rootDir, ".silicaclaw", "private-messaging.runtime.json"), () => ({
89
+ schema_version: 1,
90
+ app_version: "",
91
+ last_started_at: 0,
92
+ encryption_public_key: "",
93
+ encryption_public_key_fingerprint: "",
94
+ message_count: 0,
95
+ self_sent_count: 0,
96
+ cached_plaintext_count: 0,
97
+ warnings: [],
98
+ }));
99
+ }
100
+ }
101
+ exports.PrivateMessagingRuntimeRepo = PrivateMessagingRuntimeRepo;
@@ -72,6 +72,8 @@ import {
72
72
  IdentityRepo,
73
73
  LogRepo,
74
74
  PrivateEncryptionKeyRepo,
75
+ PrivateMessagingRuntimeRepo,
76
+ PrivateMessagingRuntimeState,
75
77
  PrivateMessageReceiptRepo,
76
78
  PrivateMessageRepo,
77
79
  ProfileRepo,
@@ -139,6 +141,10 @@ const SOCIAL_MESSAGE_REPLAY_MAX_PER_BROADCAST = Number(process.env.SOCIAL_MESSAG
139
141
  const SOCIAL_MESSAGE_REPLAY_REFRESH_INTERVAL_MS = Number(
140
142
  process.env.SOCIAL_MESSAGE_REPLAY_REFRESH_INTERVAL_MS || 120_000
141
143
  );
144
+
145
+ type StoredPrivateMessageRecord = PrivateMessageRecord & {
146
+ local_plaintext?: string;
147
+ };
142
148
  const PROFILE_RELAY_REFRESH_INTERVAL_MS = Number(
143
149
  process.env.PROFILE_RELAY_REFRESH_INTERVAL_MS || 120_000
144
150
  );
@@ -1035,6 +1041,7 @@ export class LocalNodeService {
1035
1041
  private privateMessageRepo: PrivateMessageRepo;
1036
1042
  private privateMessageReceiptRepo: PrivateMessageReceiptRepo;
1037
1043
  private privateEncryptionKeyRepo: PrivateEncryptionKeyRepo;
1044
+ private privateMessagingRuntimeRepo: PrivateMessagingRuntimeRepo;
1038
1045
  private socialRuntimeRepo: SocialRuntimeRepo;
1039
1046
 
1040
1047
  private identity: AgentIdentity | null = null;
@@ -1045,7 +1052,9 @@ export class LocalNodeService {
1045
1052
  private privateMessages: PrivateMessageRecord[] = [];
1046
1053
  private privateMessageReceipts: PrivateMessageReceiptRecord[] = [];
1047
1054
  private privateEncryptionKeyPair: PrivateEncryptionKeyPair | null = null;
1055
+ private privateMessagingRuntime: PrivateMessagingRuntimeState | null = null;
1048
1056
  private privatePeerRoutes: Record<string, string> = {};
1057
+ private privatePeerEncryptionKeys: Record<string, string> = {};
1049
1058
  private privateMessageBodyCache = new Map<string, string>();
1050
1059
  private privateMessageDeliveryStatusCache = new Map<string, PrivateMessageView["delivery_status"]>();
1051
1060
  private messageGovernance: RuntimeMessageGovernance;
@@ -1129,6 +1138,7 @@ export class LocalNodeService {
1129
1138
  this.privateMessageRepo = new PrivateMessageRepo(this.storageRoot);
1130
1139
  this.privateMessageReceiptRepo = new PrivateMessageReceiptRepo(this.storageRoot);
1131
1140
  this.privateEncryptionKeyRepo = new PrivateEncryptionKeyRepo(this.storageRoot);
1141
+ this.privateMessagingRuntimeRepo = new PrivateMessagingRuntimeRepo(this.storageRoot);
1132
1142
  this.socialRuntimeRepo = new SocialRuntimeRepo(this.storageRoot);
1133
1143
  this.messageGovernance = this.defaultMessageGovernance();
1134
1144
 
@@ -1955,6 +1965,7 @@ export class LocalNodeService {
1955
1965
  encryption_public_key: this.privateEncryptionKeyPair?.public_key || "",
1956
1966
  conversation_count: this.getPrivateConversations().length,
1957
1967
  message_count: this.privateMessages.length,
1968
+ runtime: this.privateMessagingRuntime,
1958
1969
  };
1959
1970
  }
1960
1971
 
@@ -1987,12 +1998,13 @@ export class LocalNodeService {
1987
1998
  const peerProfile = this.directory.profiles[peerAgentId];
1988
1999
  const current = conversations.get(message.conversation_id);
1989
2000
  const nextLast = Math.max(current?.last_message_at || 0, message.created_at || 0) || null;
2001
+ const learnedPeerKey = this.privatePeerEncryptionKeys[peerAgentId] || "";
1990
2002
  conversations.set(message.conversation_id, {
1991
2003
  conversation_id: message.conversation_id,
1992
2004
  peer_agent_id: peerAgentId,
1993
2005
  peer_display_name: peerProfile?.display_name || peerAgentId,
1994
2006
  peer_avatar_url: peerProfile?.avatar_url || "",
1995
- peer_public_key: peerProfile?.private_encryption_public_key || "",
2007
+ peer_public_key: learnedPeerKey || peerProfile?.private_encryption_public_key || "",
1996
2008
  last_message_at: nextLast,
1997
2009
  unread_count: current?.unread_count || 0,
1998
2010
  });
@@ -2017,8 +2029,8 @@ export class LocalNodeService {
2017
2029
  }
2018
2030
  return !normalizedConversationId || message.conversation_id === normalizedConversationId;
2019
2031
  })
2020
- .sort((a, b) => a.created_at - b.created_at)
2021
- .slice(-resolvedLimit)
2032
+ .sort((a, b) => b.created_at - a.created_at)
2033
+ .slice(0, resolvedLimit)
2022
2034
  .map((message) => ({
2023
2035
  message_id: message.message_id,
2024
2036
  conversation_id: message.conversation_id,
@@ -2043,7 +2055,9 @@ export class LocalNodeService {
2043
2055
  return { sent: false, reason: "missing_identity_or_private_key" };
2044
2056
  }
2045
2057
  const toAgentId = String(input.to_agent_id || "").trim();
2046
- const recipientKey = String(input.recipient_encryption_public_key || "").trim();
2058
+ const learnedRecipientKey = this.privatePeerEncryptionKeys[toAgentId] || "";
2059
+ const profileRecipientKey = this.directory.profiles[toAgentId]?.private_encryption_public_key || "";
2060
+ const recipientKey = String(learnedRecipientKey || input.recipient_encryption_public_key || profileRecipientKey || "").trim();
2047
2061
  const body = String(input.body || "").trim();
2048
2062
  if (toAgentId === this.identity.agent_id) {
2049
2063
  return { sent: false, reason: "self_private_message_not_allowed" };
@@ -2075,6 +2089,7 @@ export class LocalNodeService {
2075
2089
  if (toPeerId && typeof this.network.sendDirect === "function") {
2076
2090
  try {
2077
2091
  await this.network.sendDirect(toPeerId, PRIVATE_MESSAGE_TOPIC, message);
2092
+ await this.publish(PRIVATE_MESSAGE_TOPIC, message);
2078
2093
  reason = "direct-sent";
2079
2094
  } catch {
2080
2095
  await this.publish(PRIVATE_MESSAGE_TOPIC, message);
@@ -2763,8 +2778,11 @@ export class LocalNodeService {
2763
2778
  };
2764
2779
  this.socialMessages = this.normalizeSocialMessages(await this.socialMessageRepo.get());
2765
2780
  this.socialMessageObservations = this.normalizeSocialMessageObservations(await this.socialMessageObservationRepo.get());
2766
- this.privateMessages = this.normalizePrivateMessages(await this.privateMessageRepo.get());
2781
+ const storedPrivateMessages = await this.privateMessageRepo.get();
2782
+ this.hydratePrivateMessageBodyCache(storedPrivateMessages);
2783
+ this.privateMessages = this.normalizePrivateMessages(storedPrivateMessages);
2767
2784
  this.privateMessageReceipts = this.normalizePrivateMessageReceipts(await this.privateMessageReceiptRepo.get());
2785
+ await this.refreshPrivateMessagingRuntime();
2768
2786
  this.directory = ingestProfileRecord(this.directory, { type: "profile", profile: this.profile });
2769
2787
  this.compactCacheInMemory();
2770
2788
  await this.persistCache();
@@ -2870,7 +2888,7 @@ export class LocalNodeService {
2870
2888
  return;
2871
2889
  }
2872
2890
  }
2873
- if (meta?.peerId && record.profile.agent_id) {
2891
+ if (meta?.peerId && record.profile.agent_id && !this.privatePeerRoutes[record.profile.agent_id]) {
2874
2892
  this.privatePeerRoutes[record.profile.agent_id] = meta.peerId;
2875
2893
  }
2876
2894
 
@@ -2892,7 +2910,7 @@ export class LocalNodeService {
2892
2910
  return;
2893
2911
  }
2894
2912
  }
2895
- if (meta?.peerId && record.agent_id) {
2913
+ if (meta?.peerId && record.agent_id && !this.privatePeerRoutes[record.agent_id]) {
2896
2914
  this.privatePeerRoutes[record.agent_id] = meta.peerId;
2897
2915
  }
2898
2916
 
@@ -2911,7 +2929,7 @@ export class LocalNodeService {
2911
2929
  await this.log("warn", `Rejected social message with invalid signature (${record.message_id.slice(0, 10)})`);
2912
2930
  return;
2913
2931
  }
2914
- if (meta?.peerId && record.agent_id) {
2932
+ if (meta?.peerId && record.agent_id && !this.privatePeerRoutes[record.agent_id]) {
2915
2933
  this.privatePeerRoutes[record.agent_id] = meta.peerId;
2916
2934
  }
2917
2935
  if (this.hasSocialMessage(record.message_id)) {
@@ -2962,6 +2980,12 @@ export class LocalNodeService {
2962
2980
  if (!record || !verifyPrivateMessage(record)) {
2963
2981
  return;
2964
2982
  }
2983
+ if (meta?.peerId && record.from_agent_id) {
2984
+ this.privatePeerRoutes[record.from_agent_id] = meta.peerId;
2985
+ }
2986
+ if (record.from_agent_id && record.sender_encryption_public_key) {
2987
+ this.privatePeerEncryptionKeys[record.from_agent_id] = record.sender_encryption_public_key;
2988
+ }
2965
2989
  if (record.to_agent_id !== this.identity?.agent_id || this.hasPrivateMessage(record.message_id)) {
2966
2990
  return;
2967
2991
  }
@@ -2975,6 +2999,9 @@ export class LocalNodeService {
2975
2999
  if (!receipt || !verifyPrivateMessageReceipt(receipt)) {
2976
3000
  return;
2977
3001
  }
3002
+ if (meta?.peerId && receipt.from_agent_id) {
3003
+ this.privatePeerRoutes[receipt.from_agent_id] = meta.peerId;
3004
+ }
2978
3005
  if (receipt.to_agent_id !== this.identity?.agent_id) {
2979
3006
  return;
2980
3007
  }
@@ -3323,7 +3350,40 @@ export class LocalNodeService {
3323
3350
  return;
3324
3351
  }
3325
3352
  this.privateMessagesPersistDirty = false;
3326
- await this.privateMessageRepo.set(this.privateMessages);
3353
+ await this.privateMessageRepo.set(this.buildPersistedPrivateMessages() as unknown as PrivateMessageRecord[]);
3354
+ }
3355
+
3356
+ private hydratePrivateMessageBodyCache(items: unknown): void {
3357
+ if (!Array.isArray(items)) {
3358
+ return;
3359
+ }
3360
+ for (const item of items) {
3361
+ if (typeof item !== "object" || item === null) {
3362
+ continue;
3363
+ }
3364
+ const record = item as Partial<StoredPrivateMessageRecord>;
3365
+ const messageId = String(record.message_id || "").trim();
3366
+ const localPlaintext = typeof record.local_plaintext === "string" ? record.local_plaintext : "";
3367
+ if (messageId && localPlaintext) {
3368
+ this.privateMessageBodyCache.set(messageId, localPlaintext);
3369
+ }
3370
+ }
3371
+ }
3372
+
3373
+ private buildPersistedPrivateMessages(): StoredPrivateMessageRecord[] {
3374
+ return this.privateMessages.map((message) => {
3375
+ const localPlaintext =
3376
+ message.from_agent_id === this.identity?.agent_id
3377
+ ? this.privateMessageBodyCache.get(message.message_id) || ""
3378
+ : "";
3379
+ if (!localPlaintext) {
3380
+ return { ...message };
3381
+ }
3382
+ return {
3383
+ ...message,
3384
+ local_plaintext: localPlaintext,
3385
+ };
3386
+ });
3327
3387
  }
3328
3388
 
3329
3389
  private async flushPrivateMessageReceiptsPersist(): Promise<void> {
@@ -3482,6 +3542,47 @@ export class LocalNodeService {
3482
3542
  return `${digest.slice(0, 12)}:${digest.slice(-8)}`;
3483
3543
  }
3484
3544
 
3545
+ private buildPrivateMessagingRuntimeState(): PrivateMessagingRuntimeState {
3546
+ const warnings: string[] = [];
3547
+ const keypair = this.privateEncryptionKeyPair;
3548
+ const selfSentMessages = this.privateMessages.filter((message) => message.from_agent_id === this.identity?.agent_id);
3549
+ let cachedPlaintextCount = 0;
3550
+ for (const message of selfSentMessages) {
3551
+ if (this.privateMessageBodyCache.get(message.message_id)) {
3552
+ cachedPlaintextCount += 1;
3553
+ }
3554
+ }
3555
+ if (!keypair?.public_key || !keypair?.private_key) {
3556
+ warnings.push("missing_private_encryption_keypair");
3557
+ }
3558
+ if (selfSentMessages.length > 0 && cachedPlaintextCount === 0) {
3559
+ warnings.push("missing_local_plaintext_cache_for_self_messages");
3560
+ }
3561
+ if (selfSentMessages.length > 0 && cachedPlaintextCount < selfSentMessages.length) {
3562
+ warnings.push("partial_local_plaintext_cache_for_self_messages");
3563
+ }
3564
+ return {
3565
+ schema_version: 1,
3566
+ app_version: this.appVersion,
3567
+ last_started_at: Date.now(),
3568
+ encryption_public_key: keypair?.public_key || "",
3569
+ encryption_public_key_fingerprint: keypair?.public_key ? this.fingerprintPublicKey(keypair.public_key) : "",
3570
+ message_count: this.privateMessages.length,
3571
+ self_sent_count: selfSentMessages.length,
3572
+ cached_plaintext_count: cachedPlaintextCount,
3573
+ warnings,
3574
+ };
3575
+ }
3576
+
3577
+ private async refreshPrivateMessagingRuntime(): Promise<void> {
3578
+ const runtime = this.buildPrivateMessagingRuntimeState();
3579
+ this.privateMessagingRuntime = runtime;
3580
+ await this.privateMessagingRuntimeRepo.set(runtime);
3581
+ for (const warning of runtime.warnings) {
3582
+ await this.log("warn", `Private messaging startup check: ${warning}`);
3583
+ }
3584
+ }
3585
+
3485
3586
  private getOnboardingSummary() {
3486
3587
  const summary = this.getIntegrationSummary();
3487
3588
  const publicEnabled = Boolean(this.profile?.public_enabled);
@@ -3823,6 +3924,7 @@ export class LocalNodeService {
3823
3924
  this.ingestPrivateMessageReceipt(receipt);
3824
3925
  try {
3825
3926
  await this.network.sendDirect(replyPeerId, PRIVATE_MESSAGE_RECEIPT_TOPIC, receipt);
3927
+ await this.publish(PRIVATE_MESSAGE_RECEIPT_TOPIC, receipt);
3826
3928
  } catch {
3827
3929
  await this.publish(PRIVATE_MESSAGE_RECEIPT_TOPIC, receipt);
3828
3930
  }
@@ -40,6 +40,17 @@ export type SocialMessageGovernanceConfig = {
40
40
  export type PrivateMessageDecryptedContent = {
41
41
  body: string;
42
42
  };
43
+ export type PrivateMessagingRuntimeState = {
44
+ schema_version: number;
45
+ app_version: string;
46
+ last_started_at: number;
47
+ encryption_public_key: string;
48
+ encryption_public_key_fingerprint: string;
49
+ message_count: number;
50
+ self_sent_count: number;
51
+ cached_plaintext_count: number;
52
+ warnings: string[];
53
+ };
43
54
  export declare class IdentityRepo extends JsonFileRepo<AgentIdentity | null> {
44
55
  constructor(rootDir?: string);
45
56
  }
@@ -71,3 +82,6 @@ export declare class PrivateMessageReceiptRepo extends JsonFileRepo<PrivateMessa
71
82
  export declare class PrivateEncryptionKeyRepo extends JsonFileRepo<PrivateEncryptionKeyPair | null> {
72
83
  constructor(rootDir?: string);
73
84
  }
85
+ export declare class PrivateMessagingRuntimeRepo extends JsonFileRepo<PrivateMessagingRuntimeState> {
86
+ constructor(rootDir?: string);
87
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PrivateEncryptionKeyRepo = exports.PrivateMessageReceiptRepo = exports.PrivateMessageRepo = exports.SocialMessageGovernanceRepo = exports.SocialMessageObservationRepo = exports.SocialMessageRepo = exports.LogRepo = exports.CacheRepo = exports.ProfileRepo = exports.IdentityRepo = void 0;
3
+ exports.PrivateMessagingRuntimeRepo = exports.PrivateEncryptionKeyRepo = exports.PrivateMessageReceiptRepo = exports.PrivateMessageRepo = exports.SocialMessageGovernanceRepo = exports.SocialMessageObservationRepo = exports.SocialMessageRepo = exports.LogRepo = exports.CacheRepo = exports.ProfileRepo = exports.IdentityRepo = void 0;
4
4
  const path_1 = require("path");
5
5
  const core_1 = require("@silicaclaw/core");
6
6
  const jsonRepo_1 = require("./jsonRepo");
@@ -83,3 +83,19 @@ class PrivateEncryptionKeyRepo extends jsonRepo_1.JsonFileRepo {
83
83
  }
84
84
  }
85
85
  exports.PrivateEncryptionKeyRepo = PrivateEncryptionKeyRepo;
86
+ class PrivateMessagingRuntimeRepo extends jsonRepo_1.JsonFileRepo {
87
+ constructor(rootDir = process.cwd()) {
88
+ super((0, path_1.resolve)(rootDir, ".silicaclaw", "private-messaging.runtime.json"), () => ({
89
+ schema_version: 1,
90
+ app_version: "",
91
+ last_started_at: 0,
92
+ encryption_public_key: "",
93
+ encryption_public_key_fingerprint: "",
94
+ message_count: 0,
95
+ self_sent_count: 0,
96
+ cached_plaintext_count: 0,
97
+ warnings: [],
98
+ }));
99
+ }
100
+ }
101
+ exports.PrivateMessagingRuntimeRepo = PrivateMessagingRuntimeRepo;
@@ -55,6 +55,18 @@ export type PrivateMessageDecryptedContent = {
55
55
  body: string;
56
56
  };
57
57
 
58
+ export type PrivateMessagingRuntimeState = {
59
+ schema_version: number;
60
+ app_version: string;
61
+ last_started_at: number;
62
+ encryption_public_key: string;
63
+ encryption_public_key_fingerprint: string;
64
+ message_count: number;
65
+ self_sent_count: number;
66
+ cached_plaintext_count: number;
67
+ warnings: string[];
68
+ };
69
+
58
70
  export class IdentityRepo extends JsonFileRepo<AgentIdentity | null> {
59
71
  constructor(rootDir = process.cwd()) {
60
72
  super(resolve(rootDir, "data", "identity.json"), () => null);
@@ -134,3 +146,19 @@ export class PrivateEncryptionKeyRepo extends JsonFileRepo<PrivateEncryptionKeyP
134
146
  super(resolve(rootDir, "data", "private-encryption-keypair.json"), () => null);
135
147
  }
136
148
  }
149
+
150
+ export class PrivateMessagingRuntimeRepo extends JsonFileRepo<PrivateMessagingRuntimeState> {
151
+ constructor(rootDir = process.cwd()) {
152
+ super(resolve(rootDir, ".silicaclaw", "private-messaging.runtime.json"), () => ({
153
+ schema_version: 1,
154
+ app_version: "",
155
+ last_started_at: 0,
156
+ encryption_public_key: "",
157
+ encryption_public_key_fingerprint: "",
158
+ message_count: 0,
159
+ self_sent_count: 0,
160
+ cached_plaintext_count: 0,
161
+ warnings: [],
162
+ }));
163
+ }
164
+ }
@@ -1 +1 @@
1
- 2026.3.20-beta.17
1
+ 2026.3.20-beta.19
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "silicaclaw-broadcast",
3
- "version": "2026.3.20-beta.17",
3
+ "version": "2026.3.20-beta.19",
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-17",
3
+ "version": "2026.3.20-19",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -40,6 +40,17 @@ export type SocialMessageGovernanceConfig = {
40
40
  export type PrivateMessageDecryptedContent = {
41
41
  body: string;
42
42
  };
43
+ export type PrivateMessagingRuntimeState = {
44
+ schema_version: number;
45
+ app_version: string;
46
+ last_started_at: number;
47
+ encryption_public_key: string;
48
+ encryption_public_key_fingerprint: string;
49
+ message_count: number;
50
+ self_sent_count: number;
51
+ cached_plaintext_count: number;
52
+ warnings: string[];
53
+ };
43
54
  export declare class IdentityRepo extends JsonFileRepo<AgentIdentity | null> {
44
55
  constructor(rootDir?: string);
45
56
  }
@@ -71,3 +82,6 @@ export declare class PrivateMessageReceiptRepo extends JsonFileRepo<PrivateMessa
71
82
  export declare class PrivateEncryptionKeyRepo extends JsonFileRepo<PrivateEncryptionKeyPair | null> {
72
83
  constructor(rootDir?: string);
73
84
  }
85
+ export declare class PrivateMessagingRuntimeRepo extends JsonFileRepo<PrivateMessagingRuntimeState> {
86
+ constructor(rootDir?: string);
87
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PrivateEncryptionKeyRepo = exports.PrivateMessageReceiptRepo = exports.PrivateMessageRepo = exports.SocialMessageGovernanceRepo = exports.SocialMessageObservationRepo = exports.SocialMessageRepo = exports.LogRepo = exports.CacheRepo = exports.ProfileRepo = exports.IdentityRepo = void 0;
3
+ exports.PrivateMessagingRuntimeRepo = exports.PrivateEncryptionKeyRepo = exports.PrivateMessageReceiptRepo = exports.PrivateMessageRepo = exports.SocialMessageGovernanceRepo = exports.SocialMessageObservationRepo = exports.SocialMessageRepo = exports.LogRepo = exports.CacheRepo = exports.ProfileRepo = exports.IdentityRepo = void 0;
4
4
  const path_1 = require("path");
5
5
  const core_1 = require("@silicaclaw/core");
6
6
  const jsonRepo_1 = require("./jsonRepo");
@@ -83,3 +83,19 @@ class PrivateEncryptionKeyRepo extends jsonRepo_1.JsonFileRepo {
83
83
  }
84
84
  }
85
85
  exports.PrivateEncryptionKeyRepo = PrivateEncryptionKeyRepo;
86
+ class PrivateMessagingRuntimeRepo extends jsonRepo_1.JsonFileRepo {
87
+ constructor(rootDir = process.cwd()) {
88
+ super((0, path_1.resolve)(rootDir, ".silicaclaw", "private-messaging.runtime.json"), () => ({
89
+ schema_version: 1,
90
+ app_version: "",
91
+ last_started_at: 0,
92
+ encryption_public_key: "",
93
+ encryption_public_key_fingerprint: "",
94
+ message_count: 0,
95
+ self_sent_count: 0,
96
+ cached_plaintext_count: 0,
97
+ warnings: [],
98
+ }));
99
+ }
100
+ }
101
+ exports.PrivateMessagingRuntimeRepo = PrivateMessagingRuntimeRepo;
@@ -55,6 +55,18 @@ export type PrivateMessageDecryptedContent = {
55
55
  body: string;
56
56
  };
57
57
 
58
+ export type PrivateMessagingRuntimeState = {
59
+ schema_version: number;
60
+ app_version: string;
61
+ last_started_at: number;
62
+ encryption_public_key: string;
63
+ encryption_public_key_fingerprint: string;
64
+ message_count: number;
65
+ self_sent_count: number;
66
+ cached_plaintext_count: number;
67
+ warnings: string[];
68
+ };
69
+
58
70
  export class IdentityRepo extends JsonFileRepo<AgentIdentity | null> {
59
71
  constructor(rootDir = process.cwd()) {
60
72
  super(resolve(rootDir, "data", "identity.json"), () => null);
@@ -134,3 +146,19 @@ export class PrivateEncryptionKeyRepo extends JsonFileRepo<PrivateEncryptionKeyP
134
146
  super(resolve(rootDir, "data", "private-encryption-keypair.json"), () => null);
135
147
  }
136
148
  }
149
+
150
+ export class PrivateMessagingRuntimeRepo extends JsonFileRepo<PrivateMessagingRuntimeState> {
151
+ constructor(rootDir = process.cwd()) {
152
+ super(resolve(rootDir, ".silicaclaw", "private-messaging.runtime.json"), () => ({
153
+ schema_version: 1,
154
+ app_version: "",
155
+ last_started_at: 0,
156
+ encryption_public_key: "",
157
+ encryption_public_key_fingerprint: "",
158
+ message_count: 0,
159
+ self_sent_count: 0,
160
+ cached_plaintext_count: 0,
161
+ warnings: [],
162
+ }));
163
+ }
164
+ }