@claude-flow/cli 3.7.0-alpha.8 → 3.7.0-alpha.81

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 (291) hide show
  1. package/.claude/agents/github/release-manager.md +2 -2
  2. package/.claude/agents/github/release-swarm.md +1 -1
  3. package/.claude/agents/github/repo-architect.md +2 -2
  4. package/.claude/agents/github/swarm-pr.md +1 -1
  5. package/.claude/agents/github/workflow-automation.md +2 -2
  6. package/.claude/commands/github/code-review-swarm.md +1 -1
  7. package/.claude/commands/github/issue-tracker.md +3 -3
  8. package/.claude/commands/github/release-manager.md +5 -3
  9. package/.claude/commands/github/release-swarm.md +1 -1
  10. package/.claude/commands/github/repo-architect.md +2 -2
  11. package/.claude/commands/github/swarm-issue.md +4 -1
  12. package/.claude/commands/github/swarm-pr.md +6 -3
  13. package/.claude/commands/github/sync-coordinator.md +3 -1
  14. package/.claude/commands/github/workflow-automation.md +2 -2
  15. package/.claude/helpers/github-safe.js +95 -60
  16. package/.claude/helpers/github-setup.sh +26 -9
  17. package/.claude/helpers/hook-handler.cjs +12 -4
  18. package/.claude/helpers/statusline.cjs +31 -2
  19. package/.claude/helpers/statusline.js +35 -4
  20. package/.claude/settings.json +1 -1
  21. package/.claude/skills/dual-mode/README.md +71 -0
  22. package/.claude/skills/dual-mode/dual-collect.md +103 -0
  23. package/.claude/skills/dual-mode/dual-coordinate.md +85 -0
  24. package/.claude/skills/dual-mode/dual-spawn.md +81 -0
  25. package/.claude/skills/flow-nexus-neural/SKILL.md +0 -11
  26. package/.claude/skills/flow-nexus-platform/SKILL.md +2 -5
  27. package/.claude/skills/flow-nexus-swarm/SKILL.md +0 -6
  28. package/.claude/skills/github-code-review/SKILL.md +2 -17
  29. package/.claude/skills/github-multi-repo/SKILL.md +4 -16
  30. package/.claude/skills/github-project-management/SKILL.md +18 -33
  31. package/.claude/skills/github-release-management/SKILL.md +4 -21
  32. package/.claude/skills/github-workflow-automation/SKILL.md +8 -26
  33. package/.claude/skills/reasoningbank-intelligence/SKILL.md +2 -2
  34. package/.claude/skills/sparc-methodology/SKILL.md +2 -11
  35. package/.claude/skills/stream-chain/SKILL.md +0 -3
  36. package/.claude/skills/swarm-advanced/SKILL.md +2 -5
  37. package/.claude/skills/swarm-orchestration/SKILL.md +1 -1
  38. package/.claude/skills/verification-quality/SKILL.md +120 -78
  39. package/README.md +57 -38
  40. package/bin/cli.js +15 -2
  41. package/bin/mcp-server.js +1 -1
  42. package/dist/src/__probe.d.ts +2 -0
  43. package/dist/src/__probe.d.ts.map +1 -0
  44. package/dist/src/__probe.js +5 -0
  45. package/dist/src/__probe.js.map +1 -0
  46. package/dist/src/commands/agent-wasm.js +2 -2
  47. package/dist/src/commands/agent-wasm.js.map +1 -1
  48. package/dist/src/commands/benchmark-cosign.d.ts +29 -0
  49. package/dist/src/commands/benchmark-cosign.d.ts.map +1 -0
  50. package/dist/src/commands/benchmark-cosign.js +222 -0
  51. package/dist/src/commands/benchmark-cosign.js.map +1 -0
  52. package/dist/src/commands/benchmark-verify.d.ts +21 -0
  53. package/dist/src/commands/benchmark-verify.d.ts.map +1 -0
  54. package/dist/src/commands/benchmark-verify.js +202 -0
  55. package/dist/src/commands/benchmark-verify.js.map +1 -0
  56. package/dist/src/commands/daemon.d.ts +20 -0
  57. package/dist/src/commands/daemon.d.ts.map +1 -1
  58. package/dist/src/commands/daemon.js +366 -7
  59. package/dist/src/commands/daemon.js.map +1 -1
  60. package/dist/src/commands/doctor.d.ts.map +1 -1
  61. package/dist/src/commands/doctor.js +224 -46
  62. package/dist/src/commands/doctor.js.map +1 -1
  63. package/dist/src/commands/embeddings.d.ts.map +1 -1
  64. package/dist/src/commands/embeddings.js +18 -9
  65. package/dist/src/commands/embeddings.js.map +1 -1
  66. package/dist/src/commands/hive-mind.d.ts.map +1 -1
  67. package/dist/src/commands/hive-mind.js +25 -7
  68. package/dist/src/commands/hive-mind.js.map +1 -1
  69. package/dist/src/commands/hooks.d.ts.map +1 -1
  70. package/dist/src/commands/hooks.js +56 -29
  71. package/dist/src/commands/hooks.js.map +1 -1
  72. package/dist/src/commands/init.d.ts.map +1 -1
  73. package/dist/src/commands/init.js +21 -1
  74. package/dist/src/commands/init.js.map +1 -1
  75. package/dist/src/commands/memory.d.ts.map +1 -1
  76. package/dist/src/commands/memory.js +128 -3
  77. package/dist/src/commands/memory.js.map +1 -1
  78. package/dist/src/commands/start.js +1 -1
  79. package/dist/src/commands/start.js.map +1 -1
  80. package/dist/src/commands/swarm.js +1 -1
  81. package/dist/src/commands/swarm.js.map +1 -1
  82. package/dist/src/commands/task.d.ts.map +1 -1
  83. package/dist/src/commands/task.js +8 -4
  84. package/dist/src/commands/task.js.map +1 -1
  85. package/dist/src/config-adapter.js +1 -1
  86. package/dist/src/config-adapter.js.map +1 -1
  87. package/dist/src/index.d.ts +5 -1
  88. package/dist/src/index.d.ts.map +1 -1
  89. package/dist/src/index.js +61 -18
  90. package/dist/src/index.js.map +1 -1
  91. package/dist/src/init/claudemd-generator.d.ts.map +1 -1
  92. package/dist/src/init/claudemd-generator.js +1 -0
  93. package/dist/src/init/claudemd-generator.js.map +1 -1
  94. package/dist/src/init/executor.d.ts.map +1 -1
  95. package/dist/src/init/executor.js +133 -0
  96. package/dist/src/init/executor.js.map +1 -1
  97. package/dist/src/init/helpers-generator.d.ts +1 -0
  98. package/dist/src/init/helpers-generator.d.ts.map +1 -1
  99. package/dist/src/init/helpers-generator.js +19 -2
  100. package/dist/src/init/helpers-generator.js.map +1 -1
  101. package/dist/src/init/mcp-generator.js +4 -4
  102. package/dist/src/init/mcp-generator.js.map +1 -1
  103. package/dist/src/init/settings-generator.d.ts.map +1 -1
  104. package/dist/src/init/settings-generator.js +84 -19
  105. package/dist/src/init/settings-generator.js.map +1 -1
  106. package/dist/src/init/statusline-generator.d.ts.map +1 -1
  107. package/dist/src/init/statusline-generator.js +75 -31
  108. package/dist/src/init/statusline-generator.js.map +1 -1
  109. package/dist/src/init/types.d.ts +30 -0
  110. package/dist/src/init/types.d.ts.map +1 -1
  111. package/dist/src/init/types.js +18 -5
  112. package/dist/src/init/types.js.map +1 -1
  113. package/dist/src/mcp-client.d.ts.map +1 -1
  114. package/dist/src/mcp-client.js +12 -0
  115. package/dist/src/mcp-client.js.map +1 -1
  116. package/dist/src/mcp-server.d.ts.map +1 -1
  117. package/dist/src/mcp-server.js +38 -5
  118. package/dist/src/mcp-server.js.map +1 -1
  119. package/dist/src/mcp-tools/agent-execute-core.d.ts +3 -2
  120. package/dist/src/mcp-tools/agent-execute-core.d.ts.map +1 -1
  121. package/dist/src/mcp-tools/agent-execute-core.js +157 -83
  122. package/dist/src/mcp-tools/agent-execute-core.js.map +1 -1
  123. package/dist/src/mcp-tools/agent-tools.d.ts.map +1 -1
  124. package/dist/src/mcp-tools/agent-tools.js +119 -11
  125. package/dist/src/mcp-tools/agent-tools.js.map +1 -1
  126. package/dist/src/mcp-tools/agentdb-tools.d.ts.map +1 -1
  127. package/dist/src/mcp-tools/agentdb-tools.js +101 -24
  128. package/dist/src/mcp-tools/agentdb-tools.js.map +1 -1
  129. package/dist/src/mcp-tools/analyze-tools.js +6 -6
  130. package/dist/src/mcp-tools/analyze-tools.js.map +1 -1
  131. package/dist/src/mcp-tools/autopilot-tools.js +10 -10
  132. package/dist/src/mcp-tools/autopilot-tools.js.map +1 -1
  133. package/dist/src/mcp-tools/browser-session-tools.d.ts.map +1 -1
  134. package/dist/src/mcp-tools/browser-session-tools.js +18 -7
  135. package/dist/src/mcp-tools/browser-session-tools.js.map +1 -1
  136. package/dist/src/mcp-tools/browser-tools.js +23 -23
  137. package/dist/src/mcp-tools/browser-tools.js.map +1 -1
  138. package/dist/src/mcp-tools/claims-tools.js +12 -12
  139. package/dist/src/mcp-tools/claims-tools.js.map +1 -1
  140. package/dist/src/mcp-tools/config-tools.js +6 -6
  141. package/dist/src/mcp-tools/config-tools.js.map +1 -1
  142. package/dist/src/mcp-tools/coordination-tools.js +7 -7
  143. package/dist/src/mcp-tools/coordination-tools.js.map +1 -1
  144. package/dist/src/mcp-tools/daa-tools.js +8 -8
  145. package/dist/src/mcp-tools/daa-tools.js.map +1 -1
  146. package/dist/src/mcp-tools/embeddings-tools.js +10 -10
  147. package/dist/src/mcp-tools/embeddings-tools.js.map +1 -1
  148. package/dist/src/mcp-tools/github-tools.js +5 -5
  149. package/dist/src/mcp-tools/github-tools.js.map +1 -1
  150. package/dist/src/mcp-tools/guidance-tools.js +21 -21
  151. package/dist/src/mcp-tools/guidance-tools.js.map +1 -1
  152. package/dist/src/mcp-tools/hive-consensus-runtime.d.ts +149 -0
  153. package/dist/src/mcp-tools/hive-consensus-runtime.d.ts.map +1 -0
  154. package/dist/src/mcp-tools/hive-consensus-runtime.js +296 -0
  155. package/dist/src/mcp-tools/hive-consensus-runtime.js.map +1 -0
  156. package/dist/src/mcp-tools/hive-mind-tools.d.ts.map +1 -1
  157. package/dist/src/mcp-tools/hive-mind-tools.js +53 -9
  158. package/dist/src/mcp-tools/hive-mind-tools.js.map +1 -1
  159. package/dist/src/mcp-tools/hooks-tools.d.ts +2 -0
  160. package/dist/src/mcp-tools/hooks-tools.d.ts.map +1 -1
  161. package/dist/src/mcp-tools/hooks-tools.js +179 -46
  162. package/dist/src/mcp-tools/hooks-tools.js.map +1 -1
  163. package/dist/src/mcp-tools/managed-agent-tools.d.ts +22 -0
  164. package/dist/src/mcp-tools/managed-agent-tools.d.ts.map +1 -0
  165. package/dist/src/mcp-tools/managed-agent-tools.js +357 -0
  166. package/dist/src/mcp-tools/managed-agent-tools.js.map +1 -0
  167. package/dist/src/mcp-tools/memory-tools.d.ts.map +1 -1
  168. package/dist/src/mcp-tools/memory-tools.js +499 -68
  169. package/dist/src/mcp-tools/memory-tools.js.map +1 -1
  170. package/dist/src/mcp-tools/neural-tools.js +6 -6
  171. package/dist/src/mcp-tools/neural-tools.js.map +1 -1
  172. package/dist/src/mcp-tools/performance-tools.js +6 -6
  173. package/dist/src/mcp-tools/performance-tools.js.map +1 -1
  174. package/dist/src/mcp-tools/progress-tools.js +4 -4
  175. package/dist/src/mcp-tools/progress-tools.js.map +1 -1
  176. package/dist/src/mcp-tools/ruvllm-tools.d.ts.map +1 -1
  177. package/dist/src/mcp-tools/ruvllm-tools.js +27 -11
  178. package/dist/src/mcp-tools/ruvllm-tools.js.map +1 -1
  179. package/dist/src/mcp-tools/security-tools.d.ts.map +1 -1
  180. package/dist/src/mcp-tools/security-tools.js +34 -9
  181. package/dist/src/mcp-tools/security-tools.js.map +1 -1
  182. package/dist/src/mcp-tools/session-tools.d.ts.map +1 -1
  183. package/dist/src/mcp-tools/session-tools.js +130 -6
  184. package/dist/src/mcp-tools/session-tools.js.map +1 -1
  185. package/dist/src/mcp-tools/swarm-tools.d.ts +28 -0
  186. package/dist/src/mcp-tools/swarm-tools.d.ts.map +1 -1
  187. package/dist/src/mcp-tools/swarm-tools.js +80 -9
  188. package/dist/src/mcp-tools/swarm-tools.js.map +1 -1
  189. package/dist/src/mcp-tools/system-tools.d.ts.map +1 -1
  190. package/dist/src/mcp-tools/system-tools.js +91 -18
  191. package/dist/src/mcp-tools/system-tools.js.map +1 -1
  192. package/dist/src/mcp-tools/task-tools.d.ts.map +1 -1
  193. package/dist/src/mcp-tools/task-tools.js +55 -7
  194. package/dist/src/mcp-tools/task-tools.js.map +1 -1
  195. package/dist/src/mcp-tools/terminal-tools.js +5 -5
  196. package/dist/src/mcp-tools/terminal-tools.js.map +1 -1
  197. package/dist/src/mcp-tools/transfer-tools.js +11 -11
  198. package/dist/src/mcp-tools/transfer-tools.js.map +1 -1
  199. package/dist/src/mcp-tools/wasm-agent-tools.js +11 -11
  200. package/dist/src/mcp-tools/wasm-agent-tools.js.map +1 -1
  201. package/dist/src/mcp-tools/workflow-tools.d.ts.map +1 -1
  202. package/dist/src/mcp-tools/workflow-tools.js +118 -10
  203. package/dist/src/mcp-tools/workflow-tools.js.map +1 -1
  204. package/dist/src/memory/ann-router-registry.d.ts +61 -0
  205. package/dist/src/memory/ann-router-registry.d.ts.map +1 -0
  206. package/dist/src/memory/ann-router-registry.js +72 -0
  207. package/dist/src/memory/ann-router-registry.js.map +1 -0
  208. package/dist/src/memory/diskann-registry.d.ts +56 -0
  209. package/dist/src/memory/diskann-registry.d.ts.map +1 -0
  210. package/dist/src/memory/diskann-registry.js +88 -0
  211. package/dist/src/memory/diskann-registry.js.map +1 -0
  212. package/dist/src/memory/memory-bridge.d.ts +4 -0
  213. package/dist/src/memory/memory-bridge.d.ts.map +1 -1
  214. package/dist/src/memory/memory-bridge.js +49 -8
  215. package/dist/src/memory/memory-bridge.js.map +1 -1
  216. package/dist/src/memory/memory-initializer.d.ts +12 -0
  217. package/dist/src/memory/memory-initializer.d.ts.map +1 -1
  218. package/dist/src/memory/memory-initializer.js +98 -19
  219. package/dist/src/memory/memory-initializer.js.map +1 -1
  220. package/dist/src/memory/sona-optimizer.d.ts.map +1 -1
  221. package/dist/src/memory/sona-optimizer.js +3 -0
  222. package/dist/src/memory/sona-optimizer.js.map +1 -1
  223. package/dist/src/parser.d.ts +9 -0
  224. package/dist/src/parser.d.ts.map +1 -1
  225. package/dist/src/parser.js +11 -0
  226. package/dist/src/parser.js.map +1 -1
  227. package/dist/src/plugins/store/discovery.d.ts +15 -4
  228. package/dist/src/plugins/store/discovery.d.ts.map +1 -1
  229. package/dist/src/plugins/store/discovery.js +40 -18
  230. package/dist/src/plugins/store/discovery.js.map +1 -1
  231. package/dist/src/ruvector/agent-wasm.d.ts.map +1 -1
  232. package/dist/src/ruvector/agent-wasm.js +4 -1
  233. package/dist/src/ruvector/agent-wasm.js.map +1 -1
  234. package/dist/src/ruvector/coverage-tools.js +6 -6
  235. package/dist/src/ruvector/coverage-tools.js.map +1 -1
  236. package/dist/src/services/headless-worker-executor.d.ts +20 -1
  237. package/dist/src/services/headless-worker-executor.d.ts.map +1 -1
  238. package/dist/src/services/headless-worker-executor.js +91 -13
  239. package/dist/src/services/headless-worker-executor.js.map +1 -1
  240. package/dist/src/services/worker-daemon.d.ts +92 -2
  241. package/dist/src/services/worker-daemon.d.ts.map +1 -1
  242. package/dist/src/services/worker-daemon.js +431 -16
  243. package/dist/src/services/worker-daemon.js.map +1 -1
  244. package/dist/tsconfig.tsbuildinfo +1 -1
  245. package/package.json +12 -8
  246. package/.claude/agents/core/coder.md +0 -453
  247. package/.claude/agents/core/researcher.md +0 -369
  248. package/.claude/agents/core/reviewer.md +0 -520
  249. package/.claude/agents/core/tester.md +0 -512
  250. package/.claude/agents/goal/goal-planner.md +0 -73
  251. package/.claude/agents/v3/adr-architect.md +0 -184
  252. package/.claude/agents/v3/memory-specialist.md +0 -995
  253. package/.claude/agents/v3/security-auditor.md +0 -771
  254. package/.claude/agents/v3/sparc-orchestrator.md +0 -182
  255. package/.claude/commands/flow-nexus/app-store.md +0 -124
  256. package/.claude/commands/flow-nexus/challenges.md +0 -120
  257. package/.claude/commands/flow-nexus/login-registration.md +0 -65
  258. package/.claude/commands/flow-nexus/neural-network.md +0 -134
  259. package/.claude/commands/flow-nexus/payments.md +0 -116
  260. package/.claude/commands/flow-nexus/sandbox.md +0 -83
  261. package/.claude/commands/flow-nexus/swarm.md +0 -87
  262. package/.claude/commands/flow-nexus/user-tools.md +0 -152
  263. package/.claude/commands/flow-nexus/workflow.md +0 -115
  264. package/.claude/skills/agentic-jujutsu/SKILL.md +0 -645
  265. package/.claude/skills/aidefence-scan.md +0 -151
  266. package/.claude/skills/aidefence.yaml +0 -297
  267. package/.claude/skills/hive-mind-advanced/SKILL.md +0 -712
  268. package/.claude/skills/performance-analysis/SKILL.md +0 -563
  269. package/.claude/skills/secure-review.md +0 -181
  270. package/.claude/skills/worker-benchmarks/skill.md +0 -135
  271. package/.claude/skills/worker-integration/skill.md +0 -154
  272. package/dist/src/ruvector/flash-attention.d.ts +0 -195
  273. package/dist/src/ruvector/flash-attention.d.ts.map +0 -1
  274. package/dist/src/ruvector/flash-attention.js +0 -643
  275. package/dist/src/ruvector/flash-attention.js.map +0 -1
  276. package/dist/src/ruvector/moe-router.d.ts +0 -206
  277. package/dist/src/ruvector/moe-router.d.ts.map +0 -1
  278. package/dist/src/ruvector/moe-router.js +0 -626
  279. package/dist/src/ruvector/moe-router.js.map +0 -1
  280. package/dist/src/services/event-stream.d.ts +0 -25
  281. package/dist/src/services/event-stream.d.ts.map +0 -1
  282. package/dist/src/services/event-stream.js +0 -27
  283. package/dist/src/services/event-stream.js.map +0 -1
  284. package/dist/src/services/loop-worker-runner.d.ts +0 -16
  285. package/dist/src/services/loop-worker-runner.d.ts.map +0 -1
  286. package/dist/src/services/loop-worker-runner.js +0 -34
  287. package/dist/src/services/loop-worker-runner.js.map +0 -1
  288. package/dist/src/services/runtime-capabilities.d.ts +0 -22
  289. package/dist/src/services/runtime-capabilities.d.ts.map +0 -1
  290. package/dist/src/services/runtime-capabilities.js +0 -45
  291. package/dist/src/services/runtime-capabilities.js.map +0 -1
