@claude-flow/cli 3.38.9 → 3.38.11
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/catalog-manifest.json +2 -2
- package/dist/src/commands/doctor.js +53 -8
- package/dist/src/commands/mcp.js +4 -3
- package/dist/src/init/helper-refresh.d.ts +16 -9
- package/dist/src/init/helper-refresh.js +177 -20
- package/dist/src/log-filters.d.ts +3 -3
- package/dist/src/mcp-server.d.ts +1 -0
- package/dist/src/mcp-server.js +39 -3
- package/dist/src/mcp-tools/neural-tools.js +4 -3
- package/dist/src/memory/memory-bridge.d.ts +8 -0
- package/dist/src/memory/memory-bridge.js +18 -0
- package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
- package/node_modules/@claude-flow/mcp/README.md +429 -0
- package/node_modules/@claude-flow/mcp/dist/connection-pool.d.ts +36 -0
- package/node_modules/@claude-flow/mcp/dist/connection-pool.d.ts.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/connection-pool.js +273 -0
- package/node_modules/@claude-flow/mcp/dist/connection-pool.js.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/index.d.ts +75 -0
- package/node_modules/@claude-flow/mcp/dist/index.d.ts.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/index.js +85 -0
- package/node_modules/@claude-flow/mcp/dist/index.js.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/oauth.d.ts +146 -0
- package/node_modules/@claude-flow/mcp/dist/oauth.d.ts.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/oauth.js +318 -0
- package/node_modules/@claude-flow/mcp/dist/oauth.js.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/prompt-registry.d.ts +90 -0
- package/node_modules/@claude-flow/mcp/dist/prompt-registry.d.ts.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/prompt-registry.js +209 -0
- package/node_modules/@claude-flow/mcp/dist/prompt-registry.js.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/rate-limiter.d.ts +86 -0
- package/node_modules/@claude-flow/mcp/dist/rate-limiter.d.ts.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/rate-limiter.js +197 -0
- package/node_modules/@claude-flow/mcp/dist/rate-limiter.js.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/resource-registry.d.ts +144 -0
- package/node_modules/@claude-flow/mcp/dist/resource-registry.d.ts.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/resource-registry.js +405 -0
- package/node_modules/@claude-flow/mcp/dist/resource-registry.js.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/sampling.d.ts +102 -0
- package/node_modules/@claude-flow/mcp/dist/sampling.d.ts.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/sampling.js +268 -0
- package/node_modules/@claude-flow/mcp/dist/sampling.js.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/schema-validator.d.ts +30 -0
- package/node_modules/@claude-flow/mcp/dist/schema-validator.d.ts.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/schema-validator.js +182 -0
- package/node_modules/@claude-flow/mcp/dist/schema-validator.js.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/server.d.ts +125 -0
- package/node_modules/@claude-flow/mcp/dist/server.d.ts.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/server.js +849 -0
- package/node_modules/@claude-flow/mcp/dist/server.js.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/session-manager.d.ts +55 -0
- package/node_modules/@claude-flow/mcp/dist/session-manager.d.ts.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/session-manager.js +252 -0
- package/node_modules/@claude-flow/mcp/dist/session-manager.js.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/task-manager.d.ts +81 -0
- package/node_modules/@claude-flow/mcp/dist/task-manager.d.ts.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/task-manager.js +337 -0
- package/node_modules/@claude-flow/mcp/dist/task-manager.js.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts +96 -0
- package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/tool-registry.js +399 -0
- package/node_modules/@claude-flow/mcp/dist/tool-registry.js.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts +60 -0
- package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/transport/http.js +503 -0
- package/node_modules/@claude-flow/mcp/dist/transport/http.js.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/transport/index.d.ts +50 -0
- package/node_modules/@claude-flow/mcp/dist/transport/index.d.ts.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/transport/index.js +181 -0
- package/node_modules/@claude-flow/mcp/dist/transport/index.js.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/transport/stdio.d.ts +43 -0
- package/node_modules/@claude-flow/mcp/dist/transport/stdio.d.ts.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/transport/stdio.js +194 -0
- package/node_modules/@claude-flow/mcp/dist/transport/stdio.js.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/transport/websocket.d.ts +65 -0
- package/node_modules/@claude-flow/mcp/dist/transport/websocket.d.ts.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/transport/websocket.js +314 -0
- package/node_modules/@claude-flow/mcp/dist/transport/websocket.js.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/types.d.ts +482 -0
- package/node_modules/@claude-flow/mcp/dist/types.d.ts.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/types.js +40 -0
- package/node_modules/@claude-flow/mcp/dist/types.js.map +1 -0
- package/node_modules/@claude-flow/mcp/package.json +50 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/node_modules/@claude-flow/security/dist/path-validator.d.ts +23 -0
- package/node_modules/@claude-flow/security/dist/path-validator.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/path-validator.js +128 -27
- package/node_modules/@claude-flow/security/dist/path-validator.js.map +1 -1
- package/package.json +9 -2
- package/.claude/.proven-config-version +0 -1
- package/.claude/proven-config.json +0 -42
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest": {
|
|
3
|
-
"version": "3.38.
|
|
3
|
+
"version": "3.38.11",
|
|
4
4
|
"files": {
|
|
5
5
|
"auto-memory-hook.mjs": "85fe05c757421c52137c0bc8545a0896bab6b4714538c2a11d1d0835bfcc8c1c",
|
|
6
6
|
"hook-handler.cjs": "dae295fb9ae2626b89899c19a20cc911541af82b52d2eeb9b214d618b96e9a86",
|
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
"statusline.cjs": "0457fe53f8cd2c56458ff178392536a5868efd1a573665fa43bc01d2d95ca677"
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
|
-
"signature": "
|
|
11
|
+
"signature": "rNkivgIb+R5mwQcChrNF+m4lsfPPlKBn4oizP9Q7nuuIi8en/J3NJYf5l6mPRfEc4vdlqnnf0+Oid/007092Dw==",
|
|
12
12
|
"algorithm": "ed25519"
|
|
13
13
|
}
|
package/catalog-manifest.json
CHANGED
|
@@ -361,6 +361,38 @@ function isMemoryDbEncryptedAtRest(dbPath) {
|
|
|
361
361
|
return false;
|
|
362
362
|
}
|
|
363
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* Distinguish an unavailable native addon from a database that the native
|
|
366
|
+
* driver opened and found malformed. Importing better-sqlite3 can succeed even
|
|
367
|
+
* when its postinstall script was skipped: the JavaScript wrapper loads, then
|
|
368
|
+
* the first Database construction throws because better_sqlite3.node is absent
|
|
369
|
+
* or incompatible with the active Node ABI.
|
|
370
|
+
*/
|
|
371
|
+
function isNativeSqliteBindingUnavailable(error) {
|
|
372
|
+
const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
373
|
+
return [
|
|
374
|
+
/Could not locate the bindings file/i,
|
|
375
|
+
/better_sqlite3\.node/i,
|
|
376
|
+
/NODE_MODULE_VERSION/i,
|
|
377
|
+
/compiled against a different Node\.js version/i,
|
|
378
|
+
/ERR_DLOPEN_FAILED/i,
|
|
379
|
+
/invalid ELF header/i,
|
|
380
|
+
/wrong ELF class/i,
|
|
381
|
+
/not a valid Win32 application/i,
|
|
382
|
+
/Module did not self-register/i,
|
|
383
|
+
/no suitable image found/i,
|
|
384
|
+
].some((pattern) => pattern.test(message));
|
|
385
|
+
}
|
|
386
|
+
function nativeBindingUnavailableCheck(name, dbPath, error, suffix) {
|
|
387
|
+
const details = error instanceof Error ? error.message : String(error);
|
|
388
|
+
const summary = details.split(/\r?\n/, 1)[0].replace(/\s+Tried:\s*$/i, '');
|
|
389
|
+
return {
|
|
390
|
+
name,
|
|
391
|
+
status: 'warn',
|
|
392
|
+
message: `${dbPath} — better-sqlite3 package found, but its native binding is unavailable: ${summary} ${suffix}`,
|
|
393
|
+
fix: 'reinstall with npm install scripts enabled or run `npm rebuild better-sqlite3`; then rerun this check',
|
|
394
|
+
};
|
|
395
|
+
}
|
|
364
396
|
// #2737 part 1 — bare `doctor` (no --component flag) never actually opened
|
|
365
397
|
// memory.db: checkMemoryDatabase above is existsSync()+statSync() only, so
|
|
366
398
|
// it PASSES on any file that exists and can be stat'd, corrupt or not, and
|
|
@@ -416,8 +448,8 @@ async function checkMemoryStructuralIntegrity() {
|
|
|
416
448
|
return {
|
|
417
449
|
name: NAME,
|
|
418
450
|
status: 'warn',
|
|
419
|
-
message: `${dbPath} — better-sqlite3 not installed; structural-only check
|
|
420
|
-
fix: '
|
|
451
|
+
message: `${dbPath} — better-sqlite3 not installed; native WAL-backed persistence and the structural-only check are unavailable`,
|
|
452
|
+
fix: 'install better-sqlite3 with npm install scripts enabled, then rerun this check',
|
|
421
453
|
};
|
|
422
454
|
}
|
|
423
455
|
let db;
|
|
@@ -425,6 +457,9 @@ async function checkMemoryStructuralIntegrity() {
|
|
|
425
457
|
db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
426
458
|
}
|
|
427
459
|
catch (e) {
|
|
460
|
+
if (isNativeSqliteBindingUnavailable(e)) {
|
|
461
|
+
return nativeBindingUnavailableCheck(NAME, dbPath, e, '[structural-only; durable WAL-backed persistence unavailable]');
|
|
462
|
+
}
|
|
428
463
|
const msg = e.message || String(e);
|
|
429
464
|
// Encryption already ruled out above — an unencrypted file
|
|
430
465
|
// better-sqlite3 can't even open is definitive corruption.
|
|
@@ -498,9 +533,9 @@ async function checkMemoryIntegrity() {
|
|
|
498
533
|
}
|
|
499
534
|
// Prefer native better-sqlite3 (WAL-aware, full integrity_check). Module
|
|
500
535
|
// load is isolated in its own try/catch so ONLY "not installed" falls
|
|
501
|
-
// through to the sql.js fallback below
|
|
502
|
-
//
|
|
503
|
-
//
|
|
536
|
+
// through to the sql.js fallback below. A loaded JavaScript wrapper can
|
|
537
|
+
// still lack its native binding (#2968); that capability failure is a warn,
|
|
538
|
+
// while genuine open/query failures remain authoritative failures here.
|
|
504
539
|
let Database;
|
|
505
540
|
try {
|
|
506
541
|
Database = (await import('better-sqlite3')).default;
|
|
@@ -514,6 +549,9 @@ async function checkMemoryIntegrity() {
|
|
|
514
549
|
db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
515
550
|
}
|
|
516
551
|
catch (e) {
|
|
552
|
+
if (isNativeSqliteBindingUnavailable(e)) {
|
|
553
|
+
return nativeBindingUnavailableCheck('Memory Integrity', dbPath, e, '[native WAL-backed persistence unavailable]');
|
|
554
|
+
}
|
|
517
555
|
const msg = e.message || String(e);
|
|
518
556
|
return {
|
|
519
557
|
name: 'Memory Integrity',
|
|
@@ -574,7 +612,12 @@ async function checkMemoryIntegrity() {
|
|
|
574
612
|
const res = db.exec('PRAGMA integrity_check');
|
|
575
613
|
const rows = res[0]?.values?.map((v) => String(v[0])) ?? [];
|
|
576
614
|
if (rows.length === 1 && rows[0] === 'ok') {
|
|
577
|
-
return {
|
|
615
|
+
return {
|
|
616
|
+
name: 'Memory Integrity',
|
|
617
|
+
status: 'warn',
|
|
618
|
+
message: `${dbPath} — PRAGMA integrity_check: ok, but only for the main image [sql.js fallback; native WAL-backed persistence unavailable]`,
|
|
619
|
+
fix: 'install better-sqlite3 with npm install scripts enabled, then rerun this check',
|
|
620
|
+
};
|
|
578
621
|
}
|
|
579
622
|
return {
|
|
580
623
|
name: 'Memory Integrity',
|
|
@@ -885,8 +928,10 @@ async function checkApiKeys() {
|
|
|
885
928
|
found.push(key);
|
|
886
929
|
}
|
|
887
930
|
}
|
|
888
|
-
// Detect Claude Code environment — API keys are managed internally
|
|
889
|
-
|
|
931
|
+
// Detect Claude Code environment — API keys are managed internally.
|
|
932
|
+
// Claude Code sets CLAUDECODE (no underscore); CLAUDE_CODE is kept for
|
|
933
|
+
// compatibility with anything else that might set it.
|
|
934
|
+
const inClaudeCode = !!(process.env.CLAUDECODE || process.env.CLAUDE_CODE || process.env.CLAUDE_PROJECT_DIR || process.env.MCP_SESSION_ID);
|
|
890
935
|
if (found.includes('ANTHROPIC_API_KEY') || found.includes('CLAUDE_API_KEY')) {
|
|
891
936
|
return { name: 'API Keys', status: 'pass', message: `Found: ${found.join(', ')}` };
|
|
892
937
|
}
|
package/dist/src/commands/mcp.js
CHANGED
|
@@ -64,8 +64,7 @@ const startCommand = {
|
|
|
64
64
|
{
|
|
65
65
|
name: 'tools',
|
|
66
66
|
description: 'Tools to advertise (comma-separated categories, prefixes, exact names, or "all")',
|
|
67
|
-
type: 'string'
|
|
68
|
-
default: 'all'
|
|
67
|
+
type: 'string'
|
|
69
68
|
},
|
|
70
69
|
{
|
|
71
70
|
name: 'daemon',
|
|
@@ -92,7 +91,9 @@ const startCommand = {
|
|
|
92
91
|
const port = ctx.flags.port ?? 3000;
|
|
93
92
|
const host = ctx.flags.host ?? 'localhost';
|
|
94
93
|
const transport = ctx.flags.transport ?? 'stdio';
|
|
95
|
-
const tools = ctx.flags.tools
|
|
94
|
+
const tools = ctx.flags.tools
|
|
95
|
+
|| process.env.CLAUDE_FLOW_MCP_TOOLS
|
|
96
|
+
|| 'all';
|
|
96
97
|
const daemon = ctx.flags.daemon ?? false;
|
|
97
98
|
const force = ctx.flags.force ?? false;
|
|
98
99
|
output.writeln();
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
export declare const HELPERS_STAMP_FILE = ".helpers-version";
|
|
2
|
+
export declare const HELPERS_REFRESH_LOCK_FILE = ".helpers-refresh.lock";
|
|
3
|
+
interface RefreshOptions {
|
|
4
|
+
sourceDirOverride?: string;
|
|
5
|
+
pubkeyPemOverride?: string;
|
|
6
|
+
versionOverride?: string;
|
|
7
|
+
alsoRefreshGlobal?: boolean;
|
|
8
|
+
beforeWriteOverride?: () => void | Promise<void>;
|
|
9
|
+
lockWaitMsOverride?: number;
|
|
10
|
+
lockRetryMsOverride?: number;
|
|
11
|
+
malformedLockStaleMsOverride?: number;
|
|
12
|
+
}
|
|
2
13
|
/**
|
|
3
14
|
* ruflo-owned helpers that carry hook logic (or the render surface for the
|
|
4
15
|
* funnel disclosure row) and must track the package version. Adding to this
|
|
@@ -40,9 +51,9 @@ export declare function getInstalledCliVersion(): string;
|
|
|
40
51
|
* running `daemon start` (or any command) would see its own older version !=
|
|
41
52
|
* the project's newer stamp and silently overwrite hand-fixed
|
|
42
53
|
* `hook-handler.cjs`/`intelligence.cjs` with its own older, already-superseded
|
|
43
|
-
* bundled copies.
|
|
44
|
-
*
|
|
45
|
-
*
|
|
54
|
+
* bundled copies. The semver guard is therefore re-evaluated while holding a
|
|
55
|
+
* per-directory cross-process lock; otherwise an older process can pass the
|
|
56
|
+
* guard first but finish writing last.
|
|
46
57
|
*
|
|
47
58
|
* `opts` exists for tests ONLY (mirrors daemon-autostart.ts's injectable
|
|
48
59
|
* `SpawnDaemonFn` pattern): the real signed-copy path is otherwise coupled
|
|
@@ -59,12 +70,7 @@ export declare function getInstalledCliVersion(): string;
|
|
|
59
70
|
* compat with pre-3.31.3 callers). If the global pass ran, its own result is
|
|
60
71
|
* carried in the optional `global` field.
|
|
61
72
|
*/
|
|
62
|
-
export declare function autoRefreshHelpersIfStale(cwd: string, opts?: {
|
|
63
|
-
sourceDirOverride?: string;
|
|
64
|
-
pubkeyPemOverride?: string;
|
|
65
|
-
versionOverride?: string;
|
|
66
|
-
alsoRefreshGlobal?: boolean;
|
|
67
|
-
}): Promise<{
|
|
73
|
+
export declare function autoRefreshHelpersIfStale(cwd: string, opts?: RefreshOptions): Promise<{
|
|
68
74
|
refreshed: boolean;
|
|
69
75
|
from?: string;
|
|
70
76
|
to?: string;
|
|
@@ -76,4 +82,5 @@ export declare function autoRefreshHelpersIfStale(cwd: string, opts?: {
|
|
|
76
82
|
blocked?: string;
|
|
77
83
|
};
|
|
78
84
|
}>;
|
|
85
|
+
export {};
|
|
79
86
|
//# sourceMappingURL=helper-refresh.d.ts.map
|
|
@@ -53,6 +53,11 @@ function findPackageRoot(startDir, maxUp = 6) {
|
|
|
53
53
|
return null;
|
|
54
54
|
}
|
|
55
55
|
export const HELPERS_STAMP_FILE = '.helpers-version';
|
|
56
|
+
export const HELPERS_REFRESH_LOCK_FILE = '.helpers-refresh.lock';
|
|
57
|
+
const DEFAULT_LOCK_WAIT_MS = 2_000;
|
|
58
|
+
const DEFAULT_LOCK_RETRY_MS = 10;
|
|
59
|
+
const DEFAULT_MALFORMED_LOCK_STALE_MS = 5 * 60_000;
|
|
60
|
+
let tempFileCounter = 0;
|
|
56
61
|
/**
|
|
57
62
|
* ruflo-owned helpers that carry hook logic (or the render surface for the
|
|
58
63
|
* funnel disclosure row) and must track the package version. Adding to this
|
|
@@ -67,6 +72,139 @@ export const CRITICAL_HELPERS = [
|
|
|
67
72
|
// existing installs on the next `ruflo` command, not only fresh `ruflo init`.
|
|
68
73
|
'statusline.cjs',
|
|
69
74
|
];
|
|
75
|
+
function errorCode(error) {
|
|
76
|
+
return typeof error === 'object' && error !== null && 'code' in error
|
|
77
|
+
? String(error.code)
|
|
78
|
+
: undefined;
|
|
79
|
+
}
|
|
80
|
+
function processIsAlive(pid) {
|
|
81
|
+
try {
|
|
82
|
+
process.kill(pid, 0);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
return errorCode(error) !== 'ESRCH';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function removeAbandonedLock(lockPath, malformedStaleMs) {
|
|
90
|
+
try {
|
|
91
|
+
const parsed = JSON.parse(fs.readFileSync(lockPath, 'utf-8'));
|
|
92
|
+
if (Number.isSafeInteger(parsed.pid) && Number(parsed.pid) > 0) {
|
|
93
|
+
if (processIsAlive(Number(parsed.pid)))
|
|
94
|
+
return false;
|
|
95
|
+
fs.unlinkSync(lockPath);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (errorCode(error) === 'ENOENT')
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
// A process can die after the exclusive create but before writing metadata.
|
|
104
|
+
// Only reclaim such malformed locks after a generous age; valid locks with a
|
|
105
|
+
// live PID are never age-evicted, so a slow refresh cannot lose ownership.
|
|
106
|
+
try {
|
|
107
|
+
if (Date.now() - fs.statSync(lockPath).mtimeMs >= malformedStaleMs) {
|
|
108
|
+
fs.unlinkSync(lockPath);
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
if (errorCode(error) === 'ENOENT')
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
async function acquireRefreshLock(helpersDir, opts) {
|
|
119
|
+
const lockPath = path.join(helpersDir, HELPERS_REFRESH_LOCK_FILE);
|
|
120
|
+
const waitMs = opts.lockWaitMsOverride ?? DEFAULT_LOCK_WAIT_MS;
|
|
121
|
+
const retryMs = opts.lockRetryMsOverride ?? DEFAULT_LOCK_RETRY_MS;
|
|
122
|
+
const malformedStaleMs = opts.malformedLockStaleMsOverride ?? DEFAULT_MALFORMED_LOCK_STALE_MS;
|
|
123
|
+
const deadline = Date.now() + Math.max(0, waitMs);
|
|
124
|
+
while (true) {
|
|
125
|
+
const token = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
126
|
+
let fd;
|
|
127
|
+
let created = false;
|
|
128
|
+
try {
|
|
129
|
+
fd = fs.openSync(lockPath, 'wx');
|
|
130
|
+
created = true;
|
|
131
|
+
fs.writeFileSync(fd, JSON.stringify({ pid: process.pid, token }), 'utf-8');
|
|
132
|
+
fs.closeSync(fd);
|
|
133
|
+
fd = undefined;
|
|
134
|
+
return () => {
|
|
135
|
+
try {
|
|
136
|
+
const current = JSON.parse(fs.readFileSync(lockPath, 'utf-8'));
|
|
137
|
+
if (current.token === token)
|
|
138
|
+
fs.unlinkSync(lockPath);
|
|
139
|
+
}
|
|
140
|
+
catch { /* best-effort release; a replaced lock is never removed */ }
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
if (fd !== undefined) {
|
|
145
|
+
try {
|
|
146
|
+
fs.closeSync(fd);
|
|
147
|
+
}
|
|
148
|
+
catch { /* best-effort */ }
|
|
149
|
+
}
|
|
150
|
+
if (created) {
|
|
151
|
+
try {
|
|
152
|
+
fs.unlinkSync(lockPath);
|
|
153
|
+
}
|
|
154
|
+
catch { /* best-effort */ }
|
|
155
|
+
}
|
|
156
|
+
if (errorCode(error) !== 'EEXIST')
|
|
157
|
+
throw error;
|
|
158
|
+
if (removeAbandonedLock(lockPath, malformedStaleMs))
|
|
159
|
+
continue;
|
|
160
|
+
if (Date.now() >= deadline)
|
|
161
|
+
return null;
|
|
162
|
+
await new Promise((resolve) => setTimeout(resolve, Math.max(1, retryMs)));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function temporarySiblingPath(target) {
|
|
167
|
+
tempFileCounter += 1;
|
|
168
|
+
return `${target}.tmp-${process.pid}-${Date.now()}-${tempFileCounter}`;
|
|
169
|
+
}
|
|
170
|
+
function atomicCopyFileSync(source, target, mode) {
|
|
171
|
+
const temporary = temporarySiblingPath(target);
|
|
172
|
+
try {
|
|
173
|
+
fs.copyFileSync(source, temporary, fs.constants.COPYFILE_EXCL);
|
|
174
|
+
if (mode) {
|
|
175
|
+
try {
|
|
176
|
+
fs.chmodSync(temporary, mode);
|
|
177
|
+
}
|
|
178
|
+
catch { /* non-fatal */ }
|
|
179
|
+
}
|
|
180
|
+
fs.renameSync(temporary, target);
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
try {
|
|
184
|
+
fs.unlinkSync(temporary);
|
|
185
|
+
}
|
|
186
|
+
catch { /* already renamed or never created */ }
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function atomicWriteFileSync(target, content, mode) {
|
|
190
|
+
const temporary = temporarySiblingPath(target);
|
|
191
|
+
try {
|
|
192
|
+
fs.writeFileSync(temporary, content, { encoding: 'utf-8', flag: 'wx' });
|
|
193
|
+
if (mode) {
|
|
194
|
+
try {
|
|
195
|
+
fs.chmodSync(temporary, mode);
|
|
196
|
+
}
|
|
197
|
+
catch { /* non-fatal */ }
|
|
198
|
+
}
|
|
199
|
+
fs.renameSync(temporary, target);
|
|
200
|
+
}
|
|
201
|
+
finally {
|
|
202
|
+
try {
|
|
203
|
+
fs.unlinkSync(temporary);
|
|
204
|
+
}
|
|
205
|
+
catch { /* already renamed or never created */ }
|
|
206
|
+
}
|
|
207
|
+
}
|
|
70
208
|
/** Installed @claude-flow/cli version — the value the helpers are stamped with. */
|
|
71
209
|
export function getInstalledCliVersion() {
|
|
72
210
|
try {
|
|
@@ -148,20 +286,16 @@ async function writeCriticalHelpers(helpersDir, version, opts = {}) {
|
|
|
148
286
|
let wrote = false;
|
|
149
287
|
for (const name of toCopy) {
|
|
150
288
|
const tp = path.join(helpersDir, name);
|
|
151
|
-
|
|
152
|
-
try {
|
|
153
|
-
fs.chmodSync(tp, '755');
|
|
154
|
-
}
|
|
155
|
-
catch { /* non-fatal */ }
|
|
289
|
+
atomicCopyFileSync(path.join(source, name), tp, '755');
|
|
156
290
|
wrote = true;
|
|
157
291
|
}
|
|
158
292
|
try {
|
|
159
|
-
|
|
293
|
+
atomicCopyFileSync(path.join(source, HELPERS_MANIFEST_FILE), path.join(helpersDir, HELPERS_MANIFEST_FILE));
|
|
160
294
|
}
|
|
161
295
|
catch { /* non-fatal */ }
|
|
162
296
|
if (wrote) {
|
|
163
297
|
try {
|
|
164
|
-
|
|
298
|
+
atomicWriteFileSync(path.join(helpersDir, HELPERS_STAMP_FILE), version);
|
|
165
299
|
}
|
|
166
300
|
catch { /* non-fatal */ }
|
|
167
301
|
}
|
|
@@ -187,16 +321,12 @@ async function writeCriticalHelpers(helpersDir, version, opts = {}) {
|
|
|
187
321
|
let wrote = false;
|
|
188
322
|
for (const [name, content] of Object.entries(files)) {
|
|
189
323
|
const tp = path.join(helpersDir, name);
|
|
190
|
-
|
|
191
|
-
try {
|
|
192
|
-
fs.chmodSync(tp, '755');
|
|
193
|
-
}
|
|
194
|
-
catch { /* non-fatal */ }
|
|
324
|
+
atomicWriteFileSync(tp, content, '755');
|
|
195
325
|
wrote = true;
|
|
196
326
|
}
|
|
197
327
|
if (wrote) {
|
|
198
328
|
try {
|
|
199
|
-
|
|
329
|
+
atomicWriteFileSync(path.join(helpersDir, HELPERS_STAMP_FILE), version);
|
|
200
330
|
}
|
|
201
331
|
catch { /* non-fatal */ }
|
|
202
332
|
}
|
|
@@ -216,9 +346,9 @@ async function writeCriticalHelpers(helpersDir, version, opts = {}) {
|
|
|
216
346
|
* running `daemon start` (or any command) against THIS project directory
|
|
217
347
|
* would see its own older version != the project's newer stamp and silently
|
|
218
348
|
* overwrite hand-fixed `hook-handler.cjs`/`intelligence.cjs` with its own
|
|
219
|
-
* older, already-superseded bundled copies.
|
|
220
|
-
*
|
|
221
|
-
*
|
|
349
|
+
* older, already-superseded bundled copies. The semver guard is therefore
|
|
350
|
+
* re-evaluated while holding a per-directory cross-process lock; otherwise
|
|
351
|
+
* an older process can pass the guard first but finish writing last.
|
|
222
352
|
*
|
|
223
353
|
* `opts` exists for tests ONLY (mirrors daemon-autostart.ts's injectable
|
|
224
354
|
* `SpawnDaemonFn` pattern): the real signed-copy path is otherwise coupled to
|
|
@@ -231,7 +361,7 @@ async function writeCriticalHelpers(helpersDir, version, opts = {}) {
|
|
|
231
361
|
* signed fixture and get real, deterministic coverage of the verify → hash →
|
|
232
362
|
* copy logic without depending on that.
|
|
233
363
|
*/
|
|
234
|
-
async function
|
|
364
|
+
async function refreshOneHelpersDirLocked(helpersDir, version, opts) {
|
|
235
365
|
if (!fs.existsSync(path.join(helpersDir, 'hook-handler.cjs')))
|
|
236
366
|
return { refreshed: false };
|
|
237
367
|
// .LOCKED marker: users developing ruflo itself (or any project with
|
|
@@ -258,6 +388,7 @@ async function refreshOneHelpersDir(helpersDir, version, opts) {
|
|
|
258
388
|
// would silently DOWNGRADE the helpers. Skip, untouched.
|
|
259
389
|
return { refreshed: false };
|
|
260
390
|
}
|
|
391
|
+
await opts.beforeWriteOverride?.();
|
|
261
392
|
const res = await writeCriticalHelpers(helpersDir, version, {
|
|
262
393
|
sourceDirOverride: opts.sourceDirOverride,
|
|
263
394
|
pubkeyPemOverride: opts.pubkeyPemOverride,
|
|
@@ -266,6 +397,32 @@ async function refreshOneHelpersDir(helpersDir, version, opts) {
|
|
|
266
397
|
return { refreshed: false, blocked: res.blocked };
|
|
267
398
|
return res.wrote ? { refreshed: true, from: stamped || '(unstamped)', to: version } : { refreshed: false };
|
|
268
399
|
}
|
|
400
|
+
async function refreshOneHelpersDir(helpersDir, version, opts) {
|
|
401
|
+
if (!fs.existsSync(path.join(helpersDir, 'hook-handler.cjs')))
|
|
402
|
+
return { refreshed: false };
|
|
403
|
+
// Respect the repository opt-out before creating even a transient lock file
|
|
404
|
+
// in a directory whose helpers are intentionally maintained by hand. Keep
|
|
405
|
+
// the check in the locked path too in case the marker appears while waiting.
|
|
406
|
+
if (fs.existsSync(path.join(helpersDir, '.LOCKED'))) {
|
|
407
|
+
return { refreshed: false, blocked: '.LOCKED marker present — refresh skipped (delete to re-enable)' };
|
|
408
|
+
}
|
|
409
|
+
try {
|
|
410
|
+
if (fs.readFileSync(path.join(helpersDir, HELPERS_STAMP_FILE), 'utf-8').trim() === version)
|
|
411
|
+
return { refreshed: false };
|
|
412
|
+
}
|
|
413
|
+
catch { /* unstamped: continue to the locked path */ }
|
|
414
|
+
const releaseLock = await acquireRefreshLock(helpersDir, opts);
|
|
415
|
+
if (!releaseLock)
|
|
416
|
+
return { refreshed: false, blocked: 'helper refresh already in progress' };
|
|
417
|
+
try {
|
|
418
|
+
// Re-read the stamp and .LOCKED marker under the cross-process lock. A
|
|
419
|
+
// concurrent newer CLI may have completed while this caller waited.
|
|
420
|
+
return await refreshOneHelpersDirLocked(helpersDir, version, opts);
|
|
421
|
+
}
|
|
422
|
+
finally {
|
|
423
|
+
releaseLock();
|
|
424
|
+
}
|
|
425
|
+
}
|
|
269
426
|
/**
|
|
270
427
|
* On CLI startup, refresh critical helpers if their stamp is older than the
|
|
271
428
|
* installed CLI version. Two passes:
|
|
@@ -298,9 +455,9 @@ async function refreshOneHelpersDir(helpersDir, version, opts) {
|
|
|
298
455
|
* running `daemon start` (or any command) would see its own older version !=
|
|
299
456
|
* the project's newer stamp and silently overwrite hand-fixed
|
|
300
457
|
* `hook-handler.cjs`/`intelligence.cjs` with its own older, already-superseded
|
|
301
|
-
* bundled copies.
|
|
302
|
-
*
|
|
303
|
-
*
|
|
458
|
+
* bundled copies. The semver guard is therefore re-evaluated while holding a
|
|
459
|
+
* per-directory cross-process lock; otherwise an older process can pass the
|
|
460
|
+
* guard first but finish writing last.
|
|
304
461
|
*
|
|
305
462
|
* `opts` exists for tests ONLY (mirrors daemon-autostart.ts's injectable
|
|
306
463
|
* `SpawnDaemonFn` pattern): the real signed-copy path is otherwise coupled
|
|
@@ -40,7 +40,7 @@ declare const STDERR_REDIRECT_PREFIXES: string[];
|
|
|
40
40
|
declare const AGENTDB_MOCK_FALLBACK_DROP_PREFIXES: string[];
|
|
41
41
|
declare const shouldRedirectToStderr: (msg: unknown) => boolean;
|
|
42
42
|
declare const isAgentdbMockFallbackNoise: (msg: unknown) => boolean;
|
|
43
|
-
declare const origWarn: (...
|
|
44
|
-
declare const origLog: (...
|
|
45
|
-
declare const origError: (...
|
|
43
|
+
declare const origWarn: (message?: any, ...optionalParams: any[]) => void;
|
|
44
|
+
declare const origLog: (message?: any, ...optionalParams: any[]) => void;
|
|
45
|
+
declare const origError: (message?: any, ...optionalParams: any[]) => void;
|
|
46
46
|
//# sourceMappingURL=log-filters.d.ts.map
|
package/dist/src/mcp-server.d.ts
CHANGED
package/dist/src/mcp-server.js
CHANGED
|
@@ -101,6 +101,7 @@ export class MCPServerManager extends EventEmitter {
|
|
|
101
101
|
server;
|
|
102
102
|
startTime;
|
|
103
103
|
healthCheckInterval;
|
|
104
|
+
mcpServers = [];
|
|
104
105
|
constructor(options = {}) {
|
|
105
106
|
super();
|
|
106
107
|
// `options.tools`, populated by the `mcp start --tools` CLI flag, is
|
|
@@ -185,6 +186,11 @@ export class MCPServerManager extends EventEmitter {
|
|
|
185
186
|
});
|
|
186
187
|
this.server = undefined;
|
|
187
188
|
}
|
|
189
|
+
if (this.mcpServers.length > 0) {
|
|
190
|
+
const servers = this.mcpServers;
|
|
191
|
+
this.mcpServers = [];
|
|
192
|
+
await Promise.all(servers.map((server) => server.stop()));
|
|
193
|
+
}
|
|
188
194
|
// Remove PID file
|
|
189
195
|
await this.removePidFile();
|
|
190
196
|
this.startTime = undefined;
|
|
@@ -560,18 +566,48 @@ export class MCPServerManager extends EventEmitter {
|
|
|
560
566
|
warn: (msg, data) => this.emit('log', { level: 'warn', msg, data }),
|
|
561
567
|
error: (msg, data) => this.emit('log', { level: 'error', msg, data }),
|
|
562
568
|
};
|
|
569
|
+
const { listMCPTools, callMCPTool } = await import('./mcp-client.js');
|
|
570
|
+
const fallbackSessionId = `http-${randomUUID()}`;
|
|
571
|
+
const cliTools = filterAdvertisedMcpTools(listMCPTools(), this.options.tools).map((tool) => ({
|
|
572
|
+
name: tool.name,
|
|
573
|
+
description: tool.description,
|
|
574
|
+
inputSchema: tool.inputSchema,
|
|
575
|
+
category: tool.category,
|
|
576
|
+
tags: tool.tags,
|
|
577
|
+
version: tool.version,
|
|
578
|
+
cacheable: tool.cacheable,
|
|
579
|
+
cacheTTL: tool.cacheTTL,
|
|
580
|
+
handler: async (input, context) => {
|
|
581
|
+
try {
|
|
582
|
+
const result = await callMCPTool(tool.name, input || {}, { sessionId: context?.sessionId || fallbackSessionId });
|
|
583
|
+
trackRequest(tool.name, true);
|
|
584
|
+
return result;
|
|
585
|
+
}
|
|
586
|
+
catch (error) {
|
|
587
|
+
trackRequest(tool.name, false);
|
|
588
|
+
throw error;
|
|
589
|
+
}
|
|
590
|
+
},
|
|
591
|
+
}));
|
|
592
|
+
// Use one MCP server with two HTTP transports for the localhost default.
|
|
593
|
+
// Both loopback sockets therefore share sessions, tools, and notifications.
|
|
594
|
+
const dualLoopback = this.options.host === 'localhost';
|
|
563
595
|
const mcpServer = createMCPServer({
|
|
564
596
|
name: 'Claude-Flow MCP Server V3',
|
|
565
597
|
version: '3.0.0',
|
|
566
598
|
transport: this.options.transport,
|
|
567
|
-
host: this.options.host,
|
|
599
|
+
host: dualLoopback ? '127.0.0.1' : this.options.host,
|
|
600
|
+
additionalHosts: dualLoopback && this.options.transport === 'http' ? ['::1'] : undefined,
|
|
568
601
|
port: this.options.port,
|
|
569
602
|
enableMetrics: true,
|
|
570
603
|
enableCaching: true,
|
|
571
604
|
}, logger);
|
|
605
|
+
const registration = mcpServer.registerTools(cliTools);
|
|
606
|
+
if (registration.failed.length > 0) {
|
|
607
|
+
throw new Error(`Failed to register MCP tools: ${registration.failed.join(', ')}`);
|
|
608
|
+
}
|
|
572
609
|
await mcpServer.start();
|
|
573
|
-
|
|
574
|
-
this._mcpServer = mcpServer;
|
|
610
|
+
this.mcpServers = [mcpServer];
|
|
575
611
|
}
|
|
576
612
|
/**
|
|
577
613
|
* Wait for server to be ready
|
|
@@ -199,11 +199,12 @@ function ensureEmbeddings() {
|
|
|
199
199
|
})();
|
|
200
200
|
return embeddingsPromise;
|
|
201
201
|
}
|
|
202
|
-
// Storage paths
|
|
202
|
+
// Storage paths. Note: `.claude-flow/neural/patterns.json` is a SEPARATE
|
|
203
|
+
// file owned by the ReasoningBank store in memory/intelligence.ts — this
|
|
204
|
+
// module only ever reads/writes MODELS_FILE, never a patterns.json.
|
|
203
205
|
const STORAGE_DIR = '.claude-flow';
|
|
204
206
|
const NEURAL_DIR = 'neural';
|
|
205
207
|
const MODELS_FILE = 'models.json';
|
|
206
|
-
const PATTERNS_FILE = 'patterns.json';
|
|
207
208
|
function getNeuralDir() {
|
|
208
209
|
return join(getProjectCwd(), STORAGE_DIR, NEURAL_DIR);
|
|
209
210
|
}
|
|
@@ -262,7 +263,7 @@ export function getNeuralStoreStats() {
|
|
|
262
263
|
patternCount: patterns.length,
|
|
263
264
|
byType,
|
|
264
265
|
modelCount: Object.values(store.models ?? {}).length,
|
|
265
|
-
source: '.claude-flow/neural/
|
|
266
|
+
source: '.claude-flow/neural/models.json (loadNeuralStore)',
|
|
266
267
|
};
|
|
267
268
|
}
|
|
268
269
|
/**
|
|
@@ -16,6 +16,14 @@
|
|
|
16
16
|
*
|
|
17
17
|
* @module v3/cli/memory-bridge
|
|
18
18
|
*/
|
|
19
|
+
/**
|
|
20
|
+
* #3024: AgentDB's optional native controller stack can abort the whole Node
|
|
21
|
+
* process on Windows during registry initialization (a Rust allocation panic),
|
|
22
|
+
* which cannot be caught by JavaScript. Keep the CLI/MCP server on the
|
|
23
|
+
* sql.js + local-embedding path there until the native dependency is proven
|
|
24
|
+
* safe. Advanced users and CI can opt back in explicitly for diagnosis.
|
|
25
|
+
*/
|
|
26
|
+
export declare function shouldDisableNativeBridge(platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv): boolean;
|
|
19
27
|
/**
|
|
20
28
|
* Should this init-time log line be swallowed?
|
|
21
29
|
*
|
|
@@ -38,6 +38,18 @@ const ACTIVE_MEMORY_ROW_SQL = `(status = 'active' OR status IS NULL)`;
|
|
|
38
38
|
* better-sqlite3 bridge") the caller has no way to check.
|
|
39
39
|
*/
|
|
40
40
|
let bridgeFailureReason = null;
|
|
41
|
+
/**
|
|
42
|
+
* #3024: AgentDB's optional native controller stack can abort the whole Node
|
|
43
|
+
* process on Windows during registry initialization (a Rust allocation panic),
|
|
44
|
+
* which cannot be caught by JavaScript. Keep the CLI/MCP server on the
|
|
45
|
+
* sql.js + local-embedding path there until the native dependency is proven
|
|
46
|
+
* safe. Advanced users and CI can opt back in explicitly for diagnosis.
|
|
47
|
+
*/
|
|
48
|
+
export function shouldDisableNativeBridge(platform = process.platform, env = process.env) {
|
|
49
|
+
if (env.CLAUDE_FLOW_DISABLE_BRIDGE === '1')
|
|
50
|
+
return true;
|
|
51
|
+
return platform === 'win32' && env.CLAUDE_FLOW_ENABLE_NATIVE_BRIDGE_ON_WINDOWS !== '1';
|
|
52
|
+
}
|
|
41
53
|
/**
|
|
42
54
|
* ADR-323: reuse memory-initializer's provenance-type allowlist rather than
|
|
43
55
|
* duplicating it (drift risk). Lazy CJS require for the same circular-ESM-
|
|
@@ -154,6 +166,12 @@ export function shouldSuppressInitLog(msg) {
|
|
|
154
166
|
* Returns null if @claude-flow/memory is not available.
|
|
155
167
|
*/
|
|
156
168
|
async function getRegistry(dbPath) {
|
|
169
|
+
if (shouldDisableNativeBridge()) {
|
|
170
|
+
bridgeFailureReason = process.platform === 'win32'
|
|
171
|
+
? 'AgentDB native bridge disabled on Windows after #3024; set CLAUDE_FLOW_ENABLE_NATIVE_BRIDGE_ON_WINDOWS=1 to opt in'
|
|
172
|
+
: 'AgentDB native bridge disabled by CLAUDE_FLOW_DISABLE_BRIDGE=1';
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
157
175
|
if (bridgeAvailable === false)
|
|
158
176
|
return null;
|
|
159
177
|
if (registryInstance)
|
|
File without changes
|