agenthub-multiagent-mcp 1.11.1 → 1.13.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.
Files changed (77) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/brain/backend.d.ts +12 -0
  3. package/dist/brain/backend.d.ts.map +1 -0
  4. package/dist/brain/backend.js +33 -0
  5. package/dist/brain/backend.js.map +1 -0
  6. package/dist/brain/jsonlBrain.d.ts +17 -0
  7. package/dist/brain/jsonlBrain.d.ts.map +1 -0
  8. package/dist/brain/jsonlBrain.js +50 -0
  9. package/dist/brain/jsonlBrain.js.map +1 -0
  10. package/dist/brain/memvidBrain.d.ts +28 -0
  11. package/dist/brain/memvidBrain.d.ts.map +1 -0
  12. package/dist/brain/memvidBrain.js +281 -0
  13. package/dist/brain/memvidBrain.js.map +1 -0
  14. package/dist/brain/memvidBrain.test.d.ts +8 -0
  15. package/dist/brain/memvidBrain.test.d.ts.map +1 -0
  16. package/dist/brain/memvidBrain.test.js +121 -0
  17. package/dist/brain/memvidBrain.test.js.map +1 -0
  18. package/dist/brain/migrate.d.ts +36 -0
  19. package/dist/brain/migrate.d.ts.map +1 -0
  20. package/dist/brain/migrate.js +174 -0
  21. package/dist/brain/migrate.js.map +1 -0
  22. package/dist/brain/migrate.test.d.ts +5 -0
  23. package/dist/brain/migrate.test.d.ts.map +1 -0
  24. package/dist/brain/migrate.test.js +139 -0
  25. package/dist/brain/migrate.test.js.map +1 -0
  26. package/dist/brain/types.d.ts +41 -0
  27. package/dist/brain/types.d.ts.map +1 -0
  28. package/dist/brain/types.js +5 -0
  29. package/dist/brain/types.js.map +1 -0
  30. package/dist/commands/resume.d.ts +61 -0
  31. package/dist/commands/resume.d.ts.map +1 -0
  32. package/dist/commands/resume.js +145 -0
  33. package/dist/commands/resume.js.map +1 -0
  34. package/dist/commands/resume.test.d.ts +2 -0
  35. package/dist/commands/resume.test.d.ts.map +1 -0
  36. package/dist/commands/resume.test.js +106 -0
  37. package/dist/commands/resume.test.js.map +1 -0
  38. package/dist/commands/setup-shell.d.ts +21 -0
  39. package/dist/commands/setup-shell.d.ts.map +1 -0
  40. package/dist/commands/setup-shell.js +173 -0
  41. package/dist/commands/setup-shell.js.map +1 -0
  42. package/dist/commands/setup-shell.test.d.ts +2 -0
  43. package/dist/commands/setup-shell.test.d.ts.map +1 -0
  44. package/dist/commands/setup-shell.test.js +169 -0
  45. package/dist/commands/setup-shell.test.js.map +1 -0
  46. package/dist/hooks/brainCapture.d.ts.map +1 -1
  47. package/dist/hooks/brainCapture.js +12 -4
  48. package/dist/hooks/brainCapture.js.map +1 -1
  49. package/dist/index.js +20 -0
  50. package/dist/index.js.map +1 -1
  51. package/dist/setup.js +16 -4
  52. package/dist/setup.js.map +1 -1
  53. package/dist/tools/index.d.ts.map +1 -1
  54. package/dist/tools/index.js +85 -5
  55. package/dist/tools/index.js.map +1 -1
  56. package/dist/tools/tools.test.js +64 -2
  57. package/dist/tools/tools.test.js.map +1 -1
  58. package/package.json +4 -2
  59. package/scripts/memvid-smoke.ts +89 -0
  60. package/scripts/memvidBrain-smoke.ts +83 -0
  61. package/scripts/migrate-smoke.ts +134 -0
  62. package/src/brain/backend.ts +37 -0
  63. package/src/brain/jsonlBrain.ts +62 -0
  64. package/src/brain/memvidBrain.test.ts +136 -0
  65. package/src/brain/memvidBrain.ts +285 -0
  66. package/src/brain/migrate.test.ts +164 -0
  67. package/src/brain/migrate.ts +213 -0
  68. package/src/brain/types.ts +45 -0
  69. package/src/commands/resume.test.ts +127 -0
  70. package/src/commands/resume.ts +205 -0
  71. package/src/commands/setup-shell.test.ts +185 -0
  72. package/src/commands/setup-shell.ts +200 -0
  73. package/src/hooks/brainCapture.ts +11 -9
  74. package/src/index.ts +21 -0
  75. package/src/setup.ts +15 -4
  76. package/src/tools/index.ts +85 -5
  77. package/src/tools/tools.test.ts +119 -2
