@claude-flow/cli 3.38.9 → 3.38.12

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.
Files changed (96) hide show
  1. package/.claude/helpers/.helpers-version +1 -1
  2. package/.claude/helpers/helpers.manifest.json +2 -2
  3. package/catalog-manifest.json +2 -2
  4. package/dist/src/commands/doctor.js +53 -8
  5. package/dist/src/commands/hooks.js +3 -2
  6. package/dist/src/commands/mcp.js +4 -3
  7. package/dist/src/init/executor.d.ts +6 -0
  8. package/dist/src/init/executor.js +5 -3
  9. package/dist/src/init/helper-refresh.d.ts +16 -9
  10. package/dist/src/init/helper-refresh.js +177 -20
  11. package/dist/src/init/settings-generator.js +6 -2
  12. package/dist/src/log-filters.d.ts +3 -3
  13. package/dist/src/mcp-server.d.ts +1 -0
  14. package/dist/src/mcp-server.js +39 -3
  15. package/dist/src/mcp-tools/neural-tools.js +4 -3
  16. package/dist/src/memory/memory-bridge.d.ts +8 -0
  17. package/dist/src/memory/memory-bridge.js +18 -0
  18. package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
  19. package/node_modules/@claude-flow/mcp/README.md +429 -0
  20. package/node_modules/@claude-flow/mcp/dist/connection-pool.d.ts +36 -0
  21. package/node_modules/@claude-flow/mcp/dist/connection-pool.d.ts.map +1 -0
  22. package/node_modules/@claude-flow/mcp/dist/connection-pool.js +273 -0
  23. package/node_modules/@claude-flow/mcp/dist/connection-pool.js.map +1 -0
  24. package/node_modules/@claude-flow/mcp/dist/index.d.ts +75 -0
  25. package/node_modules/@claude-flow/mcp/dist/index.d.ts.map +1 -0
  26. package/node_modules/@claude-flow/mcp/dist/index.js +85 -0
  27. package/node_modules/@claude-flow/mcp/dist/index.js.map +1 -0
  28. package/node_modules/@claude-flow/mcp/dist/oauth.d.ts +146 -0
  29. package/node_modules/@claude-flow/mcp/dist/oauth.d.ts.map +1 -0
  30. package/node_modules/@claude-flow/mcp/dist/oauth.js +318 -0
  31. package/node_modules/@claude-flow/mcp/dist/oauth.js.map +1 -0
  32. package/node_modules/@claude-flow/mcp/dist/prompt-registry.d.ts +90 -0
  33. package/node_modules/@claude-flow/mcp/dist/prompt-registry.d.ts.map +1 -0
  34. package/node_modules/@claude-flow/mcp/dist/prompt-registry.js +209 -0
  35. package/node_modules/@claude-flow/mcp/dist/prompt-registry.js.map +1 -0
  36. package/node_modules/@claude-flow/mcp/dist/rate-limiter.d.ts +86 -0
  37. package/node_modules/@claude-flow/mcp/dist/rate-limiter.d.ts.map +1 -0
  38. package/node_modules/@claude-flow/mcp/dist/rate-limiter.js +197 -0
  39. package/node_modules/@claude-flow/mcp/dist/rate-limiter.js.map +1 -0
  40. package/node_modules/@claude-flow/mcp/dist/resource-registry.d.ts +144 -0
  41. package/node_modules/@claude-flow/mcp/dist/resource-registry.d.ts.map +1 -0
  42. package/node_modules/@claude-flow/mcp/dist/resource-registry.js +405 -0
  43. package/node_modules/@claude-flow/mcp/dist/resource-registry.js.map +1 -0
  44. package/node_modules/@claude-flow/mcp/dist/sampling.d.ts +102 -0
  45. package/node_modules/@claude-flow/mcp/dist/sampling.d.ts.map +1 -0
  46. package/node_modules/@claude-flow/mcp/dist/sampling.js +268 -0
  47. package/node_modules/@claude-flow/mcp/dist/sampling.js.map +1 -0
  48. package/node_modules/@claude-flow/mcp/dist/schema-validator.d.ts +30 -0
  49. package/node_modules/@claude-flow/mcp/dist/schema-validator.d.ts.map +1 -0
  50. package/node_modules/@claude-flow/mcp/dist/schema-validator.js +182 -0
  51. package/node_modules/@claude-flow/mcp/dist/schema-validator.js.map +1 -0
  52. package/node_modules/@claude-flow/mcp/dist/server.d.ts +125 -0
  53. package/node_modules/@claude-flow/mcp/dist/server.d.ts.map +1 -0
  54. package/node_modules/@claude-flow/mcp/dist/server.js +849 -0
  55. package/node_modules/@claude-flow/mcp/dist/server.js.map +1 -0
  56. package/node_modules/@claude-flow/mcp/dist/session-manager.d.ts +55 -0
  57. package/node_modules/@claude-flow/mcp/dist/session-manager.d.ts.map +1 -0
  58. package/node_modules/@claude-flow/mcp/dist/session-manager.js +252 -0
  59. package/node_modules/@claude-flow/mcp/dist/session-manager.js.map +1 -0
  60. package/node_modules/@claude-flow/mcp/dist/task-manager.d.ts +81 -0
  61. package/node_modules/@claude-flow/mcp/dist/task-manager.d.ts.map +1 -0
  62. package/node_modules/@claude-flow/mcp/dist/task-manager.js +337 -0
  63. package/node_modules/@claude-flow/mcp/dist/task-manager.js.map +1 -0
  64. package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts +96 -0
  65. package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts.map +1 -0
  66. package/node_modules/@claude-flow/mcp/dist/tool-registry.js +399 -0
  67. package/node_modules/@claude-flow/mcp/dist/tool-registry.js.map +1 -0
  68. package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts +60 -0
  69. package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts.map +1 -0
  70. package/node_modules/@claude-flow/mcp/dist/transport/http.js +503 -0
  71. package/node_modules/@claude-flow/mcp/dist/transport/http.js.map +1 -0
  72. package/node_modules/@claude-flow/mcp/dist/transport/index.d.ts +50 -0
  73. package/node_modules/@claude-flow/mcp/dist/transport/index.d.ts.map +1 -0
  74. package/node_modules/@claude-flow/mcp/dist/transport/index.js +181 -0
  75. package/node_modules/@claude-flow/mcp/dist/transport/index.js.map +1 -0
  76. package/node_modules/@claude-flow/mcp/dist/transport/stdio.d.ts +43 -0
  77. package/node_modules/@claude-flow/mcp/dist/transport/stdio.d.ts.map +1 -0
  78. package/node_modules/@claude-flow/mcp/dist/transport/stdio.js +194 -0
  79. package/node_modules/@claude-flow/mcp/dist/transport/stdio.js.map +1 -0
  80. package/node_modules/@claude-flow/mcp/dist/transport/websocket.d.ts +65 -0
  81. package/node_modules/@claude-flow/mcp/dist/transport/websocket.d.ts.map +1 -0
  82. package/node_modules/@claude-flow/mcp/dist/transport/websocket.js +314 -0
  83. package/node_modules/@claude-flow/mcp/dist/transport/websocket.js.map +1 -0
  84. package/node_modules/@claude-flow/mcp/dist/types.d.ts +482 -0
  85. package/node_modules/@claude-flow/mcp/dist/types.d.ts.map +1 -0
  86. package/node_modules/@claude-flow/mcp/dist/types.js +40 -0
  87. package/node_modules/@claude-flow/mcp/dist/types.js.map +1 -0
  88. package/node_modules/@claude-flow/mcp/package.json +50 -0
  89. package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
  90. package/node_modules/@claude-flow/security/dist/path-validator.d.ts +23 -0
  91. package/node_modules/@claude-flow/security/dist/path-validator.d.ts.map +1 -1
  92. package/node_modules/@claude-flow/security/dist/path-validator.js +128 -27
  93. package/node_modules/@claude-flow/security/dist/path-validator.js.map +1 -1
  94. package/package.json +9 -2
  95. package/.claude/.proven-config-version +0 -1
  96. package/.claude/proven-config.json +0 -42
