Releases: Drakon-Systems-Ltd/ShieldCortex
v4.20.0 — drop openclaw.extensions from main package
Stop the OpenClaw duplicate plugin id detected warning at its source.
OpenClaw's npm discovery is gated on a package having openclaw.extensions in its package.json. Pre-v4.20.0 both the main shieldcortex package AND the dedicated @drakon-systems/shieldcortex-realtime plugin declared one, so OpenClaw scanned both copies (the bare main package gets pulled in alongside as the realtime plugin's peerDependencies.shieldcortex), registered both under pluginId: shieldcortex-realtime, deduplicated, and emitted duplicate plugin id detected; global plugin will be overridden by global plugin on every openclaw update. Functionally fine (the right dist/index.js always won) but cosmetic noise on every fleet box.
This release drops openclaw.extensions from the main package's package.json. The bare shieldcortex is now invisible to OpenClaw's npm discovery — no duplicate registration, no warning. The dedicated realtime plugin remains the only discovery target.
Changed
openclaw.extensionsremoved from the mainpackage.json(keptopenclaw.hooksfor the documentedopenclaw hooks installflow).- Packaging test inverted to pin the new contract.
- Plugin README clarified — Packaging note now documents v4.20.0 contract and the full history.
Unchanged
doctorcheckOpenClaw plugin pkgkeeps its full matrix — older fleet boxes that still drag in pre-v4.20.0shieldcortexvia the realtime peer-dep continue to be diagnosed correctly.openclaw hooks installflow still works.- Dedicated realtime plugin's own
openclaw.extensionsuntouched — that's the legitimate plugin declaration.
Why this is a minor (4.20.0), not a patch
Removing a package.json field that external tooling could in principle key on counts as an interface change to the OpenClaw integration surface. In practice the only consumer was OpenClaw's own discovery (which we WANT to stop indexing the main package) — but the SemVer-correct bump is minor.
Published manually from local (cyborgninja): npm shieldcortex@4.20.0 + @drakon-systems/shieldcortex-realtime@4.20.0.
v4.19.1 — doctor INFO (not WARN) for the expected shieldcortex peer-dep state
Doctor: stop crying wolf about the expected shieldcortex peer-dep in OpenClaw's plugin tree.
Since v4.18.3, the bare shieldcortex landing at ~/.openclaw/npm/node_modules/shieldcortex is the expected steady state of a healthy install: OpenClaw resolves @drakon-systems/shieldcortex-realtime's peerDependencies.shieldcortex by installing the main package alongside in its npm tree, and the 4.18.3 root-manifest fix made this safe (OpenClaw dedupes by pluginId). shieldcortex doctor still reported the state as WARN because the v4.18.2 check author (correctly, at the time) was being conservative — the Jarvis 2026-05-15 crash mechanism was unconfirmed. With weeks of healthy fleet evidence we now narrow the diagnostic without losing the safety net.
Changed
OpenClaw plugin pkgcheck: downgraded fromWARNtoINFOwhen bareshieldcortexversion matches installed@drakon-systems/shieldcortex-realtimeAND the rootopenclaw.plugin.jsonexists. Healthy fleet boxes now report INFO instead of WARN.
Unchanged
FAILstill fires when the bare package's extension entry is missing on disk (the real crash-loop precursor).WARNstill fires for genuine surprises: version mismatch, missing realtime peer, missing root manifest, unparseable package.json.
Tests
- 4 new cases (1 INFO + 3 WARN edge cases). Full suite 1188 passing (unrelated
mcp-registrationflake unchanged).
Published manually from local (cyborgninja): npm shieldcortex@4.19.1 + @drakon-systems/shieldcortex-realtime@4.19.1.
v4.19.0 — Living Constellation knowledge graph
[4.19.0] - 2026-05-21
Living Constellation: the knowledge graph now feels alive.
The dashboard graph used to be a static cluster of dots. Five things were missing — there was no centre, no sense of flow, no visible link between memory activity and what you saw, no glow on the connections, and the controls fought you. This release rebuilds the renderer around those gaps. A high-mass entity is pinned at the canvas centre as a "sun" you can click to re-orbit; every node breathes subtly so the graph never freezes; memory.created emits a short spike on the affected entities, memory.accessed paints a warm recall ring; the hottest few edges show drifting particles to make the data flow legible; links draw as additive-blended gradient strokes that bloom where they overlap. Drag-release pins a node where you drop it (shift-click unpins, double-click empty space refits). A Settings → Graph Motion selector lets you pick Subtle / Moderate / Strong without reload, and prefers-reduced-motion short-circuits the lot.
The 527-line ConstellationGraph.tsx monolith is now a ~150-line wirer composing seven small modules under dashboard/src/components/graph/constellation/, each independently unit-tested (40 jest cases). Cluster nebula rendering — two-layer halo, golden-angle star scatter, hover dashed ring, type label — is preserved verbatim inside the wirer so cluster mode looks unchanged.
Added
PulseDriverenergy model (dashboard/src/components/graph/constellation/pulse.ts) — three composable layers: A (memory-created spike,decayCreate), B (memory-accessed warm glow,decayRecall), C (always-on sinusoidal breathing). Per-node breathing phase is derived from a stable FNV-1a hash so the same id always lands in the same phase.pickParticleEdges(links, anchorId, overrideCap?)ranks edges bymax(srcEnergy, dstEnergy)with anchor-adjacency as the tie-break.- Anchor selection + pin (
anchor.ts) —pickAnchorranks bymemoryCount × edgeCountwith amemoryCount-only fallback for lone or all-isolated graphs;applyAnchor<T extends PinnableNode>pins the new sun at(0, 0)and releases the previous one without touching user drag-pins. - Pure render math (
renderMath.ts) —computeNodeRadius,computeLinkAlpha,computeLinkWidth. Unit-tested without any canvas. - Canvas drawers —
renderNodes.ts(entity + anchor sun + recall ring + breathing modulation, exposes_paintHookfor future RTL tests) andrenderLinks.ts(gradient stroke withglobalCompositeOperation = 'lighter'). - Controls (
controls.ts) —wireControls(graphRef, opts)exposeshandleNodeDragEnd(drag-to-pin),handleNodeClick(single/double/shift-click verdicts),handleBackgroundDoubleClick(reset + zoom-to-fit). 300ms synthesised double-click on nodes triggers a smooth zoom. useGraphPulse(driver)hook (dashboard/src/hooks/useGraphPulse.ts) — subscribes the driver to/ws/eventsand dispatchesmemory.created/memory.accessedpulses, withGET /api/memories?mode=recent&limit=50polling at 10s as the fallback when WS closes or errors.- Settings → Graph Motion selector — 3-radio Subtle / Moderate / Strong, per-browser via
localStorage, broadcasts ashieldcortex:intensity-changedCustomEvent so the live graph updates without reload. prefers-reduced-motionsupport — when the OS-level setting is on, breathing stops, particles disappear (particleCap: 0), spike/recall decays vanish in one frame, and zoom tweens become instant.- Dev-only pulse debug panel —
localStorage.SHIELDCORTEX_DEBUG_PULSE = '1'reveals a small overlay that firesmemory.created/memory.accessedagainst an entity id for manual testing.
Changed
addMemoryreorder + payload extension (src/memory/store.ts) — entity extraction now runs before thememory_createdemit/persist/webhook calls so the event carriesentity_ids: number[]. Without this, the new pulse layer's WebSocket subscriber had no way to map an event to a graph node and Layer A would silently never fire on real data. The auto-link block (detectRelationships) stays in its original position — it doesn't depend on entity ids.memory_created/memory_accessedevent types (src/api/events.ts) extended withentity_ids: number[].emitMemoryCreatedandemitMemoryAccessedhelper signatures take the new arg.GET /api/memories(src/api/routes/memories.ts) now returnsentity_ids: number[]per row. Implemented as a single batchedIN (?, ?, …)query — no N+1.
Fixed
- Native d3-force unpin via cast —
react-force-graph-2d'sNodeObject<X>declaresfx?: number(no null), so the d3-canonical "set fx/fy to null to release" assignment was rejected by tsc. Cast at the assignment site preserves the runtime contract. - Latent
.jsextension resolution in three constellation modules —from './renderMath.js'etc. compiled under tsc and ran under Jest but Next.js Turbopack resolved the literals and 500'd at runtime. Dropped to extensionless imports so all three resolvers agree.
Tests
- 40 new jest cases across
intensity.test.ts(9),anchor.test.ts(11),pulse.test.ts(12),renderMath.test.ts(8). One newsrc/__tests__/memory-event-entity-ids.test.tslocks theaddMemoryreorder by asserting the emitted event carries the extracted entity ids and thememory_entitiestable is populated at emit time. - Full repo suite: 1184 passing + 2 skipped + 1 pre-existing flake (
mcp-registrationteardown bug unchanged from prior releases). Zero regressions. - Dashboard
npx tsc --noEmit: silent.npm run lint: 0 errors.npm run build: all 22 routes prerendered.
Architecture notes
- The wirer keeps cluster paint (nebula halo, golden-angle star scatter, hover dashed ring, type label + entity count) inline rather than moving it into a new
renderClusters.tsmodule. The spec's intent was preservation, and the cluster branch is naturally distinct from the entity branch — splitting it later remains an option. controls.ts'scenterAt/zoomsmooth tweens on node double-click are not yet gated onprefers-reduced-motion; only the wirer'szoomToFitcalls are. Small follow-up if reduced-motion users notice.
v4.18.5 — modern Node support (better-sqlite3 ^12) + guarded native loader
Modern Node support — no more cryptic native crash on Node 23/24/25/26.
engines.node was unbounded (>=18) while better-sqlite3 ^11 prebuilts stopped at older Node ABIs — newer-Node users without a compiler hit a bare libc++abi … Napi::Error crash-loop with zero guidance. Node-LTS users were unaffected; this closes the gap.
Changed
better-sqlite3^11→^12(prebuilts for Node 20/22/23/24/25/26 — no compiler needed on modern Node).engines.node>=18→>=20(Node 18 EOL); SKILL.mdminVersionaligned.
Added
- Guarded native loader (single runtime load path) — on ABI mismatch prints one actionable message (
npm rebuild better-sqlite3+ supported Node LTS) and exits cleanly instead of the opaque abort. - Postinstall smoke-check — surfaces the problem at install time with remediation.
Tests
- 5 new formatter unit cases; full suite green (1143 passing). One unrelated pre-existing
mcp-registrationtimeout flake (passes in CI) is not introduced by this change.
Published manually from local (cyborgninja): shieldcortex@4.18.5 + @drakon-systems/shieldcortex-realtime@4.18.5.
v4.18.4 — bound the cloud sync retry queue size
The cloud sync retry queue is now hard-bounded — it can't grow without limit on disk.
The 7-day TTL purge (purgeOldEntries) only runs while the brain worker is alive. MCP-only installs have no worker, so a long offline stretch could accumulate sync_queue rows indefinitely with nothing trimming them.
Fixed
sync_queuecapped at 5,000 rows, enforced on every enqueue (and the worker purge path). Evicts synced → terminally-failed → oldest-pending first, once-per-hour warning. Fire-and-forget preserved.
Tests
- 4 new cases; full suite green.
Published manually from local (cyborgninja): npm shieldcortex@4.18.4 + @drakon-systems/shieldcortex-realtime@4.18.4. CI publish workflow no-ops (version already on npm).
v4.18.3
Full Changelog: v4.18.2...v4.18.3
v4.18.2
Full Changelog: v4.18.1...v4.18.2
v4.18.1
Full Changelog: v4.18.0...v4.18.1
v4.18.0
Full Changelog: v4.17.0...v4.18.0
v4.17.0
Full Changelog: v4.16.0...v4.17.0