@claude-flow/cli 3.45.0 → 3.46.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/helpers.manifest.json +2 -2
- package/README.md +2 -0
- package/bin/cli.js +13 -0
- package/bin/mcp-server.js +16 -0
- package/catalog-manifest.json +2 -2
- package/dist/src/commands/doctor.d.ts +3 -0
- package/dist/src/commands/doctor.js +131 -2
- package/dist/src/commands/embeddings.js +50 -7
- package/dist/src/commands/init.js +5 -2
- package/dist/src/commands/mcp.js +27 -4
- package/dist/src/mcp-server.d.ts +50 -6
- package/dist/src/mcp-server.js +267 -33
- package/dist/src/mcp-tools/agent-execute-core.js +7 -0
- package/dist/src/mcp-tools/agentdb-tools.js +20 -8
- package/dist/src/mcp-tools/browser-tools.js +30 -23
- package/dist/src/mcp-tools/memory-tools.js +9 -4
- package/dist/src/mcp-tools/metaharness-tools.js +1 -1
- package/dist/src/mcp-tools/policy-enforcer.d.ts +4 -4
- package/dist/src/mcp-tools/policy-enforcer.js +4 -4
- package/dist/src/mcp-tools/terminal-tools.js +11 -1
- package/dist/src/mcp-tools/workflow-tools.js +1 -1
- package/dist/src/memory/ewc-consolidation.d.ts +16 -1
- package/dist/src/memory/ewc-consolidation.js +63 -13
- package/dist/src/memory/intelligence.d.ts +6 -2
- package/dist/src/memory/intelligence.js +18 -11
- package/dist/src/memory/memory-bridge.js +84 -0
- package/dist/src/ruvector/model-router.js +19 -1
- package/dist/src/services/distill-oracle.js +2 -1
- package/dist/src/services/git-workspace-identity.js +5 -8
- package/dist/src/services/policy-runtime.js +114 -4
- package/dist/src/services/worker-daemon.js +8 -3
- package/node_modules/@claude-flow/codex/dist/worktrees/coordinator.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/worktrees/coordinator.js +2 -6
- package/node_modules/@claude-flow/codex/dist/worktrees/coordinator.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.js +14 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.d.ts +40 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.d.ts.map +1 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.js +100 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.js.map +1 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/index.d.ts +1 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/index.d.ts.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/index.js +1 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/index.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/package.json +1 -0
- package/node_modules/@claude-flow/security/dist/input-validator.d.ts +6 -6
- package/node_modules/@claude-flow/security/dist/safe-git.d.ts +22 -0
- package/node_modules/@claude-flow/security/dist/safe-git.d.ts.map +1 -0
- package/node_modules/@claude-flow/security/dist/safe-git.js +63 -0
- package/node_modules/@claude-flow/security/dist/safe-git.js.map +1 -0
- package/package.json +5 -5
- package/plugins/ruflo-metaharness/agents/metaharness-architect.md +1 -1
- package/plugins/ruflo-metaharness/commands/ruflo-metaharness.md +1 -1
- package/plugins/ruflo-metaharness/scripts/_darwin.mjs +84 -12
- package/plugins/ruflo-metaharness/scripts/_harness.mjs +10 -4
- package/plugins/ruflo-metaharness/scripts/_invoke.mjs +120 -6
- package/plugins/ruflo-metaharness/scripts/_redblue.mjs +33 -6
- package/plugins/ruflo-metaharness/scripts/audit-list.mjs +11 -10
- package/plugins/ruflo-metaharness/scripts/audit-trend.mjs +8 -7
- package/plugins/ruflo-metaharness/scripts/drift-from-history.mjs +2 -3
- package/plugins/ruflo-metaharness/scripts/oia-audit.mjs +8 -7
- package/plugins/ruflo-metaharness/scripts/similarity.mjs +9 -8
- package/plugins/ruflo-metaharness/scripts/smoke.sh +31 -0
- package/plugins/ruflo-metaharness/scripts/test-no-tool-time-npx.mjs +319 -0
- package/plugins/ruflo-metaharness/scripts/test-pin-alignment.mjs +182 -0
- package/plugins/ruflo-metaharness/skills/harness-evolve/SKILL.md +4 -2
- package/plugins/ruflo-metaharness/skills/harness-gepa/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-learn/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-mcp-scan/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-score/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-security-bench/SKILL.md +3 -1
- package/plugins/ruflo-metaharness/skills/harness-threat-model/SKILL.md +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest": {
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.46.0",
|
|
4
4
|
"files": {
|
|
5
5
|
"auto-memory-hook.mjs": "85fe05c757421c52137c0bc8545a0896bab6b4714538c2a11d1d0835bfcc8c1c",
|
|
6
6
|
"hook-handler.cjs": "209d9fafe10e17d1be0866727f6f9cf9ac66f9a0793f1c793a4f58319e8e4583",
|
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
"router.js": "b6998397e7883191b62229ccdc66fb87f1ccca1d5b2039ecb8c1e686d1ad81f8"
|
|
10
10
|
}
|
|
11
11
|
},
|
|
12
|
-
"signature": "
|
|
12
|
+
"signature": "LwKXka3qAKGSIVpcv4vpg5vlv8CFHolvyCQwzW0hNvPIn7Un3+qpidc7drdoDzsrNyzx3gnZJBFEWeXxlTabDA==",
|
|
13
13
|
"algorithm": "ed25519"
|
|
14
14
|
}
|
package/README.md
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
|
|
20
20
|
# Ruflo
|
|
21
21
|
|
|
22
|
+
[English](README.md) · [简体中文](README.zh-CN.md)
|
|
23
|
+
|
|
22
24
|
**An agent meta-harness for Claude Code and Codex.**
|
|
23
25
|
|
|
24
26
|
[](docs/ruflo-explained.md)
|
package/bin/cli.js
CHANGED
|
@@ -164,6 +164,8 @@ const isMCPMode = !process.stdin.isTTY
|
|
|
164
164
|
if (isMCPMode) {
|
|
165
165
|
// Run MCP server mode
|
|
166
166
|
const { listMCPTools, callMCPTool, hasTool } = await import('../dist/src/mcp-client.js');
|
|
167
|
+
const { isPolicyEnforcementEnabled, loadMcpPolicy, evaluateToolCall } =
|
|
168
|
+
await import('../dist/src/mcp-tools/policy-enforcer.js');
|
|
167
169
|
|
|
168
170
|
const VERSION = '3.0.0';
|
|
169
171
|
const sessionId = `mcp-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
@@ -287,6 +289,17 @@ if (isMCPMode) {
|
|
|
287
289
|
};
|
|
288
290
|
}
|
|
289
291
|
|
|
292
|
+
if (isPolicyEnforcementEnabled()) {
|
|
293
|
+
const check = evaluateToolCall(loadMcpPolicy(), sessionId, toolName);
|
|
294
|
+
if (!check.allowed) {
|
|
295
|
+
return {
|
|
296
|
+
jsonrpc: '2.0',
|
|
297
|
+
id: message.id,
|
|
298
|
+
error: { code: -32001, message: `Policy denied: ${check.reason}` },
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
290
303
|
try {
|
|
291
304
|
const result = await callMCPTool(toolName, toolParams, { sessionId });
|
|
292
305
|
return {
|
package/bin/mcp-server.js
CHANGED
|
@@ -28,6 +28,11 @@ console.log = (...args) => {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
import { listMCPTools, callMCPTool, hasTool } from '../dist/src/mcp-client.js';
|
|
31
|
+
import {
|
|
32
|
+
isPolicyEnforcementEnabled,
|
|
33
|
+
loadMcpPolicy,
|
|
34
|
+
evaluateToolCall,
|
|
35
|
+
} from '../dist/src/mcp-tools/policy-enforcer.js';
|
|
31
36
|
|
|
32
37
|
const VERSION = '3.0.0';
|
|
33
38
|
const sessionId = `mcp-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
@@ -173,6 +178,17 @@ async function handleMessage(message) {
|
|
|
173
178
|
};
|
|
174
179
|
}
|
|
175
180
|
|
|
181
|
+
if (isPolicyEnforcementEnabled()) {
|
|
182
|
+
const check = evaluateToolCall(loadMcpPolicy(), sessionId, toolName);
|
|
183
|
+
if (!check.allowed) {
|
|
184
|
+
return {
|
|
185
|
+
jsonrpc: '2.0',
|
|
186
|
+
id: message.id,
|
|
187
|
+
error: { code: -32001, message: `Policy denied: ${check.reason}` },
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
176
192
|
try {
|
|
177
193
|
const result = await callMCPTool(toolName, toolParams, { sessionId });
|
|
178
194
|
return {
|
package/catalog-manifest.json
CHANGED
|
@@ -11,6 +11,9 @@ interface HealthCheck {
|
|
|
11
11
|
message: string;
|
|
12
12
|
fix?: string;
|
|
13
13
|
}
|
|
14
|
+
/** ADR-122 Phase 0: probe only the CLI version, without launching a browser. */
|
|
15
|
+
export declare function evaluateAgentBrowserVersion(rawOutput: string): HealthCheck;
|
|
16
|
+
export declare function checkAgentBrowserVersion(probe?: () => Promise<string>): Promise<HealthCheck>;
|
|
14
17
|
export declare function checkMemoryPersistenceDriver(): Promise<HealthCheck>;
|
|
15
18
|
/**
|
|
16
19
|
* #3392: pure verdict for "does the @claude-flow/memory the CLI loads satisfy
|
|
@@ -9,7 +9,7 @@ import { existsSync, readFileSync, statSync, openSync, readSync, closeSync } fro
|
|
|
9
9
|
import { join, dirname } from 'path';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
import { createHash } from 'crypto';
|
|
12
|
-
import { execSync, exec } from 'child_process';
|
|
12
|
+
import { execSync, exec, execFile } from 'child_process';
|
|
13
13
|
import { promisify } from 'util';
|
|
14
14
|
import { decodeKey, isEncryptionEnabled } from '../encryption/vault.js';
|
|
15
15
|
import { isEncryptedBlob } from '../encryption/vault.js';
|
|
@@ -17,6 +17,7 @@ import * as semver from 'semver';
|
|
|
17
17
|
import { resolveMemoryPackageFromProject, resolveMemoryPackageFromCli, readMemoryPackageVersion, recordMemoryPackagePath, } from '../init/memory-package-resolver.js';
|
|
18
18
|
// Promisified exec with proper shell and env inheritance for cross-platform support
|
|
19
19
|
const execAsync = promisify(exec);
|
|
20
|
+
const execFileAsync = promisify(execFile);
|
|
20
21
|
/**
|
|
21
22
|
* Execute command asynchronously with proper environment inheritance
|
|
22
23
|
* Critical for Windows where PATH may not be inherited properly
|
|
@@ -62,6 +63,39 @@ async function checkNpmVersion() {
|
|
|
62
63
|
return { name: 'npm Version', status: 'fail', message: 'npm not found', fix: 'Install Node.js from https://nodejs.org' };
|
|
63
64
|
}
|
|
64
65
|
}
|
|
66
|
+
/** ADR-122 Phase 0: probe only the CLI version, without launching a browser. */
|
|
67
|
+
export function evaluateAgentBrowserVersion(rawOutput) {
|
|
68
|
+
const name = 'agent-browser CLI (ADR-122)';
|
|
69
|
+
const fix = 'npm install -g agent-browser@latest';
|
|
70
|
+
const versionOutput = rawOutput.trim().replace(/\x1b\[[0-9;]*m/g, '');
|
|
71
|
+
const match = /^(?:agent-browser(?:\s+version)?\s+)?v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(?:\s|$)/i.exec(versionOutput);
|
|
72
|
+
const version = match ? semver.parse(match[1]) : null;
|
|
73
|
+
if (!version) {
|
|
74
|
+
return { name, status: 'warn', message: `Unable to parse agent-browser version${versionOutput ? `: ${versionOutput.slice(0, 120)}` : ' (empty output)'}. Browser MCP tools may be unavailable.`, fix };
|
|
75
|
+
}
|
|
76
|
+
if (semver.lt(version, '0.27.0')) {
|
|
77
|
+
return { name, status: 'warn', message: `v${version.version} is below the ADR-122 v0.27.0 minimum for browser MCP tools`, fix };
|
|
78
|
+
}
|
|
79
|
+
return { name, status: 'pass', message: `v${version.version} meets the ADR-122 v0.27.0 minimum for browser MCP tools` };
|
|
80
|
+
}
|
|
81
|
+
export async function checkAgentBrowserVersion(probe = async () => {
|
|
82
|
+
const { stdout } = await execFileAsync('agent-browser', ['--version'], {
|
|
83
|
+
encoding: 'utf8', timeout: 3000, maxBuffer: 16 * 1024, windowsHide: true,
|
|
84
|
+
});
|
|
85
|
+
return String(stdout);
|
|
86
|
+
}) {
|
|
87
|
+
try {
|
|
88
|
+
return evaluateAgentBrowserVersion(await probe());
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
const name = 'agent-browser CLI (ADR-122)';
|
|
92
|
+
const fix = 'npm install -g agent-browser@latest';
|
|
93
|
+
const code = error.code;
|
|
94
|
+
return code === 'ENOENT'
|
|
95
|
+
? { name, status: 'warn', message: 'Not found on PATH; @claude-flow/browser and browser MCP tools need agent-browser v0.27.0 or newer', fix }
|
|
96
|
+
: { name, status: 'warn', message: `Version check failed: ${error instanceof Error ? error.message : String(error)}`, fix };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
65
99
|
// Check config file
|
|
66
100
|
async function checkConfigFile() {
|
|
67
101
|
// JSON configs (parse-validated). The first three are LEGACY shapes from
|
|
@@ -306,6 +340,22 @@ async function resolveMemoryDbPath() {
|
|
|
306
340
|
return p;
|
|
307
341
|
return null;
|
|
308
342
|
}
|
|
343
|
+
/** Native AgentDB writes to a plaintext sibling of the sql.js memory file.
|
|
344
|
+
* Probe only stores that already exist: projects without the native bridge
|
|
345
|
+
* should not acquire an extra warning just by running doctor (#3195). */
|
|
346
|
+
async function existingNativeAgentDbPaths() {
|
|
347
|
+
const candidates = new Set();
|
|
348
|
+
try {
|
|
349
|
+
const { getMemoryRoot, resolveDbPath } = await import('../memory/memory-initializer.js');
|
|
350
|
+
candidates.add(join(getMemoryRoot(), 'agentdb-memory.db'));
|
|
351
|
+
// Explicit CLAUDE_FLOW_DB_PATH callers may put the sibling elsewhere.
|
|
352
|
+
candidates.add(join(dirname(resolveDbPath()), 'agentdb-memory.db'));
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
candidates.add(join(process.cwd(), '.swarm', 'agentdb-memory.db'));
|
|
356
|
+
}
|
|
357
|
+
return [...candidates].filter((path) => existsSync(path));
|
|
358
|
+
}
|
|
309
359
|
/** Open a sql.js Database over an on-disk file, returning null when the
|
|
310
360
|
* file can't be opened as a SQLite database (encrypted / corrupted / not
|
|
311
361
|
* a database). Callers decide whether that's warn or fail. */
|
|
@@ -507,6 +557,79 @@ async function checkMemoryStructuralIntegrity() {
|
|
|
507
557
|
catch { /* best-effort */ }
|
|
508
558
|
}
|
|
509
559
|
}
|
|
560
|
+
/** Check the AgentDB authority independently from the legacy memory.db.
|
|
561
|
+
* Opening read-only with SQLite keeps WAL commits visible without mutating
|
|
562
|
+
* either store; the existing legacy health checks retain their own scope. */
|
|
563
|
+
async function checkNativeAgentDbStructuralIntegrity(dbPath) {
|
|
564
|
+
const NAME = 'Native AgentDB Structural Integrity (quick_check)';
|
|
565
|
+
if (!existsSync(dbPath)) {
|
|
566
|
+
return { name: NAME, status: 'warn', message: `${dbPath} disappeared before the native check could run` };
|
|
567
|
+
}
|
|
568
|
+
if (isMemoryDbEncryptedAtRest(dbPath)) {
|
|
569
|
+
return {
|
|
570
|
+
name: NAME,
|
|
571
|
+
status: 'fail',
|
|
572
|
+
message: `${dbPath} — RFE1-encrypted; native AgentDB requires a plaintext SQLite file`,
|
|
573
|
+
fix: `back up ${dbPath} and restore a usable native AgentDB database`,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
let Database;
|
|
577
|
+
try {
|
|
578
|
+
Database = (await import('better-sqlite3')).default;
|
|
579
|
+
}
|
|
580
|
+
catch {
|
|
581
|
+
return {
|
|
582
|
+
name: NAME,
|
|
583
|
+
status: 'warn',
|
|
584
|
+
message: `${dbPath} — better-sqlite3 not installed; native AgentDB structural health is unverified`,
|
|
585
|
+
fix: 'install better-sqlite3 with npm install scripts enabled, then rerun this check',
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
let db;
|
|
589
|
+
try {
|
|
590
|
+
db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
591
|
+
}
|
|
592
|
+
catch (error) {
|
|
593
|
+
if (isNativeSqliteBindingUnavailable(error)) {
|
|
594
|
+
return nativeBindingUnavailableCheck(NAME, dbPath, error, '[native AgentDB structural health unverified]');
|
|
595
|
+
}
|
|
596
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
597
|
+
return {
|
|
598
|
+
name: NAME,
|
|
599
|
+
status: 'fail',
|
|
600
|
+
message: `${dbPath} — better-sqlite3 failed to open: ${message} [native AgentDB]`,
|
|
601
|
+
fix: `back up ${dbPath} and its WAL sidecars, then restore a known-good native AgentDB database`,
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
try {
|
|
605
|
+
const rows = db.pragma('quick_check');
|
|
606
|
+
const values = rows.map((row) => String(Object.values(row)[0]));
|
|
607
|
+
if (values.length === 1 && values[0] === 'ok') {
|
|
608
|
+
return { name: NAME, status: 'pass', message: `${dbPath} — PRAGMA quick_check: ok [native AgentDB, WAL-aware]` };
|
|
609
|
+
}
|
|
610
|
+
return {
|
|
611
|
+
name: NAME,
|
|
612
|
+
status: 'fail',
|
|
613
|
+
message: `${dbPath} — PRAGMA quick_check: ${values.slice(0, 3).join('; ')}${values.length > 3 ? ` (+${values.length - 3} more)` : ''} [native AgentDB]`,
|
|
614
|
+
fix: `back up ${dbPath} and its WAL sidecars, then restore a known-good native AgentDB database`,
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
catch (error) {
|
|
618
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
619
|
+
return {
|
|
620
|
+
name: NAME,
|
|
621
|
+
status: 'fail',
|
|
622
|
+
message: `${dbPath} — quick_check probe threw: ${message} [native AgentDB]`,
|
|
623
|
+
fix: `back up ${dbPath} and its WAL sidecars, then restore a known-good native AgentDB database`,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
finally {
|
|
627
|
+
try {
|
|
628
|
+
db.close();
|
|
629
|
+
}
|
|
630
|
+
catch { /* best-effort */ }
|
|
631
|
+
}
|
|
632
|
+
}
|
|
510
633
|
// #2968/#3321 — read-only native SQLite capability probe. A skipped
|
|
511
634
|
// postinstall can leave the wrapper importable but its binding unavailable.
|
|
512
635
|
// Schema size cannot identify the runtime driver or the database's history:
|
|
@@ -2337,7 +2460,7 @@ export const doctorCommand = {
|
|
|
2337
2460
|
{
|
|
2338
2461
|
name: 'component',
|
|
2339
2462
|
short: 'c',
|
|
2340
|
-
description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, mcp-overhead, claude, disk, typescript, agentic-flow, encryption, federation, funnel, proxy, auth, typesafe, metaharness)',
|
|
2463
|
+
description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, mcp-overhead, claude, browser, disk, typescript, agentic-flow, encryption, federation, funnel, proxy, auth, typesafe, metaharness)',
|
|
2341
2464
|
type: 'string'
|
|
2342
2465
|
},
|
|
2343
2466
|
{
|
|
@@ -2444,11 +2567,14 @@ export const doctorCommand = {
|
|
|
2444
2567
|
output.writeln(output.dim('System diagnostics and health check'));
|
|
2445
2568
|
output.writeln(output.dim('─'.repeat(50)));
|
|
2446
2569
|
output.writeln();
|
|
2570
|
+
const nativeAgentDbChecks = (await existingNativeAgentDbPaths())
|
|
2571
|
+
.map((dbPath) => () => checkNativeAgentDbStructuralIntegrity(dbPath));
|
|
2447
2572
|
const allChecks = [
|
|
2448
2573
|
checkVersionFreshness,
|
|
2449
2574
|
checkNodeVersion,
|
|
2450
2575
|
checkNpmVersion,
|
|
2451
2576
|
checkClaudeCode,
|
|
2577
|
+
checkAgentBrowserVersion, // ADR-122 Phase 0 — browser runtime compatibility
|
|
2452
2578
|
checkGit,
|
|
2453
2579
|
checkGitRepo,
|
|
2454
2580
|
checkConfigFile,
|
|
@@ -2456,6 +2582,7 @@ export const doctorCommand = {
|
|
|
2456
2582
|
checkDaemonStatus,
|
|
2457
2583
|
checkMemoryDatabase,
|
|
2458
2584
|
checkMemoryStructuralIntegrity, // #2737 — bounded, native quick_check on every default run
|
|
2585
|
+
...nativeAgentDbChecks, // #3195 — verify each present native authority file
|
|
2459
2586
|
checkMemoryPersistenceDriver, // #2968/#3321 — read-only native capability probe
|
|
2460
2587
|
checkLearningBridge, // #2545 — can the auto-memory hook actually load @claude-flow/memory?
|
|
2461
2588
|
checkMemoryPackageVersion, // #3392 — loaded @claude-flow/memory must satisfy the CLI's declared range
|
|
@@ -2489,12 +2616,14 @@ export const doctorCommand = {
|
|
|
2489
2616
|
'node': checkNodeVersion,
|
|
2490
2617
|
'npm': checkNpmVersion,
|
|
2491
2618
|
'claude': checkClaudeCode,
|
|
2619
|
+
'browser': checkAgentBrowserVersion,
|
|
2492
2620
|
'config': checkConfigFile,
|
|
2493
2621
|
'stale-settings': checkStaleSettingsNpx, // #2448
|
|
2494
2622
|
'daemon': checkDaemonStatus,
|
|
2495
2623
|
'memory': [
|
|
2496
2624
|
checkMemoryDatabase, // existing: exists + statable (unchanged)
|
|
2497
2625
|
checkMemoryIntegrity, // #2677 check 1: sql.js open + PRAGMA integrity_check
|
|
2626
|
+
...nativeAgentDbChecks, // #3195: present AgentDB authority, checked independently
|
|
2498
2627
|
checkMemoryPersistenceDriver, // #2968/#3321: read-only native capability probe
|
|
2499
2628
|
checkMemoryPackageVersion, // #3392: loaded memory package satisfies the declared range
|
|
2500
2629
|
checkMemoryContent, // #2677 check 2: memory_entries content coverage
|
|
@@ -679,7 +679,12 @@ const initCommand = {
|
|
|
679
679
|
if (!fs.existsSync(modelDir)) {
|
|
680
680
|
fs.mkdirSync(modelDir, { recursive: true });
|
|
681
681
|
}
|
|
682
|
-
// Download model if requested
|
|
682
|
+
// Download model if requested. #3376: record what actually happened
|
|
683
|
+
// instead of assuming the download ran — `getEmbeddings()` returns null
|
|
684
|
+
// whenever the optional @claude-flow/embeddings package is absent, which
|
|
685
|
+
// is every plain `npm i -g ruflo` install.
|
|
686
|
+
let modelDownloaded = false;
|
|
687
|
+
let modelSkipReason = null;
|
|
683
688
|
if (download) {
|
|
684
689
|
spinner.setText(`Downloading ONNX model: ${model}...`);
|
|
685
690
|
const embeddings = await getEmbeddings();
|
|
@@ -687,13 +692,19 @@ const initCommand = {
|
|
|
687
692
|
await embeddings.downloadEmbeddingModel(model, modelDir, (p) => {
|
|
688
693
|
spinner.setText(`Downloading ${model}... ${p.percent.toFixed(0)}%`);
|
|
689
694
|
});
|
|
695
|
+
modelDownloaded = true;
|
|
690
696
|
}
|
|
691
697
|
else {
|
|
692
|
-
//
|
|
693
|
-
|
|
694
|
-
|
|
698
|
+
// No download happened. That is the outcome of this command, not an
|
|
699
|
+
// aside — reported below via spinner.fail + a warning, never as a
|
|
700
|
+
// dimmed note on a success path. No fake progress delay: there is
|
|
701
|
+
// nothing in progress.
|
|
702
|
+
modelSkipReason = '@claude-flow/embeddings is not installed';
|
|
695
703
|
}
|
|
696
704
|
}
|
|
705
|
+
else {
|
|
706
|
+
modelSkipReason = 'download disabled by --no-download';
|
|
707
|
+
}
|
|
697
708
|
// Write embeddings config
|
|
698
709
|
spinner.setText('Writing configuration...');
|
|
699
710
|
const dimension = model.includes('mpnet') ? 768 : 384;
|
|
@@ -702,6 +713,12 @@ const initCommand = {
|
|
|
702
713
|
modelPath: modelDir,
|
|
703
714
|
dimension,
|
|
704
715
|
cacheSize,
|
|
716
|
+
// #3376: additive, optional fields. `modelPath` keeps its old meaning
|
|
717
|
+
// (the directory the model belongs in), but it no longer implies a
|
|
718
|
+
// model is in it — `modelDownloaded` says whether one is. Readers that
|
|
719
|
+
// predate these keys are unaffected; they ignore unknown properties.
|
|
720
|
+
modelDownloaded,
|
|
721
|
+
...(modelSkipReason ? { modelSkipReason } : {}),
|
|
705
722
|
hyperbolic: {
|
|
706
723
|
enabled: hyperbolic,
|
|
707
724
|
curvature,
|
|
@@ -716,7 +733,15 @@ const initCommand = {
|
|
|
716
733
|
initialized: new Date().toISOString(),
|
|
717
734
|
};
|
|
718
735
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
719
|
-
|
|
736
|
+
// A download that was asked for and did not happen is a failed init:
|
|
737
|
+
// the only substantive step of `embeddings init` did not run. #3376.
|
|
738
|
+
const downloadSkipped = download && !modelDownloaded;
|
|
739
|
+
if (downloadSkipped) {
|
|
740
|
+
spinner.fail('Embedding model NOT downloaded — embedding subsystem is not ready');
|
|
741
|
+
}
|
|
742
|
+
else {
|
|
743
|
+
spinner.succeed('Embedding subsystem initialized');
|
|
744
|
+
}
|
|
720
745
|
output.writeln();
|
|
721
746
|
output.printTable({
|
|
722
747
|
columns: [
|
|
@@ -725,6 +750,7 @@ const initCommand = {
|
|
|
725
750
|
],
|
|
726
751
|
data: [
|
|
727
752
|
{ setting: 'Model', value: model },
|
|
753
|
+
{ setting: 'Model Status', value: modelDownloaded ? output.success('Downloaded') : output.error('NOT downloaded') },
|
|
728
754
|
{ setting: 'Dimension', value: String(dimension) },
|
|
729
755
|
{ setting: 'Cache Size', value: String(cacheSize) + ' entries' },
|
|
730
756
|
{ setting: 'Hyperbolic', value: hyperbolic ? `${output.success('Enabled')} (c=${curvature})` : output.dim('Disabled') },
|
|
@@ -733,6 +759,20 @@ const initCommand = {
|
|
|
733
759
|
{ setting: 'Config', value: configPath },
|
|
734
760
|
],
|
|
735
761
|
});
|
|
762
|
+
if (downloadSkipped) {
|
|
763
|
+
output.writeln();
|
|
764
|
+
output.printWarning(`No embedding model was installed: ${modelSkipReason}.`);
|
|
765
|
+
output.printWarning('Until a real embedder is present, embeddings fall back to hash vectors, which carry no semantic meaning.');
|
|
766
|
+
output.printInfo(`Configuration was still written to ${configPath}, recording "modelDownloaded": false.`);
|
|
767
|
+
output.writeln();
|
|
768
|
+
output.writeln(output.dim('To finish initializing:'));
|
|
769
|
+
output.printList([
|
|
770
|
+
'npm install @claude-flow/embeddings - the package this command downloads with; then: embeddings init --force',
|
|
771
|
+
'npm install ruvector - the other real embedder ruflo accepts; needs no download step',
|
|
772
|
+
'embeddings init --no-download --force - keep this configuration without a model (exits 0)',
|
|
773
|
+
]);
|
|
774
|
+
return { success: false, exitCode: 1, data: config };
|
|
775
|
+
}
|
|
736
776
|
output.writeln();
|
|
737
777
|
if (hyperbolic) {
|
|
738
778
|
output.printBox([
|
|
@@ -1182,8 +1222,11 @@ const modelsCommand = {
|
|
|
1182
1222
|
}
|
|
1183
1223
|
}
|
|
1184
1224
|
else {
|
|
1185
|
-
|
|
1186
|
-
|
|
1225
|
+
// #3376, same class as `init`: nothing was downloaded, so this is a
|
|
1226
|
+
// failure, not a success with a caveat. No fake progress delay either.
|
|
1227
|
+
spinner.fail(`Download skipped — @claude-flow/embeddings is not installed`);
|
|
1228
|
+
output.printWarning(`"${download}" was not downloaded. Install @claude-flow/embeddings to enable model downloads.`);
|
|
1229
|
+
return { success: false, exitCode: 1 };
|
|
1187
1230
|
}
|
|
1188
1231
|
return { success: true };
|
|
1189
1232
|
}
|
|
@@ -113,7 +113,9 @@ export function runCodexInitializerCli(cwd, options) {
|
|
|
113
113
|
// init) and scripted `--format json` output.
|
|
114
114
|
async function maybeAutoDetectCodex(ctx, options) {
|
|
115
115
|
try {
|
|
116
|
-
|
|
116
|
+
// The CLI parser turns `--no-codex-detect` into `codexDetect: false`.
|
|
117
|
+
// Retain the literal key for programmatic CommandContext callers.
|
|
118
|
+
if (ctx.flags.codexDetect === false || ctx.flags['no-codex-detect'] === true)
|
|
117
119
|
return;
|
|
118
120
|
if (ctx.flags.format === 'json')
|
|
119
121
|
return; // scripted output stays pure
|
|
@@ -295,7 +297,8 @@ function isBloatedRufloSkillDir(dir) {
|
|
|
295
297
|
}
|
|
296
298
|
async function maybeInstallSkillsSh(ctx) {
|
|
297
299
|
try {
|
|
298
|
-
|
|
300
|
+
// The CLI parser turns `--no-skills-sh` into `skillsSh: false`.
|
|
301
|
+
if (ctx.flags.skillsSh === false || ctx.flags['no-skills-sh'] === true)
|
|
299
302
|
return;
|
|
300
303
|
if (ctx.flags.format === 'json')
|
|
301
304
|
return;
|
package/dist/src/commands/mcp.js
CHANGED
|
@@ -66,6 +66,11 @@ const startCommand = {
|
|
|
66
66
|
description: 'Tools to advertise (comma-separated categories, prefixes, exact names, or "all")',
|
|
67
67
|
type: 'string'
|
|
68
68
|
},
|
|
69
|
+
{
|
|
70
|
+
name: 'request-timeout-ms',
|
|
71
|
+
description: 'HTTP request timeout in milliseconds (default 30000; env RUFLO_MCP_REQUEST_TIMEOUT_MS)',
|
|
72
|
+
type: 'number'
|
|
73
|
+
},
|
|
69
74
|
{
|
|
70
75
|
name: 'daemon',
|
|
71
76
|
short: 'd',
|
|
@@ -84,6 +89,7 @@ const startCommand = {
|
|
|
84
89
|
examples: [
|
|
85
90
|
{ command: 'claude-flow mcp start', description: 'Start with defaults (stdio)' },
|
|
86
91
|
{ command: 'claude-flow mcp start -p 8080 -t http', description: 'Start HTTP server' },
|
|
92
|
+
{ command: 'claude-flow mcp start -t http --request-timeout-ms 120000', description: 'Allow slower cold-start HTTP tools' },
|
|
87
93
|
{ command: 'claude-flow mcp start -d', description: 'Start as daemon' },
|
|
88
94
|
{ command: 'claude-flow mcp start -f', description: 'Force restart (kill existing)' }
|
|
89
95
|
],
|
|
@@ -94,6 +100,15 @@ const startCommand = {
|
|
|
94
100
|
const tools = ctx.flags.tools
|
|
95
101
|
|| process.env.CLAUDE_FLOW_MCP_TOOLS
|
|
96
102
|
|| 'all';
|
|
103
|
+
const timeoutInput = ctx.flags.requestTimeoutMs
|
|
104
|
+
?? ctx.flags['request-timeout-ms']
|
|
105
|
+
?? process.env.RUFLO_MCP_REQUEST_TIMEOUT_MS;
|
|
106
|
+
const requestTimeoutMs = timeoutInput === undefined ? undefined : Number(timeoutInput);
|
|
107
|
+
if (requestTimeoutMs !== undefined &&
|
|
108
|
+
(!Number.isSafeInteger(requestTimeoutMs) || requestTimeoutMs < 1 || requestTimeoutMs > 3_600_000)) {
|
|
109
|
+
output.printError('Request timeout must be an integer from 1 to 3600000 milliseconds');
|
|
110
|
+
return { success: false, exitCode: 1 };
|
|
111
|
+
}
|
|
97
112
|
const daemon = ctx.flags.daemon ?? false;
|
|
98
113
|
const force = ctx.flags.force ?? false;
|
|
99
114
|
output.writeln();
|
|
@@ -104,10 +119,17 @@ const startCommand = {
|
|
|
104
119
|
// PID file exists, which would cause us to SIGKILL ourselves)
|
|
105
120
|
const existingStatus = await getMCPServerStatus();
|
|
106
121
|
const isSelfDetected = existingStatus.pid === process.pid;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
122
|
+
// #3364: only a port-bound transport (http/websocket) is single-instance.
|
|
123
|
+
// A stdio server belongs to the client that spawned it, over that client's
|
|
124
|
+
// own pipes, and any number run side by side. The PID file is a single
|
|
125
|
+
// slot per os.tmpdir(), so for stdio "already running" only ever meant
|
|
126
|
+
// "some other server is recorded" — and force-restarting SIGKILLed it
|
|
127
|
+
// (another terminal's stdio server, or a live http server). Stdio now
|
|
128
|
+
// kills a recorded server only when explicitly asked with --force.
|
|
129
|
+
const isSingleInstance = transport !== 'stdio';
|
|
130
|
+
if (existingStatus.running && !isSelfDetected && (isSingleInstance || force)) {
|
|
131
|
+
// --force always restarts; otherwise check health (http/websocket)
|
|
132
|
+
const shouldForceRestart = force;
|
|
111
133
|
if (!shouldForceRestart) {
|
|
112
134
|
// Verify the server is actually healthy/responsive
|
|
113
135
|
const manager = getServerManager();
|
|
@@ -143,6 +165,7 @@ const startCommand = {
|
|
|
143
165
|
host,
|
|
144
166
|
port,
|
|
145
167
|
tools: !tools || tools === 'all' ? 'all' : tools.split(','),
|
|
168
|
+
...(requestTimeoutMs === undefined ? {} : { requestTimeoutMs }),
|
|
146
169
|
daemonize: daemon,
|
|
147
170
|
};
|
|
148
171
|
try {
|
package/dist/src/mcp-server.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ export interface MCPServerOptions {
|
|
|
29
29
|
tools?: string[] | 'all';
|
|
30
30
|
daemonize?: boolean;
|
|
31
31
|
timeout?: number;
|
|
32
|
+
requestTimeoutMs?: number;
|
|
32
33
|
}
|
|
33
34
|
/**
|
|
34
35
|
* MCP Server status
|
|
@@ -85,6 +86,21 @@ export declare class MCPServerManager extends EventEmitter {
|
|
|
85
86
|
private startTime?;
|
|
86
87
|
private healthCheckInterval?;
|
|
87
88
|
private mcpServers;
|
|
89
|
+
/**
|
|
90
|
+
* This manager's own lifecycle (#3364). A stdio server keeps no PID record,
|
|
91
|
+
* so nothing on disk can answer "is this manager running?" — only the
|
|
92
|
+
* manager can. `idle` is a manager that has never started, and only there
|
|
93
|
+
* does the #2934 fallback in getStatus() ("assume a client-launched stdio
|
|
94
|
+
* server") apply; `stopped` is a handle on a server that is gone, and it
|
|
95
|
+
* says so rather than reporting whatever PID the shared file happens to hold.
|
|
96
|
+
*/
|
|
97
|
+
private lifecycle;
|
|
98
|
+
/**
|
|
99
|
+
* The exact bytes this manager wrote to the PID file, while they are still
|
|
100
|
+
* there (#3364). stop() retracts the record only while it is byte for byte
|
|
101
|
+
* the one we wrote: the slot may have changed hands in between.
|
|
102
|
+
*/
|
|
103
|
+
private ownedRecord;
|
|
88
104
|
constructor(options?: MCPServerOptions);
|
|
89
105
|
/**
|
|
90
106
|
* Start the MCP server
|
|
@@ -136,15 +152,43 @@ export declare class MCPServerManager extends EventEmitter {
|
|
|
136
152
|
*/
|
|
137
153
|
private startHealthMonitoring;
|
|
138
154
|
/**
|
|
139
|
-
* Write PID
|
|
155
|
+
* Write the PID record.
|
|
156
|
+
*
|
|
157
|
+
* Line 1 is the bare PID, byte for byte what this file has always held, so
|
|
158
|
+
* every existing reader keeps working: an older ruflo's
|
|
159
|
+
* `parseInt(content.trim(), 10)` stops at the newline, and
|
|
160
|
+
* `v3/scripts/start-mcp.sh` reads the first line. Line 2 is the durable
|
|
161
|
+
* identity of the instance that wrote it (#3364). Returns the bytes written,
|
|
162
|
+
* which stop() uses to retract only its own record.
|
|
140
163
|
*/
|
|
141
164
|
private writePidFile;
|
|
142
165
|
/**
|
|
143
|
-
* Read PID
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
*
|
|
166
|
+
* Read the PID record.
|
|
167
|
+
*
|
|
168
|
+
* A bare-integer file — an older ruflo, `start-mcp.sh --daemon`, or a
|
|
169
|
+
* hand-written one — parses to a record with no identity, which keeps
|
|
170
|
+
* exactly the old behaviour: the PID is checked for liveness and nothing
|
|
171
|
+
* more. An identity line is only believed for the PID it names.
|
|
172
|
+
*/
|
|
173
|
+
private readPidRecord;
|
|
174
|
+
/**
|
|
175
|
+
* Is the recorded server still the instance the record names? (#3364)
|
|
176
|
+
*
|
|
177
|
+
* `kill -0`, and isProcessRunning()'s process-name check on top of it, only
|
|
178
|
+
* answer "something with this number is alive". Durable identity answers
|
|
179
|
+
* "it is still the one we wrote down":
|
|
180
|
+
* - another host, OS, kernel boot or PID namespace issues its own PIDs, so
|
|
181
|
+
* the number says nothing here. On Linux this is the ordinary stale case,
|
|
182
|
+
* because /tmp commonly survives a reboot;
|
|
183
|
+
* - within one boot the OS reuses a PID once the process is reaped, and the
|
|
184
|
+
* owner's start time is what tells the two apart.
|
|
185
|
+
* Where the start time cannot be read — Windows — the answer falls back to
|
|
186
|
+
* isProcessRunning(), i.e. exactly the evidence used today.
|
|
187
|
+
*/
|
|
188
|
+
private recordedServerIsLive;
|
|
189
|
+
/**
|
|
190
|
+
* Remove PID file. With `ownedRecord`, only while the file still holds
|
|
191
|
+
* exactly those bytes: the slot may have changed hands (#3364).
|
|
148
192
|
*/
|
|
149
193
|
private removePidFile;
|
|
150
194
|
/**
|