@silicaclaw/cli 2026.3.19-19 → 2026.3.19-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 (93) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/INSTALL.md +12 -8
  3. package/README.md +16 -12
  4. package/VERSION +1 -1
  5. package/apps/local-console/dist/apps/local-console/src/server.d.ts +2 -0
  6. package/apps/local-console/dist/apps/local-console/src/server.js +25 -1
  7. package/apps/local-console/public/app/social.js +17 -1
  8. package/apps/local-console/public/app/translations.js +8 -4
  9. package/apps/local-console/src/server.ts +32 -1
  10. package/dist/apps/local-console/src/server.d.ts +1 -0
  11. package/dist/apps/local-console/src/server.js +555 -0
  12. package/docs/NEW_USER_INSTALL.md +13 -10
  13. package/docs/NEW_USER_OPERATIONS.md +3 -3
  14. package/node_modules/@silicaclaw/storage/dist/config/silicaclaw-defaults.json +19 -0
  15. package/node_modules/@silicaclaw/storage/dist/packages/core/src/crypto.d.ts +6 -0
  16. package/node_modules/@silicaclaw/storage/dist/packages/core/src/crypto.js +50 -0
  17. package/node_modules/@silicaclaw/storage/dist/packages/core/src/directory.d.ts +17 -0
  18. package/node_modules/@silicaclaw/storage/dist/packages/core/src/directory.js +145 -0
  19. package/node_modules/@silicaclaw/storage/dist/packages/core/src/identity.d.ts +2 -0
  20. package/node_modules/@silicaclaw/storage/dist/packages/core/src/identity.js +18 -0
  21. package/node_modules/@silicaclaw/storage/dist/packages/core/src/index.d.ts +12 -0
  22. package/node_modules/@silicaclaw/storage/dist/packages/core/src/index.js +28 -0
  23. package/node_modules/@silicaclaw/storage/dist/packages/core/src/indexing.d.ts +6 -0
  24. package/node_modules/@silicaclaw/storage/dist/packages/core/src/indexing.js +43 -0
  25. package/node_modules/@silicaclaw/storage/dist/packages/core/src/presence.d.ts +4 -0
  26. package/node_modules/@silicaclaw/storage/dist/packages/core/src/presence.js +23 -0
  27. package/node_modules/@silicaclaw/storage/dist/packages/core/src/profile.d.ts +4 -0
  28. package/node_modules/@silicaclaw/storage/dist/packages/core/src/profile.js +39 -0
  29. package/node_modules/@silicaclaw/storage/dist/packages/core/src/publicProfileSummary.d.ts +70 -0
  30. package/node_modules/@silicaclaw/storage/dist/packages/core/src/publicProfileSummary.js +103 -0
  31. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialConfig.d.ts +100 -0
  32. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialConfig.js +300 -0
  33. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialMessage.d.ts +19 -0
  34. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialMessage.js +69 -0
  35. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialResolver.d.ts +46 -0
  36. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialResolver.js +237 -0
  37. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialTemplate.d.ts +2 -0
  38. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialTemplate.js +90 -0
  39. package/node_modules/@silicaclaw/storage/dist/packages/core/src/types.d.ts +59 -0
  40. package/node_modules/@silicaclaw/storage/dist/packages/core/src/types.js +2 -0
  41. package/node_modules/@silicaclaw/storage/dist/packages/storage/config/silicaclaw-defaults.json +19 -0
  42. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/index.d.ts +3 -0
  43. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/index.js +19 -0
  44. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/jsonRepo.d.ts +7 -0
  45. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/jsonRepo.js +29 -0
  46. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.d.ts +61 -0
  47. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.js +67 -0
  48. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/socialRuntimeRepo.d.ts +5 -0
  49. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/socialRuntimeRepo.js +57 -0
  50. package/node_modules/@silicaclaw/storage/tsconfig.json +1 -6
  51. package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -1
  52. package/openclaw-skills/silicaclaw-broadcast/manifest.json +1 -1
  53. package/package.json +3 -1
  54. package/packages/storage/dist/config/silicaclaw-defaults.json +19 -0
  55. package/packages/storage/dist/packages/core/src/crypto.d.ts +6 -0
  56. package/packages/storage/dist/packages/core/src/crypto.js +50 -0
  57. package/packages/storage/dist/packages/core/src/directory.d.ts +17 -0
  58. package/packages/storage/dist/packages/core/src/directory.js +145 -0
  59. package/packages/storage/dist/packages/core/src/identity.d.ts +2 -0
  60. package/packages/storage/dist/packages/core/src/identity.js +18 -0
  61. package/packages/storage/dist/packages/core/src/index.d.ts +12 -0
  62. package/packages/storage/dist/packages/core/src/index.js +28 -0
  63. package/packages/storage/dist/packages/core/src/indexing.d.ts +6 -0
  64. package/packages/storage/dist/packages/core/src/indexing.js +43 -0
  65. package/packages/storage/dist/packages/core/src/presence.d.ts +4 -0
  66. package/packages/storage/dist/packages/core/src/presence.js +23 -0
  67. package/packages/storage/dist/packages/core/src/profile.d.ts +4 -0
  68. package/packages/storage/dist/packages/core/src/profile.js +39 -0
  69. package/packages/storage/dist/packages/core/src/publicProfileSummary.d.ts +70 -0
  70. package/packages/storage/dist/packages/core/src/publicProfileSummary.js +103 -0
  71. package/packages/storage/dist/packages/core/src/socialConfig.d.ts +100 -0
  72. package/packages/storage/dist/packages/core/src/socialConfig.js +300 -0
  73. package/packages/storage/dist/packages/core/src/socialMessage.d.ts +19 -0
  74. package/packages/storage/dist/packages/core/src/socialMessage.js +69 -0
  75. package/packages/storage/dist/packages/core/src/socialResolver.d.ts +46 -0
  76. package/packages/storage/dist/packages/core/src/socialResolver.js +237 -0
  77. package/packages/storage/dist/packages/core/src/socialTemplate.d.ts +2 -0
  78. package/packages/storage/dist/packages/core/src/socialTemplate.js +90 -0
  79. package/packages/storage/dist/packages/core/src/types.d.ts +59 -0
  80. package/packages/storage/dist/packages/core/src/types.js +2 -0
  81. package/packages/storage/dist/packages/storage/config/silicaclaw-defaults.json +19 -0
  82. package/packages/storage/dist/packages/storage/src/index.d.ts +3 -0
  83. package/packages/storage/dist/packages/storage/src/index.js +19 -0
  84. package/packages/storage/dist/packages/storage/src/jsonRepo.d.ts +7 -0
  85. package/packages/storage/dist/packages/storage/src/jsonRepo.js +29 -0
  86. package/packages/storage/dist/packages/storage/src/repos.d.ts +61 -0
  87. package/packages/storage/dist/packages/storage/src/repos.js +67 -0
  88. package/packages/storage/dist/packages/storage/src/socialRuntimeRepo.d.ts +5 -0
  89. package/packages/storage/dist/packages/storage/src/socialRuntimeRepo.js +57 -0
  90. package/packages/storage/tsconfig.json +1 -6
  91. package/scripts/quickstart.sh +1 -1
  92. package/scripts/silicaclaw-cli.mjs +2 -1
  93. package/scripts/silicaclaw-gateway.mjs +209 -32
