claude-flow 3.7.0-alpha.14 → 3.7.0-alpha.16

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.
@@ -1 +1,10 @@
1
- {}
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(mkdir -p /tmp/ruflo-smoke)",
5
+ "Bash(rm -rf /tmp/ruflo-smoke/*)",
6
+ "Read(//tmp/**)",
7
+ "Bash(npm pack *)"
8
+ ]
9
+ }
10
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-flow",
3
- "version": "3.7.0-alpha.14",
3
+ "version": "3.7.0-alpha.16",
4
4
  "description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -8,6 +8,9 @@
8
8
  *
9
9
  * @module v3/cli/memory-initializer
10
10
  */
11
+ export declare function getMemoryRoot(): string;
12
+ /** For tests + the `memory configure` flow that mutates the config at runtime. */
13
+ export declare function _resetMemoryRootCache(): void;
11
14
  /**
12
15
  * Enhanced schema with pattern confidence, temporal decay, versioning
13
16
  * Vector embeddings enabled for semantic search
@@ -11,6 +11,61 @@
11
11
  import * as fs from 'fs';
12
12
  import * as path from 'path';
13
13
  import { readFileMaybeEncrypted, writeFileRestricted } from '../fs-secure.js';
14
+ /**
15
+ * #1854: previously every site that needed the memory directory hardcoded
16
+ * `getMemoryRoot()`, so the documented config entry
17
+ * points (`memory.persistPath` config field, `memory configure --path`,
18
+ * `CLAUDE_FLOW_MEMORY_PATH` env var) all silently no-op'd. This helper
19
+ * is the single source of truth — every `.swarm/memory.db` resolution in
20
+ * this file flows through it.
21
+ *
22
+ * Precedence (highest → lowest):
23
+ * 1. CLAUDE_FLOW_MEMORY_PATH env var
24
+ * 2. memory.persistPath / memory.path in claude-flow.config.json (cwd or
25
+ * the directory the CLI was invoked from)
26
+ * 3. Default: cwd/.swarm
27
+ *
28
+ * Cached per-process so repeated lookups are cheap; reset only by spawning
29
+ * a fresh process (which is how config changes already propagate).
30
+ */
31
+ let _memoryRootCache;
32
+ export function getMemoryRoot() {
33
+ if (_memoryRootCache !== undefined)
34
+ return _memoryRootCache;
35
+ // 1. Env var
36
+ const envPath = process.env.CLAUDE_FLOW_MEMORY_PATH;
37
+ if (envPath && envPath.trim().length > 0) {
38
+ _memoryRootCache = path.resolve(envPath);
39
+ return _memoryRootCache;
40
+ }
41
+ // 2. Config file (claude-flow.config.json)
42
+ const configCandidates = [
43
+ path.resolve(process.cwd(), 'claude-flow.config.json'),
44
+ path.resolve(process.cwd(), '.claude-flow', 'config.json'),
45
+ ];
46
+ for (const configPath of configCandidates) {
47
+ if (!fs.existsSync(configPath))
48
+ continue;
49
+ try {
50
+ const raw = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
51
+ const fromConfig = raw?.memory?.persistPath ?? raw?.memory?.path;
52
+ if (typeof fromConfig === 'string' && fromConfig.trim().length > 0) {
53
+ _memoryRootCache = path.resolve(fromConfig);
54
+ return _memoryRootCache;
55
+ }
56
+ }
57
+ catch {
58
+ /* malformed config — fall through to default */
59
+ }
60
+ }
61
+ // 3. Default
62
+ _memoryRootCache = path.resolve(process.cwd(), '.swarm');
63
+ return _memoryRootCache;
64
+ }
65
+ /** For tests + the `memory configure` flow that mutates the config at runtime. */
66
+ export function _resetMemoryRootCache() {
67
+ _memoryRootCache = undefined;
68
+ }
14
69
  // ADR-053: Lazy import of AgentDB v3 bridge
15
70
  let _bridge;