@@ -34,6 +34,9 @@ export class WorkerDaemon extends EventEmitter {
34
34
  config;
35
35
  workers = new Map();
36
36
  timers = new Map();
37
+ // #1845: separate timer for the MCP-dispatch queue poller. Kept off
38
+ // the per-worker map so stop() clears both kinds without confusion.
39
+ queuePollTimer;
37
40
  running = false;
38
41
  startedAt;
39
42
  projectRoot;
@@ -54,7 +57,18 @@ export class WorkerDaemon extends EventEmitter {
54
57
  const fileConfig = this.readDaemonConfigFromFile(claudeFlowDir);
55
58
  // CPU-proportional smart default instead of hardcoded 2.0
56
59
  const cpuCount = WorkerDaemon.getEffectiveCpuCount();
57
- const smartMaxCpuLoad = Math.max(cpuCount * 0.8, 2.0); // Floor of 2.0 for single-CPU machines
60
+ let smartMaxCpuLoad = Math.max(cpuCount * 0.8, 2.0); // Floor of 2.0 for single-CPU machines
61
+ // #2110 — WSL2 reports `/proc/loadavg` values that include Windows-side
62
+ // process counts mapped into the Linux kernel. Real load on a 4-CPU
63
+ // WSL2 host can be 200-400 even when the Linux side is idle. The
64
+ // default gate of `cpuCount * 0.8` always trips, deferring every
65
+ // worker as "CPU load too high" while the daemon reports healthy.
66
+ // Bump the floor to 1000 when WSL is detected so the gate is
67
+ // effectively disabled (real load on Linux side rarely exceeds 100
68
+ // even under heavy contention).
69
+ if (WorkerDaemon.isWslEnvironment()) {
70
+ smartMaxCpuLoad = Math.max(smartMaxCpuLoad, 1000);
71
+ }
58
72
  // Platform-aware default: macOS os.freemem() excludes reclaimable file cache,
59
73
  // so reported "free" is much lower than actually available memory.
60
74
  // Linux reports available memory (including reclaimable cache) more accurately.
@@ -76,6 +90,9 @@ export class WorkerDaemon extends EventEmitter {
76
90
  };
77
91
  // Setup graceful shutdown handlers
78
92
  this.setupShutdownHandlers();
93
+ // #1855: install crash handlers so uncaught exceptions and unhandled
94
+ // rejections don't leak the PID file or orphan child processes.
95
+ this.installCrashHandlers();
79
96
  // Ensure directories exist
80
97
  if (!existsSync(claudeFlowDir)) {
81
98
  mkdirSync(claudeFlowDir, { recursive: true });
@@ -101,14 +118,19 @@ export class WorkerDaemon extends EventEmitter {
101
118
  this.headlessAvailable = await this.headlessExecutor.isAvailable();
102
119
  if (this.headlessAvailable) {
103
120
  this.log('info', 'Claude Code headless mode available - AI workers enabled');
104
- // Forward headless executor events
121
+ // Forward headless executor events. #1855: also snapshot the
122
+ // active child PIDs to disk on every transition so the next
123
+ // lifetime can reap orphans after a hard crash.
105
124
  this.headlessExecutor.on('execution:start', (data) => {
125
+ this.writeChildrenSnapshot();
106
126
  this.emit('headless:start', data);
107
127
  });
108
128
  this.headlessExecutor.on('execution:complete', (data) => {
129
+ this.writeChildrenSnapshot();
109
130
  this.emit('headless:complete', data);
110
131
  });
111
132
  this.headlessExecutor.on('execution:error', (data) => {
133
+ this.writeChildrenSnapshot();
112
134
  this.emit('headless:error', data);
113
135
  });
114
136
  this.headlessExecutor.on('output', (data) => {
@@ -144,6 +166,28 @@ export class WorkerDaemon extends EventEmitter {
144
166
  * cgroup v2 / v1 quota files first so the maxCpuLoad threshold stays
145
167
  * meaningful under resource-limited containers.
146
168
  */
169
+ /**
170
+ * #2110 — detect WSL2 / WSL1 so the CPU-load gate can use a sane
171
+ * default. `/proc/loadavg` on WSL maps in Windows-side process counts
172
+ * and routinely reports values 100-1000x larger than real Linux load.
173
+ *
174
+ * Detection order:
175
+ * 1. `WSL_DISTRO_NAME` env var (set by Microsoft's WSL launcher)
176
+ * 2. `WSL_INTEROP` env var (set by recent WSL2)
177
+ * 3. `/proc/sys/kernel/osrelease` contains "microsoft" or "WSL"
178
+ * (kernel build marker; survives env stripping)
179
+ */
180
+ static isWslEnvironment() {
181
+ if (process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP)
182
+ return true;
183
+ try {
184
+ const osrelease = readFileSync('/proc/sys/kernel/osrelease', 'utf8').toLowerCase();
185
+ if (osrelease.includes('microsoft') || osrelease.includes('wsl'))
186
+ return true;
187
+ }
188
+ catch { /* not on Linux or /proc inaccessible */ }
189
+ return false;
190
+ }
147
191
  static getEffectiveCpuCount() {
148
192
  // 1. Try cgroup v2: /sys/fs/cgroup/cpu.max
149
193
  try {
@@ -169,22 +213,52 @@ export class WorkerDaemon extends EventEmitter {
169
213
  return cpus().length || 1;
170
214
  }
171
215
  /**
172
- * Read daemon-specific config from .claude-flow/config.json
173
- * Supports dot-notation keys like 'daemon.resourceThresholds.maxCpuLoad'
216
+ * Read daemon-specific config from .claude-flow/config.{json,yaml,yml}.
217
+ * Supports dot-notation keys like 'daemon.resourceThresholds.maxCpuLoad'.
218
+ * #1844: prefer JSON when both exist (existing behavior) but fall back
219
+ * to YAML so operators using the v3 canonical YAML format aren't silently
220
+ * ignored. The chosen path is logged at info level.
174
221
  */
175
222
  readDaemonConfigFromFile(claudeFlowDir) {
176
- const configPath = join(claudeFlowDir, 'config.json');
177
- if (!existsSync(configPath)) {
178
- // Warn if config.yaml exists but config.json does not (#1395 Bug 4)
179
- const yamlPath = join(claudeFlowDir, 'config.yaml');
180
- const ymlPath = join(claudeFlowDir, 'config.yml');
181
- if (existsSync(yamlPath) || existsSync(ymlPath)) {
182
- this.log('warn', `Found ${existsSync(yamlPath) ? 'config.yaml' : 'config.yml'} but daemon reads only config.json — YAML config is being ignored. Convert to JSON or create config.json.`);
223
+ const jsonPath = join(claudeFlowDir, 'config.json');
224
+ const yamlPath = join(claudeFlowDir, 'config.yaml');
225
+ const ymlPath = join(claudeFlowDir, 'config.yml');
226
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
227
+ let raw;
228
+ let chosenPath;
229
+ if (existsSync(jsonPath)) {
230
+ try {
231
+ raw = JSON.parse(readFileSync(jsonPath, 'utf-8'));
232
+ chosenPath = jsonPath;
233
+ }
234
+ catch {
235
+ return {};
236
+ }
237
+ }
238
+ else if (existsSync(yamlPath) || existsSync(ymlPath)) {
239
+ const yPath = existsSync(yamlPath) ? yamlPath : ymlPath;
240
+ try {
241
+ // Lazy-load yaml so the daemon doesn't hard-require it; if the
242
+ // dep isn't installed, fall back to the previous warn-only path.
243
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
244
+ const yamlMod = require('yaml');
245
+ const parsed = yamlMod.parse(readFileSync(yPath, 'utf-8'));
246
+ if (parsed && typeof parsed === 'object') {
247
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
248
+ raw = parsed;
249
+ chosenPath = yPath;
250
+ }
183
251
  }
252
+ catch {
253
+ this.log('warn', `Found ${yPath} but yaml parser unavailable. Install \`yaml\` or convert to JSON. Falling back to defaults.`);
254
+ return {};
255
+ }
256
+ }
257
+ if (!raw || !chosenPath) {
184
258
  return {};
185
259
  }
260
+ this.log('info', `Daemon config loaded from ${chosenPath}`);
186
261
  try {
187
- const raw = JSON.parse(readFileSync(configPath, 'utf-8'));
188
262
  // Support both flat keys at root and nested under scopes.project
189
263
  const cfg = raw?.scopes?.project ?? raw;
190
264
  const rawCpuLoad = cfg['daemon.resourceThresholds.maxCpuLoad'] ?? raw['daemon.resourceThresholds.maxCpuLoad'];
@@ -216,6 +290,153 @@ export class WorkerDaemon extends EventEmitter {
216
290
  process.on('SIGINT', shutdown);
217
291
  process.on('SIGHUP', shutdown);
218
292
  }
293
+ /**
294
+ * #1855: install crash handlers for uncaught exceptions and unhandled
295
+ * rejections. Without these, a thrown error from any timer callback,
296
+ * worker logic path, or transitive import crashes the daemon process
297
+ * silently — the PID file leaks and any in-flight child processes
298
+ * orphan. With these, we log a structured crash record, run stop()
299
+ * to clean up, then exit 1 so the process actually dies (otherwise
300
+ * Node would crash anyway after the handler returns).
301
+ */
302
+ installCrashHandlers() {
303
+ const onCrash = (kind, err) => {
304
+ // Best-effort logging; never throw from inside the crash handler.
305
+ try {
306
+ this.writeCrashRecord(kind, err);
307
+ }
308
+ catch { /* nothing more we can do */ }
309
+ try {
310
+ // Synchronous stop — don't await; the process is dying. Just
311
+ // remove the PID file and snapshot state so the next start
312
+ // sees a clean slate.
313
+ this.removePidFile();
314
+ this.saveState();
315
+ // Snapshot any in-flight child PIDs one last time so the next
316
+ // lifetime can reap them.
317
+ this.writeChildrenSnapshot();
318
+ }
319
+ catch { /* ignore */ }
320
+ // Exit non-zero so supervisors / shells see the failure.
321
+ process.exit(1);
322
+ };
323
+ process.on('uncaughtException', (err) => onCrash('uncaughtException', err));
324
+ process.on('unhandledRejection', (err) => onCrash('unhandledRejection', err));
325
+ }
326
+ /**
327
+ * Append a structured crash record to .claude-flow/logs/crash.log.
328
+ * Inspectable by hand or via `ruflo daemon status` follow-ups.
329
+ */
330
+ writeCrashRecord(kind, err) {
331
+ const logDir = this.config.logDir;
332
+ if (!existsSync(logDir))
333
+ mkdirSync(logDir, { recursive: true });
334
+ const crashLog = join(logDir, 'crash.log');
335
+ const ts = new Date().toISOString();
336
+ const message = err instanceof Error ? err.message : String(err);
337
+ const stack = err instanceof Error && err.stack ? err.stack : '<no stack>';
338
+ const record = `[${ts}] [${kind}] pid=${process.pid} ${message}\n${stack}\n---\n`;
339
+ appendFileSync(crashLog, record, 'utf-8');
340
+ this.log('warn', `Daemon crashed (${kind}): ${message} — see ${crashLog}`);
341
+ }
342
+ /**
343
+ * Path to the on-disk children registry — list of headless worker
344
+ * child PIDs the daemon currently owns. #1855: written on every
345
+ * execution:start / :complete / :error transition; read by the next
346
+ * lifetime to reap orphans after a hard crash.
347
+ */
348
+ get childrenFile() {
349
+ return join(this.projectRoot, '.claude-flow', 'daemon-children.json');
350
+ }
351
+ /**
352
+ * #1856: detect workers that were mid-flight when the previous daemon
353
+ * lifetime ended. A mid-flight worker has `lastStartedAt > lastRun`
354
+ * (started after the last successful completion). On crash recovery
355
+ * we count these as failures so the run-counter math stays consistent
356
+ * (`runCount === successCount + failureCount`). Workers naturally
357
+ * retry at their next scheduled interval; we deliberately don't
358
+ * immediately re-run because the failure may have been deterministic.
359
+ */
360
+ detectMidFlightFailures() {
361
+ let detected = 0;
362
+ for (const [type, state] of this.workers.entries()) {
363
+ const startedAt = state.lastStartedAt?.getTime() ?? 0;
364
+ const lastRunAt = state.lastRun?.getTime() ?? 0;
365
+ // started after the last successful completion → was mid-flight
366
+ if (startedAt > 0 && startedAt > lastRunAt) {
367
+ state.failureCount++;
368
+ state.isRunning = false;
369
+ // Don't bump runCount — it was already incremented at start
370
+ this.log('info', `Worker ${type} was mid-flight at last crash (started ${state.lastStartedAt?.toISOString()}); counted as failure, will retry at next scheduled interval`);
371
+ detected++;
372
+ }
373
+ }
374
+ if (detected > 0) {
375
+ this.saveState();
376
+ }
377
+ }
378
+ /**
379
+ * Snapshot the currently-active headless worker child PIDs to disk.
380
+ * Best-effort; failures don't propagate.
381
+ */
382
+ writeChildrenSnapshot() {
383
+ if (!this.headlessExecutor)
384
+ return;
385
+ try {
386
+ const pids = this.headlessExecutor.getActiveChildPids();
387
+ const dir = join(this.projectRoot, '.claude-flow');
388
+ if (!existsSync(dir))
389
+ mkdirSync(dir, { recursive: true });
390
+ writeFileSync(this.childrenFile, JSON.stringify({ pids, daemonPid: process.pid, timestamp: new Date().toISOString() }, null, 2), 'utf-8');
391
+ }
392
+ catch { /* best-effort */ }
393
+ }
394
+ /**
395
+ * #1855: reap orphan headless worker children left behind by a
396
+ * previous crashed lifetime. Reads `.claude-flow/daemon-children.json`,
397
+ * SIGTERMs any PID still alive that doesn't belong to the current
398
+ * daemon, then truncates the file. Called at the top of `start()`
399
+ * so the next lifetime starts with a clean process tree.
400
+ */
401
+ reapOrphanedChildren() {
402
+ const file = this.childrenFile;
403
+ if (!existsSync(file))
404
+ return;
405
+ let snapshot;
406
+ try {
407
+ snapshot = JSON.parse(readFileSync(file, 'utf-8'));
408
+ }
409
+ catch {
410
+ try {
411
+ unlinkSync(file);
412
+ }
413
+ catch { /* ignore */ }
414
+ return;
415
+ }
416
+ const pids = Array.isArray(snapshot.pids) ? snapshot.pids : [];
417
+ let reaped = 0;
418
+ for (const pid of pids) {
419
+ if (typeof pid !== 'number' || pid <= 0)
420
+ continue;
421
+ if (pid === process.pid)
422
+ continue; // never our own PID
423
+ try {
424
+ process.kill(pid, 0); // is alive?
425
+ process.kill(pid, 'SIGTERM');
426
+ reaped++;
427
+ }
428
+ catch {
429
+ // already dead — fine
430
+ }
431
+ }
432
+ if (reaped > 0) {
433
+ this.log('info', `Reaped ${reaped} orphan headless worker child(ren) from previous lifetime`);
434
+ }
435
+ try {
436
+ unlinkSync(file);
437
+ }
438
+ catch { /* ignore */ }
439
+ }
219
440
  /**
220
441
  * Check if system resources allow worker execution
221
442
  */
@@ -299,12 +520,14 @@ export class WorkerDaemon extends EventEmitter {
299
520
  for (const [type, state] of Object.entries(saved.workers)) {
300
521
  const savedState = state;
301
522
  const lastRunValue = savedState.lastRun;
523
+ const lastStartedAtValue = savedState.lastStartedAt;
302
524
  this.workers.set(type, {
303
525
  runCount: savedState.runCount || 0,
304
526
  successCount: savedState.successCount || 0,
305
527
  failureCount: savedState.failureCount || 0,
306
528
  averageDurationMs: savedState.averageDurationMs || 0,
307
529
  lastRun: lastRunValue ? new Date(lastRunValue) : undefined,
530
+ lastStartedAt: lastStartedAtValue ? new Date(lastStartedAtValue) : undefined,
308
531
  nextRun: undefined,
309
532
  isRunning: false,
310
533
  });
@@ -337,6 +560,15 @@ export class WorkerDaemon extends EventEmitter {
337
560
  /**
338
561
  * Check if another daemon instance is already running.
339
562
  * Returns the existing PID if alive, or null if no daemon is running.
563
+ *
564
+ * #1853: ignore self-PID matches. The detached-spawn path in
565
+ * `commands/daemon.ts` writes the child's PID into the file as a
566
+ * fallback after a 500ms wait. If the child reaches `start()` slower
567
+ * than the parent's 500ms wait (observed on Node 25 / macOS 26), the
568
+ * child reads its own PID back from the file and concludes "another
569
+ * daemon is already running" — so it exits before scheduling workers
570
+ * and `daemon status` reports STOPPED forever. A daemon process is
571
+ * never "another instance" of itself; treat self-match as absence.
340
572
  */
341
573
  checkExistingDaemon() {
342
574
  if (!existsSync(this.pidFile))
@@ -345,6 +577,10 @@ export class WorkerDaemon extends EventEmitter {
345
577
  const pid = parseInt(readFileSync(this.pidFile, 'utf-8').trim(), 10);
346
578
  if (isNaN(pid))
347
579
  return null;
580
+ // #1853: a PID file containing our own PID is not "another daemon".
581
+ // Treat as absent so the start() path proceeds normally.
582
+ if (pid === process.pid)
583
+ return null;
348
584
  // Check if process is alive (signal 0 = existence check)
349
585
  process.kill(pid, 0);
350
586
  return pid; // Process is alive
@@ -391,6 +627,18 @@ export class WorkerDaemon extends EventEmitter {
391
627
  this.emit('warning', `Daemon already running (PID: ${existingPid})`);
392
628
  return;
393
629
  }
630
+ // #1855: reap orphan headless worker children left by a previous
631
+ // crashed lifetime, BEFORE we mark ourselves running and start
632
+ // accepting new work. The children file from the prior daemon's
633
+ // last-snapshot is the authoritative list.
634
+ this.reapOrphanedChildren();
635
+ // #1856: detect workers that were mid-flight at the previous crash
636
+ // and count them as failures so runCount/successCount/failureCount
637
+ // stay consistent. Workers retry naturally at their next scheduled
638
+ // interval — we don't immediately re-run them, which avoids a
639
+ // freshly-recovered daemon hammering the same code path that just
640
+ // killed it.
641
+ this.detectMidFlightFailures();
394
642
  this.running = true;
395
643
  this.startedAt = new Date();
396
644
  this.writePidFile();
@@ -401,10 +649,90 @@ export class WorkerDaemon extends EventEmitter {
401
649
  this.scheduleWorker(workerConfig);
402
650
  }
403
651
  }
652
+ // #1845: poll the MCP-dispatch queue directory so workers requested
653
+ // via mcp__hooks_worker-dispatch (in a separate process) actually
654
+ // execute here. Previously the dispatch wrote to a process-local Map
655
+ // that the daemon could never see.
656
+ this.queuePollTimer = setInterval(() => {
657
+ void this.processDispatchQueue();
658
+ }, 5_000);
659
+ if (typeof this.queuePollTimer.unref === 'function') {
660
+ this.queuePollTimer.unref();
661
+ }
404
662
  // Save state
405
663
  this.saveState();
406
664
  this.log('info', `Daemon started (PID: ${process.pid}, CPUs: ${cpus().length}, workers: ${this.config.workers.filter(w => w.enabled).length}, maxCpuLoad: ${this.config.resourceThresholds.maxCpuLoad}, minFreeMemoryPercent: ${this.config.resourceThresholds.minFreeMemoryPercent}%)`);
407
665
  }
666
+ /**
667
+ * #1845: ingest queue entries written by mcp__hooks_worker-dispatch.
668
+ * Each entry is a JSON file at `.claude-flow/daemon-queue/<id>.json`
669
+ * with `{ workerId, trigger, context, enqueuedAt }`. We move processed
670
+ * files to `.claude-flow/daemon-queue/.processed/` so the daemon never
671
+ * re-runs the same dispatch and operators can inspect history.
672
+ */
673
+ async processDispatchQueue() {
674
+ if (!this.running)
675
+ return;
676
+ const queueDir = join(this.projectRoot, '.claude-flow', 'daemon-queue');
677
+ if (!existsSync(queueDir))
678
+ return;
679
+ let entries;
680
+ try {
681
+ const fs = await import('fs');
682
+ entries = fs.readdirSync(queueDir).filter((n) => n.endsWith('.json'));
683
+ }
684
+ catch {
685
+ return;
686
+ }
687
+ if (entries.length === 0)
688
+ return;
689
+ const fs = await import('fs');
690
+ const processedDir = join(queueDir, '.processed');
691
+ if (!existsSync(processedDir)) {
692
+ try {
693
+ fs.mkdirSync(processedDir, { recursive: true });
694
+ }
695
+ catch { /* race ok */ }
696
+ }
697
+ for (const entry of entries) {
698
+ const src = join(queueDir, entry);
699
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
700
+ let payload;
701
+ try {
702
+ payload = JSON.parse(fs.readFileSync(src, 'utf-8'));
703
+ }
704
+ catch {
705
+ // Malformed entry — quarantine so we don't loop on it
706
+ try {
707
+ fs.renameSync(src, join(processedDir, `bad-${entry}`));
708
+ }
709
+ catch { /* nothing more we can do */ }
710
+ continue;
711
+ }
712
+ const trigger = payload?.trigger;
713
+ const workerId = payload?.workerId;
714
+ if (!trigger || !this.config.workers.some((w) => w.type === trigger)) {
715
+ try {
716
+ fs.renameSync(src, join(processedDir, `unknown-${entry}`));
717
+ }
718
+ catch { /* ok */ }
719
+ continue;
720
+ }
721
+ try {
722
+ this.log('info', `Dequeued ${trigger}${workerId ? ` (id=${workerId})` : ''} from MCP dispatch queue`);
723
+ await this.triggerWorker(trigger);
724
+ }
725
+ catch (err) {
726
+ this.log('warn', `Queued worker ${trigger} failed: ${err.message}`);
727
+ }
728
+ finally {
729
+ try {
730
+ fs.renameSync(src, join(processedDir, entry));
731
+ }
732
+ catch { /* ignore */ }
733
+ }
734
+ }
735
+ }
408
736
  /**
409
737
  * Stop the daemon and all workers
410
738
  */
@@ -420,6 +748,11 @@ export class WorkerDaemon extends EventEmitter {
420
748
  this.log('info', `Stopped worker: ${type}`);
421
749
  }
422
750
  this.timers.clear();
751
+ // #1845: stop the MCP-dispatch queue poller too.
752
+ if (this.queuePollTimer) {
753
+ clearInterval(this.queuePollTimer);
754
+ this.queuePollTimer = undefined;
755
+ }
423
756
  this.running = false;
424
757
  this.removePidFile();
425
758
  this.saveState();
@@ -500,6 +833,8 @@ export class WorkerDaemon extends EventEmitter {
500
833
  // Track running worker
501
834
  this.runningWorkers.add(workerConfig.type);
502
835
  state.isRunning = true;
836
+ state.lastStartedAt = new Date(); // #1856: timestamp the start
837
+ this.saveState(); // persist before we run anything
503
838
  this.emit('worker:start', { workerId, type: workerConfig.type });
504
839
  this.log('info', `Starting worker: ${workerConfig.type} (${this.runningWorkers.size}/${this.config.maxConcurrent} concurrent)`);
505
840
  try {
@@ -607,10 +942,42 @@ export class WorkerDaemon extends EventEmitter {
607
942
  try {
608
943
  this.log('info', `Running ${workerConfig.type} in headless mode (Claude Code AI)`);
609
944
  const result = await this.headlessExecutor.execute(workerConfig.type);
610
- return {
611
- mode: 'headless',
612
- ...result,
613
- };
945
+ // #2110 — `HeadlessWorkerExecutor.execute()` returns
946
+ // `createErrorResult(...)` with `success: false` when
947
+ // `isAvailable()` is false, instead of throwing. The previous
948
+ // try/catch never fired in that path, and the result was
949
+ // persisted as mode:"headless" despite being a stub. Downstream
950
+ // dashboards / `memory stats` couldn't distinguish a real AI
951
+ // run from a fallback. Treat falsy success the same as throw.
952
+ const ok = result?.success === true;
953
+ if (!ok) {
954
+ const reason = result?.error ||
955
+ result?.note ||
956
+ 'headless executor reported success=false';
957
+ this.log('warn', `Headless ${workerConfig.type} returned success=false (${String(reason).slice(0, 200)}); falling back to local mode`);
958
+ this.emit('headless:fallback', {
959
+ type: workerConfig.type,
960
+ error: String(reason).slice(0, 500),
961
+ });
962
+ // Fall through to local switch.
963
+ }
964
+ else {
965
+ // #1793: persist the headless result to the same metrics files the
966
+ // local workers write to. Without this, AI-mode runs produced rich
967
+ // parsedOutput that lived only in `.claude-flow/logs/headless/*` and
968
+ // never reached `.claude-flow/metrics/<name>.json` — `memory stats`
969
+ // and downstream consumers saw nothing despite successful runs.
970
+ try {
971
+ this.persistHeadlessResult(workerConfig.type, result);
972
+ }
973
+ catch (persistError) {
974
+ this.log('warn', `Failed to persist headless result for ${workerConfig.type}: ${persistError.message}`);
975
+ }
976
+ return {
977
+ mode: 'headless',
978
+ ...result,
979
+ };
980
+ }
614
981
  }
615
982
  catch (error) {
616
983
  this.log('warn', `Headless execution failed for ${workerConfig.type}, falling back to local mode`);
@@ -651,6 +1018,53 @@ export class WorkerDaemon extends EventEmitter {
651
1018
  return { status: 'unknown worker type', mode: 'local' };
652
1019
  }
653
1020
  }
1021
+ /**
1022
+ * #1793: persist a headless worker result to the same metrics file the
1023
+ * local fallback writes to. Without this, AI-mode workers produced rich
1024
+ * structured output (audit findings, perf signals, test-gap analysis)
1025
+ * that lived only in `.claude-flow/logs/headless/*_result.log` and was
1026
+ * invisible to `npx ruflo memory stats` or the metrics consumers.
1027
+ *
1028
+ * The mapping mirrors the `*Local` worker implementations below so a
1029
+ * single consumer path works regardless of execution mode.
1030
+ */
1031
+ persistHeadlessResult(workerType, result) {
1032
+ const metricsDir = join(this.projectRoot, '.claude-flow', 'metrics');
1033
+ if (!existsSync(metricsDir))
1034
+ mkdirSync(metricsDir, { recursive: true });
1035
+ // Filename mirrors the local-mode worker writes (security-audit.json,
1036
+ // performance.json, test-gaps.json) so a downstream reader doesn't
1037
+ // care which mode produced the data.
1038
+ const filenameMap = {
1039
+ audit: 'security-audit.json',
1040
+ optimize: 'performance.json',
1041
+ testgaps: 'test-gaps.json',
1042
+ document: 'documentation.json',
1043
+ refactor: 'refactor.json',
1044
+ deepdive: 'deepdive.json',
1045
+ ultralearn: 'ultralearn.json',
1046
+ predict: 'predictions.json',
1047
+ };
1048
+ const filename = filenameMap[workerType] ?? `${workerType}.json`;
1049
+ const metricsFile = join(metricsDir, filename);
1050
+ const persisted = {
1051
+ timestamp: result.timestamp instanceof Date ? result.timestamp.toISOString() : new Date().toISOString(),
1052
+ mode: 'headless',
1053
+ workerType,
1054
+ model: result.model,
1055
+ durationMs: result.durationMs,
1056
+ tokensUsed: result.tokensUsed,
1057
+ executionId: result.executionId,
1058
+ success: result.success,
1059
+ // Structured findings live here when the worker emits JSON (e.g. the
1060
+ // audit worker's vulnerability list). Fall back to a raw-output
1061
+ // pointer so consumers can still locate the full log.
1062
+ findings: result.parsedOutput ?? null,
1063
+ rawOutputPreview: typeof result.output === 'string' ? result.output.slice(0, 2000) : undefined,
1064
+ rawOutputLength: typeof result.output === 'string' ? result.output.length : 0,
1065
+ };
1066
+ writeFileSync(metricsFile, JSON.stringify(persisted, null, 2));
1067
+ }
654
1068
  // Worker implementations
655
1069
  async runMapWorker() {
656
1070
  // Scan project structure and update metrics
@@ -893,6 +1307,7 @@ export class WorkerDaemon extends EventEmitter {
893
1307
  {
894
1308
  ...state,
895
1309
  lastRun: state.lastRun?.toISOString(),
1310
+ lastStartedAt: state.lastStartedAt?.toISOString(),
896
1311
  nextRun: state.nextRun?.toISOString(),
897
1312
  }
898
1313
  ])),