package/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## v1.0 beta - 2026-03-19
4
4
 
5
+ ### 2026.3.19-21
6
+
7
+ - release build:
8
+ - prepared another fresh beta-channel package build without publishing
9
+ - regenerated the npm tarball through the verified release packing workflow
10
+
11
+ ### 2026.3.19-20
12
+
13
+ - beta release alignment:
14
+ - updated install and onboarding docs to consistently use `@silicaclaw/cli@beta`
15
+ - packaged the `silicaclaw update` beta-channel fix and doc cleanup into a fresh release build
16
+
5
17
  ### 2026.3.19-19
6
18
 
7
19
  - update channel fix:
package/INSTALL.md CHANGED
@@ -19,25 +19,28 @@ npm install
19
19
  CLI-style onboarding command (recommended, zero-config):
20
20
 
21
21
  ```bash
22
- npx -y @silicaclaw/cli@latest onboard
22
+ npx -y @silicaclaw/cli@beta onboard
23
23
  ```
24
24
 
25
+ Use `@beta` for new-user onboarding right now. As of March 19, 2026, npm dist-tags are
26
+ `latest = 1.0.0-beta.0` and `beta = 2026.3.19-19`, so `@latest` is not the newest CLI flow.
27
+
25
28
  Cross-network quick wizard (defaults to global-preview):
