@monoes/monomindcli 1.11.8 → 1.11.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/github/issue-tracker.md +5 -5
- package/.claude/agents/github/pr-manager.md +5 -5
- package/.claude/agents/github/release-manager.md +3 -3
- package/.claude/agents/github/repo-architect.md +3 -3
- package/.claude/agents/github/swarm-issue.md +1 -1
- package/.claude/agents/github/sync-coordinator.md +1 -1
- package/.claude/agents/github/workflow-automation.md +1 -1
- package/.claude/commands/github/repo-architect.md +1 -1
- package/.claude/commands/github/sync-coordinator.md +1 -1
- package/.claude/commands/mastermind/createorg.md +4 -1
- package/.claude/commands/mastermind/help.md +2 -2
- package/.claude/commands/mastermind/orgs.md +21 -0
- package/.claude/commands/mastermind/orgstatus.md +59 -0
- package/.claude/commands/mastermind/runorg.md +4 -2
- package/.claude/commands/mastermind/stoporg.md +78 -0
- package/.claude/commands/mastermind/swarm.md +1 -1
- package/.claude/helpers/handlers/gates-handler.cjs +135 -0
- package/.claude/helpers/handlers/task-handler.cjs +7 -3
- package/.claude/helpers/hook-handler.cjs +11 -2
- package/.claude/helpers/intelligence.cjs +87 -0
- package/.claude/helpers/learning-service.mjs +60 -0
- package/.claude/helpers/memory.cjs +69 -0
- package/.claude/helpers/router.cjs +68 -0
- package/.claude/helpers/session.cjs +63 -0
- package/.claude/helpers/utils/monograph.cjs +4 -2
- package/.claude/helpers/utils/telemetry.cjs +3 -2
- package/.claude/skills/agentic-jujutsu/SKILL.md +1 -1
- package/.claude/skills/hive-mind-advanced/SKILL.md +4 -4
- package/.claude/skills/mastermind/_agent-select.md +2 -2
- package/.claude/skills/mastermind/access.md +11 -11
- package/.claude/skills/mastermind/adapter-manager.md +13 -13
- package/.claude/skills/mastermind/adapters.md +7 -7
- package/.claude/skills/mastermind/agent-detail.md +1 -1
- package/.claude/skills/mastermind/agents.md +5 -5
- package/.claude/skills/mastermind/approval-detail.md +6 -6
- package/.claude/skills/mastermind/approve.md +9 -10
- package/.claude/skills/mastermind/backup.md +2 -2
- package/.claude/skills/mastermind/bootstrap.md +2 -2
- package/.claude/skills/mastermind/companies.md +7 -7
- package/.claude/skills/mastermind/createorg.md +213 -8
- package/.claude/skills/mastermind/diagnose.md +4 -4
- package/.claude/skills/mastermind/env.md +1 -1
- package/.claude/skills/mastermind/environments.md +8 -8
- package/.claude/skills/mastermind/export.md +12 -3
- package/.claude/skills/mastermind/goal-detail.md +9 -9
- package/.claude/skills/mastermind/goals.md +4 -4
- package/.claude/skills/mastermind/heartbeat.md +1 -1
- package/.claude/skills/mastermind/idea.md +4 -4
- package/.claude/skills/mastermind/import.md +8 -8
- package/.claude/skills/mastermind/inbox.md +4 -4
- package/.claude/skills/mastermind/instance-settings.md +9 -9
- package/.claude/skills/mastermind/instance.md +9 -7
- package/.claude/skills/mastermind/invite-landing.md +5 -5
- package/.claude/skills/mastermind/invites.md +12 -12
- package/.claude/skills/mastermind/issue-detail.md +8 -8
- package/.claude/skills/mastermind/monitor.md +11 -11
- package/.claude/skills/mastermind/my-issues.md +6 -6
- package/.claude/skills/mastermind/new-agent.md +4 -4
- package/.claude/skills/mastermind/org-chart.md +8 -6
- package/.claude/skills/mastermind/org-settings.md +58 -21
- package/.claude/skills/mastermind/orgs.md +98 -0
- package/.claude/skills/mastermind/orgstatus.md +194 -0
- package/.claude/skills/mastermind/plan-to-tasks.md +1 -1
- package/.claude/skills/mastermind/plugin-manager.md +12 -12
- package/.claude/skills/mastermind/plugin-settings.md +5 -5
- package/.claude/skills/mastermind/plugins.md +5 -5
- package/.claude/skills/mastermind/profile.md +2 -2
- package/.claude/skills/mastermind/project-detail.md +12 -12
- package/.claude/skills/mastermind/project-workspace.md +4 -4
- package/.claude/skills/mastermind/projects.md +4 -4
- package/.claude/skills/mastermind/review.md +50 -0
- package/.claude/skills/mastermind/routine-detail.md +3 -3
- package/.claude/skills/mastermind/routines.md +7 -6
- package/.claude/skills/mastermind/runorg.md +178 -8
- package/.claude/skills/mastermind/search.md +6 -6
- package/.claude/skills/mastermind/secrets.md +6 -6
- package/.claude/skills/mastermind/skills.md +4 -4
- package/.claude/skills/mastermind/stoporg.md +138 -0
- package/.claude/skills/mastermind/workspace-detail.md +5 -5
- package/.claude/skills/mastermind/workspaces.md +9 -9
- package/.claude/skills/performance-analysis/SKILL.md +3 -3
- package/.claude/skills/sparc-methodology/SKILL.md +2 -2
- package/.claude/skills/swarm-advanced/SKILL.md +4 -4
- package/README.md +129 -376
- package/dist/src/agents/registry-builder.d.ts +27 -1
- package/dist/src/agents/registry-builder.d.ts.map +1 -1
- package/dist/src/agents/registry-builder.js +2 -2
- package/dist/src/agents/registry-builder.js.map +1 -1
- package/dist/src/commands/agent.d.ts.map +1 -1
- package/dist/src/commands/agent.js +4 -9
- package/dist/src/commands/agent.js.map +1 -1
- package/dist/src/commands/analyze.d.ts +1 -1
- package/dist/src/commands/analyze.js +1 -1
- package/dist/src/commands/claims.d.ts +1 -1
- package/dist/src/commands/claims.js +2 -2
- package/dist/src/commands/claims.js.map +1 -1
- package/dist/src/commands/cleanup.d.ts +1 -1
- package/dist/src/commands/cleanup.js +1 -1
- package/dist/src/commands/completions.d.ts +1 -1
- package/dist/src/commands/completions.js +1 -1
- package/dist/src/commands/deployment.d.ts +1 -1
- package/dist/src/commands/deployment.js +2 -2
- package/dist/src/commands/deployment.js.map +1 -1
- package/dist/src/commands/doctor.d.ts +1 -1
- package/dist/src/commands/doctor.d.ts.map +1 -1
- package/dist/src/commands/doctor.js +69 -4
- package/dist/src/commands/doctor.js.map +1 -1
- package/dist/src/commands/guidance.d.ts.map +1 -1
- package/dist/src/commands/guidance.js +129 -0
- package/dist/src/commands/guidance.js.map +1 -1
- package/dist/src/commands/index.d.ts.map +1 -1
- package/dist/src/commands/index.js +4 -0
- package/dist/src/commands/index.js.map +1 -1
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +18 -0
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/monovector/import.d.ts +1 -1
- package/dist/src/commands/monovector/import.js +1 -1
- package/dist/src/commands/monovector/index.d.ts +1 -1
- package/dist/src/commands/monovector/index.js +1 -1
- package/dist/src/commands/monovector/setup.d.ts +1 -1
- package/dist/src/commands/monovector/setup.js +2 -2
- package/dist/src/commands/neural.d.ts +1 -1
- package/dist/src/commands/neural.js +2 -2
- package/dist/src/commands/neural.js.map +1 -1
- package/dist/src/commands/performance.d.ts +1 -1
- package/dist/src/commands/performance.js +2 -2
- package/dist/src/commands/performance.js.map +1 -1
- package/dist/src/commands/platforms.d.ts +1 -1
- package/dist/src/commands/platforms.js +1 -1
- package/dist/src/commands/plugins.d.ts +1 -1
- package/dist/src/commands/plugins.d.ts.map +1 -1
- package/dist/src/commands/plugins.js +2 -4
- package/dist/src/commands/plugins.js.map +1 -1
- package/dist/src/commands/providers.d.ts +1 -1
- package/dist/src/commands/providers.js +2 -2
- package/dist/src/commands/providers.js.map +1 -1
- package/dist/src/commands/route.d.ts +1 -1
- package/dist/src/commands/route.d.ts.map +1 -1
- package/dist/src/commands/route.js +5 -11
- package/dist/src/commands/route.js.map +1 -1
- package/dist/src/commands/security.d.ts +1 -1
- package/dist/src/commands/security.d.ts.map +1 -1
- package/dist/src/commands/security.js +140 -91
- package/dist/src/commands/security.js.map +1 -1
- package/dist/src/dlq/dlq-replayer.d.ts +7 -1
- package/dist/src/dlq/dlq-replayer.d.ts.map +1 -1
- package/dist/src/dlq/dlq-replayer.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +10 -26
- package/dist/src/index.js.map +1 -1
- package/dist/src/init/claudemd-generator.js +2 -2
- package/dist/src/init/claudemd-generator.js.map +1 -1
- package/dist/src/init/executor.js +3 -3
- package/dist/src/init/settings-generator.js +2 -2
- package/dist/src/init/settings-generator.js.map +1 -1
- package/dist/src/mcp-client.d.ts +5 -0
- package/dist/src/mcp-client.d.ts.map +1 -1
- package/dist/src/mcp-client.js +7 -0
- package/dist/src/mcp-client.js.map +1 -1
- package/dist/src/mcp-server.d.ts.map +1 -1
- package/dist/src/mcp-server.js +17 -1
- package/dist/src/mcp-server.js.map +1 -1
- package/dist/src/mcp-tools/a2a-tools.js +6 -6
- package/dist/src/mcp-tools/a2a-tools.js.map +1 -1
- package/dist/src/mcp-tools/auto-install.d.ts +2 -2
- package/dist/src/mcp-tools/auto-install.js +1 -1
- package/dist/src/mcp-tools/auto-install.js.map +1 -1
- package/dist/src/mcp-tools/hive-mind-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/hive-mind-tools.js +1 -52
- package/dist/src/mcp-tools/hive-mind-tools.js.map +1 -1
- package/dist/src/mcp-tools/index.d.ts +4 -0
- package/dist/src/mcp-tools/index.d.ts.map +1 -1
- package/dist/src/mcp-tools/index.js +4 -0
- package/dist/src/mcp-tools/index.js.map +1 -1
- package/dist/src/mcp-tools/monograph-compat.d.ts.map +1 -1
- package/dist/src/mcp-tools/monograph-compat.js +1 -2
- package/dist/src/mcp-tools/monograph-compat.js.map +1 -1
- package/dist/src/mcp-tools/monograph-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/monograph-tools.js +107 -5
- package/dist/src/mcp-tools/monograph-tools.js.map +1 -1
- package/dist/src/mcp-tools/security-tools.d.ts +6 -6
- package/dist/src/mcp-tools/security-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/security-tools.js +48 -61
- package/dist/src/mcp-tools/security-tools.js.map +1 -1
- package/dist/src/memory/memory-bridge.d.ts +0 -1
- package/dist/src/memory/memory-bridge.d.ts.map +1 -1
- package/dist/src/memory/memory-bridge.js +232 -57
- package/dist/src/memory/memory-bridge.js.map +1 -1
- package/dist/src/memory/memory-initializer.d.ts.map +1 -1
- package/dist/src/memory/memory-initializer.js +3 -32
- package/dist/src/memory/memory-initializer.js.map +1 -1
- package/dist/src/plugins/store/discovery.d.ts.map +1 -1
- package/dist/src/plugins/store/discovery.js +0 -69
- package/dist/src/plugins/store/discovery.js.map +1 -1
- package/dist/src/routing/embed-worker.d.ts +2 -0
- package/dist/src/routing/embed-worker.d.ts.map +1 -0
- package/dist/src/routing/embed-worker.js +55 -0
- package/dist/src/routing/embed-worker.js.map +1 -0
- package/dist/src/routing/embedder.d.ts +31 -0
- package/dist/src/routing/embedder.d.ts.map +1 -0
- package/dist/src/routing/embedder.js +0 -0
- package/dist/src/routing/embedder.js.map +1 -0
- package/dist/src/routing/llm-caller.d.ts +1 -1
- package/dist/src/routing/llm-caller.d.ts.map +1 -1
- package/dist/src/routing/llm-caller.js +18 -3
- package/dist/src/routing/llm-caller.js.map +1 -1
- package/dist/src/routing/route-layer-factory.d.ts +9 -0
- package/dist/src/routing/route-layer-factory.d.ts.map +1 -0
- package/dist/src/routing/route-layer-factory.js +151 -0
- package/dist/src/routing/route-layer-factory.js.map +1 -0
- package/dist/src/services/worker-daemon.d.ts.map +1 -1
- package/dist/src/services/worker-daemon.js +0 -1
- package/dist/src/services/worker-daemon.js.map +1 -1
- package/dist/src/suggest.d.ts +1 -1
- package/dist/src/suggest.js +1 -1
- package/dist/src/ui/server.mjs +5 -2
- package/dist/src/update/checker.d.ts.map +1 -1
- package/dist/src/update/checker.js +18 -5
- package/dist/src/update/checker.js.map +1 -1
- package/dist/src/update/validator.d.ts.map +1 -1
- package/dist/src/update/validator.js +15 -6
- package/dist/src/update/validator.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -10
- package/scripts/publish-registry.ts +0 -2
- package/scripts/understand-analyze.mjs +1 -1
|
@@ -75,8 +75,17 @@ const MAX_INIT_ATTEMPTS = 3;
|
|
|
75
75
|
let initAttempts = 0;
|
|
76
76
|
/** Backpressure flag for A-MEM Zettelkasten linking — see bridgeStoreEntry. */
|
|
77
77
|
let _amemInFlight = false;
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
// In-process HNSW index — built lazily on first bridgeSearchHNSW call, updated on every write.
|
|
79
|
+
// Keyed to the singleton registry: only one DB path is in use per MCP server process.
|
|
80
|
+
let _hnswIndex = null;
|
|
81
|
+
let _hnswIndexBuilt = false;
|
|
82
|
+
let _hnswBuildFailedAt = 0;
|
|
83
|
+
const HNSW_RETRY_INTERVAL_MS = 60_000;
|
|
84
|
+
// Ghost-entry tracking: upserts (replace an existing row) and soft-deletes leave stale
|
|
85
|
+
// ids in the HNSW graph that SQL filters drop at query time — wasting candidate slots.
|
|
86
|
+
// When the dirty count crosses the rebuild threshold, the next search rebuilds from DB.
|
|
87
|
+
let _hnswDirtyCount = 0;
|
|
88
|
+
const HNSW_REBUILD_THRESHOLD = 50;
|
|
80
89
|
/**
|
|
81
90
|
* Resolve database path with path traversal protection.
|
|
82
91
|
* Only allows paths within or below the project's .swarm directory,
|
|
@@ -153,6 +162,7 @@ async function getRegistry(dbPath) {
|
|
|
153
162
|
hierarchicalMemory: true,
|
|
154
163
|
memoryConsolidation: true,
|
|
155
164
|
memoryGraph: true, // issue #1214: enable MemoryGraph for graph-aware ranking
|
|
165
|
+
causalGraph: true, // required by bridgeRecordCausalEdge for A-MEM edge storage
|
|
156
166
|
},
|
|
157
167
|
});
|
|
158
168
|
}
|
|
@@ -356,14 +366,74 @@ function getDb(registry) {
|
|
|
356
366
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_bridge_ns ON memory_entries(namespace)`);
|
|
357
367
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_bridge_key ON memory_entries(key)`);
|
|
358
368
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_bridge_status ON memory_entries(status)`);
|
|
369
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_bridge_expires ON memory_entries(expires_at)`);
|
|
359
370
|
}
|
|
360
371
|
catch {
|
|
361
372
|
// Table already exists or db is read-only — that's fine
|
|
362
373
|
}
|
|
363
374
|
_dbInitialized.add(db);
|
|
375
|
+
// Warn if any existing rows were written with a different embedding dimension.
|
|
376
|
+
// This degrades semantic search silently; warn once per DB open so operators know.
|
|
377
|
+
try {
|
|
378
|
+
const dimRow = db.prepare(`SELECT embedding_dimensions FROM memory_entries WHERE embedding_dimensions IS NOT NULL AND status = 'active' LIMIT 1`).get();
|
|
379
|
+
if (dimRow && dimRow.embedding_dimensions !== BRIDGE_EMBEDDING_DIMS) {
|
|
380
|
+
console.warn(`[MemoryBridge] Dimension mismatch: DB has ${dimRow.embedding_dimensions}-dim embeddings, bridge expects ${BRIDGE_EMBEDDING_DIMS}. Semantic search will skip mismatched rows.`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
catch { /* Non-fatal */ }
|
|
364
384
|
return { db, agentdb };
|
|
365
385
|
}
|
|
366
386
|
// ===== Bridge functions — match memory-initializer.ts signatures =====
|
|
387
|
+
/**
|
|
388
|
+
* Build or return the in-process HNSWIndex, populated from all active rows in the DB.
|
|
389
|
+
* Called lazily on first semantic search; subsequent calls return the cached index.
|
|
390
|
+
*/
|
|
391
|
+
async function getOrBuildHnswIndex(db) {
|
|
392
|
+
// Trigger a lazy rebuild when ghost-entry accumulation crosses the threshold.
|
|
393
|
+
// Ghosts come from: upsert-replace (old id stays in graph), soft-delete (id
|
|
394
|
+
// filtered by SQL but wastes candidate slots). Rebuild reads fresh rows from DB.
|
|
395
|
+
if (_hnswIndexBuilt && _hnswDirtyCount >= HNSW_REBUILD_THRESHOLD) {
|
|
396
|
+
_hnswIndexBuilt = false;
|
|
397
|
+
_hnswDirtyCount = 0;
|
|
398
|
+
}
|
|
399
|
+
if (_hnswIndexBuilt)
|
|
400
|
+
return _hnswIndex;
|
|
401
|
+
// Enforce retry cooldown after a build failure to avoid rapid retry loops
|
|
402
|
+
if (_hnswBuildFailedAt > 0 && Date.now() - _hnswBuildFailedAt < HNSW_RETRY_INTERVAL_MS)
|
|
403
|
+
return null;
|
|
404
|
+
_hnswIndexBuilt = true; // Lock prevents concurrent re-build
|
|
405
|
+
try {
|
|
406
|
+
const memPkg = await import('@monoes/memory');
|
|
407
|
+
if (!memPkg?.HNSWIndex)
|
|
408
|
+
return null;
|
|
409
|
+
const rows = db.prepare(`SELECT id, embedding FROM memory_entries WHERE status = 'active' AND (expires_at IS NULL OR expires_at > ?) AND embedding IS NOT NULL`).all(Date.now());
|
|
410
|
+
const valid = [];
|
|
411
|
+
for (const row of rows) {
|
|
412
|
+
const emb = safeParseEmbedding(row.embedding);
|
|
413
|
+
if (emb && emb.length === BRIDGE_EMBEDDING_DIMS)
|
|
414
|
+
valid.push({ id: row.id, emb });
|
|
415
|
+
}
|
|
416
|
+
const index = new memPkg.HNSWIndex({
|
|
417
|
+
dimensions: BRIDGE_EMBEDDING_DIMS,
|
|
418
|
+
M: 16,
|
|
419
|
+
efConstruction: 200,
|
|
420
|
+
maxElements: Math.max(valid.length + 1000, 10000),
|
|
421
|
+
metric: 'cosine',
|
|
422
|
+
});
|
|
423
|
+
for (const { id, emb } of valid) {
|
|
424
|
+
await index.addPoint(id, new Float32Array(emb));
|
|
425
|
+
}
|
|
426
|
+
_hnswIndex = index;
|
|
427
|
+
_hnswBuildFailedAt = 0;
|
|
428
|
+
return index;
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
// Reset lock so the next caller can retry after the cooldown
|
|
432
|
+
_hnswIndexBuilt = false;
|
|
433
|
+
_hnswBuildFailedAt = Date.now();
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
367
437
|
/**
|
|
368
438
|
* Store an entry via AgentDB v1.
|
|
369
439
|
* Phase 2-5: Routes through MutationGuard → TieredCache → DB → AttestationLog.
|
|
@@ -414,6 +484,17 @@ export async function bridgeStoreEntry(options) {
|
|
|
414
484
|
// Embedding failed — store without
|
|
415
485
|
}
|
|
416
486
|
}
|
|
487
|
+
// If upserting, check whether an existing row will be replaced. INSERT OR REPLACE
|
|
488
|
+
// always generates a new id, so the old id becomes a ghost in the HNSW graph.
|
|
489
|
+
// Track that here so getOrBuildHnswIndex can trigger a lazy rebuild.
|
|
490
|
+
if (options.upsert) {
|
|
491
|
+
try {
|
|
492
|
+
const existing = ctx.db.prepare(`SELECT id FROM memory_entries WHERE key = ? AND namespace = ? AND status = 'active' LIMIT 1`).get(key, namespace);
|
|
493
|
+
if (existing)
|
|
494
|
+
_hnswDirtyCount++;
|
|
495
|
+
}
|
|
496
|
+
catch { /* Non-fatal */ }
|
|
497
|
+
}
|
|
417
498
|
// better-sqlite3 uses synchronous .run() with positional params
|
|
418
499
|
const insertSql = options.upsert
|
|
419
500
|
? `INSERT OR REPLACE INTO memory_entries (
|
|
@@ -432,6 +513,17 @@ export async function bridgeStoreEntry(options) {
|
|
|
432
513
|
// Phase 2: Write-through to TieredCache deferred — full entry populated on first read
|
|
433
514
|
// Phase 4: AttestationLog write audit
|
|
434
515
|
await logAttestation(registry, 'store', id, { key, namespace, hasEmbedding: !!embeddingJson });
|
|
516
|
+
// Update in-process HNSW index if built and embedding matches bridge dimensions.
|
|
517
|
+
// Skip if ID already indexed: HNSWIndex has no clean updatePoint and re-inserting
|
|
518
|
+
// an existing ID corrupts neighbor connections without removing the old ones.
|
|
519
|
+
if (_hnswIndex && embeddingJson && dimensions === BRIDGE_EMBEDDING_DIMS) {
|
|
520
|
+
try {
|
|
521
|
+
const emb = safeParseEmbedding(embeddingJson);
|
|
522
|
+
if (emb && !_hnswIndex.has(id))
|
|
523
|
+
void _hnswIndex.addPoint(id, new Float32Array(emb));
|
|
524
|
+
}
|
|
525
|
+
catch { /* Non-fatal */ }
|
|
526
|
+
}
|
|
435
527
|
const storeResult = {
|
|
436
528
|
success: true,
|
|
437
529
|
id,
|
|
@@ -526,10 +618,10 @@ export async function bridgeSearchEntries(options) {
|
|
|
526
618
|
const stmt = ctx.db.prepare(`
|
|
527
619
|
SELECT id, key, namespace, content, embedding
|
|
528
620
|
FROM memory_entries
|
|
529
|
-
WHERE status = 'active' ${nsFilter}
|
|
530
|
-
LIMIT
|
|
621
|
+
WHERE status = 'active' AND (expires_at IS NULL OR expires_at > ?) ${nsFilter}
|
|
622
|
+
LIMIT 5000
|
|
531
623
|
`);
|
|
532
|
-
rows = effectiveNamespace !== 'all' ? stmt.all(effectiveNamespace) : stmt.all();
|
|
624
|
+
rows = effectiveNamespace !== 'all' ? stmt.all(Date.now(), effectiveNamespace) : stmt.all(Date.now());
|
|
533
625
|
}
|
|
534
626
|
catch {
|
|
535
627
|
return null;
|
|
@@ -613,8 +705,8 @@ export async function bridgeListEntries(options) {
|
|
|
613
705
|
// Count
|
|
614
706
|
let total = 0;
|
|
615
707
|
try {
|
|
616
|
-
const countStmt = ctx.db.prepare(`SELECT COUNT(*) as cnt FROM memory_entries WHERE status = 'active' ${nsFilter}`);
|
|
617
|
-
const countRow = countStmt.get(...nsParams);
|
|
708
|
+
const countStmt = ctx.db.prepare(`SELECT COUNT(*) as cnt FROM memory_entries WHERE status = 'active' AND (expires_at IS NULL OR expires_at > ?) ${nsFilter}`);
|
|
709
|
+
const countRow = countStmt.get(Date.now(), ...nsParams);
|
|
618
710
|
total = countRow?.cnt ?? 0;
|
|
619
711
|
}
|
|
620
712
|
catch {
|
|
@@ -626,11 +718,11 @@ export async function bridgeListEntries(options) {
|
|
|
626
718
|
const stmt = ctx.db.prepare(`
|
|
627
719
|
SELECT id, key, namespace, content, embedding, access_count, created_at, updated_at
|
|
628
720
|
FROM memory_entries
|
|
629
|
-
WHERE status = 'active' ${nsFilter}
|
|
721
|
+
WHERE status = 'active' AND (expires_at IS NULL OR expires_at > ?) ${nsFilter}
|
|
630
722
|
ORDER BY updated_at DESC
|
|
631
723
|
LIMIT ? OFFSET ?
|
|
632
724
|
`);
|
|
633
|
-
const rows = stmt.all(...nsParams, limit, offset);
|
|
725
|
+
const rows = stmt.all(Date.now(), ...nsParams, limit, offset);
|
|
634
726
|
for (const row of rows) {
|
|
635
727
|
entries.push({
|
|
636
728
|
id: String(row.id),
|
|
@@ -670,7 +762,8 @@ export async function bridgeGetEntry(options) {
|
|
|
670
762
|
const safeNs = String(namespace).replace(/:/g, '_');
|
|
671
763
|
const safeKey = String(key).replace(/:/g, '_');
|
|
672
764
|
const cacheKey = `entry:${safeNs}:${safeKey}`;
|
|
673
|
-
|
|
765
|
+
// Bypass cache when agentId is set so agent_reads tracking and collaborative promotion always fire.
|
|
766
|
+
const cached = options.agentId ? null : await cacheGet(registry, cacheKey);
|
|
674
767
|
if (cached && cached.content) {
|
|
675
768
|
return {
|
|
676
769
|
success: true,
|
|
@@ -684,7 +777,7 @@ export async function bridgeGetEntry(options) {
|
|
|
684
777
|
accessCount: cached.accessCount ?? 0,
|
|
685
778
|
createdAt: cached.createdAt || new Date().toISOString(),
|
|
686
779
|
updatedAt: cached.updatedAt || new Date().toISOString(),
|
|
687
|
-
hasEmbedding:
|
|
780
|
+
hasEmbedding: cached.hasEmbedding ?? false,
|
|
688
781
|
tags: cached.tags || [],
|
|
689
782
|
},
|
|
690
783
|
};
|
|
@@ -692,12 +785,12 @@ export async function bridgeGetEntry(options) {
|
|
|
692
785
|
let row;
|
|
693
786
|
try {
|
|
694
787
|
const stmt = ctx.db.prepare(`
|
|
695
|
-
SELECT id, key, namespace, content, embedding, access_count, created_at, updated_at, tags
|
|
788
|
+
SELECT id, key, namespace, content, embedding, access_count, created_at, updated_at, tags, expires_at
|
|
696
789
|
FROM memory_entries
|
|
697
|
-
WHERE status = 'active' AND key = ? AND namespace = ?
|
|
790
|
+
WHERE status = 'active' AND (expires_at IS NULL OR expires_at > ?) AND key = ? AND namespace = ?
|
|
698
791
|
LIMIT 1
|
|
699
792
|
`);
|
|
700
|
-
row = stmt.get(key, namespace);
|
|
793
|
+
row = stmt.get(Date.now(), key, namespace);
|
|
701
794
|
}
|
|
702
795
|
catch {
|
|
703
796
|
return null;
|
|
@@ -738,6 +831,11 @@ export async function bridgeGetEntry(options) {
|
|
|
738
831
|
if (distinctAgents >= 3 && spread >= SPREAD_MS) {
|
|
739
832
|
ctx.db.prepare("UPDATE memory_entries SET access_level = 'team' WHERE id = ? AND access_level = 'private'").run(row.id);
|
|
740
833
|
}
|
|
834
|
+
// Probabilistic prune: delete agent_reads rows older than the 24h window (~10% of reads).
|
|
835
|
+
// Keeps the table bounded without per-read overhead.
|
|
836
|
+
if (Math.random() < 0.1) {
|
|
837
|
+
ctx.db.prepare('DELETE FROM agent_reads WHERE read_at < ?').run(cutoff);
|
|
838
|
+
}
|
|
741
839
|
}
|
|
742
840
|
catch { /* non-critical */ }
|
|
743
841
|
}
|
|
@@ -766,8 +864,10 @@ export async function bridgeGetEntry(options) {
|
|
|
766
864
|
hasEmbedding: !!(row.embedding && String(row.embedding).length > 10),
|
|
767
865
|
tags,
|
|
768
866
|
};
|
|
769
|
-
// Phase 2: Populate cache for next read
|
|
770
|
-
|
|
867
|
+
// Phase 2: Populate cache for next read (skip TTL'd entries — cache has no expiry awareness)
|
|
868
|
+
if (!row.expires_at) {
|
|
869
|
+
await cacheSet(registry, cacheKey, entry);
|
|
870
|
+
}
|
|
771
871
|
return { success: true, found: true, cacheHit: false, entry };
|
|
772
872
|
}
|
|
773
873
|
catch {
|
|
@@ -792,6 +892,13 @@ export async function bridgeDeleteEntry(options) {
|
|
|
792
892
|
if (!guardResult.allowed) {
|
|
793
893
|
return { success: false, deleted: false, key, namespace, remainingEntries: 0, error: `MutationGuard rejected: ${guardResult.reason}` };
|
|
794
894
|
}
|
|
895
|
+
// Fetch id before soft-delete so we can clean up agent_reads for this entry.
|
|
896
|
+
let deletedEntryId;
|
|
897
|
+
try {
|
|
898
|
+
const existing = ctx.db.prepare(`SELECT id FROM memory_entries WHERE key = ? AND namespace = ? AND status = 'active' LIMIT 1`).get(key, namespace);
|
|
899
|
+
deletedEntryId = existing?.id;
|
|
900
|
+
}
|
|
901
|
+
catch { /* non-fatal */ }
|
|
795
902
|
// Soft delete using parameterized query
|
|
796
903
|
let changes = 0;
|
|
797
904
|
try {
|
|
@@ -811,7 +918,17 @@ export async function bridgeDeleteEntry(options) {
|
|
|
811
918
|
await cacheInvalidate(registry, `entry:${safeNs}:${safeKey}`);
|
|
812
919
|
// Phase 4: AttestationLog delete audit
|
|
813
920
|
if (changes > 0) {
|
|
814
|
-
await logAttestation(registry, 'delete', key, { namespace });
|
|
921
|
+
await logAttestation(registry, 'delete', deletedEntryId ?? key, { namespace, key });
|
|
922
|
+
// Soft-deleted entry's id stays in the HNSW graph but SQL filters it out.
|
|
923
|
+
// Track so the next search can trigger a lazy rebuild to clear ghost slots.
|
|
924
|
+
_hnswDirtyCount++;
|
|
925
|
+
// Clean up orphaned agent_reads rows for the deleted entry.
|
|
926
|
+
if (deletedEntryId) {
|
|
927
|
+
try {
|
|
928
|
+
ctx.db.prepare('DELETE FROM agent_reads WHERE entry_id = ?').run(deletedEntryId);
|
|
929
|
+
}
|
|
930
|
+
catch { /* non-fatal */ }
|
|
931
|
+
}
|
|
815
932
|
}
|
|
816
933
|
let remaining = 0;
|
|
817
934
|
try {
|
|
@@ -903,10 +1020,10 @@ export async function bridgeGetHNSWStatus(dbPath) {
|
|
|
903
1020
|
const ctx = getDb(registry);
|
|
904
1021
|
if (!ctx)
|
|
905
1022
|
return null;
|
|
906
|
-
// Count entries with embeddings
|
|
1023
|
+
// Count entries with embeddings (exclude TTL-expired to match actual index contents)
|
|
907
1024
|
let entryCount = 0;
|
|
908
1025
|
try {
|
|
909
|
-
const row = ctx.db.prepare(`SELECT COUNT(*) as cnt FROM memory_entries WHERE status = 'active' AND embedding IS NOT NULL`).get();
|
|
1026
|
+
const row = ctx.db.prepare(`SELECT COUNT(*) as cnt FROM memory_entries WHERE status = 'active' AND (expires_at IS NULL OR expires_at > ?) AND embedding IS NOT NULL`).get(Date.now());
|
|
910
1027
|
entryCount = row?.cnt ?? 0;
|
|
911
1028
|
}
|
|
912
1029
|
catch {
|
|
@@ -914,7 +1031,7 @@ export async function bridgeGetHNSWStatus(dbPath) {
|
|
|
914
1031
|
}
|
|
915
1032
|
return {
|
|
916
1033
|
available: true,
|
|
917
|
-
initialized:
|
|
1034
|
+
initialized: _hnswIndexBuilt,
|
|
918
1035
|
entryCount,
|
|
919
1036
|
dimensions: BRIDGE_EMBEDDING_DIMS,
|
|
920
1037
|
};
|
|
@@ -938,49 +1055,83 @@ export async function bridgeSearchHNSW(queryEmbedding, options, dbPath) {
|
|
|
938
1055
|
try {
|
|
939
1056
|
const k = options?.k ?? 10;
|
|
940
1057
|
const threshold = options?.threshold ?? 0.3;
|
|
941
|
-
const
|
|
942
|
-
|
|
943
|
-
|
|
1058
|
+
const index = await getOrBuildHnswIndex(ctx.db);
|
|
1059
|
+
if (!index) {
|
|
1060
|
+
// HNSW unavailable — brute-force fallback (capped at 10000 rows)
|
|
1061
|
+
const nsFilter = options?.namespace && options.namespace !== 'all' ? `AND namespace = ?` : '';
|
|
1062
|
+
let rows;
|
|
1063
|
+
try {
|
|
1064
|
+
const stmt = ctx.db.prepare(`
|
|
1065
|
+
SELECT id, key, namespace, content, embedding
|
|
1066
|
+
FROM memory_entries
|
|
1067
|
+
WHERE status = 'active' AND (expires_at IS NULL OR expires_at > ?) AND embedding IS NOT NULL ${nsFilter}
|
|
1068
|
+
LIMIT 10000
|
|
1069
|
+
`);
|
|
1070
|
+
rows = nsFilter ? stmt.all(Date.now(), options.namespace) : stmt.all(Date.now());
|
|
1071
|
+
}
|
|
1072
|
+
catch {
|
|
1073
|
+
return null;
|
|
1074
|
+
}
|
|
1075
|
+
const fallback = [];
|
|
1076
|
+
for (const row of rows) {
|
|
1077
|
+
if (!row.embedding)
|
|
1078
|
+
continue;
|
|
1079
|
+
try {
|
|
1080
|
+
const emb = safeParseEmbedding(row.embedding);
|
|
1081
|
+
if (!emb || emb.length !== queryEmbedding.length)
|
|
1082
|
+
continue;
|
|
1083
|
+
const score = cosineSim(queryEmbedding, emb);
|
|
1084
|
+
if (score >= threshold) {
|
|
1085
|
+
fallback.push({
|
|
1086
|
+
id: String(row.id),
|
|
1087
|
+
key: row.key || String(row.id).substring(0, 15),
|
|
1088
|
+
content: (row.content || '').substring(0, 60) + ((row.content || '').length > 60 ? '...' : ''),
|
|
1089
|
+
score,
|
|
1090
|
+
namespace: row.namespace || 'default',
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
catch { /* skip invalid */ }
|
|
1095
|
+
}
|
|
1096
|
+
fallback.sort((a, b) => b.score - a.score);
|
|
1097
|
+
return fallback.slice(0, k);
|
|
1098
|
+
}
|
|
1099
|
+
// Real HNSW search: distance is cosine distance, similarity = 1 - distance.
|
|
1100
|
+
// When a namespace filter is active, the global top-k*2 candidates may contain
|
|
1101
|
+
// few in-namespace entries. Over-fetch by 50x so the SQL filter has enough
|
|
1102
|
+
// candidates to fill k results from the target namespace.
|
|
1103
|
+
const nsFilter = options?.namespace && options.namespace !== 'all';
|
|
1104
|
+
const candidateCount = nsFilter ? Math.min(10000, k * 50) : k * 2;
|
|
1105
|
+
const hnswResults = await index.search(new Float32Array(queryEmbedding), candidateCount);
|
|
1106
|
+
if (!hnswResults.length)
|
|
1107
|
+
return [];
|
|
1108
|
+
const placeholders = hnswResults.map(() => '?').join(',');
|
|
1109
|
+
const nsWhere = nsFilter ? `AND namespace = ?` : '';
|
|
1110
|
+
const nsParam = nsFilter ? [options.namespace] : [];
|
|
944
1111
|
let rows;
|
|
945
1112
|
try {
|
|
946
|
-
|
|
947
|
-
SELECT id, key, namespace, content, embedding
|
|
948
|
-
FROM memory_entries
|
|
949
|
-
WHERE status = 'active' AND embedding IS NOT NULL ${nsFilter}
|
|
950
|
-
LIMIT 10000
|
|
951
|
-
`);
|
|
952
|
-
rows = nsFilter
|
|
953
|
-
? stmt.all(options.namespace)
|
|
954
|
-
: stmt.all();
|
|
1113
|
+
rows = ctx.db.prepare(`SELECT id, key, namespace, content FROM memory_entries WHERE id IN (${placeholders}) AND status = 'active' AND (expires_at IS NULL OR expires_at > ?) ${nsWhere}`).all(...hnswResults.map((r) => r.id), Date.now(), ...nsParam);
|
|
955
1114
|
}
|
|
956
1115
|
catch {
|
|
957
1116
|
return null;
|
|
958
1117
|
}
|
|
1118
|
+
const rowMap = new Map(rows.map((r) => [r.id, r]));
|
|
959
1119
|
const results = [];
|
|
960
|
-
for (const
|
|
961
|
-
|
|
1120
|
+
for (const { id, distance } of hnswResults) {
|
|
1121
|
+
const row = rowMap.get(id);
|
|
1122
|
+
if (!row)
|
|
962
1123
|
continue;
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
((row.content || '').length > 60 ? '...' : ''),
|
|
974
|
-
score,
|
|
975
|
-
namespace: row.namespace || 'default',
|
|
976
|
-
});
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
catch {
|
|
980
|
-
// Skip invalid embeddings
|
|
981
|
-
}
|
|
1124
|
+
const score = 1 - distance;
|
|
1125
|
+
if (score < threshold)
|
|
1126
|
+
continue;
|
|
1127
|
+
results.push({
|
|
1128
|
+
id: row.id,
|
|
1129
|
+
key: row.key || String(id).substring(0, 15),
|
|
1130
|
+
content: (row.content || '').substring(0, 60) + ((row.content || '').length > 60 ? '...' : ''),
|
|
1131
|
+
score,
|
|
1132
|
+
namespace: row.namespace || 'default',
|
|
1133
|
+
});
|
|
982
1134
|
}
|
|
983
|
-
results.sort((a, b) => b.score - a.score);
|
|
984
1135
|
return results.slice(0, k);
|
|
985
1136
|
}
|
|
986
1137
|
catch {
|
|
@@ -1008,6 +1159,17 @@ export async function bridgeAddToHNSW(id, embedding, entry, dbPath) {
|
|
|
1008
1159
|
}
|
|
1009
1160
|
const now = Date.now();
|
|
1010
1161
|
const embeddingJson = JSON.stringify(embedding);
|
|
1162
|
+
// Ghost-displacement check: INSERT OR REPLACE deletes any existing row with the same
|
|
1163
|
+
// (namespace, key) but a different id, leaving that old id as a HNSW ghost without
|
|
1164
|
+
// being tracked by _hnswDirtyCount.
|
|
1165
|
+
if (_hnswIndex) {
|
|
1166
|
+
try {
|
|
1167
|
+
const existing = ctx.db.prepare(`SELECT id FROM memory_entries WHERE namespace = ? AND key = ? LIMIT 1`).get(entry.namespace, entry.key);
|
|
1168
|
+
if (existing && existing.id !== id)
|
|
1169
|
+
_hnswDirtyCount++;
|
|
1170
|
+
}
|
|
1171
|
+
catch { /* Non-fatal */ }
|
|
1172
|
+
}
|
|
1011
1173
|
ctx.db.prepare(`
|
|
1012
1174
|
INSERT OR REPLACE INTO memory_entries (
|
|
1013
1175
|
id, key, namespace, content, type,
|
|
@@ -1015,6 +1177,17 @@ export async function bridgeAddToHNSW(id, embedding, entry, dbPath) {
|
|
|
1015
1177
|
created_at, updated_at, status
|
|
1016
1178
|
) VALUES (?, ?, ?, ?, 'semantic', ?, ?, ?, ?, ?, 'active')
|
|
1017
1179
|
`).run(id, entry.key, entry.namespace, entry.content, embeddingJson, embedding.length, BRIDGE_EMBEDDING_MODEL, now, now);
|
|
1180
|
+
// Update in-process HNSW index if built and dimensions match.
|
|
1181
|
+
// Skip re-insert for existing IDs (no clean updatePoint API on HNSWIndex).
|
|
1182
|
+
if (_hnswIndex && embedding.length === BRIDGE_EMBEDDING_DIMS) {
|
|
1183
|
+
try {
|
|
1184
|
+
if (!_hnswIndex.has(id))
|
|
1185
|
+
void _hnswIndex.addPoint(id, new Float32Array(embedding));
|
|
1186
|
+
else
|
|
1187
|
+
_hnswDirtyCount++;
|
|
1188
|
+
}
|
|
1189
|
+
catch { /* Non-fatal */ }
|
|
1190
|
+
}
|
|
1018
1191
|
return true;
|
|
1019
1192
|
}
|
|
1020
1193
|
catch {
|
|
@@ -1463,7 +1636,7 @@ export async function bridgeHealthCheck(dbPath) {
|
|
|
1463
1636
|
const s = cache.stats();
|
|
1464
1637
|
cacheStats = { size: s.size ?? 0, hits: s.hits ?? 0, misses: s.misses ?? 0 };
|
|
1465
1638
|
}
|
|
1466
|
-
return { available: true, controllers, attestationCount, cacheStats
|
|
1639
|
+
return { available: true, controllers, attestationCount, cacheStats };
|
|
1467
1640
|
}
|
|
1468
1641
|
catch {
|
|
1469
1642
|
return null;
|
|
@@ -1613,9 +1786,11 @@ export async function bridgeBatchOperation(params) {
|
|
|
1613
1786
|
break;
|
|
1614
1787
|
}
|
|
1615
1788
|
case 'update': {
|
|
1616
|
-
// bulkUpdate(table, updates, conditions)
|
|
1789
|
+
// bulkUpdate(table, updates, conditions) — cap content at 1 MB for parity with insert
|
|
1790
|
+
const MAX_CONTENT_BYTES = 1024 * 1024;
|
|
1617
1791
|
for (const entry of params.entries) {
|
|
1618
|
-
|
|
1792
|
+
const content = String(entry.value || entry.content || '').slice(0, MAX_CONTENT_BYTES);
|
|
1793
|
+
await batch.bulkUpdate('episodes', { content }, { key: entry.key });
|
|
1619
1794
|
}
|
|
1620
1795
|
result = { updated: params.entries.length };
|
|
1621
1796
|
break;
|