16
71
  async function getBridge() {
@@ -358,7 +413,7 @@ export async function getHNSWIndex(options) {
358
413
  }
359
414
  const { VectorDb } = ruvectorCore;
360
415
  // Persistent storage paths — resolve to absolute to survive CWD changes
361
- const swarmDir = path.resolve(process.cwd(), '.swarm');
416
+ const swarmDir = getMemoryRoot();
362
417
  if (!fs.existsSync(swarmDir)) {
363
418
  fs.mkdirSync(swarmDir, { recursive: true });
364
419
  }
@@ -459,7 +514,7 @@ function saveHNSWMetadata() {
459
514
  if (!hnswIndex?.entries)
460
515
  return;
461
516
  try {
462
- const swarmDir = path.join(process.cwd(), '.swarm');
517
+ const swarmDir = getMemoryRoot();
463
518
  const metadataPath = path.join(swarmDir, 'hnsw.metadata.json');
464
519
  const metadata = Array.from(hnswIndex.entries.entries());
465
520
  fs.writeFileSync(metadataPath, JSON.stringify(metadata));
@@ -974,7 +1029,7 @@ async function activateControllerRegistry(dbPath, verbose) {
974
1029
  */
975
1030
  export async function initializeMemoryDatabase(options) {
976
1031
  const { backend = 'hybrid', dbPath: customPath, force = false, verbose = false, migrate = true } = options;
977
- const swarmDir = path.join(process.cwd(), '.swarm');
1032
+ const swarmDir = getMemoryRoot();
978
1033
  const dbPath = customPath || path.join(swarmDir, 'memory.db');
979
1034
  const dbDir = path.dirname(dbPath);
980
1035
  try {
@@ -1162,7 +1217,7 @@ export async function initializeMemoryDatabase(options) {
1162
1217
  * Check if memory database is properly initialized
1163
1218
  */
1164
1219
  export async function checkMemoryInitialization(dbPath) {
1165
- const swarmDir = path.join(process.cwd(), '.swarm');
1220
+ const swarmDir = getMemoryRoot();
1166
1221
  const path_ = dbPath || path.join(swarmDir, 'memory.db');
1167
1222
  if (!fs.existsSync(path_)) {
1168
1223
  return { initialized: false };
@@ -1211,7 +1266,7 @@ export async function checkMemoryInitialization(dbPath) {
1211
1266
  * Reduces confidence of patterns that haven't been used recently
1212
1267
  */
1213
1268
  export async function applyTemporalDecay(dbPath) {
1214
- const swarmDir = path.join(process.cwd(), '.swarm');
1269
+ const swarmDir = getMemoryRoot();
1215
1270
  const path_ = dbPath || path.join(swarmDir, 'memory.db');
1216
1271
  try {
1217
1272
  const initSqlJs = (await import('sql.js')).default;
@@ -1737,7 +1792,7 @@ export async function storeEntry(options) {
1737
1792
  }
1738
1793
  // Fallback: raw sql.js
1739
1794
  const { key, value, namespace = 'default', generateEmbeddingFlag = true, tags = [], ttl, dbPath: customPath, upsert = false } = options;
1740
- const swarmDir = path.resolve(process.cwd(), '.swarm');
1795
+ const swarmDir = getMemoryRoot();
1741
1796
  const dbPath = customPath ? path.resolve(customPath) : path.join(swarmDir, 'memory.db');
1742
1797
  try {
1743
1798
  if (!fs.existsSync(dbPath)) {
@@ -1830,7 +1885,7 @@ export async function searchEntries(options) {
1830
1885
  // Fallback: raw sql.js
1831
1886
  const { query, namespace, limit = 10, threshold = 0.3, dbPath: customPath } = options;
1832
1887
  const effectiveNamespace = namespace || 'all';
1833
- const swarmDir = path.resolve(process.cwd(), '.swarm');
1888
+ const swarmDir = getMemoryRoot();
1834
1889
  const dbPath = customPath ? path.resolve(customPath) : path.join(swarmDir, 'memory.db');
1835
1890
  const startTime = Date.now();
1836
1891
  try {
@@ -2001,7 +2056,7 @@ export async function listEntries(options) {
2001
2056
  }
2002
2057
  // Fallback: raw sql.js
2003
2058
  const { namespace, limit = 20, offset = 0, dbPath: customPath } = options;
2004
- const swarmDir = path.join(process.cwd(), '.swarm');
2059
+ const swarmDir = getMemoryRoot();
2005
2060
  const dbPath = customPath || path.join(swarmDir, 'memory.db');
2006
2061
  try {
2007
2062
  if (!fs.existsSync(dbPath)) {
@@ -2086,7 +2141,7 @@ export async function getEntry(options) {
2086
2141
  }
2087
2142
  // Fallback: raw sql.js
2088
2143
  const { key, namespace = 'default', dbPath: customPath } = options;
2089
- const swarmDir = path.join(process.cwd(), '.swarm');
2144
+ const swarmDir = getMemoryRoot();
2090
2145
  const dbPath = customPath || path.join(swarmDir, 'memory.db');
2091
2146
  try {
2092
2147
  if (!fs.existsSync(dbPath)) {
@@ -2190,7 +2245,7 @@ export async function deleteEntry(options) {
2190
2245
  }
2191
2246
  // Fallback: raw sql.js
2192
2247
  const { key, namespace = 'default', dbPath: customPath } = options;
2193
- const swarmDir = path.join(process.cwd(), '.swarm');
2248
+ const swarmDir = getMemoryRoot();
2194
2249
  const dbPath = customPath || path.join(swarmDir, 'memory.db');
2195
2250
  try {
2196
2251
  if (!fs.existsSync(dbPath)) {
@@ -222,6 +222,12 @@ export declare class HeadlessWorkerExecutor extends EventEmitter {
222
222
  /**
223
223
  * Get pool status
224
224
  */
225
+ /**
226
+ * #1855: return the PIDs of all currently-running headless worker
227
+ * children. Used by `WorkerDaemon` to snapshot active child PIDs to
228
+ * disk so the next lifetime can reap orphans after a hard crash.
229
+ */
230
+ getActiveChildPids(): number[];
225
231
  getPoolStatus(): PoolStatus;
226
232
  /**
227
233
  * Get number of active executions
@@ -457,6 +457,20 @@ export class HeadlessWorkerExecutor extends EventEmitter {
457
457
  /**
458
458
  * Get pool status
459
459
  */
460
+ /**
461
+ * #1855: return the PIDs of all currently-running headless worker
462
+ * children. Used by `WorkerDaemon` to snapshot active child PIDs to
463
+ * disk so the next lifetime can reap orphans after a hard crash.
464
+ */
465
+ getActiveChildPids() {
466
+ const out = [];
467
+ for (const entry of this.processPool.values()) {
468
+ const pid = entry.process?.pid;
469
+ if (typeof pid === 'number' && pid > 0)
470
+ out.push(pid);
471
+ }
472
+ return out;
473
+ }
460
474
  getPoolStatus() {
461
475
  const now = Date.now();
462
476
  return {
@@ -831,13 +845,33 @@ Analyze the above codebase context and provide your response following the forma
831
845
  // Set model
832
846
  // Resolve model: user env override > config override > default alias
833
847
  env.ANTHROPIC_MODEL = process.env.ANTHROPIC_MODEL || MODEL_IDS[options.model];
834
- // Spawn claude CLI process
835
- const child = spawn('claude', ['--print', prompt], {
848
+ // Spawn claude CLI process. #1852: previously the prompt was passed
849
+ // as a positional CLI arg. On Windows `claude` resolves to
850
+ // `claude.cmd`, which Node refuses to exec directly (CVE-2024-27980
851
+ // mitigation) — it routes through `cmd.exe /d /s /c`, which then
852
+ // re-tokenizes the entire command line including the prompt.
853
+ // Source-code prompts contain `>` `<` `&` `|` (arrow functions,
854
+ // comparisons, redirections) — cmd.exe parses those as redirects
855
+ // and creates zero-byte files in cwd named after the next token
856
+ // (`controller.abort()`, `{const`, `0`, `HTTP`, etc.).
857
+ //
858
+ // Fix: pipe the prompt via stdin instead. `child.stdin.end(prompt)`
859
+ // writes the prompt and closes stdin atomically — the EOF still
860
+ // unblocks `claude --print` (the original concern in #1395) but no
861
+ // shell tokenization touches the prompt.
862
+ const child = spawn('claude', ['--print'], {
836
863
  cwd: this.projectRoot,
837
864
  env,
838
- stdio: ['ignore', 'pipe', 'pipe'], // 'ignore' closes stdin at spawn — fixes #1395 where claude --print blocks on EOF
865
+ stdio: ['pipe', 'pipe', 'pipe'],
839
866
  windowsHide: true, // Prevent phantom console windows on Windows
840
867
  });
868
+ try {
869
+ child.stdin?.end(prompt);
870
+ }
871
+ catch {
872
+ // stdin already closed (e.g. spawn failed) — `error` handler below
873
+ // will surface the real cause.
874
+ }
841
875
  // Setup timeout
842
876
  const timeoutHandle = setTimeout(() => {
843
877
  if (this.processPool.has(options.executionId)) {
@@ -106,6 +106,41 @@ export declare class WorkerDaemon extends EventEmitter {
106
106
  * Setup graceful shutdown handlers
107
107
  */
108
108
  private setupShutdownHandlers;
109
+ /**
110
+ * #1855: install crash handlers for uncaught exceptions and unhandled
111
+ * rejections. Without these, a thrown error from any timer callback,
112
+ * worker logic path, or transitive import crashes the daemon process
113
+ * silently — the PID file leaks and any in-flight child processes
114
+ * orphan. With these, we log a structured crash record, run stop()
115
+ * to clean up, then exit 1 so the process actually dies (otherwise
116
+ * Node would crash anyway after the handler returns).
117
+ */
118
+ private installCrashHandlers;
119
+ /**
120
+ * Append a structured crash record to .claude-flow/logs/crash.log.
121
+ * Inspectable by hand or via `ruflo daemon status` follow-ups.
122
+ */
123
+ private writeCrashRecord;
124
+ /**
125
+ * Path to the on-disk children registry — list of headless worker
126
+ * child PIDs the daemon currently owns. #1855: written on every
127
+ * execution:start / :complete / :error transition; read by the next
128
+ * lifetime to reap orphans after a hard crash.
129
+ */
130
+ private get childrenFile();
131
+ /**
132
+ * Snapshot the currently-active headless worker child PIDs to disk.
133
+ * Best-effort; failures don't propagate.
134
+ */
135
+ private writeChildrenSnapshot;
136
+ /**
137
+ * #1855: reap orphan headless worker children left behind by a
138
+ * previous crashed lifetime. Reads `.claude-flow/daemon-children.json`,
139
+ * SIGTERMs any PID still alive that doesn't belong to the current
140
+ * daemon, then truncates the file. Called at the top of `start()`
141
+ * so the next lifetime starts with a clean process tree.
142
+ */
143
+ private reapOrphanedChildren;
109
144
  /**
110
145
  * Check if system resources allow worker execution
111
146
  */
@@ -128,6 +163,15 @@ export declare class WorkerDaemon extends EventEmitter {
128
163
  /**
129
164
  * Check if another daemon instance is already running.
130
165
  * Returns the existing PID if alive, or null if no daemon is running.
166
+ *
167
+ * #1853: ignore self-PID matches. The detached-spawn path in
168
+ * `commands/daemon.ts` writes the child's PID into the file as a
169
+ * fallback after a 500ms wait. If the child reaches `start()` slower
170
+ * than the parent's 500ms wait (observed on Node 25 / macOS 26), the
171
+ * child reads its own PID back from the file and concludes "another
172
+ * daemon is already running" — so it exits before scheduling workers
173
+ * and `daemon status` reports STOPPED forever. A daemon process is
174
+ * never "another instance" of itself; treat self-match as absence.
131
175
  */
132
176
  private checkExistingDaemon;
133
177
  /**
@@ -79,6 +79,9 @@ export class WorkerDaemon extends EventEmitter {
79
79
  };
80
80
  // Setup graceful shutdown handlers
81
81
  this.setupShutdownHandlers();
82
+ // #1855: install crash handlers so uncaught exceptions and unhandled
83
+ // rejections don't leak the PID file or orphan child processes.
84
+ this.installCrashHandlers();
82
85
  // Ensure directories exist
83
86
  if (!existsSync(claudeFlowDir)) {
84
87
  mkdirSync(claudeFlowDir, { recursive: true });
@@ -104,14 +107,19 @@ export class WorkerDaemon extends EventEmitter {
104
107
  this.headlessAvailable = await this.headlessExecutor.isAvailable();
105
108
  if (this.headlessAvailable) {
106
109
  this.log('info', 'Claude Code headless mode available - AI workers enabled');
107
- // Forward headless executor events
110
+ // Forward headless executor events. #1855: also snapshot the
111
+ // active child PIDs to disk on every transition so the next
112
+ // lifetime can reap orphans after a hard crash.
108
113
  this.headlessExecutor.on('execution:start', (data) => {
114
+ this.writeChildrenSnapshot();
109
115
  this.emit('headless:start', data);
110
116
  });
111
117
  this.headlessExecutor.on('execution:complete', (data) => {
118
+ this.writeChildrenSnapshot();
112
119
  this.emit('headless:complete', data);
113
120
  });
114
121
  this.headlessExecutor.on('execution:error', (data) => {
122
+ this.writeChildrenSnapshot();
115
123
  this.emit('headless:error', data);
116
124
  });
117
125
  this.headlessExecutor.on('output', (data) => {
@@ -249,6 +257,126 @@ export class WorkerDaemon extends EventEmitter {
249
257
  process.on('SIGINT', shutdown);
250
258
  process.on('SIGHUP', shutdown);
251
259
  }
260
+ /**
261
+ * #1855: install crash handlers for uncaught exceptions and unhandled
262
+ * rejections. Without these, a thrown error from any timer callback,
263
+ * worker logic path, or transitive import crashes the daemon process
264
+ * silently — the PID file leaks and any in-flight child processes
265
+ * orphan. With these, we log a structured crash record, run stop()
266
+ * to clean up, then exit 1 so the process actually dies (otherwise
267
+ * Node would crash anyway after the handler returns).
268
+ */
269
+ installCrashHandlers() {
270
+ const onCrash = (kind, err) => {
271
+ // Best-effort logging; never throw from inside the crash handler.
272
+ try {
273
+ this.writeCrashRecord(kind, err);
274
+ }
275
+ catch { /* nothing more we can do */ }
276
+ try {
277
+ // Synchronous stop — don't await; the process is dying. Just
278
+ // remove the PID file and snapshot state so the next start
279
+ // sees a clean slate.
280
+ this.removePidFile();
281
+ this.saveState();
282
+ // Snapshot any in-flight child PIDs one last time so the next
283
+ // lifetime can reap them.
284
+ this.writeChildrenSnapshot();
285
+ }
286
+ catch { /* ignore */ }
287
+ // Exit non-zero so supervisors / shells see the failure.
288
+ process.exit(1);
289
+ };
290
+ process.on('uncaughtException', (err) => onCrash('uncaughtException', err));
291
+ process.on('unhandledRejection', (err) => onCrash('unhandledRejection', err));
292
+ }
293
+ /**
294
+ * Append a structured crash record to .claude-flow/logs/crash.log.
295
+ * Inspectable by hand or via `ruflo daemon status` follow-ups.
296
+ */
297
+ writeCrashRecord(kind, err) {
298
+ const logDir = this.config.logDir;
299
+ if (!existsSync(logDir))
300
+ mkdirSync(logDir, { recursive: true });
301
+ const crashLog = join(logDir, 'crash.log');
302
+ const ts = new Date().toISOString();
303
+ const message = err instanceof Error ? err.message : String(err);
304
+ const stack = err instanceof Error && err.stack ? err.stack : '<no stack>';
305
+ const record = `[${ts}] [${kind}] pid=${process.pid} ${message}\n${stack}\n---\n`;
306
+ appendFileSync(crashLog, record, 'utf-8');
307
+ this.log('warn', `Daemon crashed (${kind}): ${message} — see ${crashLog}`);
308
+ }
309
+ /**
310
+ * Path to the on-disk children registry — list of headless worker
311
+ * child PIDs the daemon currently owns. #1855: written on every
312
+ * execution:start / :complete / :error transition; read by the next
313
+ * lifetime to reap orphans after a hard crash.
314
+ */
315
+ get childrenFile() {
316
+ return join(this.projectRoot, '.claude-flow', 'daemon-children.json');
317
+ }
318
+ /**
319
+ * Snapshot the currently-active headless worker child PIDs to disk.
320
+ * Best-effort; failures don't propagate.
321
+ */
322
+ writeChildrenSnapshot() {
323
+ if (!this.headlessExecutor)
324
+ return;
325
+ try {
326
+ const pids = this.headlessExecutor.getActiveChildPids();
327
+ const dir = join(this.projectRoot, '.claude-flow');
328
+ if (!existsSync(dir))
329
+ mkdirSync(dir, { recursive: true });
330
+ writeFileSync(this.childrenFile, JSON.stringify({ pids, daemonPid: process.pid, timestamp: new Date().toISOString() }, null, 2), 'utf-8');
331
+ }
332
+ catch { /* best-effort */ }
333
+ }
334
+ /**
335
+ * #1855: reap orphan headless worker children left behind by a
336
+ * previous crashed lifetime. Reads `.claude-flow/daemon-children.json`,
337
+ * SIGTERMs any PID still alive that doesn't belong to the current
338
+ * daemon, then truncates the file. Called at the top of `start()`
339
+ * so the next lifetime starts with a clean process tree.
340
+ */
341
+ reapOrphanedChildren() {
342
+ const file = this.childrenFile;
343
+ if (!existsSync(file))
344
+ return;
345
+ let snapshot;
346
+ try {
347
+ snapshot = JSON.parse(readFileSync(file, 'utf-8'));
348
+ }
349
+ catch {
350
+ try {
351
+ unlinkSync(file);
352
+ }
353
+ catch { /* ignore */ }
354
+ return;
355
+ }
356
+ const pids = Array.isArray(snapshot.pids) ? snapshot.pids : [];
357
+ let reaped = 0;
358
+ for (const pid of pids) {
359
+ if (typeof pid !== 'number' || pid <= 0)
360
+ continue;
361
+ if (pid === process.pid)
362
+ continue; // never our own PID
363
+ try {
364
+ process.kill(pid, 0); // is alive?
365
+ process.kill(pid, 'SIGTERM');
366
+ reaped++;
367
+ }
368
+ catch {
369
+ // already dead — fine
370
+ }
371
+ }
372
+ if (reaped > 0) {
373
+ this.log('info', `Reaped ${reaped} orphan headless worker child(ren) from previous lifetime`);
374
+ }
375
+ try {
376
+ unlinkSync(file);
377
+ }
378
+ catch { /* ignore */ }
379
+ }
252
380
  /**
253
381
  * Check if system resources allow worker execution
254
382
  */
@@ -370,6 +498,15 @@ export class WorkerDaemon extends EventEmitter {
370
498
  /**
371
499
  * Check if another daemon instance is already running.
372
500
  * Returns the existing PID if alive, or null if no daemon is running.
501
+ *
502
+ * #1853: ignore self-PID matches. The detached-spawn path in
503
+ * `commands/daemon.ts` writes the child's PID into the file as a
504
+ * fallback after a 500ms wait. If the child reaches `start()` slower
505
+ * than the parent's 500ms wait (observed on Node 25 / macOS 26), the
506
+ * child reads its own PID back from the file and concludes "another
507
+ * daemon is already running" — so it exits before scheduling workers
508
+ * and `daemon status` reports STOPPED forever. A daemon process is
509
+ * never "another instance" of itself; treat self-match as absence.
373
510
  */
374
511
  checkExistingDaemon() {
375
512
  if (!existsSync(this.pidFile))
@@ -378,6 +515,10 @@ export class WorkerDaemon extends EventEmitter {
378
515
  const pid = parseInt(readFileSync(this.pidFile, 'utf-8').trim(), 10);
379
516
  if (isNaN(pid))
380
517
  return null;
518
+ // #1853: a PID file containing our own PID is not "another daemon".
519
+ // Treat as absent so the start() path proceeds normally.
520
+ if (pid === process.pid)
521
+ return null;
381
522
  // Check if process is alive (signal 0 = existence check)
382
523
  process.kill(pid, 0);
383
524
  return pid; // Process is alive
@@ -424,6 +565,11 @@ export class WorkerDaemon extends EventEmitter {
424
565
  this.emit('warning', `Daemon already running (PID: ${existingPid})`);
425
566
  return;
426
567
  }
568
+ // #1855: reap orphan headless worker children left by a previous
569
+ // crashed lifetime, BEFORE we mark ourselves running and start
570
+ // accepting new work. The children file from the prior daemon's
571
+ // last-snapshot is the authoritative list.
572
+ this.reapOrphanedChildren();
427
573
  this.running = true;
428
574
  this.startedAt = new Date();
429
575
  this.writePidFile();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.7.0-alpha.14",
3
+ "version": "3.7.0-alpha.16",
4
4
  "type": "module",
5
5
  "description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
6
6
  "main": "dist/src/index.js",