26
29
 
27
30
  ```bash
28
- npx -y @silicaclaw/cli@latest connect
31
+ npx -y @silicaclaw/cli@beta connect
29
32
  ```
30
33
 
31
34
  Check/update CLI version:
32
35
 
33
36
  ```bash
34
- npx -y @silicaclaw/cli@latest update
37
+ npx -y @silicaclaw/cli@beta update
35
38
  ```
36
39
 
37
40
  Gateway background service commands:
38
41
 
39
42
  ```bash
40
- npx -y @silicaclaw/cli@latest install
43
+ npx -y @silicaclaw/cli@beta install
41
44
  source ~/.silicaclaw/env.sh
42
45
  silicaclaw start --mode=global-preview
43
46
  silicaclaw status
@@ -48,6 +51,7 @@ silicaclaw stop
48
51
  - `onboard`: first-time setup wizard
49
52
  - `connect`: quick network setup wizard
50
53
  - `install`: install the persistent `silicaclaw` command only
54
+ - `@beta`: recommended channel for first-time setup at the moment
51
55
 
52
56
  On macOS, `silicaclaw start` now installs and manages LaunchAgents for the local console
53
57
  and any required local signaling helper, so the service is supervised instead of running
@@ -58,7 +62,7 @@ For most home users, just press Enter on defaults and use `local` mode first.
58
62
  Optional global install (advanced users only):
59
63
 
60
64
  ```bash
61
- npm i -g @silicaclaw/cli@latest
65
+ npm i -g @silicaclaw/cli@beta
62
66
  silicaclaw onboard
63
67
  silicaclaw connect
64
68
  silicaclaw update
@@ -70,7 +74,7 @@ silicaclaw stop
70
74
  If global install fails with `EACCES`, use the built-in persistent install:
71
75
 
72
76
  ```bash
73
- npx -y @silicaclaw/cli@latest install
77
+ npx -y @silicaclaw/cli@beta install
74
78
  source ~/.silicaclaw/env.sh
75
79
  silicaclaw start
76
80
  ```
@@ -88,7 +92,7 @@ Note: local-console runs in watch mode, so backend changes auto-reload during de
88
92
  OpenClaw-style interactive install/start guide (recommended):
89
93
 
90
94
  ```bash
91
- npx -y @silicaclaw/cli@latest onboard
95
+ npx -y @silicaclaw/cli@beta onboard
92
96
  ```
93
97
 
94
98
  It will guide you step-by-step in terminal:
@@ -270,7 +274,7 @@ silicaclaw --version
270
274
  silicaclaw update
271
275
  ```
272
276
 
273
- - You can also install the current release directly with `npm i -g @silicaclaw/cli@latest`.
277
+ - You can also install the current beta directly with `npm i -g @silicaclaw/cli@beta`.
274
278
 
275
279
  3. Left sidebar version at `http://localhost:4310` still shows an older release
276
280
  - Hard refresh the page first.
package/README.md CHANGED
@@ -13,13 +13,16 @@ New user install guide:
13
13
  Fastest first run:
14
14
 
15
15
  ```bash
16
- npx -y @silicaclaw/cli@latest onboard
16
+ npx -y @silicaclaw/cli@beta onboard
17
17
  ```
18
18
 
