claude-flow 3.57.1 → 3.58.0
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/intelligence.cjs +3 -5
- package/.claude/proven-config.json +1 -1
- package/.claude-plugin/marketplace.json +11 -0
- package/README.md +1 -0
- package/node_modules/@claude-flow/codex/README.md +87 -9
- package/node_modules/@claude-flow/codex/dist/activity/cli.d.ts +3 -0
- package/node_modules/@claude-flow/codex/dist/activity/cli.d.ts.map +1 -0
- package/node_modules/@claude-flow/codex/dist/activity/cli.js +115 -0
- package/node_modules/@claude-flow/codex/dist/activity/cli.js.map +1 -0
- package/node_modules/@claude-flow/codex/dist/activity/files.d.ts +14 -0
- package/node_modules/@claude-flow/codex/dist/activity/files.d.ts.map +1 -0
- package/node_modules/@claude-flow/codex/dist/activity/files.js +142 -0
- package/node_modules/@claude-flow/codex/dist/activity/files.js.map +1 -0
- package/node_modules/@claude-flow/codex/dist/activity/parser.d.ts +21 -0
- package/node_modules/@claude-flow/codex/dist/activity/parser.d.ts.map +1 -0
- package/node_modules/@claude-flow/codex/dist/activity/parser.js +179 -0
- package/node_modules/@claude-flow/codex/dist/activity/parser.js.map +1 -0
- package/node_modules/@claude-flow/codex/dist/activity/reader.d.ts +20 -0
- package/node_modules/@claude-flow/codex/dist/activity/reader.d.ts.map +1 -0
- package/node_modules/@claude-flow/codex/dist/activity/reader.js +106 -0
- package/node_modules/@claude-flow/codex/dist/activity/reader.js.map +1 -0
- package/node_modules/@claude-flow/codex/dist/activity/sanitize.d.ts +4 -0
- package/node_modules/@claude-flow/codex/dist/activity/sanitize.d.ts.map +1 -0
- package/node_modules/@claude-flow/codex/dist/activity/sanitize.js +27 -0
- package/node_modules/@claude-flow/codex/dist/activity/sanitize.js.map +1 -0
- package/node_modules/@claude-flow/codex/dist/activity/types.d.ts +31 -0
- package/node_modules/@claude-flow/codex/dist/activity/types.d.ts.map +1 -0
- package/node_modules/@claude-flow/codex/dist/activity/types.js +5 -0
- package/node_modules/@claude-flow/codex/dist/activity/types.js.map +1 -0
- package/node_modules/@claude-flow/codex/dist/activity/viewer.d.ts +21 -0
- package/node_modules/@claude-flow/codex/dist/activity/viewer.d.ts.map +1 -0
- package/node_modules/@claude-flow/codex/dist/activity/viewer.js +168 -0
- package/node_modules/@claude-flow/codex/dist/activity/viewer.js.map +1 -0
- package/node_modules/@claude-flow/codex/dist/cli.js +3 -0
- package/node_modules/@claude-flow/codex/dist/cli.js.map +1 -1
- package/node_modules/@claude-flow/codex/dist/dual-mode/cli.js +3 -3
- package/node_modules/@claude-flow/codex/dist/dual-mode/cli.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/server.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/server.js +5 -3
- package/node_modules/@claude-flow/mcp/dist/server.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/types.d.ts +11 -0
- package/node_modules/@claude-flow/mcp/dist/types.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/types.js.map +1 -1
- package/package.json +3 -2
- package/v3/@claude-flow/cli/README.md +1 -0
- package/v3/@claude-flow/cli/catalog-manifest.json +2 -2
- package/v3/@claude-flow/cli/dist/src/commands/agent.js +4 -1
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +20 -1
- package/v3/@claude-flow/cli/dist/src/commands/mcp.js +66 -6
- package/v3/@claude-flow/cli/dist/src/commands/memory.js +78 -3
- package/v3/@claude-flow/cli/dist/src/commands/workflow.js +55 -15
- package/v3/@claude-flow/cli/dist/src/init/executor.js +9 -5
- package/v3/@claude-flow/cli/dist/src/init/helper-companions.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/init/helper-companions.js +4 -3
- package/v3/@claude-flow/cli/dist/src/mcp-server.d.ts +5 -0
- package/v3/@claude-flow/cli/dist/src/mcp-server.js +39 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-tools.js +28 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/claims-tools.js +25 -15
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +9 -3
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +58 -9
- package/v3/@claude-flow/cli/dist/src/mcp-tools/workflow-tools.js +2 -1
- package/v3/@claude-flow/cli/dist/src/memory/graph-edge-writer.d.ts +16 -4
- package/v3/@claude-flow/cli/dist/src/memory/graph-edge-writer.js +19 -7
- package/v3/@claude-flow/cli/dist/src/memory/intelligence.js +4 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +55 -9
- package/v3/@claude-flow/cli/dist/src/memory/memory-peek.d.ts +28 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-peek.js +222 -0
- package/v3/@claude-flow/cli/dist/src/memory/model-cache-dir.d.ts +11 -0
- package/v3/@claude-flow/cli/dist/src/memory/model-cache-dir.js +17 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/task-embedder.js +38 -4
- package/v3/@claude-flow/cli/dist/src/services/memory-backup.d.ts +6 -0
- package/v3/@claude-flow/cli/dist/src/services/memory-backup.js +109 -81
- package/v3/@claude-flow/cli/dist/src/services/policy-ledger-anchor.d.ts +2 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-ledger-anchor.js +4 -4
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.js +54 -3
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +4 -0
- package/v3/@claude-flow/cli/package.json +5 -4
|
@@ -2333,6 +2333,19 @@ const workerListCommand = {
|
|
|
2333
2333
|
}
|
|
2334
2334
|
}
|
|
2335
2335
|
};
|
|
2336
|
+
// Render the status hooks_worker-dispatch actually returned. Only a status that
|
|
2337
|
+
// means work finished may read as "completed" (#4196).
|
|
2338
|
+
function dispatchStatusLabel(status) {
|
|
2339
|
+
switch (status) {
|
|
2340
|
+
case 'dispatched': return output.highlight('dispatched (background)');
|
|
2341
|
+
case 'queued': return output.highlight('queued (daemon)');
|
|
2342
|
+
case 'no-daemon': return output.warning('no-daemon (not run)');
|
|
2343
|
+
case 'mcp-only': return output.warning('mcp-only (not queued)');
|
|
2344
|
+
case 'synthetic-completed': return output.warning('recorded (no work executed)');
|
|
2345
|
+
case 'completed': return output.success('completed');
|
|
2346
|
+
default: return output.dim(String(status));
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2336
2349
|
const workerDispatchCommand = {
|
|
2337
2350
|
name: 'dispatch',
|
|
2338
2351
|
description: 'Dispatch a background worker for analysis/optimization',
|
|
@@ -2386,9 +2399,15 @@ const workerDispatchCommand = {
|
|
|
2386
2399
|
{ field: 'Description', value: result.config.description },
|
|
2387
2400
|
{ field: 'Est. Duration', value: result.config.estimatedDuration },
|
|
2388
2401
|
{ field: 'Capabilities', value: result.config.capabilities.join(', ') },
|
|
2389
|
-
{ field: 'Status', value: result.status
|
|
2402
|
+
{ field: 'Status', value: dispatchStatusLabel(result.status) },
|
|
2390
2403
|
],
|
|
2391
2404
|
});
|
|
2405
|
+
// The tool says why it could not run the worker (e.g. no daemon); show it
|
|
2406
|
+
// rather than letting a recorded-only dispatch read as finished work (#4196).
|
|
2407
|
+
if (result.note) {
|
|
2408
|
+
output.writeln();
|
|
2409
|
+
output.printWarning(result.note);
|
|
2410
|
+
}
|
|
2392
2411
|
if (background) {
|
|
2393
2412
|
output.writeln();
|
|
2394
2413
|
output.writeln(output.dim(`Check status: claude-flow hooks worker status --id ${result.workerId}`));
|
|
@@ -34,6 +34,14 @@ function formatUptime(seconds) {
|
|
|
34
34
|
const mins = Math.floor((seconds % 3600) / 60);
|
|
35
35
|
return `${hours}h ${mins}m`;
|
|
36
36
|
}
|
|
37
|
+
function readIntegerOption(flags, name, envName) {
|
|
38
|
+
const camelName = name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
39
|
+
const input = flags[camelName] ?? flags[name] ?? process.env[envName];
|
|
40
|
+
return input === undefined ? undefined : Number(input);
|
|
41
|
+
}
|
|
42
|
+
function isOutOfRange(value, min, max) {
|
|
43
|
+
return value !== undefined && (!Number.isSafeInteger(value) || value < min || value > max);
|
|
44
|
+
}
|
|
37
45
|
// Start MCP server
|
|
38
46
|
const startCommand = {
|
|
39
47
|
name: 'start',
|
|
@@ -71,6 +79,31 @@ const startCommand = {
|
|
|
71
79
|
description: 'HTTP request timeout in milliseconds (default 30000; env RUFLO_MCP_REQUEST_TIMEOUT_MS)',
|
|
72
80
|
type: 'number'
|
|
73
81
|
},
|
|
82
|
+
{
|
|
83
|
+
name: 'rate-limit-per-ip',
|
|
84
|
+
description: 'HTTP requests allowed per client IP per window (env RUFLO_MCP_RATE_LIMIT_PER_IP)',
|
|
85
|
+
type: 'number'
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'rate-limit-window-ms',
|
|
89
|
+
description: 'HTTP rate limit window in milliseconds (env RUFLO_MCP_RATE_LIMIT_WINDOW_MS)',
|
|
90
|
+
type: 'number'
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'rate-limit-per-session',
|
|
94
|
+
description: 'Request burst allowed per MCP session, refilled over 10s; also bounded by the server-wide bucket (env RUFLO_MCP_RATE_LIMIT_PER_SESSION)',
|
|
95
|
+
type: 'number'
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: 'rate-limit-global-rps',
|
|
99
|
+
description: 'Server-wide requests per second across all sessions (env RUFLO_MCP_RATE_LIMIT_GLOBAL_RPS)',
|
|
100
|
+
type: 'number'
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'rate-limit-global-burst',
|
|
104
|
+
description: 'Server-wide request burst across all sessions (env RUFLO_MCP_RATE_LIMIT_GLOBAL_BURST)',
|
|
105
|
+
type: 'number'
|
|
106
|
+
},
|
|
74
107
|
{
|
|
75
108
|
name: 'auth-token-file',
|
|
76
109
|
description: 'Read the HTTP bearer token from this file (http transport). Preferred over --auth-token; env RUFLO_MCP_HTTP_TOKEN also works',
|
|
@@ -100,6 +133,7 @@ const startCommand = {
|
|
|
100
133
|
{ command: 'claude-flow mcp start', description: 'Start with defaults (stdio)' },
|
|
101
134
|
{ command: 'claude-flow mcp start -p 8080 -t http', description: 'Start HTTP server' },
|
|
102
135
|
{ command: 'claude-flow mcp start -t http --request-timeout-ms 120000', description: 'Allow slower cold-start HTTP tools' },
|
|
136
|
+
{ command: 'claude-flow mcp start -t http --rate-limit-per-ip 600 --rate-limit-per-session 500 --rate-limit-global-burst 1000', description: 'Raise rate limits for a shared control plane' },
|
|
103
137
|
{ command: 'RUFLO_MCP_HTTP_TOKEN=<16+ chars> claude-flow mcp start -t http', description: 'HTTP server requiring "Authorization: Bearer <token>" on every request (except GET /health)' },
|
|
104
138
|
{ command: 'RUFLO_MCP_HTTP_TOKEN=<16+ chars> claude-flow mcp start -t http --host 0.0.0.0', description: 'Non-loopback bind: allowed only with a token (or RUFLO_MCP_ALLOW_UNAUTHENTICATED_HTTP=1)' },
|
|
105
139
|
{ command: 'claude-flow mcp start -d', description: 'Start as daemon' },
|
|
@@ -112,15 +146,36 @@ const startCommand = {
|
|
|
112
146
|
const tools = ctx.flags.tools
|
|
113
147
|
|| process.env.CLAUDE_FLOW_MCP_TOOLS
|
|
114
148
|
|| 'all';
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
?? process.env.RUFLO_MCP_REQUEST_TIMEOUT_MS;
|
|
118
|
-
const requestTimeoutMs = timeoutInput === undefined ? undefined : Number(timeoutInput);
|
|
119
|
-
if (requestTimeoutMs !== undefined &&
|
|
120
|
-
(!Number.isSafeInteger(requestTimeoutMs) || requestTimeoutMs < 1 || requestTimeoutMs > 3_600_000)) {
|
|
149
|
+
const requestTimeoutMs = readIntegerOption(ctx.flags, 'request-timeout-ms', 'RUFLO_MCP_REQUEST_TIMEOUT_MS');
|
|
150
|
+
if (isOutOfRange(requestTimeoutMs, 1, 3_600_000)) {
|
|
121
151
|
output.printError('Request timeout must be an integer from 1 to 3600000 milliseconds');
|
|
122
152
|
return { success: false, exitCode: 1 };
|
|
123
153
|
}
|
|
154
|
+
const rateLimitPerIp = readIntegerOption(ctx.flags, 'rate-limit-per-ip', 'RUFLO_MCP_RATE_LIMIT_PER_IP');
|
|
155
|
+
if (isOutOfRange(rateLimitPerIp, 1, 100_000)) {
|
|
156
|
+
output.printError('HTTP rate limit must be an integer from 1 to 100000 requests per window');
|
|
157
|
+
return { success: false, exitCode: 1 };
|
|
158
|
+
}
|
|
159
|
+
const rateLimitWindowMs = readIntegerOption(ctx.flags, 'rate-limit-window-ms', 'RUFLO_MCP_RATE_LIMIT_WINDOW_MS');
|
|
160
|
+
if (isOutOfRange(rateLimitWindowMs, 1000, 3_600_000)) {
|
|
161
|
+
output.printError('Rate limit window must be an integer from 1000 to 3600000 milliseconds');
|
|
162
|
+
return { success: false, exitCode: 1 };
|
|
163
|
+
}
|
|
164
|
+
const rateLimitPerSession = readIntegerOption(ctx.flags, 'rate-limit-per-session', 'RUFLO_MCP_RATE_LIMIT_PER_SESSION');
|
|
165
|
+
if (isOutOfRange(rateLimitPerSession, 1, 10_000)) {
|
|
166
|
+
output.printError('Session rate limit must be an integer from 1 to 10000 requests');
|
|
167
|
+
return { success: false, exitCode: 1 };
|
|
168
|
+
}
|
|
169
|
+
const rateLimitGlobalRps = readIntegerOption(ctx.flags, 'rate-limit-global-rps', 'RUFLO_MCP_RATE_LIMIT_GLOBAL_RPS');
|
|
170
|
+
if (isOutOfRange(rateLimitGlobalRps, 1, 10_000)) {
|
|
171
|
+
output.printError('Global rate limit must be an integer from 1 to 10000 requests per second');
|
|
172
|
+
return { success: false, exitCode: 1 };
|
|
173
|
+
}
|
|
174
|
+
const rateLimitGlobalBurst = readIntegerOption(ctx.flags, 'rate-limit-global-burst', 'RUFLO_MCP_RATE_LIMIT_GLOBAL_BURST');
|
|
175
|
+
if (isOutOfRange(rateLimitGlobalBurst, 1, 20_000)) {
|
|
176
|
+
output.printError('Global rate limit burst must be an integer from 1 to 20000 requests');
|
|
177
|
+
return { success: false, exitCode: 1 };
|
|
178
|
+
}
|
|
124
179
|
let authToken;
|
|
125
180
|
try {
|
|
126
181
|
authToken = resolveMcpHttpAuthToken({
|
|
@@ -189,6 +244,11 @@ const startCommand = {
|
|
|
189
244
|
port,
|
|
190
245
|
tools: !tools || tools === 'all' ? 'all' : tools.split(','),
|
|
191
246
|
...(requestTimeoutMs === undefined ? {} : { requestTimeoutMs }),
|
|
247
|
+
...(rateLimitPerIp === undefined ? {} : { rateLimitPerIp }),
|
|
248
|
+
...(rateLimitWindowMs === undefined ? {} : { rateLimitWindowMs }),
|
|
249
|
+
...(rateLimitPerSession === undefined ? {} : { rateLimitPerSession }),
|
|
250
|
+
...(rateLimitGlobalRps === undefined ? {} : { rateLimitGlobalRps }),
|
|
251
|
+
...(rateLimitGlobalBurst === undefined ? {} : { rateLimitGlobalBurst }),
|
|
192
252
|
...(authToken ? { authToken } : {}),
|
|
193
253
|
daemonize: daemon,
|
|
194
254
|
};
|
|
@@ -16,6 +16,7 @@ import { existsSync } from 'node:fs';
|
|
|
16
16
|
import { siblingAgentDbPath } from '../memory/memory-bridge.js';
|
|
17
17
|
import { validateIdentifier } from '../mcp-tools/validate-input.js';
|
|
18
18
|
import { memoryKeyError } from '../mcp-tools/memory-tools.js';
|
|
19
|
+
import { memoryPeek } from '../memory/memory-peek.js';
|
|
19
20
|
/**
|
|
20
21
|
* #3228: a miss in one store is not a miss in the memory.
|
|
21
22
|
*
|
|
@@ -659,7 +660,9 @@ const searchCommand = {
|
|
|
659
660
|
});
|
|
660
661
|
return {
|
|
661
662
|
results: r.results.map(e => ({
|
|
662
|
-
|
|
663
|
+
// #2271: searchEntries() ids are truncated to 12 chars and collide
|
|
664
|
+
// for entries written within ~2.8h; SmartRetrieval dedupes on id.
|
|
665
|
+
id: JSON.stringify([e.namespace, e.key]),
|
|
663
666
|
key: e.key,
|
|
664
667
|
content: e.content,
|
|
665
668
|
score: e.score,
|
|
@@ -1221,6 +1224,54 @@ const statsCommand = {
|
|
|
1221
1224
|
}
|
|
1222
1225
|
}
|
|
1223
1226
|
};
|
|
1227
|
+
// #3508: read-only route/peek — which store files this cwd resolves to,
|
|
1228
|
+
// their row counts and last write, without creating or initializing
|
|
1229
|
+
// anything. Unlike `stats`, this never calls into the MCP tool layer (no
|
|
1230
|
+
// `authorizeMcpTool` receipt, no `ensureInitialized()`): it reads both
|
|
1231
|
+
// files directly, in-process, read-only.
|
|
1232
|
+
const peekCommand = {
|
|
1233
|
+
name: 'peek',
|
|
1234
|
+
description: 'Report which memory store files this cwd resolves to, their row counts and last write — read-only, creates nothing',
|
|
1235
|
+
options: [DB_PATH_OPTION],
|
|
1236
|
+
action: async (ctx) => {
|
|
1237
|
+
try {
|
|
1238
|
+
const result = await memoryPeek({ path: ctx.flags.path });
|
|
1239
|
+
if (ctx.flags.format === 'json') {
|
|
1240
|
+
output.printJson(result);
|
|
1241
|
+
return { success: true, data: result };
|
|
1242
|
+
}
|
|
1243
|
+
output.writeln();
|
|
1244
|
+
output.writeln(output.bold('Memory Peek'));
|
|
1245
|
+
output.writeln();
|
|
1246
|
+
output.writeln(`cwd: ${result.cwd}`);
|
|
1247
|
+
output.writeln(`root: ${result.root.path} (source: ${result.root.source})`);
|
|
1248
|
+
output.writeln();
|
|
1249
|
+
output.printTable({
|
|
1250
|
+
columns: [
|
|
1251
|
+
{ key: 'role', header: 'Role', width: 6 },
|
|
1252
|
+
{ key: 'path', header: 'Path', width: 40 },
|
|
1253
|
+
{ key: 'exists', header: 'Exists', width: 8 },
|
|
1254
|
+
{ key: 'rows', header: 'Rows', width: 8, align: 'right' },
|
|
1255
|
+
{ key: 'lastWrite', header: 'Last Write', width: 24 },
|
|
1256
|
+
],
|
|
1257
|
+
data: result.stores.map((store) => ({
|
|
1258
|
+
role: store.role,
|
|
1259
|
+
path: store.path,
|
|
1260
|
+
exists: store.exists ? 'yes' : 'no',
|
|
1261
|
+
rows: store.encrypted ? 'encrypted' : (store.rows === null ? 'unknown' : String(store.rows)),
|
|
1262
|
+
lastWrite: store.lastWrite ?? 'N/A',
|
|
1263
|
+
})),
|
|
1264
|
+
});
|
|
1265
|
+
output.writeln();
|
|
1266
|
+
output.writeln(`Shared keys (same namespace+key in both stores): ${result.sharedKeys === null ? 'unknown' : result.sharedKeys}`);
|
|
1267
|
+
return { success: true, data: result };
|
|
1268
|
+
}
|
|
1269
|
+
catch (error) {
|
|
1270
|
+
output.printError(`Failed to peek: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
1271
|
+
return { success: false, exitCode: 1 };
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
};
|
|
1224
1275
|
// Configure command
|
|
1225
1276
|
const configureCommand = {
|
|
1226
1277
|
name: 'configure',
|
|
@@ -1592,6 +1643,13 @@ const exportCommand = {
|
|
|
1592
1643
|
namespace: ctx.flags.namespace,
|
|
1593
1644
|
includeVectors: ctx.flags.includeVectors ?? true,
|
|
1594
1645
|
});
|
|
1646
|
+
// memory_export reports failure as { error } rather than throwing; without
|
|
1647
|
+
// this check the command printed "Exported to undefined" and then crashed
|
|
1648
|
+
// reading result.exported.entries (#4204).
|
|
1649
|
+
if (!result || result.error || !result.exported) {
|
|
1650
|
+
output.printError(`Export error: ${result?.error ?? 'memory_export returned no export summary'}`);
|
|
1651
|
+
return { success: false, exitCode: 1 };
|
|
1652
|
+
}
|
|
1595
1653
|
output.printSuccess(`Exported to ${result.outputPath}`);
|
|
1596
1654
|
output.printList([
|
|
1597
1655
|
`Entries: ${result.exported.entries}`,
|
|
@@ -1657,6 +1715,21 @@ const importCommand = {
|
|
|
1657
1715
|
namespace: ctx.flags.namespace,
|
|
1658
1716
|
dbPath: resolveDbPath(ctx.flags.path),
|
|
1659
1717
|
});
|
|
1718
|
+
// memory_import reports a bad file as { error } rather than throwing, and
|
|
1719
|
+
// a partial write as { success: false, error, imported }. Without this the
|
|
1720
|
+
// command printed "Imported from undefined" and crashed on result.imported,
|
|
1721
|
+
// or reported success for an import that dropped entries.
|
|
1722
|
+
const failure = result?.error;
|
|
1723
|
+
if (!result || failure || result.success === false || !result.imported) {
|
|
1724
|
+
output.printError(`Import error: ${failure ?? 'memory_import returned no import summary'}`);
|
|
1725
|
+
if (result?.imported) {
|
|
1726
|
+
output.printList([
|
|
1727
|
+
`Entries imported: ${result.imported.entries}`,
|
|
1728
|
+
`Failed: ${result.failed ?? 'unknown'}`,
|
|
1729
|
+
]);
|
|
1730
|
+
}
|
|
1731
|
+
return { success: false, exitCode: 1 };
|
|
1732
|
+
}
|
|
1660
1733
|
output.printSuccess(`Imported from ${result.inputPath}`);
|
|
1661
1734
|
output.printList([
|
|
1662
1735
|
`Entries: ${result.imported.entries}`,
|
|
@@ -2014,12 +2087,13 @@ const classifyCommand = {
|
|
|
2014
2087
|
export const memoryCommand = {
|
|
2015
2088
|
name: 'memory',
|
|
2016
2089
|
description: 'Memory management commands',
|
|
2017
|
-
subcommands: [initMemoryCommand, storeCommand, retrieveCommand, searchCommand, listCommand, deleteCommand, purgeCommand, statsCommand, configureCommand, cleanupCommand, compressCommand, exportCommand, importCommand, distillCommand, backupCommand, classifyCommand, selectOperatorCommand],
|
|
2090
|
+
subcommands: [initMemoryCommand, storeCommand, retrieveCommand, searchCommand, listCommand, deleteCommand, purgeCommand, statsCommand, peekCommand, configureCommand, cleanupCommand, compressCommand, exportCommand, importCommand, distillCommand, backupCommand, classifyCommand, selectOperatorCommand],
|
|
2018
2091
|
options: [],
|
|
2019
2092
|
examples: [
|
|
2020
2093
|
{ command: 'claude-flow memory store -k "key" -v "value"', description: 'Store data' },
|
|
2021
2094
|
{ command: 'claude-flow memory search -q "auth patterns"', description: 'Search memory' },
|
|
2022
|
-
{ command: 'claude-flow memory stats', description: 'Show statistics' }
|
|
2095
|
+
{ command: 'claude-flow memory stats', description: 'Show statistics' },
|
|
2096
|
+
{ command: 'claude-flow memory peek', description: 'Read-only: which store files this cwd resolves to, rows, last write' }
|
|
2023
2097
|
],
|
|
2024
2098
|
action: async (ctx) => {
|
|
2025
2099
|
output.writeln();
|
|
@@ -2036,6 +2110,7 @@ export const memoryCommand = {
|
|
|
2036
2110
|
`${output.highlight('list')} - List memory entries`,
|
|
2037
2111
|
`${output.highlight('delete')} - Delete memory entry`,
|
|
2038
2112
|
`${output.highlight('stats')} - Show statistics`,
|
|
2113
|
+
`${output.highlight('peek')} - Read-only: resolved store files, rows, last write (creates nothing)`,
|
|
2039
2114
|
`${output.highlight('configure')} - Configure backend`,
|
|
2040
2115
|
`${output.highlight('cleanup')} - Clean expired entries`,
|
|
2041
2116
|
`${output.highlight('compress')} - Compress database`,
|
|
@@ -111,6 +111,12 @@ const runCommand = {
|
|
|
111
111
|
dryRun,
|
|
112
112
|
},
|
|
113
113
|
});
|
|
114
|
+
const runError = toolError(result);
|
|
115
|
+
if (runError || !result.metrics) {
|
|
116
|
+
spinner.fail('Workflow failed');
|
|
117
|
+
output.printError(`Workflow error: ${runError ?? 'workflow_run returned no workflow summary'}`);
|
|
118
|
+
return { success: false, exitCode: 1 };
|
|
119
|
+
}
|
|
114
120
|
if (dryRun) {
|
|
115
121
|
spinner.succeed('Workflow validated successfully');
|
|
116
122
|
}
|
|
@@ -277,6 +283,11 @@ const listCommand = {
|
|
|
277
283
|
status: status || 'all',
|
|
278
284
|
limit,
|
|
279
285
|
});
|
|
286
|
+
const listError = toolError(result);
|
|
287
|
+
if (listError || !Array.isArray(result.workflows)) {
|
|
288
|
+
output.printError(`Failed to list workflows: ${listError ?? 'workflow_list returned no workflows'}`);
|
|
289
|
+
return { success: false, exitCode: 1 };
|
|
290
|
+
}
|
|
280
291
|
if (ctx.flags.format === 'json') {
|
|
281
292
|
output.printJson(result);
|
|
282
293
|
return { success: true, data: result };
|
|
@@ -290,13 +301,19 @@ const listCommand = {
|
|
|
290
301
|
}
|
|
291
302
|
output.printTable({
|
|
292
303
|
columns: [
|
|
293
|
-
{ key: 'id', header: 'ID', width:
|
|
294
|
-
{ key: '
|
|
304
|
+
{ key: 'id', header: 'ID', width: 30 },
|
|
305
|
+
{ key: 'name', header: 'Name', width: 24 },
|
|
295
306
|
{ key: 'status', header: 'Status', width: 12, format: formatStageStatus },
|
|
296
|
-
{ key: '
|
|
297
|
-
{ key: '
|
|
307
|
+
{ key: 'steps', header: 'Steps', width: 6, align: 'right' },
|
|
308
|
+
{ key: 'createdAt', header: 'Created', width: 20, format: (v) => new Date(String(v)).toLocaleString() }
|
|
298
309
|
],
|
|
299
|
-
data: result.workflows
|
|
310
|
+
data: result.workflows.map(w => ({
|
|
311
|
+
id: w.workflowId,
|
|
312
|
+
name: w.name,
|
|
313
|
+
status: w.status,
|
|
314
|
+
steps: w.stepCount,
|
|
315
|
+
createdAt: w.createdAt,
|
|
316
|
+
}))
|
|
300
317
|
});
|
|
301
318
|
output.writeln();
|
|
302
319
|
output.printInfo(`Total: ${result.total} workflows`);
|
|
@@ -335,30 +352,37 @@ const statusCommand = {
|
|
|
335
352
|
try {
|
|
336
353
|
const result = await callMCPTool('workflow_status', {
|
|
337
354
|
workflowId,
|
|
355
|
+
verbose: true,
|
|
338
356
|
});
|
|
357
|
+
const statusError = toolError(result);
|
|
358
|
+
if (statusError) {
|
|
359
|
+
output.printError(`Failed to get status: ${statusError}`);
|
|
360
|
+
return { success: false, exitCode: 1 };
|
|
361
|
+
}
|
|
339
362
|
if (ctx.flags.format === 'json') {
|
|
340
363
|
output.printJson(result);
|
|
341
364
|
return { success: true, data: result };
|
|
342
365
|
}
|
|
366
|
+
const started = result.startedAt ? Date.parse(result.startedAt) : NaN;
|
|
367
|
+
const ended = result.completedAt ? Date.parse(result.completedAt) : Date.now();
|
|
343
368
|
output.writeln();
|
|
344
369
|
output.printBox([
|
|
345
|
-
`ID: ${result.
|
|
346
|
-
`
|
|
370
|
+
`ID: ${result.workflowId}`,
|
|
371
|
+
`Name: ${result.name}`,
|
|
347
372
|
`Status: ${formatStageStatus(result.status)}`,
|
|
348
|
-
`Progress: ${result.progress}
|
|
349
|
-
`Duration: ${(
|
|
350
|
-
`
|
|
351
|
-
`Agents: ${result.metrics.agentsSpawned}`
|
|
373
|
+
`Progress: ${Math.round(result.progress)}% (${result.completedSteps}/${result.totalSteps} steps)`,
|
|
374
|
+
...(Number.isNaN(started) ? [] : [`Duration: ${((ended - started) / 1000).toFixed(1)}s`]),
|
|
375
|
+
...(result.error ? [`Error: ${result.error}`] : []),
|
|
352
376
|
].join('\n'), 'Workflow Status');
|
|
353
377
|
output.writeln();
|
|
354
|
-
output.writeln(output.bold('
|
|
378
|
+
output.writeln(output.bold('Step Progress'));
|
|
355
379
|
output.printTable({
|
|
356
380
|
columns: [
|
|
357
|
-
{ key: 'name', header: '
|
|
381
|
+
{ key: 'name', header: 'Step', width: 24 },
|
|
382
|
+
{ key: 'type', header: 'Type', width: 10 },
|
|
358
383
|
{ key: 'status', header: 'Status', width: 12, format: formatStageStatus },
|
|
359
|
-
{ key: 'agents', header: 'Agents', width: 25, format: (v) => Array.isArray(v) ? v.length.toString() : '0' }
|
|
360
384
|
],
|
|
361
|
-
data: result.
|
|
385
|
+
data: result.steps ?? []
|
|
362
386
|
});
|
|
363
387
|
return { success: true, data: result };
|
|
364
388
|
}
|
|
@@ -408,6 +432,11 @@ const stopCommand = {
|
|
|
408
432
|
workflowId,
|
|
409
433
|
graceful: !force,
|
|
410
434
|
});
|
|
435
|
+
const stopError = toolError(result);
|
|
436
|
+
if (stopError || result.stopped !== true) {
|
|
437
|
+
output.printError(`Failed to stop workflow: ${stopError ?? 'the workflow was not stopped'}`);
|
|
438
|
+
return { success: false, exitCode: 1 };
|
|
439
|
+
}
|
|
411
440
|
output.printSuccess(`Workflow ${workflowId} stopped`);
|
|
412
441
|
return { success: true, data: result };
|
|
413
442
|
}
|
|
@@ -556,6 +585,17 @@ export const workflowCommand = {
|
|
|
556
585
|
}
|
|
557
586
|
};
|
|
558
587
|
// Helper functions
|
|
588
|
+
/**
|
|
589
|
+
* The workflow MCP tools report failure as `{ error }` (or `{ success: false, error }`)
|
|
590
|
+
* instead of throwing, so a command that reads the result without checking it prints
|
|
591
|
+
* a false success or crashes on a field the error result does not have.
|
|
592
|
+
*/
|
|
593
|
+
function toolError(result) {
|
|
594
|
+
const r = result;
|
|
595
|
+
if (!r)
|
|
596
|
+
return 'the workflow tool returned no result';
|
|
597
|
+
return typeof r.error === 'string' && r.error.length > 0 ? r.error : undefined;
|
|
598
|
+
}
|
|
559
599
|
function formatStageStatus(status) {
|
|
560
600
|
const statusStr = String(status);
|
|
561
601
|
switch (statusStr) {
|
|
@@ -18,7 +18,7 @@ import { generatePreCommitHook, generatePostCommitHook, generateSessionManager,
|
|
|
18
18
|
import { getInstalledCliVersion, HELPERS_STAMP_FILE } from './helper-refresh.js';
|
|
19
19
|
import { generateClaudeMd } from './claudemd-generator.js';
|
|
20
20
|
import { recordMemoryPackagePath } from './memory-package-resolver.js';
|
|
21
|
-
import { ensureCommonJsCompanions } from './helper-companions.js';
|
|
21
|
+
import { COMMONJS_COMPANION_HELPERS, ensureCommonJsCompanions } from './helper-companions.js';
|
|
22
22
|
import { scanSettingsForRisk, formatRiskFindingsAsWarnings } from './settings-risk-scanner.js';
|
|
23
23
|
import { AUTO_MEMORY_HOOK_TIMEOUT_S, helperHookCommand, helperScopeFor, helperStatusLineCommand, pinToUserHelpers } from './helper-commands.js';
|
|
24
24
|
/**
|
|
@@ -548,10 +548,14 @@ export async function executeUpgrade(targetDir, upgradeSettings = false) {
|
|
|
548
548
|
catch { }
|
|
549
549
|
}
|
|
550
550
|
}
|
|
551
|
-
// #
|
|
552
|
-
//
|
|
553
|
-
|
|
554
|
-
|
|
551
|
+
// #4198: refresh existing companions too, so upgraded hook handlers never
|
|
552
|
+
// keep obsolete session/memory logic. Generate from the CLI trust root.
|
|
553
|
+
const helpersDir = path.join(targetDir, '.claude', 'helpers');
|
|
554
|
+
const existingCompanions = new Set(COMMONJS_COMPANION_HELPERS.filter((name) => fs.existsSync(path.join(helpersDir, name))));
|
|
555
|
+
for (const name of await ensureCommonJsCompanions(helpersDir, true)) {
|
|
556
|
+
const changes = existingCompanions.has(name)
|
|
557
|
+
? result.updated : result.created;
|
|
558
|
+
changes.push(`.claude/helpers/${name}`);
|
|
555
559
|
}
|
|
556
560
|
// Stamp the installed version so the startup auto-refresh treats these as
|
|
557
561
|
// current (no redundant re-copy on the next command).
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare const COMMONJS_COMPANION_HELPERS: readonly ["session.cjs", "memory.cjs"];
|
|
2
|
-
export declare function ensureCommonJsCompanions(helpersDir: string): Promise<string[]>;
|
|
2
|
+
export declare function ensureCommonJsCompanions(helpersDir: string, force?: boolean): Promise<string[]>;
|
|
3
3
|
//# sourceMappingURL=helper-companions.d.ts.map
|
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
* `.js` copies, which the refreshed hook-handler no longer requires, so the
|
|
8
8
|
* refresh writes any missing `.cjs` companion from the CLI's own compiled
|
|
9
9
|
* generators (the trust root; no external file to verify). Stale `.js` copies
|
|
10
|
-
* are left in place and ignored. Existing `.cjs` files are
|
|
10
|
+
* are left in place and ignored. Existing `.cjs` files are preserved during
|
|
11
|
+
* startup refresh; an explicit init upgrade replaces them from the generators.
|
|
11
12
|
*/
|
|
12
13
|
import * as fs from 'fs';
|
|
13
14
|
import * as path from 'path';
|
|
14
15
|
export const COMMONJS_COMPANION_HELPERS = ['session.cjs', 'memory.cjs'];
|
|
15
|
-
export async function ensureCommonJsCompanions(helpersDir) {
|
|
16
|
-
const missing = COMMONJS_COMPANION_HELPERS.filter((name) => !fs.existsSync(path.join(helpersDir, name)));
|
|
16
|
+
export async function ensureCommonJsCompanions(helpersDir, force = false) {
|
|
17
|
+
const missing = COMMONJS_COMPANION_HELPERS.filter((name) => force || !fs.existsSync(path.join(helpersDir, name)));
|
|
17
18
|
if (missing.length === 0)
|
|
18
19
|
return [];
|
|
19
20
|
const gen = await import('./helpers-generator.js');
|
|
@@ -30,6 +30,11 @@ export interface MCPServerOptions {
|
|
|
30
30
|
daemonize?: boolean;
|
|
31
31
|
timeout?: number;
|
|
32
32
|
requestTimeoutMs?: number;
|
|
33
|
+
rateLimitPerIp?: number;
|
|
34
|
+
rateLimitWindowMs?: number;
|
|
35
|
+
rateLimitPerSession?: number;
|
|
36
|
+
rateLimitGlobalRps?: number;
|
|
37
|
+
rateLimitGlobalBurst?: number;
|
|
33
38
|
/**
|
|
34
39
|
* Bearer token every HTTP request must present (`Authorization: Bearer <token>`).
|
|
35
40
|
* http transport only. Prefer RUFLO_MCP_HTTP_TOKEN / a token file over this
|
|
@@ -103,6 +103,10 @@ function processStartToken(pid) {
|
|
|
103
103
|
return undefined;
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
+
function definedFields(fields) {
|
|
107
|
+
const entries = Object.entries(fields).filter(([, value]) => value !== undefined);
|
|
108
|
+
return entries.length === 0 ? undefined : Object.fromEntries(entries);
|
|
109
|
+
}
|
|
106
110
|
/**
|
|
107
111
|
* Hosts treated as same-machine-only for MCP HTTP tool-call authorization
|
|
108
112
|
* purposes (see startHttpServer's non-loopback authorization gate below).
|
|
@@ -134,6 +138,26 @@ export function isUnauthenticatedHttpAllowed(env = process.env) {
|
|
|
134
138
|
export function shouldRefuseUnauthenticatedHttp(host, env = process.env, authenticated = false) {
|
|
135
139
|
return !isLoopbackHost(host) && !authenticated && !isUnauthenticatedHttpAllowed(env);
|
|
136
140
|
}
|
|
141
|
+
/** The limits @claude-flow/mcp applies when a rate limit option is unset. */
|
|
142
|
+
const DEFAULT_RATE_LIMITS = {
|
|
143
|
+
rateLimitPerIp: 120,
|
|
144
|
+
rateLimitPerSession: 50,
|
|
145
|
+
rateLimitGlobalRps: 100,
|
|
146
|
+
rateLimitGlobalBurst: 200,
|
|
147
|
+
};
|
|
148
|
+
const DEFAULT_RATE_LIMIT_WINDOW_MS = 60_000;
|
|
149
|
+
/**
|
|
150
|
+
* Rate limit options that are looser than the @claude-flow/mcp defaults. A
|
|
151
|
+
* shorter window counts as looser.
|
|
152
|
+
*/
|
|
153
|
+
function raisedRateLimits(options) {
|
|
154
|
+
const raised = Object.keys(DEFAULT_RATE_LIMITS)
|
|
155
|
+
.filter((name) => (options[name] ?? 0) > DEFAULT_RATE_LIMITS[name]);
|
|
156
|
+
if ((options.rateLimitWindowMs ?? DEFAULT_RATE_LIMIT_WINDOW_MS) < DEFAULT_RATE_LIMIT_WINDOW_MS) {
|
|
157
|
+
raised.push('rateLimitWindowMs');
|
|
158
|
+
}
|
|
159
|
+
return raised;
|
|
160
|
+
}
|
|
137
161
|
/** Printable ASCII without space (RFC 6750 token-ish), 16-512 chars. */
|
|
138
162
|
const AUTH_TOKEN_PATTERN = /^[\x21-\x7e]{16,512}$/;
|
|
139
163
|
/**
|
|
@@ -822,6 +846,15 @@ export class MCPServerManager extends EventEmitter {
|
|
|
822
846
|
'RUFLO_MCP_ALLOW_UNAUTHENTICATED_HTTP=1 to acknowledge the risk and proceed ' +
|
|
823
847
|
'(e.g. when a trusted reverse proxy or network boundary already enforces auth).');
|
|
824
848
|
}
|
|
849
|
+
const raised = raisedRateLimits(this.options);
|
|
850
|
+
if (raised.length > 0 && !isLoopbackHost(this.options.host) && !authToken) {
|
|
851
|
+
// RUFLO_MCP_ALLOW_UNAUTHENTICATED_HTTP=1 lets an open server start, but
|
|
852
|
+
// anyone who can reach it should not also get more than the default
|
|
853
|
+
// request budget.
|
|
854
|
+
throw new Error(`Refusing to raise MCP rate limits (${raised.join(', ')}) above the defaults on non-loopback ` +
|
|
855
|
+
`host "${this.options.host}" without a bearer token. Set RUFLO_MCP_HTTP_TOKEN (or ` +
|
|
856
|
+
'--auth-token-file), or bind to a loopback host (127.0.0.1, ::1, or localhost).');
|
|
857
|
+
}
|
|
825
858
|
// Dynamically import the MCP server package
|
|
826
859
|
// FIX for issue #942: Use proper package import instead of broken relative path
|
|
827
860
|
const { createMCPServer } = await import('@claude-flow/mcp');
|
|
@@ -867,6 +900,12 @@ export class MCPServerManager extends EventEmitter {
|
|
|
867
900
|
enableMetrics: true,
|
|
868
901
|
enableCaching: true,
|
|
869
902
|
requestTimeout: this.options.requestTimeoutMs,
|
|
903
|
+
rateLimit: definedFields({ windowMs: this.options.rateLimitWindowMs, limit: this.options.rateLimitPerIp }),
|
|
904
|
+
sessionRateLimit: definedFields({
|
|
905
|
+
requestsPerSecond: this.options.rateLimitGlobalRps,
|
|
906
|
+
burstSize: this.options.rateLimitGlobalBurst,
|
|
907
|
+
perSessionLimit: this.options.rateLimitPerSession,
|
|
908
|
+
}),
|
|
870
909
|
auth: authToken
|
|
871
910
|
? { enabled: true, method: 'token', tokens: [authToken] }
|
|
872
911
|
: undefined,
|
|
@@ -216,6 +216,7 @@ export const agentTools = [
|
|
|
216
216
|
properties: {
|
|
217
217
|
agentType: { type: 'string', description: 'Type of agent to spawn' },
|
|
218
218
|
agentId: { type: 'string', description: 'Optional custom agent ID' },
|
|
219
|
+
name: { type: 'string', description: 'Optional display name for the agent (shown by agent_list, agent_status and ruflo-console)' },
|
|
219
220
|
// #2085 — accept swarmId so spawned agents register in the
|
|
220
221
|
// swarm.agents array that swarm_status reports. Omit to register
|
|
221
222
|
// with the most-recently-created swarm.
|
|
@@ -241,6 +242,18 @@ export const agentTools = [
|
|
|
241
242
|
}
|
|
242
243
|
const agentId = input.agentId || `agent-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
243
244
|
const agentType = input.agentType;
|
|
245
|
+
// #4300 — older callers (including `agent spawn` before this fix) send
|
|
246
|
+
// the name only as metadata.name.
|
|
247
|
+
const metadata = input.metadata;
|
|
248
|
+
const rawName = input.name ?? metadata?.name;
|
|
249
|
+
let name;
|
|
250
|
+
if (rawName !== undefined && rawName !== '') {
|
|
251
|
+
const vName = validateText(rawName, 'name', 200);
|
|
252
|
+
if (!vName.valid) {
|
|
253
|
+
return { success: false, error: `Input validation failed: ${vName.error}` };
|
|
254
|
+
}
|
|
255
|
+
name = vName.sanitized;
|
|
256
|
+
}
|
|
244
257
|
const config = input.config || {};
|
|
245
258
|
// Add explicit model to config if provided
|
|
246
259
|
if (input.model) {
|
|
@@ -262,6 +275,7 @@ export const agentTools = [
|
|
|
262
275
|
const agent = {
|
|
263
276
|
agentId,
|
|
264
277
|
agentType,
|
|
278
|
+
...(name ? { name } : {}),
|
|
265
279
|
status: 'idle',
|
|
266
280
|
health: 1.0,
|
|
267
281
|
taskCount: 0,
|
|
@@ -347,6 +361,7 @@ export const agentTools = [
|
|
|
347
361
|
success: true,
|
|
348
362
|
agentId,
|
|
349
363
|
agentType: agent.agentType,
|
|
364
|
+
...(agent.name ? { name: agent.name } : {}),
|
|
350
365
|
model: agent.model,
|
|
351
366
|
modelRoutedBy: routingResult.routedBy,
|
|
352
367
|
...(routingResult.modelId ? { modelId: routingResult.modelId } : {}),
|
|
@@ -501,6 +516,7 @@ export const agentTools = [
|
|
|
501
516
|
return {
|
|
502
517
|
agentId: agent.agentId,
|
|
503
518
|
agentType: agent.agentType,
|
|
519
|
+
...(agent.name ? { name: agent.name } : {}),
|
|
504
520
|
status: agent.status,
|
|
505
521
|
health: agent.health,
|
|
506
522
|
taskCount: agent.taskCount,
|
|
@@ -525,6 +541,7 @@ export const agentTools = [
|
|
|
525
541
|
properties: {
|
|
526
542
|
status: { type: 'string', description: 'Filter by status' },
|
|
527
543
|
domain: { type: 'string', description: 'Filter by domain' },
|
|
544
|
+
agentType: { type: 'string', description: 'Filter by agent type' },
|
|
528
545
|
includeTerminated: { type: 'boolean', description: 'Include terminated agents' },
|
|
529
546
|
},
|
|
530
547
|
},
|
|
@@ -539,6 +556,11 @@ export const agentTools = [
|
|
|
539
556
|
if (!v.valid)
|
|
540
557
|
return { agents: [], total: 0, error: `Input validation failed: ${v.error}` };
|
|
541
558
|
}
|
|
559
|
+
if (input.agentType) {
|
|
560
|
+
const v = validateIdentifier(input.agentType, 'agentType');
|
|
561
|
+
if (!v.valid)
|
|
562
|
+
return { agents: [], total: 0, error: `Input validation failed: ${v.error}` };
|
|
563
|
+
}
|
|
542
564
|
let agents = Object.values(loadAllAgents()); // #1916: includes hive-mind-spawned workers
|
|
543
565
|
// Filter by status
|
|
544
566
|
if (input.status) {
|
|
@@ -551,10 +573,15 @@ export const agentTools = [
|
|
|
551
573
|
if (input.domain) {
|
|
552
574
|
agents = agents.filter(a => a.domain === input.domain);
|
|
553
575
|
}
|
|
576
|
+
// Filter by agent type
|
|
577
|
+
if (input.agentType) {
|
|
578
|
+
agents = agents.filter(a => a.agentType === input.agentType);
|
|
579
|
+
}
|
|
554
580
|
return {
|
|
555
581
|
agents: agents.map(a => ({
|
|
556
582
|
agentId: a.agentId,
|
|
557
583
|
agentType: a.agentType,
|
|
584
|
+
...(a.name ? { name: a.name } : {}),
|
|
558
585
|
status: a.status,
|
|
559
586
|
health: a.health,
|
|
560
587
|
taskCount: a.taskCount,
|
|
@@ -565,6 +592,7 @@ export const agentTools = [
|
|
|
565
592
|
filters: {
|
|
566
593
|
status: input.status,
|
|
567
594
|
domain: input.domain,
|
|
595
|
+
agentType: input.agentType,
|
|
568
596
|
includeTerminated: input.includeTerminated,
|
|
569
597
|
},
|
|
570
598
|
};
|