@claude-flow/cli 3.38.6 → 3.38.8
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/helpers/helpers.manifest.json +3 -3
- package/.claude/helpers/intelligence.cjs +6 -1
- package/catalog-manifest.json +2 -2
- package/dist/src/mcp-tools/agent-execute-core.d.ts +10 -2
- package/dist/src/mcp-tools/agent-execute-core.js +85 -21
- package/dist/src/mcp-tools/agent-tools.js +30 -4
- package/dist/src/mcp-tools/memory-tools.js +24 -7
- package/dist/src/memory/memory-bridge.d.ts +19 -10
- package/dist/src/memory/memory-bridge.js +18 -9
- package/dist/src/memory/memory-initializer.d.ts +1 -0
- package/dist/src/memory/memory-initializer.js +18 -8
- package/package.json +1 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest": {
|
|
3
|
-
"version": "3.38.
|
|
3
|
+
"version": "3.38.8",
|
|
4
4
|
"files": {
|
|
5
5
|
"auto-memory-hook.mjs": "85fe05c757421c52137c0bc8545a0896bab6b4714538c2a11d1d0835bfcc8c1c",
|
|
6
6
|
"hook-handler.cjs": "dae295fb9ae2626b89899c19a20cc911541af82b52d2eeb9b214d618b96e9a86",
|
|
7
|
-
"intelligence.cjs": "
|
|
7
|
+
"intelligence.cjs": "30e42ed7ec4ca5a94ac54fdb1330d2d47ac5f3fdeeef207753574723a9e77b5c",
|
|
8
8
|
"statusline.cjs": "0457fe53f8cd2c56458ff178392536a5868efd1a573665fa43bc01d2d95ca677"
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
|
-
"signature": "
|
|
11
|
+
"signature": "EoEEuSnNwaVCAmnwd7Evq+V2sD0Zz5cOqqZO4PecBOchzrcimyqFqkz4eOyhYF6iIhEtQ8+XsOvZKFKMbtKIDg==",
|
|
12
12
|
"algorithm": "ed25519"
|
|
13
13
|
}
|
|
@@ -830,11 +830,16 @@ function consolidate() {
|
|
|
830
830
|
pageRanks = computePageRank(nodes, edges, 0.85, 30);
|
|
831
831
|
}
|
|
832
832
|
|
|
833
|
-
// 6. Write updated graph
|
|
833
|
+
// 6. Write updated graph. #2920 follow-up: include contentFingerprint so
|
|
834
|
+
// init()'s cache-hit gate (line ~511) doesn't unconditionally miss on the
|
|
835
|
+
// very next init after a consolidate — without this, nodeCount alone
|
|
836
|
+
// matched but contentFingerprint was undefined here vs a real hash in
|
|
837
|
+
// init()'s own write, forcing a full rebuild every time.
|
|
834
838
|
writeJSON(GRAPH_PATH, {
|
|
835
839
|
version: 1,
|
|
836
840
|
updatedAt: Date.now(),
|
|
837
841
|
nodeCount: Object.keys(nodes).length,
|
|
842
|
+
contentFingerprint: storeFingerprint(store),
|
|
838
843
|
nodes,
|
|
839
844
|
edges,
|
|
840
845
|
pageRanks,
|
package/catalog-manifest.json
CHANGED
|
@@ -27,8 +27,8 @@ export interface AgentRecord {
|
|
|
27
27
|
* falling back to MODEL_MAP[tier].
|
|
28
28
|
*/
|
|
29
29
|
modelId?: string;
|
|
30
|
-
/** ADR-148 phase 2 — execution provider hint. */
|
|
31
|
-
provider?: 'anthropic' | 'openrouter';
|
|
30
|
+
/** ADR-148 phase 2 — execution provider hint. #2962 widened to include 'ollama'. */
|
|
31
|
+
provider?: 'anthropic' | 'openrouter' | 'ollama';
|
|
32
32
|
/** ADR-148 phase 2 — concrete OpenRouter slug when provider='openrouter'. */
|
|
33
33
|
openrouterModel?: string;
|
|
34
34
|
lastResult?: Record<string, unknown>;
|
|
@@ -42,6 +42,14 @@ export interface AnthropicCallInput {
|
|
|
42
42
|
maxTokens?: number;
|
|
43
43
|
temperature?: number;
|
|
44
44
|
timeoutMs?: number;
|
|
45
|
+
/**
|
|
46
|
+
* #2962 — explicit provider carried from the agent record (agent.provider,
|
|
47
|
+
* itself populated from a user's `--provider` flag or persisted
|
|
48
|
+
* `providers configure`). When set, this outranks the env-var-only
|
|
49
|
+
* inference callAnthropicMessages otherwise does — see the precedence
|
|
50
|
+
* comment on that function.
|
|
51
|
+
*/
|
|
52
|
+
provider?: 'anthropic' | 'openrouter' | 'ollama';
|
|
45
53
|
}
|
|
46
54
|
export interface AnthropicCallResult {
|
|
47
55
|
success: boolean;
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
import { getProjectCwd } from './types.js';
|
|
13
|
+
import { configManager } from '../services/config-file-manager.js';
|
|
13
14
|
const STORAGE_DIR = '.claude-flow';
|
|
14
15
|
const AGENT_DIR = 'agents';
|
|
15
16
|
const AGENT_FILE = 'store.json';
|
|
@@ -57,6 +58,29 @@ const MODEL_MAP = {
|
|
|
57
58
|
export function modelRejectsSamplingParams(model) {
|
|
58
59
|
return /^claude-(fable-5|opus-4-8|opus-4-7|sonnet-5)/.test(model);
|
|
59
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* #2962 — read the enabled `agents.providers` entry matching `name` from
|
|
63
|
+
* cwd-scoped `claude-flow.config.json` (written by `providers configure`,
|
|
64
|
+
* `commands/providers.ts`). Best-effort: any failure (no config file, wrong
|
|
65
|
+
* shape, etc.) returns undefined rather than throwing — this must never
|
|
66
|
+
* break execution, and env vars remain a fully-supported override that
|
|
67
|
+
* doesn't require this file to exist.
|
|
68
|
+
*/
|
|
69
|
+
function getPersistedProviderConfig(name) {
|
|
70
|
+
try {
|
|
71
|
+
const providers = configManager.get(getProjectCwd(), 'agents.providers');
|
|
72
|
+
if (!Array.isArray(providers))
|
|
73
|
+
return undefined;
|
|
74
|
+
const entry = providers.find((p) => !!p && typeof p === 'object' && typeof p.name === 'string' &&
|
|
75
|
+
p.name.toLowerCase() === name.toLowerCase());
|
|
76
|
+
if (!entry || entry.enabled === false)
|
|
77
|
+
return undefined;
|
|
78
|
+
return entry;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
60
84
|
/**
|
|
61
85
|
* Generic Anthropic Messages API call. No agent registry coupling — used
|
|
62
86
|
* by agent_execute (with the agent's configured model) and by the WASM
|
|
@@ -69,7 +93,13 @@ export function modelRejectsSamplingParams(model) {
|
|
|
69
93
|
* don't need to know which provider answered.
|
|
70
94
|
*/
|
|
71
95
|
export async function callAnthropicMessages(input) {
|
|
72
|
-
|
|
96
|
+
// #2962 — precedence: explicit per-agent flag (input.provider, forwarded
|
|
97
|
+
// from agent.provider by executeAgentTask, itself populated from a
|
|
98
|
+
// user's `agent spawn --provider` flag) → env vars (RUFLO_PROVIDER + the
|
|
99
|
+
// *_API_KEY family — unchanged back-compat surface) → persisted
|
|
100
|
+
// `agents.providers` config (`providers configure`) → the original
|
|
101
|
+
// key-presence inference, kept below as the last-resort fallback.
|
|
102
|
+
const explicitProvider = (input.provider || process.env.RUFLO_PROVIDER || '').toLowerCase();
|
|
73
103
|
const ollamaKey = process.env.OLLAMA_API_KEY;
|
|
74
104
|
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
75
105
|
// #2042 — OpenRouter is an OpenAI-compat endpoint that fronts dozens of
|
|
@@ -80,22 +110,47 @@ export async function callAnthropicMessages(input) {
|
|
|
80
110
|
// branch above).
|
|
81
111
|
const openrouterKey = process.env.OPENROUTER_API_KEY;
|
|
82
112
|
const useOpenRouter = explicitProvider === 'openrouter' || (!anthropicKey && !!openrouterKey);
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
113
|
+
// #2962 — only consult the persisted config when a candidate is actually
|
|
114
|
+
// relevant (explicit choice, or no env key found anywhere), so a normal
|
|
115
|
+
// ANTHROPIC_API_KEY-only setup never pays a config-file read.
|
|
116
|
+
const persistedOllama = explicitProvider === 'ollama' || (!anthropicKey && !openrouterKey && !ollamaKey)
|
|
117
|
+
? getPersistedProviderConfig('ollama')
|
|
118
|
+
: undefined;
|
|
119
|
+
const persistedOpenRouter = explicitProvider === 'openrouter' && !openrouterKey ? getPersistedProviderConfig('openrouter') : undefined;
|
|
120
|
+
const useOllama = explicitProvider === 'ollama' || (!anthropicKey && !openrouterKey && (!!ollamaKey || !!persistedOllama));
|
|
121
|
+
if (useOpenRouter) {
|
|
122
|
+
const apiKey = openrouterKey || persistedOpenRouter?.apiKey;
|
|
123
|
+
if (apiKey) {
|
|
124
|
+
return callOpenAICompat({
|
|
125
|
+
...input,
|
|
126
|
+
apiKey,
|
|
127
|
+
baseUrl: process.env.OPENROUTER_BASE_URL || persistedOpenRouter?.baseUrl || 'https://openrouter.ai/api',
|
|
128
|
+
providerLabel: 'openrouter',
|
|
129
|
+
// #2357 Finding C: anthropic/claude-3.5-sonnet was retired Oct 2025.
|
|
130
|
+
// Default to the same canonical family the rest of the resolver uses
|
|
131
|
+
// (MODEL_MAP). `OPENROUTER_DEFAULT_MODEL` still wins for callers who
|
|
132
|
+
// want to pin a specific OpenRouter slug.
|
|
133
|
+
defaultModel: process.env.OPENROUTER_DEFAULT_MODEL || persistedOpenRouter?.model || 'anthropic/claude-sonnet-4-6',
|
|
134
|
+
});
|
|
135
|
+
}
|
|
96
136
|
}
|
|
97
|
-
if (useOllama
|
|
98
|
-
|
|
137
|
+
if (useOllama) {
|
|
138
|
+
// #2962 — retire the undocumented OLLAMA_API_KEY=local sentinel
|
|
139
|
+
// requirement. A self-hosted, unauthenticated Ollama daemon shouldn't
|
|
140
|
+
// need a fake credential to become reachable; "self-hosted" is any
|
|
141
|
+
// resolved base URL that isn't the public Ollama Cloud endpoint
|
|
142
|
+
// (persisted `providers configure -e` baseUrl, or OLLAMA_BASE_URL,
|
|
143
|
+
// checked in that order — matching callOllamaCompat's own precedence).
|
|
144
|
+
const resolvedBaseUrl = process.env.OLLAMA_BASE_URL || persistedOllama?.baseUrl;
|
|
145
|
+
const isSelfHosted = !!resolvedBaseUrl && !/^https:\/\/ollama\.com\/?$/i.test(resolvedBaseUrl);
|
|
146
|
+
if (ollamaKey || persistedOllama?.apiKey || isSelfHosted) {
|
|
147
|
+
return callOllamaCompat({
|
|
148
|
+
...input,
|
|
149
|
+
apiKey: ollamaKey || persistedOllama?.apiKey || 'local',
|
|
150
|
+
baseUrl: resolvedBaseUrl,
|
|
151
|
+
model: input.model || persistedOllama?.model,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
99
154
|
}
|
|
100
155
|
if (!anthropicKey) {
|
|
101
156
|
return {
|
|
@@ -185,15 +240,18 @@ export async function callAnthropicMessages(input) {
|
|
|
185
240
|
async function callOllamaCompat(input) {
|
|
186
241
|
const model = resolveOllamaModel(input.model);
|
|
187
242
|
const startedAt = Date.now();
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
|
|
243
|
+
// #2962 — input.baseUrl (resolved by the caller from persisted
|
|
244
|
+
// `providers configure` config, then OLLAMA_BASE_URL) takes precedence
|
|
245
|
+
// over re-reading OLLAMA_BASE_URL here, so a persisted-config-only setup
|
|
246
|
+
// (no env vars) still reaches a self-hosted endpoint instead of Ollama
|
|
247
|
+
// Cloud. Falls back to the original OLLAMA_BASE_URL-or-cloud behavior
|
|
248
|
+
// for any direct caller that doesn't pass baseUrl.
|
|
249
|
+
const base = (input.baseUrl || process.env.OLLAMA_BASE_URL || 'https://ollama.com').replace(/\/+$/, '');
|
|
192
250
|
const url = `${base}/v1/chat/completions`;
|
|
193
251
|
// Self-hosted endpoints typically don't need an Authorization header
|
|
194
252
|
// (the daemon binds to 11434 with no auth by default), but Ollama Cloud
|
|
195
253
|
// does. Send the bearer when the key is non-empty AND looks cloud-shaped.
|
|
196
|
-
const sendAuth = input.apiKey && input.apiKey !== 'local';
|
|
254
|
+
const sendAuth = !!input.apiKey && input.apiKey !== 'local';
|
|
197
255
|
try {
|
|
198
256
|
const controller = new AbortController();
|
|
199
257
|
const timer = setTimeout(() => controller.abort(), input.timeoutMs || 60000);
|
|
@@ -406,6 +464,11 @@ export async function executeAgentTask(input) {
|
|
|
406
464
|
const startedAt = Date.now();
|
|
407
465
|
// #2042 — delegate to callAnthropicMessages so the v3 provider router
|
|
408
466
|
// (Anthropic / Ollama / OpenRouter) governs which backend is hit.
|
|
467
|
+
// #2962 — forward the agent's own explicit provider (set at spawn time
|
|
468
|
+
// from --provider / persisted config) so it outranks env-var inference.
|
|
469
|
+
// Only the first-attempt call; the ADR-149 fallback-retry call below is
|
|
470
|
+
// driven by the cost-optimal neural router picking model-id alternatives,
|
|
471
|
+
// an orthogonal mechanism left as-is to avoid unintended interaction.
|
|
409
472
|
let result = await callAnthropicMessages({
|
|
410
473
|
model: anthropicModel,
|
|
411
474
|
prompt: input.prompt,
|
|
@@ -413,6 +476,7 @@ export async function executeAgentTask(input) {
|
|
|
413
476
|
maxTokens: input.maxTokens,
|
|
414
477
|
temperature: input.temperature,
|
|
415
478
|
timeoutMs: input.timeoutMs,
|
|
479
|
+
provider: agent.provider,
|
|
416
480
|
});
|
|
417
481
|
// ADR-149 iter 7 — fallback chain on retryable failures (429, 5xx,
|
|
418
482
|
// timeout). When the cost-optimal neural backend picked a specific
|
|
@@ -126,8 +126,19 @@ async function embedTaskSafe(task) {
|
|
|
126
126
|
*/
|
|
127
127
|
async function determineAgentModel(agentType, config, task) {
|
|
128
128
|
// 1. Explicit model in config
|
|
129
|
-
if (config.model
|
|
130
|
-
|
|
129
|
+
if (config.model) {
|
|
130
|
+
const explicitModel = config.model;
|
|
131
|
+
if (['haiku', 'sonnet', 'opus', 'opus-4.7', 'inherit'].includes(explicitModel)) {
|
|
132
|
+
return { model: explicitModel, routedBy: 'explicit' };
|
|
133
|
+
}
|
|
134
|
+
// #2962 — a non-alias model string (e.g. an Ollama tag like
|
|
135
|
+
// 'qwen3.6:27b', or any other provider-native model id) is still an
|
|
136
|
+
// explicit user selection. Route it through the modelId fast-path
|
|
137
|
+
// (executeAgentTask already prefers agent.modelId over
|
|
138
|
+
// MODEL_MAP[agent.model] — ADR-149 iter 13) instead of falling through
|
|
139
|
+
// to task-based routing / agent-type defaults, which silently
|
|
140
|
+
// substituted 'sonnet' and discarded the user's request.
|
|
141
|
+
return { model: 'sonnet', routedBy: 'explicit', modelId: explicitModel };
|
|
131
142
|
}
|
|
132
143
|
// 2. Enhanced task-based routing with deterministic Tier-1 codemods
|
|
133
144
|
if (task) {
|
|
@@ -248,6 +259,15 @@ export const agentTools = [
|
|
|
248
259
|
const task = input.task || config.task || undefined;
|
|
249
260
|
// Determine model using ADR-026 3-tier routing logic
|
|
250
261
|
const routingResult = await determineAgentModel(agentType, config, task);
|
|
262
|
+
// #2962 — an explicit, unambiguous provider choice in config wins over
|
|
263
|
+
// the router's own pick. 'anthropic' is deliberately excluded: the CLI's
|
|
264
|
+
// `agent spawn` action always sets config.provider (defaulting to
|
|
265
|
+
// 'anthropic' when --provider isn't passed — commands/agent.ts), so
|
|
266
|
+
// 'anthropic' here is indistinguishable from that silent default.
|
|
267
|
+
// 'ollama'/'openrouter' are never silently defaulted and are unambiguous.
|
|
268
|
+
const explicitConfigProvider = config.provider === 'ollama' || config.provider === 'openrouter'
|
|
269
|
+
? config.provider
|
|
270
|
+
: undefined;
|
|
251
271
|
const agent = {
|
|
252
272
|
agentId,
|
|
253
273
|
agentType,
|
|
@@ -260,7 +280,9 @@ export const agentTools = [
|
|
|
260
280
|
model: routingResult.model,
|
|
261
281
|
modelRoutedBy: routingResult.routedBy,
|
|
262
282
|
...(routingResult.modelId ? { modelId: routingResult.modelId } : {}),
|
|
263
|
-
...(
|
|
283
|
+
...(explicitConfigProvider
|
|
284
|
+
? { provider: explicitConfigProvider }
|
|
285
|
+
: routingResult.provider ? { provider: routingResult.provider } : {}),
|
|
264
286
|
...(routingResult.openrouterModel ? { openrouterModel: routingResult.openrouterModel } : {}),
|
|
265
287
|
};
|
|
266
288
|
store.agents[agentId] = agent;
|
|
@@ -331,7 +353,11 @@ export const agentTools = [
|
|
|
331
353
|
model: agent.model,
|
|
332
354
|
modelRoutedBy: routingResult.routedBy,
|
|
333
355
|
...(routingResult.modelId ? { modelId: routingResult.modelId } : {}),
|
|
334
|
-
|
|
356
|
+
// #2962 — mirror the same precedence used for the stored agent
|
|
357
|
+
// record, so the response a caller sees matches what was actually
|
|
358
|
+
// persisted (previously this always reported the router's own
|
|
359
|
+
// pick, silently dropping an explicit config.provider override).
|
|
360
|
+
...(agent.provider ? { provider: agent.provider } : {}),
|
|
335
361
|
...(routingResult.openrouterModel ? { openrouterModel: routingResult.openrouterModel } : {}),
|
|
336
362
|
status: 'registered',
|
|
337
363
|
createdAt: agent.createdAt,
|
|
@@ -203,6 +203,23 @@ async function getMemoryFunctions() {
|
|
|
203
203
|
checkMemoryInitialization,
|
|
204
204
|
};
|
|
205
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* #2922: every `backend:` field in this file's tool responses used to be the
|
|
208
|
+
* hardcoded literal `'sql.js + HNSW'` regardless of which search path was
|
|
209
|
+
* actually active — most of the time that's the bridge doing a brute-force
|
|
210
|
+
* cosine scan, not HNSW. This reports the real algorithm via the same
|
|
211
|
+
* capability probe `getHNSWStatus()` already exposes.
|
|
212
|
+
*/
|
|
213
|
+
async function describeBackend() {
|
|
214
|
+
try {
|
|
215
|
+
const { getHNSWStatus } = await import('../memory/memory-initializer.js');
|
|
216
|
+
const status = getHNSWStatus();
|
|
217
|
+
return status.algorithm === 'hnsw' ? 'sql.js + HNSW' : 'sqlite (bridge, brute-force cosine)';
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return 'sqlite';
|
|
221
|
+
}
|
|
222
|
+
}
|
|
206
223
|
/**
|
|
207
224
|
* Ensure memory database is initialized and migrate legacy data if needed.
|
|
208
225
|
* #1606: Wrapped in try/catch to prevent process-level crashes that kill
|
|
@@ -318,7 +335,7 @@ export const memoryTools = [
|
|
|
318
335
|
hasEmbedding: !!result.embedding,
|
|
319
336
|
embeddingDimensions: result.embedding?.dimensions || null,
|
|
320
337
|
provenanceType: provenanceType || 'unknown',
|
|
321
|
-
backend:
|
|
338
|
+
backend: await describeBackend(),
|
|
322
339
|
storeTime: `${duration.toFixed(2)}ms`,
|
|
323
340
|
error: result.error,
|
|
324
341
|
};
|
|
@@ -371,7 +388,7 @@ export const memoryTools = [
|
|
|
371
388
|
accessCount: result.entry.accessCount,
|
|
372
389
|
hasEmbedding: result.entry.hasEmbedding,
|
|
373
390
|
found: true,
|
|
374
|
-
backend:
|
|
391
|
+
backend: await describeBackend(),
|
|
375
392
|
};
|
|
376
393
|
}
|
|
377
394
|
return {
|
|
@@ -584,7 +601,7 @@ export const memoryTools = [
|
|
|
584
601
|
namespace,
|
|
585
602
|
deleted: result.deleted,
|
|
586
603
|
hnswIndexInvalidated: result.deleted,
|
|
587
|
-
backend:
|
|
604
|
+
backend: await describeBackend(),
|
|
588
605
|
};
|
|
589
606
|
}
|
|
590
607
|
catch (error) {
|
|
@@ -641,7 +658,7 @@ export const memoryTools = [
|
|
|
641
658
|
total: result.total,
|
|
642
659
|
limit,
|
|
643
660
|
offset,
|
|
644
|
-
backend:
|
|
661
|
+
backend: await describeBackend(),
|
|
645
662
|
};
|
|
646
663
|
}
|
|
647
664
|
catch (error) {
|
|
@@ -685,7 +702,7 @@ export const memoryTools = [
|
|
|
685
702
|
? `${((withEmbeddings / allEntries.total) * 100).toFixed(1)}%`
|
|
686
703
|
: '0%',
|
|
687
704
|
namespaces,
|
|
688
|
-
backend:
|
|
705
|
+
backend: await describeBackend(),
|
|
689
706
|
version: status.version || '3.0.0',
|
|
690
707
|
features: status.features || {
|
|
691
708
|
vectorEmbeddings: true,
|
|
@@ -736,7 +753,7 @@ export const memoryTools = [
|
|
|
736
753
|
success: true,
|
|
737
754
|
message: 'Migration completed',
|
|
738
755
|
migrated: Object.keys(legacyStore.entries).length,
|
|
739
|
-
backend:
|
|
756
|
+
backend: await describeBackend(),
|
|
740
757
|
};
|
|
741
758
|
},
|
|
742
759
|
},
|
|
@@ -1149,7 +1166,7 @@ export const memoryTools = [
|
|
|
1149
1166
|
bytes += e.size || 0;
|
|
1150
1167
|
}
|
|
1151
1168
|
return {
|
|
1152
|
-
backend:
|
|
1169
|
+
backend: await describeBackend(),
|
|
1153
1170
|
entries: all.total ?? all.entries.length,
|
|
1154
1171
|
size: bytes,
|
|
1155
1172
|
namespaces: Object.entries(nsCounts).map(([name, entries]) => ({ name, entries })),
|
|
@@ -209,21 +209,29 @@ export declare function bridgeLoadEmbeddingModel(dbPath?: string): Promise<{
|
|
|
209
209
|
loadTime?: number;
|
|
210
210
|
} | null>;
|
|
211
211
|
/**
|
|
212
|
-
* Get
|
|
212
|
+
* Get vector search status from AgentDB v3's SQLite-backed store.
|
|
213
213
|
* Returns null if unavailable.
|
|
214
|
-
|
|
215
|
-
|
|
214
|
+
*
|
|
215
|
+
* #2922: previously named `bridgeGetHNSWStatus` and unconditionally returned
|
|
216
|
+
* `available: true` whenever a DB connection succeeded — but the search this
|
|
217
|
+
* status describes (`bridgeSearchBruteForceCosine`, below) is a full-table
|
|
218
|
+
* SELECT + brute-force cosine scan, not an HNSW index lookup. Renamed and
|
|
219
|
+
* given an explicit `algorithm` field so callers can't mistake "vector
|
|
220
|
+
* search works" for "vector search is HNSW-accelerated".
|
|
221
|
+
*/
|
|
222
|
+
export declare function bridgeGetVectorSearchStatus(dbPath?: string): Promise<{
|
|
216
223
|
available: boolean;
|
|
217
224
|
initialized: boolean;
|
|
218
225
|
entryCount: number;
|
|
219
226
|
dimensions: number;
|
|
227
|
+
algorithm: 'brute-force-cosine';
|
|
220
228
|
} | null>;
|
|
221
229
|
/**
|
|
222
|
-
* Search
|
|
223
|
-
*
|
|
224
|
-
*
|
|
230
|
+
* Search AgentDB v3's embedder + SQLite entries via a full-table scan and
|
|
231
|
+
* brute-force cosine similarity. NOT HNSW-accelerated — see #2922. Returns
|
|
232
|
+
* null if unavailable.
|
|
225
233
|
*/
|
|
226
|
-
export declare function
|
|
234
|
+
export declare function bridgeSearchBruteForceCosine(queryEmbedding: number[], options?: {
|
|
227
235
|
k?: number;
|
|
228
236
|
namespace?: string;
|
|
229
237
|
threshold?: number;
|
|
@@ -235,10 +243,11 @@ export declare function bridgeSearchHNSW(queryEmbedding: number[], options?: {
|
|
|
235
243
|
namespace: string;
|
|
236
244
|
}> | null>;
|
|
237
245
|
/**
|
|
238
|
-
* Add entry to the bridge's database with embedding.
|
|
239
|
-
*
|
|
246
|
+
* Add entry to the bridge's database with embedding. No HNSW index is built
|
|
247
|
+
* or updated here — see #2922; the embedding is only stored for a later
|
|
248
|
+
* brute-force scan. Returns null if unavailable.
|
|
240
249
|
*/
|
|
241
|
-
export declare function
|
|
250
|
+
export declare function bridgeAddEmbedding(id: string, embedding: number[], entry: {
|
|
242
251
|
id: string;
|
|
243
252
|
key: string;
|
|
244
253
|
namespace: string;
|
|
@@ -1478,10 +1478,17 @@ export async function bridgeLoadEmbeddingModel(dbPath) {
|
|
|
1478
1478
|
}
|
|
1479
1479
|
// ===== Phase 3: HNSW bridge =====
|
|
1480
1480
|
/**
|
|
1481
|
-
* Get
|
|
1481
|
+
* Get vector search status from AgentDB v3's SQLite-backed store.
|
|
1482
1482
|
* Returns null if unavailable.
|
|
1483
|
+
*
|
|
1484
|
+
* #2922: previously named `bridgeGetHNSWStatus` and unconditionally returned
|
|
1485
|
+
* `available: true` whenever a DB connection succeeded — but the search this
|
|
1486
|
+
* status describes (`bridgeSearchBruteForceCosine`, below) is a full-table
|
|
1487
|
+
* SELECT + brute-force cosine scan, not an HNSW index lookup. Renamed and
|
|
1488
|
+
* given an explicit `algorithm` field so callers can't mistake "vector
|
|
1489
|
+
* search works" for "vector search is HNSW-accelerated".
|
|
1483
1490
|
*/
|
|
1484
|
-
export async function
|
|
1491
|
+
export async function bridgeGetVectorSearchStatus(dbPath) {
|
|
1485
1492
|
const registry = await getRegistry(dbPath);
|
|
1486
1493
|
if (!registry)
|
|
1487
1494
|
return null;
|
|
@@ -1503,6 +1510,7 @@ export async function bridgeGetHNSWStatus(dbPath) {
|
|
|
1503
1510
|
initialized: true,
|
|
1504
1511
|
entryCount,
|
|
1505
1512
|
dimensions: 384,
|
|
1513
|
+
algorithm: 'brute-force-cosine',
|
|
1506
1514
|
};
|
|
1507
1515
|
}
|
|
1508
1516
|
catch {
|
|
@@ -1510,11 +1518,11 @@ export async function bridgeGetHNSWStatus(dbPath) {
|
|
|
1510
1518
|
}
|
|
1511
1519
|
}
|
|
1512
1520
|
/**
|
|
1513
|
-
* Search
|
|
1514
|
-
*
|
|
1515
|
-
*
|
|
1521
|
+
* Search AgentDB v3's embedder + SQLite entries via a full-table scan and
|
|
1522
|
+
* brute-force cosine similarity. NOT HNSW-accelerated — see #2922. Returns
|
|
1523
|
+
* null if unavailable.
|
|
1516
1524
|
*/
|
|
1517
|
-
export async function
|
|
1525
|
+
export async function bridgeSearchBruteForceCosine(queryEmbedding, options, dbPath) {
|
|
1518
1526
|
const registry = await getRegistry(dbPath);
|
|
1519
1527
|
if (!registry)
|
|
1520
1528
|
return null;
|
|
@@ -1576,10 +1584,11 @@ export async function bridgeSearchHNSW(queryEmbedding, options, dbPath) {
|
|
|
1576
1584
|
}
|
|
1577
1585
|
}
|
|
1578
1586
|
/**
|
|
1579
|
-
* Add entry to the bridge's database with embedding.
|
|
1580
|
-
*
|
|
1587
|
+
* Add entry to the bridge's database with embedding. No HNSW index is built
|
|
1588
|
+
* or updated here — see #2922; the embedding is only stored for a later
|
|
1589
|
+
* brute-force scan. Returns null if unavailable.
|
|
1581
1590
|
*/
|
|
1582
|
-
export async function
|
|
1591
|
+
export async function bridgeAddEmbedding(id, embedding, entry, dbPath) {
|
|
1583
1592
|
const registry = await getRegistry(dbPath);
|
|
1584
1593
|
if (!registry)
|
|
1585
1594
|
return null;
|
|
@@ -750,7 +750,7 @@ export async function addToHNSWIndex(id, embedding, entry) {
|
|
|
750
750
|
// ADR-053: Try AgentDB v3 bridge first
|
|
751
751
|
const bridge = await getBridge();
|
|
752
752
|
if (bridge) {
|
|
753
|
-
const bridgeResult = await bridge.
|
|
753
|
+
const bridgeResult = await bridge.bridgeAddEmbedding(id, embedding, entry);
|
|
754
754
|
if (bridgeResult === true)
|
|
755
755
|
return true;
|
|
756
756
|
}
|
|
@@ -780,7 +780,7 @@ export async function searchHNSWIndex(queryEmbedding, options) {
|
|
|
780
780
|
// ADR-053: Try AgentDB v3 bridge first
|
|
781
781
|
const bridge = await getBridge();
|
|
782
782
|
if (bridge) {
|
|
783
|
-
const bridgeResult = await bridge.
|
|
783
|
+
const bridgeResult = await bridge.bridgeSearchBruteForceCosine(queryEmbedding, options);
|
|
784
784
|
if (bridgeResult)
|
|
785
785
|
return bridgeResult;
|
|
786
786
|
}
|
|
@@ -826,14 +826,23 @@ export async function searchHNSWIndex(queryEmbedding, options) {
|
|
|
826
826
|
* Get HNSW index status
|
|
827
827
|
*/
|
|
828
828
|
export function getHNSWStatus() {
|
|
829
|
-
//
|
|
829
|
+
// #2922: this branch previously reported `available: true` whenever the
|
|
830
|
+
// bridge was loaded, on the theory that "AgentDB v3 is HNSW-equivalent" —
|
|
831
|
+
// but the bridge's actual search path (bridgeSearchBruteForceCosine, see
|
|
832
|
+
// memory-bridge.ts) does a full-table SELECT + brute-force cosine loop and
|
|
833
|
+
// never touches @ruvector/core or an HNSW index, regardless of whether
|
|
834
|
+
// ruvector-core itself is installed. `available` here means "an HNSW
|
|
835
|
+
// index is the one actually searched", matching what this function's own
|
|
836
|
+
// name promises — that's false while the bridge is the active path, so
|
|
837
|
+
// this no longer claims otherwise. Vector search itself still works via
|
|
838
|
+
// the bridge; `algorithm` tells callers it's brute-force, not HNSW.
|
|
830
839
|
if (_bridge && _bridge !== null) {
|
|
831
|
-
// Bridge is loaded — HNSW-equivalent is available via AgentDB v3
|
|
832
840
|
return {
|
|
833
|
-
available:
|
|
834
|
-
initialized:
|
|
841
|
+
available: false,
|
|
842
|
+
initialized: false,
|
|
835
843
|
entryCount: hnswIndex?.entries.size ?? 0,
|
|
836
|
-
dimensions: hnswIndex?.dimensions ?? 384
|
|
844
|
+
dimensions: hnswIndex?.dimensions ?? 384,
|
|
845
|
+
algorithm: 'brute-force-cosine',
|
|
837
846
|
};
|
|
838
847
|
}
|
|
839
848
|
// #2356: `available` now reflects real capability (index already loaded OR
|
|
@@ -845,7 +854,8 @@ export function getHNSWStatus() {
|
|
|
845
854
|
available: hnswIndex !== null || isRuvectorCoreResolvable(),
|
|
846
855
|
initialized: hnswIndex?.initialized ?? false,
|
|
847
856
|
entryCount: hnswIndex?.entries.size ?? 0,
|
|
848
|
-
dimensions: hnswIndex?.dimensions ?? 384
|
|
857
|
+
dimensions: hnswIndex?.dimensions ?? 384,
|
|
858
|
+
algorithm: 'hnsw',
|
|
849
859
|
};
|
|
850
860
|
}
|
|
851
861
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.38.
|
|
3
|
+
"version": "3.38.8",
|
|
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",
|