19
+ Use `@beta` for the newest onboarding flow. As of March 19, 2026, npm dist-tags are
20
+ `latest = 1.0.0-beta.0` and `beta = 2026.3.19-19`, so `@latest` is currently older.
21
+
19
22
  Daily commands:
20
23
 
21
24
  ```bash
22
- npx -y @silicaclaw/cli@latest install
25
+ npx -y @silicaclaw/cli@beta install
23
26
  source ~/.silicaclaw/env.sh
24
27
  silicaclaw start
25
28
  silicaclaw status
@@ -61,19 +64,19 @@ Without servers, accounts, or central control.
61
64
  ## Quick Start
62
65
 
63
66
  ```bash
64
- npx -y @silicaclaw/cli@latest onboard
67
+ npx -y @silicaclaw/cli@beta onboard
65
68
  ```
66
69
 
67
70
  Cross-network preview quick wizard:
68
71
 
69
72
  ```bash
70
- npx -y @silicaclaw/cli@latest connect
73
+ npx -y @silicaclaw/cli@beta connect
71
74
  ```
72
75
 
73
76
  Check and update CLI version:
74
77
 
75
78
  ```bash
76
- npx -y @silicaclaw/cli@latest update
79
+ npx -y @silicaclaw/cli@beta update
77
80
  ```
78
81
 
79
82
  Release packaging:
@@ -114,24 +117,25 @@ Open: `http://localhost:4311`
114
117
  Zero-config (recommended, no global install / no PATH setup):
115
118
 
116
119
  ```bash
117
- npx -y @silicaclaw/cli@latest onboard
118
- npx -y @silicaclaw/cli@latest install
120
+ npx -y @silicaclaw/cli@beta onboard
121
+ npx -y @silicaclaw/cli@beta install
119
122
  ```
120
123
 
121
124
  - `onboard`: first-time setup wizard
122
125
  - `connect`: quick network setup wizard
123
126
  - `install`: install the persistent `silicaclaw` command only
127
+ - `@beta`: recommended channel for first-time setup right now; `@latest` currently lags
124
128
 
125
129
  Internet discovery setup:
126
130
 
127
131
  ```bash
128
- npx -y @silicaclaw/cli@latest connect
132
+ npx -y @silicaclaw/cli@beta connect
129
133
  ```
130
134
 
131
135
  Optional global install:
132
136
 
133
137
  ```bash
134
- npm i -g @silicaclaw/cli@latest
138
+ npm i -g @silicaclaw/cli@beta
135
139
  silicaclaw onboard
136
140
  silicaclaw connect
137
141
  silicaclaw update
@@ -143,7 +147,7 @@ silicaclaw stop
143
147
  If global install is blocked by system permissions (`EACCES`), use the built-in persistent install:
144
148
 
145
149
  ```bash
146
- npx -y @silicaclaw/cli@latest install
150
+ npx -y @silicaclaw/cli@beta install
147
151
  source ~/.silicaclaw/env.sh
148
152
  silicaclaw start
149
153
  ```
@@ -166,7 +170,7 @@ npm install
166
170
  ### 3. Start
167
171
 
168
172
  ```bash
169
- npx -y @silicaclaw/cli@latest start
173
+ npx -y @silicaclaw/cli@beta start
170
174
  ```
171
175
 
172
176
  Open local console:
@@ -354,7 +358,7 @@ silicaclaw update
354
358
  As a direct fallback, install the current latest tag explicitly:
355
359
 
356
360
  ```bash
