claude-flow 3.5.24 → 3.5.26

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.
@@ -1,82 +1,212 @@
1
1
  /**
2
2
  * Swarm MCP Tools for CLI
3
3
  *
4
- * Tool definitions for swarm coordination.
4
+ * Tool definitions for swarm coordination with file-based state persistence.
5
+ * Replaces previous stub implementations with real state tracking.
5
6
  */
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+ // Swarm state persistence
10
+ const SWARM_DIR = '.claude-flow/swarm';
11
+ const SWARM_STATE_FILE = 'swarm-state.json';
12
+ function getSwarmDir() {
13
+ return join(process.cwd(), SWARM_DIR);
14
+ }
15
+ function getSwarmStatePath() {
16
+ return join(getSwarmDir(), SWARM_STATE_FILE);
17
+ }
18
+ function ensureSwarmDir() {
19
+ const dir = getSwarmDir();
20
+ if (!existsSync(dir)) {
21
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
22
+ }
23
+ }
24
+ function loadSwarmStore() {
25
+ try {
26
+ const path = getSwarmStatePath();
27
+ if (existsSync(path)) {
28
+ return JSON.parse(readFileSync(path, 'utf-8'));
29
+ }
30
+ }
31
+ catch { /* return default */ }
32
+ return { swarms: {}, version: '3.0.0' };
33
+ }
34
+ function saveSwarmStore(store) {
35
+ ensureSwarmDir();
36
+ writeFileSync(getSwarmStatePath(), JSON.stringify(store, null, 2), 'utf-8');
37
+ }
38
+ // Input validation
39
+ const VALID_TOPOLOGIES = new Set([
40
+ 'hierarchical', 'mesh', 'hierarchical-mesh', 'ring', 'star', 'hybrid', 'adaptive',
41
+ ]);
6
42
  export const swarmTools = [
7
43
  {
8
44
  name: 'swarm_init',
9
- description: 'Initialize a swarm',
45
+ description: 'Initialize a swarm with persistent state tracking',
10
46
  category: 'swarm',
11
47
  inputSchema: {
12
48
  type: 'object',
13
49
  properties: {
14
- topology: { type: 'string', description: 'Swarm topology type' },
15
- maxAgents: { type: 'number', description: 'Maximum number of agents' },
16
- config: { type: 'object', description: 'Swarm configuration' },
50
+ topology: { type: 'string', description: 'Swarm topology type (hierarchical, mesh, hierarchical-mesh, ring, star, hybrid, adaptive)' },
51
+ maxAgents: { type: 'number', description: 'Maximum number of agents (1-50)' },
52
+ strategy: { type: 'string', description: 'Agent strategy (specialized, balanced, adaptive)' },
53
+ config: { type: 'object', description: 'Additional swarm configuration' },
17
54
  },
18
55
  },
19
56
  handler: async (input) => {
20
57
  const topology = input.topology || 'hierarchical-mesh';
21
- const maxAgents = input.maxAgents || 15;
58
+ const maxAgents = Math.min(Math.max(input.maxAgents || 15, 1), 50);
59
+ const strategy = input.strategy || 'specialized';
22
60
  const config = (input.config || {});
23
- return {
24
- success: true,
25
- swarmId: `swarm-${Date.now()}`,
61
+ if (!VALID_TOPOLOGIES.has(topology)) {
62
+ return {
63
+ success: false,
64
+ error: `Invalid topology: ${topology}. Valid: ${[...VALID_TOPOLOGIES].join(', ')}`,
65
+ };
66
+ }
67
+ const swarmId = `swarm-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
68
+ const now = new Date().toISOString();
69
+ const swarmState = {
70
+ swarmId,
26
71
  topology,
27
- initializedAt: new Date().toISOString(),
72
+ maxAgents,
73
+ status: 'running',
74
+ agents: [],
75
+ tasks: [],
28
76
  config: {
29
77
  topology,
30
78
  maxAgents,
31
- currentAgents: 0,
79
+ strategy,
32
80
  communicationProtocol: config.communicationProtocol || 'message-bus',
33
81
  autoScaling: config.autoScaling ?? true,
34
82
  consensusMechanism: config.consensusMechanism || 'majority',
35
83
  },
84
+ createdAt: now,
85
+ updatedAt: now,
86
+ };
87
+ const store = loadSwarmStore();
88
+ store.swarms[swarmId] = swarmState;
89
+ saveSwarmStore(store);
90
+ return {
91
+ success: true,
92
+ swarmId,
93
+ topology,
94
+ strategy,
95
+ maxAgents,
96
+ initializedAt: now,
97
+ config: swarmState.config,
98
+ persisted: true,
36
99
  };
37
100
  },
38
101
  },
39
102
  {
40
103
  name: 'swarm_status',
41
- description: 'Get swarm status',
104
+ description: 'Get swarm status from persistent state',
42
105
  category: 'swarm',
43
106
  inputSchema: {
44
107
  type: 'object',
45
108
  properties: {
46
- swarmId: { type: 'string', description: 'Swarm ID' },
109
+ swarmId: { type: 'string', description: 'Swarm ID (omit for most recent)' },
47
110
  },
48
111
  },
49
112
  handler: async (input) => {
113
+ const store = loadSwarmStore();
114
+ const swarmId = input.swarmId;
115
+ if (swarmId && store.swarms[swarmId]) {
116
+ const swarm = store.swarms[swarmId];
117
+ return {
118
+ swarmId: swarm.swarmId,
119
+ status: swarm.status,
120
+ topology: swarm.topology,
121
+ maxAgents: swarm.maxAgents,
122
+ agentCount: swarm.agents.length,
123
+ taskCount: swarm.tasks.length,
124
+ config: swarm.config,
125
+ createdAt: swarm.createdAt,
126
+ updatedAt: swarm.updatedAt,
127
+ };
128
+ }
129
+ // Return most recent swarm if no ID specified
130
+ const swarmIds = Object.keys(store.swarms);
131
+ if (swarmIds.length === 0) {
132
+ return {
133
+ status: 'no_swarm',
134
+ message: 'No active swarms. Use swarm_init to create one.',
135
+ totalSwarms: 0,
136
+ };
137
+ }
138
+ const latest = swarmIds
139
+ .map(id => store.swarms[id])
140
+ .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())[0];
50
141
  return {
51
- swarmId: input.swarmId,
52
- status: 'running',
53
- agentCount: 0,
54
- taskCount: 0,
142
+ swarmId: latest.swarmId,
143
+ status: latest.status,
144
+ topology: latest.topology,
145
+ maxAgents: latest.maxAgents,
146
+ agentCount: latest.agents.length,
147
+ taskCount: latest.tasks.length,
148
+ config: latest.config,
149
+ createdAt: latest.createdAt,
150
+ updatedAt: latest.updatedAt,
151
+ totalSwarms: swarmIds.length,
55
152
  };
56
153
  },
57
154
  },
58
155
  {
59
156
  name: 'swarm_shutdown',
60
- description: 'Shutdown a swarm',
157
+ description: 'Shutdown a swarm and update persistent state',
61
158
  category: 'swarm',
62
159
  inputSchema: {
63
160
  type: 'object',
64
161
  properties: {
65
- swarmId: { type: 'string', description: 'Swarm ID' },
66
- graceful: { type: 'boolean', description: 'Graceful shutdown' },
162
+ swarmId: { type: 'string', description: 'Swarm ID to shutdown' },
163
+ graceful: { type: 'boolean', description: 'Graceful shutdown (default: true)' },
67
164
  },
68
165
  },
69
166
  handler: async (input) => {
167
+ const store = loadSwarmStore();
168
+ const swarmId = input.swarmId;
169
+ // Find the swarm
170
+ let target;
171
+ if (swarmId && store.swarms[swarmId]) {
172
+ target = store.swarms[swarmId];
173
+ }
174
+ else {
175
+ // Shutdown most recent running swarm
176
+ const running = Object.values(store.swarms)
177
+ .filter(s => s.status === 'running')
178
+ .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
179
+ target = running[0];
180
+ }
181
+ if (!target) {
182
+ return {
183
+ success: false,
184
+ error: swarmId ? `Swarm ${swarmId} not found` : 'No running swarms to shutdown',
185
+ };
186
+ }
187
+ if (target.status === 'terminated') {
188
+ return {
189
+ success: false,
190
+ swarmId: target.swarmId,
191
+ error: 'Swarm already terminated',
192
+ };
193
+ }
194
+ target.status = 'terminated';
195
+ target.updatedAt = new Date().toISOString();
196
+ saveSwarmStore(store);
70
197
  return {
71
198
  success: true,
72
- swarmId: input.swarmId,
199
+ swarmId: target.swarmId,
73
200
  terminated: true,
201
+ graceful: input.graceful ?? true,
202
+ agentsTerminated: target.agents.length,
203
+ terminatedAt: target.updatedAt,
74
204
  };
75
205
  },
76
206
  },
77
207
  {
78
208
  name: 'swarm_health',
79
- description: 'Check swarm health status',
209
+ description: 'Check swarm health status with real state inspection',
80
210
  category: 'swarm',
81
211
  inputSchema: {
82
212
  type: 'object',
@@ -85,15 +215,71 @@ export const swarmTools = [
85
215
  },
86
216
  },
87
217
  handler: async (input) => {
218
+ const store = loadSwarmStore();
219
+ const swarmId = input.swarmId;
220
+ // Find the swarm
221
+ let target;
222
+ if (swarmId) {
223
+ target = store.swarms[swarmId];
224
+ if (!target) {
225
+ return {
226
+ status: 'not_found',
227
+ healthy: false,
228
+ checks: [
229
+ { name: 'swarm_exists', status: 'fail', message: `Swarm ${swarmId} not found` },
230
+ ],
231
+ checkedAt: new Date().toISOString(),
232
+ };
233
+ }
234
+ }
235
+ else {
236
+ const running = Object.values(store.swarms)
237
+ .filter(s => s.status === 'running')
238
+ .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
239
+ target = running[0];
240
+ }
241
+ if (!target) {
242
+ return {
243
+ status: 'no_swarm',
244
+ healthy: false,
245
+ checks: [
246
+ { name: 'swarm_exists', status: 'fail', message: 'No active swarm found' },
247
+ ],
248
+ checkedAt: new Date().toISOString(),
249
+ };
250
+ }
251
+ const isRunning = target.status === 'running';
252
+ const stateFileExists = existsSync(getSwarmStatePath());
253
+ const checks = [
254
+ {
255
+ name: 'coordinator',
256
+ status: isRunning ? 'ok' : 'warn',
257
+ message: isRunning ? 'Coordinator active' : `Swarm status: ${target.status}`,
258
+ },
259
+ {
260
+ name: 'agents',
261
+ status: target.agents.length > 0 ? 'ok' : 'info',
262
+ message: `${target.agents.length} agents registered (max: ${target.maxAgents})`,
263
+ },
264
+ {
265
+ name: 'persistence',
266
+ status: stateFileExists ? 'ok' : 'warn',
267
+ message: stateFileExists ? 'State file persisted' : 'State file missing',
268
+ },
269
+ {
270
+ name: 'topology',
271
+ status: 'ok',
272
+ message: `Topology: ${target.topology}`,
273
+ },
274
+ ];
275
+ const healthy = isRunning && stateFileExists;
88
276
  return {
89
- status: 'healthy',
90
- swarmId: input.swarmId || 'default',
91
- checks: [
92
- { name: 'coordinator', status: 'ok', message: 'Coordinator responding' },
93
- { name: 'agents', status: 'ok', message: 'Agent pool healthy' },
94
- { name: 'memory', status: 'ok', message: 'Memory backend connected' },
95
- { name: 'messaging', status: 'ok', message: 'Message bus active' },
96
- ],
277
+ status: healthy ? 'healthy' : 'degraded',
278
+ healthy,
279
+ swarmId: target.swarmId,
280
+ topology: target.topology,
281
+ agentCount: target.agents.length,
282
+ checks,
97
283
  checkedAt: new Date().toISOString(),
98
284
  };
99
285
  },
@@ -0,0 +1,9 @@
1
+ /**
2
+ * WASM Agent MCP Tools
3
+ *
4
+ * Exposes @ruvector/rvagent-wasm operations via MCP protocol.
5
+ * All tools gracefully degrade when the WASM package is not installed.
6
+ */
7
+ import type { MCPTool } from './types.js';
8
+ export declare const wasmAgentTools: MCPTool[];
9
+ //# sourceMappingURL=wasm-agent-tools.d.ts.map
@@ -0,0 +1,230 @@
1
+ /**
2
+ * WASM Agent MCP Tools
3
+ *
4
+ * Exposes @ruvector/rvagent-wasm operations via MCP protocol.
5
+ * All tools gracefully degrade when the WASM package is not installed.
6
+ */
7
+ async function loadAgentWasm() {
8
+ const mod = await import('../ruvector/agent-wasm.js');
9
+ return mod;
10
+ }
11
+ export const wasmAgentTools = [
12
+ {
13
+ name: 'wasm_agent_create',
14
+ description: 'Create a sandboxed WASM agent with virtual filesystem (no OS access). Optionally use a gallery template.',
15
+ inputSchema: {
16
+ type: 'object',
17
+ properties: {
18
+ template: { type: 'string', description: 'Gallery template name (coder, researcher, tester, reviewer, security, swarm)' },
19
+ model: { type: 'string', description: 'Model identifier (default: anthropic:claude-sonnet-4-20250514)' },
20
+ instructions: { type: 'string', description: 'System instructions for the agent' },
21
+ maxTurns: { type: 'number', description: 'Max conversation turns (default: 50)' },
22
+ },
23
+ },
24
+ handler: async (args) => {
25
+ try {
26
+ const wasm = await loadAgentWasm();
27
+ if (args.template) {
28
+ const info = await wasm.createAgentFromTemplate(args.template);
29
+ return { content: [{ type: 'text', text: JSON.stringify({ success: true, agent: info, source: 'gallery' }, null, 2) }] };
30
+ }
31
+ const info = await wasm.createWasmAgent({
32
+ model: args.model,
33
+ instructions: args.instructions,
34
+ maxTurns: args.maxTurns,
35
+ });
36
+ return { content: [{ type: 'text', text: JSON.stringify({ success: true, agent: info }, null, 2) }] };
37
+ }
38
+ catch (err) {
39
+ return { content: [{ type: 'text', text: JSON.stringify({ error: String(err) }) }], isError: true };
40
+ }
41
+ },
42
+ },
43
+ {
44
+ name: 'wasm_agent_prompt',
45
+ description: 'Send a prompt to a WASM agent and get a response.',
46
+ inputSchema: {
47
+ type: 'object',
48
+ properties: {
49
+ agentId: { type: 'string', description: 'WASM agent ID' },
50
+ input: { type: 'string', description: 'User prompt to send' },
51
+ },
52
+ required: ['agentId', 'input'],
53
+ },
54
+ handler: async (args) => {
55
+ try {
56
+ const wasm = await loadAgentWasm();
57
+ const result = await wasm.promptWasmAgent(args.agentId, args.input);
58
+ return { content: [{ type: 'text', text: result }] };
59
+ }
60
+ catch (err) {
61
+ return { content: [{ type: 'text', text: JSON.stringify({ error: String(err) }) }], isError: true };
62
+ }
63
+ },
64
+ },
65
+ {
66
+ name: 'wasm_agent_tool',
67
+ description: 'Execute a tool on a WASM agent sandbox. Tools: read_file, write_file, edit_file, write_todos, list_files. Use flat format: {tool, path, content, ...}.',
68
+ inputSchema: {
69
+ type: 'object',
70
+ properties: {
71
+ agentId: { type: 'string', description: 'WASM agent ID' },
72
+ toolName: { type: 'string', description: 'Tool name (read_file, write_file, edit_file, write_todos, list_files)' },
73
+ toolInput: { type: 'object', description: 'Tool parameters (flat: {path, content, old_string, new_string, todos})' },
74
+ },
75
+ required: ['agentId', 'toolName'],
76
+ },
77
+ handler: async (args) => {
78
+ try {
79
+ const wasm = await loadAgentWasm();
80
+ // Flat format: {tool: 'write_file', path: '...', content: '...'}
81
+ const toolCall = {
82
+ tool: args.toolName,
83
+ ...(args.toolInput ?? {}),
84
+ };
85
+ const result = await wasm.executeWasmTool(args.agentId, toolCall);
86
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
87
+ }
88
+ catch (err) {
89
+ return { content: [{ type: 'text', text: JSON.stringify({ error: String(err) }) }], isError: true };
90
+ }
91
+ },
92
+ },
93
+ {
94
+ name: 'wasm_agent_list',
95
+ description: 'List all active WASM agents.',
96
+ inputSchema: { type: 'object', properties: {} },
97
+ handler: async () => {
98
+ try {
99
+ const wasm = await loadAgentWasm();
100
+ const agents = wasm.listWasmAgents();
101
+ return { content: [{ type: 'text', text: JSON.stringify({ agents, count: agents.length }, null, 2) }] };
102
+ }
103
+ catch (err) {
104
+ return { content: [{ type: 'text', text: JSON.stringify({ error: String(err) }) }], isError: true };
105
+ }
106
+ },
107
+ },
108
+ {
109
+ name: 'wasm_agent_terminate',
110
+ description: 'Terminate a WASM agent and free resources.',
111
+ inputSchema: {
112
+ type: 'object',
113
+ properties: {
114
+ agentId: { type: 'string', description: 'WASM agent ID' },
115
+ },
116
+ required: ['agentId'],
117
+ },
118
+ handler: async (args) => {
119
+ try {
120
+ const wasm = await loadAgentWasm();
121
+ const ok = wasm.terminateWasmAgent(args.agentId);
122
+ return { content: [{ type: 'text', text: JSON.stringify({ success: ok }) }] };
123
+ }
124
+ catch (err) {
125
+ return { content: [{ type: 'text', text: JSON.stringify({ error: String(err) }) }], isError: true };
126
+ }
127
+ },
128
+ },
129
+ {
130
+ name: 'wasm_agent_files',
131
+ description: 'Get a WASM agent\'s available tools and info.',
132
+ inputSchema: {
133
+ type: 'object',
134
+ properties: {
135
+ agentId: { type: 'string', description: 'WASM agent ID' },
136
+ },
137
+ required: ['agentId'],
138
+ },
139
+ handler: async (args) => {
140
+ try {
141
+ const wasm = await loadAgentWasm();
142
+ const tools = wasm.getWasmAgentTools(args.agentId);
143
+ const info = wasm.getWasmAgent(args.agentId);
144
+ return { content: [{ type: 'text', text: JSON.stringify({ tools, fileCount: info?.fileCount ?? 0, turnCount: info?.turnCount ?? 0 }, null, 2) }] };
145
+ }
146
+ catch (err) {
147
+ return { content: [{ type: 'text', text: JSON.stringify({ error: String(err) }) }], isError: true };
148
+ }
149
+ },
150
+ },
151
+ {
152
+ name: 'wasm_agent_export',
153
+ description: 'Export a WASM agent\'s full state (config, filesystem, conversation) as JSON.',
154
+ inputSchema: {
155
+ type: 'object',
156
+ properties: {
157
+ agentId: { type: 'string', description: 'WASM agent ID' },
158
+ },
159
+ required: ['agentId'],
160
+ },
161
+ handler: async (args) => {
162
+ try {
163
+ const wasm = await loadAgentWasm();
164
+ const state = wasm.exportWasmState(args.agentId);
165
+ return { content: [{ type: 'text', text: state }] };
166
+ }
167
+ catch (err) {
168
+ return { content: [{ type: 'text', text: JSON.stringify({ error: String(err) }) }], isError: true };
169
+ }
170
+ },
171
+ },
172
+ {
173
+ name: 'wasm_gallery_list',
174
+ description: 'List all available WASM agent gallery templates (Coder, Researcher, Tester, Reviewer, Security, Swarm).',
175
+ inputSchema: { type: 'object', properties: {} },
176
+ handler: async () => {
177
+ try {
178
+ const wasm = await loadAgentWasm();
179
+ const templates = await wasm.listGalleryTemplates();
180
+ return { content: [{ type: 'text', text: JSON.stringify({ templates, count: templates.length }, null, 2) }] };
181
+ }
182
+ catch (err) {
183
+ return { content: [{ type: 'text', text: JSON.stringify({ error: String(err) }) }], isError: true };
184
+ }
185
+ },
186
+ },
187
+ {
188
+ name: 'wasm_gallery_search',
189
+ description: 'Search WASM agent gallery templates by query.',
190
+ inputSchema: {
191
+ type: 'object',
192
+ properties: {
193
+ query: { type: 'string', description: 'Search query' },
194
+ },
195
+ required: ['query'],
196
+ },
197
+ handler: async (args) => {
198
+ try {
199
+ const wasm = await loadAgentWasm();
200
+ const results = await wasm.searchGalleryTemplates(args.query);
201
+ return { content: [{ type: 'text', text: JSON.stringify({ results, count: results.length }, null, 2) }] };
202
+ }
203
+ catch (err) {
204
+ return { content: [{ type: 'text', text: JSON.stringify({ error: String(err) }) }], isError: true };
205
+ }
206
+ },
207
+ },
208
+ {
209
+ name: 'wasm_gallery_create',
210
+ description: 'Create a WASM agent from a gallery template.',
211
+ inputSchema: {
212
+ type: 'object',
213
+ properties: {
214
+ template: { type: 'string', description: 'Template name (coder, researcher, tester, reviewer, security, swarm)' },
215
+ },
216
+ required: ['template'],
217
+ },
218
+ handler: async (args) => {
219
+ try {
220
+ const wasm = await loadAgentWasm();
221
+ const info = await wasm.createAgentFromTemplate(args.template);
222
+ return { content: [{ type: 'text', text: JSON.stringify({ success: true, agent: info, template: args.template }, null, 2) }] };
223
+ }
224
+ catch (err) {
225
+ return { content: [{ type: 'text', text: JSON.stringify({ error: String(err) }) }], isError: true };
226
+ }
227
+ },
228
+ },
229
+ ];
230
+ //# sourceMappingURL=wasm-agent-tools.js.map
@@ -1315,6 +1315,60 @@ export async function loadEmbeddingModel(options) {
1315
1315
  loadTime: Date.now() - startTime
1316
1316
  };
1317
1317
  }
1318
+ // Fallback: Check for ruvector ONNX embedder (bundled MiniLM-L6-v2 since v0.2.15)
1319
+ // v0.2.16: LoRA B=0 fix makes AdaptiveEmbedder safe (identity when untrained)
1320
+ const ruvector = await import('ruvector').catch(() => null);
1321
+ if (ruvector?.initOnnxEmbedder) {
1322
+ try {
1323
+ await ruvector.initOnnxEmbedder();
1324
+ // Prefer AdaptiveEmbedder (LoRA adaptation capability, identity when untrained)
1325
+ if (ruvector.AdaptiveEmbedder) {
1326
+ try {
1327
+ const adaptive = new ruvector.AdaptiveEmbedder({ useEpisodic: false });
1328
+ if (adaptive?.embed && adaptive.isReady?.()) {
1329
+ if (verbose) {
1330
+ console.log('Loading ruvector AdaptiveEmbedder (all-MiniLM-L6-v2 + LoRA)...');
1331
+ }
1332
+ embeddingModelState = {
1333
+ loaded: true,
1334
+ model: (text) => adaptive.embed(text),
1335
+ tokenizer: null,
1336
+ dimensions: adaptive.getDimension?.() || 384
1337
+ };
1338
+ return {
1339
+ success: true,
1340
+ dimensions: adaptive.getDimension?.() || 384,
1341
+ modelName: 'ruvector/adaptive',
1342
+ loadTime: Date.now() - startTime
1343
+ };
1344
+ }
1345
+ }
1346
+ catch { /* fall through to OptimizedOnnxEmbedder */ }
1347
+ }
1348
+ // Fallback: OptimizedOnnxEmbedder (raw ONNX without LoRA)
1349
+ const onnxEmb = ruvector.getOptimizedOnnxEmbedder?.();
1350
+ if (onnxEmb?.embed && onnxEmb.isReady?.()) {
1351
+ if (verbose) {
1352
+ console.log('Loading ruvector ONNX embedder (all-MiniLM-L6-v2)...');
1353
+ }
1354
+ embeddingModelState = {
1355
+ loaded: true,
1356
+ model: (text) => onnxEmb.embed(text),
1357
+ tokenizer: null,
1358
+ dimensions: onnxEmb.getDimension?.() || 384
1359
+ };
1360
+ return {
1361
+ success: true,
1362
+ dimensions: onnxEmb.getDimension?.() || 384,
1363
+ modelName: 'ruvector/onnx',
1364
+ loadTime: Date.now() - startTime
1365
+ };
1366
+ }
1367
+ }
1368
+ catch {
1369
+ // ruvector ONNX init failed, continue to next fallback
1370
+ }
1371
+ }
1318
1372
  // Legacy fallback: Check for agentic-flow core embeddings
1319
1373
  const agenticFlow = await import('agentic-flow').catch(() => null);
1320
1374
  if (agenticFlow && agenticFlow.embeddings) {
@@ -1378,12 +1432,17 @@ export async function generateEmbedding(text) {
1378
1432
  if (state.model && typeof state.model === 'function') {
1379
1433
  try {
1380
1434
  const output = await state.model(text, { pooling: 'mean', normalize: true });
1381
- const embedding = Array.from(output.data);
1382
- return {
1383
- embedding,
1384
- dimensions: embedding.length,
1385
- model: 'onnx'
1386
- };
1435
+ // Handle both @xenova/transformers (output.data) and ruvector (plain array) formats
1436
+ const embedding = output?.data
1437
+ ? Array.from(output.data)
1438
+ : Array.isArray(output) ? output : null;
1439
+ if (embedding) {
1440
+ return {
1441
+ embedding,
1442
+ dimensions: embedding.length,
1443
+ model: 'onnx'
1444
+ };
1445
+ }
1387
1446
  }
1388
1447
  catch {
1389
1448
  // Fall through to fallback