Skip to content

feat: add plugin_version column to memory + sessions tables#120

Merged
efenocchi merged 11 commits into
mainfrom
feat/plugin-version
May 13, 2026
Merged

feat: add plugin_version column to memory + sessions tables#120
efenocchi merged 11 commits into
mainfrom
feat/plugin-version

Conversation

@efenocchi
Copy link
Copy Markdown
Collaborator

@efenocchi efenocchi commented May 11, 2026

Summary

  • Adds a plugin_version TEXT NOT NULL DEFAULT '' column to both memory and sessions tables, idempotently migrated on first contact via ensureColumn.
  • Stamps the installed hivemind version on every INSERT and UPDATE the plugin produces (capture hooks, session-start placeholders, wiki-worker summary writes), across all five agents (claude-code, codex, cursor, hermes, pi).
  • Per-agent guard test (plugin-version-resolution.test.ts) verifies each shipped bundle's on-disk layout resolves a non-empty semver and that every capture / session-start INSERT actually lists plugin_version in its column list.

Why

Today, rows landing in memory / sessions have no record of which plugin version wrote them. This blocks (a) telemetry that needs to attribute regressions to a specific release, (b) backend-side compatibility decisions when a schema or write pattern changes across versions, and (c) debugging cases where stale plugins keep writing alongside upgraded ones.

Schema and migration

Both tables get one new column:

plugin_version TEXT NOT NULL DEFAULT ''
  • Greenfield: CREATE TABLE includes the column, no extra round-trip.
  • Existing tables: ensureColumn(tbl, "plugin_version", "TEXT NOT NULL DEFAULT ''") performs the same SELECT-then-ALTER flow already used for agent (added 2026-04-11). Marker-store TTL caches the result so steady-state startup pays nothing.
  • Backward compat (old clients on new schema): omitted from the INSERT column list → backend fills '' from the DEFAULT. Verified end-to-end (see below).

The race-tolerated "Column already exists" path is preserved: if SELECT reports missing but ALTER fails with already exists, re-SELECT confirms the column landed before declaring success. All other ALTER failures propagate.

Wire-through

Each call site resolves the version through getInstalledVersion(__bundleDir, manifestDir):

Site When resolved How
capture hooks (4 agents) once at module load inline const PLUGIN_VERSION = ...
session-start hooks (4 agents) once at top of main() passed into createPlaceholder()
codex/stop.ts once at module load inline
wiki-worker (5 agents) parent process writes pluginVersion into spawn-config JSON child reads cfg.pluginVersion
upload-summary.ts passed as UploadParams.pluginVersion landed in same SQL statement as summary to preserve the single-UPDATE invariant (Deeplake silently drops one of two rapid UPDATEs on the same row)
session-queue.ts (currently unused, tested) buildQueuedSessionRow({pluginVersion}) included in buildSessionInsertSql

Tests

npm test113 files, 2206 tests pass.

New / updated coverage:

  • schema-scenarios.test.ts — extended to cover all 7 table-state combinations × the new plugin_version migration (greenfield, full legacy, half-legacy mem, half-legacy sessions, fully migrated, mixed).
  • deeplake-api.test.tsensureTable / ensureSessionsTable tests updated to assert the extra SELECT info_schema + (when missing) ALTER for plugin_version.
  • plugin-version-resolution.test.ts (new):
    • Resolves getInstalledVersion against every agent's actual shipped bundle layout, asserts non-null semver.
    • Bundle-level scan of capture.js / stop.js / session-start*.js confirms plugin_version is in the INSERT column list. Mirrors the spirit of wiki-worker-upload-sql.test.ts.

End-to-end verification

Ran a real session against test_plugin/default (the sandbox per CLAUDE.md), with the memory + sessions tables already populated and without the new column:

Scenario Result
ALTER ADD COLUMN on existing populated tables (6 + 143 rows) column added, existing rows backfilled to ''
Race against concurrent writer running ensureColumn tolerated correctly (re-SELECT confirms, no error propagated)
Placeholder INSERT from upgraded plugin (this branch) plugin_version="0.7.17"
Capture INSERT from upgraded plugin plugin_version="0.7.17"
INSERT from old client (installed 0.7.15 plugin without the column in its INSERT list) backend fills '' via DEFAULT — no error, no regression

Test plan

  • CI passes (typecheck + 2206 unit tests)
  • Manual: install built bundle into a fresh test_plugin workspace, confirm CREATE includes the column
  • Manual: install built bundle over a legacy table (no plugin_version), confirm ALTER ADD COLUMN fires once then markers cache
  • Manual: verify a parallel session running an older installed plugin writes rows with plugin_version='' and the new plugin writes the actual version

Out of scope (filed as side findings)

  • codex/.codex-plugin/plugin.json is stuck at 0.6.7 while the repo is 0.7.18. The version-bump scripts appear to only touch package.json and claude-code/.claude-plugin/plugin.json. The per-agent test in this PR asserts non-empty semver rather than strict equality so this pre-existing release-process bug doesn't block the change — but it should be fixed separately.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added plugin version tracking to record which plugin version was active during each session and summary generation, providing version-specific analytics and enabling targeted debugging of version-related issues.
  • Tests

    • Added comprehensive test coverage for plugin version detection across multiple agent configurations and database schema migration scenarios to ensure consistent versioning.

efenocchi added 3 commits May 11, 2026 23:18
Track the hivemind version that produced each row. The column is TEXT
NOT NULL DEFAULT '' on both tables (memory + sessions) so older clients
that omit it from their INSERT column list keep working — the backend
fills '' from the DEFAULT.

Migration is handled the same way as the agent column (added
2026-04-11): CREATE TABLE includes plugin_version on greenfield tables,
and ensureColumn() ALTERs existing tables on first contact with the new
client. The marker-store TTL caches the ensure() result so steady-state
calls cost zero round-trips.