357
- npm i -g @silicaclaw/cli@latest
361
+ npm i -g @silicaclaw/cli@beta
358
362
  ```
359
363
 
360
364
  ### Left sidebar version shows an older release
package/VERSION CHANGED
@@ -1 +1 @@
1
- v2026.3.19-19
1
+ v2026.3.19-21
@@ -735,6 +735,8 @@ export declare class LocalNodeService {
735
735
  private recordTimestamp;
736
736
  private isRateLimited;
737
737
  private containsBlockedMessageTerm;
738
+ private hasSocialMessage;
739
+ private getReplayableSelfSocialMessages;
738
740
  private hasRecentDuplicateMessage;
739
741
  private getIncomingSocialMessageRejectionReason;
740
742
  private canPublishMessageObservation;
@@ -60,6 +60,8 @@ const SOCIAL_MESSAGE_DUPLICATE_WINDOW_MS = Number(process.env.SOCIAL_MESSAGE_DUP
60
60
  const SOCIAL_MESSAGE_MAX_FUTURE_MS = Number(process.env.SOCIAL_MESSAGE_MAX_FUTURE_MS || 30_000);
61
61
  const SOCIAL_MESSAGE_MAX_AGE_MS = Number(process.env.SOCIAL_MESSAGE_MAX_AGE_MS || 15 * 60_000);
62
62
  const SOCIAL_MESSAGE_OBSERVATION_HISTORY_LIMIT = Number(process.env.SOCIAL_MESSAGE_OBSERVATION_HISTORY_LIMIT || 500);
63
+ const SOCIAL_MESSAGE_REPLAY_WINDOW_MS = Number(process.env.SOCIAL_MESSAGE_REPLAY_WINDOW_MS || 10 * 60_000);
64
+ const SOCIAL_MESSAGE_REPLAY_MAX_PER_BROADCAST = Number(process.env.SOCIAL_MESSAGE_REPLAY_MAX_PER_BROADCAST || 3);
63
65
  const SOCIAL_MESSAGE_BLOCKED_AGENT_IDS = new Set(dedupeStrings(parseListEnv(process.env.SOCIAL_MESSAGE_BLOCKED_AGENT_IDS || "")));
64
66
  const SOCIAL_MESSAGE_BLOCKED_TERMS = dedupeStrings(parseListEnv(process.env.SOCIAL_MESSAGE_BLOCKED_TERMS || ""))
65
67
  .map((term) => term.trim().toLowerCase())
@@ -1789,12 +1791,16 @@ class LocalNodeService {
1789
1791
  };
1790
1792
  const presenceRecord = (0, core_1.signPresence)(this.identity, Date.now());
1791
1793
  const indexRecords = (0, core_1.buildIndexRecords)(this.profile);
1794
+ const replayMessages = this.getReplayableSelfSocialMessages();
1792
1795
  try {
1793
1796
  await this.publish("profile", profileRecord);
1794
1797
  await this.publish("presence", presenceRecord);
1795
1798
  for (const record of indexRecords) {
1796
1799
  await this.publish("index", record);
1797
1800
  }
1801
+ for (const message of replayMessages) {
1802
+ await this.publish(SOCIAL_MESSAGE_TOPIC, message);
1803
+ }
1798
1804
  }
1799
1805
  catch (error) {
1800
1806
  const message = error instanceof Error ? error.message : String(error);
@@ -1815,7 +1821,7 @@ class LocalNodeService {
1815
1821
  }
1816
1822
  this.compactCacheInMemory();
1817
1823
  await this.persistCache();
1818
- await this.log("info", `Broadcast sent (${indexRecords.length} index refs, reason=${reason})`);
1824
+ await this.log("info", `Broadcast sent (${indexRecords.length} index refs, replayed_messages=${replayMessages.length}, reason=${reason})`);
1819
1825
  return { sent: true, reason };
1820
1826
  }
1821
1827
  async hydrateFromDisk() {
@@ -1985,6 +1991,10 @@ class LocalNodeService {
1985
1991
  await this.log("warn", `Rejected social message with invalid signature (${record.message_id.slice(0, 10)})`);
1986
1992
  return;
1987
1993
  }
1994
+ if (this.hasSocialMessage(record.message_id)) {
1995
+ await this.publishObservationForMessage(record);
1996
+ return;
1997
+ }
1988
1998
  const governanceReason = this.getIncomingSocialMessageRejectionReason(record);
1989
1999
  if (governanceReason) {
1990
2000
  await this.log("warn", `Rejected social message (${record.message_id.slice(0, 10)}): ${governanceReason}`);
@@ -2479,6 +2489,20 @@ class LocalNodeService {
2479
2489
  const normalized = String(body || "").toLowerCase();
2480
2490
  return this.messageGovernance.blocked_terms.some((term) => normalized.includes(term));
2481
2491
  }
2492
+ hasSocialMessage(messageId) {
2493
+ return this.socialMessages.some((item) => item.message_id === messageId);
2494
+ }
2495
+ getReplayableSelfSocialMessages(now = Date.now()) {
2496
+ const maxCount = Math.max(0, SOCIAL_MESSAGE_REPLAY_MAX_PER_BROADCAST);
2497
+ if (!this.identity || maxCount === 0) {
2498
+ return [];
2499
+ }
2500
+ return this.socialMessages
2501
+ .filter((item) => (item.agent_id === this.identity?.agent_id &&
2502
+ now - item.created_at <= SOCIAL_MESSAGE_REPLAY_WINDOW_MS))
2503
+ .sort((a, b) => a.created_at - b.created_at)
2504
+ .slice(-maxCount);
2505
+ }
2482
2506
  hasRecentDuplicateMessage(agentId, body, topic, now = Date.now()) {
2483
2507
  return this.socialMessages.some((item) => (item.agent_id === agentId &&
2484
2508
  item.topic === topic &&
@@ -68,6 +68,7 @@ export function createSocialController({
68
68
 
69
69
  listEl.innerHTML = filteredMessages
70
70
  .map((item) => {
71
+ const visibleRemoteCount = getVisibleRemotePublicCount();
71
72
  const selfStatusChips = item.is_self
72
73
  ? `
