@claude-flow/cli 3.47.0 → 3.48.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/auto-memory-hook.mjs +6 -2
- package/.claude/helpers/helpers.manifest.json +3 -3
- package/.claude/helpers/learning-service.mjs +4 -2
- package/.claude/helpers/metrics-db.mjs +4 -2
- package/bin/cli.js +4 -1
- package/catalog-manifest.json +2 -2
- package/dist/src/commands/analyze.js +2 -2
- package/dist/src/commands/config.js +5 -7
- package/dist/src/commands/hive-mind-worker.d.ts +88 -0
- package/dist/src/commands/hive-mind-worker.js +242 -0
- package/dist/src/commands/hive-mind.js +24 -41
- package/dist/src/commands/hooks.js +15 -5
- package/dist/src/commands/memory.js +89 -27
- package/dist/src/commands/metaharness.js +1 -0
- package/dist/src/commands/security.js +113 -55
- package/dist/src/commands/session.js +6 -6
- package/dist/src/commands/task.js +5 -4
- package/dist/src/init/helpers-generator.js +4 -2
- package/dist/src/mcp-server.js +10 -3
- package/dist/src/mcp-tools/agentdb-tools.js +29 -62
- package/dist/src/mcp-tools/hooks-tools.d.ts +0 -4
- package/dist/src/mcp-tools/hooks-tools.js +139 -37
- package/dist/src/mcp-tools/memory-tools.js +89 -35
- package/dist/src/mcp-tools/policy-enforcer.d.ts +8 -1
- package/dist/src/mcp-tools/policy-enforcer.js +80 -8
- package/dist/src/mcp-tools/session-tools.js +34 -8
- package/dist/src/mcp-tools/task-tools.js +73 -53
- package/dist/src/memory/feedback-patterns.d.ts +13 -0
- package/dist/src/memory/feedback-patterns.js +14 -0
- package/dist/src/memory/live-memory-row.d.ts +3 -0
- package/dist/src/memory/live-memory-row.js +5 -0
- package/dist/src/memory/memory-bridge.d.ts +11 -20
- package/dist/src/memory/memory-bridge.js +348 -134
- package/dist/src/memory/memory-initializer.d.ts +9 -1
- package/dist/src/memory/memory-initializer.js +41 -31
- package/dist/src/ruvector/graph-analyzer.js +4 -1
- package/dist/src/services/config-file-manager.js +30 -8
- package/dist/src/services/memory-backup.d.ts +1 -1
- package/dist/src/services/memory-backup.js +67 -27
- package/dist/src/services/worker-daemon.d.ts +2 -2
- package/dist/src/services/worker-daemon.js +23 -14
- package/node_modules/@claude-flow/codex/package.json +1 -1
- package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/tool-registry.js +11 -3
- package/node_modules/@claude-flow/mcp/dist/tool-registry.js.map +1 -1
- package/node_modules/@claude-flow/mcp/package.json +6 -3
- package/node_modules/@claude-flow/plugin-agent-federation/package.json +3 -3
- package/node_modules/@claude-flow/security/package.json +2 -3
- package/package.json +9 -9
- package/plugins/ruflo-metaharness/scripts/_cli-args.mjs +37 -0
- package/plugins/ruflo-metaharness/scripts/audit-list.mjs +8 -0
- package/plugins/ruflo-metaharness/scripts/audit-trend.mjs +12 -0
- package/plugins/ruflo-metaharness/scripts/bench-parse-mcp-scan.mjs +8 -0
- package/plugins/ruflo-metaharness/scripts/bench-recordpair-overhead.mjs +8 -0
- package/plugins/ruflo-metaharness/scripts/bench-similarity.mjs +8 -0
- package/plugins/ruflo-metaharness/scripts/bench.mjs +10 -0
- package/plugins/ruflo-metaharness/scripts/drift-from-history.mjs +13 -0
- package/plugins/ruflo-metaharness/scripts/evolve.mjs +27 -0
- package/plugins/ruflo-metaharness/scripts/genome.mjs +8 -0
- package/plugins/ruflo-metaharness/scripts/gepa.mjs +12 -0
- package/plugins/ruflo-metaharness/scripts/learn.mjs +13 -0
- package/plugins/ruflo-metaharness/scripts/mcp-scan.mjs +8 -0
- package/plugins/ruflo-metaharness/scripts/mint.mjs +11 -0
- package/plugins/ruflo-metaharness/scripts/oia-audit.mjs +9 -0
- package/plugins/ruflo-metaharness/scripts/router-parallel-analyze.mjs +8 -0
- package/plugins/ruflo-metaharness/scripts/score.mjs +8 -0
- package/plugins/ruflo-metaharness/scripts/security-bench.mjs +11 -0
- package/plugins/ruflo-metaharness/scripts/similarity.mjs +12 -0
- package/plugins/ruflo-metaharness/scripts/test-cli-help.mjs +72 -0
- package/plugins/ruflo-metaharness/scripts/threat-model.mjs +8 -0
|
@@ -12,12 +12,16 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
15
|
-
import { join, dirname } from 'path';
|
|
15
|
+
import { join, dirname, resolve } from 'path';
|
|
16
16
|
import { fileURLToPath } from 'url';
|
|
17
17
|
|
|
18
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
19
19
|
const __dirname = dirname(__filename);
|
|
20
|
-
|
|
20
|
+
// Home-level helpers can serve a different project. Keep the memory writer on
|
|
21
|
+
// the same project root as intelligence.cjs, which reads CLAUDE_PROJECT_DIR.
|
|
22
|
+
const PROJECT_ROOT = process.env.CLAUDE_PROJECT_DIR
|
|
23
|
+
? resolve(process.env.CLAUDE_PROJECT_DIR)
|
|
24
|
+
: join(__dirname, '../..');
|
|
21
25
|
const DATA_DIR = join(PROJECT_ROOT, '.claude-flow', 'data');
|
|
22
26
|
const STORE_PATH = join(DATA_DIR, 'auto-memory-store.json');
|
|
23
27
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest": {
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.48.0",
|
|
4
4
|
"files": {
|
|
5
|
-
"auto-memory-hook.mjs": "
|
|
5
|
+
"auto-memory-hook.mjs": "c8d218526a3c34034c2a69de23f507f0eb0f70d59c16426a8edc849e6ac22529",
|
|
6
6
|
"hook-handler.cjs": "209d9fafe10e17d1be0866727f6f9cf9ac66f9a0793f1c793a4f58319e8e4583",
|
|
7
7
|
"intelligence.cjs": "30e42ed7ec4ca5a94ac54fdb1330d2d47ac5f3fdeeef207753574723a9e77b5c",
|
|
8
8
|
"statusline.cjs": "4a48353b4f1566fa4379b00fd0321b6676a22b6cc91fbbd8380ac5183d619468",
|
|
9
9
|
"router.js": "b6998397e7883191b62229ccdc66fb87f1ccca1d5b2039ecb8c1e686d1ad81f8"
|
|
10
10
|
}
|
|
11
11
|
},
|
|
12
|
-
"signature": "
|
|
12
|
+
"signature": "Y5fY+vJvnledNFp6edS2V1gxJ9P7llOQuq860/gCMvxr9DAzrlDuuKMuGgVP/Tb/25FStYm9+WyBVuOTDos0BQ==",
|
|
13
13
|
"algorithm": "ed25519"
|
|
14
14
|
}
|
|
@@ -19,14 +19,16 @@
|
|
|
19
19
|
|
|
20
20
|
import { createRequire } from 'module';
|
|
21
21
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
22
|
-
import { join, dirname } from 'path';
|
|
22
|
+
import { join, dirname, resolve } from 'path';
|
|
23
23
|
import { fileURLToPath } from 'url';
|
|
24
24
|
import { execSync, spawn } from 'child_process';
|
|
25
25
|
import Database from 'better-sqlite3';
|
|
26
26
|
|
|
27
27
|
const __filename = fileURLToPath(import.meta.url);
|
|
28
28
|
const __dirname = dirname(__filename);
|
|
29
|
-
const PROJECT_ROOT =
|
|
29
|
+
const PROJECT_ROOT = process.env.CLAUDE_PROJECT_DIR
|
|
30
|
+
? resolve(process.env.CLAUDE_PROJECT_DIR)
|
|
31
|
+
: join(__dirname, '../..');
|
|
30
32
|
const DATA_DIR = join(PROJECT_ROOT, '.claude-flow/learning');
|
|
31
33
|
const DB_PATH = join(DATA_DIR, 'patterns.db');
|
|
32
34
|
const METRICS_PATH = join(DATA_DIR, 'learning-metrics.json');
|
|
@@ -7,12 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
import initSqlJs from 'sql.js';
|
|
9
9
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, openSync, writeSync, fsyncSync, closeSync, renameSync, rmSync } from 'fs';
|
|
10
|
-
import { dirname, join, basename } from 'path';
|
|
10
|
+
import { dirname, join, basename, resolve } from 'path';
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
12
12
|
import { execSync } from 'child_process';
|
|
13
13
|
|
|
14
14
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
-
const PROJECT_ROOT =
|
|
15
|
+
const PROJECT_ROOT = process.env.CLAUDE_PROJECT_DIR
|
|
16
|
+
? resolve(process.env.CLAUDE_PROJECT_DIR)
|
|
17
|
+
: join(__dirname, '../..');
|
|
16
18
|
const V3_DIR = join(PROJECT_ROOT, 'v3');
|
|
17
19
|
const DB_PATH = join(PROJECT_ROOT, '.claude-flow', 'metrics.db');
|
|
18
20
|
|
package/bin/cli.js
CHANGED
|
@@ -302,10 +302,13 @@ if (isMCPMode) {
|
|
|
302
302
|
|
|
303
303
|
try {
|
|
304
304
|
const result = await callMCPTool(toolName, toolParams, { sessionId });
|
|
305
|
+
// Failed task outcomes are data; an explicit handler error is a tool failure.
|
|
306
|
+
const isError = result !== null && typeof result === 'object'
|
|
307
|
+
&& typeof result.error === 'string' && result.error.trim().length > 0;
|
|
305
308
|
return {
|
|
306
309
|
jsonrpc: '2.0',
|
|
307
310
|
id: message.id,
|
|
308
|
-
result: { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] },
|
|
311
|
+
result: { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], isError },
|
|
309
312
|
};
|
|
310
313
|
} catch (error) {
|
|
311
314
|
return {
|
package/catalog-manifest.json
CHANGED
|
@@ -1480,7 +1480,7 @@ const boundariesCommand = {
|
|
|
1480
1480
|
output.writeln();
|
|
1481
1481
|
for (const cycle of result.circularDependencies.slice(0, 5)) {
|
|
1482
1482
|
const severityColor = cycle.severity === 'high' ? output.error : cycle.severity === 'medium' ? output.warning : output.dim;
|
|
1483
|
-
output.writeln(`${severityColor(`[${cycle.severity.toUpperCase()}]`)} ${cycle.cycle.join(' -> ')}`);
|
|
1483
|
+
output.writeln(`${severityColor.call(output, `[${cycle.severity.toUpperCase()}]`)} ${cycle.cycle.join(' -> ')}`);
|
|
1484
1484
|
output.writeln(output.dim(` ${cycle.suggestion}`));
|
|
1485
1485
|
output.writeln();
|
|
1486
1486
|
}
|
|
@@ -1905,7 +1905,7 @@ const circularCommand = {
|
|
|
1905
1905
|
if (items.length === 0)
|
|
1906
1906
|
continue;
|
|
1907
1907
|
const color = severity === 'high' ? output.error : severity === 'medium' ? output.warning : output.dim;
|
|
1908
|
-
output.writeln(color(output.bold(`${severity.toUpperCase()} SEVERITY (${items.length})`)));
|
|
1908
|
+
output.writeln(color.call(output, output.bold(`${severity.toUpperCase()} SEVERITY (${items.length})`)));
|
|
1909
1909
|
output.writeln();
|
|
1910
1910
|
for (const cycle of items.slice(0, 5)) {
|
|
1911
1911
|
output.writeln(` ${cycle.cycle.join(' -> ')}`);
|
|
@@ -124,15 +124,13 @@ const setCommand = {
|
|
|
124
124
|
name: 'key',
|
|
125
125
|
short: 'k',
|
|
126
126
|
description: 'Configuration key',
|
|
127
|
-
type: 'string'
|
|
128
|
-
required: true
|
|
127
|
+
type: 'string'
|
|
129
128
|
},
|
|
130
129
|
{
|
|
131
130
|
name: 'value',
|
|
132
131
|
short: 'v',
|
|
133
132
|
description: 'Configuration value',
|
|
134
|
-
type: 'string'
|
|
135
|
-
required: true
|
|
133
|
+
type: 'string'
|
|
136
134
|
}
|
|
137
135
|
],
|
|
138
136
|
examples: [
|
|
@@ -140,14 +138,14 @@ const setCommand = {
|
|
|
140
138
|
{ command: 'claude-flow config set -k memory.backend -v agentdb', description: 'Set memory backend' }
|
|
141
139
|
],
|
|
142
140
|
action: async (ctx) => {
|
|
143
|
-
const key = ctx.flags.key
|
|
144
|
-
const value = ctx.flags.value
|
|
141
|
+
const key = (ctx.flags.key ?? ctx.args[0]);
|
|
142
|
+
const value = ctx.flags.value ?? ctx.args[1];
|
|
145
143
|
if (!key || value === undefined) {
|
|
146
144
|
output.printError('Both key and value are required');
|
|
147
145
|
return { success: false, exitCode: 1 };
|
|
148
146
|
}
|
|
149
147
|
try {
|
|
150
|
-
const parsedValue = parseConfigValue(value);
|
|
148
|
+
const parsedValue = parseConfigValue(String(value));
|
|
151
149
|
configManager.set(ctx.cwd, key, parsedValue);
|
|
152
150
|
output.writeln(`Set ${key} = ${value}`);
|
|
153
151
|
return { success: true };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hive-mind worker execution: the MCP config handed to a spawned Claude, and
|
|
3
|
+
* `hive-mind work`, which actually runs a queued task.
|
|
4
|
+
*
|
|
5
|
+
* Before this, `hive-mind task` only wrote a `pending` row to the task store.
|
|
6
|
+
* Workers are records, not processes, and nothing ever claimed the row: the
|
|
7
|
+
* task stayed pending and the worker stayed idle forever. `work` is the missing
|
|
8
|
+
* consumer — claim one task, assign it to an idle hive worker, run it through
|
|
9
|
+
* `claude -p`, and record the outcome.
|
|
10
|
+
*/
|
|
11
|
+
import type { Command } from '../types.js';
|
|
12
|
+
export interface WorkerMcpConfigOptions {
|
|
13
|
+
/** `--mcp-config` from the caller; used verbatim when given. */
|
|
14
|
+
explicit?: string;
|
|
15
|
+
cwd: string;
|
|
16
|
+
home: string;
|
|
17
|
+
/** Where the generated config is written (e.g. `.hive-mind/sessions`). */
|
|
18
|
+
outDir: string;
|
|
19
|
+
swarmId: string;
|
|
20
|
+
/** The ruflo MCP server to register when no source config provides one. */
|
|
21
|
+
rufloServer: {
|
|
22
|
+
command: string;
|
|
23
|
+
args: string[];
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The MCP config file to pass to a spawned Claude.
|
|
28
|
+
*
|
|
29
|
+
* A source file is never passed through as-is. `~/.claude.json` is Claude
|
|
30
|
+
* Code's whole state file; on a machine where it has no top-level
|
|
31
|
+
* `mcpServers` (a fresh desktop), Claude exits at startup with "Invalid MCP
|
|
32
|
+
* configuration: mcpServers: Invalid input", before any task can run. And a
|
|
33
|
+
* config lacking a `ruflo` server leaves the worker without the tools its
|
|
34
|
+
* prompt names. So the servers are extracted, a `ruflo` server is guaranteed,
|
|
35
|
+
* and the result is written as a config Claude is known to accept.
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveWorkerMcpConfig(opts: WorkerMcpConfigOptions): string;
|
|
38
|
+
/**
|
|
39
|
+
* The ruflo MCP server as launched by THIS cli, so a worker and the CLI that
|
|
40
|
+
* dispatched it share one version and one store format. `@latest` could
|
|
41
|
+
* resolve to a different release mid-session.
|
|
42
|
+
*/
|
|
43
|
+
export declare function currentRufloServer(): {
|
|
44
|
+
command: string;
|
|
45
|
+
args: string[];
|
|
46
|
+
};
|
|
47
|
+
export interface RunOutcome {
|
|
48
|
+
ok: boolean;
|
|
49
|
+
output: string;
|
|
50
|
+
exitCode: number | null;
|
|
51
|
+
sessionId?: string;
|
|
52
|
+
costUsd?: number;
|
|
53
|
+
durationMs: number;
|
|
54
|
+
}
|
|
55
|
+
export type TaskRunner = (prompt: string, opts: {
|
|
56
|
+
mcpConfig: string;
|
|
57
|
+
timeoutMs: number;
|
|
58
|
+
cwd: string;
|
|
59
|
+
}) => Promise<RunOutcome>;
|
|
60
|
+
/** Run one prompt through `claude -p` and parse its JSON result. */
|
|
61
|
+
export declare const runWithClaude: TaskRunner;
|
|
62
|
+
export interface WorkOnceOptions {
|
|
63
|
+
cwd: string;
|
|
64
|
+
mcpConfig: string;
|
|
65
|
+
runner: TaskRunner;
|
|
66
|
+
taskId?: string;
|
|
67
|
+
workerId?: string;
|
|
68
|
+
defaultTimeoutMs?: number;
|
|
69
|
+
}
|
|
70
|
+
export type WorkOnceResult = {
|
|
71
|
+
dispatched: false;
|
|
72
|
+
reason: string;
|
|
73
|
+
} | {
|
|
74
|
+
dispatched: true;
|
|
75
|
+
taskId: string;
|
|
76
|
+
workerId: string;
|
|
77
|
+
status: 'completed' | 'failed';
|
|
78
|
+
outcome: RunOutcome;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* One dispatch cycle: the oldest pending hive task goes to an idle hive worker,
|
|
82
|
+
* runs, and its outcome is recorded on the task. Every state change goes
|
|
83
|
+
* through the MCP task tools, so the store is the same one `hive-mind status`
|
|
84
|
+
* and `task status` read.
|
|
85
|
+
*/
|
|
86
|
+
export declare function workOnce(opts: WorkOnceOptions): Promise<WorkOnceResult>;
|
|
87
|
+
export declare const workCommand: Command;
|
|
88
|
+
//# sourceMappingURL=hive-mind-worker.d.ts.map
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hive-mind worker execution: the MCP config handed to a spawned Claude, and
|
|
3
|
+
* `hive-mind work`, which actually runs a queued task.
|
|
4
|
+
*
|
|
5
|
+
* Before this, `hive-mind task` only wrote a `pending` row to the task store.
|
|
6
|
+
* Workers are records, not processes, and nothing ever claimed the row: the
|
|
7
|
+
* task stayed pending and the worker stayed idle forever. `work` is the missing
|
|
8
|
+
* consumer — claim one task, assign it to an idle hive worker, run it through
|
|
9
|
+
* `claude -p`, and record the outcome.
|
|
10
|
+
*/
|
|
11
|
+
import { output } from '../output.js';
|
|
12
|
+
import { callMCPTool, MCPClientError } from '../mcp-client.js';
|
|
13
|
+
import { spawn as childSpawn } from 'child_process';
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
15
|
+
import { join } from 'path';
|
|
16
|
+
import { resolveClaudeLaunchCommand } from '../runtime/claude-command.js';
|
|
17
|
+
/** The server key the hive-mind prompt addresses (`mcp__ruflo__*`). */
|
|
18
|
+
const RUFLO_SERVER = 'ruflo';
|
|
19
|
+
function readServers(path, cwd) {
|
|
20
|
+
try {
|
|
21
|
+
if (!existsSync(path))
|
|
22
|
+
return null;
|
|
23
|
+
const parsed = JSON.parse(readFileSync(path, 'utf-8'));
|
|
24
|
+
const isMap = (v) => !!v && typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length > 0;
|
|
25
|
+
if (isMap(parsed.mcpServers))
|
|
26
|
+
return parsed.mcpServers;
|
|
27
|
+
// ~/.claude.json keeps project-scoped servers under projects[<cwd>].
|
|
28
|
+
const project = parsed.projects?.[cwd];
|
|
29
|
+
if (isMap(project?.mcpServers))
|
|
30
|
+
return project.mcpServers;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Unreadable or not JSON: not a usable config.
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The MCP config file to pass to a spawned Claude.
|
|
39
|
+
*
|
|
40
|
+
* A source file is never passed through as-is. `~/.claude.json` is Claude
|
|
41
|
+
* Code's whole state file; on a machine where it has no top-level
|
|
42
|
+
* `mcpServers` (a fresh desktop), Claude exits at startup with "Invalid MCP
|
|
43
|
+
* configuration: mcpServers: Invalid input", before any task can run. And a
|
|
44
|
+
* config lacking a `ruflo` server leaves the worker without the tools its
|
|
45
|
+
* prompt names. So the servers are extracted, a `ruflo` server is guaranteed,
|
|
46
|
+
* and the result is written as a config Claude is known to accept.
|
|
47
|
+
*/
|
|
48
|
+
export function resolveWorkerMcpConfig(opts) {
|
|
49
|
+
if (opts.explicit)
|
|
50
|
+
return opts.explicit;
|
|
51
|
+
const candidates = [join(opts.cwd, '.mcp.json')];
|
|
52
|
+
if (opts.home)
|
|
53
|
+
candidates.push(join(opts.home, '.claude.json'), join(opts.home, '.claude', 'mcp.json'));
|
|
54
|
+
let servers = {};
|
|
55
|
+
for (const c of candidates) {
|
|
56
|
+
const found = readServers(c, opts.cwd);
|
|
57
|
+
if (found) {
|
|
58
|
+
servers = found;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!servers[RUFLO_SERVER]) {
|
|
63
|
+
servers = {
|
|
64
|
+
...servers,
|
|
65
|
+
[RUFLO_SERVER]: {
|
|
66
|
+
command: opts.rufloServer.command,
|
|
67
|
+
args: opts.rufloServer.args,
|
|
68
|
+
// The MCP server keys its stores off this, so the worker's tool calls
|
|
69
|
+
// land in the same task/agent stores as the CLI that dispatched it.
|
|
70
|
+
env: { CLAUDE_FLOW_CWD: opts.cwd },
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
mkdirSync(opts.outDir, { recursive: true });
|
|
75
|
+
const path = join(opts.outDir, `mcp-${opts.swarmId}.json`);
|
|
76
|
+
writeFileSync(path, JSON.stringify({ mcpServers: servers }, null, 2), 'utf-8');
|
|
77
|
+
return path;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The ruflo MCP server as launched by THIS cli, so a worker and the CLI that
|
|
81
|
+
* dispatched it share one version and one store format. `@latest` could
|
|
82
|
+
* resolve to a different release mid-session.
|
|
83
|
+
*/
|
|
84
|
+
export function currentRufloServer() {
|
|
85
|
+
const entry = process.argv[1];
|
|
86
|
+
return entry
|
|
87
|
+
? { command: process.execPath, args: [entry, 'mcp', 'start'] }
|
|
88
|
+
: { command: 'npx', args: ['-y', '@claude-flow/cli@latest', 'mcp', 'start'] };
|
|
89
|
+
}
|
|
90
|
+
/** Run one prompt through `claude -p` and parse its JSON result. */
|
|
91
|
+
export const runWithClaude = (prompt, opts) => new Promise((resolve) => {
|
|
92
|
+
const started = Date.now();
|
|
93
|
+
const launch = resolveClaudeLaunchCommand();
|
|
94
|
+
if (!launch) {
|
|
95
|
+
resolve({ ok: false, output: 'Claude Code CLI not found in PATH', exitCode: null, durationMs: 0 });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// The prompt goes on stdin, not argv: a task description has no length
|
|
99
|
+
// bound and `--mcp-config` is variadic (#1780).
|
|
100
|
+
const child = childSpawn(launch.command, [...launch.argsPrefix, '-p', '--output-format', 'json', `--mcp-config=${opts.mcpConfig}`], { cwd: opts.cwd, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
101
|
+
let stdout = '';
|
|
102
|
+
let stderr = '';
|
|
103
|
+
child.stdout.on('data', (d) => (stdout += d));
|
|
104
|
+
child.stderr.on('data', (d) => (stderr += d));
|
|
105
|
+
const timer = setTimeout(() => child.kill('SIGTERM'), opts.timeoutMs);
|
|
106
|
+
child.on('error', (e) => {
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
resolve({ ok: false, output: e.message, exitCode: null, durationMs: Date.now() - started });
|
|
109
|
+
});
|
|
110
|
+
child.on('close', (code, signal) => {
|
|
111
|
+
clearTimeout(timer);
|
|
112
|
+
const durationMs = Date.now() - started;
|
|
113
|
+
try {
|
|
114
|
+
const r = JSON.parse(stdout);
|
|
115
|
+
resolve({
|
|
116
|
+
ok: code === 0 && !r.is_error,
|
|
117
|
+
output: r.result ?? '',
|
|
118
|
+
exitCode: code,
|
|
119
|
+
sessionId: r.session_id,
|
|
120
|
+
costUsd: r.total_cost_usd,
|
|
121
|
+
durationMs,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
const why = signal === 'SIGTERM' ? `timed out after ${opts.timeoutMs}ms` : (stderr || stdout).trim().slice(-2000);
|
|
126
|
+
resolve({ ok: false, output: why, exitCode: code, durationMs });
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
child.stdin.end(prompt);
|
|
130
|
+
});
|
|
131
|
+
function timeoutFromTags(tags, fallback) {
|
|
132
|
+
const tag = tags?.find((t) => /^timeout:\d+s$/.test(t));
|
|
133
|
+
return tag ? Number(tag.slice('timeout:'.length, -1)) * 1000 : fallback;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* One dispatch cycle: the oldest pending hive task goes to an idle hive worker,
|
|
137
|
+
* runs, and its outcome is recorded on the task. Every state change goes
|
|
138
|
+
* through the MCP task tools, so the store is the same one `hive-mind status`
|
|
139
|
+
* and `task status` read.
|
|
140
|
+
*/
|
|
141
|
+
export async function workOnce(opts) {
|
|
142
|
+
let taskId = opts.taskId;
|
|
143
|
+
if (!taskId) {
|
|
144
|
+
const listed = await callMCPTool('task_list', { status: 'pending', type: 'hive-mind', limit: 1000 });
|
|
145
|
+
const oldest = [...(listed.tasks ?? [])].sort((a, b) => a.createdAt.localeCompare(b.createdAt))[0];
|
|
146
|
+
if (!oldest)
|
|
147
|
+
return { dispatched: false, reason: 'No pending hive-mind tasks' };
|
|
148
|
+
taskId = oldest.taskId;
|
|
149
|
+
}
|
|
150
|
+
const task = await callMCPTool('task_status', { taskId });
|
|
151
|
+
if (task.status !== 'pending') {
|
|
152
|
+
return { dispatched: false, reason: `Task ${taskId} is ${task.status}, not pending` };
|
|
153
|
+
}
|
|
154
|
+
let workerId = opts.workerId;
|
|
155
|
+
if (!workerId) {
|
|
156
|
+
const hive = await callMCPTool('hive-mind_status', {});
|
|
157
|
+
workerId = hive.workers?.find((w) => w.status === 'idle')?.id;
|
|
158
|
+
if (!workerId)
|
|
159
|
+
return { dispatched: false, reason: 'No idle hive worker — run "hive-mind spawn" first' };
|
|
160
|
+
}
|
|
161
|
+
await callMCPTool('task_assign', { taskId, agentIds: [workerId] });
|
|
162
|
+
const prompt = `You are hive-mind worker ${workerId}. Complete this task, then reply with ` +
|
|
163
|
+
`the result only.\n\nTask ${taskId}:\n${task.description}`;
|
|
164
|
+
const outcome = await opts.runner(prompt, {
|
|
165
|
+
mcpConfig: opts.mcpConfig,
|
|
166
|
+
timeoutMs: timeoutFromTags(task.tags, opts.defaultTimeoutMs ?? 300_000),
|
|
167
|
+
cwd: opts.cwd,
|
|
168
|
+
});
|
|
169
|
+
const result = {
|
|
170
|
+
worker: workerId,
|
|
171
|
+
output: outcome.output,
|
|
172
|
+
exitCode: outcome.exitCode,
|
|
173
|
+
durationMs: outcome.durationMs,
|
|
174
|
+
...(outcome.sessionId ? { sessionId: outcome.sessionId } : {}),
|
|
175
|
+
...(outcome.costUsd !== undefined ? { costUsd: outcome.costUsd } : {}),
|
|
176
|
+
};
|
|
177
|
+
if (outcome.ok) {
|
|
178
|
+
await callMCPTool('task_complete', { taskId, result });
|
|
179
|
+
return { dispatched: true, taskId, workerId, status: 'completed', outcome };
|
|
180
|
+
}
|
|
181
|
+
await callMCPTool('task_update', { taskId, status: 'failed', result });
|
|
182
|
+
return { dispatched: true, taskId, workerId, status: 'failed', outcome };
|
|
183
|
+
}
|
|
184
|
+
export const workCommand = {
|
|
185
|
+
name: 'work',
|
|
186
|
+
description: 'Run the next pending hive task on an idle worker via Claude Code and record the result',
|
|
187
|
+
options: [
|
|
188
|
+
{ name: 'task', description: 'Run this task id instead of the oldest pending one', type: 'string' },
|
|
189
|
+
{ name: 'worker', description: 'Assign to this worker id instead of the first idle one', type: 'string' },
|
|
190
|
+
{ name: 'mcp-config', description: 'MCP config for the worker (default: generated, with the ruflo server)', type: 'string' },
|
|
191
|
+
],
|
|
192
|
+
examples: [
|
|
193
|
+
{ command: 'claude-flow hive-mind work', description: 'Run the oldest pending hive task' },
|
|
194
|
+
{ command: 'claude-flow hive-mind work --task task-123', description: 'Run a specific task' },
|
|
195
|
+
],
|
|
196
|
+
action: async (ctx) => {
|
|
197
|
+
const cwd = ctx.cwd || process.cwd();
|
|
198
|
+
try {
|
|
199
|
+
const mcpConfig = resolveWorkerMcpConfig({
|
|
200
|
+
explicit: ctx.flags.mcpConfig,
|
|
201
|
+
cwd,
|
|
202
|
+
home: process.env.HOME || process.env.USERPROFILE || '',
|
|
203
|
+
outDir: join(cwd, '.hive-mind', 'sessions'),
|
|
204
|
+
swarmId: 'worker',
|
|
205
|
+
rufloServer: currentRufloServer(),
|
|
206
|
+
});
|
|
207
|
+
const r = await workOnce({
|
|
208
|
+
cwd,
|
|
209
|
+
mcpConfig,
|
|
210
|
+
runner: runWithClaude,
|
|
211
|
+
taskId: ctx.flags.task,
|
|
212
|
+
workerId: ctx.flags.worker,
|
|
213
|
+
});
|
|
214
|
+
if (ctx.flags.format === 'json') {
|
|
215
|
+
output.printJson(r);
|
|
216
|
+
}
|
|
217
|
+
else if (!r.dispatched) {
|
|
218
|
+
output.printInfo(r.reason);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
output.printBox([
|
|
222
|
+
`Task ID: ${r.taskId}`,
|
|
223
|
+
`Worker: ${r.workerId}`,
|
|
224
|
+
`Status: ${r.status}`,
|
|
225
|
+
`Duration: ${r.outcome.durationMs}ms`,
|
|
226
|
+
'',
|
|
227
|
+
r.outcome.output.slice(0, 2000),
|
|
228
|
+
].join('\n'), r.status === 'completed' ? 'Task Completed' : 'Task Failed');
|
|
229
|
+
output.writeln(output.dim(` Full record: claude-flow task status ${r.taskId}`));
|
|
230
|
+
}
|
|
231
|
+
// Nothing to do is not an error; a task that ran and failed is.
|
|
232
|
+
const failed = r.dispatched && r.status === 'failed';
|
|
233
|
+
return { success: !failed, exitCode: failed ? 1 : 0, data: r };
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
const msg = error instanceof MCPClientError ? error.message : String(error);
|
|
237
|
+
output.printError(`Work error: ${msg}`);
|
|
238
|
+
return { success: false, exitCode: 1 };
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
//# sourceMappingURL=hive-mind-worker.js.map
|
|
@@ -10,7 +10,7 @@ import { select, confirm, input } from '../prompt.js';
|
|
|
10
10
|
import { callMCPTool, MCPClientError } from '../mcp-client.js';
|
|
11
11
|
import { spawn as childSpawn } from 'child_process';
|
|
12
12
|
import { mkdir, writeFile } from 'fs/promises';
|
|
13
|
-
import {
|
|
13
|
+
import { resolveWorkerMcpConfig, currentRufloServer, workCommand } from './hive-mind-worker.js';
|
|
14
14
|
import { join } from 'path';
|
|
15
15
|
import { resolveClaudeLaunchCommand } from '../runtime/claude-command.js';
|
|
16
16
|
import { getHiveTokenForCli } from '../mcp-tools/hive-mind-tools.js';
|
|
@@ -202,44 +202,25 @@ async function spawnClaudeCodeInstance(swarmId, swarmName, objective, workers, f
|
|
|
202
202
|
// Build arguments - flags first, then prompt
|
|
203
203
|
const claudeArgs = [];
|
|
204
204
|
// #1748 Issue 2 — pass --mcp-config so the spawned worker actually has
|
|
205
|
-
// mcp__ruflo__* tools registered.
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
mcpConfigPath = c;
|
|
225
|
-
break;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
catch { /* continue */ }
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
if (mcpConfigPath) {
|
|
232
|
-
// #1780 — Claude Code's `--mcp-config` is variadic; passing it as two
|
|
233
|
-
// argv tokens (`--mcp-config`, `<path>`) lets a later positional (the
|
|
234
|
-
// hive-mind prompt) be slurped as a second config file, producing
|
|
235
|
-
// `ENAMETOOLONG: name too long, open` once the prompt exceeds PATH_MAX.
|
|
236
|
-
// Use `=`-syntax so the flag stays attached to its single value.
|
|
237
|
-
claudeArgs.push(`--mcp-config=${mcpConfigPath}`);
|
|
238
|
-
output.printInfo(`Spawned worker MCP config: ${mcpConfigPath}`);
|
|
239
|
-
}
|
|
240
|
-
else {
|
|
241
|
-
output.printWarning('No .mcp.json or ~/.claude.json found — spawned worker will not have mcp__ruflo__* tools (#1748 Issue 2). Pass --mcp-config <path> or run "ruflo init" to generate one.');
|
|
242
|
-
}
|
|
205
|
+
// mcp__ruflo__* tools registered. The config is always a generated file
|
|
206
|
+
// that Claude accepts; see resolveWorkerMcpConfig for why a source
|
|
207
|
+
// config (notably ~/.claude.json) is never passed through as-is.
|
|
208
|
+
// The parser stores flags camelCased (#2269), so read both forms.
|
|
209
|
+
const mcpConfigPath = resolveWorkerMcpConfig({
|
|
210
|
+
explicit: (flags['mcp-config'] ?? flags.mcpConfig),
|
|
211
|
+
cwd: process.cwd(),
|
|
212
|
+
home: process.env.HOME || process.env.USERPROFILE || '',
|
|
213
|
+
outDir: sessionsDir,
|
|
214
|
+
swarmId,
|
|
215
|
+
rufloServer: currentRufloServer(),
|
|
216
|
+
});
|
|
217
|
+
// #1780 — Claude Code's `--mcp-config` is variadic; passing it as two
|
|
218
|
+
// argv tokens (`--mcp-config`, `<path>`) lets a later positional (the
|
|
219
|
+
// hive-mind prompt) be slurped as a second config file, producing
|
|
220
|
+
// `ENAMETOOLONG: name too long, open` once the prompt exceeds PATH_MAX.
|
|
221
|
+
// Use `=`-syntax so the flag stays attached to its single value.
|
|
222
|
+
claudeArgs.push(`--mcp-config=${mcpConfigPath}`);
|
|
223
|
+
output.printInfo(`Spawned worker MCP config: ${mcpConfigPath}`);
|
|
243
224
|
// Check for non-interactive mode
|
|
244
225
|
const isNonInteractive = flags['non-interactive'] || flags.nonInteractive;
|
|
245
226
|
if (isNonInteractive) {
|
|
@@ -882,7 +863,8 @@ const taskCommand = {
|
|
|
882
863
|
].join('\n'), 'Task Submitted');
|
|
883
864
|
output.writeln();
|
|
884
865
|
output.printSuccess('Task submitted to hive');
|
|
885
|
-
output.writeln(output.dim(`
|
|
866
|
+
output.writeln(output.dim(` Run it with: claude-flow hive-mind work --task ${result.taskId}`));
|
|
867
|
+
output.writeln(output.dim(` Track with: claude-flow task status ${result.taskId}`));
|
|
886
868
|
return { success: true, data: result };
|
|
887
869
|
}
|
|
888
870
|
catch (error) {
|
|
@@ -1210,7 +1192,7 @@ export const hiveMindCommand = {
|
|
|
1210
1192
|
name: 'hive-mind',
|
|
1211
1193
|
aliases: ['hive'],
|
|
1212
1194
|
description: 'Queen-led consensus-based multi-agent coordination',
|
|
1213
|
-
subcommands: [initCommand, spawnCommand, statusCommand, taskCommand, joinCommand, leaveCommand, consensusCommand, broadcastCommand, memorySubCommand, optimizeMemoryCommand, shutdownCommand],
|
|
1195
|
+
subcommands: [initCommand, spawnCommand, statusCommand, taskCommand, workCommand, joinCommand, leaveCommand, consensusCommand, broadcastCommand, memorySubCommand, optimizeMemoryCommand, shutdownCommand],
|
|
1214
1196
|
options: [],
|
|
1215
1197
|
examples: [
|
|
1216
1198
|
{ command: 'claude-flow hive-mind init -t hierarchical-mesh', description: 'Initialize hive' },
|
|
@@ -1230,6 +1212,7 @@ export const hiveMindCommand = {
|
|
|
1230
1212
|
`${output.highlight('spawn')} - Spawn worker agents (use --claude to launch Claude Code)`,
|
|
1231
1213
|
`${output.highlight('status')} - Show hive status`,
|
|
1232
1214
|
`${output.highlight('task')} - Submit task to hive`,
|
|
1215
|
+
`${output.highlight('work')} - Run the next pending task on an idle worker`,
|
|
1233
1216
|
`${output.highlight('join')} - Join an agent to the hive`,
|
|
1234
1217
|
`${output.highlight('leave')} - Remove an agent from the hive`,
|
|
1235
1218
|
`${output.highlight('consensus')} - Manage consensus proposals`,
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* V3 CLI Hooks Command
|
|
3
|
-
* Self-learning hooks system for intelligent workflow automation
|
|
4
|
-
*/
|
|
1
|
+
import { validateFeedbackPatterns } from '../memory/feedback-patterns.js';
|
|
5
2
|
import { output } from '../output.js';
|
|
6
3
|
import { confirm } from '../prompt.js';
|
|
7
4
|
import { callMCPTool, MCPClientError } from '../mcp-client.js';
|
|
@@ -572,6 +569,11 @@ const postCommandCommand = {
|
|
|
572
569
|
short: 'd',
|
|
573
570
|
description: 'Execution duration in milliseconds',
|
|
574
571
|
type: 'number'
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
name: 'ttl',
|
|
575
|
+
description: 'Command history lifetime in seconds (default: 30 days)',
|
|
576
|
+
type: 'number'
|
|
575
577
|
}
|
|
576
578
|
],
|
|
577
579
|
examples: [
|
|
@@ -594,11 +596,16 @@ const postCommandCommand = {
|
|
|
594
596
|
success,
|
|
595
597
|
exitCode: ctx.flags.exitCode || 0,
|
|
596
598
|
duration: ctx.flags.duration,
|
|
599
|
+
ttl: ctx.flags.ttl,
|
|
597
600
|
timestamp: Date.now(),
|
|
598
601
|
});
|
|
599
602
|
if (ctx.flags.format === 'json') {
|
|
600
603
|
output.printJson(result);
|
|
601
|
-
return { success:
|
|
604
|
+
return { success: result.recorded, exitCode: result.recorded ? 0 : 1, data: result };
|
|
605
|
+
}
|
|
606
|
+
if (!result.recorded) {
|
|
607
|
+
output.printError('Command outcome could not be recorded');
|
|
608
|
+
return { success: false, exitCode: 1, data: result };
|
|
602
609
|
}
|
|
603
610
|
output.writeln();
|
|
604
611
|
output.printSuccess('Command outcome recorded');
|
|
@@ -1699,6 +1706,7 @@ const postTaskCommand = {
|
|
|
1699
1706
|
name: 'post-task',
|
|
1700
1707
|
description: 'Record task completion for learning',
|
|
1701
1708
|
options: [
|
|
1709
|
+
{ name: 'patterns', description: 'JSON array of learned pattern strings for feedback and skill creation', type: 'string' },
|
|
1702
1710
|
{
|
|
1703
1711
|
name: 'task-id',
|
|
1704
1712
|
short: 'i',
|
|
@@ -1784,8 +1792,10 @@ const postTaskCommand = {
|
|
|
1784
1792
|
const success = ctx.flags.success !== undefined ? ctx.flags.success : true;
|
|
1785
1793
|
output.printInfo(`Recording outcome for task: ${output.highlight(taskId)}`);
|
|
1786
1794
|
try {
|
|
1795
|
+
const patterns = validateFeedbackPatterns(ctx.flags.patterns === undefined ? undefined : JSON.parse(ctx.flags.patterns));
|
|
1787
1796
|
const result = await callMCPTool('hooks_post-task', {
|
|
1788
1797
|
taskId,
|
|
1798
|
+
patterns,
|
|
1789
1799
|
success,
|
|
1790
1800
|
quality: ctx.flags.quality,
|
|
1791
1801
|
agent: ctx.flags.agent,
|