package/CHANGELOG.md ADDED
@@ -0,0 +1,23 @@
1
+ # Changelog
2
+
3
+ All notable changes to `agenthub-multiagent-mcp` are documented here.
4
+
5
+ ## 1.12.0 — 2026-04-20
6
+
7
+ ### Added
8
+ - **Memvid backend** is now the default local brain store. Sessions are written to `~/.claude/brains/<agent>.mv2` via `@memvid/sdk@^2.0.159` with lexical (BM25) search and metadata-preserving recall-by-session.
9
+ - **JSONL fallback** retained for compatibility. Set `AGENTHUB_BRAIN_BACKEND=jsonl` to keep using `~/.claude/brains/<agent>.jsonl`.
10
+ - **Auto-migration at boot.** When the memvid backend is active, any agent with an existing `.jsonl` brain is migrated once to `.mv2`. Source is frozen to `<agent>.jsonl.frozen.<ISO>` and left in place. Migration is atomic (temp agent id + count verification + `renameSync`) and idempotent.
11
+ - **Windows semantic search is opt-in.** Memvid's local embedding models (bge-small, nomic) do not ship ONNX runtime builds for Windows, so the default install is BM25-only. To enable semantic search, set `OPENAI_BASE_URL` and `OPENAI_API_KEY` to any OpenAI-compatible `/v1/embeddings` endpoint (OpenRouter verified).
12
+
13
+ ### Changed
14
+ - `@memvid/sdk` dependency adds ~250 transitive packages (langchain, llamaindex, ai, langgraph, OCR/PDF tooling). `npm audit` reports 10 vulnerabilities in the tree (3 moderate, 7 high); none affect the code paths we use. Accepted trade-off for persistent vector-capable local brain.
15
+
16
+ ### Fixed
17
+ - Corrected session-189's premature JSONL pivot: `@memvid/sdk@2.0.159` is publicly available on npm. See `openspec/archive/add-org-brain/design.md` ADR D3.1.
18
+
19
+ ## 1.11.1 — 2026-04-17
20
+
21
+ ### Fixed
22
+ - WebSocket client now sends `X-API-Key` as a header instead of a query param, aligning with server-side auth convention.
23
+ - FTS5 query sanitizer in Go server prevents malformed queries from 500-ing the search endpoint.
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Brain backend selector (§1.3 finish-agenthub-v1-backlog).
3
+ *
4
+ * `AGENTHUB_BRAIN_BACKEND=memvid` (default) → MemvidBrain (.mv2 files)
5
+ * `AGENTHUB_BRAIN_BACKEND=jsonl` → JsonlBrain (legacy .jsonl files)
6
+ *
7
+ * The selector is lazy + cached so tests can flip the env var before first use.
8
+ */
9
+ import type { BrainBackend } from "./types.js";
10
+ export declare function getBrain(): BrainBackend;
11
+ export declare function resetBrainBackendCache(): void;
12
+ //# sourceMappingURL=backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../src/brain/backend.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAO/C,wBAAgB,QAAQ,IAAI,YAAY,CAevC;AAED,wBAAgB,sBAAsB,IAAI,IAAI,CAG7C"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Brain backend selector (§1.3 finish-agenthub-v1-backlog).
3
+ *
4
+ * `AGENTHUB_BRAIN_BACKEND=memvid` (default) → MemvidBrain (.mv2 files)
5
+ * `AGENTHUB_BRAIN_BACKEND=jsonl` → JsonlBrain (legacy .jsonl files)
6
+ *
7
+ * The selector is lazy + cached so tests can flip the env var before first use.
8
+ */
9
+ import { memvidBrain } from "./memvidBrain.js";
10
+ import { jsonlBrain } from "./jsonlBrain.js";
11
+ let cached = null;
12
+ let cachedBackendName = null;
13
+ export function getBrain() {
14
+ const requested = (process.env.AGENTHUB_BRAIN_BACKEND ?? "memvid").toLowerCase();
15
+ if (cached && cachedBackendName === requested)
16
+ return cached;
17
+ switch (requested) {
18
+ case "jsonl":
19
+ cached = jsonlBrain;
20
+ break;
21
+ case "memvid":
22
+ default:
23
+ cached = memvidBrain;
24
+ break;
25
+ }
26
+ cachedBackendName = requested;
27
+ return cached;
28
+ }
29
+ export function resetBrainBackendCache() {
30
+ cached = null;
31
+ cachedBackendName = null;
32
+ }
33
+ //# sourceMappingURL=backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backend.js","sourceRoot":"","sources":["../../src/brain/backend.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,IAAI,MAAM,GAAwB,IAAI,CAAC;AACvC,IAAI,iBAAiB,GAAkB,IAAI,CAAC;AAE5C,MAAM,UAAU,QAAQ;IACtB,MAAM,SAAS,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IACjF,IAAI,MAAM,IAAI,iBAAiB,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IAE7D,QAAQ,SAAS,EAAE,CAAC;QAClB,KAAK,OAAO;YACV,MAAM,GAAG,UAAU,CAAC;YACpB,MAAM;QACR,KAAK,QAAQ,CAAC;QACd;YACE,MAAM,GAAG,WAAW,CAAC;YACrB,MAAM;IACV,CAAC;IACD,iBAAiB,GAAG,SAAS,CAAC;IAC9B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,MAAM,GAAG,IAAI,CAAC;IACd,iBAAiB,GAAG,IAAI,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * JSONL Brain — BrainBackend adapter over the existing localBrain.ts functions.
3
+ *
4
+ * Preserved as the fallback when AGENTHUB_BRAIN_BACKEND=jsonl, and as a
5
+ * disaster-recovery path if memvid proves unstable on a given platform.
6
+ */
7
+ import type { BrainBackend, SessionInput, SessionRecord, SearchHit, BrainInfo } from "./types.js";
8
+ export declare class JsonlBrain implements BrainBackend {
9
+ getBrainPath(agentId: string): string;
10
+ appendSession(agentId: string, session: SessionInput): Promise<number>;
11
+ search(agentId: string, query: string, limit?: number): Promise<SearchHit[]>;
12
+ recall(agentId: string, sessionNumber: number): Promise<SessionRecord | null>;
13
+ getSessionCount(agentId: string): Promise<number>;
14
+ listBrains(): Promise<BrainInfo[]>;
15
+ }
16
+ export declare const jsonlBrain: JsonlBrain;
17
+ //# sourceMappingURL=jsonlBrain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonlBrain.d.ts","sourceRoot":"","sources":["../../src/brain/jsonlBrain.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAElG,qBAAa,UAAW,YAAW,YAAY;IAC7C,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAa/B,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAUtE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAIvE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAc7E,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIjD,UAAU,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;CAGzC;AAED,eAAO,MAAM,UAAU,YAAmB,CAAC"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * JSONL Brain — BrainBackend adapter over the existing localBrain.ts functions.
3
+ *
4
+ * Preserved as the fallback when AGENTHUB_BRAIN_BACKEND=jsonl, and as a
5
+ * disaster-recovery path if memvid proves unstable on a given platform.
6
+ */
7
+ import * as localBrain from "./localBrain.js";
8
+ export class JsonlBrain {
9
+ getBrainPath(agentId) {
10
+ // localBrain has a private getBrainPath; recover it via listBrains if needed.
11
+ // For now, we do not publicly expose localBrain's path helper; return a best-effort path.
12
+ const entry = localBrain.listBrains().find((b) => b.agentId === agentId);
13
+ if (entry)
14
+ return entry.path;
15
+ // Fallback: mirror localBrain's sanitization for callers like the migration
16
+ // script that need a target path before any session exists.
17
+ const safe = agentId.replace(/[^a-zA-Z0-9_-]/g, "_");
18
+ const { join } = require("path");
19
+ const { homedir } = require("os");
20
+ return join(homedir(), ".claude", "brains", `${safe}.jsonl`);
21
+ }
22
+ async appendSession(agentId, session) {
23
+ return localBrain.appendSession(agentId, session.summary, session.content, session.files_changed, session.outcome);
24
+ }
25
+ async search(agentId, query, limit = 5) {
26
+ return localBrain.search(agentId, query, limit);
27
+ }
28
+ async recall(agentId, sessionNumber) {
29
+ const entry = localBrain.recall(agentId, sessionNumber);
30
+ if (!entry)
31
+ return null;
32
+ return {
33
+ session_number: entry.session_number,
34
+ agent_id: entry.agent_id,
35
+ timestamp: entry.timestamp,
36
+ summary: entry.summary,
37
+ content: entry.content,
38
+ files_changed: entry.files_changed,
39
+ outcome: entry.outcome,
40
+ };
41
+ }
42
+ async getSessionCount(agentId) {
43
+ return localBrain.getSessionCount(agentId);
44
+ }
45
+ async listBrains() {
46
+ return localBrain.listBrains();
47
+ }
48
+ }
49
+ export const jsonlBrain = new JsonlBrain();
50
+ //# sourceMappingURL=jsonlBrain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonlBrain.js","sourceRoot":"","sources":["../../src/brain/jsonlBrain.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAC;AAG9C,MAAM,OAAO,UAAU;IACrB,YAAY,CAAC,OAAe;QAC1B,8EAA8E;QAC9E,0FAA0F;QAC1F,MAAM,KAAK,GAAG,UAAU,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC;QACzE,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC,IAAI,CAAC;QAC7B,4EAA4E;QAC5E,4DAA4D;QAC5D,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;QACrD,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QACjC,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAClC,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,QAAQ,CAAC,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,OAAe,EAAE,OAAqB;QACxD,OAAO,UAAU,CAAC,aAAa,CAC7B,OAAO,EACP,OAAO,CAAC,OAAO,EACf,OAAO,CAAC,OAAO,EACf,OAAO,CAAC,aAAa,EACrB,OAAO,CAAC,OAAO,CAChB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,OAAe,EAAE,KAAa,EAAE,KAAK,GAAG,CAAC;QACpD,OAAO,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,OAAe,EAAE,aAAqB;QACjD,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;QACxD,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,OAAO;YACL,cAAc,EAAE,KAAK,CAAC,cAAc;YACpC,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,OAAO,EAAE,KAAK,CAAC,OAAO;SACvB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,OAAe;QACnC,OAAO,UAAU,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,UAAU;QACd,OAAO,UAAU,CAAC,UAAU,EAAE,CAAC;IACjC,CAAC;CACF;AAED,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,UAAU,EAAE,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Memvid Brain — .mv2-backed per-agent session store (§1.2 finish-agenthub-v1-backlog).
3
+ *
4
+ * Storage: ~/.claude/brains/<agent-id>.mv2
5
+ *
6
+ * Encoding conventions (session_number must survive a round-trip through memvid's
7
+ * document model, which stores metadata as inline "key: value" text lines):
8
+ * - title: `[session <N>] <summary>` — N is extractable via regex
9
+ * - text: full session content — indexed by BM25
10
+ * - metadata: { session_number, agent_id, timestamp, files_changed (JSON), outcome, summary }
11
+ * Memvid serializes each metadata field as a "key: value" line appended to the
12
+ * document's searchable body, so they round-trip through find().text.
13
+ *
14
+ * Search is lexical (BM25) by default — works everywhere including Windows.
15
+ * Semantic search is opt-in via AGENTHUB_BRAIN_EMBEDDINGS=openai|openrouter and the
16
+ * usual OpenAI-compatible env vars. Not wired in this file yet (future phase).
17
+ */
18
+ import type { BrainBackend, SessionInput, SessionRecord, SearchHit, BrainInfo } from "./types.js";
19
+ export declare class MemvidBrain implements BrainBackend {
20
+ getBrainPath(agentId: string): string;
21
+ appendSession(agentId: string, session: SessionInput): Promise<number>;
22
+ search(agentId: string, query: string, limit?: number): Promise<SearchHit[]>;
23
+ recall(agentId: string, sessionNumber: number): Promise<SessionRecord | null>;
24
+ getSessionCount(agentId: string): Promise<number>;
25
+ listBrains(): Promise<BrainInfo[]>;
26
+ }
27
+ export declare const memvidBrain: MemvidBrain;
28
+ //# sourceMappingURL=memvidBrain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memvidBrain.d.ts","sourceRoot":"","sources":["../../src/brain/memvidBrain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAOH,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAmJlG,qBAAa,WAAY,YAAW,YAAY;IAC9C,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAI/B,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IA0BtE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IA0BvE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAmB7E,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAYjD,UAAU,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;CAwBzC;AAED,eAAO,MAAM,WAAW,aAAoB,CAAC"}
@@ -0,0 +1,281 @@
1
+ /**
2
+ * Memvid Brain — .mv2-backed per-agent session store (§1.2 finish-agenthub-v1-backlog).
3
+ *
4
+ * Storage: ~/.claude/brains/<agent-id>.mv2
5
+ *
6
+ * Encoding conventions (session_number must survive a round-trip through memvid's
7
+ * document model, which stores metadata as inline "key: value" text lines):
8
+ * - title: `[session <N>] <summary>` — N is extractable via regex
9
+ * - text: full session content — indexed by BM25
10
+ * - metadata: { session_number, agent_id, timestamp, files_changed (JSON), outcome, summary }
11
+ * Memvid serializes each metadata field as a "key: value" line appended to the
12
+ * document's searchable body, so they round-trip through find().text.
13
+ *
14
+ * Search is lexical (BM25) by default — works everywhere including Windows.
15
+ * Semantic search is opt-in via AGENTHUB_BRAIN_EMBEDDINGS=openai|openrouter and the
16
+ * usual OpenAI-compatible env vars. Not wired in this file yet (future phase).
17
+ */
18
+ import { use } from "@memvid/sdk";
19
+ import { existsSync, mkdirSync, readdirSync } from "fs";
20
+ import { join } from "path";
21
+ import { homedir } from "os";
22
+ const BRAIN_DIR_NAME = "brains";
23
+ const SESSION_TITLE_RE = /^\[session\s+(\d+)\]\s*(.*)$/i;
24
+ function getBrainsDir() {
25
+ return join(homedir(), ".claude", BRAIN_DIR_NAME);
26
+ }
27
+ function sanitizeAgentId(agentId) {
28
+ return agentId.replace(/[^a-zA-Z0-9_-]/g, "_");
29
+ }
30
+ function ensureBrainsDir() {
31
+ const dir = getBrainsDir();
32
+ if (!existsSync(dir))
33
+ mkdirSync(dir, { recursive: true });
34
+ }
35
+ function getBrainPath(agentId) {
36
+ return join(getBrainsDir(), `${sanitizeAgentId(agentId)}.mv2`);
37
+ }
38
+ async function openForWrite(path) {
39
+ return use("basic", path, { mode: "auto" });
40
+ }
41
+ async function openForRead(path) {
42
+ if (!existsSync(path))
43
+ return null;
44
+ return use("basic", path, { mode: "open", readOnly: true });
45
+ }
46
+ /**
47
+ * Memvid treats `seal()` on a readOnly handle as a mutation and throws.
48
+ * We still need to release the file handle, but there is no dedicated close()
49
+ * for readOnly handles. Relying on GC is acceptable — the N-API layer releases
50
+ * the Rust resources when the JS wrapper is collected.
51
+ */
52
+ async function closeRead(_mv) {
53
+ // no-op; see comment above
54
+ }
55
+ /**
56
+ * Parse `[session N] summary` title. Returns null if the title doesn't match.
57
+ */
58
+ function parseTitle(title) {
59
+ if (!title)
60
+ return null;
61
+ const m = SESSION_TITLE_RE.exec(title.trim());
62
+ if (!m)
63
+ return null;
64
+ return { sessionNumber: Number(m[1]), summary: m[2] ?? "" };
65
+ }
66
+ /**
67
+ * Extract a metadata value from memvid's serialized hit text.
68
+ *
69
+ * Memvid appends metadata as `key: <JSON-encoded-value>` tokens to the document
70
+ * body, whitespace-separated (no newlines). Each value is a JSON string/array/
71
+ * object/number/bool/null.
72
+ *
73
+ * Returns the decoded JS value (string, array, etc.) or null if not present.
74
+ */
75
+ function extractMetaValue(text, key) {
76
+ if (!text)
77
+ return null;
78
+ // Match `<whitespace or start><key>:<space>` then capture a JSON value.
79
+ // JSON values are: "..." | [...] | {...} | number | true/false/null | unquoted word.
80
+ const re = new RegExp(`(?:^|\\s)${key}:\\s*("(?:[^"\\\\]|\\\\.)*"|\\[[^\\]]*\\]|\\{[^}]*\\}|-?\\d+(?:\\.\\d+)?|true|false|null|[^\\s]+)`);
81
+ const m = re.exec(text);
82
+ if (!m)
83
+ return null;
84
+ const raw = m[1];
85
+ try {
86
+ return JSON.parse(raw);
87
+ }
88
+ catch {
89
+ return raw;
90
+ }
91
+ }
92
+ function extractMetaString(text, key) {
93
+ const v = extractMetaValue(text, key);
94
+ return typeof v === "string" ? v : v != null ? String(v) : null;
95
+ }
96
+ function extractMetaStringArray(text, key) {
97
+ const v = extractMetaValue(text, key);
98
+ return Array.isArray(v) ? v.map(String) : undefined;
99
+ }
100
+ function hitToRecord(agentId, hit) {
101
+ const parsed = parseTitle(hit?.title);
102
+ if (!parsed)
103
+ return null;
104
+ const text = hit?.text ?? "";
105
+ const timestamp = extractMetaString(text, "timestamp") ?? new Date().toISOString();
106
+ const outcomeRaw = extractMetaString(text, "outcome");
107
+ const outcome = outcomeRaw && outcomeRaw.length > 0 ? outcomeRaw : undefined;
108
+ const filesChanged = extractMetaStringArray(text, "files_changed");
109
+ const content = stripMetadataTokens(text);
110
+ return {
111
+ session_number: parsed.sessionNumber,
112
+ agent_id: agentId,
113
+ timestamp,
114
+ summary: parsed.summary,
115
+ content,
116
+ files_changed: filesChanged && filesChanged.length > 0 ? filesChanged : undefined,
117
+ outcome,
118
+ };
119
+ }
120
+ /**
121
+ * Strip memvid's auto-appended `<key>: <JSON>` metadata tail from a hit's text
122
+ * to recover the original content. Cuts at the first known metadata key we see.
123
+ */
124
+ function stripMetadataTokens(text) {
125
+ if (!text)
126
+ return "";
127
+ // Find the earliest occurrence of any known trailing-metadata sentinel.
128
+ const sentinels = [
129
+ /\btitle:\s/,
130
+ /\btags:\s/,
131
+ /\blabels:\s/,
132
+ /\bextractous_metadata:\s/,
133
+ /\bsession_number:\s/,
134
+ /\bagent_id:\s/,
135
+ /\btimestamp:\s/,
136
+ /\bfiles_changed:\s/,
137
+ /\boutcome:\s/,
138
+ /\bsummary:\s/,
139
+ ];
140
+ let cutIdx = text.length;
141
+ for (const re of sentinels) {
142
+ const m = re.exec(text);
143
+ if (m && m.index < cutIdx)
144
+ cutIdx = m.index;
145
+ }
146
+ return text.slice(0, cutIdx).trim();
147
+ }
148
+ async function nextSessionNumber(agentId) {
149
+ const path = getBrainPath(agentId);
150
+ const mv = await openForRead(path);
151
+ if (!mv)
152
+ return 1;
153
+ try {
154
+ const stats = await mv.stats();
155
+ return (stats?.frame_count ?? 0) + 1;
156
+ }
157
+ finally {
158
+ await closeRead(mv);
159
+ }
160
+ }
161
+ export class MemvidBrain {
162
+ getBrainPath(agentId) {
163
+ return getBrainPath(agentId);
164
+ }
165
+ async appendSession(agentId, session) {
166
+ ensureBrainsDir();
167
+ const sessionNumber = session.session_number ?? (await nextSessionNumber(agentId));
168
+ const timestamp = session.timestamp ?? new Date().toISOString();
169
+ const path = getBrainPath(agentId);
170
+ const mv = await openForWrite(path);
171
+ try {
172
+ await mv.put({
173
+ title: `[session ${sessionNumber}] ${session.summary}`,
174
+ label: sanitizeAgentId(agentId),
175
+ text: session.content,
176
+ metadata: {
177
+ session_number: sessionNumber,
178
+ agent_id: agentId,
179
+ timestamp,
180
+ summary: session.summary,
181
+ files_changed: session.files_changed ?? [],
182
+ outcome: session.outcome ?? "",
183
+ },
184
+ });
185
+ }
186
+ finally {
187
+ await mv.seal();
188
+ }
189
+ return sessionNumber;
190
+ }
191
+ async search(agentId, query, limit = 5) {
192
+ const path = getBrainPath(agentId);
193
+ const mv = await openForRead(path);
194
+ if (!mv)
195
+ return [];
196
+ try {
197
+ const res = await mv.find(query, { k: limit, mode: "lex" });
198
+ const hits = [];
199
+ for (const rawHit of res.hits ?? []) {
200
+ const hit = rawHit;
201
+ const parsed = parseTitle(hit?.title);
202
+ if (!parsed)
203
+ continue;
204
+ const timestamp = extractMetaString(hit?.text, "timestamp") ?? "";
205
+ hits.push({
206
+ session_number: parsed.sessionNumber,
207
+ timestamp,
208
+ summary: parsed.summary,
209
+ snippet: hit?.snippet ?? "",
210
+ score: typeof hit?.score === "number" ? hit.score : 0,
211
+ });
212
+ }
213
+ return hits;
214
+ }
215
+ finally {
216
+ await closeRead(mv);
217
+ }
218
+ }
219
+ async recall(agentId, sessionNumber) {
220
+ const path = getBrainPath(agentId);
221
+ const mv = await openForRead(path);
222
+ if (!mv)
223
+ return null;
224
+ try {
225
+ // Match the title prefix. `[session N]` is distinctive enough.
226
+ const res = await mv.find(`[session ${sessionNumber}]`, { k: 5, mode: "lex" });
227
+ for (const hit of res.hits ?? []) {
228
+ const parsed = parseTitle(hit?.title);
229
+ if (parsed?.sessionNumber === sessionNumber) {
230
+ return hitToRecord(agentId, hit);
231
+ }
232
+ }
233
+ return null;
234
+ }
235
+ finally {
236
+ await closeRead(mv);
237
+ }
238
+ }
239
+ async getSessionCount(agentId) {
240
+ const path = getBrainPath(agentId);
241
+ const mv = await openForRead(path);
242
+ if (!mv)
243
+ return 0;
244
+ try {
245
+ const stats = await mv.stats();
246
+ return stats?.frame_count ?? 0;
247
+ }
248
+ finally {
249
+ await closeRead(mv);
250
+ }
251
+ }
252
+ async listBrains() {
253
+ const dir = getBrainsDir();
254
+ if (!existsSync(dir))
255
+ return [];
256
+ const result = [];
257
+ for (const file of readdirSync(dir)) {
258
+ if (!file.endsWith(".mv2"))
259
+ continue;
260
+ const agentId = file.slice(0, -".mv2".length);
261
+ const fullPath = join(dir, file);
262
+ let sessions = 0;
263
+ try {
264
+ const mv = await openForRead(fullPath);
265
+ if (mv) {
266
+ const stats = await mv.stats();
267
+ sessions = stats?.frame_count ?? 0;
268
+ await closeRead(mv);
269
+ }
270
+ }
271
+ catch {
272
+ // If a .mv2 is locked or corrupted, still list it with 0 sessions.
273
+ sessions = 0;
274
+ }
275
+ result.push({ agentId, sessions, path: fullPath });
276
+ }
277
+ return result;
278
+ }
279
+ }
280
+ export const memvidBrain = new MemvidBrain();
281
+ //# sourceMappingURL=memvidBrain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memvidBrain.js","sourceRoot":"","sources":["../../src/brain/memvidBrain.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Unit tests for MemvidBrain (§1.5 finish-agenthub-v1-backlog).
3
+ *
4
+ * Uses a per-test temp dir as the fake HOME, so `~/.claude/brains/` maps under
5
+ * that temp dir and real user data is never touched.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=memvidBrain.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memvidBrain.test.d.ts","sourceRoot":"","sources":["../../src/brain/memvidBrain.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Unit tests for MemvidBrain (§1.5 finish-agenthub-v1-backlog).
3
+ *
4
+ * Uses a per-test temp dir as the fake HOME, so `~/.claude/brains/` maps under
5
+ * that temp dir and real user data is never touched.
6
+ */
7
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
8
+ import { mkdtempSync, rmSync } from "fs";
9
+ import { tmpdir } from "os";
10
+ import { join } from "path";
11
+ let tempHome;
12
+ function setFakeHome(dir) {
13
+ // memvidBrain reads `os.homedir()` lazily — patch it via env + module-level mock.
14
+ if (process.platform === "win32") {
15
+ process.env.USERPROFILE = dir;
16
+ }
17
+ else {
18
+ process.env.HOME = dir;
19
+ }
20
+ }
21
+ // Mock os.homedir before the modules under test are imported.
22
+ vi.mock("os", async () => {
23
+ const actual = await vi.importActual("os");
24
+ return {
25
+ ...actual,
26
+ homedir: () => process.env.__TEST_HOME__ ?? actual.homedir(),
27
+ };
28
+ });
29
+ async function importMemvidBrain() {
30
+ // Import lazily so the os mock is applied before the module reads homedir.
31
+ const mod = await import("./memvidBrain.js");
32
+ return mod;
33
+ }
34
+ describe("MemvidBrain", () => {
35
+ beforeEach(() => {
36
+ tempHome = mkdtempSync(join(tmpdir(), "memvid-brain-test-"));
37
+ process.env.__TEST_HOME__ = tempHome;
38
+ setFakeHome(tempHome);
39
+ });
40
+ afterEach(() => {
41
+ try {
42
+ rmSync(tempHome, { recursive: true, force: true });
43
+ }
44
+ catch {
45
+ // best effort
46
+ }
47
+ delete process.env.__TEST_HOME__;
48
+ });
49
+ it("round-trip: append, search, recall", async () => {
50
+ const { MemvidBrain } = await importMemvidBrain();
51
+ const brain = new MemvidBrain();
52
+ const agent = "test-agent-1";
53
+ const sessions = [
54
+ { summary: "intel deploy", content: "Deployed intel-layer runs." },
55
+ { summary: "org brain staging", content: "Shipped org brain models and store." },
56
+ { summary: "FTS5 sanitizer", content: "Hardened FTS5 query sanitizer against punctuation." },
57
+ { summary: "WS header auth", content: "Migrated WebSocket API key to X-API-Key header." },
58
+ { summary: "openspec cleanup", content: "Archived 10 changes, reconciled tasks." },
59
+ ];
60
+ const appended = [];
61
+ for (const s of sessions) {
62
+ const n = await brain.appendSession(agent, s);
63
+ appended.push(n);
64
+ }
65
+ expect(appended).toEqual([1, 2, 3, 4, 5]);
66
+ expect(await brain.getSessionCount(agent)).toBe(5);
67
+ });
68
+ it("search returns the expected session for a lexical query", async () => {
69
+ const { MemvidBrain } = await importMemvidBrain();
70
+ const brain = new MemvidBrain();
71
+ const agent = "test-agent-2";
72
+ await brain.appendSession(agent, { summary: "intel deploy", content: "Deployed intel-layer runs." });
73
+ await brain.appendSession(agent, {
74
+ summary: "FTS5 sanitizer",
75
+ content: "Hardened FTS5 query sanitizer against reserved tokens.",
76
+ });
77
+ await brain.appendSession(agent, { summary: "openspec", content: "Archived changes." });
78
+ const hits = await brain.search(agent, "FTS5 sanitizer", 5);
79
+ expect(hits.length).toBeGreaterThan(0);
80
+ expect(hits[0].summary).toBe("FTS5 sanitizer");
81
+ expect(hits[0].session_number).toBe(2);
82
+ });
83
+ it("recall returns full record for a known session number", async () => {
84
+ const { MemvidBrain } = await importMemvidBrain();
85
+ const brain = new MemvidBrain();
86
+ const agent = "test-agent-3";
87
+ await brain.appendSession(agent, { summary: "s1", content: "c1" });
88
+ const n = await brain.appendSession(agent, {
89
+ summary: "target",
90
+ content: "the content we want to recall",
91
+ files_changed: ["a.go", "b.ts"],
92
+ outcome: "success",
93
+ });
94
+ await brain.appendSession(agent, { summary: "s3", content: "c3" });
95
+ const recalled = await brain.recall(agent, n);
96
+ expect(recalled).not.toBeNull();
97
+ expect(recalled?.summary).toBe("target");
98
+ expect(recalled?.content).toBe("the content we want to recall");
99
+ expect(recalled?.outcome).toBe("success");
100
+ expect(recalled?.files_changed).toEqual(["a.go", "b.ts"]);
101
+ expect(recalled?.session_number).toBe(n);
102
+ });
103
+ it("empty brain: search returns [] without error", async () => {
104
+ const { MemvidBrain } = await importMemvidBrain();
105
+ const brain = new MemvidBrain();
106
+ const hits = await brain.search("never-registered-agent", "anything");
107
+ expect(hits).toEqual([]);
108
+ });
109
+ it("empty brain: recall returns null without error", async () => {
110
+ const { MemvidBrain } = await importMemvidBrain();
111
+ const brain = new MemvidBrain();
112
+ const record = await brain.recall("never-registered-agent", 1);
113
+ expect(record).toBeNull();
114
+ });
115
+ it("empty brain: getSessionCount returns 0", async () => {
116
+ const { MemvidBrain } = await importMemvidBrain();
117
+ const brain = new MemvidBrain();
118
+ expect(await brain.getSessionCount("never-registered-agent")).toBe(0);
119
+ });
120
+ });
121
+ //# sourceMappingURL=memvidBrain.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memvidBrain.test.js","sourceRoot":"","sources":["../../src/brain/memvidBrain.test.ts"],"names":[],"mappings":"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"}