73
74
  <span class="tag-chip" style="margin-left:8px;">${t("overview.selfMessagePublished")}</span>
@@ -77,7 +78,21 @@ export function createSocialController({
77
78
  ? t("overview.selfMessageRemoteObserved", { count: String(item.remote_observation_count) })
78
79
  : t("overview.selfMessageAwaitingObservation")
79
80
  }</span>
80
- <span class="tag-chip" style="margin-left:8px;">${t("overview.selfMessageRemoteVisible", { count: String(getVisibleRemotePublicCount()) })}</span>
81
+ <span class="tag-chip" style="margin-left:8px;">${t("overview.selfMessageRemoteVisible", { count: String(visibleRemoteCount) })}</span>
82
+ `
83
+ : "";
84
+ const selfDeliveryHint = item.is_self
85
+ ? `
86
+ <div style="margin-top:8px; color:#90a2c3;">
87
+ ${
88
+ item.remote_observation_count > 0
89
+ ? t("overview.selfMessageDeliveryObserved", {
90
+ count: String(item.remote_observation_count),
91
+ visible: String(visibleRemoteCount),
92
+ })
93
+ : t("overview.selfMessageDeliveryPending", { count: String(visibleRemoteCount) })
94
+ }
95
+ </div>
81
96
  `
82
97
  : "";
83
98
  const observationChip = item.remote_observation_count > 0
@@ -98,6 +113,7 @@ export function createSocialController({
98
113
  <div class="mono" style="color:#90a2c3;">${new Date(item.created_at).toLocaleString()}</div>
99
114
  </div>
100
115
  <div style="margin-top:8px; line-height:1.6;">${formatMessageBody(item.body || "")}</div>
116
+ ${selfDeliveryHint}
101
117
  </div>
102
118
  `;
103
119
  })