Schema-scenario tests cover all 7 combinations (greenfield, full legacy,
half-legacy mem/sessions, fully migrated, mixed) plus the cross-cutting
invariants (race-tolerated "Column already exists" via re-SELECT, no
silent swallow of unrelated ALTER errors).
Wire the installed hivemind version into every INSERT and UPDATE the
plugin produces, across all five agents (claude-code, codex, cursor,
hermes, pi).

Sources of the value differ by call site, all resolved through
getInstalledVersion(__bundleDir, manifestDir):
- capture hooks resolve once at module load and reuse for the lifetime
  of the hook process
- session-start hooks resolve once at the top of main() and thread the
  value into createPlaceholder()
- wiki-worker reads pluginVersion from the spawn-config JSON written by
  the parent process, since the worker runs detached from a bundle
  layout that may differ from its spawner

upload-summary.ts gains a pluginVersion UploadParams field and writes
it on both the UPDATE (refresh) and INSERT (new summary) paths. Kept
atomic with the summary column to preserve the single-statement
invariant (Deeplake silently drops one of two rapid UPDATEs on the
same row — see CLAUDE.md).

session-queue.ts (currently unused but tested) carries the column
through QueuedSessionRow + buildSessionInsertSql so the shape stays
consistent if and when the queue path is re-enabled.

plugin-version-resolution.test.ts is a per-agent guard that:
1. resolves getInstalledVersion against each shipped bundle's actual
   on-disk layout and asserts a semver-shaped non-empty result, and
2. scans each capture.js / session-start*.js bundle to confirm
   plugin_version is in the INSERT column list.

A silent regression in either dimension (renamed manifest dir, dropped
.hivemind_version stamp, esbuild dropping the helper, source-edit not
re-bundled) would now fail in CI instead of landing empty strings in
production.

