@claude-flow/cli 3.7.0-alpha.7 → 3.7.0-alpha.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/helpers/hook-handler.cjs +12 -4
- package/.claude/helpers/statusline.cjs +31 -2
- package/.claude/helpers/statusline.js +35 -4
- package/README.md +51 -34
- package/bin/cli.js +15 -2
- package/bin/mcp-server.js +1 -1
- package/dist/src/__probe.d.ts +2 -0
- package/dist/src/__probe.d.ts.map +1 -0
- package/dist/src/__probe.js +5 -0
- package/dist/src/__probe.js.map +1 -0
- package/dist/src/commands/agent-wasm.js +2 -2
- package/dist/src/commands/agent-wasm.js.map +1 -1
- package/dist/src/commands/benchmark-cosign.d.ts +29 -0
- package/dist/src/commands/benchmark-cosign.d.ts.map +1 -0
- package/dist/src/commands/benchmark-cosign.js +222 -0
- package/dist/src/commands/benchmark-cosign.js.map +1 -0
- package/dist/src/commands/benchmark-verify.d.ts +21 -0
- package/dist/src/commands/benchmark-verify.d.ts.map +1 -0
- package/dist/src/commands/benchmark-verify.js +202 -0
- package/dist/src/commands/benchmark-verify.js.map +1 -0
- package/dist/src/commands/daemon.d.ts +20 -0
- package/dist/src/commands/daemon.d.ts.map +1 -1
- package/dist/src/commands/daemon.js +366 -7
- package/dist/src/commands/daemon.js.map +1 -1
- package/dist/src/commands/doctor.d.ts.map +1 -1
- package/dist/src/commands/doctor.js +224 -46
- package/dist/src/commands/doctor.js.map +1 -1
- package/dist/src/commands/embeddings.d.ts.map +1 -1
- package/dist/src/commands/embeddings.js +18 -9
- package/dist/src/commands/embeddings.js.map +1 -1
- package/dist/src/commands/hive-mind.d.ts.map +1 -1
- package/dist/src/commands/hive-mind.js +25 -7
- package/dist/src/commands/hive-mind.js.map +1 -1
- package/dist/src/commands/hooks.d.ts.map +1 -1
- package/dist/src/commands/hooks.js +56 -29
- package/dist/src/commands/hooks.js.map +1 -1
- package/dist/src/commands/memory.d.ts.map +1 -1
- package/dist/src/commands/memory.js +104 -3
- package/dist/src/commands/memory.js.map +1 -1
- package/dist/src/commands/start.js +1 -1
- package/dist/src/commands/start.js.map +1 -1
- package/dist/src/commands/swarm.js +1 -1
- package/dist/src/commands/swarm.js.map +1 -1
- package/dist/src/commands/task.d.ts.map +1 -1
- package/dist/src/commands/task.js +8 -4
- package/dist/src/commands/task.js.map +1 -1
- package/dist/src/config-adapter.js +1 -1
- package/dist/src/config-adapter.js.map +1 -1
- package/dist/src/index.d.ts +5 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +61 -18
- package/dist/src/index.js.map +1 -1
- package/dist/src/init/executor.d.ts.map +1 -1
- package/dist/src/init/executor.js +92 -0
- package/dist/src/init/executor.js.map +1 -1
- package/dist/src/init/helpers-generator.d.ts.map +1 -1
- package/dist/src/init/helpers-generator.js +6 -2
- package/dist/src/init/helpers-generator.js.map +1 -1
- package/dist/src/init/mcp-generator.js +4 -4
- package/dist/src/init/mcp-generator.js.map +1 -1
- package/dist/src/init/settings-generator.d.ts.map +1 -1
- package/dist/src/init/settings-generator.js +78 -19
- package/dist/src/init/settings-generator.js.map +1 -1
- package/dist/src/init/statusline-generator.d.ts.map +1 -1
- package/dist/src/init/statusline-generator.js +75 -31
- package/dist/src/init/statusline-generator.js.map +1 -1
- package/dist/src/init/types.d.ts +7 -0
- package/dist/src/init/types.d.ts.map +1 -1
- package/dist/src/init/types.js.map +1 -1
- package/dist/src/mcp-client.d.ts.map +1 -1
- package/dist/src/mcp-client.js +12 -0
- package/dist/src/mcp-client.js.map +1 -1
- package/dist/src/mcp-server.d.ts.map +1 -1
- package/dist/src/mcp-server.js +38 -5
- package/dist/src/mcp-server.js.map +1 -1
- package/dist/src/mcp-tools/agent-execute-core.d.ts +3 -2
- package/dist/src/mcp-tools/agent-execute-core.d.ts.map +1 -1
- package/dist/src/mcp-tools/agent-execute-core.js +16 -9
- package/dist/src/mcp-tools/agent-execute-core.js.map +1 -1
- package/dist/src/mcp-tools/agent-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/agent-tools.js +88 -11
- package/dist/src/mcp-tools/agent-tools.js.map +1 -1
- package/dist/src/mcp-tools/agentdb-tools.d.ts +3 -0
- package/dist/src/mcp-tools/agentdb-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/agentdb-tools.js +206 -21
- package/dist/src/mcp-tools/agentdb-tools.js.map +1 -1
- package/dist/src/mcp-tools/analyze-tools.js +6 -6
- package/dist/src/mcp-tools/analyze-tools.js.map +1 -1
- package/dist/src/mcp-tools/autopilot-tools.js +10 -10
- package/dist/src/mcp-tools/autopilot-tools.js.map +1 -1
- package/dist/src/mcp-tools/browser-session-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/browser-session-tools.js +18 -7
- package/dist/src/mcp-tools/browser-session-tools.js.map +1 -1
- package/dist/src/mcp-tools/browser-tools.js +23 -23
- package/dist/src/mcp-tools/browser-tools.js.map +1 -1
- package/dist/src/mcp-tools/claims-tools.js +12 -12
- package/dist/src/mcp-tools/claims-tools.js.map +1 -1
- package/dist/src/mcp-tools/config-tools.js +6 -6
- package/dist/src/mcp-tools/config-tools.js.map +1 -1
- package/dist/src/mcp-tools/coordination-tools.js +7 -7
- package/dist/src/mcp-tools/coordination-tools.js.map +1 -1
- package/dist/src/mcp-tools/daa-tools.js +8 -8
- package/dist/src/mcp-tools/daa-tools.js.map +1 -1
- package/dist/src/mcp-tools/embeddings-tools.js +10 -10
- package/dist/src/mcp-tools/embeddings-tools.js.map +1 -1
- package/dist/src/mcp-tools/github-tools.js +5 -5
- package/dist/src/mcp-tools/github-tools.js.map +1 -1
- package/dist/src/mcp-tools/guidance-tools.js +21 -21
- package/dist/src/mcp-tools/guidance-tools.js.map +1 -1
- package/dist/src/mcp-tools/hive-consensus-runtime.d.ts +149 -0
- package/dist/src/mcp-tools/hive-consensus-runtime.d.ts.map +1 -0
- package/dist/src/mcp-tools/hive-consensus-runtime.js +296 -0
- package/dist/src/mcp-tools/hive-consensus-runtime.js.map +1 -0
- package/dist/src/mcp-tools/hive-mind-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/hive-mind-tools.js +53 -9
- package/dist/src/mcp-tools/hive-mind-tools.js.map +1 -1
- package/dist/src/mcp-tools/hooks-tools.d.ts +2 -0
- package/dist/src/mcp-tools/hooks-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/hooks-tools.js +179 -46
- package/dist/src/mcp-tools/hooks-tools.js.map +1 -1
- package/dist/src/mcp-tools/managed-agent-tools.d.ts +22 -0
- package/dist/src/mcp-tools/managed-agent-tools.d.ts.map +1 -0
- package/dist/src/mcp-tools/managed-agent-tools.js +357 -0
- package/dist/src/mcp-tools/managed-agent-tools.js.map +1 -0
- package/dist/src/mcp-tools/memory-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/memory-tools.js +490 -68
- package/dist/src/mcp-tools/memory-tools.js.map +1 -1
- package/dist/src/mcp-tools/neural-tools.js +6 -6
- package/dist/src/mcp-tools/neural-tools.js.map +1 -1
- package/dist/src/mcp-tools/performance-tools.js +6 -6
- package/dist/src/mcp-tools/performance-tools.js.map +1 -1
- package/dist/src/mcp-tools/progress-tools.js +4 -4
- package/dist/src/mcp-tools/progress-tools.js.map +1 -1
- package/dist/src/mcp-tools/ruvllm-tools.js +10 -10
- package/dist/src/mcp-tools/ruvllm-tools.js.map +1 -1
- package/dist/src/mcp-tools/security-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/security-tools.js +34 -9
- package/dist/src/mcp-tools/security-tools.js.map +1 -1
- package/dist/src/mcp-tools/session-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/session-tools.js +130 -6
- package/dist/src/mcp-tools/session-tools.js.map +1 -1
- package/dist/src/mcp-tools/swarm-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/swarm-tools.js +76 -7
- package/dist/src/mcp-tools/swarm-tools.js.map +1 -1
- package/dist/src/mcp-tools/system-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/system-tools.js +91 -18
- package/dist/src/mcp-tools/system-tools.js.map +1 -1
- package/dist/src/mcp-tools/task-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/task-tools.js +55 -7
- package/dist/src/mcp-tools/task-tools.js.map +1 -1
- package/dist/src/mcp-tools/terminal-tools.js +5 -5
- package/dist/src/mcp-tools/terminal-tools.js.map +1 -1
- package/dist/src/mcp-tools/transfer-tools.js +11 -11
- package/dist/src/mcp-tools/transfer-tools.js.map +1 -1
- package/dist/src/mcp-tools/wasm-agent-tools.js +11 -11
- package/dist/src/mcp-tools/wasm-agent-tools.js.map +1 -1
- package/dist/src/mcp-tools/workflow-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/workflow-tools.js +118 -10
- package/dist/src/mcp-tools/workflow-tools.js.map +1 -1
- package/dist/src/memory/ann-router-registry.d.ts +61 -0
- package/dist/src/memory/ann-router-registry.d.ts.map +1 -0
- package/dist/src/memory/ann-router-registry.js +72 -0
- package/dist/src/memory/ann-router-registry.js.map +1 -0
- package/dist/src/memory/diskann-registry.d.ts +56 -0
- package/dist/src/memory/diskann-registry.d.ts.map +1 -0
- package/dist/src/memory/diskann-registry.js +88 -0
- package/dist/src/memory/diskann-registry.js.map +1 -0
- package/dist/src/memory/memory-bridge.d.ts +69 -0
- package/dist/src/memory/memory-bridge.d.ts.map +1 -1
- package/dist/src/memory/memory-bridge.js +293 -5
- package/dist/src/memory/memory-bridge.js.map +1 -1
- package/dist/src/memory/memory-initializer.d.ts +8 -0
- package/dist/src/memory/memory-initializer.d.ts.map +1 -1
- package/dist/src/memory/memory-initializer.js +89 -16
- package/dist/src/memory/memory-initializer.js.map +1 -1
- package/dist/src/memory/sona-optimizer.d.ts.map +1 -1
- package/dist/src/memory/sona-optimizer.js +3 -0
- package/dist/src/memory/sona-optimizer.js.map +1 -1
- package/dist/src/parser.d.ts +9 -0
- package/dist/src/parser.d.ts.map +1 -1
- package/dist/src/parser.js +11 -0
- package/dist/src/parser.js.map +1 -1
- package/dist/src/plugins/store/discovery.d.ts +15 -4
- package/dist/src/plugins/store/discovery.d.ts.map +1 -1
- package/dist/src/plugins/store/discovery.js +40 -18
- package/dist/src/plugins/store/discovery.js.map +1 -1
- package/dist/src/ruvector/agent-wasm.d.ts.map +1 -1
- package/dist/src/ruvector/agent-wasm.js +4 -1
- package/dist/src/ruvector/agent-wasm.js.map +1 -1
- package/dist/src/ruvector/coverage-tools.js +6 -6
- package/dist/src/ruvector/coverage-tools.js.map +1 -1
- package/dist/src/services/headless-worker-executor.d.ts +6 -0
- package/dist/src/services/headless-worker-executor.d.ts.map +1 -1
- package/dist/src/services/headless-worker-executor.js +37 -3
- package/dist/src/services/headless-worker-executor.js.map +1 -1
- package/dist/src/services/worker-daemon.d.ts +80 -2
- package/dist/src/services/worker-daemon.d.ts.map +1 -1
- package/dist/src/services/worker-daemon.js +372 -11
- package/dist/src/services/worker-daemon.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -8
- package/.claude/skills/agentdb-advanced/SKILL.md +0 -550
- package/.claude/skills/agentdb-learning/SKILL.md +0 -545
- package/.claude/skills/agentdb-memory-patterns/SKILL.md +0 -339
- package/.claude/skills/agentdb-optimization/SKILL.md +0 -509
- package/.claude/skills/agentdb-vector-search/SKILL.md +0 -339
- package/.claude/skills/agentic-jujutsu/SKILL.md +0 -645
- package/.claude/skills/aidefence-scan.md +0 -151
- package/.claude/skills/aidefence.yaml +0 -297
- package/.claude/skills/browser/SKILL.md +0 -204
- package/.claude/skills/flow-nexus-neural/SKILL.md +0 -738
- package/.claude/skills/flow-nexus-platform/SKILL.md +0 -1157
- package/.claude/skills/flow-nexus-swarm/SKILL.md +0 -610
- package/.claude/skills/github-code-review/SKILL.md +0 -1140
- package/.claude/skills/github-multi-repo/SKILL.md +0 -874
- package/.claude/skills/github-project-management/SKILL.md +0 -1277
- package/.claude/skills/github-release-management/SKILL.md +0 -1081
- package/.claude/skills/github-workflow-automation/SKILL.md +0 -1065
- package/.claude/skills/hive-mind-advanced/SKILL.md +0 -712
- package/.claude/skills/hooks-automation/SKILL.md +0 -1201
- package/.claude/skills/pair-programming/SKILL.md +0 -1202
- package/.claude/skills/performance-analysis/SKILL.md +0 -563
- package/.claude/skills/reasoningbank-agentdb/SKILL.md +0 -446
- package/.claude/skills/reasoningbank-intelligence/SKILL.md +0 -201
- package/.claude/skills/secure-review.md +0 -181
- package/.claude/skills/skill-builder/SKILL.md +0 -910
- package/.claude/skills/sparc-methodology/SKILL.md +0 -1115
- package/.claude/skills/stream-chain/SKILL.md +0 -563
- package/.claude/skills/swarm-advanced/SKILL.md +0 -973
- package/.claude/skills/swarm-orchestration/SKILL.md +0 -179
- package/.claude/skills/v3-cli-modernization/SKILL.md +0 -872
- package/.claude/skills/v3-core-implementation/SKILL.md +0 -797
- package/.claude/skills/v3-ddd-architecture/SKILL.md +0 -442
- package/.claude/skills/v3-integration-deep/SKILL.md +0 -241
- package/.claude/skills/v3-mcp-optimization/SKILL.md +0 -777
- package/.claude/skills/v3-memory-unification/SKILL.md +0 -174
- package/.claude/skills/v3-performance-optimization/SKILL.md +0 -390
- package/.claude/skills/v3-security-overhaul/SKILL.md +0 -82
- package/.claude/skills/v3-swarm-coordination/SKILL.md +0 -340
- package/.claude/skills/verification-quality/SKILL.md +0 -649
- package/.claude/skills/worker-benchmarks/skill.md +0 -135
- package/.claude/skills/worker-integration/skill.md +0 -154
- package/dist/src/ruvector/flash-attention.d.ts +0 -195
- package/dist/src/ruvector/flash-attention.d.ts.map +0 -1
- package/dist/src/ruvector/flash-attention.js +0 -643
- package/dist/src/ruvector/flash-attention.js.map +0 -1
- package/dist/src/ruvector/moe-router.d.ts +0 -206
- package/dist/src/ruvector/moe-router.d.ts.map +0 -1
- package/dist/src/ruvector/moe-router.js +0 -626
- package/dist/src/ruvector/moe-router.js.map +0 -1
- package/dist/src/services/event-stream.d.ts +0 -25
- package/dist/src/services/event-stream.d.ts.map +0 -1
- package/dist/src/services/event-stream.js +0 -27
- package/dist/src/services/event-stream.js.map +0 -1
- package/dist/src/services/loop-worker-runner.d.ts +0 -16
- package/dist/src/services/loop-worker-runner.d.ts.map +0 -1
- package/dist/src/services/loop-worker-runner.js +0 -34
- package/dist/src/services/loop-worker-runner.js.map +0 -1
- package/dist/src/services/runtime-capabilities.d.ts +0 -22
- package/dist/src/services/runtime-capabilities.d.ts.map +0 -1
- package/dist/src/services/runtime-capabilities.js +0 -45
- 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;
|
|
@@ -76,6 +79,9 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
76
79
|
};
|
|
77
80
|
// Setup graceful shutdown handlers
|
|
78
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();
|
|
79
85
|
// Ensure directories exist
|
|
80
86
|
if (!existsSync(claudeFlowDir)) {
|
|
81
87
|
mkdirSync(claudeFlowDir, { recursive: true });
|
|
@@ -101,14 +107,19 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
101
107
|
this.headlessAvailable = await this.headlessExecutor.isAvailable();
|
|
102
108
|
if (this.headlessAvailable) {
|
|
103
109
|
this.log('info', 'Claude Code headless mode available - AI workers enabled');
|
|
104
|
-
// 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.
|
|
105
113
|
this.headlessExecutor.on('execution:start', (data) => {
|
|
114
|
+
this.writeChildrenSnapshot();
|
|
106
115
|
this.emit('headless:start', data);
|
|
107
116
|
});
|
|
108
117
|
this.headlessExecutor.on('execution:complete', (data) => {
|
|
118
|
+
this.writeChildrenSnapshot();
|
|
109
119
|
this.emit('headless:complete', data);
|
|
110
120
|
});
|
|
111
121
|
this.headlessExecutor.on('execution:error', (data) => {
|
|
122
|
+
this.writeChildrenSnapshot();
|
|
112
123
|
this.emit('headless:error', data);
|
|
113
124
|
});
|
|
114
125
|
this.headlessExecutor.on('output', (data) => {
|
|
@@ -169,22 +180,52 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
169
180
|
return cpus().length || 1;
|
|
170
181
|
}
|
|
171
182
|
/**
|
|
172
|
-
* Read daemon-specific config from .claude-flow/config.json
|
|
173
|
-
* Supports dot-notation keys like 'daemon.resourceThresholds.maxCpuLoad'
|
|
183
|
+
* Read daemon-specific config from .claude-flow/config.{json,yaml,yml}.
|
|
184
|
+
* Supports dot-notation keys like 'daemon.resourceThresholds.maxCpuLoad'.
|
|
185
|
+
* #1844: prefer JSON when both exist (existing behavior) but fall back
|
|
186
|
+
* to YAML so operators using the v3 canonical YAML format aren't silently
|
|
187
|
+
* ignored. The chosen path is logged at info level.
|
|
174
188
|
*/
|
|
175
189
|
readDaemonConfigFromFile(claudeFlowDir) {
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
190
|
+
const jsonPath = join(claudeFlowDir, 'config.json');
|
|
191
|
+
const yamlPath = join(claudeFlowDir, 'config.yaml');
|
|
192
|
+
const ymlPath = join(claudeFlowDir, 'config.yml');
|
|
193
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
194
|
+
let raw;
|
|
195
|
+
let chosenPath;
|
|
196
|
+
if (existsSync(jsonPath)) {
|
|
197
|
+
try {
|
|
198
|
+
raw = JSON.parse(readFileSync(jsonPath, 'utf-8'));
|
|
199
|
+
chosenPath = jsonPath;
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return {};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else if (existsSync(yamlPath) || existsSync(ymlPath)) {
|
|
206
|
+
const yPath = existsSync(yamlPath) ? yamlPath : ymlPath;
|
|
207
|
+
try {
|
|
208
|
+
// Lazy-load yaml so the daemon doesn't hard-require it; if the
|
|
209
|
+
// dep isn't installed, fall back to the previous warn-only path.
|
|
210
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
211
|
+
const yamlMod = require('yaml');
|
|
212
|
+
const parsed = yamlMod.parse(readFileSync(yPath, 'utf-8'));
|
|
213
|
+
if (parsed && typeof parsed === 'object') {
|
|
214
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
215
|
+
raw = parsed;
|
|
216
|
+
chosenPath = yPath;
|
|
217
|
+
}
|
|
183
218
|
}
|
|
219
|
+
catch {
|
|
220
|
+
this.log('warn', `Found ${yPath} but yaml parser unavailable. Install \`yaml\` or convert to JSON. Falling back to defaults.`);
|
|
221
|
+
return {};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (!raw || !chosenPath) {
|
|
184
225
|
return {};
|
|
185
226
|
}
|
|
227
|
+
this.log('info', `Daemon config loaded from ${chosenPath}`);
|
|
186
228
|
try {
|
|
187
|
-
const raw = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
188
229
|
// Support both flat keys at root and nested under scopes.project
|
|
189
230
|
const cfg = raw?.scopes?.project ?? raw;
|
|
190
231
|
const rawCpuLoad = cfg['daemon.resourceThresholds.maxCpuLoad'] ?? raw['daemon.resourceThresholds.maxCpuLoad'];
|
|
@@ -216,6 +257,153 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
216
257
|
process.on('SIGINT', shutdown);
|
|
217
258
|
process.on('SIGHUP', shutdown);
|
|
218
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
|
+
* #1856: detect workers that were mid-flight when the previous daemon
|
|
320
|
+
* lifetime ended. A mid-flight worker has `lastStartedAt > lastRun`
|
|
321
|
+
* (started after the last successful completion). On crash recovery
|
|
322
|
+
* we count these as failures so the run-counter math stays consistent
|
|
323
|
+
* (`runCount === successCount + failureCount`). Workers naturally
|
|
324
|
+
* retry at their next scheduled interval; we deliberately don't
|
|
325
|
+
* immediately re-run because the failure may have been deterministic.
|
|
326
|
+
*/
|
|
327
|
+
detectMidFlightFailures() {
|
|
328
|
+
let detected = 0;
|
|
329
|
+
for (const [type, state] of this.workers.entries()) {
|
|
330
|
+
const startedAt = state.lastStartedAt?.getTime() ?? 0;
|
|
331
|
+
const lastRunAt = state.lastRun?.getTime() ?? 0;
|
|
332
|
+
// started after the last successful completion → was mid-flight
|
|
333
|
+
if (startedAt > 0 && startedAt > lastRunAt) {
|
|
334
|
+
state.failureCount++;
|
|
335
|
+
state.isRunning = false;
|
|
336
|
+
// Don't bump runCount — it was already incremented at start
|
|
337
|
+
this.log('info', `Worker ${type} was mid-flight at last crash (started ${state.lastStartedAt?.toISOString()}); counted as failure, will retry at next scheduled interval`);
|
|
338
|
+
detected++;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (detected > 0) {
|
|
342
|
+
this.saveState();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Snapshot the currently-active headless worker child PIDs to disk.
|
|
347
|
+
* Best-effort; failures don't propagate.
|
|
348
|
+
*/
|
|
349
|
+
writeChildrenSnapshot() {
|
|
350
|
+
if (!this.headlessExecutor)
|
|
351
|
+
return;
|
|
352
|
+
try {
|
|
353
|
+
const pids = this.headlessExecutor.getActiveChildPids();
|
|
354
|
+
const dir = join(this.projectRoot, '.claude-flow');
|
|
355
|
+
if (!existsSync(dir))
|
|
356
|
+
mkdirSync(dir, { recursive: true });
|
|
357
|
+
writeFileSync(this.childrenFile, JSON.stringify({ pids, daemonPid: process.pid, timestamp: new Date().toISOString() }, null, 2), 'utf-8');
|
|
358
|
+
}
|
|
359
|
+
catch { /* best-effort */ }
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* #1855: reap orphan headless worker children left behind by a
|
|
363
|
+
* previous crashed lifetime. Reads `.claude-flow/daemon-children.json`,
|
|
364
|
+
* SIGTERMs any PID still alive that doesn't belong to the current
|
|
365
|
+
* daemon, then truncates the file. Called at the top of `start()`
|
|
366
|
+
* so the next lifetime starts with a clean process tree.
|
|
367
|
+
*/
|
|
368
|
+
reapOrphanedChildren() {
|
|
369
|
+
const file = this.childrenFile;
|
|
370
|
+
if (!existsSync(file))
|
|
371
|
+
return;
|
|
372
|
+
let snapshot;
|
|
373
|
+
try {
|
|
374
|
+
snapshot = JSON.parse(readFileSync(file, 'utf-8'));
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
try {
|
|
378
|
+
unlinkSync(file);
|
|
379
|
+
}
|
|
380
|
+
catch { /* ignore */ }
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
const pids = Array.isArray(snapshot.pids) ? snapshot.pids : [];
|
|
384
|
+
let reaped = 0;
|
|
385
|
+
for (const pid of pids) {
|
|
386
|
+
if (typeof pid !== 'number' || pid <= 0)
|
|
387
|
+
continue;
|
|
388
|
+
if (pid === process.pid)
|
|
389
|
+
continue; // never our own PID
|
|
390
|
+
try {
|
|
391
|
+
process.kill(pid, 0); // is alive?
|
|
392
|
+
process.kill(pid, 'SIGTERM');
|
|
393
|
+
reaped++;
|
|
394
|
+
}
|
|
395
|
+
catch {
|
|
396
|
+
// already dead — fine
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (reaped > 0) {
|
|
400
|
+
this.log('info', `Reaped ${reaped} orphan headless worker child(ren) from previous lifetime`);
|
|
401
|
+
}
|
|
402
|
+
try {
|
|
403
|
+
unlinkSync(file);
|
|
404
|
+
}
|
|
405
|
+
catch { /* ignore */ }
|
|
406
|
+
}
|
|
219
407
|
/**
|
|
220
408
|
* Check if system resources allow worker execution
|
|
221
409
|
*/
|
|
@@ -299,12 +487,14 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
299
487
|
for (const [type, state] of Object.entries(saved.workers)) {
|
|
300
488
|
const savedState = state;
|
|
301
489
|
const lastRunValue = savedState.lastRun;
|
|
490
|
+
const lastStartedAtValue = savedState.lastStartedAt;
|
|
302
491
|
this.workers.set(type, {
|
|
303
492
|
runCount: savedState.runCount || 0,
|
|
304
493
|
successCount: savedState.successCount || 0,
|
|
305
494
|
failureCount: savedState.failureCount || 0,
|
|
306
495
|
averageDurationMs: savedState.averageDurationMs || 0,
|
|
307
496
|
lastRun: lastRunValue ? new Date(lastRunValue) : undefined,
|
|
497
|
+
lastStartedAt: lastStartedAtValue ? new Date(lastStartedAtValue) : undefined,
|
|
308
498
|
nextRun: undefined,
|
|
309
499
|
isRunning: false,
|
|
310
500
|
});
|
|
@@ -337,6 +527,15 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
337
527
|
/**
|
|
338
528
|
* Check if another daemon instance is already running.
|
|
339
529
|
* Returns the existing PID if alive, or null if no daemon is running.
|
|
530
|
+
*
|
|
531
|
+
* #1853: ignore self-PID matches. The detached-spawn path in
|
|
532
|
+
* `commands/daemon.ts` writes the child's PID into the file as a
|
|
533
|
+
* fallback after a 500ms wait. If the child reaches `start()` slower
|
|
534
|
+
* than the parent's 500ms wait (observed on Node 25 / macOS 26), the
|
|
535
|
+
* child reads its own PID back from the file and concludes "another
|
|
536
|
+
* daemon is already running" — so it exits before scheduling workers
|
|
537
|
+
* and `daemon status` reports STOPPED forever. A daemon process is
|
|
538
|
+
* never "another instance" of itself; treat self-match as absence.
|
|
340
539
|
*/
|
|
341
540
|
checkExistingDaemon() {
|
|
342
541
|
if (!existsSync(this.pidFile))
|
|
@@ -345,6 +544,10 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
345
544
|
const pid = parseInt(readFileSync(this.pidFile, 'utf-8').trim(), 10);
|
|
346
545
|
if (isNaN(pid))
|
|
347
546
|
return null;
|
|
547
|
+
// #1853: a PID file containing our own PID is not "another daemon".
|
|
548
|
+
// Treat as absent so the start() path proceeds normally.
|
|
549
|
+
if (pid === process.pid)
|
|
550
|
+
return null;
|
|
348
551
|
// Check if process is alive (signal 0 = existence check)
|
|
349
552
|
process.kill(pid, 0);
|
|
350
553
|
return pid; // Process is alive
|
|
@@ -391,6 +594,18 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
391
594
|
this.emit('warning', `Daemon already running (PID: ${existingPid})`);
|
|
392
595
|
return;
|
|
393
596
|
}
|
|
597
|
+
// #1855: reap orphan headless worker children left by a previous
|
|
598
|
+
// crashed lifetime, BEFORE we mark ourselves running and start
|
|
599
|
+
// accepting new work. The children file from the prior daemon's
|
|
600
|
+
// last-snapshot is the authoritative list.
|
|
601
|
+
this.reapOrphanedChildren();
|
|
602
|
+
// #1856: detect workers that were mid-flight at the previous crash
|
|
603
|
+
// and count them as failures so runCount/successCount/failureCount
|
|
604
|
+
// stay consistent. Workers retry naturally at their next scheduled
|
|
605
|
+
// interval — we don't immediately re-run them, which avoids a
|
|
606
|
+
// freshly-recovered daemon hammering the same code path that just
|
|
607
|
+
// killed it.
|
|
608
|
+
this.detectMidFlightFailures();
|
|
394
609
|
this.running = true;
|
|
395
610
|
this.startedAt = new Date();
|
|
396
611
|
this.writePidFile();
|
|
@@ -401,10 +616,90 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
401
616
|
this.scheduleWorker(workerConfig);
|
|
402
617
|
}
|
|
403
618
|
}
|
|
619
|
+
// #1845: poll the MCP-dispatch queue directory so workers requested
|
|
620
|
+
// via mcp__hooks_worker-dispatch (in a separate process) actually
|
|
621
|
+
// execute here. Previously the dispatch wrote to a process-local Map
|
|
622
|
+
// that the daemon could never see.
|
|
623
|
+
this.queuePollTimer = setInterval(() => {
|
|
624
|
+
void this.processDispatchQueue();
|
|
625
|
+
}, 5_000);
|
|
626
|
+
if (typeof this.queuePollTimer.unref === 'function') {
|
|
627
|
+
this.queuePollTimer.unref();
|
|
628
|
+
}
|
|
404
629
|
// Save state
|
|
405
630
|
this.saveState();
|
|
406
631
|
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
632
|
}
|
|
633
|
+
/**
|
|
634
|
+
* #1845: ingest queue entries written by mcp__hooks_worker-dispatch.
|
|
635
|
+
* Each entry is a JSON file at `.claude-flow/daemon-queue/<id>.json`
|
|
636
|
+
* with `{ workerId, trigger, context, enqueuedAt }`. We move processed
|
|
637
|
+
* files to `.claude-flow/daemon-queue/.processed/` so the daemon never
|
|
638
|
+
* re-runs the same dispatch and operators can inspect history.
|
|
639
|
+
*/
|
|
640
|
+
async processDispatchQueue() {
|
|
641
|
+
if (!this.running)
|
|
642
|
+
return;
|
|
643
|
+
const queueDir = join(this.projectRoot, '.claude-flow', 'daemon-queue');
|
|
644
|
+
if (!existsSync(queueDir))
|
|
645
|
+
return;
|
|
646
|
+
let entries;
|
|
647
|
+
try {
|
|
648
|
+
const fs = await import('fs');
|
|
649
|
+
entries = fs.readdirSync(queueDir).filter((n) => n.endsWith('.json'));
|
|
650
|
+
}
|
|
651
|
+
catch {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (entries.length === 0)
|
|
655
|
+
return;
|
|
656
|
+
const fs = await import('fs');
|
|
657
|
+
const processedDir = join(queueDir, '.processed');
|
|
658
|
+
if (!existsSync(processedDir)) {
|
|
659
|
+
try {
|
|
660
|
+
fs.mkdirSync(processedDir, { recursive: true });
|
|
661
|
+
}
|
|
662
|
+
catch { /* race ok */ }
|
|
663
|
+
}
|
|
664
|
+
for (const entry of entries) {
|
|
665
|
+
const src = join(queueDir, entry);
|
|
666
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
667
|
+
let payload;
|
|
668
|
+
try {
|
|
669
|
+
payload = JSON.parse(fs.readFileSync(src, 'utf-8'));
|
|
670
|
+
}
|
|
671
|
+
catch {
|
|
672
|
+
// Malformed entry — quarantine so we don't loop on it
|
|
673
|
+
try {
|
|
674
|
+
fs.renameSync(src, join(processedDir, `bad-${entry}`));
|
|
675
|
+
}
|
|
676
|
+
catch { /* nothing more we can do */ }
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
const trigger = payload?.trigger;
|
|
680
|
+
const workerId = payload?.workerId;
|
|
681
|
+
if (!trigger || !this.config.workers.some((w) => w.type === trigger)) {
|
|
682
|
+
try {
|
|
683
|
+
fs.renameSync(src, join(processedDir, `unknown-${entry}`));
|
|
684
|
+
}
|
|
685
|
+
catch { /* ok */ }
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
try {
|
|
689
|
+
this.log('info', `Dequeued ${trigger}${workerId ? ` (id=${workerId})` : ''} from MCP dispatch queue`);
|
|
690
|
+
await this.triggerWorker(trigger);
|
|
691
|
+
}
|
|
692
|
+
catch (err) {
|
|
693
|
+
this.log('warn', `Queued worker ${trigger} failed: ${err.message}`);
|
|
694
|
+
}
|
|
695
|
+
finally {
|
|
696
|
+
try {
|
|
697
|
+
fs.renameSync(src, join(processedDir, entry));
|
|
698
|
+
}
|
|
699
|
+
catch { /* ignore */ }
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
408
703
|
/**
|
|
409
704
|
* Stop the daemon and all workers
|
|
410
705
|
*/
|
|
@@ -420,6 +715,11 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
420
715
|
this.log('info', `Stopped worker: ${type}`);
|
|
421
716
|
}
|
|
422
717
|
this.timers.clear();
|
|
718
|
+
// #1845: stop the MCP-dispatch queue poller too.
|
|
719
|
+
if (this.queuePollTimer) {
|
|
720
|
+
clearInterval(this.queuePollTimer);
|
|
721
|
+
this.queuePollTimer = undefined;
|
|
722
|
+
}
|
|
423
723
|
this.running = false;
|
|
424
724
|
this.removePidFile();
|
|
425
725
|
this.saveState();
|
|
@@ -500,6 +800,8 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
500
800
|
// Track running worker
|
|
501
801
|
this.runningWorkers.add(workerConfig.type);
|
|
502
802
|
state.isRunning = true;
|
|
803
|
+
state.lastStartedAt = new Date(); // #1856: timestamp the start
|
|
804
|
+
this.saveState(); // persist before we run anything
|
|
503
805
|
this.emit('worker:start', { workerId, type: workerConfig.type });
|
|
504
806
|
this.log('info', `Starting worker: ${workerConfig.type} (${this.runningWorkers.size}/${this.config.maxConcurrent} concurrent)`);
|
|
505
807
|
try {
|
|
@@ -607,6 +909,17 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
607
909
|
try {
|
|
608
910
|
this.log('info', `Running ${workerConfig.type} in headless mode (Claude Code AI)`);
|
|
609
911
|
const result = await this.headlessExecutor.execute(workerConfig.type);
|
|
912
|
+
// #1793: persist the headless result to the same metrics files the
|
|
913
|
+
// local workers write to. Without this, AI-mode runs produced rich
|
|
914
|
+
// parsedOutput that lived only in `.claude-flow/logs/headless/*` and
|
|
915
|
+
// never reached `.claude-flow/metrics/<name>.json` — `memory stats`
|
|
916
|
+
// and downstream consumers saw nothing despite successful runs.
|
|
917
|
+
try {
|
|
918
|
+
this.persistHeadlessResult(workerConfig.type, result);
|
|
919
|
+
}
|
|
920
|
+
catch (persistError) {
|
|
921
|
+
this.log('warn', `Failed to persist headless result for ${workerConfig.type}: ${persistError.message}`);
|
|
922
|
+
}
|
|
610
923
|
return {
|
|
611
924
|
mode: 'headless',
|
|
612
925
|
...result,
|
|
@@ -651,6 +964,53 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
651
964
|
return { status: 'unknown worker type', mode: 'local' };
|
|
652
965
|
}
|
|
653
966
|
}
|
|
967
|
+
/**
|
|
968
|
+
* #1793: persist a headless worker result to the same metrics file the
|
|
969
|
+
* local fallback writes to. Without this, AI-mode workers produced rich
|
|
970
|
+
* structured output (audit findings, perf signals, test-gap analysis)
|
|
971
|
+
* that lived only in `.claude-flow/logs/headless/*_result.log` and was
|
|
972
|
+
* invisible to `npx ruflo memory stats` or the metrics consumers.
|
|
973
|
+
*
|
|
974
|
+
* The mapping mirrors the `*Local` worker implementations below so a
|
|
975
|
+
* single consumer path works regardless of execution mode.
|
|
976
|
+
*/
|
|
977
|
+
persistHeadlessResult(workerType, result) {
|
|
978
|
+
const metricsDir = join(this.projectRoot, '.claude-flow', 'metrics');
|
|
979
|
+
if (!existsSync(metricsDir))
|
|
980
|
+
mkdirSync(metricsDir, { recursive: true });
|
|
981
|
+
// Filename mirrors the local-mode worker writes (security-audit.json,
|
|
982
|
+
// performance.json, test-gaps.json) so a downstream reader doesn't
|
|
983
|
+
// care which mode produced the data.
|
|
984
|
+
const filenameMap = {
|
|
985
|
+
audit: 'security-audit.json',
|
|
986
|
+
optimize: 'performance.json',
|
|
987
|
+
testgaps: 'test-gaps.json',
|
|
988
|
+
document: 'documentation.json',
|
|
989
|
+
refactor: 'refactor.json',
|
|
990
|
+
deepdive: 'deepdive.json',
|
|
991
|
+
ultralearn: 'ultralearn.json',
|
|
992
|
+
predict: 'predictions.json',
|
|
993
|
+
};
|
|
994
|
+
const filename = filenameMap[workerType] ?? `${workerType}.json`;
|
|
995
|
+
const metricsFile = join(metricsDir, filename);
|
|
996
|
+
const persisted = {
|
|
997
|
+
timestamp: result.timestamp instanceof Date ? result.timestamp.toISOString() : new Date().toISOString(),
|
|
998
|
+
mode: 'headless',
|
|
999
|
+
workerType,
|
|
1000
|
+
model: result.model,
|
|
1001
|
+
durationMs: result.durationMs,
|
|
1002
|
+
tokensUsed: result.tokensUsed,
|
|
1003
|
+
executionId: result.executionId,
|
|
1004
|
+
success: result.success,
|
|
1005
|
+
// Structured findings live here when the worker emits JSON (e.g. the
|
|
1006
|
+
// audit worker's vulnerability list). Fall back to a raw-output
|
|
1007
|
+
// pointer so consumers can still locate the full log.
|
|
1008
|
+
findings: result.parsedOutput ?? null,
|
|
1009
|
+
rawOutputPreview: typeof result.output === 'string' ? result.output.slice(0, 2000) : undefined,
|
|
1010
|
+
rawOutputLength: typeof result.output === 'string' ? result.output.length : 0,
|
|
1011
|
+
};
|
|
1012
|
+
writeFileSync(metricsFile, JSON.stringify(persisted, null, 2));
|
|
1013
|
+
}
|
|
654
1014
|
// Worker implementations
|
|
655
1015
|
async runMapWorker() {
|
|
656
1016
|
// Scan project structure and update metrics
|
|
@@ -893,6 +1253,7 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
893
1253
|
{
|
|
894
1254
|
...state,
|
|
895
1255
|
lastRun: state.lastRun?.toISOString(),
|
|
1256
|
+
lastStartedAt: state.lastStartedAt?.toISOString(),
|
|
896
1257
|
nextRun: state.nextRun?.toISOString(),
|
|
897
1258
|
}
|
|
898
1259
|
])),
|