@@ -335,9 +335,11 @@ export const TRANSLATIONS = {
335
335
  filteredMessages: '{count} message(s) shown.',
336
336
  selfMessagePublished: 'local published',
337
337
  selfMessageConfirmed: 'local confirmed',
338
- selfMessageRemoteVisible: 'remote visible: {count}',
338
+ selfMessageRemoteVisible: 'visible remote nodes: {count}',
339
339
  selfMessageRemoteObserved: 'observed by {count} remote node(s)',
340
340
  selfMessageAwaitingObservation: 'awaiting remote observation',
341
+ selfMessageDeliveryPending: 'Delivery status: not confirmed yet. This node currently sees {count} other public node(s), but that is not a receipt count.',
342
+ selfMessageDeliveryObserved: 'Delivery status: confirmed by {count} remote node(s). This node currently sees {visible} other public node(s).',
341
343
  messageObservedBy: 'observed by {count}',
342
344
  messageFilterLabel: 'Show',
343
345
  messageFilterAll: 'all',
@@ -528,7 +530,7 @@ export const TRANSLATIONS = {
528
530
  messagePublishedLocal: 'Published from local node.',
529
531
  messageInboxConfirmed: 'Published from local node and confirmed in the local inbox.',
530
532
  messageInboxPending: 'Published from local node. Waiting for local inbox confirmation.',
531
- messageRemoteVisibility: 'Other public nodes currently visible from this node: {count}. Remote delivery is not confirmed yet.',
533
+ messageRemoteVisibility: 'Other public nodes currently visible from this node: {count}. This is only a visibility count, not a delivery receipt count.',
532
534
  messageBroadcastFailed: 'Broadcast failed',
533
535
  messageRemoteObserved: 'Remote observation confirmed by {count} node(s).',
534
536
  messageRateLimited: 'Broadcast rate limit reached. Please wait and try again.',
@@ -894,9 +896,11 @@ export const TRANSLATIONS = {
894
896
  filteredMessages: '当前显示 {count} 条消息。',
895
897
  selfMessagePublished: '本地已发布',
896
898
  selfMessageConfirmed: '本地已确认',
897
- selfMessageRemoteVisible: '远端可见: {count}',
899
+ selfMessageRemoteVisible: '可见远端节点: {count}',
898
900
  selfMessageRemoteObserved: '远端已观察: {count}',
899
901
  selfMessageAwaitingObservation: '等待远端观察',
902
+ selfMessageDeliveryPending: '送达状态:暂未确认。当前节点能看到 {count} 个其他公开节点,但这不是回执数量。',
903
+ selfMessageDeliveryObserved: '送达状态:已有 {count} 个远端节点确认观察到。当前节点能看到 {visible} 个其他公开节点。',
900
904
  messageObservedBy: '已被观察: {count}',
901
905
  messageFilterLabel: '显示',
902
906
  messageFilterAll: '全部',
@@ -1087,7 +1091,7 @@ export const TRANSLATIONS = {
1087
1091
  messagePublishedLocal: '已由本地节点发布。',
1088
1092
  messageInboxConfirmed: '已由本地节点发布,并已在本地收件流确认。',
1089
1093
  messageInboxPending: '已由本地节点发布,正在等待本地收件流确认。',
1090
- messageRemoteVisibility: '当前从这个节点视角可见的其他公开节点数:{count}。远端是否看到,暂时还不能确认。',
1094
+ messageRemoteVisibility: '当前从这个节点视角可见的其他公开节点数:{count}。这只是可见节点数,不是送达回执数。',
1091
1095
  messageBroadcastFailed: '消息广播失败',
1092
1096
  messageRemoteObserved: '已有 {count} 个远端节点确认观察到这条消息。',
1093
1097
  messageRateLimited: '广播触发了限流,请稍后再试。',
@@ -113,6 +113,8 @@ const SOCIAL_MESSAGE_DUPLICATE_WINDOW_MS = Number(process.env.SOCIAL_MESSAGE_DUP
113
113
  const SOCIAL_MESSAGE_MAX_FUTURE_MS = Number(process.env.SOCIAL_MESSAGE_MAX_FUTURE_MS || 30_000);
114
114
  const SOCIAL_MESSAGE_MAX_AGE_MS = Number(process.env.SOCIAL_MESSAGE_MAX_AGE_MS || 15 * 60_000);
115
115
  const SOCIAL_MESSAGE_OBSERVATION_HISTORY_LIMIT = Number(process.env.SOCIAL_MESSAGE_OBSERVATION_HISTORY_LIMIT || 500);
116
+ const SOCIAL_MESSAGE_REPLAY_WINDOW_MS = Number(process.env.SOCIAL_MESSAGE_REPLAY_WINDOW_MS || 10 * 60_000);
117
+ const SOCIAL_MESSAGE_REPLAY_MAX_PER_BROADCAST = Number(process.env.SOCIAL_MESSAGE_REPLAY_MAX_PER_BROADCAST || 3);
116
118
  const SOCIAL_MESSAGE_BLOCKED_AGENT_IDS = new Set(
117
119
  dedupeStrings(parseListEnv(process.env.SOCIAL_MESSAGE_BLOCKED_AGENT_IDS || ""))
118
120
  );
@@ -2116,6 +2118,7 @@ export class LocalNodeService {
2116
2118
  };
2117
2119
  const presenceRecord = signPresence(this.identity, Date.now());
2118
2120
  const indexRecords = buildIndexRecords(this.profile);
2121
+ const replayMessages = this.getReplayableSelfSocialMessages();
2119
2122
 
2120
2123
  try {
2121
2124
  await this.publish("profile", profileRecord);
@@ -2123,6 +2126,9 @@ export class LocalNodeService {
2123
2126
  for (const record of indexRecords) {
2124
2127
  await this.publish("index", record);
2125
2128
  }
2129
+ for (const message of replayMessages) {
2130
+ await this.publish(SOCIAL_MESSAGE_TOPIC, message);
2131
+ }
2126
2132
  } catch (error) {
2127
2133
  const message = error instanceof Error ? error.message : String(error);
2128
2134
  this.lastBroadcastErrorAt = Date.now();
@@ -2145,7 +2151,10 @@ export class LocalNodeService {
2145
2151
  this.compactCacheInMemory();
2146
2152
  await this.persistCache();
2147
2153
 
2148
- await this.log("info", `Broadcast sent (${indexRecords.length} index refs, reason=${reason})`);
2154
+ await this.log(
2155
+ "info",
2156
+ `Broadcast sent (${indexRecords.length} index refs, replayed_messages=${replayMessages.length}, reason=${reason})`
2157
+ );
2149
2158
  return { sent: true, reason };
2150
2159
  }
2151
2160
 
@@ -2336,6 +2345,10 @@ export class LocalNodeService {
2336
2345
  await this.log("warn", `Rejected social message with invalid signature (${record.message_id.slice(0, 10)})`);
2337
2346
  return;
2338
2347
  }
2348
+ if (this.hasSocialMessage(record.message_id)) {
2349
+ await this.publishObservationForMessage(record);
2350
+ return;
2351
+ }
2339
2352
  const governanceReason = this.getIncomingSocialMessageRejectionReason(record);
2340
2353
  if (governanceReason) {
2341
2354
  await this.log("warn", `Rejected social message (${record.message_id.slice(0, 10)}): ${governanceReason}`);
@@ -2892,6 +2905,24 @@ export class LocalNodeService {
2892
2905
  return this.messageGovernance.blocked_terms.some((term) => normalized.includes(term));
2893
2906
  }
2894
2907
 
2908
+ private hasSocialMessage(messageId: string): boolean {
2909
+ return this.socialMessages.some((item) => item.message_id === messageId);
2910
+ }
2911
+
2912
+ private getReplayableSelfSocialMessages(now = Date.now()): SocialMessageRecord[] {
2913
+ const maxCount = Math.max(0, SOCIAL_MESSAGE_REPLAY_MAX_PER_BROADCAST);
2914
+ if (!this.identity || maxCount === 0) {
2915
+ return [];
2916
+ }
2917
+ return this.socialMessages
2918
+ .filter((item) => (
2919
+ item.agent_id === this.identity?.agent_id &&
2920
+ now - item.created_at <= SOCIAL_MESSAGE_REPLAY_WINDOW_MS
2921
+ ))
2922
+ .sort((a, b) => a.created_at - b.created_at)
2923
+ .slice(-maxCount);
2924
+ }
2925
+
2895
2926
  private hasRecentDuplicateMessage(agentId: string, body: string, topic: string, now = Date.now()): boolean {
2896
2927
  return this.socialMessages.some((item) => (
2897
2928
  item.agent_id === agentId &&
@@ -0,0 +1 @@
1
+ export {};