Side finding worth a separate fix: codex/.codex-plugin/plugin.json is
stuck at 0.6.7 (the bump script appears to only update the top-level
package.json and claude-code's manifest). The per-agent test asserts
non-empty semver rather than strict equality with package.json so this
unrelated release-process bug doesn't block this change.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f281f0a3-1565-4cde-9dc7-e0ef6de0cbd7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds comprehensive plugin version tracking across the HiveMind codebase. It introduces a plugin_version column to Deeplake tables, creates a version detection utility, extends table schema migrations with backward compatibility, and threads version data through capture/stop hooks and wiki worker uploads for all supported agents (claude-code, codex, cursor, hermes, pi, mcp).

Changes

Plugin Version Tracking Across All Agents

Layer / File(s) Summary
Core Schema and Type Updates
src/deeplake-api.ts, src/hooks/session-queue.ts, src/hooks/wiki-worker.ts, src/hooks/codex/wiki-worker.ts, src/hooks/cursor/wiki-worker.ts, src/hooks/hermes/wiki-worker.ts, src/hooks/pi/wiki-worker.ts
Base DeeplakeApi table creation gains plugin_version column definition and migration calls; worker configs and session row interfaces include pluginVersion field.
Version Detection Utility
claude-code/bundle/capture.js, claude-code/bundle/session-end.js, cursor/bundle/capture.js, cursor/bundle/session-end.js, hermes/bundle/capture.js, hermes/bundle/session-end.js, codex/bundle/session-start-setup.js, codex/bundle/stop.js
New getInstalledVersion(bundleDir, pluginManifestDir) helper reads version from plugin.json, .hivemind_version, or walks parent directories to find known packages in package.json; implemented across all bundled modules.
Table Schema Migrations (All Agents)
bundle/cli.js, claude-code/bundle/{capture,commands/auth-login,pre-tool-use,session-start-setup,session-start,shell/deeplake-shell}.js, codex/bundle/{capture,commands/auth-login,pre-tool-use,session-start-setup,session-start,shell/deeplake-shell,stop}.js, cursor/bundle/{capture,commands/auth-login,pre-tool-use,session-start,shell/deeplake-shell}.js, hermes/bundle/{capture,commands/auth-login,pre-tool-use,session-start,shell/deeplake-shell}.js, pi/bundle/autopull-worker.js, mcp/bundle/server.js
Memory and sessions table CREATE TABLE statements include plugin_version column; post-creation ensureColumn() calls provide backward-compatible migration for existing deployments lacking the column.
Worker Spawning: Plugin Version Configuration
claude-code/bundle/{capture,session-end}.js, codex/bundle/capture.js, codex/bundle/stop.js, cursor/bundle/capture.js, cursor/bundle/session-end.js, hermes/bundle/{capture,session-end}.js, src/hooks/{codex,cursor,hermes}/spawn-wiki-worker.ts, src/hooks/spawn-wiki-worker.ts
All spawnWikiWorker() variants compute installed plugin version and include pluginVersion in temporary worker config.json alongside existing API/auth/workspace/session metadata.
Capture and Stop Hooks: Version Persistence
src/hooks/{capture,codex/capture,codex/stop,cursor/capture,hermes/capture}.ts, claude-code/bundle/capture.js, codex/bundle/capture.js, cursor/bundle/capture.js, hermes/bundle/capture.js
Compute PLUGIN_VERSION at hook startup using version detection; include plugin_version column in sessions table INSERTs with resolved version value across all agents.
Session Start: Placeholder Creation with Version
src/hooks/{session-start,codex/session-start-setup,cursor/session-start,hermes/session-start}.ts, claude-code/bundle/session-start.js, codex/bundle/session-start-setup.js, cursor/bundle/session-start.js, hermes/bundle/session-start.js
Extend createPlaceholder() functions to accept pluginVersion parameter and include it in placeholder row INSERTs; resolve version once at hook startup for reuse throughout the session initialization flow.
Wiki Worker: Summary Upload with Version
src/hooks/{upload-summary,wiki-worker,cursor/wiki-worker,hermes/wiki-worker,pi/wiki-worker}.ts, claude-code/bundle/wiki-worker.js, codex/bundle/wiki-worker.js, cursor/bundle/wiki-worker.js, hermes/bundle/wiki-worker.js, pi/bundle/wiki-worker.js
Extend uploadSummary() to read and persist pluginVersion in both UPDATE and INSERT SQL statements; thread version through worker config to upload call across all agents.
Bundled Path Helpers and Import Aliases
claude-code/bundle/{session-end,session-start-setup}.js, codex/bundle/{session-start-setup,stop}.js, cursor/bundle/session-end.js, hermes/bundle/session-end.js
Minor bundled-output path import alias updates and join/dirname/readFileSync binding changes for skillify state/config modules; functional logic unchanged.
Tests: Deeplake API Schema Updates
claude-code/tests/deeplake-api.test.ts
Expand test mocks and assertions to verify plugin_version column checks via info_schema.columns queries and corresponding ALTER TABLE ADD COLUMN plugin_version operations across all scenarios.
Tests: Schema Upgrade Scenarios
claude-code/tests/schema-scenarios.test.ts
Update test suite for 7 schema upgrade scenarios to verify plugin_version column presence checks and ALTER TABLE behavior across greenfield/legacy/mixed deployment states.
Tests: Plugin Version Resolution
claude-code/tests/plugin-version-resolution.test.ts
New comprehensive test validating getInstalledVersion() resolution for all agent layouts and verifying plugin_version SQL column presence in bundled capture/stop/session-start INSERT statements.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • activeloopai/hivemind#112: Modifies the same SessionStart hook files and placeholder creation flows; potential overlap in schema or version-related changes.

Suggested reviewers

  • kaghni

🐰 A version so grand, now tracked with care,
Through every table, everywhere,
From capture to wiki, each hop so fine,
The plugin now wears its version's shine!

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/plugin-version

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link
Copy Markdown

claude Bot commented May 11, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

@coderabbitai coderabbitai Bot requested a review from kaghni May 11, 2026 23:21
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 11, 2026

Coverage Report

Scope: files changed in this PR. Enforced threshold: 90% per metric (per file via vitest.config.ts).

Status Category Percentage Covered / Total
🟢 Lines 97.03% (🎯 90%) 1341 / 1382
🟢 Statements 96.28% (🎯 90%) 1474 / 1531
🟢 Functions 97.25% (🎯 90%) 177 / 182
🔴 Branches 88.56% (🎯 90%) 743 / 839
File Coverage — 21 files changed
File Stmts Branches Functions Lines
src/deeplake-api.ts 🟢 99.1% 🟢 91.5% 🟢 97.4% 🟢 100.0%
src/hooks/capture.ts 🟢 98.6% 🔴 88.1% 🟢 100.0% 🟢 100.0%
src/hooks/codex/capture.ts 🟢 100.0% 🔴 88.9% 🟢 100.0% 🟢 100.0%
src/hooks/codex/session-start-setup.ts 🟢 100.0% 🔴 81.8% 🟢 100.0% 🟢 100.0%
src/hooks/codex/spawn-wiki-worker.ts 🟢 95.0% 🔴 75.0% 🔴 66.7% 🟢 95.0%
src/hooks/codex/stop.ts 🟢 98.6% 🔴 85.4% 🟢 100.0% 🟢 98.4%
src/hooks/codex/wiki-worker.ts 🟢 97.7% 🟢 94.9% 🟢 100.0% 🟢 97.5%
src/hooks/cursor/capture.ts 🔴 86.4% 🔴 84.5% 🟢 100.0% 🔴 87.1%
src/hooks/cursor/session-start.ts 🟢 98.1% 🟢 91.2% 🟢 100.0% 🟢 97.9%
src/hooks/cursor/spawn-wiki-worker.ts 🟢 100.0% 🔴 62.5% 🟢 100.0% 🟢 100.0%
src/hooks/cursor/wiki-worker.ts 🟢 90.8% 🔴 87.2% 🟢 90.0% 🟢 92.6%
src/hooks/hermes/capture.ts 🟢 93.0% 🔴 88.1% 🟢 100.0% 🟢 94.4%
src/hooks/hermes/session-start.ts 🟢 100.0% 🔴 88.5% 🟢 100.0% 🟢 100.0%
src/hooks/hermes/spawn-wiki-worker.ts 🟢 100.0% 🔴 80.0% 🟢 100.0% 🟢 100.0%
src/hooks/hermes/wiki-worker.ts 🟢 90.8% 🔴 87.2% 🟢 90.0% 🟢 92.6%
src/hooks/pi/wiki-worker.ts 🟢 90.7% 🔴 87.2% 🟢 90.0% 🟢 92.5%
src/hooks/session-queue.ts 🟢 96.7% 🔴 87.8% 🟢 100.0% 🟢 98.3%
src/hooks/session-start.ts 🟢 100.0% 🟢 91.7% 🟢 100.0% 🟢 100.0%
src/hooks/spawn-wiki-worker.ts 🟢 100.0% 🔴 75.0% 🟢 100.0% 🟢 100.0%
src/hooks/upload-summary.ts 🟢 100.0% 🟢 100.0% 🟢 100.0% 🟢 100.0%
src/hooks/wiki-worker.ts 🟢 97.7% 🟢 94.9% 🟢 100.0% 🟢 97.5%

Generated for commit 6af4f8e.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
hermes/bundle/capture.js (1)

1112-1124: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Resolve Hermes from its own manifest in both write paths.

Both the summary-worker config and the capture hook still read .claude-plugin, so Hermes can persist the wrong plugin_version to both memory and sessions.

Also applies to: 1489-1490

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hermes/bundle/capture.js` around lines 1112 - 1124, The code currently calls
getInstalledVersion(bundleDir, ".claude-plugin") when writing config.json (via
writeFileSync3) and in the other write path; change both calls to resolve Hermes
from its own manifest instead of ".claude-plugin" — e.g., replace the manifest
name with Hermes' manifest identifier (the Hermes plugin/manifest constant or
filename) when calling getInstalledVersion(bundleDir, ...), so the pluginVersion
variable used in the config write (configFile / writeFileSync3) and the
summary-worker write path is derived from Hermes' manifest rather than
".claude-plugin".
src/hooks/upload-summary.ts (1)

73-87: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid clearing plugin_version on UPDATE when pluginVersion is omitted.

Because pluginVersion defaults to "" (Line 73) and UPDATE always writes it (Line 86), callers that don’t pass pluginVersion will erase previously stored values.

Suggested conditional UPDATE clause
-  const pluginVersion = params.pluginVersion ?? "";
+  const pluginVersion = params.pluginVersion;
+  const pluginVersionForInsert = params.pluginVersion ?? "";
...
-      `plugin_version = '${esc(pluginVersion)}', ` +
+      (pluginVersion === undefined ? "" : `plugin_version = '${esc(pluginVersion)}', `) +
       `last_update_date = '${ts}' ` +
...
-    `${sizeBytes}, '${esc(project)}', E'${esc(desc)}', '${esc(agent)}', '${esc(pluginVersion)}', '${ts}', '${ts}')`;
+    `${sizeBytes}, '${esc(project)}', E'${esc(desc)}', '${esc(agent)}', '${esc(pluginVersionForInsert)}', '${ts}', '${ts}')`;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/upload-summary.ts` around lines 73 - 87, The UPDATE currently
always writes plugin_version because pluginVersion defaults to "" which will
overwrite stored values; change the logic around the pluginVersion variable and
the SQL construction in the existing-branch where the UPDATE string is built
(the code that defines pluginVersion and the SQL variable) so that
plugin_version is only included in the SET clause when the caller actually
provided a pluginVersion (e.g., params.pluginVersion !== undefined/null/empty),
by conditionally appending `, plugin_version = '...'"` to the SQL string only
when present and leaving it out otherwise; ensure you still use
esc(pluginVersion) when you include it and that the rest of the SET fields
(summary, summary_embedding, size_bytes, description, last_update_date) remain
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@claude-code/tests/plugin-version-resolution.test.ts`:
- Line 55: The test currently asserts expect(version).toMatch(/^\d+\.\d+\.\d+$/)
which rejects valid semver prerelease/build strings; update the assertion on the
version variable (the expect(...).toMatch call in
plugin-version-resolution.test.ts) to use a regex that allows optional
prerelease and build metadata (e.g. permit a trailing -prerelease and +build
segment) so strings like 1.2.3-beta.1 and 1.2.3+exp.sha.5114f85 pass.

In `@src/hooks/cursor/capture.ts`:
- Around line 44-45: PLUGIN_VERSION is being resolved from ".claude-plugin"
which pulls the wrong manifest; update the call to getInstalledVersion so it
resolves Cursor’s own manifest instead (use the Cursor manifest identifier
rather than ".claude-plugin"), keeping __bundleDir and PLUGIN_VERSION the same
so the hook stamps session rows with the correct plugin_version; modify the
getInstalledVersion call invoked where PLUGIN_VERSION is defined (refer to
__bundleDir and getInstalledVersion) to point at the Cursor manifest.

In `@src/hooks/cursor/session-start.ts`:
- Around line 155-157: Replace the hard-coded ".claude-plugin" manifest when
calling getInstalledVersion so SessionStart uses the Cursor manifest like the
rest of the codebase; update the call getInstalledVersion(__bundleDir,
".claude-plugin") to pass the same Cursor manifest identifier used elsewhere
(e.g. the constant or string used in other hooks), and keep assigning
pluginVersion = current ?? "" so placeholder rows and the injected version
notice report Cursor’s version instead of Claude’s.

---

Outside diff comments:
In `@hermes/bundle/capture.js`:
- Around line 1112-1124: The code currently calls getInstalledVersion(bundleDir,
".claude-plugin") when writing config.json (via writeFileSync3) and in the other
write path; change both calls to resolve Hermes from its own manifest instead of
".claude-plugin" — e.g., replace the manifest name with Hermes' manifest
identifier (the Hermes plugin/manifest constant or filename) when calling
getInstalledVersion(bundleDir, ...), so the pluginVersion variable used in the
config write (configFile / writeFileSync3) and the summary-worker write path is
derived from Hermes' manifest rather than ".claude-plugin".

In `@src/hooks/upload-summary.ts`:
- Around line 73-87: The UPDATE currently always writes plugin_version because
pluginVersion defaults to "" which will overwrite stored values; change the
logic around the pluginVersion variable and the SQL construction in the
existing-branch where the UPDATE string is built (the code that defines
pluginVersion and the SQL variable) so that plugin_version is only included in
the SET clause when the caller actually provided a pluginVersion (e.g.,
params.pluginVersion !== undefined/null/empty), by conditionally appending `,
plugin_version = '...'"` to the SQL string only when present and leaving it out
otherwise; ensure you still use esc(pluginVersion) when you include it and that
the rest of the SET fields (summary, summary_embedding, size_bytes, description,
last_update_date) remain unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d90e6193-9cdd-4966-9d07-ac043606addf

