claude-flow 3.5.69 → 3.5.70
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/commands/autopilot.js +1 -1
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +4 -7
- package/v3/@claude-flow/cli/dist/src/commands/init.js +0 -1
- package/v3/@claude-flow/cli/dist/src/commands/neural.js +1 -0
- package/v3/@claude-flow/cli/dist/src/commands/providers.js +228 -96
- package/v3/@claude-flow/cli/dist/src/commands/security.js +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/config-tools.js +53 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.js +29 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/swarm-tools.js +30 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/task-tools.js +33 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/terminal-tools.js +31 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/workflow-tools.js +82 -0
- package/v3/@claude-flow/cli/dist/src/memory/intelligence.d.ts +6 -1
- package/v3/@claude-flow/cli/dist/src/memory/intelligence.js +51 -1
- package/v3/@claude-flow/cli/package.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-flow",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.70",
|
|
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",
|
|
@@ -287,7 +287,7 @@ const predictCommand = {
|
|
|
287
287
|
else {
|
|
288
288
|
output.writeln(`Action: ${prediction?.action || 'unknown'}`);
|
|
289
289
|
output.writeln(`Confidence: ${prediction?.confidence || 0}`);
|
|
290
|
-
if (prediction?.alternatives
|
|
290
|
+
if (prediction?.alternatives && prediction.alternatives.length > 0)
|
|
291
291
|
output.writeln(`Alternatives: ${prediction.alternatives.join(', ')}`);
|
|
292
292
|
}
|
|
293
293
|
return { success: true };
|
|
@@ -3763,7 +3763,7 @@ const postBashCommand = {
|
|
|
3763
3763
|
// Token Optimizer command - integrates agentic-flow Agent Booster
|
|
3764
3764
|
const tokenOptimizeCommand = {
|
|
3765
3765
|
name: 'token-optimize',
|
|
3766
|
-
description: 'Token optimization via agentic-flow Agent Booster
|
|
3766
|
+
description: 'Token optimization via agentic-flow Agent Booster integration',
|
|
3767
3767
|
options: [
|
|
3768
3768
|
{ name: 'query', short: 'q', type: 'string', description: 'Query for compact context retrieval' },
|
|
3769
3769
|
{ name: 'agents', short: 'A', type: 'number', description: 'Agent count for optimal config', default: '6' },
|
|
@@ -3791,7 +3791,6 @@ const tokenOptimizeCommand = {
|
|
|
3791
3791
|
memoriesRetrieved: 0,
|
|
3792
3792
|
};
|
|
3793
3793
|
let agenticFlowAvailable = false;
|
|
3794
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3795
3794
|
let reasoningBank = null;
|
|
3796
3795
|
try {
|
|
3797
3796
|
// Check if agentic-flow v3 is available
|
|
@@ -3846,10 +3845,8 @@ const tokenOptimizeCommand = {
|
|
|
3846
3845
|
output.writeln();
|
|
3847
3846
|
output.printInfo('ReasoningBank not available - query skipped');
|
|
3848
3847
|
}
|
|
3849
|
-
//
|
|
3850
|
-
|
|
3851
|
-
stats.cacheHits = 2;
|
|
3852
|
-
stats.cacheMisses = 1;
|
|
3848
|
+
// Note: stats reflect only actual measured values from this session.
|
|
3849
|
+
// No simulated/fabricated data is added.
|
|
3853
3850
|
// Show stats
|
|
3854
3851
|
if (showStats || showReport) {
|
|
3855
3852
|
output.writeln();
|
|
@@ -4413,7 +4410,7 @@ export const hooksCommand = {
|
|
|
4413
4410
|
`${output.highlight('coverage-route')} - Route tasks based on coverage gaps (ruvector)`,
|
|
4414
4411
|
`${output.highlight('coverage-suggest')}- Suggest coverage improvements`,
|
|
4415
4412
|
`${output.highlight('coverage-gaps')} - List all coverage gaps with agents`,
|
|
4416
|
-
`${output.highlight('token-optimize')} - Token optimization (
|
|
4413
|
+
`${output.highlight('token-optimize')} - Token optimization (agentic-flow integration)`,
|
|
4417
4414
|
`${output.highlight('model-route')} - Route to optimal model (haiku/sonnet/opus)`,
|
|
4418
4415
|
`${output.highlight('model-outcome')} - Record model routing outcome`,
|
|
4419
4416
|
`${output.highlight('model-stats')} - View model routing statistics`,
|
|
@@ -18,7 +18,6 @@ async function initCodexAction(ctx, options) {
|
|
|
18
18
|
const spinner = output.createSpinner({ text: 'Initializing Codex project...' });
|
|
19
19
|
spinner.start();
|
|
20
20
|
try {
|
|
21
|
-
// Dynamic import of the Codex initializer with lazy loading fallback
|
|
22
21
|
let CodexInitializer;
|
|
23
22
|
// Try multiple resolution strategies for the @claude-flow/codex package
|
|
24
23
|
// Use a variable to prevent TypeScript from statically resolving the optional module
|
|
@@ -1333,6 +1333,7 @@ const benchmarkCommand = {
|
|
|
1333
1333
|
const spinner = output.createSpinner({ text: 'Running benchmarks...', spinner: 'dots' });
|
|
1334
1334
|
spinner.start();
|
|
1335
1335
|
try {
|
|
1336
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- dynamic import of optional native WASM module with no type declarations
|
|
1336
1337
|
const attention = await import('@ruvector/attention');
|
|
1337
1338
|
// Manual benchmark since benchmarkAttention has a binding bug
|
|
1338
1339
|
const benchmarkMechanism = async (name, mechanism) => {
|
|
@@ -6,6 +6,91 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { output } from '../output.js';
|
|
8
8
|
import { configManager } from '../services/config-file-manager.js';
|
|
9
|
+
const PROVIDER_CATALOG = [
|
|
10
|
+
{ name: 'Anthropic', type: 'LLM', models: 'claude-3.5-sonnet, opus', envVar: 'ANTHROPIC_API_KEY', configName: 'anthropic' },
|
|
11
|
+
{ name: 'OpenAI', type: 'LLM', models: 'gpt-4o, gpt-4-turbo', envVar: 'OPENAI_API_KEY', configName: 'openai' },
|
|
12
|
+
{ name: 'OpenAI', type: 'Embedding', models: 'text-embedding-3-small/large', envVar: 'OPENAI_API_KEY', configName: 'openai' },
|
|
13
|
+
{ name: 'Google', type: 'LLM', models: 'gemini-pro, gemini-ultra', envVar: 'GOOGLE_API_KEY', configName: 'google' },
|
|
14
|
+
{ name: 'Transformers.js', type: 'Embedding', models: 'Xenova/all-MiniLM-L6-v2' },
|
|
15
|
+
{ name: 'Agentic Flow', type: 'Embedding', models: 'ONNX optimized' },
|
|
16
|
+
{ name: 'Mock', type: 'All', models: 'mock-*' },
|
|
17
|
+
];
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the API key for a provider by checking the config file first,
|
|
20
|
+
* then falling back to well-known environment variables.
|
|
21
|
+
*/
|
|
22
|
+
function resolveApiKey(providerName, configuredProviders) {
|
|
23
|
+
// Check config file entry
|
|
24
|
+
const entry = configuredProviders.find((p) => typeof p.name === 'string' && p.name.toLowerCase() === providerName.toLowerCase());
|
|
25
|
+
if (entry?.apiKey && typeof entry.apiKey === 'string') {
|
|
26
|
+
return entry.apiKey;
|
|
27
|
+
}
|
|
28
|
+
// Check environment variable
|
|
29
|
+
const envMapping = {
|
|
30
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
31
|
+
openai: 'OPENAI_API_KEY',
|
|
32
|
+
google: 'GOOGLE_API_KEY',
|
|
33
|
+
};
|
|
34
|
+
const envVar = envMapping[providerName.toLowerCase()];
|
|
35
|
+
if (envVar && process.env[envVar]) {
|
|
36
|
+
return process.env[envVar];
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Make a lightweight HTTP request to verify provider API key validity.
|
|
42
|
+
* Uses a 5-second timeout. Returns { ok, reason }.
|
|
43
|
+
*/
|
|
44
|
+
async function testProviderConnectivity(providerName, apiKey) {
|
|
45
|
+
const endpoints = {
|
|
46
|
+
anthropic: {
|
|
47
|
+
url: 'https://api.anthropic.com/v1/models',
|
|
48
|
+
headers: {
|
|
49
|
+
'x-api-key': apiKey,
|
|
50
|
+
'anthropic-version': '2023-06-01',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
openai: {
|
|
54
|
+
url: 'https://api.openai.com/v1/models',
|
|
55
|
+
headers: {
|
|
56
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
google: {
|
|
60
|
+
url: `https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}`,
|
|
61
|
+
headers: {},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
const endpointConfig = endpoints[providerName.toLowerCase()];
|
|
65
|
+
if (!endpointConfig) {
|
|
66
|
+
return { ok: false, reason: 'No test endpoint available for this provider' };
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const controller = new AbortController();
|
|
70
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
71
|
+
const res = await fetch(endpointConfig.url, {
|
|
72
|
+
method: 'GET',
|
|
73
|
+
headers: endpointConfig.headers,
|
|
74
|
+
signal: controller.signal,
|
|
75
|
+
});
|
|
76
|
+
clearTimeout(timeout);
|
|
77
|
+
if (res.ok || res.status === 200) {
|
|
78
|
+
return { ok: true, reason: 'Connected successfully' };
|
|
79
|
+
}
|
|
80
|
+
if (res.status === 401 || res.status === 403) {
|
|
81
|
+
return { ok: false, reason: `Authentication failed (HTTP ${res.status})` };
|
|
82
|
+
}
|
|
83
|
+
// A non-auth error but the server responded — key format may be fine
|
|
84
|
+
return { ok: false, reason: `Unexpected response (HTTP ${res.status})` };
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
88
|
+
return { ok: false, reason: 'Connection timed out (5s)' };
|
|
89
|
+
}
|
|
90
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
91
|
+
return { ok: false, reason: `Connection failed: ${msg}` };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
9
94
|
// List subcommand
|
|
10
95
|
const listCommand = {
|
|
11
96
|
name: 'list',
|
|
@@ -20,26 +105,88 @@ const listCommand = {
|
|
|
20
105
|
],
|
|
21
106
|
action: async (ctx) => {
|
|
22
107
|
const type = ctx.flags.type || 'all';
|
|
23
|
-
|
|
108
|
+
const activeOnly = ctx.flags.active;
|
|
109
|
+
// Load user configuration
|
|
110
|
+
const cwd = process.cwd();
|
|
111
|
+
const config = configManager.getConfig(cwd);
|
|
112
|
+
const agents = (config.agents ?? {});
|
|
113
|
+
const configuredProviders = (agents.providers ?? []);
|
|
114
|
+
// Build table rows from the catalog, enriched with configuration status
|
|
115
|
+
const rows = [];
|
|
116
|
+
for (const entry of PROVIDER_CATALOG) {
|
|
117
|
+
// Apply type filter
|
|
118
|
+
if (type !== 'all' && entry.type.toLowerCase() !== type.toLowerCase()) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
let status;
|
|
122
|
+
let keySource = '';
|
|
123
|
+
if (entry.configName) {
|
|
124
|
+
const apiKey = resolveApiKey(entry.configName, configuredProviders);
|
|
125
|
+
if (apiKey) {
|
|
126
|
+
// Determine the source for the key
|
|
127
|
+
const configEntry = configuredProviders.find((p) => typeof p.name === 'string' && p.name.toLowerCase() === entry.configName.toLowerCase());
|
|
128
|
+
if (configEntry?.apiKey && typeof configEntry.apiKey === 'string') {
|
|
129
|
+
keySource = 'config';
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
keySource = 'env';
|
|
133
|
+
}
|
|
134
|
+
status = output.success(`Configured (${keySource})`);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
status = output.warning('Not configured');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else if (entry.name === 'Mock') {
|
|
141
|
+
status = output.dim('Dev only');
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
// Local-only providers (Transformers.js, Agentic Flow) — always available
|
|
145
|
+
status = output.success('Available (local)');
|
|
146
|
+
}
|
|
147
|
+
if (activeOnly && !status.includes('Configured') && !status.includes('Available')) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
rows.push({
|
|
151
|
+
provider: entry.name,
|
|
152
|
+
type: entry.type,
|
|
153
|
+
models: entry.models,
|
|
154
|
+
status,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
// Also show any providers in config that are not in the static catalog
|
|
158
|
+
for (const cp of configuredProviders) {
|
|
159
|
+
const cpName = cp.name || '';
|
|
160
|
+
const alreadyListed = PROVIDER_CATALOG.some((e) => e.configName?.toLowerCase() === cpName.toLowerCase() || e.name.toLowerCase() === cpName.toLowerCase());
|
|
161
|
+
if (!alreadyListed && cpName) {
|
|
162
|
+
const hasKey = !!(cp.apiKey || resolveApiKey(cpName, configuredProviders));
|
|
163
|
+
rows.push({
|
|
164
|
+
provider: cpName,
|
|
165
|
+
type: cp.type || 'Custom',
|
|
166
|
+
models: cp.model || output.dim('(not specified)'),
|
|
167
|
+
status: hasKey ? output.success('Configured (config)') : output.warning('Not configured'),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
24
171
|
output.writeln();
|
|
25
|
-
output.writeln(output.bold('
|
|
172
|
+
output.writeln(output.bold('Providers'));
|
|
26
173
|
output.writeln(output.dim('─'.repeat(60)));
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
174
|
+
if (rows.length === 0) {
|
|
175
|
+
output.writeln(output.dim(' No providers match the current filter.'));
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
output.printTable({
|
|
179
|
+
columns: [
|
|
180
|
+
{ key: 'provider', header: 'Provider', width: 18 },
|
|
181
|
+
{ key: 'type', header: 'Type', width: 12 },
|
|
182
|
+
{ key: 'models', header: 'Models', width: 25 },
|
|
183
|
+
{ key: 'status', header: 'Status', width: 20 },
|
|
184
|
+
],
|
|
185
|
+
data: rows,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
output.writeln();
|
|
189
|
+
output.writeln(output.dim('Tip: Use "providers configure -p <name> -k <key>" to set API keys.'));
|
|
43
190
|
return { success: true };
|
|
44
191
|
},
|
|
45
192
|
};
|
|
@@ -133,98 +280,83 @@ const testCommand = {
|
|
|
133
280
|
const config = configManager.getConfig(cwd);
|
|
134
281
|
const agents = (config.agents ?? {});
|
|
135
282
|
const configuredProviders = (agents.providers ?? []);
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const knownChecks = [
|
|
141
|
-
{
|
|
142
|
-
name: 'Anthropic',
|
|
143
|
-
test: async () => {
|
|
144
|
-
const key = process.env.ANTHROPIC_API_KEY || getConfigApiKey('anthropic');
|
|
145
|
-
if (key)
|
|
146
|
-
return { pass: true, reason: 'API key found' };
|
|
147
|
-
return { pass: false, reason: 'ANTHROPIC_API_KEY not set and no apiKey in config' };
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
name: 'OpenAI',
|
|
152
|
-
test: async () => {
|
|
153
|
-
const key = process.env.OPENAI_API_KEY || getConfigApiKey('openai');
|
|
154
|
-
if (key)
|
|
155
|
-
return { pass: true, reason: 'API key found' };
|
|
156
|
-
return { pass: false, reason: 'OPENAI_API_KEY not set and no apiKey in config' };
|
|
157
|
-
},
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
name: 'Google',
|
|
161
|
-
test: async () => {
|
|
162
|
-
const key = process.env.GOOGLE_API_KEY || getConfigApiKey('google');
|
|
163
|
-
if (key)
|
|
164
|
-
return { pass: true, reason: 'API key found' };
|
|
165
|
-
return { pass: false, reason: 'GOOGLE_API_KEY not set and no apiKey in config' };
|
|
166
|
-
},
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
name: 'Ollama',
|
|
170
|
-
test: async () => {
|
|
171
|
-
const entry = configuredProviders.find((p) => typeof p.name === 'string' && p.name.toLowerCase() === 'ollama');
|
|
172
|
-
const baseUrl = entry?.baseUrl || 'http://localhost:11434';
|
|
173
|
-
try {
|
|
174
|
-
const controller = new AbortController();
|
|
175
|
-
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
176
|
-
const res = await fetch(baseUrl, { signal: controller.signal });
|
|
177
|
-
clearTimeout(timeout);
|
|
178
|
-
if (res.ok)
|
|
179
|
-
return { pass: true, reason: `Reachable at ${baseUrl}` };
|
|
180
|
-
return { pass: false, reason: `HTTP ${res.status} from ${baseUrl}` };
|
|
181
|
-
}
|
|
182
|
-
catch {
|
|
183
|
-
return { pass: false, reason: `Unreachable at ${baseUrl}` };
|
|
184
|
-
}
|
|
185
|
-
},
|
|
186
|
-
},
|
|
283
|
+
const knownTargets = [
|
|
284
|
+
{ name: 'Anthropic', configName: 'anthropic' },
|
|
285
|
+
{ name: 'OpenAI', configName: 'openai' },
|
|
286
|
+
{ name: 'Google', configName: 'google' },
|
|
187
287
|
];
|
|
188
|
-
//
|
|
189
|
-
|
|
288
|
+
// Add Ollama as a special case (endpoint-based, no API key)
|
|
289
|
+
const ollamaEntry = configuredProviders.find((p) => typeof p.name === 'string' && p.name.toLowerCase() === 'ollama');
|
|
290
|
+
let targets;
|
|
190
291
|
if (testAll || !provider) {
|
|
191
|
-
|
|
292
|
+
targets = [...knownTargets];
|
|
192
293
|
}
|
|
193
294
|
else {
|
|
194
|
-
const match =
|
|
195
|
-
|
|
196
|
-
|
|
295
|
+
const match = knownTargets.find((t) => t.name.toLowerCase() === provider.toLowerCase() || t.configName === provider.toLowerCase());
|
|
296
|
+
targets = match ? [match] : [{ name: provider, configName: provider.toLowerCase() }];
|
|
297
|
+
}
|
|
298
|
+
const results = [];
|
|
299
|
+
// Test API-key-based providers with real connectivity checks
|
|
300
|
+
for (const target of targets) {
|
|
301
|
+
const apiKey = resolveApiKey(target.configName, configuredProviders);
|
|
302
|
+
if (!apiKey) {
|
|
303
|
+
results.push({ name: target.name, pass: false, reason: 'Not configured (no API key found)' });
|
|
304
|
+
continue;
|
|
197
305
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
306
|
+
output.writeln(output.dim(` Testing ${target.name}...`));
|
|
307
|
+
const result = await testProviderConnectivity(target.name, apiKey);
|
|
308
|
+
results.push({ name: target.name, pass: result.ok, reason: result.reason });
|
|
309
|
+
}
|
|
310
|
+
// Test Ollama separately (endpoint-based, no API key needed)
|
|
311
|
+
if (testAll || !provider || provider.toLowerCase() === 'ollama') {
|
|
312
|
+
const baseUrl = ollamaEntry?.baseUrl || 'http://localhost:11434';
|
|
313
|
+
output.writeln(output.dim(` Testing Ollama at ${baseUrl}...`));
|
|
314
|
+
try {
|
|
315
|
+
const controller = new AbortController();
|
|
316
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
317
|
+
const res = await fetch(baseUrl, { signal: controller.signal });
|
|
318
|
+
clearTimeout(timeout);
|
|
319
|
+
if (res.ok) {
|
|
320
|
+
results.push({ name: 'Ollama', pass: true, reason: `Connected at ${baseUrl}` });
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
results.push({ name: 'Ollama', pass: false, reason: `HTTP ${res.status} from ${baseUrl}` });
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
results.push({ name: 'Ollama', pass: false, reason: `Unreachable at ${baseUrl}` });
|
|
211
328
|
}
|
|
212
329
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
330
|
+
// Also test any custom providers from config that were not in the known list
|
|
331
|
+
if (testAll || !provider) {
|
|
332
|
+
for (const cp of configuredProviders) {
|
|
333
|
+
const cpName = cp.name || '';
|
|
334
|
+
const alreadyTested = results.some((r) => r.name.toLowerCase() === cpName.toLowerCase());
|
|
335
|
+
if (alreadyTested || !cpName)
|
|
336
|
+
continue;
|
|
337
|
+
const apiKey = resolveApiKey(cpName, configuredProviders);
|
|
338
|
+
if (!apiKey) {
|
|
339
|
+
results.push({ name: cpName, pass: false, reason: 'No API key found' });
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
// For custom providers we can only verify the key exists
|
|
343
|
+
results.push({ name: cpName, pass: true, reason: 'API key found (no test endpoint available)' });
|
|
344
|
+
}
|
|
345
|
+
}
|
|
220
346
|
}
|
|
347
|
+
let anyPassed = false;
|
|
221
348
|
output.writeln();
|
|
222
349
|
for (const r of results) {
|
|
223
350
|
const icon = r.pass ? output.success('PASS') : output.error('FAIL');
|
|
224
351
|
output.writeln(` ${icon} ${r.name}: ${r.reason}`);
|
|
352
|
+
if (r.pass)
|
|
353
|
+
anyPassed = true;
|
|
225
354
|
}
|
|
226
355
|
output.writeln();
|
|
227
|
-
if (
|
|
356
|
+
if (results.length === 0) {
|
|
357
|
+
output.writeln(output.warning('No providers to test. Use "providers configure" to add providers.'));
|
|
358
|
+
}
|
|
359
|
+
else if (anyPassed) {
|
|
228
360
|
output.writeln(output.success(`${results.filter((r) => r.pass).length}/${results.length} provider(s) passed.`));
|
|
229
361
|
}
|
|
230
362
|
else {
|
|
@@ -53,7 +53,7 @@ const scanCommand = {
|
|
|
53
53
|
}
|
|
54
54
|
catch (auditErr) {
|
|
55
55
|
// npm audit exits non-zero when vulnerabilities found — stdout still has JSON
|
|
56
|
-
auditResult = auditErr.stdout || '{}';
|
|
56
|
+
auditResult = (auditErr instanceof Error && 'stdout' in auditErr ? auditErr.stdout : undefined) || '{}';
|
|
57
57
|
}
|
|
58
58
|
try {
|
|
59
59
|
const audit = JSON.parse(auditResult);
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { getProjectCwd } from './types.js';
|
|
9
|
+
import { validateIdentifier, validateText } from './validate-input.js';
|
|
9
10
|
// Storage paths
|
|
10
11
|
const STORAGE_DIR = '.claude-flow';
|
|
11
12
|
const CONFIG_FILE = 'config.json';
|
|
@@ -115,6 +116,15 @@ export const configTools = [
|
|
|
115
116
|
required: ['key'],
|
|
116
117
|
},
|
|
117
118
|
handler: async (input) => {
|
|
119
|
+
// Validate user-provided input (#1425)
|
|
120
|
+
const vKey = validateText(input.key, 'key', 256);
|
|
121
|
+
if (!vKey.valid)
|
|
122
|
+
return { success: false, error: vKey.error };
|
|
123
|
+
if (input.scope) {
|
|
124
|
+
const v = validateIdentifier(input.scope, 'scope');
|
|
125
|
+
if (!v.valid)
|
|
126
|
+
return { success: false, error: v.error };
|
|
127
|
+
}
|
|
118
128
|
const store = loadConfigStore();
|
|
119
129
|
const key = input.key;
|
|
120
130
|
const scope = input.scope || 'default';
|
|
@@ -152,6 +162,15 @@ export const configTools = [
|
|
|
152
162
|
required: ['key', 'value'],
|
|
153
163
|
},
|
|
154
164
|
handler: async (input) => {
|
|
165
|
+
// Validate user-provided input (#1425)
|
|
166
|
+
const vKey = validateText(input.key, 'key', 256);
|
|
167
|
+
if (!vKey.valid)
|
|
168
|
+
return { success: false, error: vKey.error };
|
|
169
|
+
if (input.scope) {
|
|
170
|
+
const v = validateIdentifier(input.scope, 'scope');
|
|
171
|
+
if (!v.valid)
|
|
172
|
+
return { success: false, error: v.error };
|
|
173
|
+
}
|
|
155
174
|
const store = loadConfigStore();
|
|
156
175
|
const key = input.key;
|
|
157
176
|
const value = input.value;
|
|
@@ -190,6 +209,17 @@ export const configTools = [
|
|
|
190
209
|
},
|
|
191
210
|
},
|
|
192
211
|
handler: async (input) => {
|
|
212
|
+
// Validate user-provided input (#1425)
|
|
213
|
+
if (input.scope) {
|
|
214
|
+
const v = validateIdentifier(input.scope, 'scope');
|
|
215
|
+
if (!v.valid)
|
|
216
|
+
return { success: false, error: v.error };
|
|
217
|
+
}
|
|
218
|
+
if (input.prefix) {
|
|
219
|
+
const v = validateText(input.prefix, 'prefix', 256);
|
|
220
|
+
if (!v.valid)
|
|
221
|
+
return { success: false, error: v.error };
|
|
222
|
+
}
|
|
193
223
|
const store = loadConfigStore();
|
|
194
224
|
const scope = input.scope || 'default';
|
|
195
225
|
const prefix = input.prefix;
|
|
@@ -236,6 +266,17 @@ export const configTools = [
|
|
|
236
266
|
},
|
|
237
267
|
},
|
|
238
268
|
handler: async (input) => {
|
|
269
|
+
// Validate user-provided input (#1425)
|
|
270
|
+
if (input.scope) {
|
|
271
|
+
const v = validateIdentifier(input.scope, 'scope');
|
|
272
|
+
if (!v.valid)
|
|
273
|
+
return { success: false, error: v.error };
|
|
274
|
+
}
|
|
275
|
+
if (input.key) {
|
|
276
|
+
const v = validateText(input.key, 'key', 256);
|
|
277
|
+
if (!v.valid)
|
|
278
|
+
return { success: false, error: v.error };
|
|
279
|
+
}
|
|
239
280
|
const store = loadConfigStore();
|
|
240
281
|
const scope = input.scope || 'default';
|
|
241
282
|
const key = input.key;
|
|
@@ -286,6 +327,12 @@ export const configTools = [
|
|
|
286
327
|
},
|
|
287
328
|
},
|
|
288
329
|
handler: async (input) => {
|
|
330
|
+
// Validate user-provided input (#1425)
|
|
331
|
+
if (input.scope) {
|
|
332
|
+
const v = validateIdentifier(input.scope, 'scope');
|
|
333
|
+
if (!v.valid)
|
|
334
|
+
return { success: false, error: v.error };
|
|
335
|
+
}
|
|
289
336
|
const store = loadConfigStore();
|
|
290
337
|
const scope = input.scope || 'default';
|
|
291
338
|
const includeDefaults = input.includeDefaults !== false;
|
|
@@ -320,6 +367,12 @@ export const configTools = [
|
|
|
320
367
|
required: ['config'],
|
|
321
368
|
},
|
|
322
369
|
handler: async (input) => {
|
|
370
|
+
// Validate user-provided input (#1425)
|
|
371
|
+
if (input.scope) {
|
|
372
|
+
const v = validateIdentifier(input.scope, 'scope');
|
|
373
|
+
if (!v.valid)
|
|
374
|
+
return { success: false, error: v.error };
|
|
375
|
+
}
|
|
323
376
|
const store = loadConfigStore();
|
|
324
377
|
const config = filterDangerousKeys(input.config);
|
|
325
378
|
const scope = input.scope || 'default';
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, unlinkSync, statSync } from 'node:fs';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { getProjectCwd } from './types.js';
|
|
9
|
+
import { validateIdentifier, validateText } from './validate-input.js';
|
|
9
10
|
// Storage paths
|
|
10
11
|
const STORAGE_DIR = '.claude-flow';
|
|
11
12
|
const SESSION_DIR = 'sessions';
|
|
@@ -105,6 +106,15 @@ export const sessionTools = [
|
|
|
105
106
|
required: ['name'],
|
|
106
107
|
},
|
|
107
108
|
handler: async (input) => {
|
|
109
|
+
// Validate user-provided input (#1425)
|
|
110
|
+
const vName = validateText(input.name, 'name', 256);
|
|
111
|
+
if (!vName.valid)
|
|
112
|
+
return { success: false, error: vName.error };
|
|
113
|
+
if (input.description) {
|
|
114
|
+
const v = validateText(input.description, 'description');
|
|
115
|
+
if (!v.valid)
|
|
116
|
+
return { success: false, error: v.error };
|
|
117
|
+
}
|
|
108
118
|
const sessionId = `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
109
119
|
// Load related data based on options
|
|
110
120
|
const data = loadRelatedStores({
|
|
@@ -152,6 +162,17 @@ export const sessionTools = [
|
|
|
152
162
|
},
|
|
153
163
|
},
|
|
154
164
|
handler: async (input) => {
|
|
165
|
+
// Validate user-provided input (#1425)
|
|
166
|
+
if (input.sessionId) {
|
|
167
|
+
const v = validateIdentifier(input.sessionId, 'sessionId');
|
|
168
|
+
if (!v.valid)
|
|
169
|
+
return { success: false, error: v.error };
|
|
170
|
+
}
|
|
171
|
+
if (input.name) {
|
|
172
|
+
const v = validateText(input.name, 'name', 256);
|
|
173
|
+
if (!v.valid)
|
|
174
|
+
return { success: false, error: v.error };
|
|
175
|
+
}
|
|
155
176
|
let session = null;
|
|
156
177
|
// Try to find by sessionId first
|
|
157
178
|
if (input.sessionId) {
|
|
@@ -279,6 +300,10 @@ export const sessionTools = [
|
|
|
279
300
|
required: ['sessionId'],
|
|
280
301
|
},
|
|
281
302
|
handler: async (input) => {
|
|
303
|
+
// Validate user-provided input (#1425)
|
|
304
|
+
const vId = validateIdentifier(input.sessionId, 'sessionId');
|
|
305
|
+
if (!vId.valid)
|
|
306
|
+
return { success: false, error: vId.error };
|
|
282
307
|
const sessionId = input.sessionId;
|
|
283
308
|
const path = getSessionPath(sessionId);
|
|
284
309
|
if (existsSync(path)) {
|
|
@@ -308,6 +333,10 @@ export const sessionTools = [
|
|
|
308
333
|
required: ['sessionId'],
|
|
309
334
|
},
|
|
310
335
|
handler: async (input) => {
|
|
336
|
+
// Validate user-provided input (#1425)
|
|
337
|
+
const vId = validateIdentifier(input.sessionId, 'sessionId');
|
|
338
|
+
if (!vId.valid)
|
|
339
|
+
return { success: false, error: vId.error };
|
|
311
340
|
const sessionId = input.sessionId;
|
|
312
341
|
const session = loadSession(sessionId);
|
|
313
342
|
if (session) {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import { getProjectCwd } from './types.js';
|
|
10
|
+
import { validateIdentifier } from './validate-input.js';
|
|
10
11
|
// Swarm state persistence
|
|
11
12
|
const SWARM_DIR = '.claude-flow/swarm';
|
|
12
13
|
const SWARM_STATE_FILE = 'swarm-state.json';
|
|
@@ -55,6 +56,17 @@ export const swarmTools = [
|
|
|
55
56
|
},
|
|
56
57
|
},
|
|
57
58
|
handler: async (input) => {
|
|
59
|
+
// Validate user-provided input (#1425)
|
|
60
|
+
if (input.topology) {
|
|
61
|
+
const v = validateIdentifier(input.topology, 'topology');
|
|
62
|
+
if (!v.valid)
|
|
63
|
+
return { success: false, error: v.error };
|
|
64
|
+
}
|
|
65
|
+
if (input.strategy) {
|
|
66
|
+
const v = validateIdentifier(input.strategy, 'strategy');
|
|
67
|
+
if (!v.valid)
|
|
68
|
+
return { success: false, error: v.error };
|
|
69
|
+
}
|
|
58
70
|
const topology = input.topology || 'hierarchical-mesh';
|
|
59
71
|
const maxAgents = Math.min(Math.max(input.maxAgents || 15, 1), 50);
|
|
60
72
|
const strategy = input.strategy || 'specialized';
|
|
@@ -111,6 +123,12 @@ export const swarmTools = [
|
|
|
111
123
|
},
|
|
112
124
|
},
|
|
113
125
|
handler: async (input) => {
|
|
126
|
+
// Validate user-provided input (#1425)
|
|
127
|
+
if (input.swarmId) {
|
|
128
|
+
const v = validateIdentifier(input.swarmId, 'swarmId');
|
|
129
|
+
if (!v.valid)
|
|
130
|
+
return { success: false, error: v.error };
|
|
131
|
+
}
|
|
114
132
|
const store = loadSwarmStore();
|
|
115
133
|
const swarmId = input.swarmId;
|
|
116
134
|
if (swarmId && store.swarms[swarmId]) {
|
|
@@ -165,6 +183,12 @@ export const swarmTools = [
|
|
|
165
183
|
},
|
|
166
184
|
},
|
|
167
185
|
handler: async (input) => {
|
|
186
|
+
// Validate user-provided input (#1425)
|
|
187
|
+
if (input.swarmId) {
|
|
188
|
+
const v = validateIdentifier(input.swarmId, 'swarmId');
|
|
189
|
+
if (!v.valid)
|
|
190
|
+
return { success: false, error: v.error };
|
|
191
|
+
}
|
|
168
192
|
const store = loadSwarmStore();
|
|
169
193
|
const swarmId = input.swarmId;
|
|
170
194
|
// Find the swarm
|
|
@@ -216,6 +240,12 @@ export const swarmTools = [
|
|
|
216
240
|
},
|
|
217
241
|
},
|
|
218
242
|
handler: async (input) => {
|
|
243
|
+
// Validate user-provided input (#1425)
|
|
244
|
+
if (input.swarmId) {
|
|
245
|
+
const v = validateIdentifier(input.swarmId, 'swarmId');
|
|
246
|
+
if (!v.valid)
|
|
247
|
+
return { success: false, error: v.error };
|
|
248
|
+
}
|
|
219
249
|
const store = loadSwarmStore();
|
|
220
250
|
const swarmId = input.swarmId;
|
|
221
251
|
// Find the swarm
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { getProjectCwd } from './types.js';
|
|
9
|
+
import { validateIdentifier, validateText } from './validate-input.js';
|
|
9
10
|
// Storage paths
|
|
10
11
|
const STORAGE_DIR = '.claude-flow';
|
|
11
12
|
const TASK_DIR = 'tasks';
|
|
@@ -56,6 +57,13 @@ export const taskTools = [
|
|
|
56
57
|
required: ['type', 'description'],
|
|
57
58
|
},
|
|
58
59
|
handler: async (input) => {
|
|
60
|
+
// Validate user-provided input (#1425)
|
|
61
|
+
const vType = validateIdentifier(input.type, 'type');
|
|
62
|
+
if (!vType.valid)
|
|
63
|
+
return { success: false, error: vType.error };
|
|
64
|
+
const vDesc = validateText(input.description, 'description');
|
|
65
|
+
if (!vDesc.valid)
|
|
66
|
+
return { success: false, error: vDesc.error };
|
|
59
67
|
const store = loadTaskStore();
|
|
60
68
|
const taskId = `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
61
69
|
const task = {
|
|
@@ -97,6 +105,10 @@ export const taskTools = [
|
|
|
97
105
|
required: ['taskId'],
|
|
98
106
|
},
|
|
99
107
|
handler: async (input) => {
|
|
108
|
+
// Validate user-provided input (#1425)
|
|
109
|
+
const vId = validateIdentifier(input.taskId, 'taskId');
|
|
110
|
+
if (!vId.valid)
|
|
111
|
+
return { success: false, error: vId.error };
|
|
100
112
|
const store = loadTaskStore();
|
|
101
113
|
const taskId = input.taskId;
|
|
102
114
|
const task = store.tasks[taskId];
|
|
@@ -194,6 +206,10 @@ export const taskTools = [
|
|
|
194
206
|
required: ['taskId'],
|
|
195
207
|
},
|
|
196
208
|
handler: async (input) => {
|
|
209
|
+
// Validate user-provided input (#1425)
|
|
210
|
+
const vId = validateIdentifier(input.taskId, 'taskId');
|
|
211
|
+
if (!vId.valid)
|
|
212
|
+
return { success: false, error: vId.error };
|
|
197
213
|
const store = loadTaskStore();
|
|
198
214
|
const taskId = input.taskId;
|
|
199
215
|
const task = store.tasks[taskId];
|
|
@@ -254,6 +270,10 @@ export const taskTools = [
|
|
|
254
270
|
required: ['taskId'],
|
|
255
271
|
},
|
|
256
272
|
handler: async (input) => {
|
|
273
|
+
// Validate user-provided input (#1425)
|
|
274
|
+
const vId = validateIdentifier(input.taskId, 'taskId');
|
|
275
|
+
if (!vId.valid)
|
|
276
|
+
return { success: false, error: vId.error };
|
|
257
277
|
const store = loadTaskStore();
|
|
258
278
|
const taskId = input.taskId;
|
|
259
279
|
const task = store.tasks[taskId];
|
|
@@ -301,6 +321,10 @@ export const taskTools = [
|
|
|
301
321
|
required: ['taskId'],
|
|
302
322
|
},
|
|
303
323
|
handler: async (input) => {
|
|
324
|
+
// Validate user-provided input (#1425)
|
|
325
|
+
const vId = validateIdentifier(input.taskId, 'taskId');
|
|
326
|
+
if (!vId.valid)
|
|
327
|
+
return { success: false, error: vId.error };
|
|
304
328
|
const store = loadTaskStore();
|
|
305
329
|
const taskId = input.taskId;
|
|
306
330
|
const task = store.tasks[taskId];
|
|
@@ -380,6 +404,15 @@ export const taskTools = [
|
|
|
380
404
|
required: ['taskId'],
|
|
381
405
|
},
|
|
382
406
|
handler: async (input) => {
|
|
407
|
+
// Validate user-provided input (#1425)
|
|
408
|
+
const vId = validateIdentifier(input.taskId, 'taskId');
|
|
409
|
+
if (!vId.valid)
|
|
410
|
+
return { success: false, error: vId.error };
|
|
411
|
+
if (input.reason) {
|
|
412
|
+
const v = validateText(input.reason, 'reason');
|
|
413
|
+
if (!v.valid)
|
|
414
|
+
return { success: false, error: v.error };
|
|
415
|
+
}
|
|
383
416
|
const store = loadTaskStore();
|
|
384
417
|
const taskId = input.taskId;
|
|
385
418
|
const task = store.tasks[taskId];
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { getProjectCwd } from './types.js';
|
|
7
7
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
8
|
+
import { validateIdentifier, validatePath, validateText } from './validate-input.js';
|
|
8
9
|
import { join } from 'node:path';
|
|
9
10
|
import { execSync } from 'node:child_process';
|
|
10
11
|
// Storage paths
|
|
@@ -53,6 +54,17 @@ export const terminalTools = [
|
|
|
53
54
|
},
|
|
54
55
|
},
|
|
55
56
|
handler: async (input) => {
|
|
57
|
+
// Validate user-provided input (#1425)
|
|
58
|
+
if (input.name) {
|
|
59
|
+
const v = validateText(input.name, 'name', 256);
|
|
60
|
+
if (!v.valid)
|
|
61
|
+
return { success: false, error: v.error };
|
|
62
|
+
}
|
|
63
|
+
if (input.workingDir) {
|
|
64
|
+
const v = validatePath(input.workingDir, 'workingDir');
|
|
65
|
+
if (!v.valid)
|
|
66
|
+
return { success: false, error: v.error };
|
|
67
|
+
}
|
|
56
68
|
const store = loadTerminalStore();
|
|
57
69
|
const id = `term-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
58
70
|
const session = {
|
|
@@ -92,6 +104,15 @@ export const terminalTools = [
|
|
|
92
104
|
required: ['command'],
|
|
93
105
|
},
|
|
94
106
|
handler: async (input) => {
|
|
107
|
+
// Validate user-provided input (#1425)
|
|
108
|
+
const vCmd = validateText(input.command, 'command', 10_000);
|
|
109
|
+
if (!vCmd.valid)
|
|
110
|
+
return { success: false, error: vCmd.error };
|
|
111
|
+
if (input.sessionId) {
|
|
112
|
+
const v = validateIdentifier(input.sessionId, 'sessionId');
|
|
113
|
+
if (!v.valid)
|
|
114
|
+
return { success: false, error: v.error };
|
|
115
|
+
}
|
|
95
116
|
const store = loadTerminalStore();
|
|
96
117
|
const sessionId = input.sessionId;
|
|
97
118
|
const command = input.command;
|
|
@@ -201,6 +222,10 @@ export const terminalTools = [
|
|
|
201
222
|
required: ['sessionId'],
|
|
202
223
|
},
|
|
203
224
|
handler: async (input) => {
|
|
225
|
+
// Validate user-provided input (#1425)
|
|
226
|
+
const vId = validateIdentifier(input.sessionId, 'sessionId');
|
|
227
|
+
if (!vId.valid)
|
|
228
|
+
return { success: false, error: vId.error };
|
|
204
229
|
const store = loadTerminalStore();
|
|
205
230
|
const sessionId = input.sessionId;
|
|
206
231
|
const session = store.sessions[sessionId];
|
|
@@ -229,6 +254,12 @@ export const terminalTools = [
|
|
|
229
254
|
},
|
|
230
255
|
},
|
|
231
256
|
handler: async (input) => {
|
|
257
|
+
// Validate user-provided input (#1425)
|
|
258
|
+
if (input.sessionId) {
|
|
259
|
+
const v = validateIdentifier(input.sessionId, 'sessionId');
|
|
260
|
+
if (!v.valid)
|
|
261
|
+
return { success: false, error: v.error };
|
|
262
|
+
}
|
|
232
263
|
const store = loadTerminalStore();
|
|
233
264
|
const sessionId = input.sessionId;
|
|
234
265
|
const limit = input.limit || 50;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { getProjectCwd } from './types.js';
|
|
9
|
+
import { validateIdentifier, validatePath, validateText } from './validate-input.js';
|
|
9
10
|
// Storage paths
|
|
10
11
|
const STORAGE_DIR = '.claude-flow';
|
|
11
12
|
const WORKFLOW_DIR = 'workflows';
|
|
@@ -63,6 +64,22 @@ export const workflowTools = [
|
|
|
63
64
|
},
|
|
64
65
|
},
|
|
65
66
|
handler: async (input) => {
|
|
67
|
+
// Validate user-provided input (#1425)
|
|
68
|
+
if (input.template) {
|
|
69
|
+
const v = validateIdentifier(input.template, 'template');
|
|
70
|
+
if (!v.valid)
|
|
71
|
+
return { success: false, error: v.error };
|
|
72
|
+
}
|
|
73
|
+
if (input.file) {
|
|
74
|
+
const v = validatePath(input.file, 'file');
|
|
75
|
+
if (!v.valid)
|
|
76
|
+
return { success: false, error: v.error };
|
|
77
|
+
}
|
|
78
|
+
if (input.task) {
|
|
79
|
+
const v = validateText(input.task, 'task');
|
|
80
|
+
if (!v.valid)
|
|
81
|
+
return { success: false, error: v.error };
|
|
82
|
+
}
|
|
66
83
|
const store = loadWorkflowStore();
|
|
67
84
|
const template = input.template;
|
|
68
85
|
const task = input.task;
|
|
@@ -157,6 +174,15 @@ export const workflowTools = [
|
|
|
157
174
|
required: ['name'],
|
|
158
175
|
},
|
|
159
176
|
handler: async (input) => {
|
|
177
|
+
// Validate user-provided input (#1425)
|
|
178
|
+
const vName = validateText(input.name, 'name', 256);
|
|
179
|
+
if (!vName.valid)
|
|
180
|
+
return { success: false, error: vName.error };
|
|
181
|
+
if (input.description) {
|
|
182
|
+
const v = validateText(input.description, 'description');
|
|
183
|
+
if (!v.valid)
|
|
184
|
+
return { success: false, error: v.error };
|
|
185
|
+
}
|
|
160
186
|
const store = loadWorkflowStore();
|
|
161
187
|
const workflowId = `workflow-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
162
188
|
const steps = (input.steps || []).map((s, i) => ({
|
|
@@ -201,6 +227,10 @@ export const workflowTools = [
|
|
|
201
227
|
required: ['workflowId'],
|
|
202
228
|
},
|
|
203
229
|
handler: async (input) => {
|
|
230
|
+
// Validate user-provided input (#1425)
|
|
231
|
+
const vId = validateIdentifier(input.workflowId, 'workflowId');
|
|
232
|
+
if (!vId.valid)
|
|
233
|
+
return { success: false, error: vId.error };
|
|
204
234
|
const store = loadWorkflowStore();
|
|
205
235
|
const workflowId = input.workflowId;
|
|
206
236
|
const workflow = store.workflows[workflowId];
|
|
@@ -252,6 +282,10 @@ export const workflowTools = [
|
|
|
252
282
|
required: ['workflowId'],
|
|
253
283
|
},
|
|
254
284
|
handler: async (input) => {
|
|
285
|
+
// Validate user-provided input (#1425)
|
|
286
|
+
const vId = validateIdentifier(input.workflowId, 'workflowId');
|
|
287
|
+
if (!vId.valid)
|
|
288
|
+
return { success: false, error: vId.error };
|
|
255
289
|
const store = loadWorkflowStore();
|
|
256
290
|
const workflowId = input.workflowId;
|
|
257
291
|
const workflow = store.workflows[workflowId];
|
|
@@ -303,6 +337,12 @@ export const workflowTools = [
|
|
|
303
337
|
},
|
|
304
338
|
},
|
|
305
339
|
handler: async (input) => {
|
|
340
|
+
// Validate user-provided input (#1425)
|
|
341
|
+
if (input.status) {
|
|
342
|
+
const v = validateIdentifier(input.status, 'status');
|
|
343
|
+
if (!v.valid)
|
|
344
|
+
return { success: false, error: v.error };
|
|
345
|
+
}
|
|
306
346
|
const store = loadWorkflowStore();
|
|
307
347
|
let workflows = Object.values(store.workflows);
|
|
308
348
|
// Apply filters
|
|
@@ -340,6 +380,10 @@ export const workflowTools = [
|
|
|
340
380
|
required: ['workflowId'],
|
|
341
381
|
},
|
|
342
382
|
handler: async (input) => {
|
|
383
|
+
// Validate user-provided input (#1425)
|
|
384
|
+
const vId = validateIdentifier(input.workflowId, 'workflowId');
|
|
385
|
+
if (!vId.valid)
|
|
386
|
+
return { success: false, error: vId.error };
|
|
343
387
|
const store = loadWorkflowStore();
|
|
344
388
|
const workflowId = input.workflowId;
|
|
345
389
|
const workflow = store.workflows[workflowId];
|
|
@@ -371,6 +415,10 @@ export const workflowTools = [
|
|
|
371
415
|
required: ['workflowId'],
|
|
372
416
|
},
|
|
373
417
|
handler: async (input) => {
|
|
418
|
+
// Validate user-provided input (#1425)
|
|
419
|
+
const vId = validateIdentifier(input.workflowId, 'workflowId');
|
|
420
|
+
if (!vId.valid)
|
|
421
|
+
return { success: false, error: vId.error };
|
|
374
422
|
const store = loadWorkflowStore();
|
|
375
423
|
const workflowId = input.workflowId;
|
|
376
424
|
const workflow = store.workflows[workflowId];
|
|
@@ -413,6 +461,15 @@ export const workflowTools = [
|
|
|
413
461
|
required: ['workflowId'],
|
|
414
462
|
},
|
|
415
463
|
handler: async (input) => {
|
|
464
|
+
// Validate user-provided input (#1425)
|
|
465
|
+
const vId = validateIdentifier(input.workflowId, 'workflowId');
|
|
466
|
+
if (!vId.valid)
|
|
467
|
+
return { success: false, error: vId.error };
|
|
468
|
+
if (input.reason) {
|
|
469
|
+
const v = validateText(input.reason, 'reason');
|
|
470
|
+
if (!v.valid)
|
|
471
|
+
return { success: false, error: v.error };
|
|
472
|
+
}
|
|
416
473
|
const store = loadWorkflowStore();
|
|
417
474
|
const workflowId = input.workflowId;
|
|
418
475
|
const workflow = store.workflows[workflowId];
|
|
@@ -451,6 +508,10 @@ export const workflowTools = [
|
|
|
451
508
|
required: ['workflowId'],
|
|
452
509
|
},
|
|
453
510
|
handler: async (input) => {
|
|
511
|
+
// Validate user-provided input (#1425)
|
|
512
|
+
const vId = validateIdentifier(input.workflowId, 'workflowId');
|
|
513
|
+
if (!vId.valid)
|
|
514
|
+
return { success: false, error: vId.error };
|
|
454
515
|
const store = loadWorkflowStore();
|
|
455
516
|
const workflowId = input.workflowId;
|
|
456
517
|
if (!store.workflows[workflowId]) {
|
|
@@ -485,6 +546,27 @@ export const workflowTools = [
|
|
|
485
546
|
required: ['action'],
|
|
486
547
|
},
|
|
487
548
|
handler: async (input) => {
|
|
549
|
+
// Validate user-provided input (#1425)
|
|
550
|
+
if (input.workflowId) {
|
|
551
|
+
const v = validateIdentifier(input.workflowId, 'workflowId');
|
|
552
|
+
if (!v.valid)
|
|
553
|
+
return { success: false, error: v.error };
|
|
554
|
+
}
|
|
555
|
+
if (input.templateId) {
|
|
556
|
+
const v = validateIdentifier(input.templateId, 'templateId');
|
|
557
|
+
if (!v.valid)
|
|
558
|
+
return { success: false, error: v.error };
|
|
559
|
+
}
|
|
560
|
+
if (input.templateName) {
|
|
561
|
+
const v = validateText(input.templateName, 'templateName', 256);
|
|
562
|
+
if (!v.valid)
|
|
563
|
+
return { success: false, error: v.error };
|
|
564
|
+
}
|
|
565
|
+
if (input.newName) {
|
|
566
|
+
const v = validateText(input.newName, 'newName', 256);
|
|
567
|
+
if (!v.valid)
|
|
568
|
+
return { success: false, error: v.error };
|
|
569
|
+
}
|
|
488
570
|
const store = loadWorkflowStore();
|
|
489
571
|
const action = input.action;
|
|
490
572
|
if (action === 'save') {
|
|
@@ -157,7 +157,9 @@ declare class LocalReasoningBank {
|
|
|
157
157
|
persistence?: boolean;
|
|
158
158
|
});
|
|
159
159
|
/**
|
|
160
|
-
* Load patterns from disk
|
|
160
|
+
* Load patterns from disk, deduplicating by content.
|
|
161
|
+
* When multiple patterns share identical content, keeps the one with
|
|
162
|
+
* highest confidence (ties broken by most recent lastUsedAt).
|
|
161
163
|
*/
|
|
162
164
|
private loadFromDisk;
|
|
163
165
|
/**
|
|
@@ -170,6 +172,9 @@ declare class LocalReasoningBank {
|
|
|
170
172
|
flushToDisk(): void;
|
|
171
173
|
/**
|
|
172
174
|
* Store a pattern - O(1)
|
|
175
|
+
* Deduplicates by content: if a pattern with the same content already
|
|
176
|
+
* exists, the existing entry is updated (bumped usageCount, higher
|
|
177
|
+
* confidence wins, refreshed lastUsedAt) instead of adding a duplicate.
|
|
173
178
|
*/
|
|
174
179
|
store(pattern: Omit<StoredPattern, 'usageCount' | 'createdAt' | 'lastUsedAt'> & Partial<StoredPattern>): void;
|
|
175
180
|
/**
|
|
@@ -327,7 +327,9 @@ class LocalReasoningBank {
|
|
|
327
327
|
}
|
|
328
328
|
}
|
|
329
329
|
/**
|
|
330
|
-
* Load patterns from disk
|
|
330
|
+
* Load patterns from disk, deduplicating by content.
|
|
331
|
+
* When multiple patterns share identical content, keeps the one with
|
|
332
|
+
* highest confidence (ties broken by most recent lastUsedAt).
|
|
331
333
|
*/
|
|
332
334
|
loadFromDisk() {
|
|
333
335
|
try {
|
|
@@ -335,10 +337,41 @@ class LocalReasoningBank {
|
|
|
335
337
|
if (existsSync(path)) {
|
|
336
338
|
const data = JSON.parse(readFileSync(path, 'utf-8'));
|
|
337
339
|
if (Array.isArray(data)) {
|
|
340
|
+
const totalLoaded = data.length;
|
|
341
|
+
// Group by content to deduplicate
|
|
342
|
+
const byContent = new Map();
|
|
338
343
|
for (const pattern of data) {
|
|
344
|
+
const key = pattern.content;
|
|
345
|
+
const existing = byContent.get(key);
|
|
346
|
+
if (!existing) {
|
|
347
|
+
byContent.set(key, pattern);
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
// Keep the one with higher confidence; break ties by lastUsedAt
|
|
351
|
+
if (pattern.confidence > existing.confidence ||
|
|
352
|
+
(pattern.confidence === existing.confidence &&
|
|
353
|
+
(pattern.lastUsedAt ?? 0) > (existing.lastUsedAt ?? 0))) {
|
|
354
|
+
// Merge: adopt the higher usageCount sum
|
|
355
|
+
pattern.usageCount = (pattern.usageCount ?? 0) + (existing.usageCount ?? 0);
|
|
356
|
+
byContent.set(key, pattern);
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
existing.usageCount = (existing.usageCount ?? 0) + (pattern.usageCount ?? 0);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// Populate the bank from deduplicated entries
|
|
364
|
+
for (const pattern of byContent.values()) {
|
|
339
365
|
this.patterns.set(pattern.id, pattern);
|
|
340
366
|
this.patternList.push(pattern);
|
|
341
367
|
}
|
|
368
|
+
const removed = totalLoaded - byContent.size;
|
|
369
|
+
if (removed > 0) {
|
|
370
|
+
console.log(`Deduplicated ${removed} patterns (${byContent.size} unique)`);
|
|
371
|
+
// Persist the compacted set immediately so the file shrinks on disk
|
|
372
|
+
this.dirty = true;
|
|
373
|
+
this.flushToDisk();
|
|
374
|
+
}
|
|
342
375
|
}
|
|
343
376
|
}
|
|
344
377
|
}
|
|
@@ -380,6 +413,9 @@ class LocalReasoningBank {
|
|
|
380
413
|
}
|
|
381
414
|
/**
|
|
382
415
|
* Store a pattern - O(1)
|
|
416
|
+
* Deduplicates by content: if a pattern with the same content already
|
|
417
|
+
* exists, the existing entry is updated (bumped usageCount, higher
|
|
418
|
+
* confidence wins, refreshed lastUsedAt) instead of adding a duplicate.
|
|
383
419
|
*/
|
|
384
420
|
store(pattern) {
|
|
385
421
|
const now = Date.now();
|
|
@@ -401,6 +437,20 @@ class LocalReasoningBank {
|
|
|
401
437
|
}
|
|
402
438
|
}
|
|
403
439
|
else {
|
|
440
|
+
// Check for content-duplicate before inserting a new entry
|
|
441
|
+
const contentDupe = this.patternList.find(p => p.content === pattern.content);
|
|
442
|
+
if (contentDupe) {
|
|
443
|
+
// Merge into the existing pattern instead of adding a duplicate
|
|
444
|
+
contentDupe.usageCount++;
|
|
445
|
+
contentDupe.lastUsedAt = now;
|
|
446
|
+
if (stored.confidence > contentDupe.confidence) {
|
|
447
|
+
contentDupe.confidence = stored.confidence;
|
|
448
|
+
}
|
|
449
|
+
// Keep the Map in sync with the mutated object
|
|
450
|
+
this.patterns.set(contentDupe.id, contentDupe);
|
|
451
|
+
this.saveToDisk();
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
404
454
|
// Evict oldest if at capacity
|
|
405
455
|
if (this.patterns.size >= this.maxSize) {
|
|
406
456
|
const oldest = this.patternList.shift();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.70",
|
|
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",
|