@silicaclaw/cli 2026.3.19-9 → 2026.3.20-10

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 (331) hide show
  1. package/CHANGELOG.md +182 -0
  2. package/DEMO_GUIDE.md +1 -1
  3. package/INSTALL.md +53 -13
  4. package/README.md +106 -23
  5. package/VERSION +1 -1
  6. package/apps/local-console/dist/apps/local-console/src/server.d.ts +180 -14
  7. package/apps/local-console/dist/apps/local-console/src/server.js +1499 -267
  8. package/apps/local-console/dist/config/silicaclaw-defaults.json +19 -0
  9. package/apps/local-console/dist/packages/core/src/index.d.ts +2 -0
  10. package/apps/local-console/dist/packages/core/src/index.js +2 -0
  11. package/apps/local-console/dist/packages/core/src/privateCrypto.d.ts +17 -0
  12. package/apps/local-console/dist/packages/core/src/privateCrypto.js +40 -0
  13. package/apps/local-console/dist/packages/core/src/privateMessage.d.ts +23 -0
  14. package/apps/local-console/dist/packages/core/src/privateMessage.js +74 -0
  15. package/apps/local-console/dist/packages/core/src/profile.js +2 -0
  16. package/apps/local-console/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
  17. package/apps/local-console/dist/packages/core/src/publicProfileSummary.js +3 -0
  18. package/apps/local-console/dist/packages/core/src/socialConfig.js +9 -5
  19. package/apps/local-console/dist/packages/core/src/types.d.ts +40 -0
  20. package/apps/local-console/dist/packages/network/src/realPreview.js +6 -2
  21. package/apps/local-console/dist/packages/network/src/relayPreview.d.ts +12 -0
  22. package/apps/local-console/dist/packages/network/src/relayPreview.js +116 -10
  23. package/apps/local-console/dist/packages/network/src/transport/udpLanBroadcastTransport.js +2 -1
  24. package/apps/local-console/dist/packages/network/src/types.d.ts +4 -0
  25. package/apps/local-console/dist/packages/network/src/webrtcPreview.js +5 -1
  26. package/apps/local-console/dist/packages/storage/config/silicaclaw-defaults.json +19 -0
  27. package/apps/local-console/dist/packages/storage/src/repos.d.ts +13 -1
  28. package/apps/local-console/dist/packages/storage/src/repos.js +19 -1
  29. package/apps/local-console/dist/packages/storage/src/socialRuntimeRepo.js +8 -4
  30. package/apps/local-console/public/app/app.js +486 -12
  31. package/apps/local-console/public/app/events.js +61 -2
  32. package/apps/local-console/public/app/network.js +176 -35
  33. package/apps/local-console/public/app/overview.js +75 -53
  34. package/apps/local-console/public/app/shell.js +18 -34
  35. package/apps/local-console/public/app/social.js +495 -93
  36. package/apps/local-console/public/app/styles.css +309 -15
  37. package/apps/local-console/public/app/template.js +182 -51
  38. package/apps/local-console/public/app/translations.js +476 -266
  39. package/apps/local-console/src/server.ts +1669 -271
  40. package/apps/public-explorer/dist/apps/public-explorer/src/server.d.ts +1 -0
  41. package/apps/public-explorer/dist/apps/public-explorer/src/server.js +41 -0
  42. package/apps/public-explorer/dist/config/silicaclaw-defaults.json +19 -0
  43. package/apps/public-explorer/public/app/app.js +22 -2
  44. package/apps/public-explorer/public/app/template.js +4 -4
  45. package/apps/public-explorer/public/app/translations.js +29 -29
  46. package/apps/public-explorer/src/server.ts +11 -1
  47. package/config/silicaclaw-defaults.json +19 -0
  48. package/dist/apps/local-console/src/server.d.ts +1 -0
  49. package/dist/apps/local-console/src/server.js +555 -0
  50. package/docs/NEW_USER_INSTALL.md +14 -10
  51. package/docs/NEW_USER_OPERATIONS.md +9 -9
  52. package/docs/OPENCLAW_BRIDGE.md +22 -7
  53. package/docs/OPENCLAW_BRIDGE_ZH.md +21 -6
  54. package/docs/RELEASE_CHECKLIST.md +95 -0
  55. package/node_modules/@silicaclaw/core/dist/config/silicaclaw-defaults.json +19 -0
  56. package/node_modules/@silicaclaw/core/dist/packages/core/src/crypto.d.ts +6 -0
  57. package/node_modules/@silicaclaw/core/dist/packages/core/src/crypto.js +50 -0
  58. package/node_modules/@silicaclaw/core/dist/packages/core/src/directory.d.ts +17 -0
  59. package/node_modules/@silicaclaw/core/dist/packages/core/src/directory.js +145 -0
  60. package/node_modules/@silicaclaw/core/dist/packages/core/src/identity.d.ts +2 -0
  61. package/node_modules/@silicaclaw/core/dist/packages/core/src/identity.js +18 -0
  62. package/node_modules/@silicaclaw/core/dist/packages/core/src/index.d.ts +14 -0
  63. package/node_modules/@silicaclaw/core/dist/packages/core/src/index.js +30 -0
  64. package/node_modules/@silicaclaw/core/dist/packages/core/src/indexing.d.ts +6 -0
  65. package/node_modules/@silicaclaw/core/dist/packages/core/src/indexing.js +43 -0
  66. package/node_modules/@silicaclaw/core/dist/packages/core/src/presence.d.ts +4 -0
  67. package/node_modules/@silicaclaw/core/dist/packages/core/src/presence.js +23 -0
  68. package/node_modules/@silicaclaw/core/dist/packages/core/src/privateCrypto.d.ts +17 -0
  69. package/node_modules/@silicaclaw/core/dist/packages/core/src/privateCrypto.js +40 -0
  70. package/node_modules/@silicaclaw/core/dist/packages/core/src/privateMessage.d.ts +23 -0
  71. package/node_modules/@silicaclaw/core/dist/packages/core/src/privateMessage.js +74 -0
  72. package/node_modules/@silicaclaw/core/dist/packages/core/src/profile.d.ts +4 -0
  73. package/node_modules/@silicaclaw/core/dist/packages/core/src/profile.js +41 -0
  74. package/node_modules/@silicaclaw/core/dist/packages/core/src/publicProfileSummary.d.ts +74 -0
  75. package/node_modules/@silicaclaw/core/dist/packages/core/src/publicProfileSummary.js +106 -0
  76. package/node_modules/@silicaclaw/core/dist/packages/core/src/socialConfig.d.ts +100 -0
  77. package/node_modules/@silicaclaw/core/dist/packages/core/src/socialConfig.js +300 -0
  78. package/node_modules/@silicaclaw/core/dist/packages/core/src/socialMessage.d.ts +19 -0
  79. package/node_modules/@silicaclaw/core/dist/packages/core/src/socialMessage.js +69 -0
  80. package/node_modules/@silicaclaw/core/dist/packages/core/src/socialResolver.d.ts +46 -0
  81. package/node_modules/@silicaclaw/core/dist/packages/core/src/socialResolver.js +237 -0
  82. package/node_modules/@silicaclaw/core/dist/packages/core/src/socialTemplate.d.ts +2 -0
  83. package/node_modules/@silicaclaw/core/dist/packages/core/src/socialTemplate.js +90 -0
  84. package/node_modules/@silicaclaw/core/dist/packages/core/src/types.d.ts +99 -0
  85. package/node_modules/@silicaclaw/core/dist/packages/core/src/types.js +2 -0
  86. package/node_modules/@silicaclaw/core/src/index.ts +2 -0
  87. package/node_modules/@silicaclaw/core/src/privateCrypto.ts +57 -0
  88. package/node_modules/@silicaclaw/core/src/privateMessage.ts +101 -0
  89. package/node_modules/@silicaclaw/core/src/profile.ts +2 -0
  90. package/node_modules/@silicaclaw/core/src/publicProfileSummary.ts +7 -0
  91. package/node_modules/@silicaclaw/core/src/socialConfig.ts +7 -5
  92. package/node_modules/@silicaclaw/core/src/types.ts +44 -0
  93. package/node_modules/@silicaclaw/network/dist/config/silicaclaw-defaults.json +19 -0
  94. package/node_modules/@silicaclaw/network/dist/packages/network/src/abstractions/messageEnvelope.d.ts +28 -0
  95. package/node_modules/@silicaclaw/network/dist/packages/network/src/abstractions/messageEnvelope.js +36 -0
  96. package/node_modules/@silicaclaw/network/dist/packages/network/src/abstractions/peerDiscovery.d.ts +43 -0
  97. package/node_modules/@silicaclaw/network/dist/packages/network/src/abstractions/peerDiscovery.js +2 -0
  98. package/node_modules/@silicaclaw/network/dist/packages/network/src/abstractions/topicCodec.d.ts +4 -0
  99. package/node_modules/@silicaclaw/network/dist/packages/network/src/abstractions/topicCodec.js +2 -0
  100. package/node_modules/@silicaclaw/network/dist/packages/network/src/abstractions/transport.d.ts +36 -0
  101. package/node_modules/@silicaclaw/network/dist/packages/network/src/abstractions/transport.js +2 -0
  102. package/node_modules/@silicaclaw/network/dist/packages/network/src/codec/jsonMessageEnvelopeCodec.d.ts +5 -0
  103. package/node_modules/@silicaclaw/network/dist/packages/network/src/codec/jsonMessageEnvelopeCodec.js +24 -0
  104. package/node_modules/@silicaclaw/network/dist/packages/network/src/codec/jsonTopicCodec.d.ts +5 -0
  105. package/node_modules/@silicaclaw/network/dist/packages/network/src/codec/jsonTopicCodec.js +12 -0
  106. package/node_modules/@silicaclaw/network/dist/packages/network/src/discovery/heartbeatPeerDiscovery.d.ts +28 -0
  107. package/node_modules/@silicaclaw/network/dist/packages/network/src/discovery/heartbeatPeerDiscovery.js +144 -0
  108. package/node_modules/@silicaclaw/network/dist/packages/network/src/index.d.ts +14 -0
  109. package/node_modules/@silicaclaw/network/dist/packages/network/src/index.js +30 -0
  110. package/node_modules/@silicaclaw/network/dist/packages/network/src/localEventBus.d.ts +9 -0
  111. package/node_modules/@silicaclaw/network/dist/packages/network/src/localEventBus.js +47 -0
  112. package/node_modules/@silicaclaw/network/dist/packages/network/src/mock.d.ts +8 -0
  113. package/node_modules/@silicaclaw/network/dist/packages/network/src/mock.js +24 -0
  114. package/node_modules/@silicaclaw/network/dist/packages/network/src/realPreview.d.ts +105 -0
  115. package/node_modules/@silicaclaw/network/dist/packages/network/src/realPreview.js +331 -0
  116. package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.d.ts +178 -0
  117. package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.js +548 -0
  118. package/node_modules/@silicaclaw/network/dist/packages/network/src/transport/udpLanBroadcastTransport.d.ts +23 -0
  119. package/node_modules/@silicaclaw/network/dist/packages/network/src/transport/udpLanBroadcastTransport.js +154 -0
  120. package/node_modules/@silicaclaw/network/dist/packages/network/src/types.d.ts +10 -0
  121. package/node_modules/@silicaclaw/network/dist/packages/network/src/types.js +2 -0
  122. package/node_modules/@silicaclaw/network/dist/packages/network/src/webrtcPreview.d.ts +163 -0
  123. package/node_modules/@silicaclaw/network/dist/packages/network/src/webrtcPreview.js +848 -0
  124. package/node_modules/@silicaclaw/network/src/realPreview.ts +3 -2
  125. package/node_modules/@silicaclaw/network/src/relayPreview.ts +125 -12
  126. package/node_modules/@silicaclaw/network/src/transport/udpLanBroadcastTransport.ts +2 -1
  127. package/node_modules/@silicaclaw/network/src/types.ts +2 -0
  128. package/node_modules/@silicaclaw/network/src/webrtcPreview.ts +2 -1
  129. package/node_modules/@silicaclaw/storage/config/silicaclaw-defaults.json +19 -0
  130. package/node_modules/@silicaclaw/storage/dist/config/silicaclaw-defaults.json +19 -0
  131. package/node_modules/@silicaclaw/storage/dist/packages/core/src/crypto.d.ts +6 -0
  132. package/node_modules/@silicaclaw/storage/dist/packages/core/src/crypto.js +50 -0
  133. package/node_modules/@silicaclaw/storage/dist/packages/core/src/directory.d.ts +17 -0
  134. package/node_modules/@silicaclaw/storage/dist/packages/core/src/directory.js +145 -0
  135. package/node_modules/@silicaclaw/storage/dist/packages/core/src/identity.d.ts +2 -0
  136. package/node_modules/@silicaclaw/storage/dist/packages/core/src/identity.js +18 -0
  137. package/node_modules/@silicaclaw/storage/dist/packages/core/src/index.d.ts +14 -0
  138. package/node_modules/@silicaclaw/storage/dist/packages/core/src/index.js +30 -0
  139. package/node_modules/@silicaclaw/storage/dist/packages/core/src/indexing.d.ts +6 -0
  140. package/node_modules/@silicaclaw/storage/dist/packages/core/src/indexing.js +43 -0
  141. package/node_modules/@silicaclaw/storage/dist/packages/core/src/presence.d.ts +4 -0
  142. package/node_modules/@silicaclaw/storage/dist/packages/core/src/presence.js +23 -0
  143. package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateCrypto.d.ts +17 -0
  144. package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateCrypto.js +40 -0
  145. package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateMessage.d.ts +23 -0
  146. package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateMessage.js +74 -0
  147. package/node_modules/@silicaclaw/storage/dist/packages/core/src/profile.d.ts +4 -0
  148. package/node_modules/@silicaclaw/storage/dist/packages/core/src/profile.js +41 -0
  149. package/node_modules/@silicaclaw/storage/dist/packages/core/src/publicProfileSummary.d.ts +74 -0
  150. package/node_modules/@silicaclaw/storage/dist/packages/core/src/publicProfileSummary.js +106 -0
  151. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialConfig.d.ts +100 -0
  152. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialConfig.js +300 -0
  153. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialMessage.d.ts +19 -0
  154. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialMessage.js +69 -0
  155. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialResolver.d.ts +46 -0
  156. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialResolver.js +237 -0
  157. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialTemplate.d.ts +2 -0
  158. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialTemplate.js +90 -0
  159. package/node_modules/@silicaclaw/storage/dist/packages/core/src/types.d.ts +99 -0
  160. package/node_modules/@silicaclaw/storage/dist/packages/core/src/types.js +2 -0
  161. package/node_modules/@silicaclaw/storage/dist/packages/storage/config/silicaclaw-defaults.json +19 -0
  162. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/index.d.ts +3 -0
  163. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/index.js +19 -0
  164. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/jsonRepo.d.ts +7 -0
  165. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/jsonRepo.js +29 -0
  166. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.d.ts +73 -0
  167. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.js +85 -0
  168. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/socialRuntimeRepo.d.ts +5 -0
  169. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/socialRuntimeRepo.js +57 -0
  170. package/node_modules/@silicaclaw/storage/dist/socialRuntimeRepo.js +8 -4
  171. package/node_modules/@silicaclaw/storage/src/repos.ts +31 -1
  172. package/node_modules/@silicaclaw/storage/src/socialRuntimeRepo.ts +5 -4
  173. package/node_modules/@silicaclaw/storage/tsconfig.json +1 -6
  174. package/openclaw-skills/silicaclaw-bridge-setup/SKILL.md +165 -0
  175. package/openclaw-skills/silicaclaw-bridge-setup/VERSION +1 -0
  176. package/openclaw-skills/silicaclaw-bridge-setup/agents/openai.yaml +6 -0
  177. package/openclaw-skills/silicaclaw-bridge-setup/manifest.json +27 -0
  178. package/openclaw-skills/silicaclaw-bridge-setup/references/owner-dialogue-cheatsheet-zh.md +58 -0
  179. package/openclaw-skills/silicaclaw-bridge-setup/references/runtime-setup.md +43 -0
  180. package/openclaw-skills/silicaclaw-bridge-setup/references/troubleshooting.md +24 -0
  181. package/openclaw-skills/silicaclaw-broadcast/SKILL.md +150 -0
  182. package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -1
  183. package/openclaw-skills/silicaclaw-broadcast/agents/openai.yaml +2 -2
  184. package/openclaw-skills/silicaclaw-broadcast/manifest.json +4 -3
  185. package/openclaw-skills/silicaclaw-broadcast/references/owner-dialogue-cheatsheet-zh.md +81 -0
  186. package/openclaw-skills/silicaclaw-network-config/SKILL.md +158 -0
  187. package/openclaw-skills/silicaclaw-network-config/VERSION +1 -0
  188. package/openclaw-skills/silicaclaw-network-config/agents/openai.yaml +6 -0
  189. package/openclaw-skills/silicaclaw-network-config/manifest.json +27 -0
  190. package/openclaw-skills/silicaclaw-network-config/references/network-modes.md +22 -0
  191. package/openclaw-skills/silicaclaw-network-config/references/owner-dialogue-cheatsheet-zh.md +47 -0
  192. package/openclaw-skills/silicaclaw-network-config/references/public-discovery.md +22 -0
  193. package/openclaw-skills/silicaclaw-owner-push/SKILL.md +235 -0
  194. package/openclaw-skills/silicaclaw-owner-push/VERSION +1 -0
  195. package/openclaw-skills/silicaclaw-owner-push/agents/openai.yaml +6 -0
  196. package/openclaw-skills/silicaclaw-owner-push/manifest.json +30 -0
  197. package/openclaw-skills/silicaclaw-owner-push/references/owner-dialogue-cheatsheet-zh.md +87 -0
  198. package/openclaw-skills/silicaclaw-owner-push/references/push-routing-policy.md +43 -0
  199. package/openclaw-skills/silicaclaw-owner-push/references/runtime-setup.md +44 -0
  200. package/openclaw-skills/silicaclaw-owner-push/scripts/owner-push-forwarder.mjs +356 -0
  201. package/openclaw-skills/silicaclaw-owner-push/scripts/send-to-owner-via-openclaw.mjs +69 -0
  202. package/package.json +5 -1
  203. package/packages/core/dist/config/silicaclaw-defaults.json +19 -0
  204. package/packages/core/dist/packages/core/src/crypto.d.ts +6 -0
  205. package/packages/core/dist/packages/core/src/crypto.js +50 -0
  206. package/packages/core/dist/packages/core/src/directory.d.ts +17 -0
  207. package/packages/core/dist/packages/core/src/directory.js +145 -0
  208. package/packages/core/dist/packages/core/src/identity.d.ts +2 -0
  209. package/packages/core/dist/packages/core/src/identity.js +18 -0
  210. package/packages/core/dist/packages/core/src/index.d.ts +14 -0
  211. package/packages/core/dist/packages/core/src/index.js +30 -0
  212. package/packages/core/dist/packages/core/src/indexing.d.ts +6 -0
  213. package/packages/core/dist/packages/core/src/indexing.js +43 -0
  214. package/packages/core/dist/packages/core/src/presence.d.ts +4 -0
  215. package/packages/core/dist/packages/core/src/presence.js +23 -0
  216. package/packages/core/dist/packages/core/src/privateCrypto.d.ts +17 -0
  217. package/packages/core/dist/packages/core/src/privateCrypto.js +40 -0
  218. package/packages/core/dist/packages/core/src/privateMessage.d.ts +23 -0
  219. package/packages/core/dist/packages/core/src/privateMessage.js +74 -0
  220. package/packages/core/dist/packages/core/src/profile.d.ts +4 -0
  221. package/packages/core/dist/packages/core/src/profile.js +41 -0
  222. package/packages/core/dist/packages/core/src/publicProfileSummary.d.ts +74 -0
  223. package/packages/core/dist/packages/core/src/publicProfileSummary.js +106 -0
  224. package/packages/core/dist/packages/core/src/socialConfig.d.ts +100 -0
  225. package/packages/core/dist/packages/core/src/socialConfig.js +300 -0
  226. package/packages/core/dist/packages/core/src/socialMessage.d.ts +19 -0
  227. package/packages/core/dist/packages/core/src/socialMessage.js +69 -0
  228. package/packages/core/dist/packages/core/src/socialResolver.d.ts +46 -0
  229. package/packages/core/dist/packages/core/src/socialResolver.js +237 -0
  230. package/packages/core/dist/packages/core/src/socialTemplate.d.ts +2 -0
  231. package/packages/core/dist/packages/core/src/socialTemplate.js +90 -0
  232. package/packages/core/dist/packages/core/src/types.d.ts +99 -0
  233. package/packages/core/dist/packages/core/src/types.js +2 -0
  234. package/packages/core/src/index.ts +2 -0
  235. package/packages/core/src/privateCrypto.ts +57 -0
  236. package/packages/core/src/privateMessage.ts +101 -0
  237. package/packages/core/src/profile.ts +2 -0
  238. package/packages/core/src/publicProfileSummary.ts +7 -0
  239. package/packages/core/src/socialConfig.ts +7 -5
  240. package/packages/core/src/types.ts +44 -0
  241. package/packages/network/dist/config/silicaclaw-defaults.json +19 -0
  242. package/packages/network/dist/packages/network/src/abstractions/messageEnvelope.d.ts +28 -0
  243. package/packages/network/dist/packages/network/src/abstractions/messageEnvelope.js +36 -0
  244. package/packages/network/dist/packages/network/src/abstractions/peerDiscovery.d.ts +43 -0
  245. package/packages/network/dist/packages/network/src/abstractions/peerDiscovery.js +2 -0
  246. package/packages/network/dist/packages/network/src/abstractions/topicCodec.d.ts +4 -0
  247. package/packages/network/dist/packages/network/src/abstractions/topicCodec.js +2 -0
  248. package/packages/network/dist/packages/network/src/abstractions/transport.d.ts +36 -0
  249. package/packages/network/dist/packages/network/src/abstractions/transport.js +2 -0
  250. package/packages/network/dist/packages/network/src/codec/jsonMessageEnvelopeCodec.d.ts +5 -0
  251. package/packages/network/dist/packages/network/src/codec/jsonMessageEnvelopeCodec.js +24 -0
  252. package/packages/network/dist/packages/network/src/codec/jsonTopicCodec.d.ts +5 -0
  253. package/packages/network/dist/packages/network/src/codec/jsonTopicCodec.js +12 -0
  254. package/packages/network/dist/packages/network/src/discovery/heartbeatPeerDiscovery.d.ts +28 -0
  255. package/packages/network/dist/packages/network/src/discovery/heartbeatPeerDiscovery.js +144 -0
  256. package/packages/network/dist/packages/network/src/index.d.ts +14 -0
  257. package/packages/network/dist/packages/network/src/index.js +30 -0
  258. package/packages/network/dist/packages/network/src/localEventBus.d.ts +9 -0
  259. package/packages/network/dist/packages/network/src/localEventBus.js +47 -0
  260. package/packages/network/dist/packages/network/src/mock.d.ts +8 -0
  261. package/packages/network/dist/packages/network/src/mock.js +24 -0
  262. package/packages/network/dist/packages/network/src/realPreview.d.ts +105 -0
  263. package/packages/network/dist/packages/network/src/realPreview.js +331 -0
  264. package/packages/network/dist/packages/network/src/relayPreview.d.ts +178 -0
  265. package/packages/network/dist/packages/network/src/relayPreview.js +548 -0
  266. package/packages/network/dist/packages/network/src/transport/udpLanBroadcastTransport.d.ts +23 -0
  267. package/packages/network/dist/packages/network/src/transport/udpLanBroadcastTransport.js +154 -0
  268. package/packages/network/dist/packages/network/src/types.d.ts +10 -0
  269. package/packages/network/dist/packages/network/src/types.js +2 -0
  270. package/packages/network/dist/packages/network/src/webrtcPreview.d.ts +163 -0
  271. package/packages/network/dist/packages/network/src/webrtcPreview.js +848 -0
  272. package/packages/network/src/realPreview.ts +3 -2
  273. package/packages/network/src/relayPreview.ts +125 -12
  274. package/packages/network/src/transport/udpLanBroadcastTransport.ts +2 -1
  275. package/packages/network/src/types.ts +2 -0
  276. package/packages/network/src/webrtcPreview.ts +2 -1
  277. package/packages/storage/config/silicaclaw-defaults.json +19 -0
  278. package/packages/storage/dist/config/silicaclaw-defaults.json +19 -0
  279. package/packages/storage/dist/packages/core/src/crypto.d.ts +6 -0
  280. package/packages/storage/dist/packages/core/src/crypto.js +50 -0
  281. package/packages/storage/dist/packages/core/src/directory.d.ts +17 -0
  282. package/packages/storage/dist/packages/core/src/directory.js +145 -0
  283. package/packages/storage/dist/packages/core/src/identity.d.ts +2 -0
  284. package/packages/storage/dist/packages/core/src/identity.js +18 -0
  285. package/packages/storage/dist/packages/core/src/index.d.ts +14 -0
  286. package/packages/storage/dist/packages/core/src/index.js +30 -0
  287. package/packages/storage/dist/packages/core/src/indexing.d.ts +6 -0
  288. package/packages/storage/dist/packages/core/src/indexing.js +43 -0
  289. package/packages/storage/dist/packages/core/src/presence.d.ts +4 -0
  290. package/packages/storage/dist/packages/core/src/presence.js +23 -0
  291. package/packages/storage/dist/packages/core/src/privateCrypto.d.ts +17 -0
  292. package/packages/storage/dist/packages/core/src/privateCrypto.js +40 -0
  293. package/packages/storage/dist/packages/core/src/privateMessage.d.ts +23 -0
  294. package/packages/storage/dist/packages/core/src/privateMessage.js +74 -0
  295. package/packages/storage/dist/packages/core/src/profile.d.ts +4 -0
  296. package/packages/storage/dist/packages/core/src/profile.js +41 -0
  297. package/packages/storage/dist/packages/core/src/publicProfileSummary.d.ts +74 -0
  298. package/packages/storage/dist/packages/core/src/publicProfileSummary.js +106 -0
  299. package/packages/storage/dist/packages/core/src/socialConfig.d.ts +100 -0
  300. package/packages/storage/dist/packages/core/src/socialConfig.js +300 -0
  301. package/packages/storage/dist/packages/core/src/socialMessage.d.ts +19 -0
  302. package/packages/storage/dist/packages/core/src/socialMessage.js +69 -0
  303. package/packages/storage/dist/packages/core/src/socialResolver.d.ts +46 -0
  304. package/packages/storage/dist/packages/core/src/socialResolver.js +237 -0
  305. package/packages/storage/dist/packages/core/src/socialTemplate.d.ts +2 -0
  306. package/packages/storage/dist/packages/core/src/socialTemplate.js +90 -0
  307. package/packages/storage/dist/packages/core/src/types.d.ts +99 -0
  308. package/packages/storage/dist/packages/core/src/types.js +2 -0
  309. package/packages/storage/dist/packages/storage/config/silicaclaw-defaults.json +19 -0
  310. package/packages/storage/dist/packages/storage/src/index.d.ts +3 -0
  311. package/packages/storage/dist/packages/storage/src/index.js +19 -0
  312. package/packages/storage/dist/packages/storage/src/jsonRepo.d.ts +7 -0
  313. package/packages/storage/dist/packages/storage/src/jsonRepo.js +29 -0
  314. package/packages/storage/dist/packages/storage/src/repos.d.ts +73 -0
  315. package/packages/storage/dist/packages/storage/src/repos.js +85 -0
  316. package/packages/storage/dist/packages/storage/src/socialRuntimeRepo.d.ts +5 -0
  317. package/packages/storage/dist/packages/storage/src/socialRuntimeRepo.js +57 -0
  318. package/packages/storage/dist/socialRuntimeRepo.js +8 -4
  319. package/packages/storage/src/repos.ts +31 -1
  320. package/packages/storage/src/socialRuntimeRepo.ts +5 -4
  321. package/packages/storage/tsconfig.json +1 -6
  322. package/scripts/functional-check.mjs +35 -6
  323. package/scripts/install-openclaw-skill.mjs +9 -2
  324. package/scripts/openclaw-bridge-adapter.mjs +3 -1
  325. package/scripts/openclaw-bridge-client.mjs +3 -1
  326. package/scripts/openclaw-runtime-demo.mjs +3 -1
  327. package/scripts/quickstart.sh +14 -10
  328. package/scripts/release-pack.mjs +59 -1
  329. package/scripts/silicaclaw-cli.mjs +166 -51
  330. package/scripts/silicaclaw-gateway.mjs +410 -84
  331. package/scripts/validate-openclaw-skill.mjs +98 -21