📥 Commits

Reviewing files that changed from the base of the PR and between e6f4a02 and 5c98eca.

📒 Files selected for processing (58)
  • bundle/cli.js
  • claude-code/bundle/capture.js
  • claude-code/bundle/commands/auth-login.js
  • claude-code/bundle/pre-tool-use.js
  • claude-code/bundle/session-end.js
  • claude-code/bundle/session-start-setup.js
  • claude-code/bundle/session-start.js
  • claude-code/bundle/shell/deeplake-shell.js
  • claude-code/bundle/wiki-worker.js
  • claude-code/tests/deeplake-api.test.ts
  • claude-code/tests/plugin-version-resolution.test.ts
  • claude-code/tests/schema-scenarios.test.ts
  • codex/bundle/capture.js
  • codex/bundle/commands/auth-login.js
  • codex/bundle/pre-tool-use.js
  • codex/bundle/session-start-setup.js
  • codex/bundle/session-start.js
  • codex/bundle/shell/deeplake-shell.js
  • codex/bundle/stop.js
  • codex/bundle/wiki-worker.js
  • cursor/bundle/capture.js
  • cursor/bundle/commands/auth-login.js
  • cursor/bundle/pre-tool-use.js
  • cursor/bundle/session-end.js
  • cursor/bundle/session-start.js
  • cursor/bundle/shell/deeplake-shell.js
  • cursor/bundle/wiki-worker.js
  • hermes/bundle/capture.js
  • hermes/bundle/commands/auth-login.js
  • hermes/bundle/pre-tool-use.js
  • hermes/bundle/session-end.js
  • hermes/bundle/session-start.js
  • hermes/bundle/shell/deeplake-shell.js
  • hermes/bundle/wiki-worker.js
  • mcp/bundle/server.js
  • pi/bundle/autopull-worker.js
  • pi/bundle/wiki-worker.js
  • src/deeplake-api.ts
  • src/hooks/capture.ts
  • src/hooks/codex/capture.ts
  • src/hooks/codex/session-start-setup.ts
  • src/hooks/codex/spawn-wiki-worker.ts
  • src/hooks/codex/stop.ts
  • src/hooks/codex/wiki-worker.ts
  • src/hooks/cursor/capture.ts
  • src/hooks/cursor/session-start.ts
  • src/hooks/cursor/spawn-wiki-worker.ts
  • src/hooks/cursor/wiki-worker.ts
  • src/hooks/hermes/capture.ts
  • src/hooks/hermes/session-start.ts
  • src/hooks/hermes/spawn-wiki-worker.ts
  • src/hooks/hermes/wiki-worker.ts
  • src/hooks/pi/wiki-worker.ts
  • src/hooks/session-queue.ts
  • src/hooks/session-start.ts
  • src/hooks/spawn-wiki-worker.ts
  • src/hooks/upload-summary.ts
  • src/hooks/wiki-worker.ts

