claude-flow 3.6.4 → 3.6.6
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/package.json +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/browser-tools.js +86 -32
- package/v3/@claude-flow/cli/dist/src/mcp-tools/embeddings-tools.js +72 -3
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +8 -3
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +16 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +68 -5
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +57 -2
- package/v3/@claude-flow/cli/dist/src/memory/rabitq-index.d.ts +60 -0
- package/v3/@claude-flow/cli/dist/src/memory/rabitq-index.js +242 -0
- package/v3/@claude-flow/cli/package.json +4 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-flow",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.6",
|
|
4
4
|
"description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -8,47 +8,90 @@ import { validateIdentifier, validateText } from './validate-input.js';
|
|
|
8
8
|
// Session registry for multi-session support
|
|
9
9
|
const browserSessions = new Map();
|
|
10
10
|
/**
|
|
11
|
-
* Execute agent-browser CLI command
|
|
11
|
+
* Execute agent-browser CLI command.
|
|
12
|
+
* Tries global agent-browser first, falls back to npx if ENOENT.
|
|
12
13
|
*/
|
|
13
14
|
async function execBrowserCommand(args, session = 'default') {
|
|
14
15
|
const { execFileSync } = await import('child_process');
|
|
16
|
+
const fullArgs = ['--session', session, '--json', ...args];
|
|
17
|
+
let result;
|
|
15
18
|
try {
|
|
16
|
-
|
|
17
|
-
const result = execFileSync('agent-browser', fullArgs, {
|
|
19
|
+
result = execFileSync('agent-browser', fullArgs, {
|
|
18
20
|
encoding: 'utf-8',
|
|
19
21
|
timeout: 30000,
|
|
20
22
|
});
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
const err = error;
|
|
26
|
+
if (err.code === 'ENOENT') {
|
|
27
|
+
try {
|
|
28
|
+
result = execFileSync('npx', ['--yes', 'agent-browser', ...fullArgs], {
|
|
29
|
+
encoding: 'utf-8',
|
|
30
|
+
timeout: 60000,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch (npxError) {
|
|
34
|
+
const npxErr = npxError;
|
|
35
|
+
return {
|
|
36
|
+
content: [{
|
|
37
|
+
type: 'text',
|
|
38
|
+
text: JSON.stringify({
|
|
39
|
+
success: false,
|
|
40
|
+
error: npxErr.code === 'ENOENT'
|
|
41
|
+
? 'Neither agent-browser nor npx found. Install with: npm i -g agent-browser'
|
|
42
|
+
: npxErr instanceof Error ? npxErr.message : String(npxError),
|
|
43
|
+
}),
|
|
44
|
+
}],
|
|
45
|
+
isError: true,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
27
48
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
49
|
+
else {
|
|
50
|
+
return {
|
|
51
|
+
content: [{
|
|
52
|
+
type: 'text',
|
|
53
|
+
text: JSON.stringify({
|
|
54
|
+
success: false,
|
|
55
|
+
error: err instanceof Error ? err.message : String(error),
|
|
56
|
+
}),
|
|
57
|
+
}],
|
|
58
|
+
isError: true,
|
|
59
|
+
};
|
|
32
60
|
}
|
|
33
|
-
return {
|
|
34
|
-
content: [{
|
|
35
|
-
type: 'text',
|
|
36
|
-
text: JSON.stringify(data, null, 2),
|
|
37
|
-
}],
|
|
38
|
-
};
|
|
39
61
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
error: error instanceof Error ? error.message : String(error),
|
|
47
|
-
}),
|
|
48
|
-
}],
|
|
49
|
-
isError: true,
|
|
50
|
-
};
|
|
62
|
+
let data;
|
|
63
|
+
try {
|
|
64
|
+
data = JSON.parse(result);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
data = result.trim();
|
|
51
68
|
}
|
|
69
|
+
const sessionInfo = browserSessions.get(session);
|
|
70
|
+
if (sessionInfo) {
|
|
71
|
+
sessionInfo.lastActivity = new Date().toISOString();
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
content: [{
|
|
75
|
+
type: 'text',
|
|
76
|
+
text: JSON.stringify(data, null, 2),
|
|
77
|
+
}],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Detect if Linux needs --no-sandbox for Chrome.
|
|
82
|
+
*/
|
|
83
|
+
function needsNoSandbox() {
|
|
84
|
+
try {
|
|
85
|
+
const { readFileSync, existsSync } = require('fs');
|
|
86
|
+
if (process.platform !== 'linux')
|
|
87
|
+
return false;
|
|
88
|
+
const clonePath = '/proc/sys/kernel/unprivileged_userns_clone';
|
|
89
|
+
if (existsSync(clonePath)) {
|
|
90
|
+
return readFileSync(clonePath, 'utf-8').trim() === '0';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch { /* not Linux or can't read */ }
|
|
94
|
+
return false;
|
|
52
95
|
}
|
|
53
96
|
/**
|
|
54
97
|
* Browser MCP Tools
|
|
@@ -72,6 +115,11 @@ export const browserTools = [
|
|
|
72
115
|
enum: ['load', 'domcontentloaded', 'networkidle'],
|
|
73
116
|
description: 'Wait condition',
|
|
74
117
|
},
|
|
118
|
+
args: {
|
|
119
|
+
type: 'array',
|
|
120
|
+
items: { type: 'string' },
|
|
121
|
+
description: 'Additional Chrome launch args (e.g., ["--no-sandbox", "--disable-dev-shm-usage"])',
|
|
122
|
+
},
|
|
75
123
|
},
|
|
76
124
|
required: ['url'],
|
|
77
125
|
},
|
|
@@ -85,9 +133,15 @@ export const browserTools = [
|
|
|
85
133
|
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vS.error }) }], isError: true };
|
|
86
134
|
}
|
|
87
135
|
const { url, session, waitUntil } = input;
|
|
136
|
+
const launchArgs = input.args || [];
|
|
137
|
+
if (needsNoSandbox() && !launchArgs.includes('--no-sandbox')) {
|
|
138
|
+
launchArgs.push('--no-sandbox');
|
|
139
|
+
}
|
|
88
140
|
const args = ['open', url];
|
|
89
141
|
if (waitUntil)
|
|
90
142
|
args.push('--wait-until', waitUntil);
|
|
143
|
+
for (const a of launchArgs)
|
|
144
|
+
args.push('--arg', a);
|
|
91
145
|
// Create session if new
|
|
92
146
|
const sessionId = session || 'default';
|
|
93
147
|
if (!browserSessions.has(sessionId)) {
|
|
@@ -369,13 +423,13 @@ export const browserTools = [
|
|
|
369
423
|
},
|
|
370
424
|
{
|
|
371
425
|
name: 'browser_hover',
|
|
372
|
-
description: 'Hover over an element',
|
|
426
|
+
description: 'Hover over an element using ref (@e1) or CSS selector',
|
|
373
427
|
category: 'browser',
|
|
374
428
|
tags: ['interaction'],
|
|
375
429
|
inputSchema: {
|
|
376
430
|
type: 'object',
|
|
377
431
|
properties: {
|
|
378
|
-
target: { type: 'string', description: 'Element ref or CSS selector' },
|
|
432
|
+
target: { type: 'string', description: 'Element ref (@e1) or CSS selector' },
|
|
379
433
|
session: { type: 'string', description: 'Session ID' },
|
|
380
434
|
},
|
|
381
435
|
required: ['target'],
|
|
@@ -412,7 +412,7 @@ export const embeddingsTools = [
|
|
|
412
412
|
query,
|
|
413
413
|
limit: topK,
|
|
414
414
|
threshold,
|
|
415
|
-
namespace: namespace || '
|
|
415
|
+
namespace: namespace || 'all'
|
|
416
416
|
});
|
|
417
417
|
const searchTime = (performance.now() - startTime).toFixed(2);
|
|
418
418
|
return {
|
|
@@ -428,7 +428,7 @@ export const embeddingsTools = [
|
|
|
428
428
|
model: config.model,
|
|
429
429
|
topK,
|
|
430
430
|
threshold,
|
|
431
|
-
namespace: namespace || '
|
|
431
|
+
namespace: namespace || 'all',
|
|
432
432
|
searchTime: `${searchTime}ms`,
|
|
433
433
|
indexType: config.hyperbolic.enabled ? 'HNSW (hyperbolic)' : 'HNSW (euclidean)',
|
|
434
434
|
resultCount: searchResult.results.length
|
|
@@ -446,7 +446,7 @@ export const embeddingsTools = [
|
|
|
446
446
|
model: config.model,
|
|
447
447
|
topK,
|
|
448
448
|
threshold,
|
|
449
|
-
namespace: namespace || '
|
|
449
|
+
namespace: namespace || 'all',
|
|
450
450
|
searchTime: `${searchTime}ms`,
|
|
451
451
|
indexType: config.hyperbolic.enabled ? 'HNSW (hyperbolic)' : 'HNSW (euclidean)',
|
|
452
452
|
},
|
|
@@ -804,5 +804,74 @@ export const embeddingsTools = [
|
|
|
804
804
|
};
|
|
805
805
|
},
|
|
806
806
|
},
|
|
807
|
+
// --- RaBitQ 1-bit quantized vector index ---
|
|
808
|
+
{
|
|
809
|
+
name: 'embeddings_rabitq_build',
|
|
810
|
+
description: 'Build RaBitQ 1-bit quantized index from stored embeddings (32× compression). Pre-filters candidates via Hamming scan before exact rerank.',
|
|
811
|
+
category: 'embeddings',
|
|
812
|
+
inputSchema: {
|
|
813
|
+
type: 'object',
|
|
814
|
+
properties: {
|
|
815
|
+
force: { type: 'boolean', description: 'Force rebuild even if index exists' },
|
|
816
|
+
},
|
|
817
|
+
},
|
|
818
|
+
handler: async (params) => {
|
|
819
|
+
const { buildRabitqIndex } = await import('../memory/rabitq-index.js');
|
|
820
|
+
return buildRabitqIndex({ force: params.force });
|
|
821
|
+
},
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
name: 'embeddings_rabitq_search',
|
|
825
|
+
description: 'Search via RaBitQ quantized index (fast Hamming scan). Returns candidate IDs for reranking.',
|
|
826
|
+
category: 'embeddings',
|
|
827
|
+
inputSchema: {
|
|
828
|
+
type: 'object',
|
|
829
|
+
properties: {
|
|
830
|
+
query: { type: 'string', description: 'Search query text' },
|
|
831
|
+
k: { type: 'number', description: 'Number of results (default: 10)' },
|
|
832
|
+
namespace: { type: 'string', description: 'Filter by namespace' },
|
|
833
|
+
},
|
|
834
|
+
required: ['query'],
|
|
835
|
+
},
|
|
836
|
+
handler: async (params) => {
|
|
837
|
+
const { validateText: vt } = await import('./validate-input.js');
|
|
838
|
+
const v = vt(params.query, 'query');
|
|
839
|
+
if (!v.valid)
|
|
840
|
+
return { success: false, error: v.error };
|
|
841
|
+
const { searchRabitq } = await import('../memory/rabitq-index.js');
|
|
842
|
+
const { generateEmbedding } = await import('../memory/memory-initializer.js');
|
|
843
|
+
const queryEmb = await generateEmbedding(params.query);
|
|
844
|
+
const results = await searchRabitq(queryEmb.embedding, {
|
|
845
|
+
k: params.k || 10,
|
|
846
|
+
namespace: params.namespace,
|
|
847
|
+
});
|
|
848
|
+
if (!results) {
|
|
849
|
+
return { success: false, error: 'RaBitQ index not built. Call embeddings_rabitq_build first.' };
|
|
850
|
+
}
|
|
851
|
+
return {
|
|
852
|
+
success: true,
|
|
853
|
+
results: results.map(r => ({
|
|
854
|
+
id: r.id.substring(0, 12),
|
|
855
|
+
key: r.key,
|
|
856
|
+
namespace: r.namespace,
|
|
857
|
+
distance: Math.round(r.distance * 10000) / 10000,
|
|
858
|
+
})),
|
|
859
|
+
count: results.length,
|
|
860
|
+
};
|
|
861
|
+
},
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
name: 'embeddings_rabitq_status',
|
|
865
|
+
description: 'Get RaBitQ quantized index status — availability, vector count, compression ratio',
|
|
866
|
+
category: 'embeddings',
|
|
867
|
+
inputSchema: {
|
|
868
|
+
type: 'object',
|
|
869
|
+
properties: {},
|
|
870
|
+
},
|
|
871
|
+
handler: async () => {
|
|
872
|
+
const { getRabitqStatus } = await import('../memory/rabitq-index.js');
|
|
873
|
+
return { success: true, ...getRabitqStatus() };
|
|
874
|
+
},
|
|
875
|
+
},
|
|
807
876
|
];
|
|
808
877
|
//# sourceMappingURL=embeddings-tools.js.map
|
|
@@ -2479,20 +2479,25 @@ export const hooksPatternStore = {
|
|
|
2479
2479
|
}
|
|
2480
2480
|
const success = reasoningResult?.success || storeResult.success;
|
|
2481
2481
|
const controller = reasoningResult?.controller || (storeResult.success ? 'bridge-store' : 'none');
|
|
2482
|
+
const hasEmbedding = !!storeResult.embedding || controller === 'reasoningBank' || controller === 'bridge-fallback';
|
|
2482
2483
|
return {
|
|
2483
2484
|
patternId: reasoningResult?.patternId || storeResult.id || patternId,
|
|
2484
2485
|
pattern,
|
|
2485
2486
|
type,
|
|
2486
2487
|
confidence,
|
|
2487
2488
|
indexed: success,
|
|
2488
|
-
hnswIndexed: success &&
|
|
2489
|
+
hnswIndexed: success && hasEmbedding,
|
|
2489
2490
|
embedding: storeResult.embedding,
|
|
2490
2491
|
timestamp,
|
|
2491
2492
|
controller,
|
|
2492
|
-
implementation: controller === 'reasoningBank'
|
|
2493
|
+
implementation: (controller === 'reasoningBank' || controller === 'bridge-fallback')
|
|
2494
|
+
? 'reasoning-bank-controller'
|
|
2495
|
+
: (storeResult.success ? 'real-hnsw-indexed' : 'memory-only'),
|
|
2493
2496
|
note: controller === 'reasoningBank'
|
|
2494
2497
|
? 'Pattern stored via ReasoningBank controller with HNSW indexing'
|
|
2495
|
-
:
|
|
2498
|
+
: controller === 'bridge-fallback'
|
|
2499
|
+
? 'Pattern stored via bridge with embedding and HNSW indexing'
|
|
2500
|
+
: (storeResult.success ? 'Pattern stored with vector embedding for semantic search' : (storeResult.error || 'Store function unavailable')),
|
|
2496
2501
|
};
|
|
2497
2502
|
},
|
|
2498
2503
|
};
|
|
@@ -37,6 +37,7 @@ export declare function bridgeStoreEntry(options: {
|
|
|
37
37
|
dimensions: number;
|
|
38
38
|
model: string;
|
|
39
39
|
};
|
|
40
|
+
rawEmbedding?: number[];
|
|
40
41
|
guarded?: boolean;
|
|
41
42
|
cached?: boolean;
|
|
42
43
|
attested?: boolean;
|
|
@@ -404,4 +405,19 @@ export declare function bridgeContextSynthesize(params: {
|
|
|
404
405
|
export declare function bridgeSemanticRoute(params: {
|
|
405
406
|
input: string;
|
|
406
407
|
}): Promise<any>;
|
|
408
|
+
/**
|
|
409
|
+
* Export all embeddings from the bridge's better-sqlite3 connection.
|
|
410
|
+
* Used by RaBitQ to build its index from the same data that memory_store writes.
|
|
411
|
+
* Returns null if bridge is unavailable (caller falls back to sql.js).
|
|
412
|
+
*/
|
|
413
|
+
export declare function bridgeGetAllEmbeddings(options?: {
|
|
414
|
+
dimensions?: number;
|
|
415
|
+
limit?: number;
|
|
416
|
+
dbPath?: string;
|
|
417
|
+
}): Promise<Array<{
|
|
418
|
+
id: string;
|
|
419
|
+
key: string;
|
|
420
|
+
namespace: string;
|
|
421
|
+
embedding: number[];
|
|
422
|
+
}> | null>;
|
|
407
423
|
//# sourceMappingURL=memory-bridge.d.ts.map
|
|
@@ -381,6 +381,7 @@ export async function bridgeStoreEntry(options) {
|
|
|
381
381
|
success: true,
|
|
382
382
|
id,
|
|
383
383
|
embedding: embeddingJson ? { dimensions, model } : undefined,
|
|
384
|
+
rawEmbedding: embeddingJson ? JSON.parse(embeddingJson) : undefined,
|
|
384
385
|
guarded: true,
|
|
385
386
|
cached: true,
|
|
386
387
|
attested: true,
|
|
@@ -460,10 +461,11 @@ export async function bridgeSearchEntries(options) {
|
|
|
460
461
|
bm25ScoreVal = Math.min(bm25ScoreVal / 10, 1.0);
|
|
461
462
|
}
|
|
462
463
|
// Reciprocal rank fusion: combine semantic and BM25
|
|
463
|
-
// Weight: 0.7 semantic + 0.3 BM25
|
|
464
|
-
|
|
464
|
+
// Weight: 0.7 semantic + 0.3 BM25 when both embeddings present
|
|
465
|
+
// Fall back to BM25-only when either query or row lacks an embedding
|
|
466
|
+
const score = semanticScore > 0
|
|
465
467
|
? (0.7 * semanticScore + 0.3 * bm25ScoreVal)
|
|
466
|
-
: bm25ScoreVal;
|
|
468
|
+
: bm25ScoreVal;
|
|
467
469
|
if (score >= threshold) {
|
|
468
470
|
// Phase 4: ExplainableRecall provenance
|
|
469
471
|
const provenance = queryEmbedding
|
|
@@ -971,15 +973,31 @@ export async function bridgeStorePattern(options) {
|
|
|
971
973
|
return { success: true, patternId, controller: 'reasoningBank' };
|
|
972
974
|
}
|
|
973
975
|
// Fallback: store via bridge SQL
|
|
976
|
+
const patternValue = JSON.stringify({ pattern: options.pattern, type: options.type, confidence: options.confidence, metadata: options.metadata });
|
|
974
977
|
const result = await bridgeStoreEntry({
|
|
975
978
|
key: patternId,
|
|
976
|
-
value:
|
|
979
|
+
value: patternValue,
|
|
977
980
|
namespace: 'pattern',
|
|
978
981
|
generateEmbeddingFlag: true,
|
|
979
982
|
tags: [options.type, 'reasoning-pattern'],
|
|
980
983
|
dbPath: options.dbPath,
|
|
981
984
|
});
|
|
982
|
-
|
|
985
|
+
if (!result)
|
|
986
|
+
return null;
|
|
987
|
+
// Add to HNSW index for fast semantic search (bridgeStoreEntry stores SQL only)
|
|
988
|
+
if (result.rawEmbedding) {
|
|
989
|
+
try {
|
|
990
|
+
const { addToHNSWIndex } = await import('./memory-initializer.js');
|
|
991
|
+
await addToHNSWIndex(result.id, result.rawEmbedding, {
|
|
992
|
+
id: result.id,
|
|
993
|
+
key: patternId,
|
|
994
|
+
namespace: 'pattern',
|
|
995
|
+
content: patternValue,
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
catch { /* HNSW is best-effort */ }
|
|
999
|
+
}
|
|
1000
|
+
return { success: true, patternId: result.id, controller: 'bridge-fallback' };
|
|
983
1001
|
}
|
|
984
1002
|
catch {
|
|
985
1003
|
return null;
|
|
@@ -1536,6 +1554,51 @@ export async function bridgeSemanticRoute(params) {
|
|
|
1536
1554
|
return { route: null, error: e.message };
|
|
1537
1555
|
}
|
|
1538
1556
|
}
|
|
1557
|
+
// ===== RaBitQ data export =====
|
|
1558
|
+
/**
|
|
1559
|
+
* Export all embeddings from the bridge's better-sqlite3 connection.
|
|
1560
|
+
* Used by RaBitQ to build its index from the same data that memory_store writes.
|
|
1561
|
+
* Returns null if bridge is unavailable (caller falls back to sql.js).
|
|
1562
|
+
*/
|
|
1563
|
+
export async function bridgeGetAllEmbeddings(options) {
|
|
1564
|
+
const registry = await getRegistry(options?.dbPath);
|
|
1565
|
+
if (!registry)
|
|
1566
|
+
return null;
|
|
1567
|
+
const ctx = getDb(registry);
|
|
1568
|
+
if (!ctx)
|
|
1569
|
+
return null;
|
|
1570
|
+
try {
|
|
1571
|
+
const dims = options?.dimensions ?? 384;
|
|
1572
|
+
const maxRows = options?.limit ?? 50000;
|
|
1573
|
+
const rows = ctx.db.prepare(`
|
|
1574
|
+
SELECT id, key, namespace, embedding
|
|
1575
|
+
FROM memory_entries
|
|
1576
|
+
WHERE status = 'active' AND embedding IS NOT NULL
|
|
1577
|
+
LIMIT ?
|
|
1578
|
+
`).all(maxRows);
|
|
1579
|
+
const results = [];
|
|
1580
|
+
for (const row of rows) {
|
|
1581
|
+
if (!row.embedding)
|
|
1582
|
+
continue;
|
|
1583
|
+
try {
|
|
1584
|
+
const emb = JSON.parse(row.embedding);
|
|
1585
|
+
if (emb.length !== dims)
|
|
1586
|
+
continue;
|
|
1587
|
+
results.push({
|
|
1588
|
+
id: String(row.id),
|
|
1589
|
+
key: row.key || String(row.id),
|
|
1590
|
+
namespace: row.namespace || 'default',
|
|
1591
|
+
embedding: emb,
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
catch { /* skip invalid */ }
|
|
1595
|
+
}
|
|
1596
|
+
return results;
|
|
1597
|
+
}
|
|
1598
|
+
catch {
|
|
1599
|
+
return null;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1539
1602
|
// ===== Utility =====
|
|
1540
1603
|
function cosineSim(a, b) {
|
|
1541
1604
|
if (!a || !b || a.length === 0 || b.length === 0)
|
|
@@ -1689,8 +1689,19 @@ export async function storeEntry(options) {
|
|
|
1689
1689
|
const bridge = await getBridge();
|
|
1690
1690
|
if (bridge) {
|
|
1691
1691
|
const bridgeResult = await bridge.bridgeStoreEntry(options);
|
|
1692
|
-
if (bridgeResult)
|
|
1692
|
+
if (bridgeResult) {
|
|
1693
|
+
// Keep HNSW index in sync with bridge-stored entries
|
|
1694
|
+
if (bridgeResult.rawEmbedding && bridgeResult.success) {
|
|
1695
|
+
const ns = options.namespace || 'default';
|
|
1696
|
+
await addToHNSWIndex(bridgeResult.id, bridgeResult.rawEmbedding, {
|
|
1697
|
+
id: bridgeResult.id,
|
|
1698
|
+
key: options.key,
|
|
1699
|
+
namespace: ns,
|
|
1700
|
+
content: options.value,
|
|
1701
|
+
}).catch(() => { });
|
|
1702
|
+
}
|
|
1693
1703
|
return bridgeResult;
|
|
1704
|
+
}
|
|
1694
1705
|
}
|
|
1695
1706
|
// Fallback: raw sql.js
|
|
1696
1707
|
const { key, value, namespace = 'default', generateEmbeddingFlag = true, tags = [], ttl, dbPath: customPath, upsert = false } = options;
|
|
@@ -1799,7 +1810,51 @@ export async function searchEntries(options) {
|
|
|
1799
1810
|
// Generate query embedding
|
|
1800
1811
|
const queryEmb = await generateEmbedding(query);
|
|
1801
1812
|
const queryEmbedding = queryEmb.embedding;
|
|
1802
|
-
// Try
|
|
1813
|
+
// Try RaBitQ pre-filter first (32× compressed Hamming scan)
|
|
1814
|
+
try {
|
|
1815
|
+
const { searchRabitq } = await import('./rabitq-index.js');
|
|
1816
|
+
const rabitqCandidates = await searchRabitq(queryEmbedding, { k: limit * 2, namespace: effectiveNamespace });
|
|
1817
|
+
if (rabitqCandidates && rabitqCandidates.length > 0) {
|
|
1818
|
+
// Rerank candidates with exact cosine similarity from SQLite
|
|
1819
|
+
const initSqlJs = (await import('sql.js')).default;
|
|
1820
|
+
const SQL = await initSqlJs();
|
|
1821
|
+
const fileBuffer = fs.readFileSync(dbPath);
|
|
1822
|
+
const db = new SQL.Database(fileBuffer);
|
|
1823
|
+
const reranked = [];
|
|
1824
|
+
for (const candidate of rabitqCandidates) {
|
|
1825
|
+
const stmt = db.prepare('SELECT content, embedding FROM memory_entries WHERE id = ? AND status = ?');
|
|
1826
|
+
stmt.bind([candidate.id, 'active']);
|
|
1827
|
+
if (stmt.step()) {
|
|
1828
|
+
const [content, embeddingJson] = stmt.get();
|
|
1829
|
+
let score = 0;
|
|
1830
|
+
if (embeddingJson) {
|
|
1831
|
+
try {
|
|
1832
|
+
const embedding = JSON.parse(embeddingJson);
|
|
1833
|
+
score = cosineSim(queryEmbedding, embedding);
|
|
1834
|
+
}
|
|
1835
|
+
catch { /* skip */ }
|
|
1836
|
+
}
|
|
1837
|
+
if (score >= threshold) {
|
|
1838
|
+
reranked.push({
|
|
1839
|
+
id: candidate.id.substring(0, 12),
|
|
1840
|
+
key: candidate.key || candidate.id.substring(0, 15),
|
|
1841
|
+
content: (content || '').substring(0, 60) + ((content || '').length > 60 ? '...' : ''),
|
|
1842
|
+
score,
|
|
1843
|
+
namespace: candidate.namespace,
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
stmt.free();
|
|
1848
|
+
}
|
|
1849
|
+
db.close();
|
|
1850
|
+
if (reranked.length > 0) {
|
|
1851
|
+
reranked.sort((a, b) => b.score - a.score);
|
|
1852
|
+
return { success: true, results: reranked.slice(0, limit), searchTime: Date.now() - startTime };
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
catch { /* RaBitQ unavailable, fall through */ }
|
|
1857
|
+
// Try HNSW search (150x faster than brute-force)
|
|
1803
1858
|
const hnswResults = await searchHNSWIndex(queryEmbedding, { k: limit, namespace: effectiveNamespace });
|
|
1804
1859
|
if (hnswResults && hnswResults.length > 0) {
|
|
1805
1860
|
// Filter by threshold
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RaBitQ Index — 1-bit quantized vector pre-filter (32× compression)
|
|
3
|
+
*
|
|
4
|
+
* Wraps @ruvector/rabitq-wasm to provide Hamming-scan pre-filtering
|
|
5
|
+
* over quantized embeddings. Candidates are reranked with exact cosine
|
|
6
|
+
* similarity from the full-precision source (HNSW or SQLite).
|
|
7
|
+
*
|
|
8
|
+
* Lifecycle:
|
|
9
|
+
* 1. build() — bulk-load all embeddings from SQLite into the WASM index
|
|
10
|
+
* 2. search() — fast Hamming scan → candidate ids → caller reranks
|
|
11
|
+
* 3. rebuild() — called when entry count drifts >20% from last build
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Build or rebuild the RaBitQ index from SQLite embeddings.
|
|
15
|
+
* Returns entry count or 0 if RaBitQ is unavailable.
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildRabitqIndex(options?: {
|
|
18
|
+
dbPath?: string;
|
|
19
|
+
dimensions?: number;
|
|
20
|
+
force?: boolean;
|
|
21
|
+
}): Promise<{
|
|
22
|
+
success: boolean;
|
|
23
|
+
vectorCount: number;
|
|
24
|
+
dimensions: number;
|
|
25
|
+
compressionRatio: number;
|
|
26
|
+
buildTimeMs: number;
|
|
27
|
+
wasmVersion?: string;
|
|
28
|
+
error?: string;
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* Search the RaBitQ index for candidate IDs.
|
|
32
|
+
* Returns null if index not built or unavailable.
|
|
33
|
+
* Caller is responsible for reranking with exact similarity.
|
|
34
|
+
*/
|
|
35
|
+
export declare function searchRabitq(queryEmbedding: number[], options?: {
|
|
36
|
+
k?: number;
|
|
37
|
+
namespace?: string;
|
|
38
|
+
}): Promise<Array<{
|
|
39
|
+
id: string;
|
|
40
|
+
key: string;
|
|
41
|
+
namespace: string;
|
|
42
|
+
distance: number;
|
|
43
|
+
position: number;
|
|
44
|
+
}> | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Check if the RaBitQ index needs rebuilding.
|
|
47
|
+
*/
|
|
48
|
+
export declare function shouldRebuildRabitq(currentEntryCount: number): Promise<boolean>;
|
|
49
|
+
/**
|
|
50
|
+
* Get RaBitQ index status.
|
|
51
|
+
*/
|
|
52
|
+
export declare function getRabitqStatus(): {
|
|
53
|
+
available: boolean;
|
|
54
|
+
initialized: boolean;
|
|
55
|
+
vectorCount: number;
|
|
56
|
+
dimensions: number;
|
|
57
|
+
builtAt: number | null;
|
|
58
|
+
compressionRatio: number;
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=rabitq-index.d.ts.map
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RaBitQ Index — 1-bit quantized vector pre-filter (32× compression)
|
|
3
|
+
*
|
|
4
|
+
* Wraps @ruvector/rabitq-wasm to provide Hamming-scan pre-filtering
|
|
5
|
+
* over quantized embeddings. Candidates are reranked with exact cosine
|
|
6
|
+
* similarity from the full-precision source (HNSW or SQLite).
|
|
7
|
+
*
|
|
8
|
+
* Lifecycle:
|
|
9
|
+
* 1. build() — bulk-load all embeddings from SQLite into the WASM index
|
|
10
|
+
* 2. search() — fast Hamming scan → candidate ids → caller reranks
|
|
11
|
+
* 3. rebuild() — called when entry count drifts >20% from last build
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from 'fs';
|
|
14
|
+
import * as path from 'path';
|
|
15
|
+
const RABITQ_SEED = 42n;
|
|
16
|
+
const RABITQ_RERANK_FACTOR = 20;
|
|
17
|
+
const REBUILD_DRIFT_THRESHOLD = 0.2; // rebuild when count drifts >20%
|
|
18
|
+
let rabitqState = null;
|
|
19
|
+
let rabitqInitializing = false;
|
|
20
|
+
async function loadRabitqModule() {
|
|
21
|
+
try {
|
|
22
|
+
const mod = await import('@ruvector/rabitq-wasm');
|
|
23
|
+
// Node.js: use initSync with the WASM bytes
|
|
24
|
+
const { createRequire } = await import('module');
|
|
25
|
+
const require = createRequire(import.meta.url);
|
|
26
|
+
const wasmPath = require.resolve('@ruvector/rabitq-wasm/ruvector_rabitq_wasm_bg.wasm');
|
|
27
|
+
const wasmBytes = fs.readFileSync(wasmPath);
|
|
28
|
+
mod.initSync({ module: wasmBytes });
|
|
29
|
+
return {
|
|
30
|
+
RabitqIndex: mod.RabitqIndex,
|
|
31
|
+
initSync: mod.initSync,
|
|
32
|
+
version: mod.version,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Build or rebuild the RaBitQ index from SQLite embeddings.
|
|
41
|
+
* Returns entry count or 0 if RaBitQ is unavailable.
|
|
42
|
+
*/
|
|
43
|
+
export async function buildRabitqIndex(options) {
|
|
44
|
+
if (rabitqInitializing) {
|
|
45
|
+
return { success: false, vectorCount: 0, dimensions: 0, compressionRatio: 0, buildTimeMs: 0, error: 'Build already in progress' };
|
|
46
|
+
}
|
|
47
|
+
rabitqInitializing = true;
|
|
48
|
+
const startTime = Date.now();
|
|
49
|
+
try {
|
|
50
|
+
const mod = await loadRabitqModule();
|
|
51
|
+
if (!mod) {
|
|
52
|
+
rabitqInitializing = false;
|
|
53
|
+
return { success: false, vectorCount: 0, dimensions: 0, compressionRatio: 0, buildTimeMs: 0, error: '@ruvector/rabitq-wasm not available' };
|
|
54
|
+
}
|
|
55
|
+
const dimensions = options?.dimensions ?? 384;
|
|
56
|
+
const swarmDir = path.resolve(process.cwd(), '.swarm');
|
|
57
|
+
const dbPath = options?.dbPath ? path.resolve(options.dbPath) : path.join(swarmDir, 'memory.db');
|
|
58
|
+
const entries = [];
|
|
59
|
+
const vectors = [];
|
|
60
|
+
// Try bridge first (reads via better-sqlite3, sees WAL data)
|
|
61
|
+
let usedBridge = false;
|
|
62
|
+
try {
|
|
63
|
+
const { bridgeGetAllEmbeddings } = await import('./memory-bridge.js');
|
|
64
|
+
const bridgeRows = await bridgeGetAllEmbeddings({ dimensions, dbPath: options?.dbPath });
|
|
65
|
+
if (bridgeRows && bridgeRows.length > 0) {
|
|
66
|
+
for (const row of bridgeRows) {
|
|
67
|
+
entries.push({ id: row.id, key: row.key, namespace: row.namespace });
|
|
68
|
+
vectors.push(...row.embedding);
|
|
69
|
+
}
|
|
70
|
+
usedBridge = true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch { /* bridge unavailable, fall through */ }
|
|
74
|
+
// Fallback: read .swarm/memory.db via sql.js
|
|
75
|
+
if (!usedBridge) {
|
|
76
|
+
if (!fs.existsSync(dbPath)) {
|
|
77
|
+
rabitqInitializing = false;
|
|
78
|
+
return { success: false, vectorCount: 0, dimensions, compressionRatio: 0, buildTimeMs: 0, error: 'Database not found' };
|
|
79
|
+
}
|
|
80
|
+
const initSqlJs = (await import('sql.js')).default;
|
|
81
|
+
const SQL = await initSqlJs();
|
|
82
|
+
const fileBuffer = fs.readFileSync(dbPath);
|
|
83
|
+
const db = new SQL.Database(fileBuffer);
|
|
84
|
+
const result = db.exec(`
|
|
85
|
+
SELECT id, key, namespace, embedding
|
|
86
|
+
FROM memory_entries
|
|
87
|
+
WHERE status = 'active' AND embedding IS NOT NULL
|
|
88
|
+
LIMIT 50000
|
|
89
|
+
`);
|
|
90
|
+
if (result[0]?.values) {
|
|
91
|
+
for (const row of result[0].values) {
|
|
92
|
+
const [id, key, ns, embeddingJson] = row;
|
|
93
|
+
if (!embeddingJson)
|
|
94
|
+
continue;
|
|
95
|
+
try {
|
|
96
|
+
const embedding = JSON.parse(embeddingJson);
|
|
97
|
+
if (embedding.length !== dimensions)
|
|
98
|
+
continue;
|
|
99
|
+
entries.push({ id: String(id), key: key || String(id), namespace: ns || 'default' });
|
|
100
|
+
vectors.push(...embedding);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// skip invalid
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
db.close();
|
|
108
|
+
}
|
|
109
|
+
if (entries.length < 2) {
|
|
110
|
+
rabitqInitializing = false;
|
|
111
|
+
return { success: false, vectorCount: entries.length, dimensions, compressionRatio: 0, buildTimeMs: Date.now() - startTime, error: 'Need at least 2 vectors to build RaBitQ index' };
|
|
112
|
+
}
|
|
113
|
+
// Build the RaBitQ index
|
|
114
|
+
const flatVectors = new Float32Array(vectors);
|
|
115
|
+
const index = mod.RabitqIndex.build(flatVectors, dimensions, RABITQ_SEED, RABITQ_RERANK_FACTOR);
|
|
116
|
+
// Free old index if exists
|
|
117
|
+
if (rabitqState?.index) {
|
|
118
|
+
try {
|
|
119
|
+
rabitqState.index.free();
|
|
120
|
+
}
|
|
121
|
+
catch { /* already freed */ }
|
|
122
|
+
}
|
|
123
|
+
rabitqState = {
|
|
124
|
+
index,
|
|
125
|
+
entries,
|
|
126
|
+
dimensions,
|
|
127
|
+
builtAt: Date.now(),
|
|
128
|
+
vectorCount: entries.length,
|
|
129
|
+
};
|
|
130
|
+
// Persist metadata for fast reload hint
|
|
131
|
+
try {
|
|
132
|
+
const metaPath = path.join(swarmDir, 'rabitq.meta.json');
|
|
133
|
+
fs.writeFileSync(metaPath, JSON.stringify({
|
|
134
|
+
vectorCount: entries.length,
|
|
135
|
+
dimensions,
|
|
136
|
+
builtAt: rabitqState.builtAt,
|
|
137
|
+
wasmVersion: mod.version(),
|
|
138
|
+
}));
|
|
139
|
+
}
|
|
140
|
+
catch { /* best-effort */ }
|
|
141
|
+
const rawBytes = entries.length * dimensions * 4; // f32 = 4 bytes
|
|
142
|
+
const quantizedBytes = entries.length * Math.ceil(dimensions / 8); // 1 bit per dim
|
|
143
|
+
const compressionRatio = rawBytes / Math.max(quantizedBytes, 1);
|
|
144
|
+
rabitqInitializing = false;
|
|
145
|
+
return {
|
|
146
|
+
success: true,
|
|
147
|
+
vectorCount: entries.length,
|
|
148
|
+
dimensions,
|
|
149
|
+
compressionRatio: Math.round(compressionRatio * 10) / 10,
|
|
150
|
+
buildTimeMs: Date.now() - startTime,
|
|
151
|
+
wasmVersion: mod.version(),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
rabitqInitializing = false;
|
|
156
|
+
return {
|
|
157
|
+
success: false,
|
|
158
|
+
vectorCount: 0,
|
|
159
|
+
dimensions: 0,
|
|
160
|
+
compressionRatio: 0,
|
|
161
|
+
buildTimeMs: Date.now() - startTime,
|
|
162
|
+
error: error instanceof Error ? error.message : String(error),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Search the RaBitQ index for candidate IDs.
|
|
168
|
+
* Returns null if index not built or unavailable.
|
|
169
|
+
* Caller is responsible for reranking with exact similarity.
|
|
170
|
+
*/
|
|
171
|
+
export async function searchRabitq(queryEmbedding, options) {
|
|
172
|
+
if (!rabitqState?.index)
|
|
173
|
+
return null;
|
|
174
|
+
try {
|
|
175
|
+
const query = new Float32Array(queryEmbedding);
|
|
176
|
+
if (query.length !== rabitqState.dimensions)
|
|
177
|
+
return null;
|
|
178
|
+
const k = options?.k ?? 10;
|
|
179
|
+
// Get more candidates than needed for namespace filtering + rerank
|
|
180
|
+
const expandedK = Math.min(k * 3, rabitqState.vectorCount);
|
|
181
|
+
const rawResults = rabitqState.index.search(query, expandedK);
|
|
182
|
+
const results = [];
|
|
183
|
+
for (const hit of rawResults) {
|
|
184
|
+
const pos = hit.id; // row index from build()
|
|
185
|
+
const entry = rabitqState.entries[pos];
|
|
186
|
+
if (!entry)
|
|
187
|
+
continue;
|
|
188
|
+
// Namespace filter
|
|
189
|
+
if (options?.namespace && options.namespace !== 'all' && entry.namespace !== options.namespace) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
results.push({
|
|
193
|
+
id: entry.id,
|
|
194
|
+
key: entry.key,
|
|
195
|
+
namespace: entry.namespace,
|
|
196
|
+
distance: hit.distance,
|
|
197
|
+
position: pos,
|
|
198
|
+
});
|
|
199
|
+
// Free WASM SearchResult to prevent leak
|
|
200
|
+
try {
|
|
201
|
+
hit.free();
|
|
202
|
+
}
|
|
203
|
+
catch { /* already freed */ }
|
|
204
|
+
if (results.length >= k)
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
// Free remaining SearchResults
|
|
208
|
+
for (const hit of rawResults) {
|
|
209
|
+
try {
|
|
210
|
+
hit.free();
|
|
211
|
+
}
|
|
212
|
+
catch { /* already freed or used */ }
|
|
213
|
+
}
|
|
214
|
+
return results;
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Check if the RaBitQ index needs rebuilding.
|
|
222
|
+
*/
|
|
223
|
+
export async function shouldRebuildRabitq(currentEntryCount) {
|
|
224
|
+
if (!rabitqState)
|
|
225
|
+
return currentEntryCount >= 10; // Build if we have enough vectors
|
|
226
|
+
const drift = Math.abs(currentEntryCount - rabitqState.vectorCount) / Math.max(rabitqState.vectorCount, 1);
|
|
227
|
+
return drift > REBUILD_DRIFT_THRESHOLD;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Get RaBitQ index status.
|
|
231
|
+
*/
|
|
232
|
+
export function getRabitqStatus() {
|
|
233
|
+
return {
|
|
234
|
+
available: rabitqState !== null,
|
|
235
|
+
initialized: rabitqState !== null,
|
|
236
|
+
vectorCount: rabitqState?.vectorCount ?? 0,
|
|
237
|
+
dimensions: rabitqState?.dimensions ?? 384,
|
|
238
|
+
builtAt: rabitqState?.builtAt ?? null,
|
|
239
|
+
compressionRatio: rabitqState ? Math.round((rabitqState.dimensions * 4) / Math.ceil(rabitqState.dimensions / 8) * 10) / 10 : 0,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=rabitq-index.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -97,23 +97,24 @@
|
|
|
97
97
|
"@claude-flow/mcp": "^3.0.0-alpha.8",
|
|
98
98
|
"@claude-flow/shared": "^3.0.0-alpha.7",
|
|
99
99
|
"@noble/ed25519": "^2.1.0",
|
|
100
|
+
"@ruvector/rabitq-wasm": "^0.1.0",
|
|
100
101
|
"semver": "^7.6.0"
|
|
101
102
|
},
|
|
102
103
|
"optionalDependencies": {
|
|
103
104
|
"@claude-flow/aidefence": "^3.0.2",
|
|
104
|
-
"@claude-flow/security": "^3.0.0-alpha.1",
|
|
105
105
|
"@claude-flow/codex": "^3.0.0-alpha.8",
|
|
106
106
|
"@claude-flow/embeddings": "^3.0.0-alpha.12",
|
|
107
107
|
"@claude-flow/guidance": "^3.0.0-alpha.1",
|
|
108
108
|
"@claude-flow/memory": "^3.0.0-alpha.12",
|
|
109
109
|
"@claude-flow/plugin-gastown-bridge": "^0.1.3",
|
|
110
|
+
"@claude-flow/security": "^3.0.0-alpha.1",
|
|
110
111
|
"@ruvector/attention": "^0.1.32",
|
|
111
112
|
"@ruvector/attention-darwin-arm64": "0.1.32",
|
|
113
|
+
"@ruvector/diskann": "^0.1.0",
|
|
112
114
|
"@ruvector/learning-wasm": "^0.1.29",
|
|
113
115
|
"@ruvector/router": "^0.1.30",
|
|
114
116
|
"@ruvector/ruvllm-wasm": "^2.0.2",
|
|
115
117
|
"@ruvector/rvagent-wasm": "^0.1.0",
|
|
116
|
-
"@ruvector/diskann": "^0.1.0",
|
|
117
118
|
"@ruvector/sona": "^0.1.5",
|
|
118
119
|
"agentdb": "^3.0.0-alpha.11",
|
|
119
120
|
"agentic-flow": "^3.0.0-alpha.1"
|