@@ -1 +1 @@
1
- 3.32.29
1
+ 3.38.12
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest": {
3
- "version": "3.38.9",
3
+ "version": "3.38.12",
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": "3d1AL1dnpOuXC44aTkQIjVmh6laRNnIEo99/iamUYH1IlCzQEwvA2svMe3hgchi7XjeKFRCkjWWtGpq0jv5uDg==",
11
+ "signature": "8JINtA8uubiBqNQbsz8OL2fcc4xmKGk6gKPGoa1ib7/21nYDaYWiWrZB/4xli8AGUdmQuMS9GT6ANbpPjqAcCw==",
12
12
  "algorithm": "ed25519"
13
13
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "generation": 4,
4
- "generatedAt": "2026-08-13T12:38:48.969Z",
5
- "gitSha": "c1e8e0c4",
4
+ "generatedAt": "2026-08-15T15:33:50.000Z",
5
+ "gitSha": "44b9dad1",
6
6
  "catalog": {
7
7
  "agents": 164,
8
8
  "tools": 397,
@@ -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 skipped (optional native module)`,
420
- fix: 'npm install better-sqlite3 (enables WAL-aware structural checks on every `doctor` run)',
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 once the module loaded, any
502
- // open/query failure is resolved (fail) right here, not silently
503
- // reclassified as "sql.js fallback, main-image-only" by an outer catch.
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 { name: 'Memory Integrity', status: 'pass', message: `${dbPath} — PRAGMA integrity_check: ok [main-image-only fallback — sql.js can't see WAL-only data; install better-sqlite3 for full coverage]` };
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
- const inClaudeCode = !!(process.env.CLAUDE_CODE || process.env.CLAUDE_PROJECT_DIR || process.env.MCP_SESSION_ID);
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
  }
@@ -4457,7 +4457,8 @@ const teammateIdleCommand = {
4457
4457
  short: 'a',
4458
4458
  description: 'Automatically assign pending tasks to idle teammate',
4459
4459
  type: 'boolean',
4460
- default: true
4460
+ // #3031: explicit opt-in only. Idle/liveness is not assignment authority.
4461
+ default: false
4461
4462
  },
4462
4463
  {
4463
4464
  name: 'check-task-list',
@@ -4483,7 +4484,7 @@ const teammateIdleCommand = {
4483
4484
  { command: 'claude-flow hooks teammate-idle -t worker-1 --check-task-list', description: 'Check tasks for specific teammate' }
4484
4485
  ],
4485
4486
  action: async (ctx) => {
4486
- const autoAssign = ctx.flags.autoAssign !== false;
4487
+ const autoAssign = ctx.flags.autoAssign === true;
4487
4488
  const checkTaskList = ctx.flags.checkTaskList !== false;
4488
4489
  const teammateId = ctx.flags.teammateId;
4489
4490
  const teamName = ctx.flags.teamName;
@@ -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 || 'all';
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();
@@ -23,6 +23,12 @@ export interface UpgradeResult {
23
23
  /** Added by --settings flag */
24
24
  settingsUpdated?: string[];
25
25
  }
26
+ /**
27
+ * Merge new settings into existing settings.json
28
+ * Preserves user customizations while adding new features like Agent Teams
29
+ * Uses platform-specific commands for Mac, Linux, and Windows
30
+ */
31
+ export declare function mergeSettingsForUpgrade(existing: Record<string, unknown>): Record<string, unknown>;
26
32
  /**
27
33
  * Execute upgrade - updates helpers and creates missing metrics without losing data
28
34
  * This is safe for existing users who want the latest statusline fixes
@@ -276,7 +276,7 @@ export async function executeInit(options) {
276
276
  * Preserves user customizations while adding new features like Agent Teams
277
277
  * Uses platform-specific commands for Mac, Linux, and Windows
278
278
  */
279
- function mergeSettingsForUpgrade(existing) {
279
+ export function mergeSettingsForUpgrade(existing) {
280
280
  const merged = { ...existing };
281
281
  const platform = detectPlatform();
282
282
  const isWindows = platform.os === 'windows';
@@ -440,13 +440,15 @@ function mergeSettingsForUpgrade(existing) {
440
440
  taskListEnabled: true,
441
441
  mailboxEnabled: true,
442
442
  coordination: {
443
- autoAssignOnIdle: true,
443
+ // #3031: an upgrade must remove the unsafe generated default. Idle
444
+ // status is not proof that a task is unowned or inside agent scope.
445
+ autoAssignOnIdle: false,
444
446
  trainPatternsOnComplete: true,
445
447
  notifyLeadOnComplete: true,
446
448
  sharedMemoryNamespace: 'agent-teams',
447
449
  },
448
450
  hooks: {
449
- teammateIdle: { enabled: true, autoAssign: true, checkTaskList: true },
451
+ teammateIdle: { enabled: true, autoAssign: false, checkTaskList: true },
450
452
  taskCompleted: { enabled: true, trainPatterns: true, notifyLead: true },
451
453
  },
452
454
  },
@@ -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. Comparing with `semver.gt` instead of `!==` makes that
44
- * impossible: an older or equal installed version is always a no-op,
45
- * regardless of how it got invoked.
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
- fs.copyFileSync(path.join(source, name), tp);
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
- fs.copyFileSync(path.join(source, HELPERS_MANIFEST_FILE), path.join(helpersDir, HELPERS_MANIFEST_FILE));
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
- fs.writeFileSync(path.join(helpersDir, HELPERS_STAMP_FILE), version, 'utf-8');
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
- fs.writeFileSync(tp, content, 'utf-8');
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
- fs.writeFileSync(path.join(helpersDir, HELPERS_STAMP_FILE), version, 'utf-8');
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. Comparing with `semver.gt`
220
- * instead of `!==` makes that impossible: an older or equal installed
221
- * version is always a no-op, regardless of how it got invoked.
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 refreshOneHelpersDir(helpersDir, version, opts) {
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. Comparing with `semver.gt` instead of `!==` makes that
302
- * impossible: an older or equal installed version is always a no-op,
303
- * regardless of how it got invoked.
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
@@ -83,7 +83,11 @@ export function generateSettings(options) {
83
83
  taskListEnabled: true,
84
84
  mailboxEnabled: true,
85
85
  coordination: {
86
- autoAssignOnIdle: true, // Auto-assign pending tasks when teammate is idle
86
+ // #3031: Liveness alone is not authority. Keep idle assignment off
87
+ // until the scheduler can prove task ownership, agent scope, and a
88
+ // refusal/back-off state. Users may explicitly opt in after supplying
89
+ // those controls in their host configuration.
90
+ autoAssignOnIdle: false,
87
91
  trainPatternsOnComplete: true, // Train neural patterns when tasks complete
88
92
  notifyLeadOnComplete: true, // Notify team lead when tasks complete
89
93
  sharedMemoryNamespace: 'agent-teams', // Memory namespace for team coordination
@@ -91,7 +95,7 @@ export function generateSettings(options) {
91
95
  hooks: {
92
96
  teammateIdle: {
93
97
  enabled: true,
94
- autoAssign: true,
98
+ autoAssign: false,
95
99
  checkTaskList: true,
96
100
  },
97
101
  taskCompleted: {
@@ -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: (...data: any[]) => void;
44
- declare const origLog: (...data: any[]) => void;
45
- declare const origError: (...data: any[]) => void;
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
@@ -84,6 +84,7 @@ export declare class MCPServerManager extends EventEmitter {
84
84
  private server?;
85
85
  private startTime?;
86
86
  private healthCheckInterval?;
87
+ private mcpServers;
87
88
  constructor(options?: MCPServerOptions);
88
89
  /**
89
90
  * Start the MCP server
@@ -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
- // Store reference for stopping
574
- this._mcpServer = mcpServer;
610
+ this.mcpServers = [mcpServer];
575
611
  }
576
612
  /**
577
613
  * Wait for server to be ready