shieldcortex 4.28.0 → 4.29.0
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/dashboard/.next/standalone/dashboard/.next/BUILD_ID +1 -1
- package/dashboard/.next/standalone/dashboard/.next/build-manifest.json +2 -2
- package/dashboard/.next/standalone/dashboard/.next/prerender-manifest.json +3 -3
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/admin.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/admin.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/!KGRhc2hib2FyZCk/admin/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/!KGRhc2hib2FyZCk/admin.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/!KGRhc2hib2FyZCk/cloud/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/!KGRhc2hib2FyZCk/cloud.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/!KGRhc2hib2FyZCk/memory/capture/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/!KGRhc2hib2FyZCk/memory/capture.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/!KGRhc2hib2FyZCk/memory/graph/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/!KGRhc2hib2FyZCk/memory/graph.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/!KGRhc2hib2FyZCk/memory/recall/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/!KGRhc2hib2FyZCk/memory/recall.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/!KGRhc2hib2FyZCk/memory/replay/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/!KGRhc2hib2FyZCk/memory/replay.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/!KGRhc2hib2FyZCk/memory/review/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/!KGRhc2hib2FyZCk/memory/review.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/!KGRhc2hib2FyZCk/memory/timeline/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/!KGRhc2hib2FyZCk/memory/timeline.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/!KGRhc2hib2FyZCk/memory/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/overview.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/overview.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/!KGRhc2hib2FyZCk/overview/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/!KGRhc2hib2FyZCk/overview.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/!KGRhc2hib2FyZCk/protection/audit/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/!KGRhc2hib2FyZCk/protection/audit.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/!KGRhc2hib2FyZCk/protection/intercepts/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/!KGRhc2hib2FyZCk/protection/intercepts.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/!KGRhc2hib2FyZCk/protection/iron-dome/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/!KGRhc2hib2FyZCk/protection/iron-dome.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/!KGRhc2hib2FyZCk/protection/policies/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/!KGRhc2hib2FyZCk/protection/policies.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/!KGRhc2hib2FyZCk/protection/quarantine/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/!KGRhc2hib2FyZCk/protection/quarantine.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/!KGRhc2hib2FyZCk/protection/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/settings.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/settings.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/!KGRhc2hib2FyZCk/settings/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/!KGRhc2hib2FyZCk/settings.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/!KGRhc2hib2FyZCk/supply-chain/xray/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/!KGRhc2hib2FyZCk/supply-chain/xray.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/!KGRhc2hib2FyZCk/supply-chain.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/!KGRhc2hib2FyZCk/supply-chain/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/!KGRhc2hib2FyZCk/supply-chain.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/xray.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/xray.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/!KGRhc2hib2FyZCk/xray/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/!KGRhc2hib2FyZCk/xray.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/pages/404.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/pages/500.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.js +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.json +1 -1
- package/dist/cli/doctor.d.ts +17 -0
- package/dist/cli/doctor.js +44 -0
- package/dist/cli/memory.d.ts +23 -0
- package/dist/cli/memory.js +101 -0
- package/dist/database/init.d.ts +9 -0
- package/dist/database/init.js +32 -4
- package/dist/database/migrations.js +126 -0
- package/dist/license/store.js +42 -2
- package/dist/memory/consolidate.d.ts +82 -6
- package/dist/memory/consolidate.js +287 -117
- package/dist/setup/openclaw.d.ts +26 -0
- package/dist/setup/openclaw.js +61 -0
- package/dist/tools/remember.js +24 -3
- package/hooks/openclaw/cortex-memory/handler.ts +223 -171
- package/hooks/openclaw/cortex-memory/runtime.mjs +64 -0
- package/package.json +1 -1
- package/scripts/lib/dedup.mjs +99 -0
- package/scripts/lib/openclaw-extract.mjs +129 -0
- package/scripts/lib/recall-log.mjs +16 -1
- package/scripts/lib/recall-relevance.mjs +191 -0
- package/scripts/lib/salience.mjs +8 -3
- package/scripts/lib/save-memory.mjs +62 -6
- package/scripts/lib/session-context.mjs +30 -0
- package/scripts/postinstall.mjs +29 -10
- package/scripts/prompt-recall-hook.mjs +118 -15
- package/scripts/session-start-hook.mjs +17 -6
- /package/dashboard/.next/standalone/dashboard/.next/static/{3HiFjAMoQUDn05ZMNC0Ae → 0HpUm8SRvm9fnWVO0OBB2}/_buildManifest.js +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{3HiFjAMoQUDn05ZMNC0Ae → 0HpUm8SRvm9fnWVO0OBB2}/_clientMiddlewareManifest.json +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{3HiFjAMoQUDn05ZMNC0Ae → 0HpUm8SRvm9fnWVO0OBB2}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, synchronous near-duplicate detection for the hook write path.
|
|
3
|
+
*
|
|
4
|
+
* No DB, no native deps, no dist dependency — this lets the hook writer
|
|
5
|
+
* (save-memory.mjs) dedup even in a dev workspace where `dist/` hasn't been
|
|
6
|
+
* built. It is deliberately a .mjs sibling rather than an import of
|
|
7
|
+
* dist/memory/similarity.js so the write path never gains a build dependency.
|
|
8
|
+
*
|
|
9
|
+
* The Jaccard algorithm below MIRRORS src/memory/similarity.ts:tokenize/
|
|
10
|
+
* jaccardSimilarity so the write-path gate and the consolidate/contradiction
|
|
11
|
+
* paths agree on what "similar" means. Keep them in lockstep if either moves.
|
|
12
|
+
*
|
|
13
|
+
* NOTE (future convergence): hooks/openclaw/cortex-memory/handler.ts has its
|
|
14
|
+
* own `inspectNovelty` Jaccard gate. This module is the candidate convergence
|
|
15
|
+
* point for all three, but the handler is intentionally left untouched here.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Tokenize text into a set of normalized words.
|
|
20
|
+
* Lowercase, strip punctuation to whitespace, split on whitespace, drop words
|
|
21
|
+
* of length <= 2. Mirrors src/memory/similarity.ts:tokenize exactly.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} text
|
|
24
|
+
* @returns {Set<string>}
|
|
25
|
+
*/
|
|
26
|
+
export function tokenize(text) {
|
|
27
|
+
return new Set(
|
|
28
|
+
String(text ?? '')
|
|
29
|
+
.toLowerCase()
|
|
30
|
+
.replace(/[^\w\s]/g, ' ') // punctuation -> space
|
|
31
|
+
.split(/\s+/)
|
|
32
|
+
.filter((word) => word.length > 2), // drop very short words (and empties)
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Jaccard similarity between two texts: |A ∩ B| / |A ∪ B|.
|
|
38
|
+
* Mirrors src/memory/similarity.ts:jaccardSimilarity (two empty token sets => 1.0).
|
|
39
|
+
*
|
|
40
|
+
* @param {string} textA
|
|
41
|
+
* @param {string} textB
|
|
42
|
+
* @returns {number} 0..1
|
|
43
|
+
*/
|
|
44
|
+
export function jaccardSimilarity(textA, textB) {
|
|
45
|
+
const setA = tokenize(textA);
|
|
46
|
+
const setB = tokenize(textB);
|
|
47
|
+
|
|
48
|
+
if (setA.size === 0 && setB.size === 0) return 1.0;
|
|
49
|
+
if (setA.size === 0 || setB.size === 0) return 0.0;
|
|
50
|
+
|
|
51
|
+
let intersection = 0;
|
|
52
|
+
for (const word of setA) {
|
|
53
|
+
if (setB.has(word)) intersection++;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const union = setA.size + setB.size - intersection;
|
|
57
|
+
return intersection / union;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Decide whether `candidate` is a near-duplicate of `existing`.
|
|
62
|
+
*
|
|
63
|
+
* Two-stage to keep cost down and avoid false merges:
|
|
64
|
+
* 1. Title-Jaccard PRE-GATE — only consider pairs whose titles already
|
|
65
|
+
* overlap (>= titleJaccard). If titles are unrelated we never compute
|
|
66
|
+
* content similarity. This both bounds work and stops two genuinely
|
|
67
|
+
* different notes that happen to reuse a few content words from merging.
|
|
68
|
+
* 2. Combined score — content*0.6 + title*0.4, matching the weighting in
|
|
69
|
+
* src/memory/consolidate.ts (~L603-613). >= combinedThreshold => dup.
|
|
70
|
+
*
|
|
71
|
+
* @param {{title: string, content: string}} candidate — the incoming write
|
|
72
|
+
* @param {{title: string, content: string}} existing — a stored row
|
|
73
|
+
* @param {{titleJaccard: number, combinedThreshold: number}} thresholds
|
|
74
|
+
* @returns {{ duplicate: boolean, combined: number, titleSim: number }}
|
|
75
|
+
*/
|
|
76
|
+
export function isNearDuplicate(candidate, existing, { titleJaccard, combinedThreshold }) {
|
|
77
|
+
// Empty-title guard: jaccardSimilarity() (faithful to similarity.ts) returns
|
|
78
|
+
// 1.0 for two empty token sets, but for a dedup PRE-GATE that's a false
|
|
79
|
+
// match — two titles that tokenize to nothing (e.g. "A", "PC", a bare digit)
|
|
80
|
+
// carry no overlap signal and must NOT auto-pass the gate. Require real title
|
|
81
|
+
// tokens on both sides before trusting titleSim.
|
|
82
|
+
const candTitleTokens = tokenize(candidate.title);
|
|
83
|
+
const existTitleTokens = tokenize(existing.title);
|
|
84
|
+
if (candTitleTokens.size === 0 || existTitleTokens.size === 0) {
|
|
85
|
+
return { duplicate: false, combined: 0, titleSim: 0 };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const titleSim = jaccardSimilarity(candidate.title, existing.title);
|
|
89
|
+
|
|
90
|
+
// Pre-gate: unrelated titles short-circuit before any content work.
|
|
91
|
+
if (titleSim < titleJaccard) {
|
|
92
|
+
return { duplicate: false, combined: 0, titleSim };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const contentSim = jaccardSimilarity(candidate.content, existing.content);
|
|
96
|
+
const combined = contentSim * 0.6 + titleSim * 0.4;
|
|
97
|
+
|
|
98
|
+
return { duplicate: combined >= combinedThreshold, combined, titleSim };
|
|
99
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure extraction wrapper for the OpenClaw cortex-memory hook.
|
|
3
|
+
*
|
|
4
|
+
* The OpenClaw hook runs inside a LONG-LIVED gateway process. It must NOT
|
|
5
|
+
* touch the native DB path (saveAutoExtractedMemory / initDatabase install
|
|
6
|
+
* global shutdown handlers and open better-sqlite3 — a confirmed crash-loop
|
|
7
|
+
* mechanism). Persistence happens via the existing `callCortex("remember")`
|
|
8
|
+
* shell-out, which runs in a throwaway mcporter subprocess.
|
|
9
|
+
*
|
|
10
|
+
* This module exists so the hook gets the SAME extraction QUALITY as the
|
|
11
|
+
* Claude-Code side (sentence-bounded capture, rejection corpus, deterministic
|
|
12
|
+
* taxonomy, 0.6 salience cap) without importing anything native. It depends
|
|
13
|
+
* ONLY on the pure chunker — string in, plain objects out, no DB, no runtime.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
extractMemorableSegments,
|
|
18
|
+
processSegments,
|
|
19
|
+
shouldRejectCandidate,
|
|
20
|
+
EXTRACTOR_TO_CATEGORY,
|
|
21
|
+
EXTRACTOR_TO_PURPOSE,
|
|
22
|
+
} from './extract-memorable-segments.mjs';
|
|
23
|
+
|
|
24
|
+
// Session extraction uses the lighter session-end threshold band. 0.30 is the
|
|
25
|
+
// same dynamic threshold the Claude-Code session-end hook passes; the chunker
|
|
26
|
+
// then applies its category-aware floors and the 0.6 cap.
|
|
27
|
+
const SESSION_DYNAMIC_THRESHOLD = 0.30;
|
|
28
|
+
|
|
29
|
+
// Explicit keyword captures ("remember this: ...") are plain user notes whose
|
|
30
|
+
// content rarely contains a chunker trigger word. When no extractor matches
|
|
31
|
+
// the content, treat it as an explicit important-note (category note,
|
|
32
|
+
// purpose project) — the chunker's own taxonomy for that type.
|
|
33
|
+
const KEYWORD_FALLBACK_EXTRACTOR = 'important-note';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Extract memories from a full session transcript.
|
|
37
|
+
*
|
|
38
|
+
* Runs the chunker over the conversation text and returns the processed
|
|
39
|
+
* memories — already rejection-filtered, deduped, taxonomy-pinned,
|
|
40
|
+
* threshold-filtered, and salience-capped at 0.6.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} conversationText
|
|
43
|
+
* @returns {Array<{ title: string, content: string, category: string, memoryPurpose: string, tags: string[] }>}
|
|
44
|
+
*/
|
|
45
|
+
export function extractSessionMemories(conversationText) {
|
|
46
|
+
if (!conversationText || typeof conversationText !== 'string') return [];
|
|
47
|
+
|
|
48
|
+
const segments = extractMemorableSegments(conversationText);
|
|
49
|
+
if (segments.length === 0) return [];
|
|
50
|
+
|
|
51
|
+
const processed = processSegments(segments, SESSION_DYNAMIC_THRESHOLD, {
|
|
52
|
+
conversationText,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return Array.isArray(processed) ? processed : [];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Derive the chunker extractor type for an explicit keyword capture.
|
|
60
|
+
*
|
|
61
|
+
* Reuses the chunker: if the content itself contains a recognised trigger
|
|
62
|
+
* shape (a decision, a fix, a learning, etc.), adopt that extractor's type so
|
|
63
|
+
* the taxonomy is accurate. Otherwise fall back to an explicit important-note.
|
|
64
|
+
* The longest captured segment wins (most context retained).
|
|
65
|
+
*/
|
|
66
|
+
function deriveKeywordExtractorType(content) {
|
|
67
|
+
const segments = extractMemorableSegments(content);
|
|
68
|
+
if (segments.length === 0) return KEYWORD_FALLBACK_EXTRACTOR;
|
|
69
|
+
let best = segments[0];
|
|
70
|
+
for (const seg of segments) {
|
|
71
|
+
if (seg.content.length > best.content.length) best = seg;
|
|
72
|
+
}
|
|
73
|
+
return best.extractorType || KEYWORD_FALLBACK_EXTRACTOR;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Build a single memory from an EXPLICIT keyword trigger.
|
|
78
|
+
*
|
|
79
|
+
* Explicit user intent ("remember this", "for the record", ...) must never be
|
|
80
|
+
* silently dropped by the salience threshold (design B8). So this path applies
|
|
81
|
+
* ONLY the rejection corpus (to drop true malformations) and bypasses the
|
|
82
|
+
* threshold. It still derives category + memory_purpose from the chunker's
|
|
83
|
+
* deterministic taxonomy — no new classification logic.
|
|
84
|
+
*
|
|
85
|
+
* Classification is driven by the AUTHORITATIVE extractorType passed in from
|
|
86
|
+
* the matched trigger (the trigger phrase carries the intent signal, e.g.
|
|
87
|
+
* "the fix was" → error-fix). When extractorType is absent or unknown we fall
|
|
88
|
+
* back to re-scanning the content with the chunker (the truly-generic case).
|
|
89
|
+
* We never silently collapse a typed trigger to a generic `note`.
|
|
90
|
+
*
|
|
91
|
+
* @param {string} content — the captured content after the trigger
|
|
92
|
+
* @param {string} [extractorType] — authoritative chunker extractor type from
|
|
93
|
+
* the matched trigger (one of EXTRACTOR_TO_CATEGORY's keys). Optional.
|
|
94
|
+
* @returns {Array<{ title: string, content: string, category: string, memoryPurpose: string }>}
|
|
95
|
+
* exactly ONE memory, or [] if the rejection corpus flags it as malformed.
|
|
96
|
+
*/
|
|
97
|
+
export function extractKeywordMemory(content, extractorType) {
|
|
98
|
+
if (!content || typeof content !== 'string') return [];
|
|
99
|
+
const trimmed = content.trim();
|
|
100
|
+
if (trimmed.length < 5) return [];
|
|
101
|
+
|
|
102
|
+
// The trigger's extractorType is authoritative when supplied & recognised;
|
|
103
|
+
// otherwise re-scan the content (generic triggers like "remember this" whose
|
|
104
|
+
// text may itself carry a decision/fix/learning shape).
|
|
105
|
+
const resolvedType =
|
|
106
|
+
extractorType && extractorType in EXTRACTOR_TO_CATEGORY
|
|
107
|
+
? extractorType
|
|
108
|
+
: deriveKeywordExtractorType(trimmed);
|
|
109
|
+
|
|
110
|
+
const candidate = { title: '', content: trimmed.slice(0, 500), extractorType: resolvedType };
|
|
111
|
+
|
|
112
|
+
// Rejection corpus only — drop true malformations (bare imperatives,
|
|
113
|
+
// negation-scope drops, email-body bleed, etc.). NOT the salience threshold.
|
|
114
|
+
if (shouldRejectCandidate(candidate, trimmed).rejected) return [];
|
|
115
|
+
|
|
116
|
+
const category = EXTRACTOR_TO_CATEGORY[resolvedType] ?? 'note';
|
|
117
|
+
const memoryPurpose = EXTRACTOR_TO_PURPOSE[resolvedType] ?? 'project';
|
|
118
|
+
|
|
119
|
+
const title = trimmed.slice(0, 80).replace(/["\n]/g, ' ').trim();
|
|
120
|
+
|
|
121
|
+
return [
|
|
122
|
+
{
|
|
123
|
+
title,
|
|
124
|
+
content: candidate.content,
|
|
125
|
+
category,
|
|
126
|
+
memoryPurpose,
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
}
|
|
@@ -98,7 +98,22 @@ function rotate() {
|
|
|
98
98
|
* source?: 'fts' | 'category-boost',
|
|
99
99
|
* effectiveSalience?: number,
|
|
100
100
|
* injected?: boolean,
|
|
101
|
-
*
|
|
101
|
+
* // Why a candidate was not injected (null = injected). Recognised values:
|
|
102
|
+
* // 'dedupe' — content hash seen in a recent turn
|
|
103
|
+
* // 'outside_top_n' — ranked below the MAX_RESULTS cut
|
|
104
|
+
* // 'not_injected' — early-exit before the dedupe filter ran
|
|
105
|
+
* // 'below_term_coverage' — P4 gate: matched too few distinct query terms
|
|
106
|
+
* // 'below_relevance_floor' — P4 gate: BM25 rank below the relative floor
|
|
107
|
+
* // The two P4 reasons appear in SHADOW mode too (the row is still
|
|
108
|
+
* // injected then) so `shieldcortex inspect last-recall` can show
|
|
109
|
+
* // "considered but below floor" for threshold tuning before enforcement.
|
|
110
|
+
* dropReason?:
|
|
111
|
+
* | 'dedupe'
|
|
112
|
+
* | 'outside_top_n'
|
|
113
|
+
* | 'not_injected'
|
|
114
|
+
* | 'below_term_coverage'
|
|
115
|
+
* | 'below_relevance_floor'
|
|
116
|
+
* | null,
|
|
102
117
|
* }>,
|
|
103
118
|
* injectedCount?: number,
|
|
104
119
|
* finalContextChars?: number,
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recall relevance gate (P4, B9 design).
|
|
3
|
+
*
|
|
4
|
+
* The per-turn UserPromptSubmit recall hook runs an FTS5 OR-of-terms query.
|
|
5
|
+
* FTS5 happily returns a row that matches just ONE common term out of six —
|
|
6
|
+
* which is how off-topic, high-salience memories ended up injected every turn
|
|
7
|
+
* ("same 5 every turn, none relevant" — EDITH, 2026-05). A relative BM25 floor
|
|
8
|
+
* alone does NOT fix this: a "what is the weather today" query still returns
|
|
9
|
+
* five hits within 35% of the best rank.
|
|
10
|
+
*
|
|
11
|
+
* The load-bearing discriminator is TERM-COVERAGE: how many DISTINCT query
|
|
12
|
+
* terms a row actually matches. A 1-of-6-terms OR-match is noise; a real match
|
|
13
|
+
* covers multiple terms.
|
|
14
|
+
*
|
|
15
|
+
* Two-stage gate:
|
|
16
|
+
* 1. TERM-COVERAGE (primary). Keep iff
|
|
17
|
+
* matchedTerms >= minTermMatches
|
|
18
|
+
* OR (totalQueryTerms <= minTermMatches AND matchedTerms === totalQueryTerms)
|
|
19
|
+
* — i.e. a multi-term match, OR a terse prompt that was matched in full.
|
|
20
|
+
* So a 1-of-6 match drops; a 2-of-2 terse match keeps.
|
|
21
|
+
* 2. RELATIVE BM25 FLOOR (secondary). Among coverage survivors that carry an
|
|
22
|
+
* FTS `rank`, drop rows weaker than `relFactor` of this query's best rank.
|
|
23
|
+
* SQLite bm25 ranks are negative; more negative = more relevant, so the
|
|
24
|
+
* floor is `best * relFactor` (less negative) and we drop `rank > floor`.
|
|
25
|
+
* Rows WITHOUT a `rank` (category-boost path) have no FTS rank and are
|
|
26
|
+
* exempt from the BM25 floor — but are STILL subject to term-coverage.
|
|
27
|
+
*
|
|
28
|
+
* Pure: no DB, no env reads, no mutation of inputs. The caller resolves all
|
|
29
|
+
* options (mirror the pickNumber/env pattern in salience.mjs at the call site).
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Count how many DISTINCT query terms appear in the given text, matched
|
|
34
|
+
* case-insensitively on word-ish boundaries (NOT substring — "schema" must
|
|
35
|
+
* not match inside "schematics"). A term made entirely of non-word characters
|
|
36
|
+
* after lowercasing is skipped.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} text
|
|
39
|
+
* @param {string[]} queryTerms
|
|
40
|
+
* @returns {number}
|
|
41
|
+
*/
|
|
42
|
+
function countMatchedTerms(text, queryTerms) {
|
|
43
|
+
const haystack = String(text || '').toLowerCase();
|
|
44
|
+
let matched = 0;
|
|
45
|
+
const seen = new Set();
|
|
46
|
+
for (const raw of queryTerms) {
|
|
47
|
+
const term = String(raw || '').toLowerCase();
|
|
48
|
+
if (!term || seen.has(term)) continue;
|
|
49
|
+
seen.add(term);
|
|
50
|
+
// Word-ish boundary: the term must be bounded by a non-word-char (or
|
|
51
|
+
// string edge) on both sides. Escape regex metacharacters in the term.
|
|
52
|
+
const escaped = term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
53
|
+
const re = new RegExp(`(^|[^\\p{L}\\p{N}_])${escaped}([^\\p{L}\\p{N}_]|$)`, 'u');
|
|
54
|
+
if (re.test(haystack)) matched += 1;
|
|
55
|
+
}
|
|
56
|
+
return matched;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Apply the term-coverage + relative-BM25 relevance gate to a set of recall
|
|
61
|
+
* candidate rows.
|
|
62
|
+
*
|
|
63
|
+
* @param {Array<{ id?: any, title?: string, content?: string, rank?: number }>} rows
|
|
64
|
+
* @param {{
|
|
65
|
+
* queryTerms: string[],
|
|
66
|
+
* minTermMatches?: number,
|
|
67
|
+
* relFactor?: number,
|
|
68
|
+
* maxBm25?: number | null,
|
|
69
|
+
* }} opts
|
|
70
|
+
* @returns {{ kept: any[], dropped: Array<{ row: any, reason: 'below_term_coverage' | 'below_relevance_floor' }> }}
|
|
71
|
+
*/
|
|
72
|
+
export function filterByRelevance(rows, opts = {}) {
|
|
73
|
+
const list = Array.isArray(rows) ? rows : [];
|
|
74
|
+
const queryTerms = Array.isArray(opts.queryTerms) ? opts.queryTerms : [];
|
|
75
|
+
const minTermMatches =
|
|
76
|
+
typeof opts.minTermMatches === 'number' && Number.isFinite(opts.minTermMatches)
|
|
77
|
+
? opts.minTermMatches
|
|
78
|
+
: 2;
|
|
79
|
+
const relFactor =
|
|
80
|
+
typeof opts.relFactor === 'number' && Number.isFinite(opts.relFactor) ? opts.relFactor : 0.35;
|
|
81
|
+
// Absolute BM25 floor is OPT-IN: null/undefined (the default) means "no
|
|
82
|
+
// absolute dreg cut". It has no safe cross-corpus default — on a small/new
|
|
83
|
+
// FTS index real bm25 ranks are tiny (~-1e-6), so an absolute floor like
|
|
84
|
+
// -0.5 would drop even a perfect full-coverage match the moment an operator
|
|
85
|
+
// turns on enforce mode. Only honour it when an explicit numeric value is
|
|
86
|
+
// passed; the relative floor + term-coverage do the gating otherwise.
|
|
87
|
+
const maxBm25 =
|
|
88
|
+
typeof opts.maxBm25 === 'number' && Number.isFinite(opts.maxBm25) ? opts.maxBm25 : null;
|
|
89
|
+
|
|
90
|
+
// Distinct, non-empty query terms (lowercased) — defines totalQueryTerms.
|
|
91
|
+
const distinctTerms = [];
|
|
92
|
+
const seenTerm = new Set();
|
|
93
|
+
for (const raw of queryTerms) {
|
|
94
|
+
const t = String(raw || '').toLowerCase();
|
|
95
|
+
if (t && !seenTerm.has(t)) {
|
|
96
|
+
seenTerm.add(t);
|
|
97
|
+
distinctTerms.push(t);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const totalQueryTerms = distinctTerms.length;
|
|
101
|
+
|
|
102
|
+
// With no usable query terms there is nothing to gate on — keep everything
|
|
103
|
+
// (defensive; the hook short-circuits before calling us in that case).
|
|
104
|
+
if (totalQueryTerms === 0) {
|
|
105
|
+
return { kept: [...list], dropped: [] };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const dropped = [];
|
|
109
|
+
|
|
110
|
+
// ── Stage 1: term coverage ────────────────────────────────────────────
|
|
111
|
+
const coverageSurvivors = [];
|
|
112
|
+
for (const row of list) {
|
|
113
|
+
const text = `${row && row.title ? row.title : ''} ${row && row.content ? row.content : ''}`;
|
|
114
|
+
const matchedTerms = countMatchedTerms(text, distinctTerms);
|
|
115
|
+
const multiTerm = matchedTerms >= minTermMatches;
|
|
116
|
+
const terseFullMatch = totalQueryTerms <= minTermMatches && matchedTerms === totalQueryTerms;
|
|
117
|
+
if (multiTerm || terseFullMatch) {
|
|
118
|
+
coverageSurvivors.push(row);
|
|
119
|
+
} else {
|
|
120
|
+
dropped.push({ row, reason: 'below_term_coverage' });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ── Stage 2: relative BM25 floor (only over rows carrying an FTS rank) ──
|
|
125
|
+
const ranked = coverageSurvivors.filter(
|
|
126
|
+
(r) => typeof r.rank === 'number' && Number.isFinite(r.rank),
|
|
127
|
+
);
|
|
128
|
+
// best = most-negative (most relevant) rank among survivors.
|
|
129
|
+
let best = null;
|
|
130
|
+
for (const r of ranked) {
|
|
131
|
+
if (best === null || r.rank < best) best = r.rank;
|
|
132
|
+
}
|
|
133
|
+
// Relative floor: drop ranked rows weaker (greater, i.e. less negative) than
|
|
134
|
+
// best * relFactor. Only meaningful when best is negative (normal BM25).
|
|
135
|
+
const relativeFloor = best !== null && best < 0 ? best * relFactor : null;
|
|
136
|
+
|
|
137
|
+
const kept = [];
|
|
138
|
+
for (const row of coverageSurvivors) {
|
|
139
|
+
const hasRank = typeof row.rank === 'number' && Number.isFinite(row.rank);
|
|
140
|
+
if (hasRank) {
|
|
141
|
+
// Absolute dreg cut: a rank weaker than maxBm25 is noise regardless of
|
|
142
|
+
// the relative floor (guards a query where even the best hit is weak).
|
|
143
|
+
if (maxBm25 !== null && row.rank > maxBm25) {
|
|
144
|
+
dropped.push({ row, reason: 'below_relevance_floor' });
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (relativeFloor !== null && row.rank > relativeFloor) {
|
|
148
|
+
dropped.push({ row, reason: 'below_relevance_floor' });
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// No rank (category-boost) → exempt from the BM25 floor, already passed
|
|
153
|
+
// term coverage. Or a ranked row at/under the floor.
|
|
154
|
+
kept.push(row);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { kept, dropped };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Extract the distinct FTS query terms from a raw prompt, using the SAME
|
|
162
|
+
* normalisation the hook's escapeFts5 applies before the OR-join (strip FTS5
|
|
163
|
+
* operators, drop boolean keywords, split on whitespace, keep words longer
|
|
164
|
+
* than two chars, cap at six). Exposed here so the relevance gate and the FTS
|
|
165
|
+
* query agree on exactly which terms count.
|
|
166
|
+
*
|
|
167
|
+
* @param {string} query
|
|
168
|
+
* @returns {string[]} up to 6 distinct lowercased terms
|
|
169
|
+
*/
|
|
170
|
+
export function extractQueryTerms(query) {
|
|
171
|
+
const words = String(query || '')
|
|
172
|
+
.replace(/[*(){}[\]<>~^"]/g, ' ')
|
|
173
|
+
.replace(/\b(AND|OR|NOT|NEAR)\b/gi, '')
|
|
174
|
+
.split(/\s+/)
|
|
175
|
+
.filter((w) => w.length > 2);
|
|
176
|
+
// Dedup BEFORE the cap so repeated early terms don't crowd out distinct
|
|
177
|
+
// later ones (e.g. "drizzle drizzle drizzle migration migration rollback
|
|
178
|
+
// schema postgres journal" → up to 6 DISTINCT terms, not 3). First
|
|
179
|
+
// occurrence wins (order preserved); the slice then keeps the first 6.
|
|
180
|
+
const out = [];
|
|
181
|
+
const seen = new Set();
|
|
182
|
+
for (const w of words) {
|
|
183
|
+
const lw = w.toLowerCase();
|
|
184
|
+
if (!seen.has(lw)) {
|
|
185
|
+
seen.add(lw);
|
|
186
|
+
out.push(lw);
|
|
187
|
+
}
|
|
188
|
+
if (out.length >= 6) break;
|
|
189
|
+
}
|
|
190
|
+
return out;
|
|
191
|
+
}
|
package/scripts/lib/salience.mjs
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* effective = base × recency × access × pin × downvote_penalty
|
|
12
12
|
*
|
|
13
13
|
* recency = exp(-Δt_days / halfLifeDays) // decay
|
|
14
|
-
* access = log(1 + access_count) / log(1 + accessNorm)
|
|
14
|
+
* access = accessFloor + (1 - accessFloor) × log(1 + access_count) / log(1 + accessNorm)
|
|
15
15
|
* pin = pinned ? pinBoost : 1
|
|
16
16
|
* downvote_penalty = max(0.1, 1 - downvoteDecay × downvote_count)
|
|
17
17
|
*
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
* @param {{
|
|
31
31
|
* halfLifeDays?: number,
|
|
32
32
|
* accessNorm?: number,
|
|
33
|
+
* accessFloor?: number,
|
|
33
34
|
* pinBoost?: number,
|
|
34
35
|
* downvoteDecay?: number,
|
|
35
36
|
* now?: number,
|
|
@@ -52,6 +53,7 @@ export function computeEffectiveSalience(memory, opts = {}) {
|
|
|
52
53
|
const accessNorm = pickNumber(opts.accessNorm, 'SHIELDCORTEX_SALIENCE_ACCESS_NORM', 10);
|
|
53
54
|
const pinBoost = pickNumber(opts.pinBoost, 'SHIELDCORTEX_SALIENCE_PIN_BOOST', 1.5);
|
|
54
55
|
const downvoteDecay = pickNumber(opts.downvoteDecay, 'SHIELDCORTEX_SALIENCE_DOWNVOTE_DECAY', 0.3);
|
|
56
|
+
const accessFloor = pickNumber(opts.accessFloor, 'SHIELDCORTEX_ACCESS_FLOOR', 0.4);
|
|
55
57
|
const now = opts.now ?? Date.now();
|
|
56
58
|
|
|
57
59
|
const base = typeof memory.salience === 'number' ? memory.salience : 0;
|
|
@@ -67,9 +69,12 @@ export function computeEffectiveSalience(memory, opts = {}) {
|
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
|
|
70
|
-
// Access: log-scaled,
|
|
72
|
+
// Access: log-scaled boost, NOT a gate. Floored at accessFloor so a
|
|
73
|
+
// never-accessed memory (access_count=0, ~44% of the live DB) keeps a
|
|
74
|
+
// non-zero multiplier instead of collapsing the whole product to 0.
|
|
75
|
+
// access_count=0 → accessFloor; access_count=accessNorm → ~1.0.
|
|
71
76
|
const accessCount = Math.max(0, Number(memory.access_count) || 0);
|
|
72
|
-
const access = Math.log1p(accessCount) / Math.log1p(accessNorm);
|
|
77
|
+
const access = accessFloor + (1 - accessFloor) * (Math.log1p(accessCount) / Math.log1p(accessNorm));
|
|
73
78
|
|
|
74
79
|
// Pin: SQLite stores boolean as 0/1.
|
|
75
80
|
const pin = memory.pinned ? pinBoost : 1;
|
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
import { randomUUID } from 'crypto';
|
|
2
2
|
import { dirname, resolve } from 'path';
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
4
|
+
import { isNearDuplicate } from './dedup.mjs';
|
|
5
|
+
|
|
6
|
+
// Env-tunable dedup thresholds (pickNumber/env pattern — mirrors
|
|
7
|
+
// scripts/prompt-recall-hook.mjs). A typo'd or empty env var falls back to the
|
|
8
|
+
// documented default rather than silently zeroing the gate.
|
|
9
|
+
function pickNumber(envName, fallback) {
|
|
10
|
+
const fromEnv = Number(process.env[envName]);
|
|
11
|
+
if (Number.isFinite(fromEnv) && process.env[envName] !== undefined && process.env[envName] !== '') {
|
|
12
|
+
return fromEnv;
|
|
13
|
+
}
|
|
14
|
+
return fallback;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Title-Jaccard PRE-GATE: only pairs whose titles already overlap this much get
|
|
18
|
+
// a content comparison (bounds cost + avoids merging unrelated notes).
|
|
19
|
+
const DEDUP_TITLE_JACCARD = pickNumber('SHIELDCORTEX_DEDUP_TITLE_JACCARD', 0.6);
|
|
20
|
+
// Combined (content*0.6 + title*0.4) score at/above which the incoming write is
|
|
21
|
+
// dropped as a near-duplicate.
|
|
22
|
+
//
|
|
23
|
+
// DELIBERATE: this write-skip threshold (0.5) is STRICTER than
|
|
24
|
+
// consolidate.ts's merge threshold (0.25). Skipping silently DISCARDS the new
|
|
25
|
+
// write, so a false positive here is data loss — we demand high confidence. A
|
|
26
|
+
// consolidate false-merge only concatenates two existing rows (recoverable),
|
|
27
|
+
// so it can afford to be more aggressive. Do not lower this to match consolidate.
|
|
28
|
+
const DEDUP_COMBINED = pickNumber('SHIELDCORTEX_DEDUP_COMBINED', 0.5);
|
|
29
|
+
const DEDUP_CANDIDATE_LIMIT = 200; // bound the candidate scan per write
|
|
4
30
|
|
|
5
31
|
/**
|
|
6
32
|
* Insert an auto-extracted memory into the SC database, routed through the
|
|
@@ -76,16 +102,18 @@ export async function saveAutoExtractedMemory(db, memory, project, opts = {}) {
|
|
|
76
102
|
function insertMemoryRow(db, memory, project, sourceIdentifier) {
|
|
77
103
|
const timestamp = new Date().toISOString();
|
|
78
104
|
|
|
79
|
-
// Cross-call dedup: the hook fires repeatedly (per
|
|
80
|
-
// bypass) over overlapping transcript windows, so the
|
|
81
|
-
// tends to surface multiple times across calls. The
|
|
82
|
-
// processSegments doesn't cover that.
|
|
83
|
-
//
|
|
105
|
+
// Cross-call, CROSS-PATH exact-title dedup: the hook fires repeatedly (per
|
|
106
|
+
// turn, or per salience bypass) over overlapping transcript windows, so the
|
|
107
|
+
// same regex match tends to surface multiple times across calls. The
|
|
108
|
+
// within-batch dedup in processSegments doesn't cover that. We match on
|
|
109
|
+
// (title, project) WITHOUT a source_kind filter — incoming writes here are
|
|
110
|
+
// always hook, so dropping the filter just means a hook re-extraction of
|
|
111
|
+
// something the user ALREADY saved manually is caught too (the old
|
|
112
|
+
// source_kind='hook' filter let those through).
|
|
84
113
|
const existing = db.prepare(
|
|
85
114
|
`SELECT 1 FROM memories
|
|
86
115
|
WHERE title = ?
|
|
87
116
|
AND (project IS ? OR (project IS NULL AND ? IS NULL))
|
|
88
|
-
AND source_kind = 'hook'
|
|
89
117
|
LIMIT 1`,
|
|
90
118
|
).get(memory.title, project || null, project || null);
|
|
91
119
|
if (existing) {
|
|
@@ -93,6 +121,34 @@ function insertMemoryRow(db, memory, project, sourceIdentifier) {
|
|
|
93
121
|
return;
|
|
94
122
|
}
|
|
95
123
|
|
|
124
|
+
// Near-duplicate dedup: exact-title only catches verbatim re-saves. Reworded
|
|
125
|
+
// captures of the same fact ("Fix: X" vs "X fix") have different titles but
|
|
126
|
+
// near-identical content. Scan same-project, same-category, ACTIVE rows
|
|
127
|
+
// (most-recent first, bounded) and skip the write if any is a near-dup. This
|
|
128
|
+
// is also cross-path — a prior manual row can block a hook re-extraction.
|
|
129
|
+
const candidates = db.prepare(
|
|
130
|
+
`SELECT title, content FROM memories
|
|
131
|
+
WHERE (project IS ? OR (project IS NULL AND ? IS NULL))
|
|
132
|
+
AND category IS ?
|
|
133
|
+
AND COALESCE(status, 'active') = 'active'
|
|
134
|
+
ORDER BY created_at DESC
|
|
135
|
+
LIMIT ?`,
|
|
136
|
+
).all(project || null, project || null, memory.category ?? null, DEDUP_CANDIDATE_LIMIT);
|
|
137
|
+
|
|
138
|
+
for (const candidate of candidates) {
|
|
139
|
+
const { duplicate, combined } = isNearDuplicate(
|
|
140
|
+
{ title: memory.title, content: memory.content },
|
|
141
|
+
{ title: candidate.title, content: candidate.content },
|
|
142
|
+
{ titleJaccard: DEDUP_TITLE_JACCARD, combinedThreshold: DEDUP_COMBINED },
|
|
143
|
+
);
|
|
144
|
+
if (duplicate) {
|
|
145
|
+
process.stderr.write(
|
|
146
|
+
`[shieldcortex save-memory] skipped near-duplicate (combined=${combined.toFixed(2)}): ${memory.title}\n`,
|
|
147
|
+
);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
96
152
|
db.prepare(`
|
|
97
153
|
INSERT INTO memories (
|
|
98
154
|
uuid, title, content, type, category, salience, tags, project,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-start context ordering helpers.
|
|
3
|
+
*
|
|
4
|
+
* The boot preamble used to order high-priority memories by raw
|
|
5
|
+
* `salience DESC, last_accessed DESC`. Because ~80% of rows sit at raw
|
|
6
|
+
* salience=1.0 (the "wall"), that ordering was an 80% tie and effectively
|
|
7
|
+
* random. Ranking by *effective* salience (recency × access × pin ×
|
|
8
|
+
* downvote_penalty — see scripts/lib/salience.mjs) lets fresh/relevant
|
|
9
|
+
* memories rank above stale 1.0 ones.
|
|
10
|
+
*
|
|
11
|
+
* Kept pure + side-effect-free so it's unit-testable without importing the
|
|
12
|
+
* hook entrypoint (which runs a stdin/process.exit IIFE at module load).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { computeEffectiveSalience } from './salience.mjs';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Sort memories by effective salience, highest first. Returns a new array;
|
|
19
|
+
* the input is not mutated. `opts` is passed through to
|
|
20
|
+
* computeEffectiveSalience so tests can inject a deterministic `now`.
|
|
21
|
+
*
|
|
22
|
+
* @param {Array<object>} memories
|
|
23
|
+
* @param {object} [opts] - forwarded to computeEffectiveSalience (e.g. { now })
|
|
24
|
+
* @returns {Array<object>}
|
|
25
|
+
*/
|
|
26
|
+
export function orderByEffectiveSalience(memories, opts = {}) {
|
|
27
|
+
return [...memories].sort(
|
|
28
|
+
(a, b) => computeEffectiveSalience(b, opts) - computeEffectiveSalience(a, opts),
|
|
29
|
+
);
|
|
30
|
+
}
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -174,21 +174,40 @@ if (isGlobal && !isCI) {
|
|
|
174
174
|
console.warn('[shieldcortex] To manually install after confirming OpenClaw support:');
|
|
175
175
|
console.warn('[shieldcortex] shieldcortex openclaw install --no-plugins');
|
|
176
176
|
} else if (state.pluginInstalled) {
|
|
177
|
-
// Bug #15
|
|
177
|
+
// Bug #15 + Task 6b: a previous install left the plugin (and possibly the
|
|
178
|
+
// hook) on disk. Both are installed by file-copy, so a package bump
|
|
179
|
+
// leaves them stale until something re-copies them.
|
|
178
180
|
console.log('');
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
//
|
|
186
|
-
console.log('[shieldcortex]
|
|
181
|
+
if (state.hookInstalled) {
|
|
182
|
+
// Hook present → run the full refresh ONLY. `shieldcortex openclaw
|
|
183
|
+
// install` re-copies the hook (copyHookFiles) AND re-installs/registers
|
|
184
|
+
// the plugin in one idempotent pass, so a standalone autoCopyPlugin()
|
|
185
|
+
// here would copy the plugin a second time for no benefit. The full
|
|
186
|
+
// refresh is the single guarantee that keeps the file-copied hook from
|
|
187
|
+
// going stale on a plugin-present update.
|
|
188
|
+
console.log('[shieldcortex] Refreshing OpenClaw hook + plugin to latest version...');
|
|
187
189
|
const ok = refreshOpenClawInstall(cliPath);
|
|
188
190
|
if (!ok) {
|
|
189
|
-
console.warn('[shieldcortex] ⚠ OpenClaw
|
|
191
|
+
console.warn('[shieldcortex] ⚠ OpenClaw hook + plugin refresh failed (non-fatal).');
|
|
190
192
|
console.warn('[shieldcortex] Run manually: shieldcortex openclaw install');
|
|
191
193
|
}
|
|
194
|
+
} else {
|
|
195
|
+
// Plugin-only (no hook) → just freshen the plugin files in place, with
|
|
196
|
+
// a full-refresh fallback if the in-place copy fails.
|
|
197
|
+
console.log('[shieldcortex] Existing plugin detected. Copying updated plugin files...');
|
|
198
|
+
const copied = autoCopyPlugin(state.pluginDir, cliPath);
|
|
199
|
+
if (copied) {
|
|
200
|
+
console.log('[shieldcortex] Plugin files updated.');
|
|
201
|
+
console.log('[shieldcortex] No hook installed. Run full setup for hook + memory:');
|
|
202
|
+
console.log('[shieldcortex] shieldcortex openclaw install');
|
|
203
|
+
} else {
|
|
204
|
+
console.warn('[shieldcortex] ⚠ Plugin auto-copy failed — running full refresh.');
|
|
205
|
+
const ok = refreshOpenClawInstall(cliPath);
|
|
206
|
+
if (!ok) {
|
|
207
|
+
console.warn('[shieldcortex] ⚠ OpenClaw auto-refresh failed (non-fatal).');
|
|
208
|
+
console.warn('[shieldcortex] Run manually: shieldcortex openclaw install');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
192
211
|
}
|
|
193
212
|
} else if (state.hookInstalled) {
|
|
194
213
|
// Hook exists but no plugin — run full refresh
|