Comment thread claude-code/tests/plugin-version-resolution.test.ts Outdated
Comment on lines +44 to +45
const __bundleDir = dirname(fileURLToPath(import.meta.url));
const PLUGIN_VERSION = getInstalledVersion(__bundleDir, ".claude-plugin") ?? "";
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Resolve Cursor’s own manifest here.

This hook is still sourcing PLUGIN_VERSION from .claude-plugin, so Cursor session rows can be stamped with the wrong version. That undercuts the new plugin_version tracking on one of the primary write paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/cursor/capture.ts` around lines 44 - 45, PLUGIN_VERSION is being
resolved from ".claude-plugin" which pulls the wrong manifest; update the call
to getInstalledVersion so it resolves Cursor’s own manifest instead (use the
Cursor manifest identifier rather than ".claude-plugin"), keeping __bundleDir
and PLUGIN_VERSION the same so the hook stamps session rows with the correct
plugin_version; modify the getInstalledVersion call invoked where PLUGIN_VERSION
is defined (refer to __bundleDir and getInstalledVersion) to point at the Cursor
manifest.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rejecting.cursor-plugin/plugin.json does not exist in this repo. Cursor doesn't ship its own per-plugin manifest; it has only cursor/bundle/.

getInstalledVersion(__bundleDir, manifestDir) is documented as having three fallback paths (see src/utils/version-check.ts):

  1. <bundle>/../<manifestDir>/plugin.json — only used by claude-code and codex, which DO have manifest dirs in the repo.
  2. <bundle>/../.hivemind_versionthe actual source of truth for cursor/hermes/pi, written by writeVersionStamp(PLUGIN_DIR, getVersion()) in src/cli/install-cursor.ts at install time.
  3. Walk-up package.json with a hivemind name — dev fallback when running --plugin-dir ./cursor from the repo (resolves the repo-level @deeplake/hivemind version).

So the .claude-plugin argument is effectively a no-op for cursor (the first lookup misses, falls through). Both real paths (installed via hivemind cursor install.hivemind_version stamp, or running from the repo → walk-up package.json) resolve correctly.

Verifiable locally: ls cursor/.claude-plugin cursor/.cursor-plugin — neither exists.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@efenocchi, thank you for the detailed explanation — that's clear and I was wrong to flag this. The .claude-plugin argument is effectively a no-op for cursor because the first lookup (<bundle>/../.claude-plugin/plugin.json) simply misses, and the function correctly falls through to the .hivemind_version stamp (the install-time source of truth) or the walk-up package.json in dev. No change needed here.

(ᵔ.ᵔ)?


✏️ Learnings added
Learnt from: efenocchi
Repo: activeloopai/hivemind PR: 120
File: src/hooks/cursor/capture.ts:44-45
Timestamp: 2026-05-12T00:24:10.966Z
Learning: In `src/utils/version-check.ts`, `getInstalledVersion(bundleDir, manifestDir)` has three ordered fallback paths:
1. `<bundleDir>/../<manifestDir>/plugin.json` — only meaningful for claude-code and codex, which have actual manifest directories in the repo.
2. `<bundleDir>/../.hivemind_version` — the real source of truth for cursor, hermes, and pi agents; written at install time by `writeVersionStamp` in `src/cli/install-cursor.ts` (and equivalent install scripts).
3. Walk-up `package.json` matching known hivemind package names — dev fallback when running from the repo root.

For cursor/hermes/pi agents, passing `.claude-plugin` as `manifestDir` is a no-op (the first lookup misses), and version resolution correctly falls through to paths 2 or 3. No `.cursor-plugin`, `.hermes-plugin`, or `.pi-plugin` manifest directories exist in the repo.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines +155 to +157
// Resolve plugin version once — also stamped on the placeholder row.
const current = getInstalledVersion(__bundleDir, ".claude-plugin");
const pluginVersion = current ?? "";
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the Cursor manifest for SessionStart as well.

This path also resolves from .claude-plugin, so placeholder rows and the injected version notice can report Claude’s version instead of Cursor’s.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/cursor/session-start.ts` around lines 155 - 157, Replace the
hard-coded ".claude-plugin" manifest when calling getInstalledVersion so
SessionStart uses the Cursor manifest like the rest of the codebase; update the
call getInstalledVersion(__bundleDir, ".claude-plugin") to pass the same Cursor
manifest identifier used elsewhere (e.g. the constant or string used in other
hooks), and keep assigning pluginVersion = current ?? "" so placeholder rows and
the injected version notice report Cursor’s version instead of Claude’s.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rejecting — same reason as the cursor capture comment above. .cursor-plugin/plugin.json does not exist; cursor's installer writes ~/.cursor/hivemind/.hivemind_version at install time and getInstalledVersion reads that via its second fallback. Passing .claude-plugin as the first arg is a no-op because cursor has no manifest dir under that name either.