@@ -13,6 +13,7 @@ const fs_1 = require("fs");
13
13
  const crypto_1 = require("crypto");
14
14
  const os_1 = require("os");
15
15
  const util_1 = require("util");
16
+ const silicaclaw_defaults_json_1 = __importDefault(require("../../../config/silicaclaw-defaults.json"));
16
17
  const core_1 = require("@silicaclaw/core");
17
18
  const network_1 = require("@silicaclaw/network");
18
19
  const storage_1 = require("@silicaclaw/storage");
@@ -27,21 +28,36 @@ const NETWORK_MAX_PAST_DRIFT_MS = Number(process.env.NETWORK_MAX_PAST_DRIFT_MS |
27
28
  const NETWORK_HEARTBEAT_INTERVAL_MS = Number(process.env.NETWORK_HEARTBEAT_INTERVAL_MS || 12_000);
28
29
  const NETWORK_PEER_STALE_AFTER_MS = Number(process.env.NETWORK_PEER_STALE_AFTER_MS || 45_000);
29
30
  const OPENCLAW_GATEWAY_HOST = "127.0.0.1";
30
- const OPENCLAW_GATEWAY_PORT = 18_789;
31
+ const DEFAULT_NETWORK_MODE = silicaclaw_defaults_json_1.default.network.default_mode;
32
+ const DEFAULT_NETWORK_NAMESPACE = silicaclaw_defaults_json_1.default.network.default_namespace;
33
+ const DEFAULT_NETWORK_PORT = silicaclaw_defaults_json_1.default.ports.network_default;
34
+ const DEFAULT_GLOBAL_SIGNALING_URL = silicaclaw_defaults_json_1.default.network.global_preview.relay_url;
35
+ const DEFAULT_GLOBAL_ROOM = silicaclaw_defaults_json_1.default.network.global_preview.room;
36
+ const DEFAULT_BRIDGE_API_BASE = silicaclaw_defaults_json_1.default.bridge.api_base;
37
+ const OPENCLAW_GATEWAY_PORT = silicaclaw_defaults_json_1.default.ports.openclaw_gateway;
31
38
  const OPENCLAW_GATEWAY_URL = `http://${OPENCLAW_GATEWAY_HOST}:${OPENCLAW_GATEWAY_PORT}/`;
39
+ const OPENCLAW_RUNTIME_CACHE_MS = 15_000;
40
+ const OPENCLAW_BRIDGE_STATUS_CACHE_MS = 5_000;
32
41
  const NETWORK_PEER_REMOVE_AFTER_MS = Number(process.env.NETWORK_PEER_REMOVE_AFTER_MS || 180_000);
42
+ const DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT = Number(process.env.DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT || 1000);
33
43
  const NETWORK_UDP_BIND_ADDRESS = process.env.NETWORK_UDP_BIND_ADDRESS || "0.0.0.0";
34
44
  const NETWORK_UDP_BROADCAST_ADDRESS = process.env.NETWORK_UDP_BROADCAST_ADDRESS || "255.255.255.255";
35
45
  const NETWORK_PEER_ID = process.env.NETWORK_PEER_ID;
36
46
  const NETWORK_MODE = process.env.NETWORK_MODE || "";
37
- const WEBRTC_SIGNALING_URL = process.env.WEBRTC_SIGNALING_URL || "https://relay.silicaclaw.com";
47
+ const WEBRTC_SIGNALING_URL = process.env.WEBRTC_SIGNALING_URL || DEFAULT_GLOBAL_SIGNALING_URL;
38
48
  const WEBRTC_SIGNALING_URLS = process.env.WEBRTC_SIGNALING_URLS || "";
39
- const WEBRTC_ROOM = process.env.WEBRTC_ROOM || "silicaclaw-global-preview";
49
+ const WEBRTC_ROOM = process.env.WEBRTC_ROOM || DEFAULT_GLOBAL_ROOM;
40
50
  const WEBRTC_SEED_PEERS = process.env.WEBRTC_SEED_PEERS || "";
41
51
  const WEBRTC_BOOTSTRAP_HINTS = process.env.WEBRTC_BOOTSTRAP_HINTS || "";
42
52
  const PROFILE_VERSION = "v0.9";
43
53
  const SOCIAL_MESSAGE_TOPIC = "social.message";
44
54
  const SOCIAL_MESSAGE_OBSERVATION_TOPIC = "social.message.observation";
55
+ const PRIVATE_MESSAGE_TOPIC = "private.message";
56
+ const PRIVATE_MESSAGE_RECEIPT_TOPIC = "private.message.receipt";
57
+ const PRIVATE_MESSAGE_HISTORY_LIMIT = Number(process.env.PRIVATE_MESSAGE_HISTORY_LIMIT || 1000);
58
+ const PRIVATE_MESSAGE_RECEIPT_HISTORY_LIMIT = Number(process.env.PRIVATE_MESSAGE_RECEIPT_HISTORY_LIMIT || 2000);
59
+ const PRIVATE_MESSAGE_QUERY_LIMIT = Number(process.env.PRIVATE_MESSAGE_QUERY_LIMIT || 100);
60
+ const PRIVATE_MESSAGE_PERSIST_DEBOUNCE_MS = Number(process.env.PRIVATE_MESSAGE_PERSIST_DEBOUNCE_MS || 750);
45
61
  const DEFAULT_SOCIAL_MESSAGE_CHANNEL = "global";
46
62
  const SOCIAL_MESSAGE_MAX_BODY_CHARS = Number(process.env.SOCIAL_MESSAGE_MAX_BODY_CHARS || 500);
47
63
  const SOCIAL_MESSAGE_HISTORY_LIMIT = Number(process.env.SOCIAL_MESSAGE_HISTORY_LIMIT || 100);
@@ -53,6 +69,10 @@ const SOCIAL_MESSAGE_DUPLICATE_WINDOW_MS = Number(process.env.SOCIAL_MESSAGE_DUP
53
69
  const SOCIAL_MESSAGE_MAX_FUTURE_MS = Number(process.env.SOCIAL_MESSAGE_MAX_FUTURE_MS || 30_000);
54
70
  const SOCIAL_MESSAGE_MAX_AGE_MS = Number(process.env.SOCIAL_MESSAGE_MAX_AGE_MS || 15 * 60_000);
55
71
  const SOCIAL_MESSAGE_OBSERVATION_HISTORY_LIMIT = Number(process.env.SOCIAL_MESSAGE_OBSERVATION_HISTORY_LIMIT || 500);
72
+ const SOCIAL_MESSAGE_REPLAY_WINDOW_MS = Number(process.env.SOCIAL_MESSAGE_REPLAY_WINDOW_MS || 10 * 60_000);
73
+ const SOCIAL_MESSAGE_REPLAY_MAX_PER_BROADCAST = Number(process.env.SOCIAL_MESSAGE_REPLAY_MAX_PER_BROADCAST || 3);
74
+ const SOCIAL_MESSAGE_REPLAY_REFRESH_INTERVAL_MS = Number(process.env.SOCIAL_MESSAGE_REPLAY_REFRESH_INTERVAL_MS || 120_000);
75
+ const PROFILE_RELAY_REFRESH_INTERVAL_MS = Number(process.env.PROFILE_RELAY_REFRESH_INTERVAL_MS || 120_000);
56
76
  const SOCIAL_MESSAGE_BLOCKED_AGENT_IDS = new Set(dedupeStrings(parseListEnv(process.env.SOCIAL_MESSAGE_BLOCKED_AGENT_IDS || "")));
57
77
  const SOCIAL_MESSAGE_BLOCKED_TERMS = dedupeStrings(parseListEnv(process.env.SOCIAL_MESSAGE_BLOCKED_TERMS || ""))
58
78
  .map((term) => term.trim().toLowerCase())
@@ -60,25 +80,79 @@ const SOCIAL_MESSAGE_BLOCKED_TERMS = dedupeStrings(parseListEnv(process.env.SOCI
60
80
  const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
61
81
  const OPENCLAW_SKILL_NAME = "silicaclaw-broadcast";
62
82
  function readWorkspaceVersion(workspaceRoot) {
63
- const pkgFile = (0, path_1.resolve)(workspaceRoot, "package.json");
64
- if ((0, fs_1.existsSync)(pkgFile)) {
65
- try {
66
- const pkg = JSON.parse((0, fs_1.readFileSync)(pkgFile, "utf8"));
67
- if (pkg.version)
68
- return String(pkg.version);
83
+ const candidates = [
84
+ workspaceRoot,
85
+ process.cwd(),
86
+ (0, path_1.resolve)(__dirname, "..", "..", ".."),
87
+ (0, path_1.resolve)(__dirname, "..", "..", "..", ".."),
88
+ ].filter((dir, index, list) => dir && list.indexOf(dir) === index);
89
+ for (const candidate of candidates) {
90
+ const pkgFile = (0, path_1.resolve)(candidate, "package.json");
91
+ if ((0, fs_1.existsSync)(pkgFile)) {
92
+ try {
93
+ const pkg = JSON.parse((0, fs_1.readFileSync)(pkgFile, "utf8"));
94
+ if (pkg.version && (pkg.name === "@silicaclaw/cli" || (0, fs_1.existsSync)((0, path_1.resolve)(candidate, "apps", "local-console")))) {
95
+ return String(pkg.version);
96
+ }
97
+ }
98
+ catch {
99
+ // ignore
100
+ }
69
101
  }
70
- catch {
71
- // ignore
102
+ const versionFile = (0, path_1.resolve)(candidate, "VERSION");
103
+ if ((0, fs_1.existsSync)(versionFile)) {
104
+ const raw = (0, fs_1.readFileSync)(versionFile, "utf8").trim();
105
+ if (raw)
106
+ return raw;
72
107
  }
73
108
  }
74
- const versionFile = (0, path_1.resolve)(workspaceRoot, "VERSION");
75
- if ((0, fs_1.existsSync)(versionFile)) {
76
- const raw = (0, fs_1.readFileSync)(versionFile, "utf8").trim();
77
- if (raw)
78
- return raw;
79
- }
80
109
  return "unknown";
81
110
  }
111
+ function normalizeVersionText(value) {
112
+ const text = String(value || "").trim();
113
+ return text.startsWith("v") ? text.slice(1) : text;
114
+ }
115
+ function formatBytesToMiB(value) {
116
+ return Math.round((value / (1024 * 1024)) * 10) / 10;
117
+ }
118
+ function tokenizeVersion(value) {
119
+ return normalizeVersionText(value)
120
+ .split(/[^0-9A-Za-z]+/)
121
+ .map((token) => token.trim())
122
+ .filter(Boolean)
123
+ .map((token) => (/^\d+$/.test(token) ? Number(token) : token.toLowerCase()));
124
+ }
125
+ function compareVersionTokens(left, right) {
126
+ const leftTokens = tokenizeVersion(left);
127
+ const rightTokens = tokenizeVersion(right);
128
+ const maxLength = Math.max(leftTokens.length, rightTokens.length);
129
+ for (let index = 0; index < maxLength; index += 1) {
130
+ const leftToken = leftTokens[index];
131
+ const rightToken = rightTokens[index];
132
+ if (leftToken === undefined && rightToken === undefined)
133
+ return 0;
134
+ if (leftToken === undefined)
135
+ return -1;
136
+ if (rightToken === undefined)
137
+ return 1;
138
+ if (typeof leftToken === "number" && typeof rightToken === "number") {
139
+ if (leftToken !== rightToken)
140
+ return leftToken > rightToken ? 1 : -1;
141
+ continue;
142
+ }
143
+ const leftText = String(leftToken);
144
+ const rightText = String(rightToken);
145
+ if (leftText !== rightText)
146
+ return leftText.localeCompare(rightText);
147
+ }
148
+ return 0;
149
+ }
150
+ function userNpmCacheDir() {
151
+ return (0, path_1.resolve)((0, os_1.homedir)(), ".silicaclaw", "npm-cache");
152
+ }
153
+ function userShimPath() {
154
+ return (0, path_1.resolve)((0, os_1.homedir)(), ".silicaclaw", "bin", "silicaclaw");
155
+ }
82
156
  function resolveWorkspaceRoot(cwd = process.cwd()) {
83
157
  if ((0, fs_1.existsSync)((0, path_1.resolve)(cwd, "apps", "local-console", "package.json"))) {
84
158
  return cwd;
@@ -89,13 +163,43 @@ function resolveWorkspaceRoot(cwd = process.cwd()) {
89
163
  }
90
164
  return cwd;
91
165
  }
166
+ function resolveProjectRoot(appRoot, cwd = process.cwd()) {
167
+ const envAppRoot = String(process.env.SILICACLAW_APP_DIR || "").trim();
168
+ if (envAppRoot &&
169
+ (0, fs_1.existsSync)((0, path_1.resolve)(envAppRoot, "apps", "local-console", "package.json")) &&
170
+ (0, fs_1.existsSync)((0, path_1.resolve)(envAppRoot, "package.json"))) {
171
+ return (0, path_1.resolve)(envAppRoot);
172
+ }
173
+ const envRoot = String(process.env.SILICACLAW_WORKSPACE_DIR || "").trim();
174
+ if (envRoot) {
175
+ return (0, path_1.resolve)(envRoot);
176
+ }
177
+ if ((0, fs_1.existsSync)((0, path_1.resolve)(appRoot, "apps", "local-console", "package.json")) &&
178
+ (0, fs_1.existsSync)((0, path_1.resolve)(appRoot, "package.json"))) {
179
+ return appRoot;
180
+ }
181
+ if (!(0, fs_1.existsSync)((0, path_1.resolve)(cwd, "apps", "local-console", "package.json"))) {
182
+ return (0, path_1.resolve)(cwd);
183
+ }
184
+ return appRoot;
185
+ }
92
186
  function resolveStorageRoot(workspaceRoot, cwd = process.cwd()) {
187
+ const home = process.env.HOME || (0, os_1.homedir)();
188
+ if (home) {
189
+ return (0, path_1.resolve)(home, ".silicaclaw", "local-console");
190
+ }
93
191
  const appRoot = (0, path_1.resolve)(workspaceRoot, "apps", "local-console");
94
192
  if ((0, fs_1.existsSync)((0, path_1.resolve)(appRoot, "package.json"))) {
95
193
  return appRoot;
96
194
  }
97
195
  return cwd;
98
196
  }
197
+ function defaultOpenClawSourceDir(rootDir) {
198
+ if ((0, fs_1.existsSync)((0, path_1.resolve)(rootDir, "openclaw.mjs")) || (0, fs_1.existsSync)((0, path_1.resolve)(rootDir, "package.json"))) {
199
+ return rootDir;
200
+ }
201
+ return (0, path_1.resolve)(rootDir, "..", "openclaw");
202
+ }
99
203
  function resolveExecutableInPath(binName) {
100
204
  const pathValue = String(process.env.PATH || "").trim();
101
205
  if (!pathValue)
@@ -162,6 +266,54 @@ function summarizeSkillReadme(filePath) {
162
266
  return "";
163
267
  }
164
268
  }
269
+ function readDialogueCheatsheetPreview(filePath, limit = 6) {
270
+ if (!filePath || !(0, fs_1.existsSync)(filePath))
271
+ return [];
272
+ try {
273
+ return (0, fs_1.readFileSync)(filePath, "utf8")
274
+ .split(/\r?\n/)
275
+ .map((line) => line.trim())
276
+ .filter((line) => line.startsWith("- "))
277
+ .map((line) => line.slice(2).trim())
278
+ .filter(Boolean)
279
+ .slice(0, limit);
280
+ }
281
+ catch {
282
+ return [];
283
+ }
284
+ }
285
+ function readDialogueCheatsheetSections(filePath, maxSections = 3, maxItemsPerSection = 5) {
286
+ if (!filePath || !(0, fs_1.existsSync)(filePath))
287
+ return [];
288
+ try {
289
+ const lines = (0, fs_1.readFileSync)(filePath, "utf8").split(/\r?\n/);
290
+ const sections = [];
291
+ let current = null;
292
+ for (const rawLine of lines) {
293
+ const line = rawLine.trim();
294
+ if (line.startsWith("## ")) {
295
+ if (current && current.items.length)
296
+ sections.push(current);
297
+ current = { title: line.slice(3).trim(), items: [] };
298
+ continue;
299
+ }
300
+ if (line.startsWith("- ")) {
301
+ if (!current) {
302
+ current = { title: "Examples", items: [] };
303
+ }
304
+ if (current.items.length < maxItemsPerSection) {
305
+ current.items.push(line.slice(2).trim());
306
+ }
307
+ }
308
+ }
309
+ if (current && current.items.length)
310
+ sections.push(current);
311
+ return sections.slice(0, maxSections);
312
+ }
313
+ catch {
314
+ return [];
315
+ }
316
+ }
165
317
  function detectOpenClawInstallation(workspaceRoot) {
166
318
  const workspaceDir = (0, path_1.resolve)(workspaceRoot, ".openclaw");
167
319
  const homeDir = (0, path_1.resolve)(process.env.HOME || "", ".openclaw");
@@ -206,7 +358,7 @@ function detectOpenClawInstallation(workspaceRoot) {
206
358
  }
207
359
  function readOpenClawConfiguredGateway(workspaceRoot) {
208
360
  const configuredSourceDir = String(process.env.OPENCLAW_SOURCE_DIR || "").trim();
209
- const defaultSourceDir = (0, path_1.resolve)(workspaceRoot, "..", "openclaw");
361
+ const defaultSourceDir = defaultOpenClawSourceDir(workspaceRoot);
210
362
  const sourceDir = configuredSourceDir || defaultSourceDir;
211
363
  const homeDir = (0, path_1.resolve)(process.env.HOME || "", ".openclaw");
212
364
  const explicitConfigPath = String(process.env.OPENCLAW_CONFIG_PATH || "").trim();
@@ -248,44 +400,56 @@ function readOpenClawConfiguredGateway(workspaceRoot) {
248
400
  gateway_url: OPENCLAW_GATEWAY_URL,
249
401
  };
250
402
  }
403
+ function resolveOpenClawStatusCommand(workspaceRoot) {
404
+ const explicitBin = String(process.env.OPENCLAW_BIN || "").trim();
405
+ if (explicitBin) {
406
+ return { cmd: explicitBin, args: ["status"] };
407
+ }
408
+ const configuredSourceDir = String(process.env.OPENCLAW_SOURCE_DIR || "").trim();
409
+ const defaultSourceDir = defaultOpenClawSourceDir(workspaceRoot);
410
+ const sourceDir = configuredSourceDir || defaultSourceDir;
411
+ const sourceEntry = existingPathOrNull((0, path_1.resolve)(sourceDir, "openclaw.mjs"));
412
+ if (sourceEntry) {
413
+ return { cmd: process.execPath, args: [sourceEntry, "status"] };
414
+ }
415
+ const commandPath = resolveExecutableInPath("openclaw");
416
+ if (commandPath) {
417
+ return { cmd: commandPath, args: ["status"] };
418
+ }
419
+ return null;
420
+ }
421
+ function resolveOpenClawGatewayProbeCommand(workspaceRoot) {
422
+ const explicitBin = String(process.env.OPENCLAW_BIN || "").trim();
423
+ if (explicitBin) {
424
+ return { cmd: explicitBin, args: ["gateway", "probe"] };
425
+ }
426
+ const configuredSourceDir = String(process.env.OPENCLAW_SOURCE_DIR || "").trim();
427
+ const defaultSourceDir = defaultOpenClawSourceDir(workspaceRoot);
428
+ const sourceDir = configuredSourceDir || defaultSourceDir;
429
+ const sourceEntry = existingPathOrNull((0, path_1.resolve)(sourceDir, "openclaw.mjs"));
430
+ if (sourceEntry) {
431
+ return { cmd: process.execPath, args: [sourceEntry, "gateway", "probe"] };
432
+ }
433
+ const commandPath = resolveExecutableInPath("openclaw");
434
+ if (commandPath) {
435
+ return { cmd: commandPath, args: ["gateway", "probe"] };
436
+ }
437
+ return null;
438
+ }
251
439
  function detectOpenClawRuntime(workspaceRoot) {
252
440
  const configuredGateway = readOpenClawConfiguredGateway(workspaceRoot);
253
- const result = (0, child_process_1.spawnSync)("ps", ["-Ao", "pid=,ppid=,command="], {
441
+ const statusCommand = resolveOpenClawStatusCommand(workspaceRoot);
442
+ const statusLooksConfigured = Boolean(statusCommand ||
443
+ configuredGateway.config_path ||
444
+ detectOpenClawInstallation(workspaceRoot).detected);
445
+ const gatewayProbeCommand = ["lsof", "-nP", `-iTCP:${configuredGateway.gateway_port}`, "-sTCP:LISTEN"];
446
+ const gatewayProbe = (0, child_process_1.spawnSync)(gatewayProbeCommand[0], gatewayProbeCommand.slice(1), {
254
447
  encoding: "utf8",
448
+ timeout: 1200,
255
449
  });
256
- const stdout = String(result.stdout || "");
257
- const lines = stdout
258
- .split("\n")
259
- .map((line) => line.trim())
260
- .filter(Boolean);
261
- const processes = lines
262
- .map((line) => {
263
- const match = line.match(/^(\d+)\s+(\d+)\s+(.+)$/);
264
- if (!match)
265
- return null;
266
- const command = match[3] || "";
267
- const lower = command.toLowerCase();
268
- const isOpenClaw = lower.includes(" openclaw ") ||
269
- lower.endsWith(" openclaw") ||
270
- lower.includes("/openclaw ") ||
271
- lower.includes("openclaw.mjs") ||
272
- lower.includes("openclaw gateway") ||
273
- lower.includes("openclaw agent") ||
274
- lower.includes("openclaw message");
275
- if (!isOpenClaw)
276
- return null;
277
- return {
278
- pid: Number(match[1]),
279
- ppid: Number(match[2]),
280
- command,
281
- };
282
- })
283
- .filter((item) => Boolean(item));
284
- const openclawPids = new Set(processes.map((item) => item.pid));
285
- const gatewayProbe = (0, child_process_1.spawnSync)("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN"], {
286
- encoding: "utf8",
287
- });
288
- const gatewayLines = String(gatewayProbe.stdout || "")
450
+ const gatewayStatusStdout = String(gatewayProbe.stdout || "");
451
+ const gatewayStatusStderr = String(gatewayProbe.stderr || "");
452
+ const gatewayLines = gatewayStatusStdout
289
453
  .split("\n")
290
454
  .map((line) => line.trim())
291
455
  .filter(Boolean);
@@ -295,15 +459,10 @@ function detectOpenClawRuntime(workspaceRoot) {
295
459
  const parts = line.split(/\s+/);
296
460
  const pid = Number(parts[1] || 0);
297
461
  const command = parts[0] || "";
298
- const lowerCommand = command.toLowerCase();
299
462
  const endpoint = parts[8] || parts[parts.length - 1] || "";
300
463
  const portMatch = endpoint.match(/:(\d+)(?:\s*\(|$)/);
301
464
  if (!pid || !command || !portMatch)
302
465
  return null;
303
- const isOpenClawListener = openclawPids.has(pid) ||
304
- lowerCommand.includes("openclaw");
305
- if (!isOpenClawListener)
306
- return null;
307
466
  const port = Number(portMatch[1]);
308
467
  if (!Number.isFinite(port) || port <= 0)
309
468
  return null;
@@ -315,49 +474,107 @@ function detectOpenClawRuntime(workspaceRoot) {
315
474
  };
316
475
  })
317
476
  .filter((item) => Boolean(item));
477
+ const gatewayProbeOk = gatewayListeners.length > 0;
478
+ let processes = gatewayListeners.map((item) => ({
479
+ pid: item.pid,
480
+ ppid: item.ppid,
481
+ command: item.command,
482
+ }));
483
+ let processResult = null;
484
+ if (!gatewayProbeOk) {
485
+ processResult = (0, child_process_1.spawnSync)("ps", ["-Ao", "pid=,ppid=,command="], {
486
+ encoding: "utf8",
487
+ timeout: 1200,
488
+ });
489
+ const stdout = String(processResult.stdout || "");
490
+ const lines = stdout
491
+ .split("\n")
492
+ .map((line) => line.trim())
493
+ .filter(Boolean);
494
+ processes = lines
495
+ .map((line) => {
496
+ const match = line.match(/^(\d+)\s+(\d+)\s+(.+)$/);
497
+ if (!match)
498
+ return null;
499
+ const command = match[3] || "";
500
+ const lower = command.toLowerCase();
501
+ const isOpenClaw = lower.includes(" openclaw ") ||
502
+ lower.endsWith(" openclaw") ||
503
+ lower.includes("/openclaw ") ||
504
+ lower.includes("openclaw.mjs") ||
505
+ lower.includes("openclaw gateway") ||
506
+ lower.includes("openclaw agent") ||
507
+ lower.includes("openclaw message");
508
+ if (!isOpenClaw)
509
+ return null;
510
+ return {
511
+ pid: Number(match[1]),
512
+ ppid: Number(match[2]),
513
+ command,
514
+ };
515
+ })
516
+ .filter((item) => Boolean(item));
517
+ }
318
518
  const preferredListener = gatewayListeners.find((item) => item.port === configuredGateway.gateway_port) ||
319
519
  gatewayListeners[0] ||
320
520
  null;
321
- const combinedProcesses = new Map();
322
- for (const process of [...processes, ...gatewayListeners]) {
323
- if (!combinedProcesses.has(process.pid)) {
324
- combinedProcesses.set(process.pid, process);
325
- continue;
326
- }
327
- const current = combinedProcesses.get(process.pid);
328
- if (current && current.command.length < process.command.length) {
329
- combinedProcesses.set(process.pid, process);
330
- }
331
- }
332
- const allProcesses = Array.from(combinedProcesses.values());
333
- const gatewayReachable = gatewayListeners.length > 0;
521
+ const allProcesses = processes.slice(0, 10);
522
+ const gatewayReachable = gatewayProbeOk;
334
523
  const detectionNotes = [];
335
- if (result.status !== 0)
336
- detectionNotes.push(String(result.stderr || "ps failed").trim());
337
524
  if (gatewayProbe.status !== 0 && gatewayLines.length === 0) {
338
- detectionNotes.push(String(gatewayProbe.stderr || "lsof failed").trim());
525
+ detectionNotes.push(String(gatewayStatusStderr || "openclaw gateway probe failed").trim());
526
+ }
527
+ if (processResult && processResult.status !== 0) {
528
+ detectionNotes.push(String(processResult.stderr || "ps failed").trim());
339
529
  }
340
530
  const gatewayPort = preferredListener?.port || configuredGateway.gateway_port;
341
531
  const gatewayUrl = `http://${OPENCLAW_GATEWAY_HOST}:${gatewayPort}/`;
342
532
  return {
343
- running: allProcesses.length > 0 || gatewayReachable,
533
+ running: gatewayProbeOk || allProcesses.length > 0 || gatewayReachable,
344
534
  process_count: allProcesses.length,
345
535
  processes: allProcesses.slice(0, 10),
346
536
  detection_error: detectionNotes.filter(Boolean).join(" | ") || null,
347
537
  gateway_url: gatewayUrl,
348
538
  gateway_port: gatewayPort,
349
539
  gateway_reachable: gatewayReachable,
540
+ status_command: statusCommand ? [statusCommand.cmd, ...statusCommand.args].join(" ") : null,
541
+ status_ok: statusLooksConfigured,
542
+ status_summary: statusLooksConfigured
543
+ ? configuredGateway.config_path
544
+ ? `configured via ${configuredGateway.config_path}`
545
+ : statusCommand
546
+ ? `command available: ${[statusCommand.cmd, ...statusCommand.args].join(" ")}`
547
+ : "OpenClaw environment detected"
548
+ : null,
549
+ gateway_probe_command: gatewayProbeCommand.join(" "),
550
+ gateway_probe_ok: gatewayProbeOk,
551
+ gateway_probe_summary: gatewayProbeOk
552
+ ? gatewayStatusStdout
553
+ .split("\n")
554
+ .map((line) => line.trim())
555
+ .filter(Boolean)
556
+ .slice(0, 4)
557
+ .join(" | ")
558
+ : null,
350
559
  configured_gateway_url: configuredGateway.gateway_url,
351
560
  configured_gateway_port: configuredGateway.gateway_port,
352
561
  configured_gateway_bind: configuredGateway.gateway_bind,
353
562
  configured_gateway_config_path: configuredGateway.config_path,
354
- detection_mode: processes.length > 0 && gatewayReachable
355
- ? "process+gateway"
356
- : gatewayReachable
357
- ? "gateway"
358
- : processes.length > 0
359
- ? "process"
360
- : "not_running",
563
+ detection_mode: gatewayProbeOk
564
+ ? (processes.length > 0 && gatewayReachable
565
+ ? "gateway-probe+process+gateway"
566
+ : gatewayReachable
567
+ ? "gateway-probe+gateway"
568
+ : processes.length > 0
569
+ ? "gateway-probe+process"
570
+ : "gateway-probe")
571
+ : processes.length > 0 && gatewayReachable
572
+ ? "process+gateway"
573
+ : gatewayReachable
574
+ ? "gateway"
575
+ : processes.length > 0
576
+ ? "process"
577
+ : "not_running",
361
578
  };
362
579
  }
363
580
  function detectOpenClawSkillInstallation() {
@@ -384,7 +601,7 @@ function detectOwnerDeliveryStatus(params) {
384
601
  const ownerAccount = String(process.env.OPENCLAW_OWNER_ACCOUNT || "").trim();
385
602
  const explicitOpenClawBin = String(process.env.OPENCLAW_BIN || "").trim();
386
603
  const configuredSourceDir = String(process.env.OPENCLAW_SOURCE_DIR || "").trim();
387
- const defaultSourceDir = (0, path_1.resolve)(params.workspaceRoot, "..", "openclaw");
604
+ const defaultSourceDir = defaultOpenClawSourceDir(params.workspaceRoot);
388
605
  const openclawSourceDir = configuredSourceDir || defaultSourceDir;
389
606
  const openclawSourceEntry = existingPathOrNull((0, path_1.resolve)(openclawSourceDir, "openclaw.mjs"));
390
607
  const openclawCommandResolvable = Boolean(explicitOpenClawBin || resolveExecutableInPath("openclaw") || openclawSourceEntry);
@@ -450,11 +667,18 @@ function hasMeaningfulJson(filePath) {
450
667
  return false;
451
668
  }
452
669
  }
453
- function migrateLegacyDataIfNeeded(workspaceRoot, storageRoot) {
454
- const legacyDataDir = (0, path_1.resolve)(workspaceRoot, "data");
670
+ function migrateLegacyDataIfNeeded(appRoot, projectRoot, storageRoot) {
671
+ const homeDir = process.env.HOME || (0, os_1.homedir)();
672
+ const legacyNpxAppRoots = collectLegacyNpxAppRoots(homeDir);
455
673
  const targetDataDir = (0, path_1.resolve)(storageRoot, "data");
456
- if (legacyDataDir === targetDataDir)
457
- return;
674
+ const legacyDataDirs = [
675
+ (0, path_1.resolve)(appRoot, "data"),
676
+ (0, path_1.resolve)(appRoot, "apps", "local-console", "data"),
677
+ (0, path_1.resolve)(projectRoot, "data"),
678
+ (0, path_1.resolve)(projectRoot, "apps", "local-console", "data"),
679
+ (0, path_1.resolve)(process.cwd(), "data"),
680
+ ...legacyNpxAppRoots.map((root) => (0, path_1.resolve)(root, "apps", "local-console", "data")),
681
+ ].filter((dir, index, list) => list.indexOf(dir) === index && dir !== targetDataDir);
458
682
  const files = [
459
683
  "identity.json",
460
684
  "profile.json",
@@ -464,17 +688,70 @@ function migrateLegacyDataIfNeeded(workspaceRoot, storageRoot) {
464
688
  "social-message-observations.json",
465
689
  ];
466
690
  for (const file of files) {
467
- const src = (0, path_1.resolve)(legacyDataDir, file);
468
691
  const dst = (0, path_1.resolve)(targetDataDir, file);
469
- if (!(0, fs_1.existsSync)(src))
692
+ if (hasMeaningfulJson(dst))
470
693
  continue;
694
+ for (const legacyDataDir of legacyDataDirs) {
695
+ const src = (0, path_1.resolve)(legacyDataDir, file);
696
+ if (!(0, fs_1.existsSync)(src))
697
+ continue;
698
+ if (!hasMeaningfulJson(src))
699
+ continue;
700
+ (0, fs_1.mkdirSync)(targetDataDir, { recursive: true });
701
+ (0, fs_1.copyFileSync)(src, dst);
702
+ break;
703
+ }
704
+ }
705
+ const targetDotDir = (0, path_1.resolve)(storageRoot, ".silicaclaw");
706
+ const legacyDotDirs = [
707
+ (0, path_1.resolve)(appRoot, ".silicaclaw"),
708
+ (0, path_1.resolve)(appRoot, "apps", "local-console", ".silicaclaw"),
709
+ (0, path_1.resolve)(projectRoot, ".silicaclaw"),
710
+ (0, path_1.resolve)(projectRoot, "apps", "local-console", ".silicaclaw"),
711
+ (0, path_1.resolve)(process.cwd(), ".silicaclaw"),
712
+ ...legacyNpxAppRoots.map((root) => (0, path_1.resolve)(root, "apps", "local-console", ".silicaclaw")),
713
+ ].filter((dir, index, list) => list.indexOf(dir) === index && dir !== targetDotDir);
714
+ const dotFiles = ["social.runtime.json", "social.message-governance.json"];
715
+ for (const file of dotFiles) {
716
+ const dst = (0, path_1.resolve)(targetDotDir, file);
471
717
  if (hasMeaningfulJson(dst))
472
718
  continue;
473
- if (!hasMeaningfulJson(src))
719
+ for (const legacyDotDir of legacyDotDirs) {
720
+ const src = (0, path_1.resolve)(legacyDotDir, file);
721
+ if (!(0, fs_1.existsSync)(src))
722
+ continue;
723
+ if (!hasMeaningfulJson(src))
724
+ continue;
725
+ (0, fs_1.mkdirSync)(targetDotDir, { recursive: true });
726
+ (0, fs_1.copyFileSync)(src, dst);
727
+ break;
728
+ }
729
+ }
730
+ }
731
+ function collectLegacyNpxAppRoots(homeDir) {
732
+ const cacheRoots = [
733
+ (0, path_1.resolve)(homeDir, ".silicaclaw", "npm-cache", "_npx"),
734
+ (0, path_1.resolve)(homeDir, ".npm", "_npx"),
735
+ ];
736
+ const roots = [];
737
+ for (const cacheRoot of cacheRoots) {
738
+ if (!(0, fs_1.existsSync)(cacheRoot))
739
+ continue;
740
+ let entries = [];
741
+ try {
742
+ entries = (0, fs_1.readdirSync)(cacheRoot);
743
+ }
744
+ catch {
474
745
  continue;
475
- (0, fs_1.mkdirSync)(targetDataDir, { recursive: true });
476
- (0, fs_1.copyFileSync)(src, dst);
746
+ }
747
+ for (const entry of entries) {
748
+ const candidate = (0, path_1.resolve)(cacheRoot, entry, "node_modules", "@silicaclaw", "cli");
749
+ if (!(0, fs_1.existsSync)((0, path_1.resolve)(candidate, "apps", "local-console")))
750
+ continue;
751
+ roots.push(candidate);
752
+ }
477
753
  }
754
+ return Array.from(new Set(roots));
478
755
  }
479
756
  function parseListEnv(raw) {
480
757
  return raw
@@ -487,6 +764,7 @@ function dedupeStrings(values) {
487
764
  }
488
765
  class LocalNodeService {
489
766
  workspaceRoot;
767
+ projectRoot;
490
768
  storageRoot;
491
769
  identityRepo;
492
770
  profileRepo;
@@ -495,20 +773,39 @@ class LocalNodeService {
495
773
  socialMessageGovernanceRepo;
496
774
  socialMessageRepo;
497
775
  socialMessageObservationRepo;
776
+ privateMessageRepo;
777
+ privateMessageReceiptRepo;
778
+ privateEncryptionKeyRepo;
498
779
  socialRuntimeRepo;
499
780
  identity = null;
500
781
  profile = null;
501
782
  directory = (0, core_1.createEmptyDirectoryState)();
502
783
  socialMessages = [];
503
784
  socialMessageObservations = [];
785
+ privateMessages = [];
786
+ privateMessageReceipts = [];
787
+ privateEncryptionKeyPair = null;
788
+ privatePeerRoutes = {};
789
+ privateMessageBodyCache = new Map();
504
790
  messageGovernance;
791
+ privateMessagesPersistDirty = false;
792
+ privateMessageReceiptsPersistDirty = false;
793
+ privateMessagesPersistTimer = null;
794
+ privateMessageReceiptsPersistTimer = null;
505
795
  receivedCount = 0;
506
796
  broadcastCount = 0;
507
797
  lastMessageAt = 0;
508
798
  lastBroadcastAt = 0;
799
+ lastProfileBroadcastAt = 0;
800
+ lastProfileBroadcastSignature = "";
801
+ lastReplayBroadcastAt = 0;
802
+ lastReplayBroadcastSignature = "";
509
803
  lastBroadcastErrorAt = 0;
510
804
  lastBroadcastError = null;
511
805
  broadcastFailureCount = 0;
806
+ consecutiveBroadcastFailures = 0;
807
+ lastBroadcastRecoveryAttemptAt = 0;
808
+ broadcastRecoveryInFlight = false;
512
809
  broadcaster = null;
513
810
  subscriptionsBound = false;
514
811
  broadcastEnabled = true;
@@ -524,7 +821,7 @@ class LocalNodeService {
524
821
  };
525
822
  network;
526
823
  adapterMode;
527
- networkMode = "global-preview";
824
+ networkMode = DEFAULT_NETWORK_MODE;
528
825
  networkNamespace;
529
826
  networkPort;
530
827
  socialConfig;
@@ -537,16 +834,23 @@ class LocalNodeService {
537
834
  resolvedIdentitySource = "silicaclaw-existing";
538
835
  resolvedOpenClawIdentityPath = null;
539
836
  webrtcSignalingUrls = [];
540
- webrtcRoom = "silicaclaw-global-preview";
837
+ webrtcRoom = DEFAULT_GLOBAL_ROOM;
541
838
  webrtcSeedPeers = [];
542
839
  webrtcBootstrapHints = [];
543
840
  webrtcBootstrapSources = [];
841
+ networkStarted = false;
842
+ networkStartupError = null;
843
+ networkReconnectTimer = null;
844
+ networkReconnectDelayMs = 5_000;
544
845
  appVersion = "unknown";
846
+ openclawRuntimeCache = null;
847
+ openclawBridgeStatusCache = null;
545
848
  constructor(options) {
546
849
  this.workspaceRoot = options?.workspaceRoot || resolveWorkspaceRoot();
850
+ this.projectRoot = options?.projectRoot || resolveProjectRoot(this.workspaceRoot);
547
851
  this.storageRoot = options?.storageRoot || resolveStorageRoot(this.workspaceRoot);
548
852
  this.appVersion = readWorkspaceVersion(this.workspaceRoot);
549
- migrateLegacyDataIfNeeded(this.workspaceRoot, this.storageRoot);
853
+ migrateLegacyDataIfNeeded(this.workspaceRoot, this.projectRoot, this.storageRoot);
550
854
  this.identityRepo = new storage_1.IdentityRepo(this.storageRoot);
551
855
  this.profileRepo = new storage_1.ProfileRepo(this.storageRoot);
552
856
  this.cacheRepo = new storage_1.CacheRepo(this.storageRoot);
@@ -554,18 +858,21 @@ class LocalNodeService {
554
858
  this.socialMessageGovernanceRepo = new storage_1.SocialMessageGovernanceRepo(this.storageRoot);
555
859
  this.socialMessageRepo = new storage_1.SocialMessageRepo(this.storageRoot);
556
860
  this.socialMessageObservationRepo = new storage_1.SocialMessageObservationRepo(this.storageRoot);
861
+ this.privateMessageRepo = new storage_1.PrivateMessageRepo(this.storageRoot);
862
+ this.privateMessageReceiptRepo = new storage_1.PrivateMessageReceiptRepo(this.storageRoot);
863
+ this.privateEncryptionKeyRepo = new storage_1.PrivateEncryptionKeyRepo(this.storageRoot);
557
864
  this.socialRuntimeRepo = new storage_1.SocialRuntimeRepo(this.storageRoot);
558
865
  this.messageGovernance = this.defaultMessageGovernance();
559
- let loadedSocial = (0, core_1.loadSocialConfig)(this.workspaceRoot);
866
+ let loadedSocial = (0, core_1.loadSocialConfig)(this.projectRoot);
560
867
  if (!loadedSocial.meta.found) {
561
- (0, core_1.ensureDefaultSocialMd)(this.workspaceRoot, {
868
+ (0, core_1.ensureDefaultSocialMd)(this.projectRoot, {
562
869
  display_name: this.getDefaultDisplayName(),
563
870
  bio: "Local AI agent connected to SilicaClaw",
564
871
  tags: ["openclaw", "local-first"],
565
- mode: "global-preview",
872
+ mode: DEFAULT_NETWORK_MODE,
566
873
  public_enabled: false,
567
874
  });
568
- loadedSocial = (0, core_1.loadSocialConfig)(this.workspaceRoot);
875
+ loadedSocial = (0, core_1.loadSocialConfig)(this.projectRoot);
569
876
  this.initState.social_auto_created = true;
570
877
  }
571
878
  this.socialConfig = loadedSocial.config;
@@ -573,35 +880,46 @@ class LocalNodeService {
573
880
  this.socialFound = loadedSocial.meta.found;
574
881
  this.socialParseError = loadedSocial.meta.parse_error;
575
882
  this.socialRawFrontmatter = loadedSocial.raw_frontmatter;
576
- this.networkNamespace = this.socialConfig.network.namespace || process.env.NETWORK_NAMESPACE || "silicaclaw.preview";
577
- this.networkPort = Number(this.socialConfig.network.port || process.env.NETWORK_PORT || 44123);
883
+ this.networkNamespace = this.socialConfig.network.namespace || process.env.NETWORK_NAMESPACE || DEFAULT_NETWORK_NAMESPACE;
884
+ this.networkPort = Number(this.socialConfig.network.port || process.env.NETWORK_PORT || DEFAULT_NETWORK_PORT);
578
885
  this.applyResolvedNetworkConfig();
579
886
  const resolved = this.buildNetworkAdapter();
580
887
  this.network = resolved.adapter;
581
888
  this.adapterMode = resolved.mode;
582
889
  this.networkPort = resolved.port;
583
890
  }
891
+ getCachedOpenClawRuntime() {
892
+ const now = Date.now();
893
+ if (this.openclawRuntimeCache && this.openclawRuntimeCache.expiresAt > now) {
894
+ return this.openclawRuntimeCache.value;
895
+ }
896
+ const value = detectOpenClawRuntime(this.projectRoot);
897
+ this.openclawRuntimeCache = {
898
+ value,
899
+ expiresAt: now + OPENCLAW_RUNTIME_CACHE_MS,
900
+ };
901
+ return value;
902
+ }
903
+ invalidateOpenClawCaches() {
904
+ this.openclawRuntimeCache = null;
905
+ this.openclawBridgeStatusCache = null;
906
+ }
584
907
  async start() {
585
908
  await this.hydrateFromDisk();
586
909
  this.bindNetworkSubscriptions();
587
- await this.network.start();
588
- await this.log("info", `Local node started (${this.adapterMode}, mode=${this.networkMode}, signaling=${this.webrtcSignalingUrls[0] || "-"}, room=${this.webrtcRoom})`);
589
- if (this.profile?.public_enabled && this.broadcastEnabled) {
590
- try {
591
- await this.broadcastNow("adapter_start");
592
- }
593
- catch (error) {
594
- await this.log("warn", `Initial broadcast failed: ${error instanceof Error ? error.message : String(error)}`);
595
- }
596
- }
597
- this.startBroadcastLoop();
910
+ await this.startNetworkAdapterWithRetry("adapter_start");
598
911
  }
599
912
  async stop() {
913
+ this.clearNetworkReconnectTimer();
600
914
  if (this.broadcaster) {
601
915
  clearInterval(this.broadcaster);
602
916
  this.broadcaster = null;
603
917
  }
604
- await this.network.stop();
918
+ await this.flushPrivatePersistence();
919
+ if (this.networkStarted) {
920
+ await this.network.stop();
921
+ }
922
+ this.networkStarted = false;
605
923
  }
606
924
  ensureLocalDirectoryBaseline() {
607
925
  if (this.profile) {
@@ -614,10 +932,11 @@ class LocalNodeService {
614
932
  }
615
933
  }
616
934
  getOverview() {
617
- this.ensureLocalDirectoryBaseline();
618
- this.compactCacheInMemory();
619
- const profiles = Object.values(this.directory.profiles);
620
- const onlineCount = profiles.filter((profile) => (0, core_1.isAgentOnline)(this.directory.presence[profile.agent_id], Date.now(), PRESENCE_TTL_MS)).length;
935
+ const discovered = this.search("");
936
+ const onlineCount = discovered.filter((profile) => profile.online).length;
937
+ const openclawInstallation = detectOpenClawInstallation(this.projectRoot);
938
+ const openclawRuntime = this.getCachedOpenClawRuntime();
939
+ const openclawSkillInstallation = detectOpenClawSkillInstallation();
621
940
  return {
622
941
  app_version: this.appVersion,
623
942
  agent_id: this.identity?.agent_id ?? "",
@@ -627,12 +946,21 @@ class LocalNodeService {
627
946
  last_broadcast_error_at: this.lastBroadcastErrorAt,
628
947
  last_broadcast_error: this.lastBroadcastError,
629
948
  broadcast_failure_count: this.broadcastFailureCount,
630
- discovered_count: profiles.length,
949
+ discovered_count: discovered.length,
631
950
  online_count: onlineCount,
632
- offline_count: Math.max(0, profiles.length - onlineCount),
951
+ offline_count: Math.max(0, discovered.length - onlineCount),
633
952
  init_state: this.initState,
634
953
  presence_ttl_ms: PRESENCE_TTL_MS,
635
954
  onboarding: this.getOnboardingSummary(),
955
+ openclaw: {
956
+ detected: openclawInstallation.detected,
957
+ running: openclawRuntime.running,
958
+ detection_mode: openclawRuntime.detection_mode,
959
+ gateway_url: openclawRuntime.gateway_url,
960
+ gateway_probe_ok: openclawRuntime.gateway_probe_ok,
961
+ status_ok: openclawRuntime.status_ok,
962
+ skill_installed: openclawSkillInstallation.installed,
963
+ },
636
964
  social: {
637
965
  found: this.socialFound,
638
966
  enabled: this.socialConfig.enabled,
@@ -650,7 +978,9 @@ class LocalNodeService {
650
978
  };
651
979
  }
652
980
  getNetworkSummary() {
653
- const diagnostics = this.getAdapterDiagnostics();
981
+ const network = this.getResolvedRealtimeNetworkSummary();
982
+ const diagnostics = network.diagnostics;
983
+ const relayCapable = this.adapterMode === "webrtc-preview" || this.adapterMode === "relay-preview";
654
984
  const peerCount = diagnostics?.peers.total ?? 0;
655
985
  return {
656
986
  status: "running",
@@ -677,29 +1007,33 @@ class LocalNodeService {
677
1007
  real_preview_stats: diagnostics?.stats ?? null,
678
1008
  real_preview_transport_stats: diagnostics?.transport_stats ?? null,
679
1009
  real_preview_discovery_stats: diagnostics?.discovery_stats ?? null,
680
- webrtc_preview: diagnostics && (diagnostics.adapter === "webrtc-preview" || diagnostics.adapter === "relay-preview")
1010
+ webrtc_preview: relayCapable
681
1011
  ? {
682
- signaling_url: diagnostics.signaling_url ?? null,
683
- signaling_endpoints: diagnostics.signaling_endpoints ?? [],
684
- room: diagnostics.room ?? null,
685
- bootstrap_sources: diagnostics.bootstrap_sources ?? [],
686
- seed_peers_count: diagnostics.seed_peers_count ?? 0,
687
- discovery_events_total: diagnostics.discovery_events_total ?? 0,
688
- last_discovery_event_at: diagnostics.last_discovery_event_at ?? 0,
689
- active_webrtc_peers: diagnostics.active_webrtc_peers ?? 0,
690
- reconnect_attempts_total: diagnostics.reconnect_attempts_total ?? 0,
691
- last_join_at: diagnostics.last_join_at ?? 0,
692
- last_poll_at: diagnostics.last_poll_at ?? 0,
693
- last_publish_at: diagnostics.last_publish_at ?? 0,
694
- last_peer_refresh_at: diagnostics.last_peer_refresh_at ?? 0,
695
- last_error_at: diagnostics.last_error_at ?? 0,
696
- last_error: diagnostics.last_error ?? null,
1012
+ started: this.networkStarted,
1013
+ startup_error: this.networkStartupError,
1014
+ signaling_url: network.signaling_url,
1015
+ signaling_endpoints: network.signaling_endpoints,
1016
+ room: network.room,
1017
+ bootstrap_sources: network.bootstrap_sources,
1018
+ seed_peers_count: network.seed_peers_count,
1019
+ discovery_events_total: diagnostics?.discovery_events_total ?? 0,
1020
+ last_discovery_event_at: diagnostics?.last_discovery_event_at ?? 0,
1021
+ active_webrtc_peers: diagnostics?.active_webrtc_peers ?? 0,
1022
+ reconnect_attempts_total: diagnostics?.reconnect_attempts_total ?? 0,
1023
+ last_join_at: diagnostics?.last_join_at ?? 0,
1024
+ last_poll_at: diagnostics?.last_poll_at ?? 0,
1025
+ last_publish_at: diagnostics?.last_publish_at ?? 0,
1026
+ last_peer_refresh_at: diagnostics?.last_peer_refresh_at ?? 0,
1027
+ last_error_at: diagnostics?.last_error_at ?? 0,
1028
+ last_error: diagnostics?.last_error ?? null,
697
1029
  }
698
1030
  : null,
699
1031
  };
700
1032
  }
701
1033
  getNetworkConfig() {
702
- const diagnostics = this.getAdapterDiagnostics();
1034
+ const network = this.getResolvedRealtimeNetworkSummary();
1035
+ const diagnostics = network.diagnostics;
1036
+ const relayCapable = this.adapterMode === "webrtc-preview" || this.adapterMode === "relay-preview";
703
1037
  return {
704
1038
  adapter: this.adapterMode,
705
1039
  mode: this.networkMode,
@@ -713,23 +1047,25 @@ class LocalNodeService {
713
1047
  },
714
1048
  limits: diagnostics?.limits ?? null,
715
1049
  adapter_config: diagnostics?.config ?? null,
716
- adapter_extra: diagnostics && (diagnostics.adapter === "webrtc-preview" || diagnostics.adapter === "relay-preview")
1050
+ adapter_extra: relayCapable
717
1051
  ? {
718
- signaling_url: diagnostics.signaling_url ?? null,
719
- signaling_endpoints: diagnostics.signaling_endpoints ?? [],
720
- room: diagnostics.room ?? null,
721
- bootstrap_sources: diagnostics.bootstrap_sources ?? [],
722
- seed_peers_count: diagnostics.seed_peers_count ?? 0,
723
- discovery_events_total: diagnostics.discovery_events_total ?? 0,
724
- last_discovery_event_at: diagnostics.last_discovery_event_at ?? 0,
725
- connection_states_summary: diagnostics.connection_states_summary ?? null,
726
- datachannel_states_summary: diagnostics.datachannel_states_summary ?? null,
727
- last_join_at: diagnostics.last_join_at ?? 0,
728
- last_poll_at: diagnostics.last_poll_at ?? 0,
729
- last_publish_at: diagnostics.last_publish_at ?? 0,
730
- last_peer_refresh_at: diagnostics.last_peer_refresh_at ?? 0,
731
- last_error_at: diagnostics.last_error_at ?? 0,
732
- last_error: diagnostics.last_error ?? null,
1052
+ started: this.networkStarted,
1053
+ startup_error: this.networkStartupError,
1054
+ signaling_url: network.signaling_url,
1055
+ signaling_endpoints: network.signaling_endpoints,
1056
+ room: network.room,
1057
+ bootstrap_sources: network.bootstrap_sources,
1058
+ seed_peers_count: network.seed_peers_count,
1059
+ discovery_events_total: diagnostics?.discovery_events_total ?? 0,
1060
+ last_discovery_event_at: diagnostics?.last_discovery_event_at ?? 0,
1061
+ connection_states_summary: diagnostics?.connection_states_summary ?? null,
1062
+ datachannel_states_summary: diagnostics?.datachannel_states_summary ?? null,
1063
+ last_join_at: diagnostics?.last_join_at ?? 0,
1064
+ last_poll_at: diagnostics?.last_poll_at ?? 0,
1065
+ last_publish_at: diagnostics?.last_publish_at ?? 0,
1066
+ last_peer_refresh_at: diagnostics?.last_peer_refresh_at ?? 0,
1067
+ last_error_at: diagnostics?.last_error_at ?? 0,
1068
+ last_error: diagnostics?.last_error ?? null,
733
1069
  }
734
1070
  : null,
735
1071
  env: {
@@ -756,15 +1092,18 @@ class LocalNodeService {
756
1092
  demo_mode: this.adapterMode === "real-preview"
757
1093
  ? "lan-preview"
758
1094
  : this.adapterMode === "webrtc-preview" || this.adapterMode === "relay-preview"
759
- ? "internet-preview"
1095
+ ? "global-preview"
760
1096
  : "local-process",
761
1097
  mode_explainer: this.getModeExplainer(),
762
1098
  };
763
1099
  }
764
1100
  getNetworkStats() {
765
- const diagnostics = this.getAdapterDiagnostics();
1101
+ const network = this.getResolvedRealtimeNetworkSummary();
1102
+ const diagnostics = network.diagnostics;
1103
+ const relayCapable = this.adapterMode === "webrtc-preview" || this.adapterMode === "relay-preview";
766
1104
  const peers = diagnostics?.peers?.items ?? [];
767
1105
  const online = peers.filter((peer) => peer.status === "online").length;
1106
+ const memory = process.memoryUsage();
768
1107
  return {
769
1108
  adapter: this.adapterMode,
770
1109
  mode: this.networkMode,
@@ -788,33 +1127,53 @@ class LocalNodeService {
788
1127
  adapter_stats: diagnostics?.stats ?? null,
789
1128
  adapter_transport_stats: diagnostics?.transport_stats ?? null,
790
1129
  adapter_discovery_stats: diagnostics?.discovery_stats ?? null,
791
- adapter_diagnostics_summary: diagnostics
1130
+ runtime_diagnostics: {
1131
+ memory_mib: {
1132
+ rss: formatBytesToMiB(memory.rss),
1133
+ heap_used: formatBytesToMiB(memory.heapUsed),
1134
+ heap_total: formatBytesToMiB(memory.heapTotal),
1135
+ external: formatBytesToMiB(memory.external),
1136
+ },
1137
+ directory: {
1138
+ profile_count: Object.keys(this.directory.profiles).length,
1139
+ presence_count: Object.keys(this.directory.presence).length,
1140
+ index_key_count: Object.keys(this.directory.index).length,
1141
+ },
1142
+ social: {
1143
+ message_count: this.socialMessages.length,
1144
+ observation_count: this.socialMessageObservations.length,
1145
+ },
1146
+ },
1147
+ adapter_diagnostics_summary: relayCapable || diagnostics
792
1148
  ? {
793
- signaling_url: diagnostics.signaling_url ?? null,
794
- signaling_endpoints: diagnostics.signaling_endpoints ?? [],
795
- room: diagnostics.room ?? null,
796
- bootstrap_sources: diagnostics.bootstrap_sources ?? [],
797
- seed_peers_count: diagnostics.seed_peers_count ?? 0,
798
- discovery_events_total: diagnostics.discovery_events_total ?? 0,
799
- last_discovery_event_at: diagnostics.last_discovery_event_at ?? 0,
800
- connection_states_summary: diagnostics.connection_states_summary ?? null,
801
- datachannel_states_summary: diagnostics.datachannel_states_summary ?? null,
802
- signaling_messages_sent_total: diagnostics.signaling_messages_sent_total ?? null,
803
- signaling_messages_received_total: diagnostics.signaling_messages_received_total ?? null,
804
- reconnect_attempts_total: diagnostics.reconnect_attempts_total ?? null,
805
- active_webrtc_peers: diagnostics.active_webrtc_peers ?? null,
806
- last_join_at: diagnostics.last_join_at ?? 0,
807
- last_poll_at: diagnostics.last_poll_at ?? 0,
808
- last_publish_at: diagnostics.last_publish_at ?? 0,
809
- last_peer_refresh_at: diagnostics.last_peer_refresh_at ?? 0,
810
- last_error_at: diagnostics.last_error_at ?? 0,
811
- last_error: diagnostics.last_error ?? null,
1149
+ started: this.networkStarted,
1150
+ startup_error: this.networkStartupError,
1151
+ signaling_url: network.signaling_url,
1152
+ signaling_endpoints: network.signaling_endpoints,
1153
+ room: network.room,
1154
+ bootstrap_sources: network.bootstrap_sources,
1155
+ seed_peers_count: network.seed_peers_count,
1156
+ discovery_events_total: diagnostics?.discovery_events_total ?? 0,
1157
+ last_discovery_event_at: diagnostics?.last_discovery_event_at ?? 0,
1158
+ connection_states_summary: diagnostics?.connection_states_summary ?? null,
1159
+ datachannel_states_summary: diagnostics?.datachannel_states_summary ?? null,
1160
+ signaling_messages_sent_total: diagnostics?.signaling_messages_sent_total ?? null,
1161
+ signaling_messages_received_total: diagnostics?.signaling_messages_received_total ?? null,
1162
+ reconnect_attempts_total: diagnostics?.reconnect_attempts_total ?? null,
1163
+ active_webrtc_peers: diagnostics?.active_webrtc_peers ?? null,
1164
+ last_join_at: diagnostics?.last_join_at ?? 0,
1165
+ last_poll_at: diagnostics?.last_poll_at ?? 0,
1166
+ last_publish_at: diagnostics?.last_publish_at ?? 0,
1167
+ last_peer_refresh_at: diagnostics?.last_peer_refresh_at ?? 0,
1168
+ last_error_at: diagnostics?.last_error_at ?? 0,
1169
+ last_error: diagnostics?.last_error ?? null,
812
1170
  }
813
1171
  : null,
814
1172
  };
815
1173
  }
816
1174
  getPeersSummary() {
817
- const diagnostics = this.getAdapterDiagnostics();
1175
+ const network = this.getResolvedRealtimeNetworkSummary();
1176
+ const diagnostics = network.diagnostics;
818
1177
  if (!diagnostics) {
819
1178
  return {
820
1179
  adapter: this.adapterMode,
@@ -837,11 +1196,13 @@ class LocalNodeService {
837
1196
  components: diagnostics.components,
838
1197
  limits: diagnostics.limits,
839
1198
  diagnostics_summary: {
840
- signaling_url: diagnostics.signaling_url ?? null,
841
- signaling_endpoints: diagnostics.signaling_endpoints ?? [],
842
- room: diagnostics.room ?? null,
843
- bootstrap_sources: diagnostics.bootstrap_sources ?? [],
844
- seed_peers_count: diagnostics.seed_peers_count ?? 0,
1199
+ started: this.networkStarted,
1200
+ startup_error: this.networkStartupError,
1201
+ signaling_url: network.signaling_url,
1202
+ signaling_endpoints: network.signaling_endpoints,
1203
+ room: network.room,
1204
+ bootstrap_sources: network.bootstrap_sources,
1205
+ seed_peers_count: network.seed_peers_count,
845
1206
  discovery_events_total: diagnostics.discovery_events_total ?? 0,
846
1207
  last_discovery_event_at: diagnostics.last_discovery_event_at ?? 0,
847
1208
  connection_states_summary: diagnostics.connection_states_summary ?? null,
@@ -885,14 +1246,100 @@ class LocalNodeService {
885
1246
  getRuntimePaths() {
886
1247
  return {
887
1248
  workspace_root: this.workspaceRoot,
1249
+ project_root: this.projectRoot,
888
1250
  storage_root: this.storageRoot,
889
1251
  data_dir: (0, path_1.resolve)(this.storageRoot, "data"),
890
1252
  social_runtime_path: (0, path_1.resolve)(this.storageRoot, ".silicaclaw", "social.runtime.json"),
891
1253
  local_console_public_dir: (0, path_1.resolve)(this.workspaceRoot, "apps", "local-console", "public"),
892
- social_lookup_paths: (0, core_1.getSocialConfigSearchPaths)(this.workspaceRoot),
1254
+ social_lookup_paths: (0, core_1.getSocialConfigSearchPaths)(this.projectRoot),
893
1255
  social_source_path: this.socialSourcePath,
894
1256
  };
895
1257
  }
1258
+ getAppUpdateStatus() {
1259
+ const currentVersion = normalizeVersionText(this.appVersion) || "unknown";
1260
+ const fallback = {
1261
+ current_version: currentVersion,
1262
+ latest_version: currentVersion,
1263
+ update_available: false,
1264
+ channel: "latest",
1265
+ platform: process.platform,
1266
+ checked_at: Date.now(),
1267
+ can_update: true,
1268
+ check_error: null,
1269
+ };
1270
+ try {
1271
+ const result = (0, child_process_1.spawnSync)("npm", ["view", "@silicaclaw/cli", "dist-tags", "--json"], {
1272
+ cwd: this.projectRoot,
1273
+ encoding: "utf8",
1274
+ env: {
1275
+ ...process.env,
1276
+ SILICACLAW_WORKSPACE_DIR: this.projectRoot,
1277
+ SILICACLAW_APP_DIR: this.workspaceRoot,
1278
+ npm_config_cache: process.env.npm_config_cache || userNpmCacheDir(),
1279
+ },
1280
+ });
1281
+ if ((result.status ?? 1) !== 0) {
1282
+ return {
1283
+ ...fallback,
1284
+ check_error: String(result.stderr || result.stdout || "npm view failed").trim() || "npm view failed",
1285
+ };
1286
+ }
1287
+ const tags = JSON.parse(String(result.stdout || "{}").trim() || "{}");
1288
+ const latestVersion = normalizeVersionText(tags.latest || currentVersion) || currentVersion;
1289
+ return {
1290
+ ...fallback,
1291
+ latest_version: latestVersion,
1292
+ update_available: compareVersionTokens(latestVersion, currentVersion) > 0,
1293
+ };
1294
+ }
1295
+ catch (error) {
1296
+ return {
1297
+ ...fallback,
1298
+ check_error: error instanceof Error ? error.message : String(error),
1299
+ };
1300
+ }
1301
+ }
1302
+ startAppUpdate() {
1303
+ const status = this.getAppUpdateStatus();
1304
+ if (!status.update_available || !status.latest_version) {
1305
+ return {
1306
+ started: false,
1307
+ target_version: status.latest_version || status.current_version,
1308
+ platform: process.platform,
1309
+ reason: status.check_error || "already_current",
1310
+ };
1311
+ }
1312
+ const shimPath = userShimPath();
1313
+ const scriptPath = (0, path_1.resolve)(this.workspaceRoot, "scripts", "silicaclaw-cli.mjs");
1314
+ const useShim = (0, fs_1.existsSync)(shimPath);
1315
+ if (!useShim && !(0, fs_1.existsSync)(scriptPath)) {
1316
+ return {
1317
+ started: false,
1318
+ target_version: status.latest_version,
1319
+ platform: process.platform,
1320
+ reason: "missing_cli_script",
1321
+ };
1322
+ }
1323
+ const command = useShim ? shimPath : process.execPath;
1324
+ const args = useShim ? ["update"] : [scriptPath, "update"];
1325
+ const child = (0, child_process_1.spawn)(command, args, {
1326
+ cwd: this.projectRoot,
1327
+ detached: true,
1328
+ stdio: "ignore",
1329
+ env: {
1330
+ ...process.env,
1331
+ SILICACLAW_WORKSPACE_DIR: this.projectRoot,
1332
+ SILICACLAW_APP_DIR: this.workspaceRoot,
1333
+ npm_config_cache: process.env.npm_config_cache || userNpmCacheDir(),
1334
+ },
1335
+ });
1336
+ child.unref();
1337
+ return {
1338
+ started: true,
1339
+ target_version: status.latest_version,
1340
+ platform: process.platform,
1341
+ };
1342
+ }
896
1343
  getIntegrationSummary() {
897
1344
  const status = this.getIntegrationStatus();
898
1345
  const runtimeGenerated = Boolean(this.socialRuntime && this.socialRuntime.last_loaded_at > 0);
@@ -925,9 +1372,10 @@ class LocalNodeService {
925
1372
  }
926
1373
  getIntegrationStatus() {
927
1374
  const runtimeGenerated = Boolean(this.socialRuntime && this.socialRuntime.last_loaded_at > 0);
928
- const connected = this.socialFound && runtimeGenerated && !this.socialParseError;
929
- const configured = connected && this.socialConfig.enabled;
930
- const running = configured && this.broadcastEnabled;
1375
+ const runtimeReady = this.socialFound && runtimeGenerated && !this.socialParseError;
1376
+ const connected = runtimeReady && this.networkStarted;
1377
+ const configured = runtimeReady && this.socialConfig.enabled;
1378
+ const running = configured && this.broadcastEnabled && this.networkStarted;
931
1379
  const publicEnabled = Boolean(this.profile?.public_enabled);
932
1380
  const discoveryEnabled = this.socialConfig.discovery.discoverable &&
933
1381
  this.socialConfig.discovery.allow_profile_broadcast &&
@@ -946,9 +1394,13 @@ class LocalNodeService {
946
1394
  ? "running"
947
1395
  : !configured
948
1396
  ? "not configured"
949
- : !this.broadcastEnabled
950
- ? "broadcast paused"
951
- : "not running";
1397
+ : this.networkReconnectTimer
1398
+ ? "reconnecting to relay"
1399
+ : this.networkStartupError
1400
+ ? this.networkStartupError
1401
+ : !this.broadcastEnabled
1402
+ ? "broadcast paused"
1403
+ : "not running";
952
1404
  const discoverableReason = discoverable
953
1405
  ? "discoverable"
954
1406
  : !running
@@ -987,20 +1439,32 @@ class LocalNodeService {
987
1439
  };
988
1440
  }
989
1441
  async setNetworkModeRuntime(mode) {
990
- const currentMode = this.networkMode;
1442
+ const before = {
1443
+ mode: this.networkMode,
1444
+ adapter: this.adapterMode,
1445
+ namespace: this.networkNamespace,
1446
+ port: this.networkPort,
1447
+ };
991
1448
  if (mode !== "local" && mode !== "lan" && mode !== "global-preview") {
992
1449
  throw new Error("invalid_network_mode");
993
1450
  }
994
1451
  this.socialConfig.network.mode = mode;
995
1452
  this.socialConfig.network.adapter = this.adapterForMode(mode);
996
1453
  this.applyResolvedNetworkConfig();
997
- this.socialNetworkRequiresRestart = currentMode !== mode || this.adapterMode !== this.socialConfig.network.adapter;
1454
+ const needsRestart = before.mode !== this.networkMode ||
1455
+ before.adapter !== this.socialConfig.network.adapter ||
1456
+ before.namespace !== this.networkNamespace ||
1457
+ (before.port ?? null) !== (this.networkPort ?? null);
1458
+ if (needsRestart) {
1459
+ await this.restartNetworkAdapter("set_network_mode_runtime");
1460
+ }
1461
+ this.socialNetworkRequiresRestart = false;
998
1462
  await this.writeSocialRuntime();
999
1463
  return {
1000
1464
  mode: this.networkMode,
1001
- adapter: this.socialConfig.network.adapter,
1002
- network_requires_restart: this.socialNetworkRequiresRestart,
1003
- note: "Runtime mode updated. Existing social.md is unchanged.",
1465
+ adapter: this.adapterMode,
1466
+ network_requires_restart: false,
1467
+ note: "Runtime mode updated and adapter restarted. Existing social.md is unchanged.",
1004
1468
  };
1005
1469
  }
1006
1470
  async quickConnectGlobalPreview(options) {
@@ -1013,7 +1477,7 @@ class LocalNodeService {
1013
1477
  this.socialConfig.network.adapter = "relay-preview";
1014
1478
  this.socialConfig.network.signaling_url = signalingUrl;
1015
1479
  this.socialConfig.network.signaling_urls = [signalingUrl];
1016
- this.socialConfig.network.room = room || "silicaclaw-global-preview";
1480
+ this.socialConfig.network.room = room || DEFAULT_GLOBAL_ROOM;
1017
1481
  this.applyResolvedNetworkConfig();
1018
1482
  await this.restartNetworkAdapter("quick_connect_global_preview");
1019
1483
  this.socialNetworkRequiresRestart = false;
@@ -1035,7 +1499,7 @@ class LocalNodeService {
1035
1499
  namespace: this.networkNamespace,
1036
1500
  port: this.networkPort,
1037
1501
  };
1038
- const loaded = (0, core_1.loadSocialConfig)(this.workspaceRoot);
1502
+ const loaded = (0, core_1.loadSocialConfig)(this.projectRoot);
1039
1503
  this.socialConfig = loaded.config;
1040
1504
  this.socialSourcePath = loaded.meta.source_path;
1041
1505
  this.socialFound = loaded.meta.found;
@@ -1054,11 +1518,15 @@ class LocalNodeService {
1054
1518
  before.adapter !== after.adapter ||
1055
1519
  before.namespace !== after.namespace ||
1056
1520
  (before.port ?? null) !== (after.port ?? null);
1521
+ if (this.socialNetworkRequiresRestart) {
1522
+ await this.restartNetworkAdapter("reload_social_config");
1523
+ this.socialNetworkRequiresRestart = false;
1524
+ }
1057
1525
  await this.writeSocialRuntime();
1058
1526
  return this.getSocialConfigView();
1059
1527
  }
1060
1528
  async generateDefaultSocialMd() {
1061
- const result = (0, core_1.ensureDefaultSocialMd)(this.workspaceRoot, {
1529
+ const result = (0, core_1.ensureDefaultSocialMd)(this.projectRoot, {
1062
1530
  display_name: this.getDefaultDisplayName(),
1063
1531
  bio: "Local AI agent connected to SilicaClaw",
1064
1532
  tags: ["openclaw", "local-first"],
@@ -1082,10 +1550,11 @@ class LocalNodeService {
1082
1550
  search(keyword) {
1083
1551
  this.ensureLocalDirectoryBaseline();
1084
1552
  this.compactCacheInMemory();
1085
- return (0, core_1.searchDirectory)(this.directory, keyword, { presenceTTLms: PRESENCE_TTL_MS }).map((profile) => {
1553
+ const directMatches = (0, core_1.searchDirectory)(this.directory, keyword, { presenceTTLms: PRESENCE_TTL_MS }).map((profile) => {
1086
1554
  const lastSeenAt = this.directory.presence[profile.agent_id] ?? 0;
1087
1555
  return this.toPublicProfileSummary(profile, { last_seen_at: lastSeenAt });
1088
1556
  });
1557
+ return this.mergeMessageOnlyAgentSummaries(directMatches, keyword);
1089
1558
  }
1090
1559
  getPublicProfilePreview() {
1091
1560
  if (!this.profile) {
@@ -1127,6 +1596,7 @@ class LocalNodeService {
1127
1596
  return {
1128
1597
  ...message,
1129
1598
  display_name: profile?.display_name || message.display_name || "Unnamed",
1599
+ avatar_url: profile?.avatar_url || "",
1130
1600
  is_self: message.agent_id === this.identity?.agent_id,
1131
1601
  online: (0, core_1.isAgentOnline)(lastSeenAt, Date.now(), PRESENCE_TTL_MS),
1132
1602
  last_seen_at: lastSeenAt || null,
@@ -1145,18 +1615,123 @@ class LocalNodeService {
1145
1615
  },
1146
1616
  };
1147
1617
  }
1618
+ getPrivateMessagingState() {
1619
+ return {
1620
+ enabled: Boolean(this.identity && this.privateEncryptionKeyPair),
1621
+ agent_id: this.identity?.agent_id || "",
1622
+ encryption_public_key: this.privateEncryptionKeyPair?.public_key || "",
1623
+ conversation_count: this.getPrivateConversations().length,
1624
+ message_count: this.privateMessages.length,
1625
+ };
1626
+ }
1627
+ getPrivateConversations() {
1628
+ const conversations = new Map();
1629
+ for (const message of this.privateMessages) {
1630
+ if (message.from_agent_id === message.to_agent_id) {
1631
+ continue;
1632
+ }
1633
+ const peerAgentId = message.from_agent_id === this.identity?.agent_id ? message.to_agent_id : message.from_agent_id;
1634
+ if (!peerAgentId || peerAgentId === this.identity?.agent_id) {
1635
+ continue;
1636
+ }
1637
+ const peerProfile = this.directory.profiles[peerAgentId];
1638
+ const current = conversations.get(message.conversation_id);
1639
+ const nextLast = Math.max(current?.last_message_at || 0, message.created_at || 0) || null;
1640
+ conversations.set(message.conversation_id, {
1641
+ conversation_id: message.conversation_id,
1642
+ peer_agent_id: peerAgentId,
1643
+ peer_display_name: peerProfile?.display_name || peerAgentId,
1644
+ peer_avatar_url: peerProfile?.avatar_url || "",
1645
+ peer_public_key: peerProfile?.private_encryption_public_key || "",
1646
+ last_message_at: nextLast,
1647
+ unread_count: current?.unread_count || 0,
1648
+ });
1649
+ }
1650
+ return Array.from(conversations.values()).sort((a, b) => (b.last_message_at || 0) - (a.last_message_at || 0));
1651
+ }
1652
+ getPrivateMessages(conversationId, limit = PRIVATE_MESSAGE_QUERY_LIMIT) {
1653
+ const normalizedConversationId = String(conversationId || "").trim();
1654
+ const resolvedLimit = Math.max(1, Math.min(PRIVATE_MESSAGE_QUERY_LIMIT, Number(limit) || PRIVATE_MESSAGE_QUERY_LIMIT));
1655
+ const receiptsByMessageId = new Map(this.privateMessageReceipts.map((receipt) => [receipt.message_id, receipt.status]));
1656
+ return this.privateMessages
1657
+ .filter((message) => {
1658
+ if (message.from_agent_id === message.to_agent_id) {
1659
+ return false;
1660
+ }
1661
+ const peerAgentId = message.from_agent_id === this.identity?.agent_id ? message.to_agent_id : message.from_agent_id;
1662
+ if (!peerAgentId || peerAgentId === this.identity?.agent_id) {
1663
+ return false;
1664
+ }
1665
+ return !normalizedConversationId || message.conversation_id === normalizedConversationId;
1666
+ })
1667
+ .sort((a, b) => a.created_at - b.created_at)
1668
+ .slice(-resolvedLimit)
1669
+ .map((message) => ({
1670
+ message_id: message.message_id,
1671
+ conversation_id: message.conversation_id,
1672
+ from_agent_id: message.from_agent_id,
1673
+ to_agent_id: message.to_agent_id,
1674
+ body: this.decryptPrivateMessageBody(message),
1675
+ created_at: message.created_at,
1676
+ is_self: message.from_agent_id === this.identity?.agent_id,
1677
+ delivery_status: receiptsByMessageId.get(message.message_id) || "sent",
1678
+ }));
1679
+ }
1680
+ async sendPrivateMessage(input) {
1681
+ if (!this.identity || !this.privateEncryptionKeyPair) {
1682
+ return { sent: false, reason: "missing_identity_or_private_key" };
1683
+ }
1684
+ const toAgentId = String(input.to_agent_id || "").trim();
1685
+ const recipientKey = String(input.recipient_encryption_public_key || "").trim();
1686
+ const body = String(input.body || "").trim();
1687
+ if (toAgentId === this.identity.agent_id) {
1688
+ return { sent: false, reason: "self_private_message_not_allowed" };
1689
+ }
1690
+ const toPeerId = this.privatePeerRoutes[toAgentId] || "";
1691
+ if (!toAgentId || !toPeerId || !recipientKey || !body) {
1692
+ return { sent: false, reason: "invalid_private_message_input" };
1693
+ }
1694
+ if (typeof this.network.sendDirect !== "function") {
1695
+ return { sent: false, reason: "direct_delivery_not_supported" };
1696
+ }
1697
+ const encrypted = (0, core_1.encryptPrivatePayload)({
1698
+ plaintext: body,
1699
+ recipient_public_key: recipientKey,
1700
+ sender_keypair: this.privateEncryptionKeyPair,
1701
+ });
1702
+ const message = (0, core_1.signPrivateMessage)({
1703
+ identity: this.identity,
1704
+ message_id: (0, crypto_1.createHash)("sha256").update(`${this.identity.agent_id}:${toAgentId}:${Date.now()}:${body}:${Math.random()}`, "utf8").digest("hex"),
1705
+ conversation_id: this.buildPrivateConversationId(this.identity.agent_id, toAgentId),
1706
+ to_agent_id: toAgentId,
1707
+ sender_encryption_public_key: encrypted.sender_encryption_public_key,
1708
+ recipient_encryption_public_key: recipientKey,
1709
+ ciphertext: encrypted.ciphertext,
1710
+ nonce: encrypted.nonce,
1711
+ created_at: Date.now(),
1712
+ });
1713
+ this.ingestPrivateMessage(message);
1714
+ await this.persistPrivateMessages();
1715
+ await this.network.sendDirect(toPeerId, PRIVATE_MESSAGE_TOPIC, message);
1716
+ const view = this.getPrivateMessages(message.conversation_id).find((item) => item.message_id === message.message_id);
1717
+ return { sent: true, reason: "sent", message: view };
1718
+ }
1148
1719
  getOpenClawBridgeStatus() {
1720
+ const now = Date.now();
1721
+ if (this.openclawBridgeStatusCache && this.openclawBridgeStatusCache.expiresAt > now) {
1722
+ return this.openclawBridgeStatusCache.value;
1723
+ }
1149
1724
  const integration = this.getIntegrationStatus();
1150
- const openclawInstallation = detectOpenClawInstallation(this.workspaceRoot);
1151
- const openclawRuntime = detectOpenClawRuntime(this.workspaceRoot);
1725
+ const openclawInstallation = detectOpenClawInstallation(this.projectRoot);
1726
+ const openclawRuntime = this.getCachedOpenClawRuntime();
1152
1727
  const skillInstallation = detectOpenClawSkillInstallation();
1153
1728
  const ownerDelivery = detectOwnerDeliveryStatus({
1154
- workspaceRoot: this.workspaceRoot,
1729
+ workspaceRoot: this.projectRoot,
1155
1730
  connectedToSilicaclaw: integration.connected_to_silicaclaw,
1156
1731
  openclawRunning: openclawRuntime.running,
1157
1732
  skillInstalled: skillInstallation.installed,
1158
1733
  });
1159
- return {
1734
+ const value = {
1160
1735
  enabled: this.socialConfig.enabled,
1161
1736
  connected_to_silicaclaw: integration.connected_to_silicaclaw,
1162
1737
  public_enabled: integration.public_enabled,
@@ -1212,15 +1787,25 @@ class LocalNodeService {
1212
1787
  install_skill: "/api/openclaw/bridge/skill-install",
1213
1788
  },
1214
1789
  };
1790
+ this.openclawBridgeStatusCache = {
1791
+ value,
1792
+ expiresAt: now + OPENCLAW_BRIDGE_STATUS_CACHE_MS,
1793
+ };
1794
+ return value;
1215
1795
  }
1216
- async installOpenClawSkill() {
1796
+ async installOpenClawSkill(skillName) {
1217
1797
  const scriptPath = (0, path_1.resolve)(this.workspaceRoot, "scripts", "install-openclaw-skill.mjs");
1218
- const { stdout } = await execFileAsync(process.execPath, [scriptPath], {
1798
+ const args = [scriptPath];
1799
+ if (skillName) {
1800
+ args.push(`--skill=${skillName}`);
1801
+ }
1802
+ const { stdout } = await execFileAsync(process.execPath, args, {
1219
1803
  cwd: this.workspaceRoot,
1220
- env: process.env,
1804
+ env: { ...process.env, SILICACLAW_WORKSPACE_DIR: this.projectRoot },
1221
1805
  maxBuffer: 1024 * 1024,
1222
1806
  });
1223
1807
  const parsed = JSON.parse(String(stdout || "{}"));
1808
+ this.invalidateOpenClawCaches();
1224
1809
  return {
1225
1810
  ...parsed,
1226
1811
  bridge: this.getOpenClawBridgeStatus(),
@@ -1239,11 +1824,11 @@ class LocalNodeService {
1239
1824
  const homeDir = (0, path_1.resolve)(process.env.HOME || "", ".openclaw");
1240
1825
  const workspaceSkillDir = (0, path_1.resolve)(homeDir, "workspace", "skills");
1241
1826
  const legacySkillDir = (0, path_1.resolve)(homeDir, "skills");
1242
- const openclawSourceDir = (0, path_1.resolve)(this.workspaceRoot, "..", "openclaw");
1243
- const openclawRuntime = detectOpenClawRuntime(this.workspaceRoot);
1827
+ const openclawSourceDir = defaultOpenClawSourceDir(this.projectRoot);
1828
+ const openclawRuntime = this.getCachedOpenClawRuntime();
1244
1829
  return {
1245
- bridge_api_base: "http://localhost:4310",
1246
- openclaw_detected: detectOpenClawInstallation(this.workspaceRoot).detected,
1830
+ bridge_api_base: DEFAULT_BRIDGE_API_BASE,
1831
+ openclaw_detected: detectOpenClawInstallation(this.projectRoot).detected,
1247
1832
  openclaw_running: openclawRuntime.running,
1248
1833
  openclaw_gateway_host: OPENCLAW_GATEWAY_HOST,
1249
1834
  openclaw_gateway_port: openclawRuntime.configured_gateway_port,
@@ -1252,7 +1837,7 @@ class LocalNodeService {
1252
1837
  openclaw_workspace_skill_dir: workspaceSkillDir,
1253
1838
  openclaw_legacy_skill_dir: legacySkillDir,
1254
1839
  silicaclaw_env_template_path: (0, path_1.resolve)(this.workspaceRoot, "openclaw-owner-forward.env.example"),
1255
- recommended_skill_name: "silicaclaw-broadcast",
1840
+ recommended_skill_name: "silicaclaw-bridge-setup",
1256
1841
  recommended_install_command: "silicaclaw openclaw-skill-install",
1257
1842
  recommended_owner_forward_env: {
1258
1843
  OPENCLAW_SOURCE_DIR: openclawSourceDir,
@@ -1270,6 +1855,7 @@ class LocalNodeService {
1270
1855
  ].join(" "),
1271
1856
  notes: [
1272
1857
  "Install and maintain the skill from SilicaClaw; do not edit OpenClaw core source for this integration.",
1858
+ "Use silicaclaw-bridge-setup first when OpenClaw still needs local install, readiness checks, or troubleshooting guidance.",
1273
1859
  "OpenClaw learns broadcasts via the installed skill under ~/.openclaw/workspace/skills/.",
1274
1860
  "Runtime detection prefers the actual OpenClaw gateway listener port, then falls back to OpenClaw's own openclaw.json gateway.port.",
1275
1861
  "Owner delivery runs through OpenClaw's own message channel stack after the skill forwards a summary.",
@@ -1288,6 +1874,12 @@ class LocalNodeService {
1288
1874
  const skillPath = (0, path_1.resolve)(dir.path, "SKILL.md");
1289
1875
  const versionPath = (0, path_1.resolve)(dir.path, "VERSION");
1290
1876
  const manifest = readJsonFileSafe(manifestPath);
1877
+ const references = (manifest?.references && typeof manifest.references === "object")
1878
+ ? manifest.references
1879
+ : null;
1880
+ const ownerDialogueCheatsheetPath = references?.owner_dialogue_cheatsheet_zh
1881
+ ? (0, path_1.resolve)(dir.path, String(references.owner_dialogue_cheatsheet_zh))
1882
+ : null;
1291
1883
  const name = String(manifest?.name || dir.name);
1292
1884
  const capabilities = Array.isArray(manifest?.capabilities)
1293
1885
  ? manifest.capabilities.map((item) => String(item))
@@ -1307,6 +1899,9 @@ class LocalNodeService {
1307
1899
  skill_path: (0, fs_1.existsSync)(skillPath) ? skillPath : null,
1308
1900
  capabilities,
1309
1901
  transport: manifest?.transport || null,
1902
+ owner_dialogue_cheatsheet_path: ownerDialogueCheatsheetPath && (0, fs_1.existsSync)(ownerDialogueCheatsheetPath) ? ownerDialogueCheatsheetPath : null,
1903
+ owner_dialogue_examples_zh: ownerDialogueCheatsheetPath ? readDialogueCheatsheetPreview(ownerDialogueCheatsheetPath) : [],
1904
+ owner_dialogue_sections_zh: ownerDialogueCheatsheetPath ? readDialogueCheatsheetSections(ownerDialogueCheatsheetPath) : [],
1310
1905
  installed_in_openclaw: installedInWorkspace || installedInLegacy,
1311
1906
  install_mode: installedInWorkspace ? "workspace" : installedInLegacy ? "legacy" : "not_installed",
1312
1907
  installed_path: installedInWorkspace ? installedWorkspacePath : installedInLegacy ? installedLegacyPath : null,
@@ -1320,6 +1915,12 @@ class LocalNodeService {
1320
1915
  const skillPath = (0, path_1.resolve)(dir.path, "SKILL.md");
1321
1916
  const versionPath = (0, path_1.resolve)(dir.path, "VERSION");
1322
1917
  const manifest = readJsonFileSafe(manifestPath);
1918
+ const references = (manifest?.references && typeof manifest.references === "object")
1919
+ ? manifest.references
1920
+ : null;
1921
+ const ownerDialogueCheatsheetPath = references?.owner_dialogue_cheatsheet_zh
1922
+ ? (0, path_1.resolve)(dir.path, String(references.owner_dialogue_cheatsheet_zh))
1923
+ : null;
1323
1924
  return {
1324
1925
  key: `${dir.install_mode}:${dir.name}`,
1325
1926
  name: String(manifest?.name || dir.name),
@@ -1331,9 +1932,37 @@ class LocalNodeService {
1331
1932
  manifest_path: (0, fs_1.existsSync)(manifestPath) ? manifestPath : null,
1332
1933
  skill_path: (0, fs_1.existsSync)(skillPath) ? skillPath : null,
1333
1934
  capabilities: Array.isArray(manifest?.capabilities) ? manifest.capabilities.map((item) => String(item)) : [],
1935
+ owner_dialogue_cheatsheet_path: ownerDialogueCheatsheetPath && (0, fs_1.existsSync)(ownerDialogueCheatsheetPath) ? ownerDialogueCheatsheetPath : null,
1936
+ owner_dialogue_examples_zh: ownerDialogueCheatsheetPath ? readDialogueCheatsheetPreview(ownerDialogueCheatsheetPath) : [],
1937
+ owner_dialogue_sections_zh: ownerDialogueCheatsheetPath ? readDialogueCheatsheetSections(ownerDialogueCheatsheetPath) : [],
1334
1938
  bundled_source_path: bundledSkills.find((item) => item.name === String(manifest?.name || dir.name))?.source_path || null,
1335
1939
  };
1336
1940
  });
1941
+ const installedSkillVersions = new Map(installedSkills.map((item) => [item.name, item.version]));
1942
+ const bundledSkillsWithUpdateState = bundledSkills.map((skill) => {
1943
+ const installedVersion = installedSkillVersions.get(skill.name) || "";
1944
+ const updateAvailable = Boolean(skill.installed_in_openclaw &&
1945
+ installedVersion &&
1946
+ skill.version &&
1947
+ compareVersionTokens(installedVersion, skill.version) < 0);
1948
+ return {
1949
+ ...skill,
1950
+ installed_version: installedVersion || null,
1951
+ update_available: updateAvailable,
1952
+ };
1953
+ });
1954
+ const bundledSkillVersions = new Map(bundledSkillsWithUpdateState.map((item) => [item.name, item.version]));
1955
+ const installedSkillsWithUpdateState = installedSkills.map((skill) => {
1956
+ const bundledVersion = bundledSkillVersions.get(skill.name) || "";
1957
+ const updateAvailable = Boolean(bundledVersion &&
1958
+ skill.version &&
1959
+ compareVersionTokens(skill.version, bundledVersion) < 0);
1960
+ return {
1961
+ ...skill,
1962
+ bundled_version: bundledVersion || null,
1963
+ update_available: updateAvailable,
1964
+ };
1965
+ });
1337
1966
  return {
1338
1967
  openclaw: {
1339
1968
  detected: bridge.openclaw_installation.detected,
@@ -1344,13 +1973,14 @@ class LocalNodeService {
1344
1973
  legacy_install_root: legacyInstallRoot,
1345
1974
  },
1346
1975
  summary: {
1347
- bundled_count: bundledSkills.length,
1348
- installed_count: installedSkills.length,
1349
- installed_bundled_count: bundledSkills.filter((item) => item.installed_in_openclaw).length,
1976
+ bundled_count: bundledSkillsWithUpdateState.length,
1977
+ installed_count: installedSkillsWithUpdateState.length,
1978
+ installed_bundled_count: bundledSkillsWithUpdateState.filter((item) => item.installed_in_openclaw).length,
1979
+ update_available_count: bundledSkillsWithUpdateState.filter((item) => item.update_available).length,
1350
1980
  },
1351
1981
  install_action: bridge.skill_learning.install_action,
1352
- bundled_skills: bundledSkills,
1353
- installed_skills: installedSkills,
1982
+ bundled_skills: bundledSkillsWithUpdateState,
1983
+ installed_skills: installedSkillsWithUpdateState,
1354
1984
  };
1355
1985
  }
1356
1986
  getRuntimeMessageGovernance() {
@@ -1568,12 +2198,15 @@ class LocalNodeService {
1568
2198
  profile: this.profile,
1569
2199
  };
1570
2200
  const presenceRecord = (0, core_1.signPresence)(this.identity, Date.now());
1571
- const indexRecords = (0, core_1.buildIndexRecords)(this.profile);
2201
+ const shouldPublishProfile = this.shouldPublishProfileRecord(profileRecord, reason, presenceRecord.timestamp);
2202
+ const replayMessages = this.getReplayableSelfSocialMessages(reason);
1572
2203
  try {
1573
- await this.publish("profile", profileRecord);
2204
+ if (shouldPublishProfile) {
2205
+ await this.publish("profile", profileRecord);
2206
+ }
1574
2207
  await this.publish("presence", presenceRecord);
1575
- for (const record of indexRecords) {
1576
- await this.publish("index", record);
2208
+ for (const message of replayMessages) {
2209
+ await this.publish(SOCIAL_MESSAGE_TOPIC, message);
1577
2210
  }
1578
2211
  }
1579
2212
  catch (error) {
@@ -1581,23 +2214,67 @@ class LocalNodeService {
1581
2214
  this.lastBroadcastErrorAt = Date.now();
1582
2215
  this.lastBroadcastError = message;
1583
2216
  this.broadcastFailureCount += 1;
2217
+ this.consecutiveBroadcastFailures += 1;
1584
2218
  await this.log("error", `Broadcast failed (reason=${reason}): ${message}`);
2219
+ await this.maybeRecoverFromBroadcastFailure(reason, message);
1585
2220
  return { sent: false, reason: "publish_failed", error: message };
1586
2221
  }
1587
2222
  this.lastBroadcastAt = Date.now();
1588
2223
  this.broadcastCount += 1;
1589
2224
  this.lastBroadcastError = null;
1590
2225
  this.lastBroadcastErrorAt = 0;
2226
+ this.consecutiveBroadcastFailures = 0;
1591
2227
  this.directory = (0, core_1.ingestProfileRecord)(this.directory, profileRecord);
1592
2228
  this.directory = (0, core_1.ingestPresenceRecord)(this.directory, presenceRecord);
1593
- for (const record of indexRecords) {
1594
- this.directory = (0, core_1.ingestIndexRecord)(this.directory, record);
1595
- }
1596
2229
  this.compactCacheInMemory();
1597
2230
  await this.persistCache();
1598
- await this.log("info", `Broadcast sent (${indexRecords.length} index refs, reason=${reason})`);
2231
+ await this.log("info", `Broadcast sent (${shouldPublishProfile ? "profile + " : ""}presence, replayed_messages=${replayMessages.length}, reason=${reason})`);
1599
2232
  return { sent: true, reason };
1600
2233
  }
2234
+ shouldPublishProfileRecord(profileRecord, reason, now = Date.now()) {
2235
+ if (reason !== "interval") {
2236
+ this.lastProfileBroadcastSignature = profileRecord.profile.signature;
2237
+ this.lastProfileBroadcastAt = now;
2238
+ return true;
2239
+ }
2240
+ const signature = profileRecord.profile.signature;
2241
+ const changedSinceLastPublish = signature !== this.lastProfileBroadcastSignature;
2242
+ const refreshDue = now - this.lastProfileBroadcastAt >= PROFILE_RELAY_REFRESH_INTERVAL_MS;
2243
+ if (!changedSinceLastPublish && !refreshDue) {
2244
+ return false;
2245
+ }
2246
+ this.lastProfileBroadcastSignature = signature;
2247
+ this.lastProfileBroadcastAt = now;
2248
+ return true;
2249
+ }
2250
+ async maybeRecoverFromBroadcastFailure(reason, errorMessage) {
2251
+ const recoveryThreshold = 3;
2252
+ const recoveryCooldownMs = 60_000;
2253
+ if (this.broadcastRecoveryInFlight) {
2254
+ return;
2255
+ }
2256
+ if (this.consecutiveBroadcastFailures < recoveryThreshold) {
2257
+ return;
2258
+ }
2259
+ if (Date.now() - this.lastBroadcastRecoveryAttemptAt < recoveryCooldownMs) {
2260
+ return;
2261
+ }
2262
+ if (this.adapterMode !== "relay-preview" && this.adapterMode !== "webrtc-preview" && this.adapterMode !== "real-preview") {
2263
+ return;
2264
+ }
2265
+ this.broadcastRecoveryInFlight = true;
2266
+ this.lastBroadcastRecoveryAttemptAt = Date.now();
2267
+ try {
2268
+ await this.log("warn", `Broadcast recovery triggered after ${this.consecutiveBroadcastFailures} consecutive failures (${reason}): ${errorMessage}`);
2269
+ await this.restartNetworkAdapter("broadcast_failure_recovery");
2270
+ }
2271
+ catch (recoveryError) {
2272
+ await this.log("error", `Broadcast recovery failed: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`);
2273
+ }
2274
+ finally {
2275
+ this.broadcastRecoveryInFlight = false;
2276
+ }
2277
+ }
1601
2278
  async hydrateFromDisk() {
1602
2279
  this.initState = {
1603
2280
  identity_auto_created: false,
@@ -1613,7 +2290,7 @@ class LocalNodeService {
1613
2290
  socialConfig: this.socialConfig,
1614
2291
  existingIdentity,
1615
2292
  generatedIdentity: (0, core_1.createIdentity)(),
1616
- rootDir: this.workspaceRoot,
2293
+ rootDir: this.projectRoot,
1617
2294
  });
1618
2295
  this.identity = resolvedIdentity.identity;
1619
2296
  this.resolvedIdentitySource = resolvedIdentity.source;
@@ -1626,26 +2303,33 @@ class LocalNodeService {
1626
2303
  await this.log("info", `Bound existing OpenClaw identity: ${resolvedIdentity.openclaw_source_path}`);
1627
2304
  }
1628
2305
  await this.identityRepo.set(this.identity);
2306
+ this.privateEncryptionKeyPair = (await this.privateEncryptionKeyRepo.get()) || (0, core_1.createPrivateEncryptionKeyPair)();
2307
+ await this.privateEncryptionKeyRepo.set(this.privateEncryptionKeyPair);
1629
2308
  const existingProfile = await this.profileRepo.get();
1630
2309
  const profileInput = (0, core_1.resolveProfileInputWithSocial)({
1631
2310
  socialConfig: this.socialConfig,
1632
2311
  agentId: this.identity.agent_id,
1633
2312
  existingProfile: existingProfile && existingProfile.agent_id === this.identity.agent_id ? existingProfile : null,
1634
- rootDir: this.workspaceRoot,
2313
+ rootDir: this.projectRoot,
1635
2314
  });
1636
- this.profile = (0, core_1.signProfile)(profileInput, this.identity);
2315
+ this.profile = (0, core_1.signProfile)({
2316
+ ...profileInput,
2317
+ private_encryption_public_key: this.privateEncryptionKeyPair?.public_key || profileInput.private_encryption_public_key || "",
2318
+ }, this.identity);
1637
2319
  if (!existingProfile || existingProfile.agent_id !== this.identity.agent_id) {
1638
2320
  this.initState.profile_auto_created = true;
1639
2321
  await this.log("info", "profile.json missing/invalid, initialized from social/default profile");
1640
2322
  }
1641
2323
  await this.profileRepo.set(this.profile);
1642
- this.directory = (0, core_1.dedupeIndex)(await this.cacheRepo.get());
2324
+ this.directory = (0, core_1.createEmptyDirectoryState)();
1643
2325
  this.messageGovernance = {
1644
2326
  ...this.defaultMessageGovernance(),
1645
2327
  ...(await this.socialMessageGovernanceRepo.get()),
1646
2328
  };
1647
2329
  this.socialMessages = this.normalizeSocialMessages(await this.socialMessageRepo.get());
1648
2330
  this.socialMessageObservations = this.normalizeSocialMessageObservations(await this.socialMessageObservationRepo.get());
2331
+ this.privateMessages = this.normalizePrivateMessages(await this.privateMessageRepo.get());
2332
+ this.privateMessageReceipts = this.normalizePrivateMessageReceipts(await this.privateMessageReceiptRepo.get());
1649
2333
  this.directory = (0, core_1.ingestProfileRecord)(this.directory, { type: "profile", profile: this.profile });
1650
2334
  this.compactCacheInMemory();
1651
2335
  await this.persistCache();
@@ -1660,9 +2344,12 @@ class LocalNodeService {
1660
2344
  socialConfig: this.socialConfig,
1661
2345
  agentId: this.identity.agent_id,
1662
2346
  existingProfile: this.profile,
1663
- rootDir: this.workspaceRoot,
2347
+ rootDir: this.projectRoot,
1664
2348
  });
1665
- const nextProfile = (0, core_1.signProfile)(nextProfileInput, this.identity);
2349
+ const nextProfile = (0, core_1.signProfile)({
2350
+ ...nextProfileInput,
2351
+ private_encryption_public_key: this.privateEncryptionKeyPair?.public_key || nextProfileInput.private_encryption_public_key || "",
2352
+ }, this.identity);
1666
2353
  this.profile = nextProfile;
1667
2354
  await this.profileRepo.set(nextProfile);
1668
2355
  this.directory = (0, core_1.ingestProfileRecord)(this.directory, { type: "profile", profile: nextProfile });
@@ -1720,7 +2407,7 @@ class LocalNodeService {
1720
2407
  this.socialRuntime = runtime;
1721
2408
  await this.socialRuntimeRepo.set(runtime);
1722
2409
  }
1723
- async onMessage(topic, data) {
2410
+ async onMessage(topic, data, meta) {
1724
2411
  this.receivedCount += 1;
1725
2412
  this.receivedByTopic[topic] = (this.receivedByTopic[topic] ?? 0) + 1;
1726
2413
  this.lastMessageAt = Date.now();
@@ -1735,6 +2422,9 @@ class LocalNodeService {
1735
2422
  return;
1736
2423
  }
1737
2424
  }
2425
+ if (meta?.peerId && record.profile.agent_id) {
2426
+ this.privatePeerRoutes[record.profile.agent_id] = meta.peerId;
2427
+ }
1738
2428
  this.directory = (0, core_1.ingestProfileRecord)(this.directory, record);
1739
2429
  this.compactCacheInMemory();
1740
2430
  await this.persistCache();
@@ -1751,6 +2441,9 @@ class LocalNodeService {
1751
2441
  return;
1752
2442
  }
1753
2443
  }
2444
+ if (meta?.peerId && record.agent_id) {
2445
+ this.privatePeerRoutes[record.agent_id] = meta.peerId;
2446
+ }
1754
2447
  this.directory = (0, core_1.ingestPresenceRecord)(this.directory, record);
1755
2448
  this.compactCacheInMemory();
1756
2449
  await this.persistCache();
@@ -1765,6 +2458,13 @@ class LocalNodeService {
1765
2458
  await this.log("warn", `Rejected social message with invalid signature (${record.message_id.slice(0, 10)})`);
1766
2459
  return;
1767
2460
  }
2461
+ if (meta?.peerId && record.agent_id) {
2462
+ this.privatePeerRoutes[record.agent_id] = meta.peerId;
2463
+ }
2464
+ if (this.hasSocialMessage(record.message_id)) {
2465
+ await this.publishObservationForMessage(record);
2466
+ return;
2467
+ }
1768
2468
  const governanceReason = this.getIncomingSocialMessageRejectionReason(record);
1769
2469
  if (governanceReason) {
1770
2470
  await this.log("warn", `Rejected social message (${record.message_id.slice(0, 10)}): ${governanceReason}`);
@@ -1796,11 +2496,35 @@ class LocalNodeService {
1796
2496
  this.directory = (0, core_1.dedupeIndex)(this.directory);
1797
2497
  await this.persistCache();
1798
2498
  }
2499
+ async onDirectMessage(topic, data, meta) {
2500
+ if (topic === PRIVATE_MESSAGE_TOPIC) {
2501
+ const record = this.normalizeIncomingPrivateMessage(data);
2502
+ if (!record || !(0, core_1.verifyPrivateMessage)(record)) {
2503
+ return;
2504
+ }
2505
+ if (record.to_agent_id !== this.identity?.agent_id || this.hasPrivateMessage(record.message_id)) {
2506
+ return;
2507
+ }
2508
+ this.ingestPrivateMessage(record);
2509
+ await this.persistPrivateMessages();
2510
+ await this.sendPrivateMessageReceipt(record, meta?.peerId);
2511
+ return;
2512
+ }
2513
+ const receipt = this.normalizeIncomingPrivateMessageReceipt(data);
2514
+ if (!receipt || !(0, core_1.verifyPrivateMessageReceipt)(receipt)) {
2515
+ return;
2516
+ }
2517
+ if (receipt.to_agent_id !== this.identity?.agent_id) {
2518
+ return;
2519
+ }
2520
+ this.ingestPrivateMessageReceipt(receipt);
2521
+ await this.persistPrivateMessageReceipts();
2522
+ }
1799
2523
  startBroadcastLoop() {
1800
2524
  if (this.broadcaster) {
1801
2525
  clearInterval(this.broadcaster);
1802
2526
  }
1803
- if (!this.broadcastEnabled) {
2527
+ if (!this.broadcastEnabled || !this.networkStarted) {
1804
2528
  return;
1805
2529
  }
1806
2530
  this.broadcaster = setInterval(async () => {
@@ -1816,21 +2540,29 @@ class LocalNodeService {
1816
2540
  if (this.subscriptionsBound) {
1817
2541
  return;
1818
2542
  }
1819
- this.network.subscribe("profile", (data) => {
1820
- this.onMessage("profile", data);
2543
+ this.network.subscribe("profile", (data, meta) => {
2544
+ this.onMessage("profile", data, meta);
1821
2545
  });
1822
- this.network.subscribe("presence", (data) => {
1823
- this.onMessage("presence", data);
2546
+ this.network.subscribe("presence", (data, meta) => {
2547
+ this.onMessage("presence", data, meta);
1824
2548
  });
1825
- this.network.subscribe("index", (data) => {
1826
- this.onMessage("index", data);
2549
+ this.network.subscribe("index", (data, meta) => {
2550
+ this.onMessage("index", data, meta);
1827
2551
  });
1828
- this.network.subscribe(SOCIAL_MESSAGE_TOPIC, (data) => {
1829
- this.onMessage(SOCIAL_MESSAGE_TOPIC, data);
2552
+ this.network.subscribe(SOCIAL_MESSAGE_TOPIC, (data, meta) => {
2553
+ this.onMessage(SOCIAL_MESSAGE_TOPIC, data, meta);
1830
2554
  });
1831
- this.network.subscribe(SOCIAL_MESSAGE_OBSERVATION_TOPIC, (data) => {
1832
- this.onMessage(SOCIAL_MESSAGE_OBSERVATION_TOPIC, data);
2555
+ this.network.subscribe(SOCIAL_MESSAGE_OBSERVATION_TOPIC, (data, meta) => {
2556
+ this.onMessage(SOCIAL_MESSAGE_OBSERVATION_TOPIC, data, meta);
1833
2557
  });
2558
+ if (typeof this.network.subscribeDirect === "function") {
2559
+ this.network.subscribeDirect(PRIVATE_MESSAGE_TOPIC, (data, meta) => {
2560
+ this.onDirectMessage(PRIVATE_MESSAGE_TOPIC, data, meta);
2561
+ });
2562
+ this.network.subscribeDirect(PRIVATE_MESSAGE_RECEIPT_TOPIC, (data, meta) => {
2563
+ this.onDirectMessage(PRIVATE_MESSAGE_RECEIPT_TOPIC, data, meta);
2564
+ });
2565
+ }
1834
2566
  this.subscriptionsBound = true;
1835
2567
  }
1836
2568
  buildNetworkAdapter() {
@@ -1912,6 +2644,7 @@ class LocalNodeService {
1912
2644
  };
1913
2645
  }
1914
2646
  async restartNetworkAdapter(reason) {
2647
+ this.clearNetworkReconnectTimer();
1915
2648
  const previous = this.network;
1916
2649
  try {
1917
2650
  await previous.stop();
@@ -1923,16 +2656,104 @@ class LocalNodeService {
1923
2656
  this.network = next.adapter;
1924
2657
  this.adapterMode = next.mode;
1925
2658
  this.networkPort = next.port;
1926
- await this.network.start();
2659
+ this.subscriptionsBound = false;
1927
2660
  this.bindNetworkSubscriptions();
1928
- this.startBroadcastLoop();
1929
- if (this.broadcastEnabled && this.profile?.public_enabled) {
1930
- await this.broadcastNow("adapter_restart");
2661
+ await this.startNetworkAdapterWithRetry(reason);
2662
+ }
2663
+ clearNetworkReconnectTimer() {
2664
+ if (this.networkReconnectTimer) {
2665
+ clearTimeout(this.networkReconnectTimer);
2666
+ this.networkReconnectTimer = null;
2667
+ }
2668
+ }
2669
+ async startNetworkAdapterWithRetry(reason) {
2670
+ this.clearNetworkReconnectTimer();
2671
+ try {
2672
+ await this.network.start();
2673
+ this.networkStarted = true;
2674
+ this.networkStartupError = null;
2675
+ this.networkReconnectDelayMs = 5_000;
2676
+ await this.log("info", `Local node started (${this.adapterMode}, mode=${this.networkMode}, signaling=${this.webrtcSignalingUrls[0] || "-"}, room=${this.webrtcRoom})`);
2677
+ this.startBroadcastLoop();
2678
+ if (this.broadcastEnabled && this.profile?.public_enabled) {
2679
+ try {
2680
+ await this.broadcastNow(reason);
2681
+ }
2682
+ catch (error) {
2683
+ await this.log("warn", `Initial broadcast failed: ${error instanceof Error ? error.message : String(error)}`);
2684
+ }
2685
+ }
2686
+ }
2687
+ catch (error) {
2688
+ this.networkStarted = false;
2689
+ this.networkStartupError = error instanceof Error ? error.message : String(error);
2690
+ await this.log("warn", `Network start failed (${this.adapterMode}, mode=${this.networkMode}): ${this.networkStartupError}`);
2691
+ this.scheduleNetworkReconnect();
1931
2692
  }
1932
2693
  }
2694
+ scheduleNetworkReconnect() {
2695
+ if (this.networkReconnectTimer) {
2696
+ return;
2697
+ }
2698
+ const delayMs = this.networkReconnectDelayMs;
2699
+ this.networkReconnectTimer = setTimeout(() => {
2700
+ this.networkReconnectTimer = null;
2701
+ void this.startNetworkAdapterWithRetry("adapter_reconnect");
2702
+ }, delayMs);
2703
+ this.networkReconnectDelayMs = Math.min(30_000, Math.max(5_000, Math.floor(delayMs * 1.5)));
2704
+ }
2705
+ pruneRemoteProfilesInMemory(now = Date.now()) {
2706
+ if (!Number.isFinite(DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT) || DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT <= 0) {
2707
+ return 0;
2708
+ }
2709
+ const selfAgentId = this.profile?.agent_id || this.identity?.agent_id || "";
2710
+ const remoteProfiles = Object.values(this.directory.profiles).filter((profile) => profile.agent_id !== selfAgentId);
2711
+ if (remoteProfiles.length <= DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT) {
2712
+ return 0;
2713
+ }
2714
+ const onlineRemoteProfiles = remoteProfiles.filter((profile) => (0, core_1.isAgentOnline)(this.directory.presence[profile.agent_id], now, PRESENCE_TTL_MS));
2715
+ const offlineRemoteProfiles = remoteProfiles
2716
+ .filter((profile) => !(0, core_1.isAgentOnline)(this.directory.presence[profile.agent_id], now, PRESENCE_TTL_MS))
2717
+ .sort((a, b) => (b.updated_at || 0) - (a.updated_at || 0));
2718
+ const keepOfflineCount = Math.max(0, DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT - onlineRemoteProfiles.length);
2719
+ const keptRemoteProfiles = [
2720
+ ...onlineRemoteProfiles,
2721
+ ...offlineRemoteProfiles.slice(0, keepOfflineCount),
2722
+ ];
2723
+ const keptRemoteIds = new Set(keptRemoteProfiles.map((profile) => profile.agent_id));
2724
+ const removedIds = remoteProfiles
2725
+ .map((profile) => profile.agent_id)
2726
+ .filter((agentId) => !keptRemoteIds.has(agentId));
2727
+ if (removedIds.length === 0) {
2728
+ return 0;
2729
+ }
2730
+ const next = (0, core_1.createEmptyDirectoryState)();
2731
+ const selfProfile = selfAgentId ? this.directory.profiles[selfAgentId] : null;
2732
+ if (selfProfile) {
2733
+ next.profiles[selfAgentId] = selfProfile;
2734
+ const selfPresence = this.directory.presence[selfAgentId];
2735
+ if (typeof selfPresence === "number" && Number.isFinite(selfPresence)) {
2736
+ next.presence[selfAgentId] = selfPresence;
2737
+ }
2738
+ const rebuilt = (0, core_1.rebuildIndexForProfile)(next, selfProfile);
2739
+ next.index = rebuilt.index;
2740
+ }
2741
+ for (const profile of keptRemoteProfiles) {
2742
+ next.profiles[profile.agent_id] = profile;
2743
+ const seenAt = this.directory.presence[profile.agent_id];
2744
+ if (typeof seenAt === "number" && Number.isFinite(seenAt)) {
2745
+ next.presence[profile.agent_id] = seenAt;
2746
+ }
2747
+ const rebuilt = (0, core_1.rebuildIndexForProfile)(next, profile);
2748
+ next.index = rebuilt.index;
2749
+ }
2750
+ this.directory = (0, core_1.dedupeIndex)(next);
2751
+ return removedIds.length;
2752
+ }
1933
2753
  compactCacheInMemory() {
1934
2754
  const cleaned = (0, core_1.cleanupExpiredPresence)(this.directory, Date.now(), PRESENCE_TTL_MS);
1935
2755
  this.directory = (0, core_1.dedupeIndex)(cleaned.state);
2756
+ this.pruneRemoteProfilesInMemory();
1936
2757
  return cleaned.removed;
1937
2758
  }
1938
2759
  async publish(topic, data) {
@@ -1940,7 +2761,22 @@ class LocalNodeService {
1940
2761
  this.publishedByTopic[topic] = (this.publishedByTopic[topic] ?? 0) + 1;
1941
2762
  }
1942
2763
  async persistCache() {
1943
- await this.cacheRepo.set(this.directory);
2764
+ const persisted = (0, core_1.createEmptyDirectoryState)();
2765
+ if (this.profile) {
2766
+ const selfProfileRecord = {
2767
+ type: "profile",
2768
+ profile: this.profile,
2769
+ };
2770
+ this.directory = (0, core_1.ingestProfileRecord)(this.directory, selfProfileRecord);
2771
+ persisted.profiles[this.profile.agent_id] = this.profile;
2772
+ const selfLastSeenAt = this.directory.presence[this.profile.agent_id];
2773
+ if (typeof selfLastSeenAt === "number" && Number.isFinite(selfLastSeenAt)) {
2774
+ persisted.presence[this.profile.agent_id] = selfLastSeenAt;
2775
+ }
2776
+ const indexed = (0, core_1.rebuildIndexForProfile)(persisted, this.profile);
2777
+ persisted.index = indexed.index;
2778
+ }
2779
+ await this.cacheRepo.set(persisted);
1944
2780
  }
1945
2781
  async persistSocialMessages() {
1946
2782
  await this.socialMessageRepo.set(this.socialMessages);
@@ -1948,6 +2784,52 @@ class LocalNodeService {
1948
2784
  async persistSocialMessageObservations() {
1949
2785
  await this.socialMessageObservationRepo.set(this.socialMessageObservations);
1950
2786
  }
2787
+ async persistPrivateMessages() {
2788
+ this.privateMessagesPersistDirty = true;
2789
+ if (this.privateMessagesPersistTimer) {
2790
+ return;
2791
+ }
2792
+ this.privateMessagesPersistTimer = setTimeout(() => {
2793
+ this.flushPrivateMessagesPersist().catch(() => { });
2794
+ }, PRIVATE_MESSAGE_PERSIST_DEBOUNCE_MS);
2795
+ }
2796
+ async persistPrivateMessageReceipts() {
2797
+ this.privateMessageReceiptsPersistDirty = true;
2798
+ if (this.privateMessageReceiptsPersistTimer) {
2799
+ return;
2800
+ }
2801
+ this.privateMessageReceiptsPersistTimer = setTimeout(() => {
2802
+ this.flushPrivateMessageReceiptsPersist().catch(() => { });
2803
+ }, PRIVATE_MESSAGE_PERSIST_DEBOUNCE_MS);
2804
+ }
2805
+ async flushPrivatePersistence() {
2806
+ await Promise.all([
2807
+ this.flushPrivateMessagesPersist(),
2808
+ this.flushPrivateMessageReceiptsPersist(),
2809
+ ]);
2810
+ }
2811
+ async flushPrivateMessagesPersist() {
2812
+ if (this.privateMessagesPersistTimer) {
2813
+ clearTimeout(this.privateMessagesPersistTimer);
2814
+ this.privateMessagesPersistTimer = null;
2815
+ }
2816
+ if (!this.privateMessagesPersistDirty) {
2817
+ return;
2818
+ }
2819
+ this.privateMessagesPersistDirty = false;
2820
+ await this.privateMessageRepo.set(this.privateMessages);
2821
+ }
2822
+ async flushPrivateMessageReceiptsPersist() {
2823
+ if (this.privateMessageReceiptsPersistTimer) {
2824
+ clearTimeout(this.privateMessageReceiptsPersistTimer);
2825
+ this.privateMessageReceiptsPersistTimer = null;
2826
+ }
2827
+ if (!this.privateMessageReceiptsPersistDirty) {
2828
+ return;
2829
+ }
2830
+ this.privateMessageReceiptsPersistDirty = false;
2831
+ await this.privateMessageReceiptRepo.set(this.privateMessageReceipts);
2832
+ }
1951
2833
  async log(level, message) {
1952
2834
  await this.logRepo.append({
1953
2835
  level,
@@ -1961,6 +2843,18 @@ class LocalNodeService {
1961
2843
  }
1962
2844
  return this.network.getDiagnostics();
1963
2845
  }
2846
+ getResolvedRealtimeNetworkSummary() {
2847
+ const diagnostics = this.getAdapterDiagnostics();
2848
+ const relayCapable = this.adapterMode === "webrtc-preview" || this.adapterMode === "relay-preview";
2849
+ return {
2850
+ diagnostics,
2851
+ signaling_url: diagnostics?.signaling_url ?? (relayCapable ? this.webrtcSignalingUrls[0] ?? null : null),
2852
+ signaling_endpoints: diagnostics?.signaling_endpoints ?? (relayCapable ? this.webrtcSignalingUrls : []),
2853
+ room: diagnostics?.room ?? (relayCapable ? this.webrtcRoom : null),
2854
+ bootstrap_sources: diagnostics?.bootstrap_sources ?? (relayCapable ? this.webrtcBootstrapSources : []),
2855
+ seed_peers_count: diagnostics?.seed_peers_count ?? this.webrtcSeedPeers.length,
2856
+ };
2857
+ }
1964
2858
  toPublicProfileSummary(profile, options) {
1965
2859
  const lastSeenAt = options?.last_seen_at ?? this.directory.presence[profile.agent_id] ?? 0;
1966
2860
  const online = (0, core_1.isAgentOnline)(lastSeenAt, Date.now(), PRESENCE_TTL_MS);
@@ -1982,6 +2876,7 @@ class LocalNodeService {
1982
2876
  (0, core_1.verifyProfile)(profile, selfPublicKey));
1983
2877
  return (0, core_1.buildPublicProfileSummary)({
1984
2878
  profile,
2879
+ is_self: isSelf,
1985
2880
  online,
1986
2881
  last_seen_at: lastSeenAt || null,
1987
2882
  network_mode: isSelf ? this.networkMode : "unknown",
@@ -1996,6 +2891,63 @@ class LocalNodeService {
1996
2891
  presence_ttl_ms: PRESENCE_TTL_MS,
1997
2892
  });
1998
2893
  }
2894
+ mergeMessageOnlyAgentSummaries(summaries, keyword) {
2895
+ const normalizedKeyword = String(keyword || "").trim().toLowerCase();
2896
+ const knownAgentIds = new Set(summaries.map((item) => item.agent_id));
2897
+ const messageOnly = [];
2898
+ for (const message of this.socialMessages) {
2899
+ if (!message?.agent_id || knownAgentIds.has(message.agent_id)) {
2900
+ continue;
2901
+ }
2902
+ const displayName = String(message.display_name || "Unnamed").trim() || "Unnamed";
2903
+ if (normalizedKeyword) {
2904
+ const haystacks = [
2905
+ displayName.toLowerCase(),
2906
+ message.agent_id.toLowerCase(),
2907
+ String(message.topic || "").toLowerCase(),
2908
+ ];
2909
+ if (!haystacks.some((value) => value.includes(normalizedKeyword))) {
2910
+ continue;
2911
+ }
2912
+ }
2913
+ knownAgentIds.add(message.agent_id);
2914
+ messageOnly.push((0, core_1.buildPublicProfileSummary)({
2915
+ profile: {
2916
+ agent_id: message.agent_id,
2917
+ display_name: displayName,
2918
+ bio: "Seen from signed public message. Profile/presence not synced yet.",
2919
+ tags: ["message-only"],
2920
+ avatar_url: "",
2921
+ public_enabled: true,
2922
+ updated_at: message.created_at,
2923
+ signature: "",
2924
+ },
2925
+ is_self: message.agent_id === this.identity?.agent_id,
2926
+ online: false,
2927
+ last_seen_at: null,
2928
+ network_mode: "unknown",
2929
+ openclaw_bound: false,
2930
+ profile_version: PROFILE_VERSION,
2931
+ public_key_fingerprint: null,
2932
+ verified_profile: false,
2933
+ now: Date.now(),
2934
+ presence_ttl_ms: PRESENCE_TTL_MS,
2935
+ }));
2936
+ }
2937
+ return [...summaries, ...messageOnly].sort((a, b) => {
2938
+ if (a.online !== b.online) {
2939
+ return a.online ? -1 : 1;
2940
+ }
2941
+ if (a.updated_at !== b.updated_at) {
2942
+ return b.updated_at - a.updated_at;
2943
+ }
2944
+ const byName = a.display_name.localeCompare(b.display_name);
2945
+ if (byName !== 0) {
2946
+ return byName;
2947
+ }
2948
+ return a.agent_id.localeCompare(b.agent_id);
2949
+ });
2950
+ }
1999
2951
  fingerprintPublicKey(publicKey) {
2000
2952
  const digest = (0, crypto_1.createHash)("sha256").update(publicKey, "utf8").digest("hex");
2001
2953
  return `${digest.slice(0, 12)}:${digest.slice(-8)}`;
@@ -2003,6 +2955,22 @@ class LocalNodeService {
2003
2955
  getOnboardingSummary() {
2004
2956
  const summary = this.getIntegrationSummary();
2005
2957
  const publicEnabled = Boolean(this.profile?.public_enabled);
2958
+ const nextSteps = [];
2959
+ if (!String(this.profile?.display_name || "").trim()) {
2960
+ nextSteps.push("Update display name in Profile page");
2961
+ }
2962
+ if (!publicEnabled) {
2963
+ nextSteps.push("Enable Public Enabled in Profile");
2964
+ }
2965
+ if (!summary.running) {
2966
+ nextSteps.push("Start broadcast in Network");
2967
+ }
2968
+ if (!summary.discoverable) {
2969
+ nextSteps.push("Announce node once after the network is running");
2970
+ }
2971
+ if (nextSteps.length === 0) {
2972
+ nextSteps.push("Node is public and discoverable");
2973
+ }
2006
2974
  return {
2007
2975
  first_run: Boolean(this.initState.social_auto_created ||
2008
2976
  this.initState.identity_auto_created ||
@@ -2012,10 +2980,7 @@ class LocalNodeService {
2012
2980
  mode: this.networkMode,
2013
2981
  public_enabled: publicEnabled,
2014
2982
  can_enable_public_discovery: !publicEnabled,
2015
- next_steps: [
2016
- "Update display name in Profile page",
2017
- "Export social.md from Social Config",
2018
- ],
2983
+ next_steps: nextSteps,
2019
2984
  };
2020
2985
  }
2021
2986
  getDefaultDisplayName() {
@@ -2038,7 +3003,7 @@ class LocalNodeService {
2038
3003
  };
2039
3004
  }
2040
3005
  return {
2041
- mode: "global-preview",
3006
+ mode: DEFAULT_NETWORK_MODE,
2042
3007
  short_label: "Relay preview",
2043
3008
  summary: "Uses the public relay preview room so public nodes can find each other across the internet.",
2044
3009
  };
@@ -2066,12 +3031,12 @@ class LocalNodeService {
2066
3031
  const resolvedMode = this.socialConfig.network.mode ||
2067
3032
  (modeEnv === "local" || modeEnv === "lan" || modeEnv === "global-preview"
2068
3033
  ? modeEnv
2069
- : "global-preview");
3034
+ : DEFAULT_NETWORK_MODE);
2070
3035
  this.networkMode = resolvedMode;
2071
- this.networkNamespace = this.socialConfig.network.namespace || process.env.NETWORK_NAMESPACE || "silicaclaw.preview";
2072
- this.networkPort = Number(this.socialConfig.network.port || process.env.NETWORK_PORT || 44123);
2073
- const builtInGlobalSignalingUrls = ["https://relay.silicaclaw.com"];
2074
- const builtInGlobalRoom = "silicaclaw-global-preview";
3036
+ this.networkNamespace = this.socialConfig.network.namespace || process.env.NETWORK_NAMESPACE || DEFAULT_NETWORK_NAMESPACE;
3037
+ this.networkPort = Number(this.socialConfig.network.port || process.env.NETWORK_PORT || DEFAULT_NETWORK_PORT);
3038
+ const builtInGlobalSignalingUrls = [DEFAULT_GLOBAL_SIGNALING_URL];
3039
+ const builtInGlobalRoom = DEFAULT_GLOBAL_ROOM;
2075
3040
  const signalingUrlsSocial = dedupeStrings(this.socialConfig.network.signaling_urls || []);
2076
3041
  const signalingUrlSocial = String(this.socialConfig.network.signaling_url || "").trim();
2077
3042
  const signalingUrlsEnv = dedupeStrings(parseListEnv(WEBRTC_SIGNALING_URLS));
@@ -2099,22 +3064,22 @@ class LocalNodeService {
2099
3064
  signalingSource = "built-in-defaults:global-preview.signaling_urls";
2100
3065
  }
2101
3066
  else {
2102
- signalingUrls = ["https://relay.silicaclaw.com"];
2103
- signalingSource = "default:https://relay.silicaclaw.com";
3067
+ signalingUrls = [DEFAULT_GLOBAL_SIGNALING_URL];
3068
+ signalingSource = `default:${DEFAULT_GLOBAL_SIGNALING_URL}`;
2104
3069
  }
2105
3070
  const roomSocial = String(this.socialConfig.network.room || "").trim();
2106
3071
  const roomEnv = String(WEBRTC_ROOM || "").trim();
2107
3072
  const room = roomSocial ||
2108
3073
  roomEnv ||
2109
3074
  (this.networkMode === "global-preview" ? builtInGlobalRoom : "") ||
2110
- "silicaclaw-global-preview";
3075
+ DEFAULT_GLOBAL_ROOM;
2111
3076
  const roomSource = roomSocial
2112
3077
  ? "social.md:network.room"
2113
3078
  : roomEnv
2114
3079
  ? "env:WEBRTC_ROOM"
2115
3080
  : this.networkMode === "global-preview"
2116
3081
  ? "built-in-defaults:global-preview.room"
2117
- : "default:silicaclaw-global-preview";
3082
+ : `default:${DEFAULT_GLOBAL_ROOM}`;
2118
3083
  const seedPeersSocial = dedupeStrings(this.socialConfig.network.seed_peers || []);
2119
3084
  const seedPeersEnv = dedupeStrings(parseListEnv(WEBRTC_SEED_PEERS));
2120
3085
  const seedPeers = seedPeersSocial.length > 0 ? seedPeersSocial : seedPeersEnv;
@@ -2145,6 +3110,32 @@ class LocalNodeService {
2145
3110
  .join("\n")
2146
3111
  .trim();
2147
3112
  }
3113
+ buildPrivateConversationId(leftAgentId, rightAgentId) {
3114
+ return [String(leftAgentId || "").trim(), String(rightAgentId || "").trim()].sort().join(":");
3115
+ }
3116
+ decryptPrivateMessageBody(message) {
3117
+ const cached = this.privateMessageBodyCache.get(message.message_id);
3118
+ if (typeof cached === "string") {
3119
+ return cached;
3120
+ }
3121
+ if (!this.privateEncryptionKeyPair) {
3122
+ return "[encrypted]";
3123
+ }
3124
+ const decrypted = (0, core_1.decryptPrivatePayload)({
3125
+ ciphertext: message.ciphertext,
3126
+ nonce: message.nonce,
3127
+ sender_encryption_public_key: message.sender_encryption_public_key,
3128
+ recipient_private_key: this.privateEncryptionKeyPair.private_key,
3129
+ }) || "[encrypted]";
3130
+ this.privateMessageBodyCache.set(message.message_id, decrypted);
3131
+ if (this.privateMessageBodyCache.size > PRIVATE_MESSAGE_HISTORY_LIMIT * 2) {
3132
+ const firstKey = this.privateMessageBodyCache.keys().next().value;
3133
+ if (firstKey) {
3134
+ this.privateMessageBodyCache.delete(firstKey);
3135
+ }
3136
+ }
3137
+ return decrypted;
3138
+ }
2148
3139
  normalizeWindowTimestamps(timestamps, windowMs, now = Date.now()) {
2149
3140
  return timestamps.filter((timestamp) => now - timestamp <= windowMs);
2150
3141
  }
@@ -2162,6 +3153,34 @@ class LocalNodeService {
2162
3153
  const normalized = String(body || "").toLowerCase();
2163
3154
  return this.messageGovernance.blocked_terms.some((term) => normalized.includes(term));
2164
3155
  }
3156
+ hasSocialMessage(messageId) {
3157
+ return this.socialMessages.some((item) => item.message_id === messageId);
3158
+ }
3159
+ getReplayableSelfSocialMessages(reason = "manual", now = Date.now()) {
3160
+ const maxCount = Math.max(0, SOCIAL_MESSAGE_REPLAY_MAX_PER_BROADCAST);
3161
+ if (!this.identity || maxCount === 0) {
3162
+ return [];
3163
+ }
3164
+ const replayable = this.socialMessages
3165
+ .filter((item) => (item.agent_id === this.identity?.agent_id &&
3166
+ now - item.created_at <= SOCIAL_MESSAGE_REPLAY_WINDOW_MS))
3167
+ .sort((a, b) => a.created_at - b.created_at)
3168
+ .slice(-maxCount);
3169
+ if (!replayable.length) {
3170
+ this.lastReplayBroadcastSignature = "";
3171
+ return [];
3172
+ }
3173
+ const signature = replayable.map((item) => item.message_id).join(",");
3174
+ const isIntervalReplay = reason === "interval";
3175
+ const changedSinceLastReplay = signature !== this.lastReplayBroadcastSignature;
3176
+ const refreshDue = now - this.lastReplayBroadcastAt >= SOCIAL_MESSAGE_REPLAY_REFRESH_INTERVAL_MS;
3177
+ if (isIntervalReplay && !changedSinceLastReplay && !refreshDue) {
3178
+ return [];
3179
+ }
3180
+ this.lastReplayBroadcastSignature = signature;
3181
+ this.lastReplayBroadcastAt = now;
3182
+ return replayable;
3183
+ }
2165
3184
  hasRecentDuplicateMessage(agentId, body, topic, now = Date.now()) {
2166
3185
  return this.socialMessages.some((item) => (item.agent_id === agentId &&
2167
3186
  item.topic === topic &&
@@ -2224,6 +3243,167 @@ class LocalNodeService {
2224
3243
  await this.publish(SOCIAL_MESSAGE_OBSERVATION_TOPIC, observation);
2225
3244
  await this.persistSocialMessageObservations();
2226
3245
  }
3246
+ async sendPrivateMessageReceipt(message, replyPeerId) {
3247
+ if (!this.identity || typeof this.network.sendDirect !== "function" || !replyPeerId) {
3248
+ return;
3249
+ }
3250
+ const receipt = (0, core_1.signPrivateMessageReceipt)({
3251
+ identity: this.identity,
3252
+ receipt_id: (0, crypto_1.createHash)("sha256").update(`${message.message_id}:${this.identity.agent_id}:${Date.now()}`, "utf8").digest("hex"),
3253
+ message_id: message.message_id,
3254
+ conversation_id: message.conversation_id,
3255
+ to_agent_id: message.from_agent_id,
3256
+ status: "received",
3257
+ created_at: Date.now(),
3258
+ });
3259
+ this.ingestPrivateMessageReceipt(receipt);
3260
+ await this.network.sendDirect(replyPeerId, PRIVATE_MESSAGE_RECEIPT_TOPIC, receipt);
3261
+ await this.persistPrivateMessageReceipts();
3262
+ }
3263
+ normalizeIncomingPrivateMessage(value) {
3264
+ if (typeof value !== "object" || value === null) {
3265
+ return null;
3266
+ }
3267
+ const record = value;
3268
+ const createdAt = Number(record.created_at || 0);
3269
+ const fromAgentId = String(record.from_agent_id || "").trim();
3270
+ const toAgentId = String(record.to_agent_id || "").trim();
3271
+ const conversationId = String(record.conversation_id || "").trim();
3272
+ if (record.type !== PRIVATE_MESSAGE_TOPIC ||
3273
+ !String(record.message_id || "").trim() ||
3274
+ !conversationId ||
3275
+ !fromAgentId ||
3276
+ !toAgentId ||
3277
+ !String(record.sender_public_key || "").trim() ||
3278
+ !String(record.sender_encryption_public_key || "").trim() ||
3279
+ !String(record.recipient_encryption_public_key || "").trim() ||
3280
+ !String(record.ciphertext || "").trim() ||
3281
+ !String(record.nonce || "").trim() ||
3282
+ String(record.cipher_scheme || "") !== "nacl-box-v1" ||
3283
+ !String(record.signature || "").trim() ||
3284
+ !Number.isFinite(createdAt)) {
3285
+ return null;
3286
+ }
3287
+ if (fromAgentId === toAgentId) {
3288
+ return null;
3289
+ }
3290
+ if (conversationId !== this.buildPrivateConversationId(fromAgentId, toAgentId)) {
3291
+ return null;
3292
+ }
3293
+ return {
3294
+ type: PRIVATE_MESSAGE_TOPIC,
3295
+ message_id: String(record.message_id).trim(),
3296
+ conversation_id: conversationId,
3297
+ from_agent_id: fromAgentId,
3298
+ to_agent_id: toAgentId,
3299
+ sender_public_key: String(record.sender_public_key).trim(),
3300
+ sender_encryption_public_key: String(record.sender_encryption_public_key).trim(),
3301
+ recipient_encryption_public_key: String(record.recipient_encryption_public_key).trim(),
3302
+ cipher_scheme: "nacl-box-v1",
3303
+ ciphertext: String(record.ciphertext).trim(),
3304
+ nonce: String(record.nonce).trim(),
3305
+ created_at: createdAt,
3306
+ signature: String(record.signature).trim(),
3307
+ };
3308
+ }
3309
+ normalizePrivateMessages(items) {
3310
+ if (!Array.isArray(items)) {
3311
+ return [];
3312
+ }
3313
+ const deduped = new Set();
3314
+ return items
3315
+ .map((item) => this.normalizeIncomingPrivateMessage(item))
3316
+ .filter((item) => Boolean(item))
3317
+ .sort((a, b) => a.created_at - b.created_at)
3318
+ .filter((item) => {
3319
+ if (deduped.has(item.message_id)) {
3320
+ return false;
3321
+ }
3322
+ deduped.add(item.message_id);
3323
+ return true;
3324
+ })
3325
+ .slice(-PRIVATE_MESSAGE_HISTORY_LIMIT);
3326
+ }
3327
+ normalizeIncomingPrivateMessageReceipt(value) {
3328
+ if (typeof value !== "object" || value === null) {
3329
+ return null;
3330
+ }
3331
+ const record = value;
3332
+ const createdAt = Number(record.created_at || 0);
3333
+ const status = String(record.status || "").trim();
3334
+ if (record.type !== PRIVATE_MESSAGE_RECEIPT_TOPIC ||
3335
+ !String(record.receipt_id || "").trim() ||
3336
+ !String(record.message_id || "").trim() ||
3337
+ !String(record.conversation_id || "").trim() ||
3338
+ !String(record.from_agent_id || "").trim() ||
3339
+ !String(record.to_agent_id || "").trim() ||
3340
+ !String(record.sender_public_key || "").trim() ||
3341
+ (status !== "received" && status !== "read") ||
3342
+ !String(record.signature || "").trim() ||
3343
+ !Number.isFinite(createdAt)) {
3344
+ return null;
3345
+ }
3346
+ return {
3347
+ type: PRIVATE_MESSAGE_RECEIPT_TOPIC,
3348
+ receipt_id: String(record.receipt_id).trim(),
3349
+ message_id: String(record.message_id).trim(),
3350
+ conversation_id: String(record.conversation_id).trim(),
3351
+ from_agent_id: String(record.from_agent_id).trim(),
3352
+ to_agent_id: String(record.to_agent_id).trim(),
3353
+ sender_public_key: String(record.sender_public_key).trim(),
3354
+ status: status,
3355
+ created_at: createdAt,
3356
+ signature: String(record.signature).trim(),
3357
+ };
3358
+ }
3359
+ normalizePrivateMessageReceipts(items) {
3360
+ if (!Array.isArray(items)) {
3361
+ return [];
3362
+ }
3363
+ const deduped = new Set();
3364
+ return items
3365
+ .map((item) => this.normalizeIncomingPrivateMessageReceipt(item))
3366
+ .filter((item) => Boolean(item))
3367
+ .sort((a, b) => a.created_at - b.created_at)
3368
+ .filter((item) => {
3369
+ if (deduped.has(item.receipt_id)) {
3370
+ return false;
3371
+ }
3372
+ deduped.add(item.receipt_id);
3373
+ return true;
3374
+ })
3375
+ .slice(-PRIVATE_MESSAGE_RECEIPT_HISTORY_LIMIT);
3376
+ }
3377
+ hasPrivateMessage(messageId) {
3378
+ return this.privateMessages.some((item) => item.message_id === messageId);
3379
+ }
3380
+ ingestPrivateMessage(message) {
3381
+ const existing = this.privateMessages.findIndex((item) => item.message_id === message.message_id);
3382
+ if (existing >= 0) {
3383
+ this.privateMessages[existing] = message;
3384
+ }
3385
+ else {
3386
+ this.privateMessages.push(message);
3387
+ }
3388
+ this.privateMessages = this.normalizePrivateMessages(this.privateMessages);
3389
+ const validIds = new Set(this.privateMessages.map((item) => item.message_id));
3390
+ this.privateMessageBodyCache.delete(message.message_id);
3391
+ for (const key of Array.from(this.privateMessageBodyCache.keys())) {
3392
+ if (!validIds.has(key)) {
3393
+ this.privateMessageBodyCache.delete(key);
3394
+ }
3395
+ }
3396
+ }
3397
+ ingestPrivateMessageReceipt(receipt) {
3398
+ const existing = this.privateMessageReceipts.findIndex((item) => item.receipt_id === receipt.receipt_id);
3399
+ if (existing >= 0) {
3400
+ this.privateMessageReceipts[existing] = receipt;
3401
+ }
3402
+ else {
3403
+ this.privateMessageReceipts.push(receipt);
3404
+ }
3405
+ this.privateMessageReceipts = this.normalizePrivateMessageReceipts(this.privateMessageReceipts);
3406
+ }
2227
3407
  normalizeIncomingSocialMessage(value) {
2228
3408
  if (typeof value !== "object" || value === null) {
2229
3409
  return null;
@@ -2441,18 +3621,17 @@ function renderBootstrapScript(payload) {
2441
3621
  if (data.pillBroadcastClassName) pillBroadcast.className = data.pillBroadcastClassName;
2442
3622
  }
2443
3623
  setHtml('overviewCards', data.overviewCardsHtml || '');
2444
- setText('agentsCountHint', data.agentsCountHintText || '0 agents');
2445
- setHtml('agentsWrap', data.agentsWrapHtml || '<div class="label">No discovered agents yet.</div>');
3624
+ setText('agentsCountHint', data.agentsCountHintText || '0 nodes');
3625
+ setHtml('agentsWrap', data.agentsWrapHtml || '<div class="label">No discovered nodes yet.</div>');
2446
3626
  })();
2447
3627
  </script>`;
2448
3628
  }
2449
3629
  async function main() {
2450
3630
  const app = (0, express_1.default)();
2451
- const port = Number(process.env.PORT || 4310);
3631
+ const port = Number(process.env.PORT || silicaclaw_defaults_json_1.default.ports.local_console);
2452
3632
  const staticDir = resolveLocalConsoleStaticDir();
2453
3633
  const staticIndexFile = (0, path_1.resolve)(staticDir, "index.html");
2454
3634
  const node = new LocalNodeService();
2455
- await node.start();
2456
3635
  app.use((0, cors_1.default)({ origin: true }));
2457
3636
  app.use(express_1.default.json());
2458
3637
  app.get("/api/identity", (_req, res) => {
@@ -2471,6 +3650,36 @@ async function main() {
2471
3650
  app.get("/api/runtime/paths", (_req, res) => {
2472
3651
  sendOk(res, node.getRuntimePaths());
2473
3652
  });
3653
+ app.get("/api/app/update-status", (_req, res) => {
3654
+ sendOk(res, node.getAppUpdateStatus());
3655
+ });
3656
+ app.post("/api/app/update", asyncRoute(async (_req, res) => {
3657
+ const status = node.getAppUpdateStatus();
3658
+ if (!status.update_available || !status.latest_version) {
3659
+ sendOk(res, {
3660
+ started: false,
3661
+ current_version: status.current_version,
3662
+ latest_version: status.latest_version,
3663
+ platform: status.platform,
3664
+ reason: status.check_error || "already_current",
3665
+ }, { message: "Already on the latest version" });
3666
+ return;
3667
+ }
3668
+ sendOk(res, {
3669
+ started: true,
3670
+ current_version: status.current_version,
3671
+ target_version: status.latest_version,
3672
+ platform: status.platform,
3673
+ }, { message: `Updating to ${status.latest_version}` });
3674
+ setTimeout(() => {
3675
+ try {
3676
+ node.startAppUpdate();
3677
+ }
3678
+ catch {
3679
+ // best effort after response has been sent
3680
+ }
3681
+ }, 150);
3682
+ }));
2474
3683
  app.put("/api/profile", asyncRoute(async (req, res) => {
2475
3684
  const body = req.body;
2476
3685
  const tags = Array.isArray(body.tags)
@@ -2569,6 +3778,27 @@ async function main() {
2569
3778
  const agentId = String(req.query.agent_id ?? "").trim();
2570
3779
  sendOk(res, node.getSocialMessages(limit, { agent_id: agentId || null }));
2571
3780
  });
3781
+ app.get("/api/private/state", (_req, res) => {
3782
+ sendOk(res, node.getPrivateMessagingState());
3783
+ });
3784
+ app.get("/api/private/conversations", (_req, res) => {
3785
+ sendOk(res, node.getPrivateConversations());
3786
+ });
3787
+ app.get("/api/private/messages", (req, res) => {
3788
+ const conversationId = String(req.query.conversation_id ?? "").trim();
3789
+ const limit = Number(req.query.limit ?? PRIVATE_MESSAGE_QUERY_LIMIT);
3790
+ sendOk(res, node.getPrivateMessages(conversationId, limit));
3791
+ });
3792
+ app.post("/api/private/messages/send", asyncRoute(async (req, res) => {
3793
+ const result = await node.sendPrivateMessage({
3794
+ to_agent_id: String(req.body?.to_agent_id || ""),
3795
+ recipient_encryption_public_key: String(req.body?.recipient_encryption_public_key || ""),
3796
+ body: String(req.body?.body || ""),
3797
+ });
3798
+ sendOk(res, result, {
3799
+ message: result.sent ? "Private message sent" : `Private message skipped: ${result.reason}`,
3800
+ });
3801
+ }));
2572
3802
  app.get("/api/openclaw/bridge", (_req, res) => {
2573
3803
  sendOk(res, node.getOpenClawBridgeStatus());
2574
3804
  });
@@ -2591,9 +3821,10 @@ async function main() {
2591
3821
  message: result.sent ? "OpenClaw bridge message published" : `OpenClaw bridge message skipped: ${result.reason}`,
2592
3822
  });
2593
3823
  }));
2594
- app.post("/api/openclaw/bridge/skill-install", asyncRoute(async (_req, res) => {
3824
+ app.post("/api/openclaw/bridge/skill-install", asyncRoute(async (req, res) => {
2595
3825
  try {
2596
- const result = await node.installOpenClawSkill();
3826
+ const skillName = String(req.body?.skill_name || "").trim();
3827
+ const result = await node.installOpenClawSkill(skillName || undefined);
2597
3828
  sendOk(res, result, {
2598
3829
  message: "OpenClaw skill installed",
2599
3830
  });
@@ -2622,7 +3853,7 @@ async function main() {
2622
3853
  const agentId = req.params.agentId;
2623
3854
  const profile = state.profiles[agentId];
2624
3855
  if (!profile) {
2625
- sendError(res, 404, "AGENT_NOT_FOUND", "Agent not found", { agent_id: agentId });
3856
+ sendError(res, 404, "AGENT_NOT_FOUND", "Node not found", { agent_id: agentId });
2626
3857
  return;
2627
3858
  }
2628
3859
  const lastSeenAt = state.presence[agentId] ?? 0;
@@ -2652,7 +3883,7 @@ async function main() {
2652
3883
  .map(([k, v]) => `<div class="card"><div class="label">${escapeHtml(String(k))}</div><div class="value">${escapeHtml(String(v))}</div></div>`)
2653
3884
  .join("");
2654
3885
  const agentsWrapHtml = discovered.length === 0
2655
- ? `<div class="label">No discovered agents yet.</div>`
3886
+ ? `<div class="label">No discovered nodes yet.</div>`
2656
3887
  : `
2657
3888
  <table class="table">
2658
3889
  <thead><tr><th>Name</th><th>Agent ID</th><th>Status</th><th>Updated</th></tr></thead>
@@ -2686,7 +3917,7 @@ async function main() {
2686
3917
  pillBroadcastText: overview.broadcast_enabled ? "broadcast: running" : "broadcast: paused",
2687
3918
  pillBroadcastClassName: `pill ${overview.broadcast_enabled ? "ok" : "warn"}`,
2688
3919
  overviewCardsHtml,
2689
- agentsCountHintText: `${discovered.length} agents discovered`,
3920
+ agentsCountHintText: `${discovered.length} nodes discovered`,
2690
3921
  agentsWrapHtml,
2691
3922
  integrationStatusText: `Connected to SilicaClaw: ${integration.connected_to_silicaclaw ? "yes" : "no"} · Network mode: ${integration.network_mode || "-"} · Public discovery: ${integration.public_enabled ? "enabled" : "disabled"}`,
2692
3923
  integrationStatusClassName: `integration-strip ${integration.connected_to_silicaclaw && integration.public_enabled ? "ok" : "warn"}`,
@@ -2707,6 +3938,7 @@ async function main() {
2707
3938
  // eslint-disable-next-line no-console
2708
3939
  console.log(`SilicaClaw local-console running: http://localhost:${port}`);
2709
3940
  });
3941
+ await node.start();
2710
3942
  process.on("SIGINT", async () => {
2711
3943
  await node.stop();
2712
3944
  process.exit(0);