@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.
- package/CHANGELOG.md +182 -0
- package/DEMO_GUIDE.md +1 -1
- package/INSTALL.md +53 -13
- package/README.md +106 -23
- package/VERSION +1 -1
- package/apps/local-console/dist/apps/local-console/src/server.d.ts +180 -14
- package/apps/local-console/dist/apps/local-console/src/server.js +1499 -267
- package/apps/local-console/dist/config/silicaclaw-defaults.json +19 -0
- package/apps/local-console/dist/packages/core/src/index.d.ts +2 -0
- package/apps/local-console/dist/packages/core/src/index.js +2 -0
- package/apps/local-console/dist/packages/core/src/privateCrypto.d.ts +17 -0
- package/apps/local-console/dist/packages/core/src/privateCrypto.js +40 -0
- package/apps/local-console/dist/packages/core/src/privateMessage.d.ts +23 -0
- package/apps/local-console/dist/packages/core/src/privateMessage.js +74 -0
- package/apps/local-console/dist/packages/core/src/profile.js +2 -0
- package/apps/local-console/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
- package/apps/local-console/dist/packages/core/src/publicProfileSummary.js +3 -0
- package/apps/local-console/dist/packages/core/src/socialConfig.js +9 -5
- package/apps/local-console/dist/packages/core/src/types.d.ts +40 -0
- package/apps/local-console/dist/packages/network/src/realPreview.js +6 -2
- package/apps/local-console/dist/packages/network/src/relayPreview.d.ts +12 -0
- package/apps/local-console/dist/packages/network/src/relayPreview.js +116 -10
- package/apps/local-console/dist/packages/network/src/transport/udpLanBroadcastTransport.js +2 -1
- package/apps/local-console/dist/packages/network/src/types.d.ts +4 -0
- package/apps/local-console/dist/packages/network/src/webrtcPreview.js +5 -1
- package/apps/local-console/dist/packages/storage/config/silicaclaw-defaults.json +19 -0
- package/apps/local-console/dist/packages/storage/src/repos.d.ts +13 -1
- package/apps/local-console/dist/packages/storage/src/repos.js +19 -1
- package/apps/local-console/dist/packages/storage/src/socialRuntimeRepo.js +8 -4
- package/apps/local-console/public/app/app.js +486 -12
- package/apps/local-console/public/app/events.js +61 -2
- package/apps/local-console/public/app/network.js +176 -35
- package/apps/local-console/public/app/overview.js +75 -53
- package/apps/local-console/public/app/shell.js +18 -34
- package/apps/local-console/public/app/social.js +495 -93
- package/apps/local-console/public/app/styles.css +309 -15
- package/apps/local-console/public/app/template.js +182 -51
- package/apps/local-console/public/app/translations.js +476 -266
- package/apps/local-console/src/server.ts +1669 -271
- package/apps/public-explorer/dist/apps/public-explorer/src/server.d.ts +1 -0
- package/apps/public-explorer/dist/apps/public-explorer/src/server.js +41 -0
- package/apps/public-explorer/dist/config/silicaclaw-defaults.json +19 -0
- package/apps/public-explorer/public/app/app.js +22 -2
- package/apps/public-explorer/public/app/template.js +4 -4
- package/apps/public-explorer/public/app/translations.js +29 -29
- package/apps/public-explorer/src/server.ts +11 -1
- package/config/silicaclaw-defaults.json +19 -0
- package/dist/apps/local-console/src/server.d.ts +1 -0
- package/dist/apps/local-console/src/server.js +555 -0
- package/docs/NEW_USER_INSTALL.md +14 -10
- package/docs/NEW_USER_OPERATIONS.md +9 -9
- package/docs/OPENCLAW_BRIDGE.md +22 -7
- package/docs/OPENCLAW_BRIDGE_ZH.md +21 -6
- package/docs/RELEASE_CHECKLIST.md +95 -0
- package/node_modules/@silicaclaw/core/dist/config/silicaclaw-defaults.json +19 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/crypto.d.ts +6 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/crypto.js +50 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/directory.d.ts +17 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/directory.js +145 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/identity.d.ts +2 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/identity.js +18 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/index.d.ts +14 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/index.js +30 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/indexing.d.ts +6 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/indexing.js +43 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/presence.d.ts +4 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/presence.js +23 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/privateCrypto.d.ts +17 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/privateCrypto.js +40 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/privateMessage.d.ts +23 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/privateMessage.js +74 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/profile.d.ts +4 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/profile.js +41 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/publicProfileSummary.d.ts +74 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/publicProfileSummary.js +106 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/socialConfig.d.ts +100 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/socialConfig.js +300 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/socialMessage.d.ts +19 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/socialMessage.js +69 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/socialResolver.d.ts +46 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/socialResolver.js +237 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/socialTemplate.d.ts +2 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/socialTemplate.js +90 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/types.d.ts +99 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/types.js +2 -0
- package/node_modules/@silicaclaw/core/src/index.ts +2 -0
- package/node_modules/@silicaclaw/core/src/privateCrypto.ts +57 -0
- package/node_modules/@silicaclaw/core/src/privateMessage.ts +101 -0
- package/node_modules/@silicaclaw/core/src/profile.ts +2 -0
- package/node_modules/@silicaclaw/core/src/publicProfileSummary.ts +7 -0
- package/node_modules/@silicaclaw/core/src/socialConfig.ts +7 -5
- package/node_modules/@silicaclaw/core/src/types.ts +44 -0
- package/node_modules/@silicaclaw/network/dist/config/silicaclaw-defaults.json +19 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/abstractions/messageEnvelope.d.ts +28 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/abstractions/messageEnvelope.js +36 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/abstractions/peerDiscovery.d.ts +43 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/abstractions/peerDiscovery.js +2 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/abstractions/topicCodec.d.ts +4 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/abstractions/topicCodec.js +2 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/abstractions/transport.d.ts +36 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/abstractions/transport.js +2 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/codec/jsonMessageEnvelopeCodec.d.ts +5 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/codec/jsonMessageEnvelopeCodec.js +24 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/codec/jsonTopicCodec.d.ts +5 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/codec/jsonTopicCodec.js +12 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/discovery/heartbeatPeerDiscovery.d.ts +28 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/discovery/heartbeatPeerDiscovery.js +144 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/index.d.ts +14 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/index.js +30 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/localEventBus.d.ts +9 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/localEventBus.js +47 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/mock.d.ts +8 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/mock.js +24 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/realPreview.d.ts +105 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/realPreview.js +331 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.d.ts +178 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.js +548 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/transport/udpLanBroadcastTransport.d.ts +23 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/transport/udpLanBroadcastTransport.js +154 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/types.d.ts +10 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/types.js +2 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/webrtcPreview.d.ts +163 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/webrtcPreview.js +848 -0
- package/node_modules/@silicaclaw/network/src/realPreview.ts +3 -2
- package/node_modules/@silicaclaw/network/src/relayPreview.ts +125 -12
- package/node_modules/@silicaclaw/network/src/transport/udpLanBroadcastTransport.ts +2 -1
- package/node_modules/@silicaclaw/network/src/types.ts +2 -0
- package/node_modules/@silicaclaw/network/src/webrtcPreview.ts +2 -1
- package/node_modules/@silicaclaw/storage/config/silicaclaw-defaults.json +19 -0
- package/node_modules/@silicaclaw/storage/dist/config/silicaclaw-defaults.json +19 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/crypto.d.ts +6 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/crypto.js +50 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/directory.d.ts +17 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/directory.js +145 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/identity.d.ts +2 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/identity.js +18 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/index.d.ts +14 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/index.js +30 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/indexing.d.ts +6 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/indexing.js +43 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/presence.d.ts +4 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/presence.js +23 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateCrypto.d.ts +17 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateCrypto.js +40 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateMessage.d.ts +23 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateMessage.js +74 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/profile.d.ts +4 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/profile.js +41 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/publicProfileSummary.d.ts +74 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/publicProfileSummary.js +106 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialConfig.d.ts +100 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialConfig.js +300 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialMessage.d.ts +19 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialMessage.js +69 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialResolver.d.ts +46 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialResolver.js +237 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialTemplate.d.ts +2 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialTemplate.js +90 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/types.d.ts +99 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/types.js +2 -0
- package/node_modules/@silicaclaw/storage/dist/packages/storage/config/silicaclaw-defaults.json +19 -0
- package/node_modules/@silicaclaw/storage/dist/packages/storage/src/index.d.ts +3 -0
- package/node_modules/@silicaclaw/storage/dist/packages/storage/src/index.js +19 -0
- package/node_modules/@silicaclaw/storage/dist/packages/storage/src/jsonRepo.d.ts +7 -0
- package/node_modules/@silicaclaw/storage/dist/packages/storage/src/jsonRepo.js +29 -0
- package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.d.ts +73 -0
- package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.js +85 -0
- package/node_modules/@silicaclaw/storage/dist/packages/storage/src/socialRuntimeRepo.d.ts +5 -0
- package/node_modules/@silicaclaw/storage/dist/packages/storage/src/socialRuntimeRepo.js +57 -0
- package/node_modules/@silicaclaw/storage/dist/socialRuntimeRepo.js +8 -4
- package/node_modules/@silicaclaw/storage/src/repos.ts +31 -1
- package/node_modules/@silicaclaw/storage/src/socialRuntimeRepo.ts +5 -4
- package/node_modules/@silicaclaw/storage/tsconfig.json +1 -6
- package/openclaw-skills/silicaclaw-bridge-setup/SKILL.md +165 -0
- package/openclaw-skills/silicaclaw-bridge-setup/VERSION +1 -0
- package/openclaw-skills/silicaclaw-bridge-setup/agents/openai.yaml +6 -0
- package/openclaw-skills/silicaclaw-bridge-setup/manifest.json +27 -0
- package/openclaw-skills/silicaclaw-bridge-setup/references/owner-dialogue-cheatsheet-zh.md +58 -0
- package/openclaw-skills/silicaclaw-bridge-setup/references/runtime-setup.md +43 -0
- package/openclaw-skills/silicaclaw-bridge-setup/references/troubleshooting.md +24 -0
- package/openclaw-skills/silicaclaw-broadcast/SKILL.md +150 -0
- package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -1
- package/openclaw-skills/silicaclaw-broadcast/agents/openai.yaml +2 -2
- package/openclaw-skills/silicaclaw-broadcast/manifest.json +4 -3
- package/openclaw-skills/silicaclaw-broadcast/references/owner-dialogue-cheatsheet-zh.md +81 -0
- package/openclaw-skills/silicaclaw-network-config/SKILL.md +158 -0
- package/openclaw-skills/silicaclaw-network-config/VERSION +1 -0
- package/openclaw-skills/silicaclaw-network-config/agents/openai.yaml +6 -0
- package/openclaw-skills/silicaclaw-network-config/manifest.json +27 -0
- package/openclaw-skills/silicaclaw-network-config/references/network-modes.md +22 -0
- package/openclaw-skills/silicaclaw-network-config/references/owner-dialogue-cheatsheet-zh.md +47 -0
- package/openclaw-skills/silicaclaw-network-config/references/public-discovery.md +22 -0
- package/openclaw-skills/silicaclaw-owner-push/SKILL.md +235 -0
- package/openclaw-skills/silicaclaw-owner-push/VERSION +1 -0
- package/openclaw-skills/silicaclaw-owner-push/agents/openai.yaml +6 -0
- package/openclaw-skills/silicaclaw-owner-push/manifest.json +30 -0
- package/openclaw-skills/silicaclaw-owner-push/references/owner-dialogue-cheatsheet-zh.md +87 -0
- package/openclaw-skills/silicaclaw-owner-push/references/push-routing-policy.md +43 -0
- package/openclaw-skills/silicaclaw-owner-push/references/runtime-setup.md +44 -0
- package/openclaw-skills/silicaclaw-owner-push/scripts/owner-push-forwarder.mjs +356 -0
- package/openclaw-skills/silicaclaw-owner-push/scripts/send-to-owner-via-openclaw.mjs +69 -0
- package/package.json +5 -1
- package/packages/core/dist/config/silicaclaw-defaults.json +19 -0
- package/packages/core/dist/packages/core/src/crypto.d.ts +6 -0
- package/packages/core/dist/packages/core/src/crypto.js +50 -0
- package/packages/core/dist/packages/core/src/directory.d.ts +17 -0
- package/packages/core/dist/packages/core/src/directory.js +145 -0
- package/packages/core/dist/packages/core/src/identity.d.ts +2 -0
- package/packages/core/dist/packages/core/src/identity.js +18 -0
- package/packages/core/dist/packages/core/src/index.d.ts +14 -0
- package/packages/core/dist/packages/core/src/index.js +30 -0
- package/packages/core/dist/packages/core/src/indexing.d.ts +6 -0
- package/packages/core/dist/packages/core/src/indexing.js +43 -0
- package/packages/core/dist/packages/core/src/presence.d.ts +4 -0
- package/packages/core/dist/packages/core/src/presence.js +23 -0
- package/packages/core/dist/packages/core/src/privateCrypto.d.ts +17 -0
- package/packages/core/dist/packages/core/src/privateCrypto.js +40 -0
- package/packages/core/dist/packages/core/src/privateMessage.d.ts +23 -0
- package/packages/core/dist/packages/core/src/privateMessage.js +74 -0
- package/packages/core/dist/packages/core/src/profile.d.ts +4 -0
- package/packages/core/dist/packages/core/src/profile.js +41 -0
- package/packages/core/dist/packages/core/src/publicProfileSummary.d.ts +74 -0
- package/packages/core/dist/packages/core/src/publicProfileSummary.js +106 -0
- package/packages/core/dist/packages/core/src/socialConfig.d.ts +100 -0
- package/packages/core/dist/packages/core/src/socialConfig.js +300 -0
- package/packages/core/dist/packages/core/src/socialMessage.d.ts +19 -0
- package/packages/core/dist/packages/core/src/socialMessage.js +69 -0
- package/packages/core/dist/packages/core/src/socialResolver.d.ts +46 -0
- package/packages/core/dist/packages/core/src/socialResolver.js +237 -0
- package/packages/core/dist/packages/core/src/socialTemplate.d.ts +2 -0
- package/packages/core/dist/packages/core/src/socialTemplate.js +90 -0
- package/packages/core/dist/packages/core/src/types.d.ts +99 -0
- package/packages/core/dist/packages/core/src/types.js +2 -0
- package/packages/core/src/index.ts +2 -0
- package/packages/core/src/privateCrypto.ts +57 -0
- package/packages/core/src/privateMessage.ts +101 -0
- package/packages/core/src/profile.ts +2 -0
- package/packages/core/src/publicProfileSummary.ts +7 -0
- package/packages/core/src/socialConfig.ts +7 -5
- package/packages/core/src/types.ts +44 -0
- package/packages/network/dist/config/silicaclaw-defaults.json +19 -0
- package/packages/network/dist/packages/network/src/abstractions/messageEnvelope.d.ts +28 -0
- package/packages/network/dist/packages/network/src/abstractions/messageEnvelope.js +36 -0
- package/packages/network/dist/packages/network/src/abstractions/peerDiscovery.d.ts +43 -0
- package/packages/network/dist/packages/network/src/abstractions/peerDiscovery.js +2 -0
- package/packages/network/dist/packages/network/src/abstractions/topicCodec.d.ts +4 -0
- package/packages/network/dist/packages/network/src/abstractions/topicCodec.js +2 -0
- package/packages/network/dist/packages/network/src/abstractions/transport.d.ts +36 -0
- package/packages/network/dist/packages/network/src/abstractions/transport.js +2 -0
- package/packages/network/dist/packages/network/src/codec/jsonMessageEnvelopeCodec.d.ts +5 -0
- package/packages/network/dist/packages/network/src/codec/jsonMessageEnvelopeCodec.js +24 -0
- package/packages/network/dist/packages/network/src/codec/jsonTopicCodec.d.ts +5 -0
- package/packages/network/dist/packages/network/src/codec/jsonTopicCodec.js +12 -0
- package/packages/network/dist/packages/network/src/discovery/heartbeatPeerDiscovery.d.ts +28 -0
- package/packages/network/dist/packages/network/src/discovery/heartbeatPeerDiscovery.js +144 -0
- package/packages/network/dist/packages/network/src/index.d.ts +14 -0
- package/packages/network/dist/packages/network/src/index.js +30 -0
- package/packages/network/dist/packages/network/src/localEventBus.d.ts +9 -0
- package/packages/network/dist/packages/network/src/localEventBus.js +47 -0
- package/packages/network/dist/packages/network/src/mock.d.ts +8 -0
- package/packages/network/dist/packages/network/src/mock.js +24 -0
- package/packages/network/dist/packages/network/src/realPreview.d.ts +105 -0
- package/packages/network/dist/packages/network/src/realPreview.js +331 -0
- package/packages/network/dist/packages/network/src/relayPreview.d.ts +178 -0
- package/packages/network/dist/packages/network/src/relayPreview.js +548 -0
- package/packages/network/dist/packages/network/src/transport/udpLanBroadcastTransport.d.ts +23 -0
- package/packages/network/dist/packages/network/src/transport/udpLanBroadcastTransport.js +154 -0
- package/packages/network/dist/packages/network/src/types.d.ts +10 -0
- package/packages/network/dist/packages/network/src/types.js +2 -0
- package/packages/network/dist/packages/network/src/webrtcPreview.d.ts +163 -0
- package/packages/network/dist/packages/network/src/webrtcPreview.js +848 -0
- package/packages/network/src/realPreview.ts +3 -2
- package/packages/network/src/relayPreview.ts +125 -12
- package/packages/network/src/transport/udpLanBroadcastTransport.ts +2 -1
- package/packages/network/src/types.ts +2 -0
- package/packages/network/src/webrtcPreview.ts +2 -1
- package/packages/storage/config/silicaclaw-defaults.json +19 -0
- package/packages/storage/dist/config/silicaclaw-defaults.json +19 -0
- package/packages/storage/dist/packages/core/src/crypto.d.ts +6 -0
- package/packages/storage/dist/packages/core/src/crypto.js +50 -0
- package/packages/storage/dist/packages/core/src/directory.d.ts +17 -0
- package/packages/storage/dist/packages/core/src/directory.js +145 -0
- package/packages/storage/dist/packages/core/src/identity.d.ts +2 -0
- package/packages/storage/dist/packages/core/src/identity.js +18 -0
- package/packages/storage/dist/packages/core/src/index.d.ts +14 -0
- package/packages/storage/dist/packages/core/src/index.js +30 -0
- package/packages/storage/dist/packages/core/src/indexing.d.ts +6 -0
- package/packages/storage/dist/packages/core/src/indexing.js +43 -0
- package/packages/storage/dist/packages/core/src/presence.d.ts +4 -0
- package/packages/storage/dist/packages/core/src/presence.js +23 -0
- package/packages/storage/dist/packages/core/src/privateCrypto.d.ts +17 -0
- package/packages/storage/dist/packages/core/src/privateCrypto.js +40 -0
- package/packages/storage/dist/packages/core/src/privateMessage.d.ts +23 -0
- package/packages/storage/dist/packages/core/src/privateMessage.js +74 -0
- package/packages/storage/dist/packages/core/src/profile.d.ts +4 -0
- package/packages/storage/dist/packages/core/src/profile.js +41 -0
- package/packages/storage/dist/packages/core/src/publicProfileSummary.d.ts +74 -0
- package/packages/storage/dist/packages/core/src/publicProfileSummary.js +106 -0
- package/packages/storage/dist/packages/core/src/socialConfig.d.ts +100 -0
- package/packages/storage/dist/packages/core/src/socialConfig.js +300 -0
- package/packages/storage/dist/packages/core/src/socialMessage.d.ts +19 -0
- package/packages/storage/dist/packages/core/src/socialMessage.js +69 -0
- package/packages/storage/dist/packages/core/src/socialResolver.d.ts +46 -0
- package/packages/storage/dist/packages/core/src/socialResolver.js +237 -0
- package/packages/storage/dist/packages/core/src/socialTemplate.d.ts +2 -0
- package/packages/storage/dist/packages/core/src/socialTemplate.js +90 -0
- package/packages/storage/dist/packages/core/src/types.d.ts +99 -0
- package/packages/storage/dist/packages/core/src/types.js +2 -0
- package/packages/storage/dist/packages/storage/config/silicaclaw-defaults.json +19 -0
- package/packages/storage/dist/packages/storage/src/index.d.ts +3 -0
- package/packages/storage/dist/packages/storage/src/index.js +19 -0
- package/packages/storage/dist/packages/storage/src/jsonRepo.d.ts +7 -0
- package/packages/storage/dist/packages/storage/src/jsonRepo.js +29 -0
- package/packages/storage/dist/packages/storage/src/repos.d.ts +73 -0
- package/packages/storage/dist/packages/storage/src/repos.js +85 -0
- package/packages/storage/dist/packages/storage/src/socialRuntimeRepo.d.ts +5 -0
- package/packages/storage/dist/packages/storage/src/socialRuntimeRepo.js +57 -0
- package/packages/storage/dist/socialRuntimeRepo.js +8 -4
- package/packages/storage/src/repos.ts +31 -1
- package/packages/storage/src/socialRuntimeRepo.ts +5 -4
- package/packages/storage/tsconfig.json +1 -6
- package/scripts/functional-check.mjs +35 -6
- package/scripts/install-openclaw-skill.mjs +9 -2
- package/scripts/openclaw-bridge-adapter.mjs +3 -1
- package/scripts/openclaw-bridge-client.mjs +3 -1
- package/scripts/openclaw-runtime-demo.mjs +3 -1
- package/scripts/quickstart.sh +14 -10
- package/scripts/release-pack.mjs +59 -1
- package/scripts/silicaclaw-cli.mjs +166 -51
- package/scripts/silicaclaw-gateway.mjs +410 -84
- 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
|
|
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 ||
|
|
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 ||
|
|
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
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
71
|
-
|
|
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 = (
|
|
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
|
|
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
|
|
257
|
-
const
|
|
258
|
-
|
|
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
|
|
322
|
-
|
|
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(
|
|
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:
|
|
355
|
-
?
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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 = (
|
|
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(
|
|
454
|
-
const
|
|
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
|
-
|
|
457
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
476
|
-
(
|
|
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 =
|
|
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 =
|
|
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.
|
|
866
|
+
let loadedSocial = (0, core_1.loadSocialConfig)(this.projectRoot);
|
|
560
867
|
if (!loadedSocial.meta.found) {
|
|
561
|
-
(0, core_1.ensureDefaultSocialMd)(this.
|
|
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:
|
|
872
|
+
mode: DEFAULT_NETWORK_MODE,
|
|
566
873
|
public_enabled: false,
|
|
567
874
|
});
|
|
568
|
-
loadedSocial = (0, core_1.loadSocialConfig)(this.
|
|
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 ||
|
|
577
|
-
this.networkPort = Number(this.socialConfig.network.port || process.env.NETWORK_PORT ||
|
|
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.
|
|
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.
|
|
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.
|
|
618
|
-
|
|
619
|
-
const
|
|
620
|
-
const
|
|
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:
|
|
949
|
+
discovered_count: discovered.length,
|
|
631
950
|
online_count: onlineCount,
|
|
632
|
-
offline_count: Math.max(0,
|
|
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
|
|
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:
|
|
1010
|
+
webrtc_preview: relayCapable
|
|
681
1011
|
? {
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
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
|
|
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:
|
|
1050
|
+
adapter_extra: relayCapable
|
|
717
1051
|
? {
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
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
|
-
? "
|
|
1095
|
+
? "global-preview"
|
|
760
1096
|
: "local-process",
|
|
761
1097
|
mode_explainer: this.getModeExplainer(),
|
|
762
1098
|
};
|
|
763
1099
|
}
|
|
764
1100
|
getNetworkStats() {
|
|
765
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
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
|
|
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
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
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.
|
|
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
|
|
929
|
-
const
|
|
930
|
-
const
|
|
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
|
-
:
|
|
950
|
-
? "
|
|
951
|
-
:
|
|
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
|
|
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
|
-
|
|
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.
|
|
1002
|
-
network_requires_restart:
|
|
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 ||
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
1151
|
-
const openclawRuntime =
|
|
1725
|
+
const openclawInstallation = detectOpenClawInstallation(this.projectRoot);
|
|
1726
|
+
const openclawRuntime = this.getCachedOpenClawRuntime();
|
|
1152
1727
|
const skillInstallation = detectOpenClawSkillInstallation();
|
|
1153
1728
|
const ownerDelivery = detectOwnerDeliveryStatus({
|
|
1154
|
-
workspaceRoot: this.
|
|
1729
|
+
workspaceRoot: this.projectRoot,
|
|
1155
1730
|
connectedToSilicaclaw: integration.connected_to_silicaclaw,
|
|
1156
1731
|
openclawRunning: openclawRuntime.running,
|
|
1157
1732
|
skillInstalled: skillInstallation.installed,
|
|
1158
1733
|
});
|
|
1159
|
-
|
|
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
|
|
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 = (
|
|
1243
|
-
const openclawRuntime =
|
|
1827
|
+
const openclawSourceDir = defaultOpenClawSourceDir(this.projectRoot);
|
|
1828
|
+
const openclawRuntime = this.getCachedOpenClawRuntime();
|
|
1244
1829
|
return {
|
|
1245
|
-
bridge_api_base:
|
|
1246
|
-
openclaw_detected: detectOpenClawInstallation(this.
|
|
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-
|
|
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:
|
|
1348
|
-
installed_count:
|
|
1349
|
-
installed_bundled_count:
|
|
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:
|
|
1353
|
-
installed_skills:
|
|
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
|
|
2201
|
+
const shouldPublishProfile = this.shouldPublishProfileRecord(profileRecord, reason, presenceRecord.timestamp);
|
|
2202
|
+
const replayMessages = this.getReplayableSelfSocialMessages(reason);
|
|
1572
2203
|
try {
|
|
1573
|
-
|
|
2204
|
+
if (shouldPublishProfile) {
|
|
2205
|
+
await this.publish("profile", profileRecord);
|
|
2206
|
+
}
|
|
1574
2207
|
await this.publish("presence", presenceRecord);
|
|
1575
|
-
for (const
|
|
1576
|
-
await this.publish(
|
|
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 (${
|
|
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.
|
|
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.
|
|
2313
|
+
rootDir: this.projectRoot,
|
|
1635
2314
|
});
|
|
1636
|
-
this.profile = (0, core_1.signProfile)(
|
|
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.
|
|
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.
|
|
2347
|
+
rootDir: this.projectRoot,
|
|
1664
2348
|
});
|
|
1665
|
-
const nextProfile = (0, core_1.signProfile)(
|
|
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
|
-
|
|
2659
|
+
this.subscriptionsBound = false;
|
|
1927
2660
|
this.bindNetworkSubscriptions();
|
|
1928
|
-
this.
|
|
1929
|
-
|
|
1930
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
:
|
|
3034
|
+
: DEFAULT_NETWORK_MODE);
|
|
2070
3035
|
this.networkMode = resolvedMode;
|
|
2071
|
-
this.networkNamespace = this.socialConfig.network.namespace || process.env.NETWORK_NAMESPACE ||
|
|
2072
|
-
this.networkPort = Number(this.socialConfig.network.port || process.env.NETWORK_PORT ||
|
|
2073
|
-
const builtInGlobalSignalingUrls = [
|
|
2074
|
-
const builtInGlobalRoom =
|
|
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 = [
|
|
2103
|
-
signalingSource =
|
|
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
|
-
|
|
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
|
-
:
|
|
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
|
|
2445
|
-
setHtml('agentsWrap', data.agentsWrapHtml || '<div class="label">No discovered
|
|
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 ||
|
|
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 (
|
|
3824
|
+
app.post("/api/openclaw/bridge/skill-install", asyncRoute(async (req, res) => {
|
|
2595
3825
|
try {
|
|
2596
|
-
const
|
|
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", "
|
|
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
|
|
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}
|
|
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);
|