E2E confirmed: a cursor session in this branch's plugin lands the right hivemind version in plugin_version via the .hivemind_version stamp path.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@efenocchi, understood — thanks for the detailed explanation. The .claude-plugin argument is a no-op for cursor since that manifest directory doesn't exist in the cursor layout, so getInstalledVersion naturally falls through to the .hivemind_version stamp, which is the correct source. The review comment was mistaken. Acknowledged.


✏️ Learnings added
Learnt from: efenocchi
Repo: activeloopai/hivemind PR: 120
File: src/hooks/cursor/session-start.ts:155-157
Timestamp: 2026-05-12T00:24:22.657Z
Learning: In `src/hooks/cursor/session-start.ts` (and other cursor hooks), `getInstalledVersion(__bundleDir, ".claude-plugin")` is intentional: cursor has no `.claude-plugin` or `.cursor-plugin/plugin.json` manifest, so the first argument is a no-op. The version is resolved via the second fallback — the `.hivemind_version` stamp written by cursor's installer to `~/.cursor/hivemind/.hivemind_version`. The call correctly reports the hivemind version for cursor sessions.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

efenocchi added 4 commits May 12, 2026 00:01
PR #120 coverage report flagged eight files I touched below the 90 %
threshold. Two new test files address the worst gaps directly.

spawn-wiki-worker.test.ts — functional tests for the four spawn helpers
(claude-code, codex, cursor, hermes). Each asserts the spawn-config
JSON written to disk contains the resolved `pluginVersion`, plus the
agent-specific config fields (claudeBin / codexBin / cursorBin /
cursorModel / hermesBin / hermesProvider / hermesModel). Mocks the
child_process.spawn boundary so no subprocess actually launches.

Coverage impact for the four spawn-* variants:
- src/hooks/spawn-wiki-worker.ts          : 0  % → 95 %
- src/hooks/codex/spawn-wiki-worker.ts    : 0  % → 90 %
- src/hooks/cursor/spawn-wiki-worker.ts   : 30 % → 95 %
- src/hooks/hermes/spawn-wiki-worker.ts   : 95 % → 95 % (branches +)

wiki-worker-plugin-version.test.ts — parametrized across the three
sibling workers (cursor/hermes/pi). Each variant gets exercised through
main() with mocks at the upload-summary / execFileSync / fetch
boundaries, hitting four scenarios:
1. cfg.pluginVersion → uploadSummary params.pluginVersion (happy path)
2. cfg without pluginVersion → "" fallback (legacy spawner compat)
3. LLM-spawn failure → no upload, releaseLock still fires
4. Empty summary file → no upload, releaseLock still fires
5. No session events → early exit, no LLM spawn

Coverage impact for the three worker variants:
- src/hooks/cursor/wiki-worker.ts : 0 % → 78 % lines (54 % branches)
- src/hooks/hermes/wiki-worker.ts : 0 % → 78 % lines (54 % branches)
- src/hooks/pi/wiki-worker.ts     : 0 % → 78 % lines (54 % branches)

The remaining ~22 % uncovered in those files is the embed-daemon
path-resolution branch and the resume-from-existing-summary branch,
neither of which is touched by this PR. They're left for a follow-up.

cursor/capture.ts and hermes/capture.ts also crossed the 90 % bar as a
side effect of vi.resetModules() in the new file forcing a fresh hook
trace through their module-level constants.

Total: +24 tests, 2206 → 2230 passing.
…nches

Follow-up to the previous coverage push. The cursor/hermes/pi worker
files were at 78 % lines / 54 % branches — the remaining gap was three
specific branches we hadn't exercised:

1. **Resume path** (lines 150–155): worker reads an existing summary
   row and parses `**JSONL offset**: N` for the LLM prompt.
2. **Embeddings disabled**: `HIVEMIND_EMBEDDINGS=false` skips the
   daemon hop; uploadSummary still fires with embedding === null.
3. **API retry** (lines 89–100): each worker has its own inline retry
   loop for transient 401/403/429/5xx. setTimeout is stubbed to advance
   the exponential-backoff windows without burning real wall-clock.

Each scenario is parametrized across cursor / hermes / pi so a
regression on one variant doesn't slip past.

Coverage impact:
- src/hooks/cursor/wiki-worker.ts : 78 % → 92.6 % lines, 54 % → 87.2 % branches
- src/hooks/hermes/wiki-worker.ts : 78 % → 92.6 % lines, 54 % → 87.2 % branches
- src/hooks/pi/wiki-worker.ts     : 78 % → 92.5 % lines, 54 % → 87.2 % branches

All three now over the 90 % line threshold flagged in the PR coverage
report. +9 tests, 2230 → 2239 passing.
… gap

Three changes, two driven by CodeRabbit review on PR #120:

1. **upload-summary UPDATE no longer overwrites plugin_version with ''
   when caller omits the field** (CodeRabbit comment #5).
   Previously `pluginVersion ?? ""` collapsed undefined to "", and the
   UPDATE always wrote the column. A future caller that doesn't pass
   `pluginVersion` would silently erase a previously-stored real
   version. Fix: keep the column OUT of the SET clause when
   pluginVersion is undefined; INSERT still defaults to '' to match
   the column DEFAULT. Passing an explicit "" still clears the value
   (escape hatch). New regression test in upload-summary.test.ts
   asserts both branches and the negative pattern.

2. **plugin-version-resolution regex widened to accept full SemVer 2.0**
   (CodeRabbit comment #1). The original `/^\d+\.\d+\.\d+$/` rejected
   prerelease and build metadata strings like `0.8.0-rc.1` or
   `1.2.3+exp.sha.5114f85`. We don't ship those today but a future RC
   tag shouldn't silently fail this guard.

3. **pi/extension-source/hivemind.ts now stamps plugin_version on its
   capture INSERT and threads it into the wiki-worker spawn config.**
   Pi runs a TS extension instead of compiled hook bundles, so the
   shared `src/hooks/capture.ts` change didn't propagate. Without this
   fix, pi sessions would land rows with `plugin_version = ''` even on
   tables migrated by other agents. The extension reads the version
   stamp at `~/.pi/agent/.hivemind/.hivemind_version` (written by
   `hivemind pi install`); if the stamp is missing, falls back to '',
   which matches the column DEFAULT — schema-compatible.

CodeRabbit comments #2, #3, #4 (cursor/hermes "use own manifest") are
not addressed because the referenced manifest directories
(.cursor-plugin / .hermes-plugin) do not exist in the repo. Cursor /
Hermes / Pi rely on the fallback chain inside getInstalledVersion:
`<bundle>/../.hivemind_version` (written by their installers) →
walk-up package.json (in-repo dev). Both paths resolve to the right
version today; passing `.claude-plugin` as the first arg is a no-op
for these agents.

2244 tests pass.
@efenocchi
Copy link
Copy Markdown
Collaborator Author

Response to CodeRabbit review

Addressed the actionable comments in 3c8356e + 2daea7f. Summary:

✅ Accepted

#1 — Semver regex too strict (claude-code/tests/plugin-version-resolution.test.ts:55). Widened to the full SemVer 2.0 shape so 0.8.0-rc.1 style prerelease tags don't trip the guard in the future.

#5 — upload-summary UPDATE overwrites stored plugin_version with '' (outside-diff comment on src/hooks/upload-summary.ts:73-87). Real edge case — a future caller that doesn't pass pluginVersion would silently erase a previously-stored real version. Fixed: keep the column OUT of the SET clause when pluginVersion === undefined; INSERT still defaults to '' to match the column DEFAULT; explicit "" still clears (escape hatch). New regression tests in upload-summary.test.ts lock both branches plus a negative pattern.

❌ Rejected (CodeRabbit hallucination)

#2, #3, #4 — "resolve Cursor/Hermes from its own manifest". These comments claim there's a .cursor-plugin / .hermes-plugin manifest dir to use. Those directories do not exist in this repo. Only claude-code/.claude-plugin/ and codex/.codex-plugin/ exist as in-repo manifests.

For cursor/hermes/pi, the version resolves through getInstalledVersion's second fallback: <bundle>/../.hivemind_version, written by each installer via writeVersionStamp(PLUGIN_DIR, getVersion()) (see src/cli/install-cursor.ts, install-hermes.ts, install-pi.ts). In dev (--plugin-dir), the third fallback walks up to the repo's @deeplake/hivemind package.json. Both paths resolve to the right value today; passing .claude-plugin as the first arg is intentionally a no-op for these agents — the function signature requires something there, and we don't need a fourth lookup branch.

Verifiable: ls cursor/.claude-plugin cursor/.cursor-plugin hermes/.claude-plugin hermes/.hermes-plugin — none exist.

Bonus fix (discovered while preparing e2e)

Pi runs a TS extension (pi/extension-source/hivemind.ts) instead of compiled hook bundles, so the shared src/hooks/capture.ts change didn't propagate to it. Pi sessions would have landed rows with plugin_version = '' even on tables migrated by other agents. Added the equivalent stamping logic to the pi extension, sourcing the version from ~/.pi/agent/.hivemind/.hivemind_version (written by hivemind pi install).


2244 tests pass. Coverage report on the next CI run.

openclaw was the only agent whose capture path still wrote the sessions
row without `plugin_version` — the PR added the column to every other
agent (claude-code, codex, cursor, hermes, pi) but missed openclaw's
INSERT at openclaw/src/index.ts:1130. Production rows from openclaw
landed with an empty `plugin_version` while all other agents stamped
the real installed version.

This patch:
- adds `plugin_version` to the INSERT column list
- threads `getInstalledVersion()` (already wired via the esbuild
  `__HIVEMIND_VERSION__` define) through the VALUES clause
- extends the bundle-level regex guard in
  claude-code/tests/plugin-version-resolution.test.ts to also scan
  openclaw/dist/index.js, so a future build that drops the column
  fails the test instead of regressing silently

Live e2e against the test_plugin/default tables is blocked right now
by a backend outage on api.deeplake.ai (plain `SELECT 1` returns
HTTP 000 with a 10s timeout). Bundle test passes; the runtime
behavior matches the codex/cursor/hermes pattern verified earlier
during this PR.
@efenocchi
Copy link
Copy Markdown
Collaborator Author

Follow-up: openclaw plugin_version fix (29663d7)

Caught a gap during a final e2e sweep across all agents — openclaw's session-capture INSERT at openclaw/src/index.ts:1130 was the only write path that still omitted plugin_version. Every other agent (claude-code, codex, cursor, hermes, pi) was already wired through. Patch:

  • adds plugin_version to the INSERT column list and threads getInstalledVersion() (already populated via the esbuild __HIVEMIND_VERSION__ define) through the VALUES clause
  • extends the bundle-level regex guard in plugin-version-resolution.test.ts to also scan openclaw/dist/index.js, so a future build that drops the column fails the test instead of regressing silently

E2E status note: live verification against api.deeplake.ai is blocked right now by a backend outage — plain SELECT 1 AS ok against …/workspaces/default/tables/query returns HTTP 000 with 10s timeouts, and the openclaw embedded gateway's auto-recall is logging Query timeout after 10000ms for the same reason. Pi was originally diagnosed as "hanging at LLM init" but is in fact also impacted by the same backend issue (its session_start probe / capture INSERTs hit the same SELECT timeouts). The plugin_version path itself is verified by the bundle-level test (which passes for all 5 agents + openclaw); a fresh live e2e for openclaw will follow once the backend recovers.

@efenocchi
Copy link
Copy Markdown
Collaborator Author

Correction on the backend-status claim

Pushed back on my own previous comment after the user rightly asked "is it a backend issue?" — re-probed and it isn't:

  • direct curl to the SQL endpoint: HTTP 200 in 70ms for SELECT 1
  • DeeplakeApi (same Node code path the plugin runs) SELECT 1: HTTP 200 in 77ms
  • 5x repeat with the full plugin header set (incl. X-Activeloop-Org-Id): 60–156ms, all 200

Re-ran the openclaw live e2e against sessions_pv_e2e_1778545516:

INSERT INTO "sessions_pv_e2e_1778545516" (...,agent,plugin_version,...) VALUES (..., 'openclaw', '0.7.18-openclaw-e2e', ...)
  → ok in 490ms

SELECT path, agent, plugin_version FROM ... WHERE path = '/sessions/openclaw-e2e/...'
  → [{ "agent": "openclaw", "plugin_version": "0.7.18-openclaw-e2e" }]

PASS — openclaw is now verified live against the test table, same as the other 4 agents.

The 10s timeouts and Auto-recall failed: Query timeout after 10000ms lines I saw earlier were transient (probably contention from the running openclaw gateway holding a stuck embedded run, plus a 10s client-side timeout that's tight when the backend is busy). Not a sustained backend outage. Sorry for the earlier mischaracterization.

@efenocchi
Copy link
Copy Markdown
Collaborator Author

Update: openclaw real-e2e ✅ + main merged

openclaw end-to-end finalmente verificato in produzione

Dopo aver tracciato i log di openclaw runtime, trovate due gate di security non documentate che bloccavano agent_end:

  1. plugins.allow non includeva "hivemind" → plugin caricato come "discovered" ma non operativo
  2. plugins.entries.hivemind.hooks.allowConversationAccess non era true → il runtime logga esplicitamente:

    typed hook "agent_end" blocked because non-bundled plugins must set plugins.entries.hivemind.hooks.allowConversationAccess=true

Una volta sistemate entrambe nel mio ~/.openclaw/openclaw.json, real Telegram message → claude-sonnet-4 → agent_end → hivemind plugin INSERT scrive in modo pulito:

SELECT plugin_version, COUNT(*) FROM "sessions" WHERE agent='openclaw' AND last_update_date > '2026-05-12T20:00';
plugin_version = "0.7.18", n = 28

Scoreboard finale 6/6

agent e2e reale plugin_version osservato
claude_code 0.7.18
codex 0.7.150.7.18 (post-autoupdate stamp refresh)
cursor 0.7.15
hermes 0.7.15
pi 0.7.18
openclaw ✅ Telegram→agent_end→INSERT 0.7.18

Merge da main

  • origin/main mergeato pulito (zero conflitti) — porta in bundle gli skillify dedup/bug fix di PR fix(skilify): plug the three holes that produced cross-author duplicate skills #119 + bump 0.7.18 → 0.7.20.
  • Build: 12 CC + 10 Codex + 9 Cursor + 9 Hermes + 1 OpenClaw + 1 MCP + 1 CLI bundle.
  • Test: 2258/2258 pass (4 run consecutivi clean). Un flake intermittente in deeplake-api-retry.test.ts (mock-fetch race) preesistente, non legato al PR.

Pronto per merge.

efenocchi added 2 commits May 12, 2026 22:58
The per-agent matrix and step-by-step release checklist have been
superseded by the end-to-end verification flow validated in PR #120
(real e2e against every shipped agent — claude_code, codex, cursor,
hermes, pi, openclaw — including openclaw via Telegram). The file
was a snapshot of pre-release manual steps that no longer reflects
the current process and was last edited before the openclaw security
gates (plugins.allow + plugins.entries.hivemind.hooks.allowConversationAccess)
were documented.
# Conflicts:
#	RELEASE_CHECKLIST.md
#	tests/claude-code/plugin-version-resolution.test.ts
#	tests/claude-code/spawn-wiki-worker.test.ts
#	tests/claude-code/wiki-worker-plugin-version.test.ts
@efenocchi efenocchi merged commit 25940f4 into main May 13, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants